diff --git a/.circleci/before-script.sh b/.circleci/before-script.sh deleted file mode 100644 index 3b9c7949cd4..00000000000 --- a/.circleci/before-script.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/sh - -# Source this script during a CI run to set environment variables and print -# some informational messages about the system we are running on. - -# **************************************************************************** -# Copyright (C) 2018 Julian Rüth -# -# 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/ -# **************************************************************************** - -# CircleCI has no mechanism to hide secret variables. -# Therefore we roll our own to protect $SECRET_* variables. -. .ci/protect-secrets.sh -# Collect debug infos about the system we are running on -.ci/describe-system.sh -# Set MAKEFLAGS and SAGE_NUM_THREADS -. .ci/setup-make-parallelity.sh - -# Set DOCKER_TAG according to the current branch/tag -export DOCKER_TAG=${CIRCLE_TAG:-$CIRCLE_BRANCH} -. .ci/update-env.sh diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 2b425783ce6..00000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,89 +0,0 @@ -# This file configures automatic builds of Sage on [CircleCI](https://circleci.com). -# To make the build time not too excessive, we seed the build cache with -# sagemath/sagemath-dev:develop. When basic SPKGs change, this does not help much, -# as the full build will often exceed CircleCI's limits for open source -# projcets (five hours on 2 vCPUs as of early 2018.) -# You might want to try to build locally or with GitLab CI, see -# `.gitlab-ci.yml` for more options. - -# As of early 2018, a run on CircleCI takes usually about 25 minutes. Most of -# the time is spent pulling/pushing from/to Docker Hub and copying files -# locally during the docker build. We could probably save five minutes by not -# building and testing the sagemath-dev image for most branches. - -version: 2 -jobs: - build-test-release: &build-test-release - docker: - - image: docker:latest - environment: - DEFAULT_ARTIFACT_BASE: sagemath/sagemath-dev:develop - steps: - - run: apk --update add git openssh - - checkout - - setup_remote_docker: - version: 18.05.0-ce - - run: &build - # The docker commands sometimes take a while to produce output - no_output_timeout: 30m - name: build - command: | - . .circleci/before-script.sh - .ci/build-docker.sh - - run: &test-dev - name: test-dev - command: | - . .circleci/before-script.sh - .ci/test-dev.sh $DOCKER_IMAGE_DEV - - run: &test-cli - name: test-cli - command: | - . .circleci/before-script.sh - .ci/test-cli.sh $DOCKER_IMAGE_CLI - - run: &test-jupyter - name: test-jupyter - command: | - . .circleci/before-script.sh - .ci/test-jupyter.sh $DOCKER_IMAGE_CLI localhost - - run: &release - # The docker commands sometimes take a while to produce output - no_output_timeout: 30m - name: release - command: | - . .circleci/before-script.sh - # Push docker images to dockerhub if a dockerhub user has been configured - .ci/push-dockerhub.sh sagemath-dev - .ci/push-dockerhub.sh sagemath - build-from-latest-test-release: - <<: *build-test-release - build-from-clean-test-release: - <<: *build-test-release - environment: - ARTIFACT_BASE: source-clean - -workflows: - version: 2 - build-branch-from-clean: - jobs: - - build-from-clean-test-release: - filters: - branches: - only: - - master - - develop - build-tag-from-clean: - jobs: - - build-from-clean-test-release: - filters: - branches: - ignore: /.*/ - tags: - only: /.*/ - build-branch-from-latest: - jobs: - - build-from-latest-test-release: - filters: - branches: - ignore: - - master - - develop diff --git a/.devcontainer/develop-docker-computop/devcontainer.json b/.devcontainer/develop-docker-computop/devcontainer.json new file mode 100644 index 00000000000..a8746244086 --- /dev/null +++ b/.devcontainer/develop-docker-computop/devcontainer.json @@ -0,0 +1,18 @@ +// See https://aka.ms/devcontainer.json for format details. +{ + "name": "computop/sage Docker", + "image": "computop/sage", + "containerEnv": { + "MAKE": "make -j4" + }, + // Install build tools, get rid of sourcing /sage/activate in non-login shells. + // libgmp.a is broken and leads to a build failure of ecm. + "onCreateCommand": ".devcontainer/onCreate.sh --sudo && sudo rm -f /sage/local/lib/libgmp.a && sed -i.bak '/sage.*activate/d' ~/.bashrc", + // Do not run configure within a sage-env (see #29485). + // The pari package is broken in the computop/sage 9.5 image, need to reinstall. + // Also libnauty is broken. + "updateContentCommand": "make configure && (export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && unset CFLAGS LDFLAGS CXXFLAGS CPATH LIBRARY_PATH && ./configure --prefix=/sage/local --with-sage-venv) && make pari-clean nauty-clean build V=0", + "extensions": [ + "ms-python.python" + ] +} diff --git a/.devcontainer/downstream-archlinux-latest/devcontainer.json b/.devcontainer/downstream-archlinux-latest/devcontainer.json new file mode 100644 index 00000000000..f0b79e01ae3 --- /dev/null +++ b/.devcontainer/downstream-archlinux-latest/devcontainer.json @@ -0,0 +1,12 @@ +// See https://aka.ms/devcontainer.json for format details. +{ + "name": "archlinux:latest downstream Sage", + "image": "archlinux:latest", + // Create an empty bashrc to avoid the error "No such file or directory" when opening a terminal. + "onCreateCommand": "EXTRA_SYSTEM_PACKAGES='sagemath sagemath-doc' EXTRA_SAGE_PACKAGES='notebook pip' .devcontainer/onCreate.sh && touch ~/.bashrc", + // There's no SAGE_LOCAL, so remove the symlink 'prefix'. + "updateContentCommand": "rm -f prefix && ln -sf /usr venv", + "extensions": [ + "ms-python.python" + ] +} diff --git a/.devcontainer/downstream-conda-forge-latest/devcontainer.json b/.devcontainer/downstream-conda-forge-latest/devcontainer.json new file mode 100644 index 00000000000..fe9f18eef8b --- /dev/null +++ b/.devcontainer/downstream-conda-forge-latest/devcontainer.json @@ -0,0 +1,19 @@ +// See https://aka.ms/devcontainer.json for format details. +{ + "name": "condaforge/mambaforge:latest downstream Sage", + "image": "condaforge/mambaforge:latest", + // Install Sage from the conda-forge package. + "onCreateCommand": "mamba install --yes sage", + // * If the workspace directory looks like a copy of the Sage source tree (SAGE_ROOT): + // - it bootstraps and configures the Sage distribution, + // - thus, the script ``./sage`` and the symlinks ``prefix``, ``venv`` are set as expected, + // - the source tree is prepared for rebuilding Sage based from the source tree on + // top of the existing installation from the Docker image. + // - however, it does not start the build. + // * Otherwise, it does nothing. This is so that users can copy this devcontainer.json file as is + // into their projects. + "updateContentCommand": "if [ -d pkgs/sagemath-standard ]; then make configure && ln -sf $CONDA_PREFIX venv; else echo 'Edit .devcontainer/devcontainer.json (updateContentCommand) to run project-specific startup commands'; fi", + "extensions": [ + "ms-python.python" + ] +} diff --git a/.devcontainer/downstream-docker-cocalc/devcontainer.json b/.devcontainer/downstream-docker-cocalc/devcontainer.json new file mode 100644 index 00000000000..626bdbb9996 --- /dev/null +++ b/.devcontainer/downstream-docker-cocalc/devcontainer.json @@ -0,0 +1,19 @@ +// See https://aka.ms/devcontainer.json for format details. +{ + "name": "CoCalc Docker", + "image": "sagemathinc/cocalc", + "containerEnv": { + "MAKE": "make -j4" + }, + // libgmp.a is broken and leads to a build failure of ecm. + "onCreateCommand": ".devcontainer/onCreate.sh && rm -f /usr/local/sage/local/lib/libgmp.a", + // * If the workspace directory looks like a copy of the Sage source tree (SAGE_ROOT): + // - it bootstraps the Sage distribution, + // - sets the symlink ``venv`` as expected, + // * Otherwise, it does nothing. This is so that users can copy this devcontainer.json file as is + // into their projects. + "updateContentCommand": "if [ -d pkgs/sagemath-standard ]; then make configure && ./configure --enable-build-as-root --prefix=/usr/local/sage/local --with-sage-venv; else echo 'Edit .devcontainer/devcontainer.json (updateContentCommand) to run project-specific startup commands'; fi", + "extensions": [ + "ms-python.python" + ] +} diff --git a/.devcontainer/downstream-docker-computop/devcontainer.json b/.devcontainer/downstream-docker-computop/devcontainer.json new file mode 100644 index 00000000000..bb2e29e374e --- /dev/null +++ b/.devcontainer/downstream-docker-computop/devcontainer.json @@ -0,0 +1,18 @@ +// See https://aka.ms/devcontainer.json for format details. +{ + "name": "computop/sage Docker", + "image": "computop/sage", + "containerEnv": { + "MAKE": "make -j4" + }, + // Install build tools, get rid of sourcing /sage/activate in non-login shells. + // libgmp.a is broken and leads to a build failure of ecm. + "onCreateCommand": ".devcontainer/onCreate.sh --sudo && sudo rm -f /sage/local/lib/libgmp.a && sed -i.bak '/sage.*activate/d' ~/.bashrc", + // Do not run configure within a sage-env (see #29485). + // The pari package is broken in the computop/sage 9.5 image, need to reinstall. + // Also libnauty is broken. + "updateContentCommand": "make configure && (export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && unset CFLAGS LDFLAGS CXXFLAGS CPATH LIBRARY_PATH && ./configure --prefix=/sage/local --with-sage-venv)", + "extensions": [ + "ms-python.python" + ] +} diff --git a/.devcontainer/onCreate.sh b/.devcontainer/onCreate.sh new file mode 100755 index 00000000000..5067e93e4f7 --- /dev/null +++ b/.devcontainer/onCreate.sh @@ -0,0 +1,9 @@ +#! /bin/sh +# Run this script from SAGE_ROOT. Invoke with "--sudo" if sudo is needed. +export PATH=$(pwd)/build/bin:$PATH +SYSTEM=$(sage-guess-package-system) +eval $(sage-print-system-package-command $SYSTEM "$@" update) +eval $(sage-print-system-package-command $SYSTEM --yes "$@" --spkg install _bootstrap _prereq python3 git $EXTRA_SAGE_PACKAGES) +if [ -n "$EXTRA_SYSTEM_PACKAGES" ]; then + eval $(sage-print-system-package-command $SYSTEM --yes "$@" install $EXTRA_SYSTEM_PACKAGES) +fi diff --git a/.devcontainer/portability-Dockerfile b/.devcontainer/portability-Dockerfile new file mode 100644 index 00000000000..532a1854258 --- /dev/null +++ b/.devcontainer/portability-Dockerfile @@ -0,0 +1,8 @@ +# This Dockerfile is used by all portability-.../devcontainer.json files, +# which provide the actual values for the 4 arguments defined below, which +# feed into the FROM statement that determines the base Docker image. +ARG SYSTEM_FACTOR="ubuntu-jammy" +ARG PACKAGE_FACTOR="standard" +ARG DOCKER_TARGET="with-system-packages" +ARG DOCKER_TAG="dev" +FROM ghcr.io/sagemath/sage/sage-docker-${SYSTEM_FACTOR}-${PACKAGE_FACTOR}-${DOCKER_TARGET}:${DOCKER_TAG} diff --git a/.devcontainer/portability-ubuntu-jammy-standard/devcontainer.json b/.devcontainer/portability-ubuntu-jammy-standard/devcontainer.json new file mode 100644 index 00000000000..f59a1178179 --- /dev/null +++ b/.devcontainer/portability-ubuntu-jammy-standard/devcontainer.json @@ -0,0 +1,22 @@ +// See https://aka.ms/devcontainer.json for format details. +{ + "name": "Ubuntu jammy", + "build": { + "dockerfile": "portability-Dockerfile", + // See tox.ini for definitions + "args": { + "SYSTEM_FACTOR": "ubuntu-jammy", + "PACKAGE_FACTOR": "standard", + "DOCKER_TARGET": "with-targets", + "DOCKER_TAG": "dev" + } + }, + "containerEnv": { + "MAKE": "make -j4" + }, + "onCreateCommand": ".devcontainer/onCreate.sh", + "updateContentCommand": ".devcontainer/portability-updateContent.sh", + "extensions": [ + "ms-python.python" + ] +} diff --git a/.devcontainer/portability-ubuntu-jammy-standard/portability-Dockerfile b/.devcontainer/portability-ubuntu-jammy-standard/portability-Dockerfile new file mode 120000 index 00000000000..692e2a79d64 --- /dev/null +++ b/.devcontainer/portability-ubuntu-jammy-standard/portability-Dockerfile @@ -0,0 +1 @@ +../portability-Dockerfile \ No newline at end of file diff --git a/.devcontainer/portability-updateContent.sh b/.devcontainer/portability-updateContent.sh new file mode 100755 index 00000000000..a923fc0d106 --- /dev/null +++ b/.devcontainer/portability-updateContent.sh @@ -0,0 +1,30 @@ +#! /bin/sh +# The portability-.../devcontainer.json configurations run this script after +# the container is started. +# +# The script assumes that it is run from SAGE_ROOT. +# +# If "config.log" or "logs" are symlinks (for example, created by 'tox -e local-...', +# or after https://trac.sagemath.org/ticket/33262), they might point outside of +# the dev container, so remove them. Likewise for upstream. +for f in config.log logs upstream; do + if [ -L $f ]; then + rm -f $f + fi +done +# If possible (ensured after https://trac.sagemath.org/ticket/33262), keep the +# logs in the container. +if [ ! -d logs ]; then + ln -s /sage/logs logs +fi +# Bootstrap, configure, and build the Sage distribution, reusing the Sage +# installation from the prebuilt image. +set -e +set -x +make configure +if [ -x /sage/config.status ]; then + eval ./configure $(/sage/config.status --config) --enable-build-as-root --prefix=/sage/local --with-sage-venv +else + ./configure --enable-build-as-root --prefix=/sage/local --with-sage-venv +fi +make build V=0 diff --git a/.github/workflows/ci-linux.yml b/.github/workflows/ci-linux.yml index b405d335ab5..d5935789219 100644 --- a/.github/workflows/ci-linux.yml +++ b/.github/workflows/ci-linux.yml @@ -33,15 +33,131 @@ env: jobs: - docker: + standard-pre: uses: ./.github/workflows/docker.yml with: + # Build from scratch + docker_targets: "with-system-packages configured with-targets-pre" # FIXME: duplicated from env.TARGETS targets_pre: all-sage-local + tox_packages_factors: >- + ["standard"] + docker_push_repository: ghcr.io/${{ github.repository }}/ + + standard: + if: ${{ success() || failure() }} + needs: [standard-pre] + uses: ./.github/workflows/docker.yml + with: + # Build incrementally from previous stage (pre) + incremental: true + from_docker_repository: ghcr.io/${{ github.repository }}/ + from_docker_target: "with-targets-pre" + docker_targets: "with-targets with-targets-optional" + # FIXME: duplicated from env.TARGETS targets: build doc-html targets_optional: ptest + tox_packages_factors: >- + ["standard"] docker_push_repository: ghcr.io/${{ github.repository }}/ + minimal-pre: + if: ${{ success() || failure() }} + # It does not really "need" it. + needs: [standard] + uses: ./.github/workflows/docker.yml + with: + # Build from scratch + docker_targets: "with-system-packages configured with-targets-pre" + # FIXME: duplicated from env.TARGETS + targets_pre: all-sage-local + tox_packages_factors: >- + ["minimal"] + docker_push_repository: ghcr.io/${{ github.repository }}/ + + minimal: + if: ${{ success() || failure() }} + needs: [minimal-pre] + uses: ./.github/workflows/docker.yml + with: + # Build incrementally from previous stage (pre) + incremental: true + from_docker_repository: ghcr.io/${{ github.repository }}/ + from_docker_target: "with-targets-pre" + docker_targets: "with-targets with-targets-optional" + # FIXME: duplicated from env.TARGETS + targets: build doc-html + targets_optional: ptest + tox_packages_factors: >- + ["minimal] + docker_push_repository: ghcr.io/${{ github.repository }}/ + + maximal-pre: + if: ${{ success() || failure() }} + needs: [minimal] + uses: ./.github/workflows/docker.yml + with: + # Build from scratch + docker_targets: "with-system-packages configured with-targets-pre" + # FIXME: duplicated from env.TARGETS + targets_pre: all-sage-local + tox_packages_factors: >- + ["maximal"] + docker_push_repository: ghcr.io/${{ github.repository }}/ + + optional-0-o: + if: ${{ success() || failure() }} + needs: [maximal-pre] + uses: ./.github/workflows/docker.yml + with: + incremental: true + from_docker_repository: ghcr.io/${{ github.repository }}/ + from_docker_target: "with-targets-pre" + tox_packages_factors: >- + ["maximal"] + docker_targets: "with-targets-optional" + targets_optional: '$(echo $(export PATH=build/bin:$PATH && sage-package list :optional: --has-file "spkg-install.in|spkg-install|requirements.txt" --no-file "huge|has_nonfree_dependencies" | grep -v sagemath_doc | grep ^[0-o]))' + + + optional-p-z: + if: ${{ success() || failure() }} + needs: [optional-0-o] + uses: ./.github/workflows/docker.yml + with: + incremental: true + from_docker_repository: ghcr.io/${{ github.repository }}/ + from_docker_target: "with-targets-pre" + tox_packages_factors: >- + ["maximal"] + docker_targets: "with-targets-optional" + targets_optional: '$(echo $(export PATH=build/bin:$PATH && sage-package list :optional: --has-file "spkg-install.in|spkg-install|requirements.txt" --no-file "huge|has_nonfree_dependencies" | grep -v sagemath_doc | grep ^[p-z]))' + + experimental-0-o: + if: ${{ success() || failure() }} + needs: [optional-p-z] + uses: ./.github/workflows/docker.yml + with: + incremental: true + from_docker_repository: ghcr.io/${{ github.repository }}/ + from_docker_target: "with-targets-pre" + tox_packages_factors: >- + ["maximal"] + docker_targets: "with-targets-optional" + targets_optional: '$(echo $(export PATH=build/bin:$PATH && sage-package list :experimental: --has-file "spkg-install.in|spkg-install|requirements.txt" --no-file "huge|has_nonfree_dependencies" | grep -v sagemath_doc | grep ^[0-o]))' + + experimental-p-z: + if: ${{ success() || failure() }} + needs: [experimental-0-o] + uses: ./.github/workflows/docker.yml + with: + incremental: true + from_docker_repository: ghcr.io/${{ github.repository }}/ + from_docker_target: "with-targets-pre" + tox_packages_factors: >- + ["maximal"] + docker_targets: "with-targets-optional" + targets_optional: '$(echo $(export PATH=build/bin:$PATH && sage-package list :experimental: --has-file "spkg-install.in|spkg-install|requirements.txt" --no-file "huge|has_nonfree_dependencies" | grep -v sagemath_doc | grep ^[p-z]))' + local-ubuntu: runs-on: ubuntu-latest diff --git a/.github/workflows/ci-macos.yml b/.github/workflows/ci-macos.yml index 49b97aa6e08..d2df2268199 100644 --- a/.github/workflows/ci-macos.yml +++ b/.github/workflows/ci-macos.yml @@ -76,19 +76,19 @@ jobs: # For doctesting, we use a lower parallelization to avoid timeouts. run: | case "${{ matrix.stage }}" in - 1) export TARGETS_PRE="all-sage-local" TARGETS="all-sage-local" TARGETS_OPTIONAL="" + 1) export TARGETS_PRE="all-sage-local" TARGETS="all-sage-local" TARGETS_OPTIONAL="build/make/Makefile" ;; 2) export TARGETS_PRE="all-sage-local" TARGETS="build doc-html" TARGETS_OPTIONAL="ptest" ;; 2-optional*) export TARGETS_PRE="build/make/Makefile" TARGETS="build/make/Makefile" targets_pattern="${{ matrix.stage }}" targets_pattern="${targets_pattern#2-optional-}" - export TARGETS_OPTIONAL=$( echo $(export PATH=build/bin:$PATH && (for a in spkg-install.in spkg-install requirements.txt; do sage-package list :optional: --has-file $a --no-file huge --no-file has_nonfree_dependencies; done) | grep -v ^_ | grep -v sagemath_doc | grep "^[$targets_pattern]" ) ) + export TARGETS_OPTIONAL=$( echo $(export PATH=build/bin:$PATH && sage-package list :optional: --has-file 'spkg-install.in|spkg-install|requirements.txt' --no-file huge|has_nonfree_dependencies | grep -v sagemath_doc | grep "^[$targets_pattern]" ) ) ;; 2-experimental*) export TARGETS_PRE="build/make/Makefile" TARGETS="build/make/Makefile" targets_pattern="${{ matrix.stage }}" targets_pattern="${targets_pattern#2-experimental-}" - export TARGETS_OPTIONAL=$( echo $(export PATH=build/bin:$PATH && (for a in spkg-install.in spkg-install requirements.txt; do sage-package list :experimental: --has-file $a --no-file huge --no-file has_nonfree_dependencies; done) | grep -v ^_ | grep -v sagemath_doc | grep "^[$targets_pattern]" ) ) + export TARGETS_OPTIONAL=$( echo $(export PATH=build/bin:$PATH && sage-package list :experimental: --has-file 'spkg-install.in|spkg-install|requirements.txt' --no-file huge|has_nonfree_dependencies | grep -v sagemath_doc | grep "^[$targets_pattern]" ) ) ;; esac MAKE="make -j12" tox -e $TOX_ENV -- SAGE_NUM_THREADS=4 $TARGETS diff --git a/.github/workflows/dist.yml b/.github/workflows/dist.yml index 0e40644d0c5..5086ca60a7c 100644 --- a/.github/workflows/dist.yml +++ b/.github/workflows/dist.yml @@ -138,6 +138,8 @@ jobs: upload_wheels: needs: build_wheels runs-on: ubuntu-latest + env: + CAN_DEPLOY: ${{ secrets.SAGEMATH_PYPI_API_TOKEN != '' }} steps: - uses: actions/download-artifact@v2 diff --git a/.github/workflows/doc-build.yml b/.github/workflows/doc-build.yml index 5e317c5238c..9d5934bd3af 100644 --- a/.github/workflows/doc-build.yml +++ b/.github/workflows/doc-build.yml @@ -24,7 +24,6 @@ jobs: # Reuse built SAGE_LOCAL contained in the Docker image ./bootstrap ./configure --enable-build-as-root --prefix=/sage/local --with-sage-venv --enable-download-from-upstream-url - make furo - name: Build run: make doc-html diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 23cab40539d..8eff13405f8 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -18,24 +18,20 @@ on: default: >- ["ubuntu-trusty-toolchain-gcc_9", "ubuntu-xenial-toolchain-gcc_9", - "ubuntu-bionic", + "ubuntu-bionic-gcc_8", "ubuntu-focal", "ubuntu-jammy", "ubuntu-kinetic", - "debian-stretch", "debian-buster", "debian-bullseye", "debian-bookworm", "debian-sid", - "linuxmint-19", - "linuxmint-19.3", + "linuxmint-19-gcc_8", + "linuxmint-19.3-gcc_8", "linuxmint-20.1", "linuxmint-20.2", "linuxmint-20.3", "linuxmint-21", - "fedora-26", - "fedora-27", - "fedora-28", "fedora-29", "fedora-30", "fedora-31", @@ -51,8 +47,8 @@ on: "gentoo-python3.9", "gentoo-python3.10", "archlinux-latest", - "opensuse-15.3", - "opensuse-15.4", + "opensuse-15.3-gcc_11", + "opensuse-15.4-gcc_11", "opensuse-tumbleweed", "conda-forge", "ubuntu-bionic-i386", @@ -68,7 +64,7 @@ on: ] max_parallel: type: number - default: 20 + default: 24 # # Publishing to GitHub Packages # @@ -76,6 +72,25 @@ on: required: false type: string # + # Incremental builds + # + docker_targets: + default: "with-system-packages configured with-targets-pre with-targets with-targets-optional" + type: string + incremental: + default: false + type: boolean + from_docker_repository: + required: false + type: string + from_docker_target: + required: false + type: string + from_docker_tag: + required: false + default: "$BUILD_TAG" + type: string + # # For use in upstream CIs # upstream_artifact: @@ -104,12 +119,16 @@ jobs: tox_system_factor: ${{ fromJson(inputs.tox_system_factors) }} tox_packages_factor: ${{ fromJson(inputs.tox_packages_factors) }} env: - TOX_ENV: docker-${{ matrix.tox_system_factor }}-${{ matrix.tox_packages_factor }} + TOX_ENV: "docker-${{ matrix.tox_system_factor }}-${{ matrix.tox_packages_factor }}${{ inputs.incremental && '-incremental' || '' }}" LOGS_ARTIFACT_NAME: logs-commit-${{ github.sha }}-tox-docker-${{ matrix.tox_system_factor }}-${{ matrix.tox_packages_factor }} - DOCKER_TARGETS: with-system-packages configured with-targets-pre with-targets with-targets-optional + DOCKER_TARGETS: ${{ inputs.docker_targets }} TARGETS_PRE: ${{ inputs.targets_pre }} TARGETS: ${{ inputs.targets }} TARGETS_OPTIONAL: ${{ inputs.targets_optional }} + FROM_DOCKER_REPOSITORY: ${{ inputs.from_docker_repository }} + FROM_DOCKER_TARGET: ${{ inputs.from_docker_target }} + FROM_DOCKER_TAG: ${{ inputs.from_docker_tag }} + steps: - name: Check out SageMath uses: actions/checkout@v2 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 8456f3666dc..811a61cc928 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -50,5 +50,3 @@ jobs: run: pip install tox - name: Lint using tox -e rst run: tox -e rst - # Until all errors are fixed: - continue-on-error: true diff --git a/.github/workflows/tox-experimental.yml b/.github/workflows/tox-experimental.yml deleted file mode 100644 index 3abeec4a775..00000000000 --- a/.github/workflows/tox-experimental.yml +++ /dev/null @@ -1,123 +0,0 @@ -name: Test experimental packages with tox - -## This GitHub Actions workflow runs SAGE_ROOT/tox.ini with select environments, -## whenever a GitHub pull request is opened or synchronized in a repository -## where GitHub Actions are enabled. -## -## It builds and checks some sage spkgs as defined in TARGETS. -## -## A job succeeds if there is no error. -## -## The build is run with "make V=0", so the build logs of individual packages are suppressed. -## -## At the end, all package build logs that contain an error are printed out. -## -## After all jobs have finished (or are canceled) and a short delay, -## tar files of all logs are made available as "build artifacts". - -#on: [push, pull_request] - -on: - pull_request: - types: [opened, synchronize] - push: - tags: - - '*' - workflow_dispatch: - # Allow to run manually - -env: - TARGETS_PRE: build/make/Makefile - TARGETS: build/make/Makefile - # TARGETS_OPTIONAL see below - -jobs: - docker: - runs-on: ubuntu-latest - strategy: - fail-fast: false - max-parallel: 6 - matrix: - tox_system_factor: [ubuntu-trusty-toolchain-gcc_9, ubuntu-xenial-toolchain-gcc_9, ubuntu-bionic, ubuntu-focal, ubuntu-jammy, ubuntu-kinetic, debian-stretch, debian-buster, debian-bullseye, debian-bookworm, debian-sid, linuxmint-19, linuxmint-19.3, linuxmint-20.1, linuxmint-20.2, linuxmint-20.3, linuxmint-21, fedora-26, fedora-27, fedora-28, fedora-29, fedora-30, fedora-31, fedora-32, fedora-33, fedora-34, fedora-35, fedora-36, fedora-37, centos-7-devtoolset-gcc_11, centos-stream-8, gentoo-python3.9, gentoo-python3.10, archlinux-latest, opensuse-15.3, opensuse-tumbleweed, conda-forge, ubuntu-bionic-i386, manylinux-2_24-i686, debian-buster-i386, centos-7-i386-devtoolset-gcc_11] - tox_packages_factor: [maximal] - targets_pattern: [0-g, h-o, p, q-z] - env: - TOX_ENV: docker-${{ matrix.tox_system_factor }}-${{ matrix.tox_packages_factor }} - LOGS_ARTIFACT_NAME: logs-commit-${{ github.sha }}-tox-docker-${{ matrix.tox_system_factor }}-${{ matrix.tox_packages_factor }} - DOCKER_TARGETS: configured with-targets with-targets-optional - # Test all non-dummy experimental packages, but do not test huge packages - # and do not test packages that require external software - TARGETS_OPTIONAL: "$( echo $(export PATH=build/bin:$PATH && (for a in spkg-install.in spkg-install requirements.txt; do sage-package list :experimental: --has-file $a --no-file huge --no-file has_nonfree_dependencies; done) | grep -v ^_ | grep -v sagemath_doc | grep '^[${{ matrix.targets_pattern }}]' ) )" - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 500 - - name: fetch tags - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - - name: free disk space - run: | - df -h - sudo swapoff -a - sudo rm -f /swapfile - sudo apt-get clean - docker rmi $(docker image ls -aq) - echo "Largest packages:" - dpkg-query -Wf '${Installed-Size}\t${Package}\n' | sort -n | tail -n 50 - sudo apt-get --fix-broken --yes remove $(dpkg-query -f '${Package}\n' -W | grep -E '^(ghc-|google-cloud-sdk|google-chrome|firefox|mysql-server|dotnet-sdk|hhvm|mono)') || echo "(error ignored)" - df -h - - name: Install test prerequisites - run: | - sudo DEBIAN_FRONTEND=noninteractive apt-get update - sudo DEBIAN_FRONTEND=noninteractive apt-get install tox - sudo apt-get clean - df -h - - name: Try to login to ghcr.io - # https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable - run: | - TOKEN="${{ secrets.DOCKER_PKG_GITHUB_TOKEN }}" - if [ -z "$TOKEN" ]; then - TOKEN="${{ secrets.GITHUB_TOKEN }}" - fi - if echo "$TOKEN" | docker login ghcr.io -u ${{ github.actor }} --password-stdin; then - echo "DOCKER_PUSH_REPOSITORY=ghcr.io/${{ github.repository }}/" >> $GITHUB_ENV - echo "DOCKER_CONFIG_FILE=$HOME/.docker/config.json" >> $GITHUB_ENV - fi - # From the docker documentation via .ci/update-env.sh: - # "A tag name must be valid ASCII and may - # contain lowercase and uppercase letters, digits, underscores, periods and - # dashes. A tag name may not start with a period or a dash and may contain a - # maximum of 128 characters." - EXTRA_DOCKER_TAGS=`echo $GITHUB_REF_NAME | tr -d '[:space:]' | tr -c '[:alnum:]_.-' '-' | sed 's/^[-.]*//' | cut -c1-128` - shopt -s extglob - case "$GITHUB_REF_NAME" in - +([0-9]).+([0-9])?(.+([0-9])) ) - EXTRA_DOCKER_TAGS="latest dev $EXTRA_DOCKER_TAGS";; - +([0-9]).+([0-9])?(.+([0-9])).@(beta|rc)+([0-9]) ) - EXTRA_DOCKER_TAGS="dev $EXTRA_DOCKER_TAGS";; - esac - echo "EXTRA_DOCKER_TAGS=$EXTRA_DOCKER_TAGS" >> $GITHUB_ENV - - run: | - set -o pipefail; EXTRA_DOCKER_BUILD_ARGS="--build-arg USE_MAKEFLAGS=\"-k V=0 SAGE_NUM_THREADS=3\"" tox -e $TOX_ENV -- $TARGETS 2>&1 | sed "/^configure: notice:/s|^|::warning file=artifacts/$LOGS_ARTIFACT_NAME/config.log::|;/^configure: warning:/s|^|::warning file=artifacts/$LOGS_ARTIFACT_NAME/config.log::|;/^configure: error:/s|^|::error file=artifacts/$LOGS_ARTIFACT_NAME/config.log::|;" - - name: Copy logs from the docker image or build container - run: | - mkdir -p "artifacts/$LOGS_ARTIFACT_NAME" - cp -r .tox/$TOX_ENV/Dockerfile .tox/$TOX_ENV/log "artifacts/$LOGS_ARTIFACT_NAME" - if [ -f .tox/$TOX_ENV/Dockertags ]; then CONTAINERS=$(docker create $(tail -1 .tox/$TOX_ENV/Dockertags) /bin/bash || true); fi - if [ -n "$CONTAINERS" ]; then for CONTAINER in $CONTAINERS; do for ARTIFACT in /sage/logs; do docker cp $CONTAINER:$ARTIFACT artifacts/$LOGS_ARTIFACT_NAME && HAVE_LOG=1; done; if [ -n "$HAVE_LOG" ]; then break; fi; done; fi - if: always() - - uses: actions/upload-artifact@v1 - with: - path: artifacts - name: ${{ env.LOGS_ARTIFACT_NAME }} - if: always() - - name: Print out logs for immediate inspection - # and markup the output with GitHub Actions logging commands - run: | - .github/workflows/scan-logs.sh "artifacts/$LOGS_ARTIFACT_NAME" - if: always() - - name: List docker images - run: | - if [ -f .tox/$TOX_ENV/Dockertags ]; then - cat .tox/$TOX_ENV/Dockertags - fi - if: always() diff --git a/.github/workflows/tox-optional.yml b/.github/workflows/tox-optional.yml deleted file mode 100644 index 72b63ef61c3..00000000000 --- a/.github/workflows/tox-optional.yml +++ /dev/null @@ -1,123 +0,0 @@ -name: Test optional packages with tox - -## This GitHub Actions workflow runs SAGE_ROOT/tox.ini with select environments, -## whenever a GitHub pull request is opened or synchronized in a repository -## where GitHub Actions are enabled. -## -## It builds and checks some sage spkgs as defined in TARGETS. -## -## A job succeeds if there is no error. -## -## The build is run with "make V=0", so the build logs of individual packages are suppressed. -## -## At the end, all package build logs that contain an error are printed out. -## -## After all jobs have finished (or are canceled) and a short delay, -## tar files of all logs are made available as "build artifacts". - -#on: [push, pull_request] - -on: - pull_request: - types: [opened, synchronize] - push: - tags: - - '*' - workflow_dispatch: - # Allow to run manually - -env: - TARGETS_PRE: build/make/Makefile - TARGETS: build/make/Makefile - # TARGETS_OPTIONAL see below - -jobs: - docker: - runs-on: ubuntu-latest - strategy: - fail-fast: false - max-parallel: 6 - matrix: - tox_system_factor: [ubuntu-trusty-toolchain-gcc_9, ubuntu-xenial-toolchain-gcc_9, ubuntu-bionic, ubuntu-focal, ubuntu-jammy, ubuntu-kinetic, debian-stretch, debian-buster, debian-bullseye, debian-bookworm, debian-sid, linuxmint-19, linuxmint-19.3, linuxmint-20.1, linuxmint-20.2, linuxmint-20.3, linuxmint-21, fedora-26, fedora-27, fedora-28, fedora-29, fedora-30, fedora-31, fedora-32, fedora-33, fedora-34, fedora-35, fedora-36, fedora-37, centos-7-devtoolset-gcc_11, centos-stream-8, gentoo-python3.9, gentoo-python3.10, archlinux-latest, opensuse-15.3, opensuse-tumbleweed, conda-forge, ubuntu-bionic-i386, manylinux-2_24-i686, debian-buster-i386, centos-7-i386-devtoolset-gcc_11] - tox_packages_factor: [maximal] - targets_pattern: [0-g, h-o, p, q-z] - env: - TOX_ENV: docker-${{ matrix.tox_system_factor }}-${{ matrix.tox_packages_factor }} - LOGS_ARTIFACT_NAME: logs-commit-${{ github.sha }}-tox-docker-${{ matrix.tox_system_factor }}-${{ matrix.tox_packages_factor }} - DOCKER_TARGETS: configured with-targets with-targets-optional - # Test all non-dummy optional packages, but do not test huge packages - # and do not test packages that require external software - TARGETS_OPTIONAL: "$( echo $(export PATH=build/bin:$PATH && (for a in spkg-install.in spkg-install requirements.txt; do sage-package list :optional: --has-file $a --no-file huge --no-file has_nonfree_dependencies; done) | grep -v ^_ | grep -v sagemath_doc | grep '^[${{ matrix.targets_pattern }}]' ) )" - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 500 - - name: fetch tags - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - - name: free disk space - run: | - df -h - sudo swapoff -a - sudo rm -f /swapfile - sudo apt-get clean - docker rmi $(docker image ls -aq) - echo "Largest packages:" - dpkg-query -Wf '${Installed-Size}\t${Package}\n' | sort -n | tail -n 50 - sudo apt-get --fix-broken --yes remove $(dpkg-query -f '${Package}\n' -W | grep -E '^(ghc-|google-cloud-sdk|google-chrome|firefox|mysql-server|dotnet-sdk|hhvm|mono)') || echo "(error ignored)" - df -h - - name: Install test prerequisites - run: | - sudo DEBIAN_FRONTEND=noninteractive apt-get update - sudo DEBIAN_FRONTEND=noninteractive apt-get install tox - sudo apt-get clean - df -h - - name: Try to login to ghcr.io - # https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable - run: | - TOKEN="${{ secrets.DOCKER_PKG_GITHUB_TOKEN }}" - if [ -z "$TOKEN" ]; then - TOKEN="${{ secrets.GITHUB_TOKEN }}" - fi - if echo "$TOKEN" | docker login ghcr.io -u ${{ github.actor }} --password-stdin; then - echo "DOCKER_PUSH_REPOSITORY=ghcr.io/${{ github.repository }}/" >> $GITHUB_ENV - echo "DOCKER_CONFIG_FILE=$HOME/.docker/config.json" >> $GITHUB_ENV - fi - # From the docker documentation via .ci/update-env.sh: - # "A tag name must be valid ASCII and may - # contain lowercase and uppercase letters, digits, underscores, periods and - # dashes. A tag name may not start with a period or a dash and may contain a - # maximum of 128 characters." - EXTRA_DOCKER_TAGS=`echo $GITHUB_REF_NAME | tr -d '[:space:]' | tr -c '[:alnum:]_.-' '-' | sed 's/^[-.]*//' | cut -c1-128` - shopt -s extglob - case "$GITHUB_REF_NAME" in - +([0-9]).+([0-9])?(.+([0-9])) ) - EXTRA_DOCKER_TAGS="latest dev $EXTRA_DOCKER_TAGS";; - +([0-9]).+([0-9])?(.+([0-9])).@(beta|rc)+([0-9]) ) - EXTRA_DOCKER_TAGS="dev $EXTRA_DOCKER_TAGS";; - esac - echo "EXTRA_DOCKER_TAGS=$EXTRA_DOCKER_TAGS" >> $GITHUB_ENV - - run: | - set -o pipefail; EXTRA_DOCKER_BUILD_ARGS="--build-arg USE_MAKEFLAGS=\"-k V=0 SAGE_NUM_THREADS=3\"" tox -e $TOX_ENV -- $TARGETS 2>&1 | sed "/^configure: notice:/s|^|::warning file=artifacts/$LOGS_ARTIFACT_NAME/config.log::|;/^configure: warning:/s|^|::warning file=artifacts/$LOGS_ARTIFACT_NAME/config.log::|;/^configure: error:/s|^|::error file=artifacts/$LOGS_ARTIFACT_NAME/config.log::|;" - - name: Copy logs from the docker image or build container - run: | - mkdir -p "artifacts/$LOGS_ARTIFACT_NAME" - cp -r .tox/$TOX_ENV/Dockerfile .tox/$TOX_ENV/log "artifacts/$LOGS_ARTIFACT_NAME" - if [ -f .tox/$TOX_ENV/Dockertags ]; then CONTAINERS=$(docker create $(tail -1 .tox/$TOX_ENV/Dockertags) /bin/bash || true); fi - if [ -n "$CONTAINERS" ]; then for CONTAINER in $CONTAINERS; do for ARTIFACT in /sage/logs; do docker cp $CONTAINER:$ARTIFACT artifacts/$LOGS_ARTIFACT_NAME && HAVE_LOG=1; done; if [ -n "$HAVE_LOG" ]; then break; fi; done; fi - if: always() - - uses: actions/upload-artifact@v1 - with: - path: artifacts - name: ${{ env.LOGS_ARTIFACT_NAME }} - if: always() - - name: Print out logs for immediate inspection - # and markup the output with GitHub Actions logging commands - run: | - .github/workflows/scan-logs.sh "artifacts/$LOGS_ARTIFACT_NAME" - if: always() - - name: List docker images - run: | - if [ -f .tox/$TOX_ENV/Dockertags ]; then - cat .tox/$TOX_ENV/Dockertags - fi - if: always() diff --git a/.gitignore b/.gitignore index ccd43a4be10..4440aee8f9a 100644 --- a/.gitignore +++ b/.gitignore @@ -217,6 +217,9 @@ src/ENV/ src/env.bak/ src/venv.bak/ +# devcontainer +/.devcontainer/devcontainer.json + # mypy **/.mypy_cache/ diff --git a/.gitpod.yml b/.gitpod.yml index 6017bd072e8..fcd6027c88d 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -9,8 +9,8 @@ tasks: # Create conda environment ./bootstrap-conda mamba env create --file src/environment-dev.yml --prefix venv - conda config --append envs_dirs /workspace/sagetrac-mirror - conda activate /workspace/sagetrac-mirror/venv + conda config --append envs_dirs $(pwd) + conda activate $(pwd)/venv # Build sage ./bootstrap @@ -20,8 +20,8 @@ tasks: command: | # Activate conda environment - conda config --append envs_dirs /workspace/sagetrac-mirror - conda activate /workspace/sagetrac-mirror/venv + conda config --append envs_dirs $(pwd) + conda activate $(pwd)/venv # RestructuredText extension recommends python extension, although we have already installed it ## So disable the recommendation dialog diff --git a/.homebrew-build-env b/.homebrew-build-env index ff07cad2f64..84f5017a310 100644 --- a/.homebrew-build-env +++ b/.homebrew-build-env @@ -2,7 +2,7 @@ # that activate keg-only homebrew package installations HOMEBREW=`brew --prefix` || return 1 -for l in gettext bzip2 texinfo polymake; do +for l in bzip2 texinfo polymake; do if [ -d "$HOMEBREW/opt/$l/bin" ]; then PATH="$HOMEBREW/opt/$l/bin:$PATH" fi @@ -39,7 +39,7 @@ for l in "gcc/lib/gcc/11 gcc/lib/gcc/10 gcc/lib/gcc/9"; do done export LIBRARY_PATH export CPATH -for l in gettext; do +for l in ; do if [ -d "$HOMEBREW/opt/$l/share/aclocal" ]; then ACLOCAL_PATH="$HOMEBREW/opt/$l/share/aclocal:$ACLOCAL_PATH" fi diff --git a/.vscode/settings.json b/.vscode/settings.json index cb06559edea..10822524b25 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,9 +1,11 @@ { + // This settings file is not ignored by git. It should be kept in sync with + // the trac repo. "python.defaultInterpreterPath": "./venv/bin/python3", "files.exclude": { "**/__pycache__": true, "src/**/*.cpp": true, - "src/**/*.so": true, + "src/**/*.so": true }, "search.exclude": { "build/pkgs/sagemath_categories/src": true, @@ -12,20 +14,24 @@ "pkgs/sage-conf_pypi/sage_root/build": true, "pkgs/sagemath-categories/sage": true, "pkgs/sagemath-objects/sage": true, - "pkgs/sagemath-standard/sage": true, + "pkgs/sagemath-standard/sage": true }, "python.testing.pytestEnabled": true, "python.testing.pytestArgs": [ "--rootdir=src/sage", "-c=src/tox.ini", - "--doctest-modules", + "--doctest-modules" ], "python.testing.unittestEnabled": false, "python.linting.pycodestyleEnabled": true, "python.linting.enabled": true, + // The following pycodestyle arguments are the same as the pycodestyle-minimal + // tox environnment, see the file SAGE_ROOT/src/tox.ini + "python.linting.pycodestyleArgs": ["--select=E111,E306,E401,E701,E702,E703,W605,E711,E712,E713,E721,E722"], "cSpell.words": [ - "furo" + "furo", "Conda", + "sagemath", "Cython" - ], + ] } diff --git a/.zenodo.json b/.zenodo.json index 737a2a013df..06bf86e4065 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.7.beta8", - "version": "9.7.beta8", + "title": "sagemath/sage: 9.8.beta0", + "version": "9.8.beta0", "upload_type": "software", - "publication_date": "2022-08-07", + "publication_date": "2022-09-25", "creators": [ { "affiliation": "SageMath.org", @@ -15,7 +15,7 @@ "related_identifiers": [ { "scheme": "url", - "identifier": "https://github.com/sagemath/sage/tree/9.7.beta8", + "identifier": "https://github.com/sagemath/sage/tree/9.8.beta0", "relation": "isSupplementTo" }, { diff --git a/README.md b/README.md index fd467aa85f8..7a21328c9a4 100644 --- a/README.md +++ b/README.md @@ -21,12 +21,14 @@ Getting Started The [Sage Installation Guide](https://doc.sagemath.org/html/en/installation/index.html) provides a decision tree that guides you to the type of installation -that will work best for you. +that will work best for you. This includes building from source, +obtaining Sage from a package manager, using a container image, or using +Sage in the cloud. -If you have already cloned the git repository or downloaded the +**This README contains self-contained instructions for building Sage from source.** +It assumes that you have already cloned the git repository or downloaded the [sources](https://www.sagemath.org/download-source.html) in the form -of a tarball, please read the self-contained instructions below on how -to build Sage and work around common issues. +of a tarball. If you have questions or encounter problems, please do not hesitate to email the [sage-support mailing list](https://groups.google.com/group/sage-support) @@ -36,7 +38,7 @@ Supported Platforms ------------------- Sage attempts to support all major Linux distributions, recent versions of -macOS, and Windows (using Windows Subsystem for Linux, Cygwin, or +macOS, and Windows (using Windows Subsystem for Linux or virtualization). Detailed information on supported platforms for a specific version of Sage @@ -47,15 +49,6 @@ We highly appreciate contributions to Sage that fix portability bugs and help port Sage to new platforms; let us know at the [sage-devel mailing list](https://groups.google.com/group/sage-devel). -Docker Images -------------- - -You can also have a look at our Docker images to run Sage. -To use these images, -[install Docker](https://www.docker.com/community-edition#/download) -and follow the instructions on -[our Docker Hub page](https://hub.docker.com/r/sagemath/sagemath/). - [Windows] Preparing the Platform -------------------------------- @@ -67,57 +60,6 @@ your Windows. Then all instructions for installation in Linux apply. As an alternative, you can also run Linux on Windows using Docker (see above) or other virtualization solutions. -Finally, Sage also works on the 64-bit version of `Cygwin -`_. If you wish to use Cygwin, use the following -instructions to get started. - -1. Download [cygwin64](https://cygwin.com/install.html) (do not get - the 32-bit version; it is not supported by Sage). - -2. Run the `setup-x86_64.exe` graphical installer. Pick the default - options in most cases. At the package selection screen, use the - search bar to find and select at least the following packages: - `bzip2`, `coreutils`, `curl`, `gawk`, `gzip`, `tar`, `wget`, `git`. - -3. Start the Cygwin terminal and ensure you get a working bash prompt. - -4. Make sure the path of your Cygwin home directory does not contain - space characters. - - By default, your username in Cygwin is the same as your username in - Windows. This might contain spaces and other traditionally - non-UNIX-friendly characters, e.g., if it is your full name. You - can check this as follows: - - $ whoami - Erik M. Bray - - This means your default home directory on Cygwin contains this - username verbatim; in the above example, `/home/Erik M. Bray`. - It will save some potential trouble if you change your Cygwin home - directory to contain only alphanumeric characters, for example, - `/home/embray`. The easiest way to do this is to first create - the home directory you want to use instead, then create an - `/etc/passwd` file specifying that directory as your home, as follows: - - $ whocanibe=embray - $ mkdir /home/$whocanibe - $ mkpasswd.exe -l -u "$(whoami)" | sed -r 's,/home/[^:]+,/home/'$whocanibe, > /etc/passwd - - After this, close all Cygwin terminals (ensure nothing in - `C:\cygwin64` is running), then start a new Cygwin terminal and - your home directory should have moved. - - There are [other ways to do - this](https://stackoverflow.com/questions/1494658/how-can-i-change-my-cygwin-home-folder-after-installation), - but the above seems to be the simplest that's still supported. - -5. Install the package manager `apt-cyg`: - - $ curl -OL https://rawgit.com/transcode-open/apt-cyg/master/apt-cyg - $ install apt-cyg /usr/local/bin - $ rm -f apt-cyg - [macOS] Preparing the Platform ------------------------------ @@ -162,7 +104,7 @@ Like many other software packages, Sage is built from source using `./configure`, followed by `make`. However, we strongly recommend to read the following step-by-step instructions for building Sage. -The instructions cover all of Linux, macOS, and Cygwin. +The instructions cover all of Linux, macOS, and WSL. More details, providing a background for these instructions, can be found in the [section "Install from Source Code"](https://doc.sagemath.org/html/en/installation/source.html). @@ -193,9 +135,6 @@ in the Installation Guide. capitalization when changing into :envvar:`SAGE_ROOT` can lead to build errors for dependencies requiring exact capitalization in path names. - - [Cygwin] Avoid building in home directories of Windows domain - users or in paths with capital letters. - 2. Download/unpack or clone the sources. - Go to https://www.sagemath.org/download-source.html, select a mirror, @@ -236,14 +175,14 @@ in the Installation Guide. line endings are used. 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`. + WSL `bash` using the WSL `tar` utility and not using other + Windows tools (including mingw). Likewise, when using `git`, it + is recommended (but not necessary) to use the WSL version of + `git`. -3. [Linux, Cygwin] Install the required minimal build prerequisites. +3. [Linux, WSL] Install the required minimal build prerequisites. - - Compilers: `gcc`, `gfortran`, `g++` (GCC 6.3 to 12.x and recent + - Compilers: `gcc`, `gfortran`, `g++` (GCC 8.x to 12.x and recent versions of Clang (LLVM) are supported). See the Installation Manual for a discussion of suitable compilers. @@ -259,7 +198,6 @@ in the Installation Guide. [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) @@ -269,8 +207,8 @@ in the Installation Guide. [void.txt](build/pkgs/_prereq/distros/void.txt), or visit https://doc.sagemath.org/html/en/reference/spkg/_prereq.html#spkg-prereq -4. [Git] If you plan to work with ticket branches that make changes - to packages, install the bootstrapping prerequisites. See the +4. [Git] If you plan to do Sage development or otherwise work with ticket branches + and not only releases, install the bootstrapping prerequisites. See the files in the folder [build/pkgs/_bootstrap/distros](build/pkgs/_bootstrap/distros), or visit diff --git a/VERSION.txt b/VERSION.txt index 57ef2726b25..58778909bd8 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -SageMath version 9.7.beta8, Release Date: 2022-08-07 +SageMath version 9.8.beta0, Release Date: 2022-09-25 diff --git a/bootstrap-conda b/bootstrap-conda index 596b449e3e0..9ba4b50ab6f 100755 --- a/bootstrap-conda +++ b/bootstrap-conda @@ -7,14 +7,17 @@ export PATH="$(pwd)/build/bin:$PATH" STRIP_COMMENTS="sed s/#.*//;" -RECOMMENDED_SPKG_PATTERN="@(_recommended$(for a in $(head -n 1 build/pkgs/_recommended/dependencies); do echo -n "|"$a; done))" + +shopt -s extglob + +DEVELOP_SPKG_PATTERN="@(_develop$(for a in $(head -n 1 build/pkgs/_develop/dependencies); do echo -n "|"$a; done))" BOOTSTRAP_PACKAGES=$(echo $(${STRIP_COMMENTS} build/pkgs/_bootstrap/distros/conda.txt)) SYSTEM_PACKAGES= OPTIONAL_SYSTEM_PACKAGES= SAGELIB_SYSTEM_PACKAGES= SAGELIB_OPTIONAL_SYSTEM_PACKAGES= -RECOMMENDED_SYSTEM_PACKAGES= +DEVELOP_SYSTEM_PACKAGES= for PKG_BASE in $(sage-package list --has-file distros/conda.txt); do PKG_SCRIPTS=build/pkgs/$PKG_BASE SYSTEM_PACKAGES_FILE=$PKG_SCRIPTS/distros/conda.txt @@ -26,8 +29,8 @@ for PKG_BASE in $(sage-package list --has-file distros/conda.txt); do *:standard) SYSTEM_PACKAGES+=" $PKG_SYSTEM_PACKAGES" ;; - $RECOMMENDED_SPKG_PATTERN:*) - RECOMMENDED_SYSTEM_PACKAGES+=" $PKG_SYSTEM_PACKAGES" + $DEVELOP_SPKG_PATTERN:*) + DEVELOP_SYSTEM_PACKAGES+=" $PKG_SYSTEM_PACKAGES" ;; *) OPTIONAL_SYSTEM_PACKAGES+=" $PKG_SYSTEM_PACKAGES" @@ -46,36 +49,72 @@ for PKG_BASE in $(sage-package list --has-file distros/conda.txt); do fi done echo >&2 $0:$LINENO: generate conda environment files -echo "name: sage-build" > environment.yml -echo "channels:" >> environment.yml -echo " - conda-forge" >> environment.yml -echo " - nodefaults" >> environment.yml -echo "dependencies:" >> environment.yml -for pkg in $SYSTEM_PACKAGES; do - echo " - $pkg" >> environment.yml -done -echo " # Packages needed for ./bootstrap" >> environment.yml -for pkg in $BOOTSTRAP_PACKAGES; do - echo " - $pkg" >> environment.yml -done -sed 's/name: sage-build/name: sage/' environment.yml > src/environment.yml -for pkg in $SAGELIB_SYSTEM_PACKAGES; do - echo " - $pkg" >> src/environment.yml -done -sed 's/name: sage/name: sage-dev/' src/environment.yml > src/environment-dev.yml -echo " # Additional dev tools" >> src/environment-dev.yml -echo " - openssh" >> src/environment-dev.yml -echo " - pycodestyle" >> src/environment-dev.yml -echo " - pytest" >> src/environment-dev.yml -echo " - esbonio" >> src/environment-dev.yml +( + echo "name: sage-build" + echo "channels:" + echo " - conda-forge" + echo " - nodefaults" + echo "dependencies:" + for pkg in $SYSTEM_PACKAGES; do + echo " - $pkg" + done + echo " # Packages needed for ./bootstrap" + for pkg in $BOOTSTRAP_PACKAGES; do + echo " - $pkg" + done +) > environment.yml -cp environment.yml environment-optional.yml - echo " # optional packages" >> environment-optional.yml -for pkg in $OPTIONAL_SYSTEM_PACKAGES; do - echo " - $pkg" >> environment-optional.yml +( + sed 's/name: sage-build/name: sage/' environment.yml + echo " # Additional packages providing all dependencies for the Sage library" + for pkg in $SAGELIB_SYSTEM_PACKAGES; do + echo " - $pkg" done -cp src/environment.yml src/environment-optional.yml - echo " # optional packages" >> src/environment-optional.yml -for pkg in $OPTIONAL_SYSTEM_PACKAGES $SAGELIB_OPTIONAL_SYSTEM_PACKAGES; do - echo " - $pkg" >> src/environment-optional.yml -done +) > src/environment.yml + +( + sed 's/name: sage/name: sage-dev/' src/environment.yml + echo " # Additional dev tools" + for pkg in $DEVELOP_SYSTEM_PACKAGES; do + echo " - $pkg" + done +) > src/environment-dev.yml + +( + cat environment.yml + echo " # optional packages" + for pkg in $OPTIONAL_SYSTEM_PACKAGES; do + echo " - $pkg" + done +) > environment-optional.yml + +( + cat src/environment.yml + echo " # optional packages" + for pkg in $OPTIONAL_SYSTEM_PACKAGES $SAGELIB_OPTIONAL_SYSTEM_PACKAGES; do + echo " - $pkg" + done +) > src/environment-optional.yml +( + echo >&4 " - pip:" + echo >&5 " - pip:" + for PKG_BASE in $((sage-package list :standard: :optional: --has-file requirements.txt --no-file distros/conda.txt --no-file src; sage-package list :standard: :optional: --has-file install-requires.txt --no-file requirements.txt --no-file distros/conda.txt --no-file src) | sort); do + PKG_SCRIPTS=build/pkgs/$PKG_BASE + SYSTEM_PACKAGES_FILE=$PKG_SCRIPTS/requirements.txt + if [ ! -f $SYSTEM_PACKAGES_FILE ]; then + SYSTEM_PACKAGES_FILE=$PKG_SCRIPTS/install-requires.txt + fi + PKG_TYPE=$(cat $PKG_SCRIPTS/type) + if grep -q SAGERUNTIME $PKG_SCRIPTS/dependencies $PKG_SCRIPTS/dependencies_order_only 2>/dev/null; then + : # cannot install packages that depend on the Sage library + else + case "$PKG_BASE:$PKG_TYPE" in + $DEVELOP_SPKG_PATTERN:*) FD=4;; + *) FD=5;; + esac + ${STRIP_COMMENTS} $SYSTEM_PACKAGES_FILE | while read -r line; do + [ -n "$line" ] && echo >&$FD " - $line" + done + fi + done +) 4>> src/environment-dev.yml 5>> src/environment-optional.yml diff --git a/build/bin/sage-print-system-package-command b/build/bin/sage-print-system-package-command index 4ee33ab0e65..b493ebd0ffb 100755 --- a/build/bin/sage-print-system-package-command +++ b/build/bin/sage-print-system-package-command @@ -142,7 +142,7 @@ case $system:$command in ;; *conda*:install) [ "$YES" = yes ] && options="$options --yes" - [ -n "$system_packages" ] && print_shell_command "conda install $system_packages" + [ -n "$system_packages" ] && print_shell_command "conda install $options $system_packages" ;; homebrew*:install) [ -n "$system_packages" ] && print_shell_command "brew install $system_packages" diff --git a/build/bin/sage-spkg b/build/bin/sage-spkg index 4478bc7f2d7..53835babf27 100755 --- a/build/bin/sage-spkg +++ b/build/bin/sage-spkg @@ -451,20 +451,31 @@ echo "Setting up build directory for $PKG_NAME" cp -RLp "$PKG_SCRIPTS" "$PKG_NAME" cd "$PKG_NAME" || exit $? -sage-uncompress-spkg -d src "$PKG_SRC" -if [ $? -ne 0 ]; then - error_msg "Error: failed to extract $PKG_SRC" - exit 1 -fi - -echo "Finished extraction" - -cd src -if ! sage-apply-patches; then - error_msg "Error applying patches" - exit 1 -fi -cd .. +case "$PKG_SRC" in + *.whl) + # (Platform-independent) wheel + # Do not extract, do not create a src directory, + # just copy to dist/ and create a simple install script. + mkdir -p dist + cp "$PKG_SRC" dist/ + echo "sdh_store_and_pip_install_wheel ." > spkg-install.in + ;; + *) + # Source tarball + sage-uncompress-spkg -d src "$PKG_SRC" + if [ $? -ne 0 ]; then + error_msg "Error: failed to extract $PKG_SRC" + exit 1 + fi + echo "Finished extraction" + cd src + if ! sage-apply-patches; then + error_msg "Error applying patches" + exit 1 + fi + cd .. + ;; +esac ################################################################## # The package has been extracted, prepare for installation diff --git a/build/bin/sage-spkg-installcheck b/build/bin/sage-spkg-installcheck new file mode 100755 index 00000000000..a8ef89ba2aa --- /dev/null +++ b/build/bin/sage-spkg-installcheck @@ -0,0 +1,24 @@ +#!/usr/bin/env sage-bootstrap-python + +# usage: sage-spkg-installcheck [-h] PKG [SAGE_LOCAL] +# +# Check shared libraries that are part of an installed package. +# +# positional arguments: +# PKG the name of the package to uninstall +# SAGE_LOCAL the path to SAGE_LOCAL (default: value of the $SAGE_LOCAL +# environment variable if set; exits otherwise) +# +# optional arguments: +# -h, --help show this help message and exit + + +try: + import sage_bootstrap +except ImportError: + import os, sys + sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + import sage_bootstrap + +from sage_bootstrap.installcheck import run +run() diff --git a/build/bin/write-dockerfile.sh b/build/bin/write-dockerfile.sh index c64b9c76f2d..42eb5f53686 100755 --- a/build/bin/write-dockerfile.sh +++ b/build/bin/write-dockerfile.sh @@ -15,7 +15,7 @@ SAGE_ROOT=. export PATH="$SAGE_ROOT"/build/bin:$PATH SYSTEM_PACKAGES=$EXTRA_SYSTEM_PACKAGES CONFIGURE_ARGS="--enable-option-checking " -for PKG_BASE in $($SAGE_ROOT/sage -package list --has-file=distros/$SYSTEM.txt $SAGE_PACKAGE_LIST_ARGS) $EXTRA_SAGE_PACKAGES; do +for PKG_BASE in $(sage-package list --has-file=distros/$SYSTEM.txt $SAGE_PACKAGE_LIST_ARGS) $EXTRA_SAGE_PACKAGES; do PKG_SCRIPTS="$SAGE_ROOT"/build/pkgs/$PKG_BASE if [ -d $PKG_SCRIPTS ]; then SYSTEM_PACKAGES_FILE=$PKG_SCRIPTS/distros/$SYSTEM.txt @@ -33,18 +33,12 @@ echo "# the :comments: separate the generated file into sections" echo "# to simplify writing scripts that customize this file" ADD="ADD $__CHOWN" RUN=RUN -case $SYSTEM in - debian*|ubuntu*) - cat < /bin/dash; # but some of the scripts in /opt/conda/etc/conda/activate.d # from conda-forge (as of 2020-01-27) contain bash-isms: @@ -166,11 +162,16 @@ EOF exit 1 ;; esac -cat <&2 "# Checking $$stampfile"; \ + tree=$${stampfile%%/$(SPKG_INST_RELDIR)/*}; \ + package_with_version=$${stampfile##*/}; \ + package=$${package_with_version%%-*}; \ + if ! $(SAGE_VENV)/bin/python3 $(SAGE_ROOT)/build/bin/sage-spkg-installcheck --verbose $$package $$tree; then \ + case "$$tree" in \ + "$(SAGE_LOCAL)") echo " make $$package-SAGE_LOCAL-uninstall;";; \ + "$(SAGE_VENV)") echo " make $$package-SAGE_VENV-uninstall;";; \ + *) echo " ./sage --buildsh -c \"sage-spkg-uninstall $$package $$tree\";";; \ + esac; \ + fi; \ + fi; + +list-broken-packages: auditwheel_or_delocate + @fix_broken_packages=$$($(MAKE) -s $(patsubst %,%-installcheck,$(wildcard $(SAGE_LOCAL)/$(SPKG_INST_RELDIR)/* $(SAGE_VENV)/$(SPKG_INST_RELDIR)/*))); \ + if [ -n "$$fix_broken_packages" ]; then \ + echo >&2 ; \ + echo >&2 "Uninstall broken packages by typing:"; \ + echo >&2 ; \ + echo >&2 "$$fix_broken_packages"; \ + fi + + #============================================================================== # Setting SAGE_CHECK... variables #============================================================================== @@ -527,7 +554,7 @@ $(1)-$(4)-no-deps: echo "$$($(4)_DISABLED_MESSAGE)" 2>&1; \ exit 1; \ else \ - sage-logger -p 'SAGE_CHECK=$$(SAGE_CHECK_$(1)) PATH=$$($(4))/bin:$$$$PATH $$(SAGE_SPKG) $$(SAGE_SPKG_OPTIONS) \ + sage-logger -p 'SAGE_CHECK=$$(SAGE_CHECK_$(1)) PATH=$$(SAGE_SRC)/bin:$$($(4))/bin:$$$$PATH $$(SAGE_SPKG) $$(SAGE_SPKG_OPTIONS) \ $(if $(filter $(1),$(TOOLCHAIN_DEPS)),--keep-existing) \ $(1)-$(2) $$($(4))' '$$(SAGE_LOGS)/$(1)-$(2).log'; \ fi diff --git a/build/pkgs/4ti2/SPKG.rst b/build/pkgs/4ti2/SPKG.rst index 1a3a5e5c9c4..f3bd67741e9 100644 --- a/build/pkgs/4ti2/SPKG.rst +++ b/build/pkgs/4ti2/SPKG.rst @@ -5,7 +5,7 @@ Description ----------- A software package for algebraic, geometric and combinatorial problems -on linear spaces. Available at www.4ti2.de. +on linear spaces. Available at https://4ti2.github.io/. License ------- diff --git a/build/pkgs/_bootstrap/distros/arch.txt b/build/pkgs/_bootstrap/distros/arch.txt index 67bfbc0fae0..96fe92340d9 100644 --- a/build/pkgs/_bootstrap/distros/arch.txt +++ b/build/pkgs/_bootstrap/distros/arch.txt @@ -1,4 +1,4 @@ # Packages needed for ./bootstrap -gettext autoconf automake libtool +autoconf automake libtool # Otherwise: Error: could not locate the pkg-config autoconf macros. These are pkg-config diff --git a/build/pkgs/_bootstrap/distros/conda.txt b/build/pkgs/_bootstrap/distros/conda.txt index 5405f70cf3d..b5d2db8cfb2 100644 --- a/build/pkgs/_bootstrap/distros/conda.txt +++ b/build/pkgs/_bootstrap/distros/conda.txt @@ -1,2 +1,2 @@ # Packages needed for ./bootstrap -gettext autoconf automake libtool +autoconf automake libtool diff --git a/build/pkgs/_bootstrap/distros/cygwin.txt b/build/pkgs/_bootstrap/distros/cygwin.txt index 5cd20fd529c..b5d2db8cfb2 100644 --- a/build/pkgs/_bootstrap/distros/cygwin.txt +++ b/build/pkgs/_bootstrap/distros/cygwin.txt @@ -1,2 +1,2 @@ # Packages needed for ./bootstrap -gettext-devel autoconf automake libtool +autoconf automake libtool diff --git a/build/pkgs/_bootstrap/distros/debian.txt b/build/pkgs/_bootstrap/distros/debian.txt index d99fdbbceab..5fe960ac3a0 100644 --- a/build/pkgs/_bootstrap/distros/debian.txt +++ b/build/pkgs/_bootstrap/distros/debian.txt @@ -1,5 +1,4 @@ # Packages needed for ./bootstrap -gettext autoconf automake libtool diff --git a/build/pkgs/_bootstrap/distros/fedora.txt b/build/pkgs/_bootstrap/distros/fedora.txt index 97f16705319..e8e9330eca1 100644 --- a/build/pkgs/_bootstrap/distros/fedora.txt +++ b/build/pkgs/_bootstrap/distros/fedora.txt @@ -1,4 +1,4 @@ # Packages needed for ./bootstrap -gettext-devel autoconf automake libtool +autoconf automake libtool # Fedora 26 needs: pkg-config diff --git a/build/pkgs/_bootstrap/distros/freebsd.txt b/build/pkgs/_bootstrap/distros/freebsd.txt index 55f45bfd276..93d91f5c84b 100644 --- a/build/pkgs/_bootstrap/distros/freebsd.txt +++ b/build/pkgs/_bootstrap/distros/freebsd.txt @@ -1,2 +1,2 @@ # Packages needed for ./bootstrap -gettext autoconf automake libtool pkg-config +autoconf automake libtool pkg-config diff --git a/build/pkgs/_bootstrap/distros/homebrew.txt b/build/pkgs/_bootstrap/distros/homebrew.txt index 55f45bfd276..93d91f5c84b 100644 --- a/build/pkgs/_bootstrap/distros/homebrew.txt +++ b/build/pkgs/_bootstrap/distros/homebrew.txt @@ -1,2 +1,2 @@ # Packages needed for ./bootstrap -gettext autoconf automake libtool pkg-config +autoconf automake libtool pkg-config diff --git a/build/pkgs/_bootstrap/distros/nix.txt b/build/pkgs/_bootstrap/distros/nix.txt index 55f45bfd276..93d91f5c84b 100644 --- a/build/pkgs/_bootstrap/distros/nix.txt +++ b/build/pkgs/_bootstrap/distros/nix.txt @@ -1,2 +1,2 @@ # Packages needed for ./bootstrap -gettext autoconf automake libtool pkg-config +autoconf automake libtool pkg-config diff --git a/build/pkgs/_bootstrap/distros/opensuse.txt b/build/pkgs/_bootstrap/distros/opensuse.txt index 819e4433b53..48e74f8118e 100644 --- a/build/pkgs/_bootstrap/distros/opensuse.txt +++ b/build/pkgs/_bootstrap/distros/opensuse.txt @@ -1,5 +1,4 @@ # Packages needed for ./bootstrap -gettext-tools autoconf automake libtool diff --git a/build/pkgs/_bootstrap/distros/void.txt b/build/pkgs/_bootstrap/distros/void.txt index eefc9315cb5..159333b4216 100644 --- a/build/pkgs/_bootstrap/distros/void.txt +++ b/build/pkgs/_bootstrap/distros/void.txt @@ -1,4 +1,3 @@ # Packages needed for ./bootstrap -gettext autoconf automake libtool -gettext-devel +autoconf automake libtool xtools mk-configure diff --git a/build/pkgs/_develop/SPKG.rst b/build/pkgs/_develop/SPKG.rst new file mode 100644 index 00000000000..65166cca172 --- /dev/null +++ b/build/pkgs/_develop/SPKG.rst @@ -0,0 +1,7 @@ +\_develop: Represents system packages recommended for development +================================================================= + +Description +----------- + +Script package representing a list of system packages recommended for developers. diff --git a/build/pkgs/_develop/dependencies b/build/pkgs/_develop/dependencies new file mode 100644 index 00000000000..f50a34b8495 --- /dev/null +++ b/build/pkgs/_develop/dependencies @@ -0,0 +1 @@ +_bootstrap git pytest pytest_xdist diff --git a/build/pkgs/_develop/distros/alpine.txt b/build/pkgs/_develop/distros/alpine.txt new file mode 100644 index 00000000000..2b721fa34a4 --- /dev/null +++ b/build/pkgs/_develop/distros/alpine.txt @@ -0,0 +1,2 @@ +gnupg-gpgconf +openssh-client diff --git a/build/pkgs/_develop/distros/arch.txt b/build/pkgs/_develop/distros/arch.txt new file mode 100644 index 00000000000..59d48b3e43b --- /dev/null +++ b/build/pkgs/_develop/distros/arch.txt @@ -0,0 +1,2 @@ +gnupg +openssh diff --git a/build/pkgs/_develop/distros/conda.txt b/build/pkgs/_develop/distros/conda.txt new file mode 100644 index 00000000000..7eda6f9cb56 --- /dev/null +++ b/build/pkgs/_develop/distros/conda.txt @@ -0,0 +1,3 @@ +openssh +pycodestyle +esbonio diff --git a/build/pkgs/_develop/distros/cygwin.txt b/build/pkgs/_develop/distros/cygwin.txt new file mode 100644 index 00000000000..239772ed91d --- /dev/null +++ b/build/pkgs/_develop/distros/cygwin.txt @@ -0,0 +1 @@ +gnupg2 diff --git a/build/pkgs/_develop/distros/debian.txt b/build/pkgs/_develop/distros/debian.txt new file mode 100644 index 00000000000..5c3c17488e8 --- /dev/null +++ b/build/pkgs/_develop/distros/debian.txt @@ -0,0 +1,3 @@ +# Needed for devcontainer support in VS code +gpgconf +openssh-client diff --git a/build/pkgs/_develop/distros/fedora.txt b/build/pkgs/_develop/distros/fedora.txt new file mode 100644 index 00000000000..66e987c8959 --- /dev/null +++ b/build/pkgs/_develop/distros/fedora.txt @@ -0,0 +1,2 @@ +gnupg2 +openssh diff --git a/build/pkgs/_develop/distros/freebsd.txt b/build/pkgs/_develop/distros/freebsd.txt new file mode 100644 index 00000000000..8341acc726c --- /dev/null +++ b/build/pkgs/_develop/distros/freebsd.txt @@ -0,0 +1,2 @@ +security/gnupg +security/openssh-portable diff --git a/build/pkgs/_develop/distros/gentoo.txt b/build/pkgs/_develop/distros/gentoo.txt new file mode 100644 index 00000000000..7f306e1b41b --- /dev/null +++ b/build/pkgs/_develop/distros/gentoo.txt @@ -0,0 +1,2 @@ +app-crypt/gnupg +net-misc/openssh diff --git a/build/pkgs/_develop/distros/homebrew.txt b/build/pkgs/_develop/distros/homebrew.txt new file mode 100644 index 00000000000..a8ef4049b5d --- /dev/null +++ b/build/pkgs/_develop/distros/homebrew.txt @@ -0,0 +1 @@ +gnupg diff --git a/build/pkgs/_develop/distros/macports.txt b/build/pkgs/_develop/distros/macports.txt new file mode 100644 index 00000000000..239772ed91d --- /dev/null +++ b/build/pkgs/_develop/distros/macports.txt @@ -0,0 +1 @@ +gnupg2 diff --git a/build/pkgs/_develop/distros/nix.txt b/build/pkgs/_develop/distros/nix.txt new file mode 100644 index 00000000000..59d48b3e43b --- /dev/null +++ b/build/pkgs/_develop/distros/nix.txt @@ -0,0 +1,2 @@ +gnupg +openssh diff --git a/build/pkgs/_develop/distros/opensuse.txt b/build/pkgs/_develop/distros/opensuse.txt new file mode 100644 index 00000000000..641f598082e --- /dev/null +++ b/build/pkgs/_develop/distros/opensuse.txt @@ -0,0 +1,2 @@ +gpg2 +openssh diff --git a/build/pkgs/_develop/distros/repology.txt b/build/pkgs/_develop/distros/repology.txt new file mode 100644 index 00000000000..59d48b3e43b --- /dev/null +++ b/build/pkgs/_develop/distros/repology.txt @@ -0,0 +1,2 @@ +gnupg +openssh diff --git a/build/pkgs/_develop/distros/slackware.txt b/build/pkgs/_develop/distros/slackware.txt new file mode 100644 index 00000000000..66e987c8959 --- /dev/null +++ b/build/pkgs/_develop/distros/slackware.txt @@ -0,0 +1,2 @@ +gnupg2 +openssh diff --git a/build/pkgs/_develop/distros/void.txt b/build/pkgs/_develop/distros/void.txt new file mode 100644 index 00000000000..66e987c8959 --- /dev/null +++ b/build/pkgs/_develop/distros/void.txt @@ -0,0 +1,2 @@ +gnupg2 +openssh diff --git a/build/pkgs/_develop/spkg-configure.m4 b/build/pkgs/_develop/spkg-configure.m4 new file mode 100644 index 00000000000..dd308c02f47 --- /dev/null +++ b/build/pkgs/_develop/spkg-configure.m4 @@ -0,0 +1,3 @@ +SAGE_SPKG_CONFIGURE([_develop], [ + sage_spkg_install__develop=yes +]) diff --git a/build/pkgs/_develop/spkg-install b/build/pkgs/_develop/spkg-install new file mode 100755 index 00000000000..599e0b77a66 --- /dev/null +++ b/build/pkgs/_develop/spkg-install @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +# Nothing to do diff --git a/build/pkgs/_develop/type b/build/pkgs/_develop/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/_develop/type @@ -0,0 +1 @@ +optional diff --git a/build/pkgs/_gcc10/distros/opensuse.txt b/build/pkgs/_gcc10/distros/opensuse.txt new file mode 100644 index 00000000000..372f607e2ab --- /dev/null +++ b/build/pkgs/_gcc10/distros/opensuse.txt @@ -0,0 +1,3 @@ +gcc10 +gcc10-c++ +gcc10-fortran diff --git a/build/pkgs/_gcc11/distros/opensuse.txt b/build/pkgs/_gcc11/distros/opensuse.txt new file mode 100644 index 00000000000..467f354c123 --- /dev/null +++ b/build/pkgs/_gcc11/distros/opensuse.txt @@ -0,0 +1,3 @@ +gcc11 +gcc11-c++ +gcc11-fortran diff --git a/build/pkgs/_gcc8/distros/debian.txt b/build/pkgs/_gcc8/distros/debian.txt new file mode 100644 index 00000000000..3c56d438a9e --- /dev/null +++ b/build/pkgs/_gcc8/distros/debian.txt @@ -0,0 +1,3 @@ +gcc-8 +g++-8 +gfortran-8 diff --git a/build/pkgs/_gcc8/distros/opensuse.txt b/build/pkgs/_gcc8/distros/opensuse.txt new file mode 100644 index 00000000000..7a7764d2d5a --- /dev/null +++ b/build/pkgs/_gcc8/distros/opensuse.txt @@ -0,0 +1,3 @@ +gcc8 +gcc8-c++ +gcc8-fortran diff --git a/build/pkgs/_gcc8/type b/build/pkgs/_gcc8/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/_gcc8/type @@ -0,0 +1 @@ +optional diff --git a/build/pkgs/_gcc9/distros/opensuse.txt b/build/pkgs/_gcc9/distros/opensuse.txt new file mode 100644 index 00000000000..ea8e8a6bcaa --- /dev/null +++ b/build/pkgs/_gcc9/distros/opensuse.txt @@ -0,0 +1,3 @@ +gcc9 +gcc9-c++ +gcc9-fortran diff --git a/build/pkgs/_prereq/distros/gentoo.txt b/build/pkgs/_prereq/distros/gentoo.txt index 51316a87c0b..1e26c46cacc 100644 --- a/build/pkgs/_prereq/distros/gentoo.txt +++ b/build/pkgs/_prereq/distros/gentoo.txt @@ -14,8 +14,6 @@ sys-devel/bc dev-lang/python sys-devel/flex app-misc/ca-certificates -sys-devel/gettext -dev-libs/libcroco dev-libs/libxml2 sys-apps/findutils sys-apps/which diff --git a/build/pkgs/_prereq/distros/slackware.txt b/build/pkgs/_prereq/distros/slackware.txt index a8310031df7..4c2b7080ce8 100644 --- a/build/pkgs/_prereq/distros/slackware.txt +++ b/build/pkgs/_prereq/distros/slackware.txt @@ -15,7 +15,5 @@ flex # for https upstream_url downloads ca-certificates pkg-config -gettext-tools -libcroco # gettext dependency (msgfmt) libxml2 cyrus-sasl diff --git a/build/pkgs/appdirs/distros/conda.txt b/build/pkgs/appdirs/distros/conda.txt new file mode 100644 index 00000000000..d64bc321a11 --- /dev/null +++ b/build/pkgs/appdirs/distros/conda.txt @@ -0,0 +1 @@ +appdirs diff --git a/build/pkgs/appnope/distros/conda.txt b/build/pkgs/appnope/distros/conda.txt new file mode 100644 index 00000000000..010137fae0e --- /dev/null +++ b/build/pkgs/appnope/distros/conda.txt @@ -0,0 +1 @@ +appnope diff --git a/build/pkgs/argon2_cffi/distros/conda.txt b/build/pkgs/argon2_cffi/distros/conda.txt new file mode 100644 index 00000000000..d05a5eb79fb --- /dev/null +++ b/build/pkgs/argon2_cffi/distros/conda.txt @@ -0,0 +1 @@ +argon2-cffi diff --git a/build/pkgs/asttokens/distros/conda.txt b/build/pkgs/asttokens/distros/conda.txt new file mode 100644 index 00000000000..7adf4c51fd2 --- /dev/null +++ b/build/pkgs/asttokens/distros/conda.txt @@ -0,0 +1 @@ +asttokens diff --git a/build/pkgs/auditwheel_or_delocate/SPKG.rst b/build/pkgs/auditwheel_or_delocate/SPKG.rst new file mode 100644 index 00000000000..710ea0224e0 --- /dev/null +++ b/build/pkgs/auditwheel_or_delocate/SPKG.rst @@ -0,0 +1,25 @@ +auditwheel_or_delocate: Repair wheels on Linux or macOS +======================================================= + +Description +----------- + +This package represents ``auditwheel`` on Linux and ``delocate`` on macOS. + +(Actually, we install ``delocate`` also on Linux because our script +``make -j list-broken-packages`` uses a small subroutine of ``delocate`` +even on Linux.) + +License +------- + +MIT + +BSD 2-clause + +Upstream Contact +---------------- + +https://pypi.org/project/auditwheel/ + +https://pypi.org/project/delocate/ diff --git a/build/pkgs/auditwheel_or_delocate/dependencies b/build/pkgs/auditwheel_or_delocate/dependencies new file mode 100644 index 00000000000..0738c2d7777 --- /dev/null +++ b/build/pkgs/auditwheel_or_delocate/dependencies @@ -0,0 +1,4 @@ +$(PYTHON) | $(PYTHON_TOOLCHAIN) + +---------- +All lines of this file are ignored except the first. diff --git a/build/pkgs/auditwheel_or_delocate/requirements.txt b/build/pkgs/auditwheel_or_delocate/requirements.txt new file mode 100644 index 00000000000..5d409e519cd --- /dev/null +++ b/build/pkgs/auditwheel_or_delocate/requirements.txt @@ -0,0 +1,2 @@ +delocate +auditwheel; sys_platform != 'darwin' diff --git a/build/pkgs/auditwheel_or_delocate/type b/build/pkgs/auditwheel_or_delocate/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/auditwheel_or_delocate/type @@ -0,0 +1 @@ +optional diff --git a/build/pkgs/backports_zoneinfo/distros/conda.txt b/build/pkgs/backports_zoneinfo/distros/conda.txt new file mode 100644 index 00000000000..5a8be642f33 --- /dev/null +++ b/build/pkgs/backports_zoneinfo/distros/conda.txt @@ -0,0 +1 @@ +backports.zoneinfo diff --git a/build/pkgs/barvinok/type b/build/pkgs/barvinok/type index 134d9bc32d5..af4d63af86d 100644 --- a/build/pkgs/barvinok/type +++ b/build/pkgs/barvinok/type @@ -1 +1 @@ -optional +experimental \ No newline at end of file diff --git a/build/pkgs/beniget/distros/conda.txt b/build/pkgs/beniget/distros/conda.txt new file mode 100644 index 00000000000..8b5faaea7c2 --- /dev/null +++ b/build/pkgs/beniget/distros/conda.txt @@ -0,0 +1 @@ +beniget diff --git a/build/pkgs/charset_normalizer/distros/conda.txt b/build/pkgs/charset_normalizer/distros/conda.txt new file mode 100644 index 00000000000..5f964199cd0 --- /dev/null +++ b/build/pkgs/charset_normalizer/distros/conda.txt @@ -0,0 +1 @@ +charset-normalizer diff --git a/build/pkgs/configure/checksums.ini b/build/pkgs/configure/checksums.ini index 87f918fec86..d7e7fc0fcc1 100644 --- a/build/pkgs/configure/checksums.ini +++ b/build/pkgs/configure/checksums.ini @@ -1,4 +1,4 @@ tarball=configure-VERSION.tar.gz -sha1=0a748ea2730e013555fd8ea78bc76787290cc43f -md5=82d386f0395c2a38fc23964bd40b2cfc -cksum=2236984653 +sha1=c2e1c4db6762c4d97d5127f5f056e46fe3d5a94d +md5=70dcc35964e4234443c4e77beb2245d7 +cksum=2271434645 diff --git a/build/pkgs/configure/package-version.txt b/build/pkgs/configure/package-version.txt index 991f8b9c16d..abdbdc62467 100644 --- a/build/pkgs/configure/package-version.txt +++ b/build/pkgs/configure/package-version.txt @@ -1 +1 @@ -a3be8106604ed6a2c1d7d119e67674e3166a6eec +04fbc829e9850eedf555acc683666c55cb7052d7 diff --git a/build/pkgs/cppy/distros/conda.txt b/build/pkgs/cppy/distros/conda.txt new file mode 100644 index 00000000000..9d2b4aaeee0 --- /dev/null +++ b/build/pkgs/cppy/distros/conda.txt @@ -0,0 +1 @@ +cppy diff --git a/build/pkgs/curl/spkg-install.in b/build/pkgs/curl/spkg-install.in index a697d0b1cf8..a45fff11da1 100644 --- a/build/pkgs/curl/spkg-install.in +++ b/build/pkgs/curl/spkg-install.in @@ -4,7 +4,7 @@ CURL_CONFIGURE="--with-openssl $CURL_CONFIGURE" if [ "$SAGE_FAT_BINARY" = yes ]; then # Let's disable a bunch of stuff which might get linked. # SSL/TLS still depends on the compilation environment. - CURL_CONFIGURE="--disable-ldap --disable-ldaps --disable-rtsp --disable-ares --disable-crypto-auth --without-libpsl --without-libmetalink --without-libssh2 --without-librtmp --without-libidn --without-nghttp2 --without-gssapi $CURL_CONFIGURE" + CURL_CONFIGURE="--disable-ldap --disable-ldaps --disable-rtsp --disable-ares --disable-crypto-auth --without-libpsl --without-libssh2 --without-librtmp --without-libidn --without-nghttp2 --without-gssapi $CURL_CONFIGURE" fi sdh_configure $CURL_CONFIGURE diff --git a/build/pkgs/cython/package-version.txt b/build/pkgs/cython/package-version.txt index e413dadf0a3..6ca43241e34 100644 --- a/build/pkgs/cython/package-version.txt +++ b/build/pkgs/cython/package-version.txt @@ -1 +1 @@ -0.29.32 +0.29.32.p1 diff --git a/build/pkgs/cython/patches/4918.patch b/build/pkgs/cython/patches/4918.patch new file mode 100644 index 00000000000..a9dbb0a4a0c --- /dev/null +++ b/build/pkgs/cython/patches/4918.patch @@ -0,0 +1,287 @@ +From 0dade9353e06b052d0da5e512cdc2ce04061904a Mon Sep 17 00:00:00 2001 +From: Matthias Koeppe +Date: Sat, 23 Jul 2022 10:53:49 -0700 +Subject: [PATCH 1/3] Add PEP420 namespace support + +Backport of https://github.com/cython/cython/pull/2946 to 0.29.x +--- + Cython/Compiler/Main.py | 44 +++++++++++++++---- + Cython/Utils.py | 26 ++++++++--- + runtests.py | 1 + + .../build/cythonize_pep420_namespace.srctree | 44 +++++++++++++++++++ + 4 files changed, 99 insertions(+), 16 deletions(-) + create mode 100644 tests/build/cythonize_pep420_namespace.srctree + +diff --git a/Cython/Compiler/Main.py b/Cython/Compiler/Main.py +index dc4add541e..2be7a06a1c 100644 +--- a/Cython/Compiler/Main.py ++++ b/Cython/Compiler/Main.py +@@ -801,32 +801,58 @@ def search_include_directories(dirs, qualified_name, suffix, pos, include=False) + else: + dirs = (Utils.find_root_package_dir(file_desc.filename),) + dirs + ++ # search for dotted filename e.g. /foo.bar.pxd + dotted_filename = qualified_name + if suffix: + dotted_filename += suffix + ++ for dirname in dirs: ++ path = os.path.join(dirname, dotted_filename) ++ if os.path.exists(path): ++ return path ++ ++ # search for filename in package structure e.g. /foo/bar.pxd or /foo/bar/__init__.pxd + if not include: ++ + names = qualified_name.split('.') + package_names = tuple(names[:-1]) + module_name = names[-1] + module_filename = module_name + suffix + package_filename = "__init__" + suffix + +- for dirname in dirs: +- path = os.path.join(dirname, dotted_filename) +- if os.path.exists(path): +- return path +- +- if not include: +- package_dir = Utils.check_package_dir(dirname, package_names) ++ # search for standard packages first - PEP420 ++ namespace_dirs = [] ++ for dirname in dirs: ++ package_dir, is_namespace = Utils.check_package_dir(dirname, package_names) + if package_dir is not None: ++ ++ if is_namespace: ++ namespace_dirs.append(package_dir) ++ continue ++ ++ # matches modules of the form: /foo/bar.pxd + path = os.path.join(package_dir, module_filename) + if os.path.exists(path): + return path +- path = os.path.join(package_dir, module_name, +- package_filename) ++ ++ # matches modules of the form: /foo/bar/__init__.pxd ++ path = os.path.join(package_dir, module_name, package_filename) + if os.path.exists(path): + return path ++ ++ # search for namespaces second - PEP420 ++ for package_dir in namespace_dirs: ++ ++ # matches modules of the form: /foo/bar.pxd ++ path = os.path.join(package_dir, module_filename) ++ if os.path.exists(path): ++ return path ++ ++ # matches modules of the form: /foo/bar/__init__.pxd ++ path = os.path.join(package_dir, module_name, package_filename) ++ if os.path.exists(path): ++ return path ++ + return None + + +diff --git a/Cython/Utils.py b/Cython/Utils.py +index d59d67d78b..305ebf8412 100644 +--- a/Cython/Utils.py ++++ b/Cython/Utils.py +@@ -134,16 +134,21 @@ def find_root_package_dir(file_path): + else: + return dir + ++ + @cached_function +-def check_package_dir(dir, package_names): ++def check_package_dir(dir_path, package_names): ++ namespace = True + for dirname in package_names: +- dir = os.path.join(dir, dirname) +- if not is_package_dir(dir): +- return None +- return dir ++ dir_path = os.path.join(dir_path, dirname) ++ has_init = contains_init(dir_path) ++ if not namespace and not has_init: ++ return None, False ++ elif has_init: ++ namespace = False ++ return dir_path, namespace + +-@cached_function +-def is_package_dir(dir_path): ++ ++def contains_init(dir_path): + for filename in ("__init__.py", + "__init__.pyc", + "__init__.pyx", +@@ -152,6 +157,13 @@ def is_package_dir(dir_path): + if path_exists(path): + return 1 + ++ ++@cached_function ++def is_package_dir(dir_path): ++ if contains_init(dir_path): ++ return 1 ++ ++ + @cached_function + def path_exists(path): + # try on the filesystem first +diff --git a/runtests.py b/runtests.py +index 91a0dd2570..7d04463846 100755 +--- a/runtests.py ++++ b/runtests.py +@@ -415,6 +415,7 @@ def get_openmp_compiler_flags(language): + 'run.special_methods_T561_py2' + ]), + (3,3) : (operator.lt, lambda x: x in ['build.package_compilation', ++ 'build.cythonize_pep420_namespace', + 'run.yield_from_py33', + 'pyximport.pyximport_namespace', + ]), +diff --git a/tests/build/cythonize_pep420_namespace.srctree b/tests/build/cythonize_pep420_namespace.srctree +new file mode 100644 +index 0000000000..6a031e4170 +--- /dev/null ++++ b/tests/build/cythonize_pep420_namespace.srctree +@@ -0,0 +1,44 @@ ++PYTHON setup.py build_ext --inplace ++PYTHON -c "import runner" ++ ++######## setup.py ######## ++ ++from Cython.Build.Dependencies import cythonize ++ ++from distutils.core import setup, Extension ++ ++setup( ++ ext_modules=cythonize([ ++ Extension("nsp.m1.a", ["nsp/m1/a.pyx"]), ++ Extension("nsp.m2.b", ["nsp/m2/b.pyx"]) ++ ]), ++) ++ ++######## nsp/m1/__init__.py ######## ++ ++######## nsp/m1/a.pyx ######## ++ ++cdef class A: ++ pass ++ ++######## nsp/m1/a.pxd ######## ++ ++cdef class A: ++ pass ++ ++######## nsp/m2/__init__.py ######## ++ ++######## nsp/m2/b.pyx ######## ++ ++from nsp.m1.a cimport A ++ ++cdef class B(A): ++ pass ++ ++######## runner.py ######## ++ ++from nsp.m1.a import A ++from nsp.m2.b import B ++ ++a = A() ++b = B() + +From fcd3e7bd1351a0964704f2921ae33b4b57250668 Mon Sep 17 00:00:00 2001 +From: Fedor Alekseev +Date: Mon, 9 Nov 2020 14:34:01 +0300 +Subject: [PATCH 2/3] Support namespace packages inside regular packages + +--- + Cython/Utils.py | 4 +--- + tests/build/cythonize_pep420_namespace.srctree | 17 ++++++++++++++++- + 2 files changed, 17 insertions(+), 4 deletions(-) + +diff --git a/Cython/Utils.py b/Cython/Utils.py +index 305ebf8412..f21d5cddba 100644 +--- a/Cython/Utils.py ++++ b/Cython/Utils.py +@@ -141,9 +141,7 @@ def check_package_dir(dir_path, package_names): + for dirname in package_names: + dir_path = os.path.join(dir_path, dirname) + has_init = contains_init(dir_path) +- if not namespace and not has_init: +- return None, False +- elif has_init: ++ if has_init: + namespace = False + return dir_path, namespace + +diff --git a/tests/build/cythonize_pep420_namespace.srctree b/tests/build/cythonize_pep420_namespace.srctree +index 6a031e4170..04013a3004 100644 +--- a/tests/build/cythonize_pep420_namespace.srctree ++++ b/tests/build/cythonize_pep420_namespace.srctree +@@ -10,7 +10,8 @@ from distutils.core import setup, Extension + setup( + ext_modules=cythonize([ + Extension("nsp.m1.a", ["nsp/m1/a.pyx"]), +- Extension("nsp.m2.b", ["nsp/m2/b.pyx"]) ++ Extension("nsp.m2.b", ["nsp/m2/b.pyx"]), ++ Extension("nsp.m3.c.d", ["nsp/m3/c/d.pyx"]) + ]), + ) + +@@ -31,14 +32,28 @@ cdef class A: + ######## nsp/m2/b.pyx ######## + + from nsp.m1.a cimport A ++from nsp.m3.c.d cimport D + + cdef class B(A): + pass + ++######## nsp/m3/__init__.py ######## ++ ++######## nsp/m3/c/d.pyx ######## ++ ++cdef class D: ++ pass ++ ++######## nsp/m3/c/d.pxd ######## ++ ++cdef class D: ++ pass ++ + ######## runner.py ######## + + from nsp.m1.a import A + from nsp.m2.b import B ++from nsp.m3.c.d import D + + a = A() + b = B() + +From 9d61b95a6a71a4c88a51ab8f30d5b3a8b93998b8 Mon Sep 17 00:00:00 2001 +From: scoder +Date: Sat, 14 Nov 2020 09:42:09 +0100 +Subject: [PATCH 3/3] Anticipate future changes. + +--- + tests/build/cythonize_pep420_namespace.srctree | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tests/build/cythonize_pep420_namespace.srctree b/tests/build/cythonize_pep420_namespace.srctree +index 04013a3004..99649376a3 100644 +--- a/tests/build/cythonize_pep420_namespace.srctree ++++ b/tests/build/cythonize_pep420_namespace.srctree +@@ -11,7 +11,7 @@ setup( + ext_modules=cythonize([ + Extension("nsp.m1.a", ["nsp/m1/a.pyx"]), + Extension("nsp.m2.b", ["nsp/m2/b.pyx"]), +- Extension("nsp.m3.c.d", ["nsp/m3/c/d.pyx"]) ++ Extension("nsp.m3.c.d", ["nsp/m3/c/d.pyx"]), + ]), + ) + diff --git a/build/pkgs/database_cubic_hecke/SPKG.rst b/build/pkgs/database_cubic_hecke/SPKG.rst new file mode 100644 index 00000000000..44a02b3aef5 --- /dev/null +++ b/build/pkgs/database_cubic_hecke/SPKG.rst @@ -0,0 +1,18 @@ +database_cubic_hecke: Ivan Marin's representations of the cubic Hecke algebra +============================================================================= + +Description +----------- + +Ivan Marin's representations of the cubic Hecke algebra on 4 strands as Python dictionaries + +License +------- + +GPL + +Upstream Contact +---------------- + +https://pypi.org/project/database-cubic-hecke/ + diff --git a/build/pkgs/database_cubic_hecke/checksums.ini b/build/pkgs/database_cubic_hecke/checksums.ini new file mode 100644 index 00000000000..b2119998ee2 --- /dev/null +++ b/build/pkgs/database_cubic_hecke/checksums.ini @@ -0,0 +1,5 @@ +tarball=database_cubic_hecke-VERSION.tar.gz +sha1=f78ae31202fe077177f2c5059c028f9d40c20a46 +md5=4f83516e155515f17ebd88c56bc0f31b +cksum=3948466130 +upstream_url=https://pypi.io/packages/source/d/database_cubic_hecke/database_cubic_hecke-VERSION.tar.gz diff --git a/build/pkgs/database_cubic_hecke/dependencies b/build/pkgs/database_cubic_hecke/dependencies new file mode 100644 index 00000000000..0738c2d7777 --- /dev/null +++ b/build/pkgs/database_cubic_hecke/dependencies @@ -0,0 +1,4 @@ +$(PYTHON) | $(PYTHON_TOOLCHAIN) + +---------- +All lines of this file are ignored except the first. diff --git a/build/pkgs/database_cubic_hecke/dependencies_check b/build/pkgs/database_cubic_hecke/dependencies_check new file mode 100644 index 00000000000..1e1a85bb91a --- /dev/null +++ b/build/pkgs/database_cubic_hecke/dependencies_check @@ -0,0 +1 @@ +$(SAGERUNTIME) conway_polynomials ipywidgets sympy singular gap libhomfly libbraiding matplotlib diff --git a/build/pkgs/database_cubic_hecke/install-requires.txt b/build/pkgs/database_cubic_hecke/install-requires.txt new file mode 100644 index 00000000000..9aa14e88ee5 --- /dev/null +++ b/build/pkgs/database_cubic_hecke/install-requires.txt @@ -0,0 +1 @@ +database-cubic-hecke diff --git a/build/pkgs/database_cubic_hecke/package-version.txt b/build/pkgs/database_cubic_hecke/package-version.txt new file mode 100644 index 00000000000..7fd3fd9ac54 --- /dev/null +++ b/build/pkgs/database_cubic_hecke/package-version.txt @@ -0,0 +1 @@ +2022.3.1 diff --git a/build/pkgs/database_cubic_hecke/spkg-check.in b/build/pkgs/database_cubic_hecke/spkg-check.in new file mode 100644 index 00000000000..1929d7e3d69 --- /dev/null +++ b/build/pkgs/database_cubic_hecke/spkg-check.in @@ -0,0 +1,11 @@ +cd $SAGE_ROOT/src/sage/ + +OPTIONS="sage,database_cubic_hecke" +CHEVIE=$($SAGE_ROOT/sage -c "from sage.combinat.root_system.reflection_group_real import is_chevie_available; print(is_chevie_available())") +if [ $CHEVIE = "True" ]; then + OPTIONS=$OPTIONS",gap3" +fi + +FILES="databases/cubic_hecke_db.py algebras/hecke_algebras/" +echo "Testing: "$FILES +sage -tp --long --optional=$OPTIONS $FILES || sdh_die "Error testing cubic Hecke algebra database" diff --git a/build/pkgs/database_cubic_hecke/spkg-install.in b/build/pkgs/database_cubic_hecke/spkg-install.in new file mode 100644 index 00000000000..37ac1a53437 --- /dev/null +++ b/build/pkgs/database_cubic_hecke/spkg-install.in @@ -0,0 +1,2 @@ +cd src +sdh_pip_install . diff --git a/build/pkgs/database_cubic_hecke/type b/build/pkgs/database_cubic_hecke/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/database_cubic_hecke/type @@ -0,0 +1 @@ +optional diff --git a/build/pkgs/debugpy/distros/conda.txt b/build/pkgs/debugpy/distros/conda.txt new file mode 100644 index 00000000000..2802a6b1b1c --- /dev/null +++ b/build/pkgs/debugpy/distros/conda.txt @@ -0,0 +1 @@ +debugpy diff --git a/build/pkgs/deprecation/distros/conda.txt b/build/pkgs/deprecation/distros/conda.txt new file mode 100644 index 00000000000..4ba9b7530ed --- /dev/null +++ b/build/pkgs/deprecation/distros/conda.txt @@ -0,0 +1 @@ +deprecation diff --git a/build/pkgs/distlib/distros/conda.txt b/build/pkgs/distlib/distros/conda.txt new file mode 100644 index 00000000000..f68bb07272d --- /dev/null +++ b/build/pkgs/distlib/distros/conda.txt @@ -0,0 +1 @@ +distlib diff --git a/build/pkgs/dot2tex/distros/conda.txt b/build/pkgs/dot2tex/distros/conda.txt new file mode 100644 index 00000000000..4d0a832a550 --- /dev/null +++ b/build/pkgs/dot2tex/distros/conda.txt @@ -0,0 +1 @@ +dot2tex diff --git a/build/pkgs/ecm/SPKG.rst b/build/pkgs/ecm/SPKG.rst index ae289680f96..9815c59cac9 100644 --- a/build/pkgs/ecm/SPKG.rst +++ b/build/pkgs/ecm/SPKG.rst @@ -17,7 +17,7 @@ LGPL V3+ Upstream Contact ---------------- -- ecm-discuss@lists.gforge.inria.fr (requires subscription) +- ecm-discuss@inria.fr Special Update/Build Instructions --------------------------------- diff --git a/build/pkgs/editables/distros/conda.txt b/build/pkgs/editables/distros/conda.txt new file mode 100644 index 00000000000..35c51715e64 --- /dev/null +++ b/build/pkgs/editables/distros/conda.txt @@ -0,0 +1 @@ +editables diff --git a/build/pkgs/executing/distros/conda.txt b/build/pkgs/executing/distros/conda.txt new file mode 100644 index 00000000000..a920f2c56c3 --- /dev/null +++ b/build/pkgs/executing/distros/conda.txt @@ -0,0 +1 @@ +executing diff --git a/build/pkgs/fastjsonschema/distros/conda.txt b/build/pkgs/fastjsonschema/distros/conda.txt new file mode 100644 index 00000000000..7a8bdf5369b --- /dev/null +++ b/build/pkgs/fastjsonschema/distros/conda.txt @@ -0,0 +1 @@ +python-fastjsonschema diff --git a/build/pkgs/filelock/distros/conda.txt b/build/pkgs/filelock/distros/conda.txt new file mode 100644 index 00000000000..83c2e35706e --- /dev/null +++ b/build/pkgs/filelock/distros/conda.txt @@ -0,0 +1 @@ +filelock diff --git a/build/pkgs/flit_core/distros/conda.txt b/build/pkgs/flit_core/distros/conda.txt new file mode 100644 index 00000000000..14ccfd92035 --- /dev/null +++ b/build/pkgs/flit_core/distros/conda.txt @@ -0,0 +1 @@ +flit-core diff --git a/build/pkgs/fonttools/distros/conda.txt b/build/pkgs/fonttools/distros/conda.txt new file mode 100644 index 00000000000..d32bfca1a29 --- /dev/null +++ b/build/pkgs/fonttools/distros/conda.txt @@ -0,0 +1 @@ +fonttools diff --git a/build/pkgs/furo/checksums.ini b/build/pkgs/furo/checksums.ini new file mode 100644 index 00000000000..2ba1678635d --- /dev/null +++ b/build/pkgs/furo/checksums.ini @@ -0,0 +1,5 @@ +tarball=furo-VERSION-py3-none-any.whl +sha1=b9261dbe404cc13d399d50db0122fe48d7daeb23 +md5=fb331872d4d8a7d33f56aeb5df1f333f +cksum=3430203884 +upstream_url=https://pypi.io/packages/py3/f/furo/furo-VERSION-py3-none-any.whl diff --git a/build/pkgs/furo/dependencies b/build/pkgs/furo/dependencies index 9544f8d9e55..fec067b2058 100644 --- a/build/pkgs/furo/dependencies +++ b/build/pkgs/furo/dependencies @@ -1,4 +1,4 @@ -$(PYTHON) beautifulsoup4 sphinx pygments | $(PYTHON_TOOLCHAIN) +$(PYTHON) beautifulsoup4 sphinx pygments sphinx_basic_ng | $(PYTHON_TOOLCHAIN) ---------- All lines of this file are ignored except the first. diff --git a/build/pkgs/furo/requirements.txt b/build/pkgs/furo/install-requires.txt similarity index 100% rename from build/pkgs/furo/requirements.txt rename to build/pkgs/furo/install-requires.txt diff --git a/build/pkgs/furo/package-version.txt b/build/pkgs/furo/package-version.txt new file mode 100644 index 00000000000..95f879eb816 --- /dev/null +++ b/build/pkgs/furo/package-version.txt @@ -0,0 +1 @@ +2022.6.21 diff --git a/build/pkgs/furo/spkg-install.in b/build/pkgs/furo/spkg-install.in new file mode 100644 index 00000000000..37ac1a53437 --- /dev/null +++ b/build/pkgs/furo/spkg-install.in @@ -0,0 +1,2 @@ +cd src +sdh_pip_install . diff --git a/build/pkgs/furo/type b/build/pkgs/furo/type index 134d9bc32d5..a6a7b9cd726 100644 --- a/build/pkgs/furo/type +++ b/build/pkgs/furo/type @@ -1 +1 @@ -optional +standard diff --git a/build/pkgs/gast/distros/conda.txt b/build/pkgs/gast/distros/conda.txt new file mode 100644 index 00000000000..beb259c8453 --- /dev/null +++ b/build/pkgs/gast/distros/conda.txt @@ -0,0 +1 @@ +gast diff --git a/build/pkgs/gcc/spkg-configure.m4 b/build/pkgs/gcc/spkg-configure.m4 index 959e499b68a..63335eb7357 100644 --- a/build/pkgs/gcc/spkg-configure.m4 +++ b/build/pkgs/gcc/spkg-configure.m4 @@ -161,8 +161,8 @@ SAGE_SPKG_CONFIGURE_BASE([gcc], [ # Add the .0 because Debian/Ubuntu gives version numbers like # 4.6 instead of 4.6.4 (Trac #18885) AS_CASE(["$GXX_VERSION.0"], - [[[0-5]].*|6.[[0-2]].*], [ - # Install our own GCC if the system-provided one is older than gcc-6.3 + [[[0-7]].*], [ + # Install our own GCC if the system-provided one is older than gcc 8 SAGE_SHOULD_INSTALL_GCC([you have $CXX version $GXX_VERSION, which is quite old]) ], [1[[3-9]].*], [ diff --git a/build/pkgs/gf2x/SPKG.rst b/build/pkgs/gf2x/SPKG.rst index 7f4b11e994f..d8b4694eb45 100644 --- a/build/pkgs/gf2x/SPKG.rst +++ b/build/pkgs/gf2x/SPKG.rst @@ -8,7 +8,7 @@ gf2x is a C/C++ software package containing routines for fast arithmetic in GF(2)[x] (multiplication, squaring, GCD) and searching for irreducible/primitive trinomials. -Website: http://gf2x.gforge.inria.fr/ +Website: https://gitlab.inria.fr/gf2x/gf2x License ------- diff --git a/build/pkgs/gnulib/SPKG.rst b/build/pkgs/gnulib/SPKG.rst new file mode 100644 index 00000000000..6d4ea0f4d87 --- /dev/null +++ b/build/pkgs/gnulib/SPKG.rst @@ -0,0 +1,9 @@ +gnulib: Modules imported from Gnulib +==================================== + +This script package represents the modules imported into the Sage source tree from Gnulib. + +Upstream Contact +---------------- + +https://www.gnu.org/software/gnulib/ diff --git a/build/pkgs/gnulib/package-version.txt b/build/pkgs/gnulib/package-version.txt new file mode 100644 index 00000000000..a64fb36670b --- /dev/null +++ b/build/pkgs/gnulib/package-version.txt @@ -0,0 +1 @@ +f9b39c4e337f1dc0dd07c4f3985c476fb875d799 diff --git a/build/pkgs/gnulib/spkg-install b/build/pkgs/gnulib/spkg-install new file mode 100755 index 00000000000..599e0b77a66 --- /dev/null +++ b/build/pkgs/gnulib/spkg-install @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +# Nothing to do diff --git a/build/pkgs/gnulib/spkg-src b/build/pkgs/gnulib/spkg-src new file mode 100755 index 00000000000..98eb7f4e561 --- /dev/null +++ b/build/pkgs/gnulib/spkg-src @@ -0,0 +1,40 @@ +#! /usr/bin/env bash + +# the commit hash below is the latest as of 2022-07-31 +# For updating gnulib, change this hash and run +# +# ./sage -sh -c 'build/pkgs/gnulib/spkg-src' +# +# Then commit the results. + +gnulibcommit="f9b39c4e337f1dc0dd07c4f3985c476fb875d799" + +gnulibtool="$SAGE_ROOT/upstream/gnulib/gnulib-tool" + +if [ -x "$gnulibtool" ]; then + cd upstream/gnulib + gnulibinstalled=`git rev-parse HEAD` + cd ../.. + if [ ! x${gnulibcommit} = x${gnulibinstalled} ]; then + rm -rf upstream/gnulib + fi +fi +if [ ! -x "$gnulibtool" ]; then + echo >&2 "gnulib-tool needs to be installed and in your PATH. We install a copy in upstream/ " + mkdir -p upstream + cd upstream # use github mirror - faster, only needed stuff + mkdir -p gnulib + cd gnulib + eval git init ${quietquiet} + eval git remote add origin https://github.com/coreutils/gnulib.git ${quietquiet} + eval git fetch origin ${gnulibcommit} --depth 1 ${quietquiet} + eval git checkout FETCH_HEAD ${quietquiet} + cd ../.. + if [ ! -x "$gnulibtool" ]; then + echo >&2 "Failure installing/updating gnulib in upstream/. " + exit 179 + fi +fi +eval $gnulibtool --import iconv ${quietquiet} +rm -rf lib/ +echo "${gnulibcommit}" > build/pkgs/gnulib/package-version.txt diff --git a/build/pkgs/gnulib/type b/build/pkgs/gnulib/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/gnulib/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/hatchling/distros/conda.txt b/build/pkgs/hatchling/distros/conda.txt new file mode 100644 index 00000000000..1685d03c212 --- /dev/null +++ b/build/pkgs/hatchling/distros/conda.txt @@ -0,0 +1 @@ +hatchling diff --git a/build/pkgs/idna/distros/conda.txt b/build/pkgs/idna/distros/conda.txt new file mode 100644 index 00000000000..c40472e6fc2 --- /dev/null +++ b/build/pkgs/idna/distros/conda.txt @@ -0,0 +1 @@ +idna diff --git a/build/pkgs/igraph/checksums.ini b/build/pkgs/igraph/checksums.ini index 1e09d661bd2..9da572238e5 100644 --- a/build/pkgs/igraph/checksums.ini +++ b/build/pkgs/igraph/checksums.ini @@ -1,5 +1,5 @@ tarball=igraph-VERSION.tar.gz -sha1=66da9978e789e996e4b85cf970ab46e84dc971d7 -md5=fb0c24794bb88e88d4874802b78684bf -cksum=2298757908 +sha1=74abe82bdebdefc295a4f04b54170880dcb265e8 +md5=4e61e5e86ebe4fe478df415b7407e87e +cksum=2574937983 upstream_url=https://github.com/igraph/igraph/releases/download/VERSION/igraph-VERSION.tar.gz diff --git a/build/pkgs/igraph/dependencies b/build/pkgs/igraph/dependencies index 267354a48f6..a36c43a6a02 100644 --- a/build/pkgs/igraph/dependencies +++ b/build/pkgs/igraph/dependencies @@ -1,4 +1,4 @@ -$(MP_LIBRARY) glpk $(BLAS) suitesparse | cmake +$(MP_LIBRARY) glpk $(BLAS) | cmake ---------- All lines of this file are ignored except the first. diff --git a/build/pkgs/igraph/package-version.txt b/build/pkgs/igraph/package-version.txt index c81aa44afbf..571215736a6 100644 --- a/build/pkgs/igraph/package-version.txt +++ b/build/pkgs/igraph/package-version.txt @@ -1 +1 @@ -0.9.7 +0.10.1 diff --git a/build/pkgs/igraph/spkg-configure.m4 b/build/pkgs/igraph/spkg-configure.m4 index 441c926cdf6..30b5fcf9c53 100644 --- a/build/pkgs/igraph/spkg-configure.m4 +++ b/build/pkgs/igraph/spkg-configure.m4 @@ -1,7 +1,7 @@ SAGE_SPKG_CONFIGURE([igraph], [ SAGE_SPKG_DEPCHECK([glpk openblas gmp], [ dnl check for igraph with pkg-config - PKG_CHECK_MODULES([IGRAPH], [igraph >= 0.9.5], [], [ + PKG_CHECK_MODULES([IGRAPH], [igraph >= 0.10 igraph < 0.11], [], [ sage_spkg_install_igraph=yes]) ]) ]) diff --git a/build/pkgs/importlib_resources/distros/conda.txt b/build/pkgs/importlib_resources/distros/conda.txt new file mode 100644 index 00000000000..2b0146fc669 --- /dev/null +++ b/build/pkgs/importlib_resources/distros/conda.txt @@ -0,0 +1 @@ +importlib-resources diff --git a/build/pkgs/jupyter_jsmol/distros/conda.txt b/build/pkgs/jupyter_jsmol/distros/conda.txt new file mode 100644 index 00000000000..9465bfb8e0c --- /dev/null +++ b/build/pkgs/jupyter_jsmol/distros/conda.txt @@ -0,0 +1 @@ +jupyter-jsmol diff --git a/build/pkgs/jupyter_jsmol/spkg-install.in b/build/pkgs/jupyter_jsmol/spkg-install.in index 336f1829b2b..72d65dde8d5 100644 --- a/build/pkgs/jupyter_jsmol/spkg-install.in +++ b/build/pkgs/jupyter_jsmol/spkg-install.in @@ -1,2 +1,3 @@ cd src -eval sdh_pip_install $(eval sdh_prefix_args "--build-option" build --skip-npm) . +# Use --no-build-isolation because we have a different version of jupyter_packaging +eval sdh_pip_install --no-build-isolation --config-settings "--global-option=--skip-npm" . diff --git a/build/pkgs/jupyterlab_pygments/distros/conda.txt b/build/pkgs/jupyterlab_pygments/distros/conda.txt new file mode 100644 index 00000000000..23254749a23 --- /dev/null +++ b/build/pkgs/jupyterlab_pygments/distros/conda.txt @@ -0,0 +1 @@ +jupyterlab_pygments diff --git a/build/pkgs/latte_int/patches/6dbf7f07d5c9e1f3afe793f782d191d4465088ae.patch b/build/pkgs/latte_int/patches/6dbf7f07d5c9e1f3afe793f782d191d4465088ae.patch new file mode 100644 index 00000000000..308456304d7 --- /dev/null +++ b/build/pkgs/latte_int/patches/6dbf7f07d5c9e1f3afe793f782d191d4465088ae.patch @@ -0,0 +1,79 @@ +From 6dbf7f07d5c9e1f3afe793f782d191d4465088ae Mon Sep 17 00:00:00 2001 +From: Matthias Koeppe +Date: Thu, 7 Jul 2022 15:42:35 -0700 +Subject: [PATCH] Remove dynamic exception specifications to conform to ISO + C++17 + +--- + code/latte/ExponentialSubst.cpp | 2 -- + code/latte/ExponentialSubst.h | 6 ++---- + code/latte/sqlite/IntegrationDB.cpp | 2 +- + code/latte/sqlite/IntegrationDB.h | 2 +- + 4 files changed, 4 insertions(+), 8 deletions(-) + +diff --git a/code/latte/ExponentialSubst.cpp b/code/latte/ExponentialSubst.cpp +index a839b820..bcbfa934 100644 +--- a/code/latte/ExponentialSubst.cpp ++++ b/code/latte/ExponentialSubst.cpp +@@ -57,7 +57,6 @@ mpq_vector + computeExponentialResidueWeights(const vec_ZZ &generic_vector, + mpz_class &prod_ray_scalar_products, + const listCone *cone, int numOfVars) +- throw(NotGenericException) + { + // Compute dimension; can be smaller than numOfVars + int dimension = 0; +@@ -95,7 +94,6 @@ computeExponentialResidueWeights(const vec_ZZ &generic_vector, + mpq_vector + computeExponentialResidueWeights(const vec_ZZ &generic_vector, + const listCone *cone, int numOfVars) +- throw(NotGenericException) + { + mpz_class prod_ray_scalar_products; + return computeExponentialResidueWeights(generic_vector, +diff --git a/code/latte/ExponentialSubst.h b/code/latte/ExponentialSubst.h +index c9fa4ace..43a4ab63 100644 +--- a/code/latte/ExponentialSubst.h ++++ b/code/latte/ExponentialSubst.h +@@ -58,13 +58,11 @@ class Exponential_Single_Cone_Parameters + mpq_vector /* FIXME: This version can probably go away */ + computeExponentialResidueWeights(const vec_ZZ &generic_vector, + mpz_class &prod_ray_scalar_products, +- const listCone *cone, int numOfVars) +- throw(NotGenericException); ++ const listCone *cone, int numOfVars); + + mpq_vector + computeExponentialResidueWeights(const vec_ZZ &generic_vector, +- const listCone *cone, int numOfVars) +- throw(NotGenericException); ++ const listCone *cone, int numOfVars); + + ZZ + scalar_power(const vec_ZZ &generic_vector, +diff --git a/code/latte/sqlite/IntegrationDB.cpp b/code/latte/sqlite/IntegrationDB.cpp +index ab8df535..c1dde830 100644 +--- a/code/latte/sqlite/IntegrationDB.cpp ++++ b/code/latte/sqlite/IntegrationDB.cpp +@@ -1277,7 +1277,7 @@ void IntegrationDB::insertSpecficPolytopeIntegrationTest(string polymakeFile, i + * @parm filePath: to the latte-style polynomial. + * @return rowid of the inserted row. + */ +-int IntegrationDB::insertPolynomial(int dim, int degree, const char*filePath) throw(SqliteDBexception) ++int IntegrationDB::insertPolynomial(int dim, int degree, const char*filePath) + { + if ( doesPolynomialExist(filePath)) + throw SqliteDBexception(string("insertPolynomial::Polynomial ")+filePath+" already exist"); +diff --git a/code/latte/sqlite/IntegrationDB.h b/code/latte/sqlite/IntegrationDB.h +index d690a832..ce8cfac6 100644 +--- a/code/latte/sqlite/IntegrationDB.h ++++ b/code/latte/sqlite/IntegrationDB.h +@@ -67,7 +67,7 @@ class IntegrationDB: public SqliteDB + int insertIntegrationTest(int polynomialID, int polytopeID); + void insertIntegrationTest(int dim, int degree, int vertexCount, int count); + void insertSpecficPolytopeIntegrationTest(string polymakeFile, int degree, int count); +- int insertPolynomial(int dim, int degree, const char*filePath) throw(SqliteDBexception); ++ int insertPolynomial(int dim, int degree, const char*filePath); + + int insertPolytope(int dim, int vertexCount, int simple, int dualRowID, const char* latteFilePath, const char* polymakeFilePath); + diff --git a/build/pkgs/mathics/distros/conda.txt b/build/pkgs/mathics/distros/conda.txt new file mode 100644 index 00000000000..800ac5e8aa4 --- /dev/null +++ b/build/pkgs/mathics/distros/conda.txt @@ -0,0 +1 @@ +mathics3 diff --git a/build/pkgs/matplotlib/make-setup-config.py b/build/pkgs/matplotlib/make-setup-config.py index 98450dec2fd..4f9acf1f04c 100644 --- a/build/pkgs/matplotlib/make-setup-config.py +++ b/build/pkgs/matplotlib/make-setup-config.py @@ -1,5 +1,4 @@ from configparser import ConfigParser -import pkgconfig import os config = ConfigParser() @@ -23,7 +22,7 @@ print("NOTE: Set SAGE_MATPLOTLIB_GUI to anything but 'no' to try to build the Matplotlib GUI.") -graphical_backend='False' +graphical_backend = 'False' if os.environ.get('SAGE_MATPLOTLIB_GUI', 'no').lower() != 'no': graphical_backend = 'auto' @@ -35,7 +34,7 @@ config.add_section('gui_support') for backend in ('gtk', 'gtkagg', 'tkagg', 'wxagg', 'macosx', 'windowing'): - config.set('gui_support', backend, graphical_backend) + config.set('gui_support', backend, graphical_backend) with open('src/mplsetup.cfg', 'w') as configfile: config.write(configfile) diff --git a/build/pkgs/matplotlib_inline/distros/conda.txt b/build/pkgs/matplotlib_inline/distros/conda.txt new file mode 100644 index 00000000000..7b78209dd96 --- /dev/null +++ b/build/pkgs/matplotlib_inline/distros/conda.txt @@ -0,0 +1 @@ +matplotlib-inline diff --git a/build/pkgs/modular_resolution/SPKG.rst b/build/pkgs/modular_resolution/SPKG.rst new file mode 100644 index 00000000000..22c3f90f0fe --- /dev/null +++ b/build/pkgs/modular_resolution/SPKG.rst @@ -0,0 +1,115 @@ +p_group_cohomology: Modular cohomology rings of finite groups +============================================================= + +Description +----------- + +Modular Cohomology Rings of Finite Groups + +The package is located at http://users.fmi.uni-jena.de/cohomology/, +that's to say the tarball p_group_cohomology-x.y.tar.xz can be found +there and the documentation of the package is provided at +http://users.fmi.uni-jena.de/cohomology/documentation/ + +License +------- + +Copyright (C) 2018 Simon A. King Copyright (C) +2011 Simon A. King Copyright (C) 2009 Simon A. +King and + + David J. Green + +Distributed under the terms of the GNU General Public License (GPL), +version 2 or later (at your choice). + + 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 of the GPL is available at: + + http://www.gnu.org/licenses/ + +The package includes a data base of cohomology rings of the groups of +order 64 and provides access to a data base of cohomology rings of the +groups of order 128 and 243, located at + + http://cohomology.uni-jena.de/db/ + +These data bases are distributed under the Creative Commons +Attribution-Share Alike 3.0 License. The full text of this licence is +available at + + http://creativecommons.org/licenses/by-sa/3.0/ + + +SPKG Maintainers +---------------- + +Simon A. King + + +Upstream Contact +---------------- + +Simon A. King David J. Green + + +Acknowledgements +---------------- + +The development of the initial version of this SPKG was funded by the +German Science Foundation, DFG project GR 1585/4.1, and was accomplished +at the Friedrich Schiller University Jena. + +Since version 1.0.1, the further work on this SPKG was funded by Marie +Curie grant MTKD-CT-2006-042685 and was pursued at the National +University of Ireland, Galway. Since Novermber 2010, it is moved back to +Jena. + +We thank William Stein for giving us access to various computers on +which we could build test the SPKG and on which some huge computations +could be completed, and acknowledge the support by National Science +Foundation Grant No. DMS-0821725. + +We thank Mathieu Dutour Sikirić for hints on how to use GAP more +efficiently. + +We owe Peter Symonds the idea of using the Poincaré series in a rather +efficient completeness criterion. + +We are greatful to John Palmieri for his help on making +p_group_cohomology work with python-3. + +Dependencies +------------ + +- The SharedMeatAxe needs to be installed, as a build time dependency. + + This can be met by installing the meataxe spkg + +Testing +------- + +Our package provides a very short test suite for David Green's routines +for the computation of minimal projective resolutions. The majority of +this package's tests is formed by doc tests in the Cython code. In fact, +any class, method and function is covered by tests. + +Note that internet access is required for these tests, as it is +attempted to download cohomology rings from a public data base in the +web. + +The script ``spkg-check`` calls ``sage -t --force_lib`` on the files +in ``pGroupCohomology``. + +Documentation +------------- + +The documentation of this package is automatically built, if the +environment variable SAGE_SPKG_INSTALL_DOCS is yes (do "export +SAGE_SPKG_INSTALL_DOCS=yes" on the command line before installation). +The documents are put into +SAGE_ROOT/local/share/doc/p_group_cohomology/. diff --git a/build/pkgs/modular_resolution/checksums.ini b/build/pkgs/modular_resolution/checksums.ini new file mode 100644 index 00000000000..5d1d465cead --- /dev/null +++ b/build/pkgs/modular_resolution/checksums.ini @@ -0,0 +1,5 @@ +tarball=modular_resolution-VERSION.tar.gz +sha1=09ee61b1f9a33fb3e9bf0b658f81d3ede5748328 +md5=0e59e69a46014b8935c5e081d3bfc57a +cksum=2981185519 +upstream_url=https://github.com/sagemath/modular_resolution/releases/download/VERSION/modular_resolution-VERSION.tar.gz diff --git a/build/pkgs/modular_resolution/dependencies b/build/pkgs/modular_resolution/dependencies new file mode 100644 index 00000000000..d3ad6d2cbbc --- /dev/null +++ b/build/pkgs/modular_resolution/dependencies @@ -0,0 +1 @@ +singular meataxe diff --git a/build/pkgs/modular_resolution/package-version.txt b/build/pkgs/modular_resolution/package-version.txt new file mode 100644 index 00000000000..9459d4ba2a0 --- /dev/null +++ b/build/pkgs/modular_resolution/package-version.txt @@ -0,0 +1 @@ +1.1 diff --git a/build/pkgs/modular_resolution/spkg-check.in b/build/pkgs/modular_resolution/spkg-check.in new file mode 100644 index 00000000000..39b12d01df6 --- /dev/null +++ b/build/pkgs/modular_resolution/spkg-check.in @@ -0,0 +1,3 @@ +cd src + +$MAKE check || sdh_die "Error testing modular_resolution" diff --git a/build/pkgs/modular_resolution/spkg-install.in b/build/pkgs/modular_resolution/spkg-install.in new file mode 100644 index 00000000000..ee5610a4c14 --- /dev/null +++ b/build/pkgs/modular_resolution/spkg-install.in @@ -0,0 +1,5 @@ +cd src + +# build and install modular_resolution +sdh_configure +sdh_make_install diff --git a/build/pkgs/modular_resolution/type b/build/pkgs/modular_resolution/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/modular_resolution/type @@ -0,0 +1 @@ +optional diff --git a/build/pkgs/mpc/SPKG.rst b/build/pkgs/mpc/SPKG.rst index 821daf9f1db..5a205e0335f 100644 --- a/build/pkgs/mpc/SPKG.rst +++ b/build/pkgs/mpc/SPKG.rst @@ -4,7 +4,7 @@ mpc: Arithmetic of complex numbers with arbitrarily high precision and correct r Description ----------- -From http://www.multiprecision.org/mpc: GNU MPC is a C library for the +From https://www.multiprecision.org/mpc: GNU MPC is a C library for the arithmetic of complex numbers with arbitrarily high precision and correct rounding of the result. It extends the principles of the IEEE-754 standard for fixed precision real floating point numbers to @@ -22,11 +22,9 @@ documentation. Upstream Contact ---------------- -The MPC website is located at http://www.multiprecision.org/mpc . +The MPC website is located at https://www.multiprecision.org/mpc . -The MPC team can be contacted via the MPC mailing list: - - mpc-discuss@lists.gforge.inria.fr +The MPC team can be contacted via the MPC mailing list: mpc-discuss@inria.fr Special Update/Build Instructions --------------------------------- diff --git a/build/pkgs/mpfi/SPKG.rst b/build/pkgs/mpfi/SPKG.rst index cb145ca0e2a..9f16f859527 100644 --- a/build/pkgs/mpfi/SPKG.rst +++ b/build/pkgs/mpfi/SPKG.rst @@ -33,8 +33,7 @@ Upstream Contact http://perso.ens-lyon.fr/nathalie.revol/software.html -The MPFI website is located at http://mpfi.gforge.inria.fr/ +The MPFI website is located at https://gitlab.inria.fr/mpfi/mpfi -The MPFI team can be contacted via the MPFI mailing list: -mpfi-users@lists.gforge.inria.fr +The MPFI team can be contacted via the MPFI mailing list: mpfi-users@inria.fr diff --git a/build/pkgs/nbclient/distros/conda.txt b/build/pkgs/nbclient/distros/conda.txt new file mode 100644 index 00000000000..66ffbb0ca10 --- /dev/null +++ b/build/pkgs/nbclient/distros/conda.txt @@ -0,0 +1 @@ +nbclient diff --git a/build/pkgs/nest_asyncio/distros/conda.txt b/build/pkgs/nest_asyncio/distros/conda.txt new file mode 100644 index 00000000000..875c8427e6c --- /dev/null +++ b/build/pkgs/nest_asyncio/distros/conda.txt @@ -0,0 +1 @@ +nest-asyncio diff --git a/build/pkgs/ninja_build/checksums.ini b/build/pkgs/ninja_build/checksums.ini index cd1733990c7..d3914da794a 100644 --- a/build/pkgs/ninja_build/checksums.ini +++ b/build/pkgs/ninja_build/checksums.ini @@ -1,4 +1,5 @@ tarball=ninja_build-VERSION.tar.gz -sha1=17219deb34dd816363e37470f77ff7231509143a -md5=5fdb04461cc7f5d02536b3bfc0300166 -cksum=28253504 +sha1=f8c9279bdd4efc63b1a6be3b8c5a5031699af9ac +md5=7d1a1a2f5cdc06795b3054df5c17d5ef +cksum=3142198237 +upstream_url=https://github.com/ninja-build/ninja/archive/refs/tags/vVERSION.tar.gz diff --git a/build/pkgs/ninja_build/package-version.txt b/build/pkgs/ninja_build/package-version.txt index 53adb84c822..1cac385c6cb 100644 --- a/build/pkgs/ninja_build/package-version.txt +++ b/build/pkgs/ninja_build/package-version.txt @@ -1 +1 @@ -1.8.2 +1.11.0 diff --git a/build/pkgs/ninja_build/spkg-configure.m4 b/build/pkgs/ninja_build/spkg-configure.m4 index d9661574a13..fb6c4f00985 100644 --- a/build/pkgs/ninja_build/spkg-configure.m4 +++ b/build/pkgs/ninja_build/spkg-configure.m4 @@ -1,11 +1,12 @@ SAGE_SPKG_CONFIGURE( [ninja_build], [ - AC_CACHE_CHECK([for ninja >= 1.7.2], [ac_cv_path_NINJA], [ + dnl meson_python needs 1.8.2 or later + AC_CACHE_CHECK([for ninja >= 1.8.2], [ac_cv_path_NINJA], [ AC_PATH_PROGS_FEATURE_CHECK([NINJA], [ninja], [ ninja_version=`$ac_path_NINJA --version 2>&1 \ | $SED -n -e 's/\([[0-9]]*\.[[0-9]]*\.[[0-9]]*\).*/\1/p'` AS_IF([test -n "$ninja_version"], [ - AX_COMPARE_VERSION([$ninja_version], [ge], [1.7.2], [ + AX_COMPARE_VERSION([$ninja_version], [ge], [1.8.2], [ ac_cv_path_NINJA="$ac_path_NINJA" ac_path_NINJA_found=: ]) diff --git a/build/pkgs/ninja_build/type b/build/pkgs/ninja_build/type index 134d9bc32d5..a6a7b9cd726 100644 --- a/build/pkgs/ninja_build/type +++ b/build/pkgs/ninja_build/type @@ -1 +1 @@ -optional +standard diff --git a/build/pkgs/notebook/distros/arch.txt b/build/pkgs/notebook/distros/arch.txt new file mode 100644 index 00000000000..4d5656e81e6 --- /dev/null +++ b/build/pkgs/notebook/distros/arch.txt @@ -0,0 +1 @@ +jupyter-notebook diff --git a/build/pkgs/openssl/distros/opensuse.txt b/build/pkgs/openssl/distros/opensuse.txt index 196ba2d1153..11344b31a10 100644 --- a/build/pkgs/openssl/distros/opensuse.txt +++ b/build/pkgs/openssl/distros/opensuse.txt @@ -1 +1 @@ -"pkgconfig(libssl)" +libopenssl-3-devel diff --git a/build/pkgs/openssl/spkg-configure.m4 b/build/pkgs/openssl/spkg-configure.m4 index 2862d7ebec0..fd2d257d721 100644 --- a/build/pkgs/openssl/spkg-configure.m4 +++ b/build/pkgs/openssl/spkg-configure.m4 @@ -1,6 +1,6 @@ SAGE_SPKG_CONFIGURE([openssl], [ AX_CHECK_OPENSSL([ - AC_MSG_CHECKING([whether OpenSSL >= 1.1.1, as required by PEP 644]) + AC_MSG_CHECKING([whether OpenSSL >= 1.1.1, as required by PEP 644, and provides required APIs]) AC_COMPILE_IFELSE( dnl Trac #32580: Need OpenSSL >= 1.1.1 for PEP 644 dnl From https://www.openssl.org/docs/man3.0/man3/OPENSSL_VERSION_NUMBER.html: @@ -12,12 +12,33 @@ SAGE_SPKG_CONFIGURE([openssl], [ dnl FF is "fix" dnl S is "status" (f = release) dnl -> OPENSSL_VERSION_NUMBER is 0xMNNFFPPSL + dnl + dnl Trac #34273: Test program from ​https://github.com/python/cpython/blob/3.10/configure.ac#L5845 [AC_LANG_PROGRAM([[ + #include + #include #include #if OPENSSL_VERSION_NUMBER < 0x10101000L - # error OpenSSL >= 1.1.1 is required according to PEP 644 + #error OpenSSL >= 1.1.1 is required according to PEP 644 #endif - ]], [])], [ + static void keylog_cb(const SSL *ssl, const char *line) {} + ]], [[ + /* SSL APIs */ + SSL_CTX *ctx = SSL_CTX_new(TLS_client_method()); + SSL_CTX_set_keylog_callback(ctx, keylog_cb); + SSL *ssl = SSL_new(ctx); + X509_VERIFY_PARAM *param = SSL_get0_param(ssl); + X509_VERIFY_PARAM_set1_host(param, "python.org", 0); + SSL_free(ssl); + SSL_CTX_free(ctx); + /* hashlib APIs */ + OBJ_nid2sn(NID_md5); + OBJ_nid2sn(NID_sha1); + OBJ_nid2sn(NID_sha3_512); + OBJ_nid2sn(NID_blake2b512); + EVP_PBE_scrypt(NULL, 0, NULL, 0, 2, 8, 1, 0, NULL, 0); + ]]) + ], [ AC_MSG_RESULT([yes]) sage_spkg_install_openssl=no ], [ diff --git a/build/pkgs/p_group_cohomology/checksums.ini b/build/pkgs/p_group_cohomology/checksums.ini index a7dd222a72f..2f5d6e8f3b5 100644 --- a/build/pkgs/p_group_cohomology/checksums.ini +++ b/build/pkgs/p_group_cohomology/checksums.ini @@ -1,5 +1,5 @@ tarball=p_group_cohomology-VERSION.tar.xz -sha1=110a6dbe26ae6d7aa1227e1ff2338d7c8b38daa9 -md5=d4fad2cde21c275aed753ccbd467616a -cksum=1676569830 +sha1=d1d9a54f212a6de7d6a7e3afff80ffc3475264ad +md5=9a57cd7dd045dfd5e014473d543060c2 +cksum=2489285741 upstream_url=https://github.com/sagemath/p_group_cohomology/releases/download/vVERSION/p_group_cohomology-VERSION.tar.xz diff --git a/build/pkgs/p_group_cohomology/dependencies b/build/pkgs/p_group_cohomology/dependencies index 9492937c0f8..a9d113183c5 100644 --- a/build/pkgs/p_group_cohomology/dependencies +++ b/build/pkgs/p_group_cohomology/dependencies @@ -1 +1 @@ -$(PYTHON) cython cysignals singular meataxe $(SAGE_SRC)/sage/matrix/matrix_gfpn_dense.pxd $(SAGE_SRC)/sage/structure/element.pxd $(SAGE_SRC)/sage/matrix/matrix_gfpn_dense.pxd $(SAGE_SRC)/sage/matrix/matrix0.pxd $(SAGE_SRC)/sage/libs/meataxe.pxd $(SAGE_SRC)/sage/rings/morphism.pxd | $(PYTHON_TOOLCHAIN) matplotlib gap xz $(SAGERUNTIME) ipywidgets +$(PYTHON) cython cysignals singular meataxe modular_resolution $(SAGE_SRC)/sage/matrix/matrix_gfpn_dense.pxd $(SAGE_SRC)/sage/structure/element.pxd $(SAGE_SRC)/sage/matrix/matrix_gfpn_dense.pxd $(SAGE_SRC)/sage/matrix/matrix0.pxd $(SAGE_SRC)/sage/libs/meataxe.pxd $(SAGE_SRC)/sage/rings/morphism.pxd | $(PYTHON_TOOLCHAIN) matplotlib gap xz $(SAGERUNTIME) ipywidgets diff --git a/build/pkgs/p_group_cohomology/package-version.txt b/build/pkgs/p_group_cohomology/package-version.txt index 47725433179..b0360883a51 100644 --- a/build/pkgs/p_group_cohomology/package-version.txt +++ b/build/pkgs/p_group_cohomology/package-version.txt @@ -1 +1 @@ -3.3.2 +3.3.3.p1 diff --git a/build/pkgs/p_group_cohomology/patches/7.patch b/build/pkgs/p_group_cohomology/patches/7.patch new file mode 100644 index 00000000000..89a8b1e7294 --- /dev/null +++ b/build/pkgs/p_group_cohomology/patches/7.patch @@ -0,0 +1,23 @@ +From dde5d18ff0a0b8d1f9e88806bb9bb4b694b4666c Mon Sep 17 00:00:00 2001 +From: Matthias Koeppe +Date: Tue, 9 Aug 2022 10:09:22 -0700 +Subject: [PATCH] pGroupCohomology-3.3.3/pGroupCohomology/cohomology.pyx: Update import of + instancedoc for Sage 9.7 + +--- + pGroupCohomology-3.3.3/pGroupCohomology/cohomology.pyx | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/pGroupCohomology-3.3.3/pGroupCohomology/cohomology.pyx b/pGroupCohomology-3.3.3/pGroupCohomology/cohomology.pyx +index 3a7dc77..ae1458d 100644 +--- a/pGroupCohomology-3.3.3/pGroupCohomology/cohomology.pyx ++++ b/pGroupCohomology-3.3.3/pGroupCohomology/cohomology.pyx +@@ -68,7 +68,7 @@ from sage.all import mul + from sage.env import DOT_SAGE, SAGE_ROOT + from sage.misc.sageinspect import sage_getargspec + from sage.misc.lazy_attribute import lazy_attribute +-from sage.docs.instancedoc import instancedoc ++from sage.misc.instancedoc import instancedoc + from sage.structure.richcmp import richcmp, richcmp_method, op_LT, op_NE, op_GT + + # Sage rings etc. diff --git a/build/pkgs/p_group_cohomology/spkg-check.in b/build/pkgs/p_group_cohomology/spkg-check.in index 47b086b61df..ce0d8990676 100644 --- a/build/pkgs/p_group_cohomology/spkg-check.in +++ b/build/pkgs/p_group_cohomology/spkg-check.in @@ -1,9 +1,3 @@ cd src -# testing modular_resolution-1.1 -cd `ls -d modular_resolution*` -$MAKE check || sdh_die "Error testing modular_resolution-1.1" -cd .. - -# testing pGroupCohomology sage -tp 0 --long --force_lib --warn-long 80 --timeout=0 `ls -d pGroupCohomology*`/pGroupCohomology/ || sdh_die "Error testing pGroupCohomology" diff --git a/build/pkgs/p_group_cohomology/spkg-install.in b/build/pkgs/p_group_cohomology/spkg-install.in index dc312069886..359015fd37c 100644 --- a/build/pkgs/p_group_cohomology/spkg-install.in +++ b/build/pkgs/p_group_cohomology/spkg-install.in @@ -1,19 +1,3 @@ -# Check if meataxe was properly installed -sage -c "import sage.libs.meataxe" || sdh_die "sage.libs.meataxe cannot be imported. To solve this, it is enough to retry installation of p_group_cohomology" - -cd src - -# build and install modular_resolution -cd `ls -d modular_resolution*` -sdh_configure || sdh_die "Error configuring modular_resolution" -# sdh_make install got broken by trac ticket #24106 -$MAKE install || sdh_die "Error installing modular_resolution" -cd .. - -# install helper modules -sdh_install gap_helper "$SAGE_SHARE/gap/pkg/p_group_cohomology_helper" || sdh_die "Error installing GAP helper module" -sdh_install singular_helper/dickson.lib "$SAGE_SHARE/singular/LIB/" || sdh_die "Error installing Singular helper module" - # building pGroupCohomology -cd `ls -d pGroupCohomology*` +cd `ls -d src/pGroupCohomology*` sdh_pip_install . diff --git a/build/pkgs/p_group_cohomology/spkg-postinst.in b/build/pkgs/p_group_cohomology/spkg-postinst.in index 34bd835ef85..cd0ece5ab37 100644 --- a/build/pkgs/p_group_cohomology/spkg-postinst.in +++ b/build/pkgs/p_group_cohomology/spkg-postinst.in @@ -1,7 +1,9 @@ +cd src + # building pGroupCohomology's documentation if [ "x$SAGE_SPKG_INSTALL_DOCS" = xyes ] ; then - cd `ls -d src/pGroupCohomology*/doc` + cd `ls -d pGroupCohomology*/doc` $MAKE html || sdh_die "Error building documentation" sdh_install build/html/* "$SAGE_SHARE/doc/p_group_cohomology/" || sdh_die "Error moving documentation to $SAGE_SHARE/doc/p_group_cohomology" fi diff --git a/build/pkgs/palettable/distros/conda.txt b/build/pkgs/palettable/distros/conda.txt new file mode 100644 index 00000000000..646dd7426bb --- /dev/null +++ b/build/pkgs/palettable/distros/conda.txt @@ -0,0 +1 @@ +palettable diff --git a/build/pkgs/pathspec/distros/conda.txt b/build/pkgs/pathspec/distros/conda.txt new file mode 100644 index 00000000000..6486958df08 --- /dev/null +++ b/build/pkgs/pathspec/distros/conda.txt @@ -0,0 +1 @@ +pathspec diff --git a/build/pkgs/pint/distros/conda.txt b/build/pkgs/pint/distros/conda.txt new file mode 100644 index 00000000000..45f523a5a6e --- /dev/null +++ b/build/pkgs/pint/distros/conda.txt @@ -0,0 +1 @@ +pint diff --git a/build/pkgs/pip/distros/arch.txt b/build/pkgs/pip/distros/arch.txt new file mode 100644 index 00000000000..311c1b821ca --- /dev/null +++ b/build/pkgs/pip/distros/arch.txt @@ -0,0 +1 @@ +python-pip diff --git a/build/pkgs/platformdirs/distros/conda.txt b/build/pkgs/platformdirs/distros/conda.txt new file mode 100644 index 00000000000..67fd014bbdd --- /dev/null +++ b/build/pkgs/platformdirs/distros/conda.txt @@ -0,0 +1 @@ +platformdirs diff --git a/build/pkgs/pluggy/distros/conda.txt b/build/pkgs/pluggy/distros/conda.txt new file mode 100644 index 00000000000..11bdb5c1f5f --- /dev/null +++ b/build/pkgs/pluggy/distros/conda.txt @@ -0,0 +1 @@ +pluggy diff --git a/build/pkgs/ply/distros/conda.txt b/build/pkgs/ply/distros/conda.txt new file mode 100644 index 00000000000..90412f06833 --- /dev/null +++ b/build/pkgs/ply/distros/conda.txt @@ -0,0 +1 @@ +ply diff --git a/build/pkgs/poetry_core/distros/conda.txt b/build/pkgs/poetry_core/distros/conda.txt new file mode 100644 index 00000000000..9b1f9e3fa7d --- /dev/null +++ b/build/pkgs/poetry_core/distros/conda.txt @@ -0,0 +1 @@ +poetry-core diff --git a/build/pkgs/polylib/type b/build/pkgs/polylib/type index 134d9bc32d5..af4d63af86d 100644 --- a/build/pkgs/polylib/type +++ b/build/pkgs/polylib/type @@ -1 +1 @@ -optional +experimental \ No newline at end of file diff --git a/build/pkgs/polymake/SPKG.rst b/build/pkgs/polymake/SPKG.rst index 2f38f212b5c..6cccfcc2fa5 100644 --- a/build/pkgs/polymake/SPKG.rst +++ b/build/pkgs/polymake/SPKG.rst @@ -65,13 +65,10 @@ Information on missing Polymake prerequisites after installing polymake:: (sage-sh) $ polymake polytope> show_unconfigured; -It is strongly recommended to also install JuPyMake:: +In order to Polymake from Sage, you will need the JuPyMake:: sage -i jupymake -When JuPyMake is present, Sage is able to use a more robust interface -to Polymake. - Debugging polymake install problems ----------------------------------- diff --git a/build/pkgs/polymake/checksums.ini b/build/pkgs/polymake/checksums.ini index 4c893d90f1c..2727d96238b 100644 --- a/build/pkgs/polymake/checksums.ini +++ b/build/pkgs/polymake/checksums.ini @@ -1,5 +1,5 @@ tarball=polymake-VERSION-minimal.tar.bz2 -sha1=fe54e1e099e7e87a12d771c7ea5dd011d12ec8f8 -md5=732deb4a3cb83363448c59ec72b0e2cf -cksum=1021474111 +sha1=a3903ef9438388e56a76cb04918c1fe9b2e2b563 +md5=9a451d56cfe8c6138b91558d6d369dbe +cksum=1195315956 upstream_url=https://polymake.org/lib/exe/fetch.php/download/polymake-VERSION-minimal.tar.bz2 diff --git a/build/pkgs/polymake/package-version.txt b/build/pkgs/polymake/package-version.txt index b3d791d7525..4f8c639658e 100644 --- a/build/pkgs/polymake/package-version.txt +++ b/build/pkgs/polymake/package-version.txt @@ -1 +1 @@ -4.6 +4.7 diff --git a/build/pkgs/pure_eval/distros/conda.txt b/build/pkgs/pure_eval/distros/conda.txt new file mode 100644 index 00000000000..e50c81f634f --- /dev/null +++ b/build/pkgs/pure_eval/distros/conda.txt @@ -0,0 +1 @@ +pure_eval diff --git a/build/pkgs/py/distros/conda.txt b/build/pkgs/py/distros/conda.txt new file mode 100644 index 00000000000..edfce786a4d --- /dev/null +++ b/build/pkgs/py/distros/conda.txt @@ -0,0 +1 @@ +py diff --git a/build/pkgs/pynormaliz/distros/arch.txt b/build/pkgs/pynormaliz/distros/arch.txt new file mode 100644 index 00000000000..140dd0508e9 --- /dev/null +++ b/build/pkgs/pynormaliz/distros/arch.txt @@ -0,0 +1 @@ +python-pynormaliz diff --git a/build/pkgs/pynormaliz/distros/conda.txt b/build/pkgs/pynormaliz/distros/conda.txt new file mode 100644 index 00000000000..276703b325b --- /dev/null +++ b/build/pkgs/pynormaliz/distros/conda.txt @@ -0,0 +1 @@ +pynormaliz diff --git a/build/pkgs/pyproject_metadata/SPKG.rst b/build/pkgs/pyproject_metadata/SPKG.rst new file mode 100644 index 00000000000..6c0300e9a16 --- /dev/null +++ b/build/pkgs/pyproject_metadata/SPKG.rst @@ -0,0 +1,18 @@ +pyproject_metadata: PEP 621 metadata parsing +============================================ + +Description +----------- + +PEP 621 metadata parsing + +License +------- + +MIT + +Upstream Contact +---------------- + +https://pypi.org/project/pyproject-metadata/ + diff --git a/build/pkgs/pyproject_metadata/checksums.ini b/build/pkgs/pyproject_metadata/checksums.ini new file mode 100644 index 00000000000..da299c46588 --- /dev/null +++ b/build/pkgs/pyproject_metadata/checksums.ini @@ -0,0 +1,5 @@ +tarball=pyproject-metadata-VERSION.tar.gz +sha1=5421824aa29786bde43f510365c4d035a0614ba5 +md5=85fcbd5d777809ca2217a996e06fe2e0 +cksum=1327227039 +upstream_url=https://pypi.io/packages/source/p/pyproject_metadata/pyproject-metadata-VERSION.tar.gz diff --git a/build/pkgs/pyproject_metadata/dependencies b/build/pkgs/pyproject_metadata/dependencies new file mode 100644 index 00000000000..6d5368db738 --- /dev/null +++ b/build/pkgs/pyproject_metadata/dependencies @@ -0,0 +1,4 @@ +$(PYTHON) packaging pyparsing | $(PYTHON_TOOLCHAIN) + +---------- +All lines of this file are ignored except the first. diff --git a/build/pkgs/pyproject_metadata/install-requires.txt b/build/pkgs/pyproject_metadata/install-requires.txt new file mode 100644 index 00000000000..7ca7140f9d4 --- /dev/null +++ b/build/pkgs/pyproject_metadata/install-requires.txt @@ -0,0 +1 @@ +pyproject-metadata diff --git a/build/pkgs/pyproject_metadata/package-version.txt b/build/pkgs/pyproject_metadata/package-version.txt new file mode 100644 index 00000000000..8f0916f768f --- /dev/null +++ b/build/pkgs/pyproject_metadata/package-version.txt @@ -0,0 +1 @@ +0.5.0 diff --git a/build/pkgs/pyproject_metadata/spkg-install.in b/build/pkgs/pyproject_metadata/spkg-install.in new file mode 100644 index 00000000000..37ac1a53437 --- /dev/null +++ b/build/pkgs/pyproject_metadata/spkg-install.in @@ -0,0 +1,2 @@ +cd src +sdh_pip_install . diff --git a/build/pkgs/pyproject_metadata/type b/build/pkgs/pyproject_metadata/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/pyproject_metadata/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/python_build/distros/conda.txt b/build/pkgs/python_build/distros/conda.txt new file mode 100644 index 00000000000..378eac25d31 --- /dev/null +++ b/build/pkgs/python_build/distros/conda.txt @@ -0,0 +1 @@ +build diff --git a/build/pkgs/python_igraph/checksums.ini b/build/pkgs/python_igraph/checksums.ini index ebbdae645c8..9ddab211f0d 100644 --- a/build/pkgs/python_igraph/checksums.ini +++ b/build/pkgs/python_igraph/checksums.ini @@ -1,5 +1,5 @@ tarball=python-igraph-VERSION.tar.gz -sha1=6b717972163a4abfc8324e1a760c830526cc1a66 -md5=81929a9ffe5e7c40bf0be5a40d11a211 -cksum=2293691814 +sha1=75eae8ca6de2daa8c5ca43f99487cf20b5cdf432 +md5=18a54cd2fe507f5602e6c10584f665ee +cksum=594785782 upstream_url=https://pypi.io/packages/source/i/igraph/igraph-VERSION.tar.gz diff --git a/build/pkgs/python_igraph/package-version.txt b/build/pkgs/python_igraph/package-version.txt index 7e310bae199..571215736a6 100644 --- a/build/pkgs/python_igraph/package-version.txt +++ b/build/pkgs/python_igraph/package-version.txt @@ -1 +1 @@ -0.9.9 +0.10.1 diff --git a/build/pkgs/python_igraph/spkg-install.in b/build/pkgs/python_igraph/spkg-install.in index de8af8225a2..bafbbda375e 100644 --- a/build/pkgs/python_igraph/spkg-install.in +++ b/build/pkgs/python_igraph/spkg-install.in @@ -1,2 +1,10 @@ cd src -eval sdh_pip_install $(eval sdh_prefix_args "--build-option" --use-pkg-config) . +# Use --use-pep517 because https://github.com/igraph/python-igraph as of 0.9.11 does not have pyproject.toml +# and so "pip wheel" would use a legacy build method, ignoring --config-settings. +# +# TODO: With setuptools 63.2.0, passing another --config-settings "--global-option=--no-wait" (not really needed) +# kills the "--global-option=--use-pkg-config". +# https://github.com/pypa/setuptools/issues/3380 makes changes to this, so we can revisit this after +# the next setuptools upgrade. +# +sdh_pip_install --use-pep517 --config-settings "--global-option=--use-pkg-config" . diff --git a/build/pkgs/pythran/distros/conda.txt b/build/pkgs/pythran/distros/conda.txt new file mode 100644 index 00000000000..86d056b339f --- /dev/null +++ b/build/pkgs/pythran/distros/conda.txt @@ -0,0 +1 @@ +pythran diff --git a/build/pkgs/pytz_deprecation_shim/distros/conda.txt b/build/pkgs/pytz_deprecation_shim/distros/conda.txt new file mode 100644 index 00000000000..2fd546ce62e --- /dev/null +++ b/build/pkgs/pytz_deprecation_shim/distros/conda.txt @@ -0,0 +1 @@ +pytz-deprecation-shim diff --git a/build/pkgs/r_jupyter/type b/build/pkgs/r_jupyter/type index 134d9bc32d5..af4d63af86d 100644 --- a/build/pkgs/r_jupyter/type +++ b/build/pkgs/r_jupyter/type @@ -1 +1 @@ -optional +experimental \ No newline at end of file diff --git a/build/pkgs/rubiks/spkg-install.in b/build/pkgs/rubiks/spkg-install.in index e03c76e432a..893485ea0bd 100644 --- a/build/pkgs/rubiks/spkg-install.in +++ b/build/pkgs/rubiks/spkg-install.in @@ -2,14 +2,6 @@ ## rubiks ########################################### -# Work around a bug in gcc 4.6.0: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=48702 -if [ "`testcc.sh $CC`" = GCC ] ; then - if $CC -dumpversion 2>/dev/null |grep >/dev/null '^4\.6\.[01]$' ; then - echo "Warning: Working around bug in gcc 4.6.0" - EXTRA_FLAG="-fno-ivopts" - fi -fi - # Most packages do not need all these set # But it is better to do them all each time, rather than ommit # a flag by mistake. @@ -50,6 +42,11 @@ else INSTALL=install; export INSTALL fi +if [ $UNAME = "Darwin" ]; then + # #34293: Work around compiler hang + export CXXFLAGS="$CXXFLAGS -O1" +fi + cd src echo "Building Rubiks cube solvers" diff --git a/build/pkgs/sage_conf/install-requires.txt b/build/pkgs/sage_conf/install-requires.txt index 3e69e695072..35a8993af20 100644 --- a/build/pkgs/sage_conf/install-requires.txt +++ b/build/pkgs/sage_conf/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-conf ~= 9.7b8 +sage-conf ~= 9.8b0 diff --git a/build/pkgs/sage_docbuild/install-requires.txt b/build/pkgs/sage_docbuild/install-requires.txt index 385ae2dc009..002c6b79c65 100644 --- a/build/pkgs/sage_docbuild/install-requires.txt +++ b/build/pkgs/sage_docbuild/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-docbuild ~= 9.7b8 +sage-docbuild ~= 9.8b0 diff --git a/build/pkgs/sage_flatsurf/dependencies b/build/pkgs/sage_flatsurf/dependencies index 951fd368f40..4c62fdd4fef 100644 --- a/build/pkgs/sage_flatsurf/dependencies +++ b/build/pkgs/sage_flatsurf/dependencies @@ -1,4 +1,4 @@ -$(PYTHON) | $(PYTHON_TOOLCHAIN) surface_dynamics +$(PYTHON) | $(PYTHON_TOOLCHAIN) surface_dynamics $(SAGERUNTIME) ---------- All lines of this file are ignored except the first. diff --git a/build/pkgs/sage_setup/install-requires.txt b/build/pkgs/sage_setup/install-requires.txt index a638b3a1958..5720bc8f2c5 100644 --- a/build/pkgs/sage_setup/install-requires.txt +++ b/build/pkgs/sage_setup/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-setup ~= 9.7b8 +sage-setup ~= 9.8b0 diff --git a/build/pkgs/sage_sws2rst/install-requires.txt b/build/pkgs/sage_sws2rst/install-requires.txt index a0ebb31f9bb..70be4d731ee 100644 --- a/build/pkgs/sage_sws2rst/install-requires.txt +++ b/build/pkgs/sage_sws2rst/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-sws2rst ~= 9.7b8 +sage-sws2rst ~= 9.8b0 diff --git a/build/pkgs/sagelib/install-requires.txt b/build/pkgs/sagelib/install-requires.txt index dc87821cd9a..359dff35670 100644 --- a/build/pkgs/sagelib/install-requires.txt +++ b/build/pkgs/sagelib/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagelib ~= 9.7b8 +sagelib ~= 9.8b0 diff --git a/build/pkgs/sagemath_categories/install-requires.txt b/build/pkgs/sagemath_categories/install-requires.txt index 71565616331..070f8caa2ea 100644 --- a/build/pkgs/sagemath_categories/install-requires.txt +++ b/build/pkgs/sagemath_categories/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-categories ~= 9.7b8 +sagemath-categories ~= 9.8b0 diff --git a/build/pkgs/sagemath_doc_html/dependencies b/build/pkgs/sagemath_doc_html/dependencies index 94342638a01..039e22f11ef 100644 --- a/build/pkgs/sagemath_doc_html/dependencies +++ b/build/pkgs/sagemath_doc_html/dependencies @@ -1,4 +1,4 @@ -sagelib sphinx pplpy_doc | $(SAGERUNTIME) maxima networkx scipy sympy matplotlib pillow mathjax mpmath ipykernel jupyter_client conway_polynomials tachyon jmol ipywidgets jupyter_sphinx sage_docbuild elliptic_curves +sagelib sphinx pplpy_doc | $(SAGERUNTIME) maxima networkx scipy sympy matplotlib pillow mathjax mpmath ipykernel jupyter_client conway_polynomials tachyon jmol ipywidgets jupyter_sphinx sage_docbuild elliptic_curves furo # Building the documentation has many dependencies, because all # documented modules are imported and because we use matplotlib to diff --git a/build/pkgs/sagemath_environment/install-requires.txt b/build/pkgs/sagemath_environment/install-requires.txt index d6a22924216..e9976c7146f 100644 --- a/build/pkgs/sagemath_environment/install-requires.txt +++ b/build/pkgs/sagemath_environment/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-environment ~= 9.7b8 +sagemath-environment ~= 9.8b0 diff --git a/build/pkgs/sagemath_objects/install-requires.txt b/build/pkgs/sagemath_objects/install-requires.txt index 9f70b502b2d..0fb621f26f2 100644 --- a/build/pkgs/sagemath_objects/install-requires.txt +++ b/build/pkgs/sagemath_objects/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-objects ~= 9.7b8 +sagemath-objects ~= 9.8b0 diff --git a/build/pkgs/sagetex/SPKG.rst b/build/pkgs/sagetex/SPKG.rst index 1a3de2a92b4..b2e3bd85b18 100644 --- a/build/pkgs/sagetex/SPKG.rst +++ b/build/pkgs/sagetex/SPKG.rst @@ -55,9 +55,9 @@ needs. Full details are in the Sage installation guide at http://doc.sagemath.org/html/en/installation/ and http://doc.sagemath.org/html/en/tutorial/sagetex.html . -The directory ``$SAGE_ROOT/local/share/doc/sagetex`` contains +The directory ``$SAGE_ROOT/venv/share/doc/sagetex`` contains documentation and an example file. See -``$SAGE_ROOT/local/share/texmf/tex/latex/sagetex`` for the source code +``$SAGE_ROOT/venv/share/texmf/tex/latex/sagetex`` for the source code and some possibly useful scripts. If you have problems or suggestions see `the sage-support group `__. diff --git a/build/pkgs/sagetex/checksums.ini b/build/pkgs/sagetex/checksums.ini index bbb0fdadd37..ec20a91fb3d 100644 --- a/build/pkgs/sagetex/checksums.ini +++ b/build/pkgs/sagetex/checksums.ini @@ -1,5 +1,5 @@ tarball=sagetex-VERSION.tar.gz -sha1=f92518caf6e355cc8046847c73446679c3a1c1bc -md5=46dcd30b18c107cc6f6dca1d47685f51 -cksum=1068602151 -upstream_url=https://github.com/sagemath/sagetex/releases/download/vVERSION/sagetex-VERSION.tar.gz +sha1=821c8a6ab11ee651d0dcc599c5582fefb6706775 +md5=a7ddbe41ea3d816e839ddda3ec28f89a +cksum=1768053059 +upstream_url=https://pypi.io/packages/source/s/sagetex/sagetex-VERSION.tar.gz diff --git a/build/pkgs/sagetex/dependencies b/build/pkgs/sagetex/dependencies index 29f49d5c215..6090d5dc3ac 100644 --- a/build/pkgs/sagetex/dependencies +++ b/build/pkgs/sagetex/dependencies @@ -1,4 +1,6 @@ -$(PYTHON) maxima scipy matplotlib pillow tachyon +$(PYTHON) maxima scipy matplotlib pillow tachyon pyparsing +To build SageTeX, you just need Python and pyparsing, but to test (SAGE_CHECK=yes) +SageTeX, you actually need to run Sage, produce plots,... ---------- All lines of this file are ignored except the first. diff --git a/build/pkgs/sagetex/package-version.txt b/build/pkgs/sagetex/package-version.txt index 5a958026daa..9575d51bad2 100644 --- a/build/pkgs/sagetex/package-version.txt +++ b/build/pkgs/sagetex/package-version.txt @@ -1 +1 @@ -3.5 +3.6.1 diff --git a/build/pkgs/setuptools/distros/conda.txt b/build/pkgs/setuptools/distros/conda.txt index 49fe098d9e6..0a486bd850f 100644 --- a/build/pkgs/setuptools/distros/conda.txt +++ b/build/pkgs/setuptools/distros/conda.txt @@ -1 +1,4 @@ -setuptools +# Set this bound until https://trac.sagemath.org/ticket/34209 adds support for PEP660 editable builds +# By setting this version bound, we avoid having to include the following in our installation instructions. +# export SETUPTOOLS_ENABLE_FEATURES=legacy-editable +"setuptools<64" diff --git a/build/pkgs/setuptools/install-requires.txt b/build/pkgs/setuptools/install-requires.txt index 0810ca37277..486c3c348ee 100644 --- a/build/pkgs/setuptools/install-requires.txt +++ b/build/pkgs/setuptools/install-requires.txt @@ -1 +1,2 @@ -setuptools >=49.6.0 +# Set this bound until https://trac.sagemath.org/ticket/34209 adds support for PEP660 editable builds +setuptools >=49.6.0,<64.0.0 diff --git a/build/pkgs/setuptools_scm_git_archive/distros/conda.txt b/build/pkgs/setuptools_scm_git_archive/distros/conda.txt new file mode 100644 index 00000000000..538474ff946 --- /dev/null +++ b/build/pkgs/setuptools_scm_git_archive/distros/conda.txt @@ -0,0 +1 @@ +setuptools-scm-git-archive diff --git a/build/pkgs/slabbe/dependencies b/build/pkgs/slabbe/dependencies index 0738c2d7777..05ba0d8954b 100644 --- a/build/pkgs/slabbe/dependencies +++ b/build/pkgs/slabbe/dependencies @@ -1,4 +1,4 @@ -$(PYTHON) | $(PYTHON_TOOLCHAIN) +$(PYTHON) | $(PYTHON_TOOLCHAIN) $(SAGERUNTIME) ---------- All lines of this file are ignored except the first. diff --git a/build/pkgs/soupsieve/distros/conda.txt b/build/pkgs/soupsieve/distros/conda.txt new file mode 100644 index 00000000000..9eb997f7a10 --- /dev/null +++ b/build/pkgs/soupsieve/distros/conda.txt @@ -0,0 +1 @@ +soupsieve diff --git a/build/pkgs/sphinx_basic_ng/SPKG.rst b/build/pkgs/sphinx_basic_ng/SPKG.rst new file mode 100644 index 00000000000..c137da5c63b --- /dev/null +++ b/build/pkgs/sphinx_basic_ng/SPKG.rst @@ -0,0 +1,16 @@ +sphinx_basic_ng: A modern skeleton for Sphinx themes. +===================================================== + +Description +----------- + +A modern skeleton for Sphinx themes. + +License +------- + +Upstream Contact +---------------- + +https://pypi.org/project/sphinx-basic-ng/ + diff --git a/build/pkgs/sphinx_basic_ng/checksums.ini b/build/pkgs/sphinx_basic_ng/checksums.ini new file mode 100644 index 00000000000..1be1765fff8 --- /dev/null +++ b/build/pkgs/sphinx_basic_ng/checksums.ini @@ -0,0 +1,5 @@ +tarball=sphinx_basic_ng-VERSION.tar.gz +sha1=03857ae3336e81db16599a68b826b6ed6053cd28 +md5=bdb1b36fd46f0b5b4f6c079e7350b860 +cksum=2230250036 +upstream_url=https://pypi.io/packages/source/s/sphinx_basic_ng/sphinx_basic_ng-VERSION.tar.gz diff --git a/build/pkgs/sphinx_basic_ng/dependencies b/build/pkgs/sphinx_basic_ng/dependencies new file mode 100644 index 00000000000..175a793ecbc --- /dev/null +++ b/build/pkgs/sphinx_basic_ng/dependencies @@ -0,0 +1,4 @@ +$(PYTHON) sphinx | $(PYTHON_TOOLCHAIN) + +---------- +All lines of this file are ignored except the first. diff --git a/build/pkgs/sphinx_basic_ng/install-requires.txt b/build/pkgs/sphinx_basic_ng/install-requires.txt new file mode 100644 index 00000000000..c11d80ea377 --- /dev/null +++ b/build/pkgs/sphinx_basic_ng/install-requires.txt @@ -0,0 +1 @@ +sphinx-basic-ng diff --git a/build/pkgs/sphinx_basic_ng/package-version.txt b/build/pkgs/sphinx_basic_ng/package-version.txt new file mode 100644 index 00000000000..4110d074c83 --- /dev/null +++ b/build/pkgs/sphinx_basic_ng/package-version.txt @@ -0,0 +1 @@ +0.0.1a12 diff --git a/build/pkgs/sphinx_basic_ng/spkg-install.in b/build/pkgs/sphinx_basic_ng/spkg-install.in new file mode 100644 index 00000000000..37ac1a53437 --- /dev/null +++ b/build/pkgs/sphinx_basic_ng/spkg-install.in @@ -0,0 +1,2 @@ +cd src +sdh_pip_install . diff --git a/build/pkgs/sphinx_basic_ng/type b/build/pkgs/sphinx_basic_ng/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/sphinx_basic_ng/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/stack_data/distros/conda.txt b/build/pkgs/stack_data/distros/conda.txt new file mode 100644 index 00000000000..09e7428c13d --- /dev/null +++ b/build/pkgs/stack_data/distros/conda.txt @@ -0,0 +1 @@ +stack_data diff --git a/build/pkgs/symengine_py/type b/build/pkgs/symengine_py/type index 134d9bc32d5..af4d63af86d 100644 --- a/build/pkgs/symengine_py/type +++ b/build/pkgs/symengine_py/type @@ -1 +1 @@ -optional +experimental \ No newline at end of file diff --git a/build/pkgs/sympy/checksums.ini b/build/pkgs/sympy/checksums.ini index f2b891c8a65..f59c0083e07 100644 --- a/build/pkgs/sympy/checksums.ini +++ b/build/pkgs/sympy/checksums.ini @@ -1,5 +1,5 @@ tarball=sympy-VERSION.tar.gz -sha1=95b5323b284a3f64414dab3b9da909eeeb1c09ea -md5=8c7a956d74a47dc439c2935fec64ac46 -cksum=1092663182 +sha1=9e75c8cafa4324f2803a6488ac713d87adf5cb64 +md5=232141d248ab4164e92c8ac59a996914 +cksum=2645245679 upstream_url=https://github.com/sympy/sympy/releases/download/sympy-VERSION/sympy-VERSION.tar.gz diff --git a/build/pkgs/sympy/package-version.txt b/build/pkgs/sympy/package-version.txt index 4dae2985b58..720c7384c61 100644 --- a/build/pkgs/sympy/package-version.txt +++ b/build/pkgs/sympy/package-version.txt @@ -1 +1 @@ -1.10.1 +1.11.1 diff --git a/build/pkgs/texttable/distros/conda.txt b/build/pkgs/texttable/distros/conda.txt new file mode 100644 index 00000000000..5d54c8a784d --- /dev/null +++ b/build/pkgs/texttable/distros/conda.txt @@ -0,0 +1 @@ +texttable diff --git a/build/pkgs/threejs/distros/conda.txt b/build/pkgs/threejs/distros/conda.txt index 023a0678428..d1ffb850424 100644 --- a/build/pkgs/threejs/distros/conda.txt +++ b/build/pkgs/threejs/distros/conda.txt @@ -1 +1 @@ -three.js +threejs-sage=122.* diff --git a/build/pkgs/tinycss2/distros/conda.txt b/build/pkgs/tinycss2/distros/conda.txt new file mode 100644 index 00000000000..29992f20d2c --- /dev/null +++ b/build/pkgs/tinycss2/distros/conda.txt @@ -0,0 +1 @@ +tinycss2 diff --git a/build/pkgs/toml/distros/conda.txt b/build/pkgs/toml/distros/conda.txt new file mode 100644 index 00000000000..bd79a658fe7 --- /dev/null +++ b/build/pkgs/toml/distros/conda.txt @@ -0,0 +1 @@ +toml diff --git a/build/pkgs/tomli/distros/conda.txt b/build/pkgs/tomli/distros/conda.txt new file mode 100644 index 00000000000..aab392a3ac2 --- /dev/null +++ b/build/pkgs/tomli/distros/conda.txt @@ -0,0 +1 @@ +tomli diff --git a/build/pkgs/tomlkit/distros/conda.txt b/build/pkgs/tomlkit/distros/conda.txt new file mode 100644 index 00000000000..8141b831035 --- /dev/null +++ b/build/pkgs/tomlkit/distros/conda.txt @@ -0,0 +1 @@ +tomlkit diff --git a/build/pkgs/tox/distros/arch.txt b/build/pkgs/tox/distros/arch.txt new file mode 100644 index 00000000000..cd20859a9df --- /dev/null +++ b/build/pkgs/tox/distros/arch.txt @@ -0,0 +1 @@ +python-tox diff --git a/build/pkgs/typing_extensions/distros/conda.txt b/build/pkgs/typing_extensions/distros/conda.txt new file mode 100644 index 00000000000..5fd4f05f341 --- /dev/null +++ b/build/pkgs/typing_extensions/distros/conda.txt @@ -0,0 +1 @@ +typing_extensions diff --git a/build/pkgs/tzdata/distros/conda.txt b/build/pkgs/tzdata/distros/conda.txt new file mode 100644 index 00000000000..0883ff0705b --- /dev/null +++ b/build/pkgs/tzdata/distros/conda.txt @@ -0,0 +1 @@ +tzdata diff --git a/build/pkgs/urllib3/distros/conda.txt b/build/pkgs/urllib3/distros/conda.txt new file mode 100644 index 00000000000..a42590bebea --- /dev/null +++ b/build/pkgs/urllib3/distros/conda.txt @@ -0,0 +1 @@ +urllib3 diff --git a/build/pkgs/virtualenv/distros/conda.txt b/build/pkgs/virtualenv/distros/conda.txt new file mode 100644 index 00000000000..66072c76450 --- /dev/null +++ b/build/pkgs/virtualenv/distros/conda.txt @@ -0,0 +1 @@ +virtualenv diff --git a/build/sage_bootstrap/app.py b/build/sage_bootstrap/app.py index d48316dba46..d6094609d88 100644 --- a/build/sage_bootstrap/app.py +++ b/build/sage_bootstrap/app.py @@ -1,6 +1,11 @@ # -*- coding: utf-8 -*- """ Controller for the commandline actions + +AUTHORS: + + - Volker Braun (2016): initial version + - Thierry Monteil (2022): clean option to remove outdated source tarballs """ @@ -26,6 +31,7 @@ from sage_bootstrap.pypi import PyPiVersion, PyPiNotFound, PyPiError from sage_bootstrap.fileserver import FileServer from sage_bootstrap.expand_class import PackageClass +from sage_bootstrap.env import SAGE_DISTFILES class Application(object): @@ -303,3 +309,23 @@ def create(self, package_name, version=None, tarball=None, pkg_type=None, upstre else: update = ChecksumUpdater(package_name) update.fix_checksum() + + def clean(self): + """ + Remove outdated source tarballs from the upstream/ directory + + $ sage --package clean + 42 files were removed from the .../upstream directory + """ + log.debug('Cleaning upstream/ directory') + package_names = PackageClass(':all:').names + keep = [Package(package_name).tarball.filename for package_name in package_names] + count = 0 + for filename in os.listdir(SAGE_DISTFILES): + if filename not in keep: + filepath = os.path.join(SAGE_DISTFILES, filename) + if os.path.isfile(filepath): + log.debug('Removing file {}'.format(filepath)) + os.remove(filepath) + count += 1 + print('{} files were removed from the {} directory'.format(count, SAGE_DISTFILES)) diff --git a/build/sage_bootstrap/cmdline.py b/build/sage_bootstrap/cmdline.py index 8c0515c0ede..780f3c5a8e3 100644 --- a/build/sage_bootstrap/cmdline.py +++ b/build/sage_bootstrap/cmdline.py @@ -4,6 +4,11 @@ This module handles the main "sage-package" commandline utility, which is also exposed as "sage --package". + +AUTHORS: + + - Volker Braun (2016): initial version + - Thierry Monteil (2022): clean option to remove outdated source tarballs """ # **************************************************************************** @@ -174,6 +179,16 @@ Creating new package "foo" """ +epilog_clean = \ +""" +Remove outdated source tarballs from the upstream/ directory + +EXAMPLE: + + $ sage --package clean + 42 files were removed from the .../upstream directory +""" + def make_parser(): """ @@ -206,11 +221,11 @@ def make_parser(): parser_list.add_argument( '--has-file', action='append', default=[], metavar='FILENAME', dest='has_files', help=('only include packages that have this file in their metadata directory ' - '(examples: SPKG.rst, spkg-configure.m4, distros/debian.txt)')) + '(examples: SPKG.rst, spkg-configure.m4, distros/debian.txt, spkg-install|spkg-install.in)')) parser_list.add_argument( '--no-file', action='append', default=[], metavar='FILENAME', dest='no_files', help=('only include packages that do not have this file in their metadata directory ' - '(examples: huge, patches)')) + '(examples: huge, patches, huge|has_nonfree_dependencies)')) parser_list.add_argument( '--exclude', action='append', default=[], metavar='PACKAGE_NAME', help='exclude package from list') @@ -316,6 +331,11 @@ def make_parser(): '--pypi', action="store_true", help='Create a package for a Python package available on PyPI') + parser_clean = subparsers.add_parser( + 'clean', epilog=epilog_clean, + formatter_class=argparse.RawDescriptionHelpFormatter, + help='Remove outdated source tarballs from the upstream/ directory') + return parser @@ -356,6 +376,8 @@ def run(): app.upload_cls(args.package_name) elif args.subcommand == 'fix-checksum': app.fix_checksum_cls(*args.package_class) + elif args.subcommand == 'clean': + app.clean() else: raise RuntimeError('unknown subcommand: {0}'.format(args)) diff --git a/build/sage_bootstrap/expand_class.py b/build/sage_bootstrap/expand_class.py index 555513905a4..10ec907d0fa 100644 --- a/build/sage_bootstrap/expand_class.py +++ b/build/sage_bootstrap/expand_class.py @@ -32,9 +32,13 @@ def __init__(self, *package_names_or_classes, **filters): def included_in_filter(pkg): if pkg.name in excluded: return False - if not all(pkg.has_file(filename) for filename in filenames): + if not all(any(pkg.has_file(filename) + for filename in filename_disjunction.split('|')) + for filename_disjunction in filenames): return False - return not any(pkg.has_file(filename) for filename in no_filenames) + return not any(any(pkg.has_file(filename) + for filename in no_filename_disjunction.split('|')) + for no_filename_disjunction in no_filenames) for package_name_or_class in package_names_or_classes: if package_name_or_class == ':all:': diff --git a/build/sage_bootstrap/installcheck.py b/build/sage_bootstrap/installcheck.py new file mode 100644 index 00000000000..1448ccdcbc9 --- /dev/null +++ b/build/sage_bootstrap/installcheck.py @@ -0,0 +1,207 @@ +""" +Command-line script for checking an installed SPKG in an installation tree ($SAGE_LOCAL, $SAGE_VENV). +""" + +# **************************************************************************** +# Copyright (C) 2017 Erik M. Bray +# 2022 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 __future__ import print_function + +import glob +import json +import os +import shutil +import subprocess +import sys +import argparse +import warnings + +from .env import SAGE_ROOT + +pth = os.path +PKGS = pth.join(SAGE_ROOT, 'build', 'pkgs') +"""Directory where all spkg sources are found.""" + + +def check_lib_auditwheel(f, verbose=False): + from auditwheel.lddtree import lddtree + for lib, info in lddtree(f)["libs"].items(): + if verbose: + print('- {0}: {1}'.format(lib, info["realpath"]), file=sys.stderr) + if info["realpath"] is None: + raise RuntimeError('Shared library {0} needed by {1} is not found' + .format(lib, f)) + + +def installcheck(spkg_name, sage_local, verbose=False): + """ + Given a package name and path to an installation tree (SAGE_LOCAL or SAGE_VENV), + check the installation of the package in that tree. + """ + + # The default path to this directory; however its value should be read + # from the environment if possible + spkg_inst = pth.join(sage_local, 'var', 'lib', 'sage', 'installed') + + # Find all stamp files for the package; there should be only one, but if + # there is somehow more than one we'll work with the most recent one. + pattern = pth.join(spkg_inst, '{0}-*'.format(spkg_name)) + stamp_files = sorted(glob.glob(pattern), key=pth.getmtime) + + if stamp_files: + stamp_file = stamp_files[-1] + else: + stamp_file = None + + spkg_meta = {} + if stamp_file: + try: + with open(stamp_file) as f: + spkg_meta = json.load(f) + except (OSError, ValueError): + pass + + if 'files' not in spkg_meta: + if stamp_file: + print("Old-style or corrupt stamp file '{0}'" + .format(stamp_file), file=sys.stderr) + else: + print("Package '{0}' is currently not installed in '{1}'" + .format(spkg_name, sage_local), file=sys.stderr) + else: + files = spkg_meta['files'] + + for f in files: + f = os.path.join(sage_local, f) + if f.endswith(('.so', '.dylib')): + if verbose: + print("Checking shared library file '{0}'" + .format(f), file=sys.stderr) + if sys.platform == 'darwin': + try: + from delocate.libsana import _tree_libs_from_libraries, _filter_system_libs + except ImportError: + warnings.warn('delocate is not available, so nothing is actually checked') + else: + _tree_libs_from_libraries([f], + lib_filt_func=_filter_system_libs, + copy_filt_func=lambda path: True) + else: + try: + check_lib_auditwheel(f, verbose=False) + except ImportError: + warnings.warn('auditwheel is not available, so nothing is actually checked') + elif f.endswith('-any.whl'): + # pure Python wheel, nothing to check + pass + elif f.endswith('.whl'): + if verbose: + print("Checking wheel file '{0}'" + .format(f), file=sys.stderr) + if sys.platform == 'darwin': + try: + from delocate import wheel_libs + except ImportError: + warnings.warn('delocate is not available, so nothing is actually checked') + else: + wheel_libs(f) + else: + try: + from delocate.tmpdirs import TemporaryDirectory + from delocate.tools import zip2dir + except ImportError: + warnings.warn('delocate is not available, so nothing is actually checked') + else: + try: + with TemporaryDirectory() as tmpdir: + zip2dir(f, tmpdir) + for dirpath, dirnames, basenames in os.walk(tmpdir): + for base in basenames: + if base.endswith('.so'): + depending_path = os.path.realpath(os.path.join(dirpath, base)) + check_lib_auditwheel(depending_path, verbose=False) + except ImportError: + warnings.warn('auditwheel is not available, so nothing is actually checked') + + +def dir_type(path): + """ + A custom argument 'type' for directory paths. + """ + + if path and not pth.isdir(path): + raise argparse.ArgumentTypeError( + "'{0}' is not a directory".format(path)) + + return path + + +def spkg_type(pkg): + """ + A custom argument 'type' for spkgs--checks whether the given package name + is a known spkg. + """ + pkgbase = pth.join(PKGS, pkg) + + if not pth.isdir(pkgbase): + raise argparse.ArgumentTypeError( + "'{0}' is not an spkg listed in '{1}'".format(pkg, PKGS)) + + return pkg + + +def make_parser(): + """Returns the command-line argument parser for sage-spkg-installcheck.""" + + doc_lines = __doc__.strip().splitlines() + + parser = argparse.ArgumentParser( + description=doc_lines[0], + epilog='\n'.join(doc_lines[1:]).strip(), + formatter_class=argparse.RawDescriptionHelpFormatter) + + parser.add_argument('spkg', type=spkg_type, help='the spkg to check') + parser.add_argument('sage_local', type=dir_type, nargs='?', + default=os.environ.get('SAGE_LOCAL'), + help='the path of the installation tree (default: the $SAGE_LOCAL ' + 'environment variable if set)') + parser.add_argument('-v', '--verbose', action='store_true', + help='verbose output showing all files removed') + parser.add_argument('--debug', action='store_true', help=argparse.SUPPRESS) + + return parser + + +def run(argv=None): + parser = make_parser() + + args = parser.parse_args(argv if argv is not None else sys.argv[1:]) + + if args.sage_local is None: + print('Error: An installation tree must be specified either at the command ' + 'line or in the $SAGE_LOCAL environment variable', + file=sys.stderr) + sys.exit(1) + + try: + installcheck(args.spkg, args.sage_local, + verbose=args.verbose) + except Exception as exc: + print("Error during installcheck of '{0}': {1}".format( + args.spkg, exc), file=sys.stderr) + + if args.debug: + raise + + sys.exit(1) + + +if __name__ == '__main__': + run() diff --git a/configure.ac b/configure.ac index 4ab080c6115..10351b8c416 100644 --- a/configure.ac +++ b/configure.ac @@ -49,10 +49,10 @@ dnl Make sure the path to our own m4 macros is always properly set dnl and doesn't depend on how autoconf is called. AC_CONFIG_MACRO_DIR([m4]) -dnl The AC_LIB_RPATH macro comes from gettext, which is one of our bootstrap -dnl packages. It defines, among other things, the $acl_shlibext variable that +dnl The AC_LIB_RPATH macro comes from a Gnulib-provided file in m4/. +dnl It defines, among other things, the $acl_shlibext variable that dnl contains the shared library extension for this system. We already use the -dnl AM_ICONV macro from gettext (which ultimately calls AC_LIB_RPATH), and we +dnl AM_ICONV macro from the same source (which ultimately calls AC_LIB_RPATH), and we dnl avoid involving libtool by using it to get the shared library extension. AC_LIB_RPATH AC_SUBST(SHLIBEXT, "${acl_shlibext}") @@ -489,7 +489,7 @@ AC_ARG_ENABLE([doc], AS_HELP_STRING([--disable-doc], [disable build of the Sage documentation and packages depending on it]), [ dnl Disable packages needed for docbuilding - for pkg in sage_docbuild alabaster babel snowballstemmer imagesize sphinx sphinxcontrib_devhelp sphinxcontrib_jsmath sphinxcontrib_serializinghtml sphinxcontrib_applehelp sphinxcontrib_htmlhelp sphinxcontrib_qthelp sphinxcontrib_websupport jupyter_sphinx; do + for pkg in sage_docbuild alabaster babel snowballstemmer imagesize sphinx sphinxcontrib_devhelp sphinxcontrib_jsmath sphinxcontrib_serializinghtml sphinxcontrib_applehelp sphinxcontrib_htmlhelp sphinxcontrib_qthelp sphinxcontrib_websupport jupyter_sphinx furo; do AS_VAR_SET([SAGE_ENABLE_$pkg], [$enableval]) done AS_VAR_IF([enableval], [no], [dnl Disable the docbuild by disabling the install tree for documentation diff --git a/docker/.gitpod.Dockerfile b/docker/.gitpod.Dockerfile index ff17d331515..3ff75298c77 100644 --- a/docker/.gitpod.Dockerfile +++ b/docker/.gitpod.Dockerfile @@ -1,5 +1,20 @@ # Use minimal Ubuntu installation that includes mamba FROM condaforge/mambaforge + +# Some basic system packages +RUN apt update && apt-get install -yq --no-install-recommends sudo gpg curl lsb-release + +# Make Docker available, like the default gitpod image does +# from https://github.com/gitpod-io/workspace-images/blob/main/chunks/tool-docker/Dockerfile @ 3f0988f2d06768d22d0aa1454ef0e963b0db65f3 +# - removed unneccessary "sudo" +# - replaced use of "install-packages" (https://github.com/gitpod-io/workspace-images/blob/main/base/install-packages) +# https://docs.docker.com/engine/install/ubuntu/ +RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ + $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null \ + && apt update \ + && apt-get install -yq --no-install-recommends docker-ce docker-ce-cli containerd.io + # Workaround so that vscode internals (such as git) find things installed in the conda env (notably ssh which is required to contribute to trac) ENV PATH $PATH:/workspace/sagetrac-mirror/venv/bin # Default to non-admin user diff --git a/docker/Dockerfile b/docker/Dockerfile index 269b4667ed9..bf14547cd38 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -41,12 +41,40 @@ # local branch for this to work. # ################################################################################ +################################################################################ +# HOWTO use this file to manually create new images for Docker Hub # +################################################################################ +# Make sure you have the tag checked out that you are trying to build. Then # +# from the root directory of the sage repository run: # +# # +# bash # open a subshell so 'set -e' does not close your shell on error # +# export ARTIFACT_BASE=source-clean # +# export DOCKER_TAG=9.6 # replace with the version you are building for # +# export CI_COMMIT_SHA=`git rev-parse --short HEAD` # +# export CPUTHREADS=8 # +# export RAMTHREADS=8 # +# export RAMTHREADS_DOCBUILD=8 # +# . .ci/update-env.sh # +# . .ci/setup-make-parallelity.sh # +# .ci/build-docker.sh # +# # +# Now you can push the relevant images to the Docker Hub: # +# # +# docker push sagemath/sagemath:$DOCKER_TAG # +# docker tag sagemath/sagemath:$DOCKER_TAG sagemath/sagemath:latest # +# docker push sagemath/sagemath:latest # +# docker push sagemath/sagemath-dev:$DOCKER_TAG # +# docker tag sagemath/sagemath-dev:$DOCKER_TAG sagemath/sagemath-dev:latest # +# docker push sagemath/sagemath-dev:latest # +################################################################################ + + ARG ARTIFACT_BASE=source-clean ################################################################################ # Image containing the run-time dependencies for Sage # ################################################################################ -FROM ubuntu:impish as run-time-dependencies +FROM ubuntu:jammy as run-time-dependencies LABEL maintainer="Erik M. Bray , Julian Rüth " # Set sane defaults for common environment variables. ENV LC_ALL C.UTF-8 @@ -82,7 +110,7 @@ WORKDIR $HOME FROM run-time-dependencies as build-time-dependencies # Install the build time dependencies & git & rdfind RUN sudo apt-get -qq update \ - && sudo apt-get -qq install -y wget build-essential automake m4 dpkg-dev python libssl-dev git rdfind \ + && sudo apt-get -qq install -y wget build-essential automake m4 dpkg-dev python3 libssl-dev git rdfind \ && sudo apt-get -qq clean \ && sudo rm -r /var/lib/apt/lists/* diff --git a/docker/README.md b/docker/README.md index e77008fc886..eb0f1ba344d 100644 --- a/docker/README.md +++ b/docker/README.md @@ -4,8 +4,8 @@ * `latest` — the stable `master` branch [![GitHub last commit (branch)](https://img.shields.io/github/last-commit/sagemath/sage/master.svg)](https://github.com/sagemath/sage/commits/master) [![GitLab CI](https://gitlab.com/sagemath/sage/badges/master/pipeline.svg)](https://gitlab.com/sagemath/sage/commits/master) * `x.x` — all stable releases of Sage are tagged with their version number. -* `x.x.{beta,rc}x` - betas and release candidates of Sage as [tagged in our git repository](https://github.com/sagemath/sage/tags). -* `develop` — the current development version of Sage which gets merged into the `master` branch when a new version of Sage is released [![GitHub last commit (branch)](https://img.shields.io/github/last-commit/sagemath/sage/develop.svg)](https://github.com/sagemath/sage/commits/develop) [![GitLab CI](https://gitlab.com/sagemath/sage/badges/develop/pipeline.svg)](https://gitlab.com/sagemath/sage/commits/develop) +* `x.x.{beta,rc}x` - betas and release candidates of Sage as [tagged in our git repository](https://github.com/sagemath/sage/tags); since docker images are built and uploaded manually at the moment, there are often no recent betas here anymore. +* `develop` — the current development version of Sage which gets merged into the `master` branch when a new version of Sage is released [![GitHub last commit (branch)](https://img.shields.io/github/last-commit/sagemath/sage/develop.svg)](https://github.com/sagemath/sage/commits/develop) [![GitLab CI](https://gitlab.com/sagemath/sage/badges/develop/pipeline.svg)](https://gitlab.com/sagemath/sage/commits/develop); again, there is often no recent develop build here anymore. * `-py3` - until Sage 9.1, we provided Python 2 builds (with no suffix) and Python 3 builds (with the `-py3` suffix). From Sage 9.2.beta0 on, all images we provide are based on Python 3 and the `-py3` suffix survives only for historical reasons: with or without it, you get Python 3. # What is SageMath @@ -26,7 +26,7 @@ There are several flavours of this image. ``` docker run -p8888:8888 sagemath/sagemath:latest sage-jupyter ``` -* [`sagemath/sagemath-dev`![image size](https://img.shields.io/microbadger/image-size/sagemath/sagemath-dev.svg)](https://hub.docker.com/r/sagemath/sagemath-dev) contains all the build artifacts to rebuild Sage quickly. This version is probably only relevant for Sage developers. Run this image with: +* [`sagemath/sagemath-dev`![image size](https://img.shields.io/microbadger/image-size/sagemath/sagemath-dev.svg)](https://hub.docker.com/r/sagemath/sagemath-dev) contains all the build artifacts to rebuild Sage quickly (currently, this is broken, see [#34241](https://trac.sagemath.org/ticket/34241).) This version is probably only relevant for Sage developers. Run this image with: ``` docker run -it sagemath/sagemath-dev:develop ``` @@ -41,11 +41,11 @@ Run `docker build -f docker/Dockerfile --build-arg ARTIFACT_BASE=sagemath/sagema # How these images get updated -Every push to our [github repository](https://github.com/sagemath/sage) triggers a "build" on our [Docker Hub](https://hub.docker.com) repositories. This build is mostly disabled by the `hooks/` and only updates the `README.md`. +Currently, these images are updated manually after every release. Instructions for this are at the top of `docker/Dockerfile`. -Every push to our [GitLab repository](https://gitlab.com/sagemath/sage) triggers a pipeline in GitLab CI which pushes the actual images to Docker Hub. +Every push to our [GitLab repository](https://gitlab.com/sagemath/sage) used to trigger a pipeline in GitLab CI which pushed the actual images to Docker Hub. These pipelines are not running anymore since nobody is maintaining them or providing the CI resources for it. (Please reach out to [sage-devel](https://groups.google.com/forum/#!forum/sage-devel) if you want to help!) -Have a look at `.circleci/` and `.gitlab-ci.yml` if you want to setup CircleCI or GitLab CI for your own fork of the SageMath repository. +Have a look at `.gitlab-ci.yml` if you want to setup GitLab CI for your own fork of the SageMath repository. # Report bugs and issues diff --git a/m4/host-cpu-c-abi.m4 b/m4/host-cpu-c-abi.m4 new file mode 100644 index 00000000000..b9223241b43 --- /dev/null +++ b/m4/host-cpu-c-abi.m4 @@ -0,0 +1,678 @@ +# host-cpu-c-abi.m4 serial 15 +dnl Copyright (C) 2002-2022 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl From Bruno Haible and Sam Steingold. + +dnl Sets the HOST_CPU variable to the canonical name of the CPU. +dnl Sets the HOST_CPU_C_ABI variable to the canonical name of the CPU with its +dnl C language ABI (application binary interface). +dnl Also defines __${HOST_CPU}__ and __${HOST_CPU_C_ABI}__ as C macros in +dnl config.h. +dnl +dnl This canonical name can be used to select a particular assembly language +dnl source file that will interoperate with C code on the given host. +dnl +dnl For example: +dnl * 'i386' and 'sparc' are different canonical names, because code for i386 +dnl will not run on SPARC CPUs and vice versa. They have different +dnl instruction sets. +dnl * 'sparc' and 'sparc64' are different canonical names, because code for +dnl 'sparc' and code for 'sparc64' cannot be linked together: 'sparc' code +dnl contains 32-bit instructions, whereas 'sparc64' code contains 64-bit +dnl instructions. A process on a SPARC CPU can be in 32-bit mode or in 64-bit +dnl mode, but not both. +dnl * 'mips' and 'mipsn32' are different canonical names, because they use +dnl different argument passing and return conventions for C functions, and +dnl although the instruction set of 'mips' is a large subset of the +dnl instruction set of 'mipsn32'. +dnl * 'mipsn32' and 'mips64' are different canonical names, because they use +dnl different sizes for the C types like 'int' and 'void *', and although +dnl the instruction sets of 'mipsn32' and 'mips64' are the same. +dnl * The same canonical name is used for different endiannesses. You can +dnl determine the endianness through preprocessor symbols: +dnl - 'arm': test __ARMEL__. +dnl - 'mips', 'mipsn32', 'mips64': test _MIPSEB vs. _MIPSEL. +dnl - 'powerpc64': test _BIG_ENDIAN vs. _LITTLE_ENDIAN. +dnl * The same name 'i386' is used for CPUs of type i386, i486, i586 +dnl (Pentium), AMD K7, Pentium II, Pentium IV, etc., because +dnl - Instructions that do not exist on all of these CPUs (cmpxchg, +dnl MMX, SSE, SSE2, 3DNow! etc.) are not frequently used. If your +dnl assembly language source files use such instructions, you will +dnl need to make the distinction. +dnl - Speed of execution of the common instruction set is reasonable across +dnl the entire family of CPUs. If you have assembly language source files +dnl that are optimized for particular CPU types (like GNU gmp has), you +dnl will need to make the distinction. +dnl See . +AC_DEFUN([gl_HOST_CPU_C_ABI], +[ + AC_REQUIRE([AC_CANONICAL_HOST]) + AC_REQUIRE([gl_C_ASM]) + AC_CACHE_CHECK([host CPU and C ABI], [gl_cv_host_cpu_c_abi], + [case "$host_cpu" in + +changequote(,)dnl + i[34567]86 ) +changequote([,])dnl + gl_cv_host_cpu_c_abi=i386 + ;; + + x86_64 ) + # On x86_64 systems, the C compiler may be generating code in one of + # these ABIs: + # - 64-bit instruction set, 64-bit pointers, 64-bit 'long': x86_64. + # - 64-bit instruction set, 64-bit pointers, 32-bit 'long': x86_64 + # with native Windows (mingw, MSVC). + # - 64-bit instruction set, 32-bit pointers, 32-bit 'long': x86_64-x32. + # - 32-bit instruction set, 32-bit pointers, 32-bit 'long': i386. + AC_COMPILE_IFELSE( + [AC_LANG_SOURCE( + [[#if (defined __x86_64__ || defined __amd64__ \ + || defined _M_X64 || defined _M_AMD64) + int ok; + #else + error fail + #endif + ]])], + [AC_COMPILE_IFELSE( + [AC_LANG_SOURCE( + [[#if defined __ILP32__ || defined _ILP32 + int ok; + #else + error fail + #endif + ]])], + [gl_cv_host_cpu_c_abi=x86_64-x32], + [gl_cv_host_cpu_c_abi=x86_64])], + [gl_cv_host_cpu_c_abi=i386]) + ;; + +changequote(,)dnl + alphaev[4-8] | alphaev56 | alphapca5[67] | alphaev6[78] ) +changequote([,])dnl + gl_cv_host_cpu_c_abi=alpha + ;; + + arm* | aarch64 ) + # Assume arm with EABI. + # On arm64 systems, the C compiler may be generating code in one of + # these ABIs: + # - aarch64 instruction set, 64-bit pointers, 64-bit 'long': arm64. + # - aarch64 instruction set, 32-bit pointers, 32-bit 'long': arm64-ilp32. + # - 32-bit instruction set, 32-bit pointers, 32-bit 'long': arm or armhf. + AC_COMPILE_IFELSE( + [AC_LANG_SOURCE( + [[#ifdef __aarch64__ + int ok; + #else + error fail + #endif + ]])], + [AC_COMPILE_IFELSE( + [AC_LANG_SOURCE( + [[#if defined __ILP32__ || defined _ILP32 + int ok; + #else + error fail + #endif + ]])], + [gl_cv_host_cpu_c_abi=arm64-ilp32], + [gl_cv_host_cpu_c_abi=arm64])], + [# Don't distinguish little-endian and big-endian arm, since they + # don't require different machine code for simple operations and + # since the user can distinguish them through the preprocessor + # defines __ARMEL__ vs. __ARMEB__. + # But distinguish arm which passes floating-point arguments and + # return values in integer registers (r0, r1, ...) - this is + # gcc -mfloat-abi=soft or gcc -mfloat-abi=softfp - from arm which + # passes them in float registers (s0, s1, ...) and double registers + # (d0, d1, ...) - this is gcc -mfloat-abi=hard. GCC 4.6 or newer + # sets the preprocessor defines __ARM_PCS (for the first case) and + # __ARM_PCS_VFP (for the second case), but older GCC does not. + echo 'double ddd; void func (double dd) { ddd = dd; }' > conftest.c + # Look for a reference to the register d0 in the .s file. + AC_TRY_COMMAND(${CC-cc} $CFLAGS $CPPFLAGS $gl_c_asm_opt conftest.c) >/dev/null 2>&1 + if LC_ALL=C grep 'd0,' conftest.$gl_asmext >/dev/null; then + gl_cv_host_cpu_c_abi=armhf + else + gl_cv_host_cpu_c_abi=arm + fi + rm -f conftest* + ]) + ;; + + hppa1.0 | hppa1.1 | hppa2.0* | hppa64 ) + # On hppa, the C compiler may be generating 32-bit code or 64-bit + # code. In the latter case, it defines _LP64 and __LP64__. + AC_COMPILE_IFELSE( + [AC_LANG_SOURCE( + [[#ifdef __LP64__ + int ok; + #else + error fail + #endif + ]])], + [gl_cv_host_cpu_c_abi=hppa64], + [gl_cv_host_cpu_c_abi=hppa]) + ;; + + ia64* ) + # On ia64 on HP-UX, the C compiler may be generating 64-bit code or + # 32-bit code. In the latter case, it defines _ILP32. + AC_COMPILE_IFELSE( + [AC_LANG_SOURCE( + [[#ifdef _ILP32 + int ok; + #else + error fail + #endif + ]])], + [gl_cv_host_cpu_c_abi=ia64-ilp32], + [gl_cv_host_cpu_c_abi=ia64]) + ;; + + mips* ) + # We should also check for (_MIPS_SZPTR == 64), but gcc keeps this + # at 32. + AC_COMPILE_IFELSE( + [AC_LANG_SOURCE( + [[#if defined _MIPS_SZLONG && (_MIPS_SZLONG == 64) + int ok; + #else + error fail + #endif + ]])], + [gl_cv_host_cpu_c_abi=mips64], + [# In the n32 ABI, _ABIN32 is defined, _ABIO32 is not defined (but + # may later get defined by ), and _MIPS_SIM == _ABIN32. + # In the 32 ABI, _ABIO32 is defined, _ABIN32 is not defined (but + # may later get defined by ), and _MIPS_SIM == _ABIO32. + AC_COMPILE_IFELSE( + [AC_LANG_SOURCE( + [[#if (_MIPS_SIM == _ABIN32) + int ok; + #else + error fail + #endif + ]])], + [gl_cv_host_cpu_c_abi=mipsn32], + [gl_cv_host_cpu_c_abi=mips])]) + ;; + + powerpc* ) + # Different ABIs are in use on AIX vs. Mac OS X vs. Linux,*BSD. + # No need to distinguish them here; the caller may distinguish + # them based on the OS. + # On powerpc64 systems, the C compiler may still be generating + # 32-bit code. And on powerpc-ibm-aix systems, the C compiler may + # be generating 64-bit code. + AC_COMPILE_IFELSE( + [AC_LANG_SOURCE( + [[#if defined __powerpc64__ || defined __LP64__ + int ok; + #else + error fail + #endif + ]])], + [# On powerpc64, there are two ABIs on Linux: The AIX compatible + # one and the ELFv2 one. The latter defines _CALL_ELF=2. + AC_COMPILE_IFELSE( + [AC_LANG_SOURCE( + [[#if defined _CALL_ELF && _CALL_ELF == 2 + int ok; + #else + error fail + #endif + ]])], + [gl_cv_host_cpu_c_abi=powerpc64-elfv2], + [gl_cv_host_cpu_c_abi=powerpc64]) + ], + [gl_cv_host_cpu_c_abi=powerpc]) + ;; + + rs6000 ) + gl_cv_host_cpu_c_abi=powerpc + ;; + + riscv32 | riscv64 ) + # There are 2 architectures (with variants): rv32* and rv64*. + AC_COMPILE_IFELSE( + [AC_LANG_SOURCE( + [[#if __riscv_xlen == 64 + int ok; + #else + error fail + #endif + ]])], + [cpu=riscv64], + [cpu=riscv32]) + # There are 6 ABIs: ilp32, ilp32f, ilp32d, lp64, lp64f, lp64d. + # Size of 'long' and 'void *': + AC_COMPILE_IFELSE( + [AC_LANG_SOURCE( + [[#if defined __LP64__ + int ok; + #else + error fail + #endif + ]])], + [main_abi=lp64], + [main_abi=ilp32]) + # Float ABIs: + # __riscv_float_abi_double: + # 'float' and 'double' are passed in floating-point registers. + # __riscv_float_abi_single: + # 'float' are passed in floating-point registers. + # __riscv_float_abi_soft: + # No values are passed in floating-point registers. + AC_COMPILE_IFELSE( + [AC_LANG_SOURCE( + [[#if defined __riscv_float_abi_double + int ok; + #else + error fail + #endif + ]])], + [float_abi=d], + [AC_COMPILE_IFELSE( + [AC_LANG_SOURCE( + [[#if defined __riscv_float_abi_single + int ok; + #else + error fail + #endif + ]])], + [float_abi=f], + [float_abi='']) + ]) + gl_cv_host_cpu_c_abi="${cpu}-${main_abi}${float_abi}" + ;; + + s390* ) + # On s390x, the C compiler may be generating 64-bit (= s390x) code + # or 31-bit (= s390) code. + AC_COMPILE_IFELSE( + [AC_LANG_SOURCE( + [[#if defined __LP64__ || defined __s390x__ + int ok; + #else + error fail + #endif + ]])], + [gl_cv_host_cpu_c_abi=s390x], + [gl_cv_host_cpu_c_abi=s390]) + ;; + + sparc | sparc64 ) + # UltraSPARCs running Linux have `uname -m` = "sparc64", but the + # C compiler still generates 32-bit code. + AC_COMPILE_IFELSE( + [AC_LANG_SOURCE( + [[#if defined __sparcv9 || defined __arch64__ + int ok; + #else + error fail + #endif + ]])], + [gl_cv_host_cpu_c_abi=sparc64], + [gl_cv_host_cpu_c_abi=sparc]) + ;; + + *) + gl_cv_host_cpu_c_abi="$host_cpu" + ;; + esac + ]) + + dnl In most cases, $HOST_CPU and $HOST_CPU_C_ABI are the same. + HOST_CPU=`echo "$gl_cv_host_cpu_c_abi" | sed -e 's/-.*//'` + HOST_CPU_C_ABI="$gl_cv_host_cpu_c_abi" + AC_SUBST([HOST_CPU]) + AC_SUBST([HOST_CPU_C_ABI]) + + # This was + # AC_DEFINE_UNQUOTED([__${HOST_CPU}__]) + # AC_DEFINE_UNQUOTED([__${HOST_CPU_C_ABI}__]) + # earlier, but KAI C++ 3.2d doesn't like this. + sed -e 's/-/_/g' >> confdefs.h <&1 /dev/null 2>&1 \ + && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 \ + || PATH_SEPARATOR=';' + } +fi + +if test -n "$LD"; then + AC_MSG_CHECKING([for ld]) +elif test "$GCC" = yes; then + AC_MSG_CHECKING([for ld used by $CC]) +elif test "$with_gnu_ld" = yes; then + AC_MSG_CHECKING([for GNU ld]) +else + AC_MSG_CHECKING([for non-GNU ld]) +fi +if test -n "$LD"; then + # Let the user override the test with a path. + : +else + AC_CACHE_VAL([acl_cv_path_LD], + [ + acl_cv_path_LD= # Final result of this test + ac_prog=ld # Program to search in $PATH + if test "$GCC" = yes; then + # Check if gcc -print-prog-name=ld gives a path. + case $host in + *-*-mingw*) + # gcc leaves a trailing carriage return which upsets mingw + acl_output=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; + *) + acl_output=`($CC -print-prog-name=ld) 2>&5` ;; + esac + case $acl_output in + # Accept absolute paths. + [[\\/]]* | ?:[[\\/]]*) + re_direlt='/[[^/]][[^/]]*/\.\./' + # Canonicalize the pathname of ld + acl_output=`echo "$acl_output" | sed 's%\\\\%/%g'` + while echo "$acl_output" | grep "$re_direlt" > /dev/null 2>&1; do + acl_output=`echo $acl_output | sed "s%$re_direlt%/%"` + done + # Got the pathname. No search in PATH is needed. + acl_cv_path_LD="$acl_output" + ac_prog= + ;; + "") + # If it fails, then pretend we aren't using GCC. + ;; + *) + # If it is relative, then search for the first ld in PATH. + with_gnu_ld=unknown + ;; + esac + fi + if test -n "$ac_prog"; then + # Search for $ac_prog in $PATH. + acl_save_ifs="$IFS"; IFS=$PATH_SEPARATOR + for ac_dir in $PATH; do + IFS="$acl_save_ifs" + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then + acl_cv_path_LD="$ac_dir/$ac_prog" + # Check to see if the program is GNU ld. I'd rather use --version, + # but apparently some variants of GNU ld only accept -v. + # Break only if it was the GNU/non-GNU ld that we prefer. + case `"$acl_cv_path_LD" -v 2>&1 conftest.sh + . ./conftest.sh + rm -f ./conftest.sh + acl_cv_rpath=done + ]) + wl="$acl_cv_wl" + acl_libext="$acl_cv_libext" + acl_shlibext="$acl_cv_shlibext" + acl_libname_spec="$acl_cv_libname_spec" + acl_library_names_spec="$acl_cv_library_names_spec" + acl_hardcode_libdir_flag_spec="$acl_cv_hardcode_libdir_flag_spec" + acl_hardcode_libdir_separator="$acl_cv_hardcode_libdir_separator" + acl_hardcode_direct="$acl_cv_hardcode_direct" + acl_hardcode_minus_L="$acl_cv_hardcode_minus_L" + dnl Determine whether the user wants rpath handling at all. + AC_ARG_ENABLE([rpath], + [ --disable-rpath do not hardcode runtime library paths], + :, enable_rpath=yes) +]) + +dnl AC_LIB_FROMPACKAGE(name, package) +dnl declares that libname comes from the given package. The configure file +dnl will then not have a --with-libname-prefix option but a +dnl --with-package-prefix option. Several libraries can come from the same +dnl package. This declaration must occur before an AC_LIB_LINKFLAGS or similar +dnl macro call that searches for libname. +AC_DEFUN([AC_LIB_FROMPACKAGE], +[ + pushdef([NAME],[m4_translit([$1],[abcdefghijklmnopqrstuvwxyz./+-], + [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])]) + define([acl_frompackage_]NAME, [$2]) + popdef([NAME]) + pushdef([PACK],[$2]) + pushdef([PACKUP],[m4_translit(PACK,[abcdefghijklmnopqrstuvwxyz./+-], + [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])]) + define([acl_libsinpackage_]PACKUP, + m4_ifdef([acl_libsinpackage_]PACKUP, [m4_defn([acl_libsinpackage_]PACKUP)[, ]],)[lib$1]) + popdef([PACKUP]) + popdef([PACK]) +]) + +dnl AC_LIB_LINKFLAGS_BODY(name [, dependencies]) searches for libname and +dnl the libraries corresponding to explicit and implicit dependencies. +dnl Sets the LIB${NAME}, LTLIB${NAME} and INC${NAME} variables. +dnl Also, sets the LIB${NAME}_PREFIX variable to nonempty if libname was found +dnl in ${LIB${NAME}_PREFIX}/$acl_libdirstem. +AC_DEFUN([AC_LIB_LINKFLAGS_BODY], +[ + AC_REQUIRE([AC_LIB_PREPARE_MULTILIB]) + pushdef([NAME],[m4_translit([$1],[abcdefghijklmnopqrstuvwxyz./+-], + [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])]) + pushdef([PACK],[m4_ifdef([acl_frompackage_]NAME, [acl_frompackage_]NAME, lib[$1])]) + pushdef([PACKUP],[m4_translit(PACK,[abcdefghijklmnopqrstuvwxyz./+-], + [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])]) + pushdef([PACKLIBS],[m4_ifdef([acl_frompackage_]NAME, [acl_libsinpackage_]PACKUP, lib[$1])]) + dnl By default, look in $includedir and $libdir. + use_additional=yes + AC_LIB_WITH_FINAL_PREFIX([ + eval additional_includedir=\"$includedir\" + eval additional_libdir=\"$libdir\" + eval additional_libdir2=\"$exec_prefix/$acl_libdirstem2\" + eval additional_libdir3=\"$exec_prefix/$acl_libdirstem3\" + ]) + AC_ARG_WITH(PACK[-prefix], +[[ --with-]]PACK[[-prefix[=DIR] search for ]]PACKLIBS[[ in DIR/include and DIR/lib + --without-]]PACK[[-prefix don't search for ]]PACKLIBS[[ in includedir and libdir]], +[ + if test "X$withval" = "Xno"; then + use_additional=no + else + if test "X$withval" = "X"; then + AC_LIB_WITH_FINAL_PREFIX([ + eval additional_includedir=\"$includedir\" + eval additional_libdir=\"$libdir\" + eval additional_libdir2=\"$exec_prefix/$acl_libdirstem2\" + eval additional_libdir3=\"$exec_prefix/$acl_libdirstem3\" + ]) + else + additional_includedir="$withval/include" + additional_libdir="$withval/$acl_libdirstem" + additional_libdir2="$withval/$acl_libdirstem2" + additional_libdir3="$withval/$acl_libdirstem3" + fi + fi +]) + if test "X$additional_libdir2" = "X$additional_libdir"; then + additional_libdir2= + fi + if test "X$additional_libdir3" = "X$additional_libdir"; then + additional_libdir3= + fi + dnl Search the library and its dependencies in $additional_libdir and + dnl $LDFLAGS. Using breadth-first-seach. + LIB[]NAME= + LTLIB[]NAME= + INC[]NAME= + LIB[]NAME[]_PREFIX= + dnl HAVE_LIB${NAME} is an indicator that LIB${NAME}, LTLIB${NAME} have been + dnl computed. So it has to be reset here. + HAVE_LIB[]NAME= + rpathdirs= + ltrpathdirs= + names_already_handled= + names_next_round='$1 $2' + while test -n "$names_next_round"; do + names_this_round="$names_next_round" + names_next_round= + for name in $names_this_round; do + already_handled= + for n in $names_already_handled; do + if test "$n" = "$name"; then + already_handled=yes + break + fi + done + if test -z "$already_handled"; then + names_already_handled="$names_already_handled $name" + dnl See if it was already located by an earlier AC_LIB_LINKFLAGS + dnl or AC_LIB_HAVE_LINKFLAGS call. + uppername=`echo "$name" | sed -e 'y|abcdefghijklmnopqrstuvwxyz./+-|ABCDEFGHIJKLMNOPQRSTUVWXYZ____|'` + eval value=\"\$HAVE_LIB$uppername\" + if test -n "$value"; then + if test "$value" = yes; then + eval value=\"\$LIB$uppername\" + test -z "$value" || LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$value" + eval value=\"\$LTLIB$uppername\" + test -z "$value" || LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }$value" + else + dnl An earlier call to AC_LIB_HAVE_LINKFLAGS has determined + dnl that this library doesn't exist. So just drop it. + : + fi + else + dnl Search the library lib$name in $additional_libdir and $LDFLAGS + dnl and the already constructed $LIBNAME/$LTLIBNAME. + found_dir= + found_la= + found_so= + found_a= + eval libname=\"$acl_libname_spec\" # typically: libname=lib$name + if test -n "$acl_shlibext"; then + shrext=".$acl_shlibext" # typically: shrext=.so + else + shrext= + fi + if test $use_additional = yes; then + for additional_libdir_variable in additional_libdir additional_libdir2 additional_libdir3; do + if test "X$found_dir" = "X"; then + eval dir=\$$additional_libdir_variable + if test -n "$dir"; then + dnl The same code as in the loop below: + dnl First look for a shared library. + if test -n "$acl_shlibext"; then + if test -f "$dir/$libname$shrext" && acl_is_expected_elfclass < "$dir/$libname$shrext"; then + found_dir="$dir" + found_so="$dir/$libname$shrext" + else + if test "$acl_library_names_spec" = '$libname$shrext$versuffix'; then + ver=`(cd "$dir" && \ + for f in "$libname$shrext".*; do echo "$f"; done \ + | sed -e "s,^$libname$shrext\\\\.,," \ + | sort -t '.' -n -r -k1,1 -k2,2 -k3,3 -k4,4 -k5,5 \ + | sed 1q ) 2>/dev/null` + if test -n "$ver" && test -f "$dir/$libname$shrext.$ver" && acl_is_expected_elfclass < "$dir/$libname$shrext.$ver"; then + found_dir="$dir" + found_so="$dir/$libname$shrext.$ver" + fi + else + eval library_names=\"$acl_library_names_spec\" + for f in $library_names; do + if test -f "$dir/$f" && acl_is_expected_elfclass < "$dir/$f"; then + found_dir="$dir" + found_so="$dir/$f" + break + fi + done + fi + fi + fi + dnl Then look for a static library. + if test "X$found_dir" = "X"; then + if test -f "$dir/$libname.$acl_libext" && ${AR-ar} -p "$dir/$libname.$acl_libext" | acl_is_expected_elfclass; then + found_dir="$dir" + found_a="$dir/$libname.$acl_libext" + fi + fi + if test "X$found_dir" != "X"; then + if test -f "$dir/$libname.la"; then + found_la="$dir/$libname.la" + fi + fi + fi + fi + done + fi + if test "X$found_dir" = "X"; then + for x in $LDFLAGS $LTLIB[]NAME; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + case "$x" in + -L*) + dir=`echo "X$x" | sed -e 's/^X-L//'` + dnl First look for a shared library. + if test -n "$acl_shlibext"; then + if test -f "$dir/$libname$shrext" && acl_is_expected_elfclass < "$dir/$libname$shrext"; then + found_dir="$dir" + found_so="$dir/$libname$shrext" + else + if test "$acl_library_names_spec" = '$libname$shrext$versuffix'; then + ver=`(cd "$dir" && \ + for f in "$libname$shrext".*; do echo "$f"; done \ + | sed -e "s,^$libname$shrext\\\\.,," \ + | sort -t '.' -n -r -k1,1 -k2,2 -k3,3 -k4,4 -k5,5 \ + | sed 1q ) 2>/dev/null` + if test -n "$ver" && test -f "$dir/$libname$shrext.$ver" && acl_is_expected_elfclass < "$dir/$libname$shrext.$ver"; then + found_dir="$dir" + found_so="$dir/$libname$shrext.$ver" + fi + else + eval library_names=\"$acl_library_names_spec\" + for f in $library_names; do + if test -f "$dir/$f" && acl_is_expected_elfclass < "$dir/$f"; then + found_dir="$dir" + found_so="$dir/$f" + break + fi + done + fi + fi + fi + dnl Then look for a static library. + if test "X$found_dir" = "X"; then + if test -f "$dir/$libname.$acl_libext" && ${AR-ar} -p "$dir/$libname.$acl_libext" | acl_is_expected_elfclass; then + found_dir="$dir" + found_a="$dir/$libname.$acl_libext" + fi + fi + if test "X$found_dir" != "X"; then + if test -f "$dir/$libname.la"; then + found_la="$dir/$libname.la" + fi + fi + ;; + esac + if test "X$found_dir" != "X"; then + break + fi + done + fi + if test "X$found_dir" != "X"; then + dnl Found the library. + LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-L$found_dir -l$name" + if test "X$found_so" != "X"; then + dnl Linking with a shared library. We attempt to hardcode its + dnl directory into the executable's runpath, unless it's the + dnl standard /usr/lib. + if test "$enable_rpath" = no \ + || test "X$found_dir" = "X/usr/$acl_libdirstem" \ + || test "X$found_dir" = "X/usr/$acl_libdirstem2" \ + || test "X$found_dir" = "X/usr/$acl_libdirstem3"; then + dnl No hardcoding is needed. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" + else + dnl Use an explicit option to hardcode DIR into the resulting + dnl binary. + dnl Potentially add DIR to ltrpathdirs. + dnl The ltrpathdirs will be appended to $LTLIBNAME at the end. + haveit= + for x in $ltrpathdirs; do + if test "X$x" = "X$found_dir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + ltrpathdirs="$ltrpathdirs $found_dir" + fi + dnl The hardcoding into $LIBNAME is system dependent. + if test "$acl_hardcode_direct" = yes; then + dnl Using DIR/libNAME.so during linking hardcodes DIR into the + dnl resulting binary. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" + else + if test -n "$acl_hardcode_libdir_flag_spec" && test "$acl_hardcode_minus_L" = no; then + dnl Use an explicit option to hardcode DIR into the resulting + dnl binary. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" + dnl Potentially add DIR to rpathdirs. + dnl The rpathdirs will be appended to $LIBNAME at the end. + haveit= + for x in $rpathdirs; do + if test "X$x" = "X$found_dir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + rpathdirs="$rpathdirs $found_dir" + fi + else + dnl Rely on "-L$found_dir". + dnl But don't add it if it's already contained in the LDFLAGS + dnl or the already constructed $LIBNAME + haveit= + for x in $LDFLAGS $LIB[]NAME; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + if test "X$x" = "X-L$found_dir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$found_dir" + fi + if test "$acl_hardcode_minus_L" != no; then + dnl FIXME: Not sure whether we should use + dnl "-L$found_dir -l$name" or "-L$found_dir $found_so" + dnl here. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" + else + dnl We cannot use $acl_hardcode_runpath_var and LD_RUN_PATH + dnl here, because this doesn't fit in flags passed to the + dnl compiler. So give up. No hardcoding. This affects only + dnl very old systems. + dnl FIXME: Not sure whether we should use + dnl "-L$found_dir -l$name" or "-L$found_dir $found_so" + dnl here. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-l$name" + fi + fi + fi + fi + else + if test "X$found_a" != "X"; then + dnl Linking with a static library. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_a" + else + dnl We shouldn't come here, but anyway it's good to have a + dnl fallback. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$found_dir -l$name" + fi + fi + dnl Assume the include files are nearby. + additional_includedir= + case "$found_dir" in + */$acl_libdirstem | */$acl_libdirstem/) + basedir=`echo "X$found_dir" | sed -e 's,^X,,' -e "s,/$acl_libdirstem/"'*$,,'` + if test "$name" = '$1'; then + LIB[]NAME[]_PREFIX="$basedir" + fi + additional_includedir="$basedir/include" + ;; + */$acl_libdirstem2 | */$acl_libdirstem2/) + basedir=`echo "X$found_dir" | sed -e 's,^X,,' -e "s,/$acl_libdirstem2/"'*$,,'` + if test "$name" = '$1'; then + LIB[]NAME[]_PREFIX="$basedir" + fi + additional_includedir="$basedir/include" + ;; + */$acl_libdirstem3 | */$acl_libdirstem3/) + basedir=`echo "X$found_dir" | sed -e 's,^X,,' -e "s,/$acl_libdirstem3/"'*$,,'` + if test "$name" = '$1'; then + LIB[]NAME[]_PREFIX="$basedir" + fi + additional_includedir="$basedir/include" + ;; + esac + if test "X$additional_includedir" != "X"; then + dnl Potentially add $additional_includedir to $INCNAME. + dnl But don't add it + dnl 1. if it's the standard /usr/include, + dnl 2. if it's /usr/local/include and we are using GCC on Linux, + dnl 3. if it's already present in $CPPFLAGS or the already + dnl constructed $INCNAME, + dnl 4. if it doesn't exist as a directory. + if test "X$additional_includedir" != "X/usr/include"; then + haveit= + if test "X$additional_includedir" = "X/usr/local/include"; then + if test -n "$GCC"; then + case $host_os in + linux* | gnu* | k*bsd*-gnu) haveit=yes;; + esac + fi + fi + if test -z "$haveit"; then + for x in $CPPFLAGS $INC[]NAME; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + if test "X$x" = "X-I$additional_includedir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + if test -d "$additional_includedir"; then + dnl Really add $additional_includedir to $INCNAME. + INC[]NAME="${INC[]NAME}${INC[]NAME:+ }-I$additional_includedir" + fi + fi + fi + fi + fi + dnl Look for dependencies. + if test -n "$found_la"; then + dnl Read the .la file. It defines the variables + dnl dlname, library_names, old_library, dependency_libs, current, + dnl age, revision, installed, dlopen, dlpreopen, libdir. + save_libdir="$libdir" + case "$found_la" in + */* | *\\*) . "$found_la" ;; + *) . "./$found_la" ;; + esac + libdir="$save_libdir" + dnl We use only dependency_libs. + for dep in $dependency_libs; do + case "$dep" in + -L*) + dependency_libdir=`echo "X$dep" | sed -e 's/^X-L//'` + dnl Potentially add $dependency_libdir to $LIBNAME and $LTLIBNAME. + dnl But don't add it + dnl 1. if it's the standard /usr/lib, + dnl 2. if it's /usr/local/lib and we are using GCC on Linux, + dnl 3. if it's already present in $LDFLAGS or the already + dnl constructed $LIBNAME, + dnl 4. if it doesn't exist as a directory. + if test "X$dependency_libdir" != "X/usr/$acl_libdirstem" \ + && test "X$dependency_libdir" != "X/usr/$acl_libdirstem2" \ + && test "X$dependency_libdir" != "X/usr/$acl_libdirstem3"; then + haveit= + if test "X$dependency_libdir" = "X/usr/local/$acl_libdirstem" \ + || test "X$dependency_libdir" = "X/usr/local/$acl_libdirstem2" \ + || test "X$dependency_libdir" = "X/usr/local/$acl_libdirstem3"; then + if test -n "$GCC"; then + case $host_os in + linux* | gnu* | k*bsd*-gnu) haveit=yes;; + esac + fi + fi + if test -z "$haveit"; then + haveit= + for x in $LDFLAGS $LIB[]NAME; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + if test "X$x" = "X-L$dependency_libdir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + if test -d "$dependency_libdir"; then + dnl Really add $dependency_libdir to $LIBNAME. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$dependency_libdir" + fi + fi + haveit= + for x in $LDFLAGS $LTLIB[]NAME; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + if test "X$x" = "X-L$dependency_libdir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + if test -d "$dependency_libdir"; then + dnl Really add $dependency_libdir to $LTLIBNAME. + LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-L$dependency_libdir" + fi + fi + fi + fi + ;; + -R*) + dir=`echo "X$dep" | sed -e 's/^X-R//'` + if test "$enable_rpath" != no; then + dnl Potentially add DIR to rpathdirs. + dnl The rpathdirs will be appended to $LIBNAME at the end. + haveit= + for x in $rpathdirs; do + if test "X$x" = "X$dir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + rpathdirs="$rpathdirs $dir" + fi + dnl Potentially add DIR to ltrpathdirs. + dnl The ltrpathdirs will be appended to $LTLIBNAME at the end. + haveit= + for x in $ltrpathdirs; do + if test "X$x" = "X$dir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + ltrpathdirs="$ltrpathdirs $dir" + fi + fi + ;; + -l*) + dnl Handle this in the next round. + dnl But on GNU systems, ignore -lc options, because + dnl - linking with libc is the default anyway, + dnl - linking with libc.a may produce an error + dnl "/usr/bin/ld: dynamic STT_GNU_IFUNC symbol `strcmp' with pointer equality in `/usr/lib/libc.a(strcmp.o)' can not be used when making an executable; recompile with -fPIE and relink with -pie" + dnl or may produce an executable that always crashes, see + dnl . + dep=`echo "X$dep" | sed -e 's/^X-l//'` + if test "X$dep" != Xc \ + || case $host_os in + linux* | gnu* | k*bsd*-gnu) false ;; + *) true ;; + esac; then + names_next_round="$names_next_round $dep" + fi + ;; + *.la) + dnl Handle this in the next round. Throw away the .la's + dnl directory; it is already contained in a preceding -L + dnl option. + names_next_round="$names_next_round "`echo "X$dep" | sed -e 's,^X.*/,,' -e 's,^lib,,' -e 's,\.la$,,'` + ;; + *) + dnl Most likely an immediate library name. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$dep" + LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }$dep" + ;; + esac + done + fi + else + dnl Didn't find the library; assume it is in the system directories + dnl known to the linker and runtime loader. (All the system + dnl directories known to the linker should also be known to the + dnl runtime loader, otherwise the system is severely misconfigured.) + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-l$name" + LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-l$name" + fi + fi + fi + done + done + if test "X$rpathdirs" != "X"; then + if test -n "$acl_hardcode_libdir_separator"; then + dnl Weird platform: only the last -rpath option counts, the user must + dnl pass all path elements in one option. We can arrange that for a + dnl single library, but not when more than one $LIBNAMEs are used. + alldirs= + for found_dir in $rpathdirs; do + alldirs="${alldirs}${alldirs:+$acl_hardcode_libdir_separator}$found_dir" + done + dnl Note: acl_hardcode_libdir_flag_spec uses $libdir and $wl. + acl_save_libdir="$libdir" + libdir="$alldirs" + eval flag=\"$acl_hardcode_libdir_flag_spec\" + libdir="$acl_save_libdir" + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$flag" + else + dnl The -rpath options are cumulative. + for found_dir in $rpathdirs; do + acl_save_libdir="$libdir" + libdir="$found_dir" + eval flag=\"$acl_hardcode_libdir_flag_spec\" + libdir="$acl_save_libdir" + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$flag" + done + fi + fi + if test "X$ltrpathdirs" != "X"; then + dnl When using libtool, the option that works for both libraries and + dnl executables is -R. The -R options are cumulative. + for found_dir in $ltrpathdirs; do + LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-R$found_dir" + done + fi + popdef([PACKLIBS]) + popdef([PACKUP]) + popdef([PACK]) + popdef([NAME]) +]) + +dnl AC_LIB_APPENDTOVAR(VAR, CONTENTS) appends the elements of CONTENTS to VAR, +dnl unless already present in VAR. +dnl Works only for CPPFLAGS, not for LIB* variables because that sometimes +dnl contains two or three consecutive elements that belong together. +AC_DEFUN([AC_LIB_APPENDTOVAR], +[ + for element in [$2]; do + haveit= + for x in $[$1]; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + if test "X$x" = "X$element"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + [$1]="${[$1]}${[$1]:+ }$element" + fi + done +]) + +dnl For those cases where a variable contains several -L and -l options +dnl referring to unknown libraries and directories, this macro determines the +dnl necessary additional linker options for the runtime path. +dnl AC_LIB_LINKFLAGS_FROM_LIBS([LDADDVAR], [LIBSVALUE], [USE-LIBTOOL]) +dnl sets LDADDVAR to linker options needed together with LIBSVALUE. +dnl If USE-LIBTOOL evaluates to non-empty, linking with libtool is assumed, +dnl otherwise linking without libtool is assumed. +AC_DEFUN([AC_LIB_LINKFLAGS_FROM_LIBS], +[ + AC_REQUIRE([AC_LIB_RPATH]) + AC_REQUIRE([AC_LIB_PREPARE_MULTILIB]) + $1= + if test "$enable_rpath" != no; then + if test -n "$acl_hardcode_libdir_flag_spec" && test "$acl_hardcode_minus_L" = no; then + dnl Use an explicit option to hardcode directories into the resulting + dnl binary. + rpathdirs= + next= + for opt in $2; do + if test -n "$next"; then + dir="$next" + dnl No need to hardcode the standard /usr/lib. + if test "X$dir" != "X/usr/$acl_libdirstem" \ + && test "X$dir" != "X/usr/$acl_libdirstem2" \ + && test "X$dir" != "X/usr/$acl_libdirstem3"; then + rpathdirs="$rpathdirs $dir" + fi + next= + else + case $opt in + -L) next=yes ;; + -L*) dir=`echo "X$opt" | sed -e 's,^X-L,,'` + dnl No need to hardcode the standard /usr/lib. + if test "X$dir" != "X/usr/$acl_libdirstem" \ + && test "X$dir" != "X/usr/$acl_libdirstem2" \ + && test "X$dir" != "X/usr/$acl_libdirstem3"; then + rpathdirs="$rpathdirs $dir" + fi + next= ;; + *) next= ;; + esac + fi + done + if test "X$rpathdirs" != "X"; then + if test -n ""$3""; then + dnl libtool is used for linking. Use -R options. + for dir in $rpathdirs; do + $1="${$1}${$1:+ }-R$dir" + done + else + dnl The linker is used for linking directly. + if test -n "$acl_hardcode_libdir_separator"; then + dnl Weird platform: only the last -rpath option counts, the user + dnl must pass all path elements in one option. + alldirs= + for dir in $rpathdirs; do + alldirs="${alldirs}${alldirs:+$acl_hardcode_libdir_separator}$dir" + done + acl_save_libdir="$libdir" + libdir="$alldirs" + eval flag=\"$acl_hardcode_libdir_flag_spec\" + libdir="$acl_save_libdir" + $1="$flag" + else + dnl The -rpath options are cumulative. + for dir in $rpathdirs; do + acl_save_libdir="$libdir" + libdir="$dir" + eval flag=\"$acl_hardcode_libdir_flag_spec\" + libdir="$acl_save_libdir" + $1="${$1}${$1:+ }$flag" + done + fi + fi + fi + fi + fi + AC_SUBST([$1]) +]) diff --git a/m4/lib-prefix.m4 b/m4/lib-prefix.m4 new file mode 100644 index 00000000000..999f712f5ac --- /dev/null +++ b/m4/lib-prefix.m4 @@ -0,0 +1,323 @@ +# lib-prefix.m4 serial 20 +dnl Copyright (C) 2001-2005, 2008-2022 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl From Bruno Haible. + +dnl AC_LIB_PREFIX adds to the CPPFLAGS and LDFLAGS the flags that are needed +dnl to access previously installed libraries. The basic assumption is that +dnl a user will want packages to use other packages he previously installed +dnl with the same --prefix option. +dnl This macro is not needed if only AC_LIB_LINKFLAGS is used to locate +dnl libraries, but is otherwise very convenient. +AC_DEFUN([AC_LIB_PREFIX], +[ + AC_BEFORE([$0], [AC_LIB_LINKFLAGS]) + AC_REQUIRE([AC_PROG_CC]) + AC_REQUIRE([AC_CANONICAL_HOST]) + AC_REQUIRE([AC_LIB_PREPARE_MULTILIB]) + AC_REQUIRE([AC_LIB_PREPARE_PREFIX]) + dnl By default, look in $includedir and $libdir. + use_additional=yes + AC_LIB_WITH_FINAL_PREFIX([ + eval additional_includedir=\"$includedir\" + eval additional_libdir=\"$libdir\" + ]) + AC_ARG_WITH([lib-prefix], +[[ --with-lib-prefix[=DIR] search for libraries in DIR/include and DIR/lib + --without-lib-prefix don't search for libraries in includedir and libdir]], +[ + if test "X$withval" = "Xno"; then + use_additional=no + else + if test "X$withval" = "X"; then + AC_LIB_WITH_FINAL_PREFIX([ + eval additional_includedir=\"$includedir\" + eval additional_libdir=\"$libdir\" + ]) + else + additional_includedir="$withval/include" + additional_libdir="$withval/$acl_libdirstem" + fi + fi +]) + if test $use_additional = yes; then + dnl Potentially add $additional_includedir to $CPPFLAGS. + dnl But don't add it + dnl 1. if it's the standard /usr/include, + dnl 2. if it's already present in $CPPFLAGS, + dnl 3. if it's /usr/local/include and we are using GCC on Linux, + dnl 4. if it doesn't exist as a directory. + if test "X$additional_includedir" != "X/usr/include"; then + haveit= + for x in $CPPFLAGS; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + if test "X$x" = "X-I$additional_includedir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + if test "X$additional_includedir" = "X/usr/local/include"; then + if test -n "$GCC"; then + case $host_os in + linux* | gnu* | k*bsd*-gnu) haveit=yes;; + esac + fi + fi + if test -z "$haveit"; then + if test -d "$additional_includedir"; then + dnl Really add $additional_includedir to $CPPFLAGS. + CPPFLAGS="${CPPFLAGS}${CPPFLAGS:+ }-I$additional_includedir" + fi + fi + fi + fi + dnl Potentially add $additional_libdir to $LDFLAGS. + dnl But don't add it + dnl 1. if it's the standard /usr/lib, + dnl 2. if it's already present in $LDFLAGS, + dnl 3. if it's /usr/local/lib and we are using GCC on Linux, + dnl 4. if it doesn't exist as a directory. + if test "X$additional_libdir" != "X/usr/$acl_libdirstem"; then + haveit= + for x in $LDFLAGS; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + if test "X$x" = "X-L$additional_libdir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + if test "X$additional_libdir" = "X/usr/local/$acl_libdirstem"; then + if test -n "$GCC"; then + case $host_os in + linux*) haveit=yes;; + esac + fi + fi + if test -z "$haveit"; then + if test -d "$additional_libdir"; then + dnl Really add $additional_libdir to $LDFLAGS. + LDFLAGS="${LDFLAGS}${LDFLAGS:+ }-L$additional_libdir" + fi + fi + fi + fi + fi +]) + +dnl AC_LIB_PREPARE_PREFIX creates variables acl_final_prefix, +dnl acl_final_exec_prefix, containing the values to which $prefix and +dnl $exec_prefix will expand at the end of the configure script. +AC_DEFUN([AC_LIB_PREPARE_PREFIX], +[ + dnl Unfortunately, prefix and exec_prefix get only finally determined + dnl at the end of configure. + if test "X$prefix" = "XNONE"; then + acl_final_prefix="$ac_default_prefix" + else + acl_final_prefix="$prefix" + fi + if test "X$exec_prefix" = "XNONE"; then + acl_final_exec_prefix='${prefix}' + else + acl_final_exec_prefix="$exec_prefix" + fi + acl_save_prefix="$prefix" + prefix="$acl_final_prefix" + eval acl_final_exec_prefix=\"$acl_final_exec_prefix\" + prefix="$acl_save_prefix" +]) + +dnl AC_LIB_WITH_FINAL_PREFIX([statement]) evaluates statement, with the +dnl variables prefix and exec_prefix bound to the values they will have +dnl at the end of the configure script. +AC_DEFUN([AC_LIB_WITH_FINAL_PREFIX], +[ + acl_save_prefix="$prefix" + prefix="$acl_final_prefix" + acl_save_exec_prefix="$exec_prefix" + exec_prefix="$acl_final_exec_prefix" + $1 + exec_prefix="$acl_save_exec_prefix" + prefix="$acl_save_prefix" +]) + +dnl AC_LIB_PREPARE_MULTILIB creates +dnl - a function acl_is_expected_elfclass, that tests whether standard input +dn; has a 32-bit or 64-bit ELF header, depending on the host CPU ABI, +dnl - 3 variables acl_libdirstem, acl_libdirstem2, acl_libdirstem3, containing +dnl the basename of the libdir to try in turn, either "lib" or "lib64" or +dnl "lib/64" or "lib32" or "lib/sparcv9" or "lib/amd64" or similar. +AC_DEFUN([AC_LIB_PREPARE_MULTILIB], +[ + dnl There is no formal standard regarding lib, lib32, and lib64. + dnl On most glibc systems, the current practice is that on a system supporting + dnl 32-bit and 64-bit instruction sets or ABIs, 64-bit libraries go under + dnl $prefix/lib64 and 32-bit libraries go under $prefix/lib. However, on + dnl Arch Linux based distributions, it's the opposite: 32-bit libraries go + dnl under $prefix/lib32 and 64-bit libraries go under $prefix/lib. + dnl We determine the compiler's default mode by looking at the compiler's + dnl library search path. If at least one of its elements ends in /lib64 or + dnl points to a directory whose absolute pathname ends in /lib64, we use that + dnl for 64-bit ABIs. Similarly for 32-bit ABIs. Otherwise we use the default, + dnl namely "lib". + dnl On Solaris systems, the current practice is that on a system supporting + dnl 32-bit and 64-bit instruction sets or ABIs, 64-bit libraries go under + dnl $prefix/lib/64 (which is a symlink to either $prefix/lib/sparcv9 or + dnl $prefix/lib/amd64) and 32-bit libraries go under $prefix/lib. + AC_REQUIRE([AC_CANONICAL_HOST]) + AC_REQUIRE([gl_HOST_CPU_C_ABI_32BIT]) + + AC_CACHE_CHECK([for ELF binary format], [gl_cv_elf], + [AC_EGREP_CPP([Extensible Linking Format], + [#if defined __ELF__ || (defined __linux__ && defined __EDG__) + Extensible Linking Format + #endif + ], + [gl_cv_elf=yes], + [gl_cv_elf=no]) + ]) + if test $gl_cv_elf = yes; then + # Extract the ELF class of a file (5th byte) in decimal. + # Cf. https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header + if od -A x < /dev/null >/dev/null 2>/dev/null; then + # Use POSIX od. + func_elfclass () + { + od -A n -t d1 -j 4 -N 1 + } + else + # Use BSD hexdump. + func_elfclass () + { + dd bs=1 count=1 skip=4 2>/dev/null | hexdump -e '1/1 "%3d "' + echo + } + fi + # Use 'expr', not 'test', to compare the values of func_elfclass, because on + # Solaris 11 OpenIndiana and Solaris 11 OmniOS, the result is 001 or 002, + # not 1 or 2. +changequote(,)dnl + case $HOST_CPU_C_ABI_32BIT in + yes) + # 32-bit ABI. + acl_is_expected_elfclass () + { + expr "`func_elfclass | sed -e 's/[ ]//g'`" = 1 > /dev/null + } + ;; + no) + # 64-bit ABI. + acl_is_expected_elfclass () + { + expr "`func_elfclass | sed -e 's/[ ]//g'`" = 2 > /dev/null + } + ;; + *) + # Unknown. + acl_is_expected_elfclass () + { + : + } + ;; + esac +changequote([,])dnl + else + acl_is_expected_elfclass () + { + : + } + fi + + dnl Allow the user to override the result by setting acl_cv_libdirstems. + AC_CACHE_CHECK([for the common suffixes of directories in the library search path], + [acl_cv_libdirstems], + [dnl Try 'lib' first, because that's the default for libdir in GNU, see + dnl . + acl_libdirstem=lib + acl_libdirstem2= + acl_libdirstem3= + case "$host_os" in + solaris*) + dnl See Solaris 10 Software Developer Collection > Solaris 64-bit Developer's Guide > The Development Environment + dnl . + dnl "Portable Makefiles should refer to any library directories using the 64 symbolic link." + dnl But we want to recognize the sparcv9 or amd64 subdirectory also if the + dnl symlink is missing, so we set acl_libdirstem2 too. + if test $HOST_CPU_C_ABI_32BIT = no; then + acl_libdirstem2=lib/64 + case "$host_cpu" in + sparc*) acl_libdirstem3=lib/sparcv9 ;; + i*86 | x86_64) acl_libdirstem3=lib/amd64 ;; + esac + fi + ;; + *) + dnl If $CC generates code for a 32-bit ABI, the libraries are + dnl surely under $prefix/lib or $prefix/lib32, not $prefix/lib64. + dnl Similarly, if $CC generates code for a 64-bit ABI, the libraries + dnl are surely under $prefix/lib or $prefix/lib64, not $prefix/lib32. + dnl Find the compiler's search path. However, non-system compilers + dnl sometimes have odd library search paths. But we can't simply invoke + dnl '/usr/bin/gcc -print-search-dirs' because that would not take into + dnl account the -m32/-m31 or -m64 options from the $CC or $CFLAGS. + searchpath=`(LC_ALL=C $CC $CPPFLAGS $CFLAGS -print-search-dirs) 2>/dev/null \ + | sed -n -e 's,^libraries: ,,p' | sed -e 's,^=,,'` + if test $HOST_CPU_C_ABI_32BIT != no; then + # 32-bit or unknown ABI. + if test -d /usr/lib32; then + acl_libdirstem2=lib32 + fi + fi + if test $HOST_CPU_C_ABI_32BIT != yes; then + # 64-bit or unknown ABI. + if test -d /usr/lib64; then + acl_libdirstem3=lib64 + fi + fi + if test -n "$searchpath"; then + acl_save_IFS="${IFS= }"; IFS=":" + for searchdir in $searchpath; do + if test -d "$searchdir"; then + case "$searchdir" in + */lib32/ | */lib32 ) acl_libdirstem2=lib32 ;; + */lib64/ | */lib64 ) acl_libdirstem3=lib64 ;; + */../ | */.. ) + # Better ignore directories of this form. They are misleading. + ;; + *) searchdir=`cd "$searchdir" && pwd` + case "$searchdir" in + */lib32 ) acl_libdirstem2=lib32 ;; + */lib64 ) acl_libdirstem3=lib64 ;; + esac ;; + esac + fi + done + IFS="$acl_save_IFS" + if test $HOST_CPU_C_ABI_32BIT = yes; then + # 32-bit ABI. + acl_libdirstem3= + fi + if test $HOST_CPU_C_ABI_32BIT = no; then + # 64-bit ABI. + acl_libdirstem2= + fi + fi + ;; + esac + test -n "$acl_libdirstem2" || acl_libdirstem2="$acl_libdirstem" + test -n "$acl_libdirstem3" || acl_libdirstem3="$acl_libdirstem" + acl_cv_libdirstems="$acl_libdirstem,$acl_libdirstem2,$acl_libdirstem3" + ]) + dnl Decompose acl_cv_libdirstems into acl_libdirstem, acl_libdirstem2, and + dnl acl_libdirstem3. +changequote(,)dnl + acl_libdirstem=`echo "$acl_cv_libdirstems" | sed -e 's/,.*//'` + acl_libdirstem2=`echo "$acl_cv_libdirstems" | sed -e 's/^[^,]*,//' -e 's/,.*//'` + acl_libdirstem3=`echo "$acl_cv_libdirstems" | sed -e 's/^[^,]*,[^,]*,//' -e 's/,.*//'` +changequote([,])dnl +]) diff --git a/pkgs/sage-conf/VERSION.txt b/pkgs/sage-conf/VERSION.txt index 41184bff2fe..aadd65deb15 100644 --- a/pkgs/sage-conf/VERSION.txt +++ b/pkgs/sage-conf/VERSION.txt @@ -1 +1 @@ -9.7.beta8 +9.8.beta0 diff --git a/pkgs/sage-conf_pypi/VERSION.txt b/pkgs/sage-conf_pypi/VERSION.txt index 41184bff2fe..aadd65deb15 100644 --- a/pkgs/sage-conf_pypi/VERSION.txt +++ b/pkgs/sage-conf_pypi/VERSION.txt @@ -1 +1 @@ -9.7.beta8 +9.8.beta0 diff --git a/pkgs/sage-docbuild/VERSION.txt b/pkgs/sage-docbuild/VERSION.txt index 41184bff2fe..aadd65deb15 100644 --- a/pkgs/sage-docbuild/VERSION.txt +++ b/pkgs/sage-docbuild/VERSION.txt @@ -1 +1 @@ -9.7.beta8 +9.8.beta0 diff --git a/pkgs/sage-setup/VERSION.txt b/pkgs/sage-setup/VERSION.txt index 41184bff2fe..aadd65deb15 100644 --- a/pkgs/sage-setup/VERSION.txt +++ b/pkgs/sage-setup/VERSION.txt @@ -1 +1 @@ -9.7.beta8 +9.8.beta0 diff --git a/pkgs/sage-sws2rst/VERSION.txt b/pkgs/sage-sws2rst/VERSION.txt index 41184bff2fe..aadd65deb15 100644 --- a/pkgs/sage-sws2rst/VERSION.txt +++ b/pkgs/sage-sws2rst/VERSION.txt @@ -1 +1 @@ -9.7.beta8 +9.8.beta0 diff --git a/pkgs/sagemath-categories/VERSION.txt b/pkgs/sagemath-categories/VERSION.txt index 41184bff2fe..aadd65deb15 100644 --- a/pkgs/sagemath-categories/VERSION.txt +++ b/pkgs/sagemath-categories/VERSION.txt @@ -1 +1 @@ -9.7.beta8 +9.8.beta0 diff --git a/pkgs/sagemath-environment/VERSION.txt b/pkgs/sagemath-environment/VERSION.txt index 41184bff2fe..aadd65deb15 100644 --- a/pkgs/sagemath-environment/VERSION.txt +++ b/pkgs/sagemath-environment/VERSION.txt @@ -1 +1 @@ -9.7.beta8 +9.8.beta0 diff --git a/pkgs/sagemath-objects/VERSION.txt b/pkgs/sagemath-objects/VERSION.txt index 41184bff2fe..aadd65deb15 100644 --- a/pkgs/sagemath-objects/VERSION.txt +++ b/pkgs/sagemath-objects/VERSION.txt @@ -1 +1 @@ -9.7.beta8 +9.8.beta0 diff --git a/pkgs/sagemath-repl/VERSION.txt b/pkgs/sagemath-repl/VERSION.txt index 41184bff2fe..aadd65deb15 100644 --- a/pkgs/sagemath-repl/VERSION.txt +++ b/pkgs/sagemath-repl/VERSION.txt @@ -1 +1 @@ -9.7.beta8 +9.8.beta0 diff --git a/src/VERSION.txt b/src/VERSION.txt index 41184bff2fe..aadd65deb15 100644 --- a/src/VERSION.txt +++ b/src/VERSION.txt @@ -1 +1 @@ -9.7.beta8 +9.8.beta0 diff --git a/src/bin/sage-env b/src/bin/sage-env index 6459c5aca78..7d786f292c3 100644 --- a/src/bin/sage-env +++ b/src/bin/sage-env @@ -115,9 +115,8 @@ fi # Don't execute the commands more than once for the same version of # sage-env... for the same combination of SAGE_LOCAL and SAGE_VENV. -# "5" indicates the version of the format of the value of SAGE_ENV_VERSION. -# The current format was introduced in #32745. -SAGE_ENV_VERSION="5:$SAGE_LOCAL:$SAGE_VENV" +# "6" indicates the version of the format of the value of SAGE_ENV_VERSION. +SAGE_ENV_VERSION="6:$SAGE_LOCAL:$SAGE_VENV:$SAGE_SRC" if [ "$SAGE_ENV_SOURCED" = "$SAGE_ENV_VERSION" ]; then # Already sourced, nothing to do. return 0 diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index 29ee897d655..802ca805250 100644 --- a/src/bin/sage-version.sh +++ b/src/bin/sage-version.sh @@ -4,6 +4,6 @@ # which stops "setup.py develop" from rewriting it as a Python file. : # This file is auto-generated by the sage-update-version script, do not edit! -SAGE_VERSION='9.7.beta8' -SAGE_RELEASE_DATE='2022-08-07' -SAGE_VERSION_BANNER='SageMath version 9.7.beta8, Release Date: 2022-08-07' +SAGE_VERSION='9.8.beta0' +SAGE_RELEASE_DATE='2022-09-25' +SAGE_VERSION_BANNER='SageMath version 9.8.beta0, Release Date: 2022-09-25' diff --git a/src/doc/bootstrap b/src/doc/bootstrap index 44c94627a53..6b44bc828d8 100755 --- a/src/doc/bootstrap +++ b/src/doc/bootstrap @@ -26,13 +26,15 @@ mkdir -p "$OUTPUT_DIR" shopt -s extglob RECOMMENDED_SPKG_PATTERN="@(_recommended$(for a in $(head -n 1 build/pkgs/_recommended/dependencies); do echo -n "|"$a; done))" +DEVELOP_SPKG_PATTERN="@(_develop$(for a in $(head -n 1 build/pkgs/_develop/dependencies); do echo -n "|"$a; done))" -for SYSTEM in arch debian fedora cygwin homebrew; do +for SYSTEM in arch debian fedora cygwin homebrew opensuse; do SYSTEM_PACKAGES= OPTIONAL_SYSTEM_PACKAGES= SAGELIB_SYSTEM_PACKAGES= SAGELIB_OPTIONAL_SYSTEM_PACKAGES= RECOMMENDED_SYSTEM_PACKAGES= + DEVELOP_SYSTEM_PACKAGES= for PKG_BASE in $(sage-package list --has-file distros/$SYSTEM.txt); do PKG_SCRIPTS=build/pkgs/$PKG_BASE SYSTEM_PACKAGES_FILE=$PKG_SCRIPTS/distros/$SYSTEM.txt @@ -44,6 +46,9 @@ for SYSTEM in arch debian fedora cygwin homebrew; do *:standard) SYSTEM_PACKAGES+=" $PKG_SYSTEM_PACKAGES" ;; + $DEVELOP_SPKG_PATTERN:*) + DEVELOP_SYSTEM_PACKAGES+=" $PKG_SYSTEM_PACKAGES" + ;; $RECOMMENDED_SPKG_PATTERN:*) RECOMMENDED_SYSTEM_PACKAGES+=" $PKG_SYSTEM_PACKAGES" ;; @@ -52,8 +57,11 @@ for SYSTEM in arch debian fedora cygwin homebrew; do ;; esac else - case "$PKG_TYPE" in - standard) + case "$PKG_BASE:$PKG_TYPE" in + $DEVELOP_SPKG_PATTERN:*) + DEVELOP_SYSTEM_PACKAGES+=" $PKG_SYSTEM_PACKAGES" + ;; + *:standard) SAGELIB_SYSTEM_PACKAGES+=" $PKG_SYSTEM_PACKAGES" ;; *) @@ -70,6 +78,7 @@ for SYSTEM in arch debian fedora cygwin homebrew; do echo "$(sage-print-system-package-command $SYSTEM --prompt --sudo install $(echo $(echo $SYSTEM_PACKAGES | xargs -n 1 echo | sort | uniq)))" > "$OUTPUT_DIR"/$SYSTEM.txt echo "$(sage-print-system-package-command $SYSTEM --prompt --sudo install $(echo $(echo $OPTIONAL_SYSTEM_PACKAGES | xargs -n 1 echo | sort | uniq)))" > "$OUTPUT_DIR"/$SYSTEM-optional.txt echo "$(sage-print-system-package-command $SYSTEM --prompt --sudo install $(echo $(echo $RECOMMENDED_SYSTEM_PACKAGES | xargs -n 1 echo | sort | uniq)))" > "$OUTPUT_DIR"/$SYSTEM-recommended.txt + echo "$(sage-print-system-package-command $SYSTEM --prompt --sudo install $(echo $(echo $DEVELOP_SYSTEM_PACKAGES | xargs -n 1 echo | sort | uniq)))" > "$OUTPUT_DIR"/$SYSTEM-develop.txt done OUTPUT_DIR="src/doc/en/reference/spkg" diff --git a/src/doc/common/static/custom-furo.css b/src/doc/common/static/custom-furo.css index d9dfb2583ab..343fbb908cf 100644 --- a/src/doc/common/static/custom-furo.css +++ b/src/doc/common/static/custom-furo.css @@ -2,3 +2,12 @@ span.sidebar-brand-text { font-size: small; text-align: center; } + +div.highlight { + background: #F4F4F4; +} + +body[data-theme="dark"] div.highlight { + background: #383838; +} + diff --git a/src/doc/de/tutorial/introduction.rst b/src/doc/de/tutorial/introduction.rst index ea3bf00cf34..bb80efc7cec 100644 --- a/src/doc/de/tutorial/introduction.rst +++ b/src/doc/de/tutorial/introduction.rst @@ -98,9 +98,9 @@ Hier geben wir nur ein paar Kommentare ab. einzige Datei in ein Verzeichnis kopieren, welches TeX durchsucht. Die Dokumentation für SageTeX befindet sich in - ``$SAGE_ROOT/local/share/texmf/tex/latex/sagetex/``, wobei + ``$SAGE_ROOT/venv/share/texmf/tex/latex/sagetex/``, wobei "``$SAGE_ROOT``" auf das Verzeichnis zeigt, in welches Sie Sage - installiert haben, zum Beispiel ``/opt/sage-4.2.1``. + installiert haben, zum Beispiel ``/opt/sage-9.6``. Wie man Sage benutzen kann ========================== diff --git a/src/doc/de/tutorial/sagetex.rst b/src/doc/de/tutorial/sagetex.rst index 8f58e812a78..37780eb6cf2 100644 --- a/src/doc/de/tutorial/sagetex.rst +++ b/src/doc/de/tutorial/sagetex.rst @@ -15,7 +15,7 @@ Tutorial und den Abschnitt "Make SageTeX known to TeX" des `Sage installation gu Installationsanleitung führen) um weitere Informationen zu erhalten. Hier stellen wir ein sehr kurzes Beispiel vor wie man SageTeX nutzt. -Die komplette Dokumentation finden Sie unter ``SAGE_ROOT/local/share/texmf/tex/latex/sagetex``, +Die komplette Dokumentation finden Sie unter ``SAGE_ROOT/venv/share/texmf/tex/latex/sagetex``, wobei ``SAGE_ROOT`` das Installationsverzeichnis von Sage ist. Dieses Verzeichnis enthält die Dokumentation, eine Beispieldatei und einige nützliche Python Skripte. @@ -103,4 +103,4 @@ an den Sage Befehlen in Ihrem Dokument vorgenommen haben. Es gibt noch viel mehr über SageTeX zu sagen, aber da sowohl Sage alsauch LaTeX komplexe und mächtige Werkzeuge sind, sollten Sie die Dokumentation -über SageTeX in ``SAGE_ROOT/local/share/texmf/tex/latex/sagetex`` lesen. +über SageTeX in ``SAGE_ROOT/venv/share/texmf/tex/latex/sagetex`` lesen. diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index 5f27ccd05c2..4f208b8e357 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -179,7 +179,6 @@ the section ``options.package_data`` of the file file of another distribution). - Learn by copy/paste =================== @@ -685,6 +684,48 @@ You are strongly encouraged to: of Sage that break something else might not go seen until much later when someone uses the system, which is unacceptable. +Fine points on styles +--------------------- + +A Sage developer, in writing code and docstrings, should follow the styles +suggested in this manual, except special cases with good reasons. However, there +are some details where we as a community did not reach to an agreement on +the official style. These are + +- one space:: + + This is the first sentence. This is the second sentence. + + vs two spaces:: + + This is the first sentence. This is the second sentence. + + between sentences. + +- tight list:: + + - first item + - second item + - third item + + vs spaced list:: + + - first item + + - second item + + - third item + +There are different opinions on each of these, and in reality, we find +instances in each style in our codebase. Then what should we do? Do we decide +on one style by voting? There are different opinions even on what to do! + +We can at least do this to prevent any dispute about these style conflicts: + +- Acknowledge different authors may have different preferences on these. + +- Respect the style choice of the author who first wrote the code or the docstrings. + Private functions ^^^^^^^^^^^^^^^^^ diff --git a/src/doc/en/developer/coding_in_other.rst b/src/doc/en/developer/coding_in_other.rst index 6693aed5b8b..e7891af92d7 100644 --- a/src/doc/en/developer/coding_in_other.rst +++ b/src/doc/en/developer/coding_in_other.rst @@ -106,8 +106,7 @@ convert output from PARI to Sage objects: def frobenius(self, flag=0, var='x'): """ - Return the Frobenius form (rational canonical form) of this - matrix. + Return the Frobenius form (rational canonical form) of this matrix. INPUT: @@ -125,7 +124,7 @@ convert output from PARI to Sage objects: - ``var`` -- a string (default: 'x') - ALGORITHM: uses PARI's matfrobenius() + ALGORITHM: uses PARI's :pari:`matfrobenius` EXAMPLES:: @@ -143,15 +142,15 @@ convert output from PARI to Sage objects: raise ArithmeticError("frobenius matrix of non-square matrix not defined.") v = self.__pari__().matfrobenius(flag) - if flag==0: + if flag == 0: return self.matrix_space()(v.python()) - elif flag==1: + elif flag == 1: r = PolynomialRing(self.base_ring(), names=var) retr = [] for f in v: retr.append(eval(str(f).replace("^","**"), {'x':r.gen()}, r.gens_dict())) return retr - elif flag==2: + elif flag == 2: F = matrix_space.MatrixSpace(QQ, self.nrows())(v[0].python()) B = matrix_space.MatrixSpace(QQ, self.nrows())(v[1].python()) return F, B @@ -212,11 +211,13 @@ object. Return the Cartan matrix of given Chevalley type and rank. INPUT: - type -- a Chevalley letter name, as a string, for - a family type of simple Lie algebras - rank -- an integer (legal for that type). - EXAMPLES: + - type -- a Chevalley letter name, as a string, for + a family type of simple Lie algebras + - rank -- an integer (legal for that type). + + EXAMPLES:: + sage: cartan_matrix("A",5) [ 2 -1 0 0 0] [-1 2 -1 0 0] @@ -227,12 +228,11 @@ object. [ 2 -1] [-3 2] """ - - L = gap.SimpleLieAlgebra('"%s"'%type, rank, 'Rationals') + L = gap.SimpleLieAlgebra('"%s"' % type, rank, 'Rationals') R = L.RootSystem() sM = R.CartanMatrix() ans = eval(str(sM)) - MS = MatrixSpace(QQ, rank) + MS = MatrixSpace(QQ, rank) return MS(ans) The output ``ans`` is a Python list. The last two lines convert that @@ -460,50 +460,51 @@ just that. .. CODE-BLOCK:: python - def points_parser(string_points,F): + def points_parser(string_points, F): """ This function will parse a string of points of X over a finite field F returned by Singular's NSplaces command into a Python list of points with entries from F. - EXAMPLES: + EXAMPLES:: + sage: F = GF(5) sage: points_parser(L,F) ((0, 1, 0), (3, 4, 1), (0, 0, 1), (2, 3, 1), (3, 1, 1), (2, 2, 1)) """ - Pts=[] - n=len(L) - #start block to compute a pt - L1=L - while len(L1)>32: - idx=L1.index(" ") - pt=[] - ## start block1 for compute pt - idx=L1.index(" ") - idx2=L1[idx:].index("\n") - L2=L1[idx:idx+idx2] + Pts = [] + n = len(L) + # start block to compute a pt + L1 = L + while len(L1) > 32: + idx =L1.index(" ") + pt = [] + # start block1 for compute pt + idx = L1.index(" ") + idx2 = L1[idx:].index("\n") + L2 = L1[idx:idx+idx2] pt.append(F(eval(L2))) # end block1 to compute pt - L1=L1[idx+8:] # repeat block 2 more times - ## start block2 for compute pt - idx=L1.index(" ") - idx2=L1[idx:].index("\n") - L2=L1[idx:idx+idx2] + L1 = L1[idx+8:] # repeat block 2 more times + # start block2 for compute pt + idx = L1.index(" ") + idx2 = L1[idx:].index("\n") + L2 = L1[idx:idx+idx2] pt.append(F(eval(L2))) # end block2 to compute pt L1=L1[idx+8:] # repeat block 1 more time - ## start block3 for compute pt + # start block3 for compute pt idx=L1.index(" ") if "\n" in L1[idx:]: - idx2=L1[idx:].index("\n") + idx2 = L1[idx:].index("\n") else: - idx2=len(L1[idx:]) - L2=L1[idx:idx+idx2] + idx2 = len(L1[idx:]) + L2 = L1[idx:idx+idx2] pt.append(F(eval(L2))) # end block3 to compute pt - #end block to compute a pt + # end block to compute a pt Pts.append(tuple(pt)) # repeat until no more pts - L1=L1[idx+8:] # repeat block 2 more times + L1 = L1[idx+8:] # repeat block 2 more times return tuple(Pts) Now it is an easy matter to put these ingredients together into a Sage @@ -519,20 +520,23 @@ ourselves to points of degree one. .. CODE-BLOCK:: python - def places_on_curve(f,F): + def places_on_curve(f, F): """ INPUT: - f -- element of F[x,y], defining X: f(x,y)=0 - F -- a finite field of *prime order* + + - f -- element of F[x,y], defining X: f(x,y)=0 + - F -- a finite field of *prime order* OUTPUT: - integer -- the number of places in X of degree d=1 over F - EXAMPLES: - sage: F=GF(5) - sage: R=PolynomialRing(F,2,names=["x","y"]) - sage: x,y=R.gens() - sage: f=y^2-x^9-x + integer -- the number of places in X of degree d=1 over F + + EXAMPLES:: + + sage: F = GF(5) + sage: R = PolynomialRing(F,2,names=["x","y"]) + sage: x,y = R.gens() + sage: f = y^2-x^9-x sage: places_on_curve(f,F) ((0, 1, 0), (3, 4, 1), (0, 0, 1), (2, 3, 1), (3, 1, 1), (2, 2, 1)) """ @@ -600,7 +604,7 @@ function is not required: .. CODE-BLOCK:: python - def places_on_curve(f,F): + def places_on_curve(f, F): p = F.characteristic() if F.degree() > 1: raise NotImplementedError @@ -677,7 +681,7 @@ This uses the class ``Expect`` to set up the Octave interface: """ Set the variable var to the given value. """ - cmd = '%s=%s;'%(var,value) + cmd = '%s=%s;' % (var,value) out = self.eval(cmd) if out.find("error") != -1: raise TypeError("Error executing code in Octave\nCODE:\n\t%s\nOctave ERROR:\n\t%s"%(cmd, out)) @@ -686,7 +690,7 @@ This uses the class ``Expect`` to set up the Octave interface: """ Get the value of the variable var. """ - s = self.eval('%s'%var) + s = self.eval('%s' % var) i = s.find('=') return s[i+1:] @@ -729,16 +733,16 @@ dumps the user into an Octave interactive shell: raise ValueError("dimensions of A and b must be compatible") from sage.matrix.all import MatrixSpace from sage.rings.all import QQ - MS = MatrixSpace(QQ,m,1) - b = MS(list(b)) # converted b to a "column vector" + MS = MatrixSpace(QQ, m, 1) + b = MS(list(b)) # converted b to a "column vector" sA = self.sage2octave_matrix_string(A) sb = self.sage2octave_matrix_string(b) self.eval("a = " + sA ) self.eval("b = " + sb ) soln = octave.eval("c = a \\ b") - soln = soln.replace("\n\n ","[") - soln = soln.replace("\n\n","]") - soln = soln.replace("\n",",") + soln = soln.replace("\n\n ", "[") + soln = soln.replace("\n\n", "]") + soln = soln.replace("\n", ",") sol = soln[3:] return eval(sol) diff --git a/src/doc/en/developer/doctesting.rst b/src/doc/en/developer/doctesting.rst index f00e22042f4..9619460c30f 100644 --- a/src/doc/en/developer/doctesting.rst +++ b/src/doc/en/developer/doctesting.rst @@ -1070,7 +1070,7 @@ appear in real time use the ``--verbose`` flag). To have doctests run under the control of gdb, use the ``--gdb`` flag:: [roed@sage sage-6.0]$ sage -t --gdb src/sage/schemes/elliptic_curves/constructor.py - gdb -x /home/roed/sage-6.0.b5/local/bin/sage-gdb-commands --args python /home/roed/sage-6.0.b5/local/bin/sage-runtests --serial --nthreads 1 --timeout 1048576 --optional sagemath_doc_html,sage --stats_path /home/roed/.sage/timings2.json src/sage/schemes/elliptic_curves/constructor.py + exec gdb --eval-commands="run" --args /home/roed/sage-9.7/local/var/lib/sage/venv-python3.9/bin/python3 sage-runtests --serial --timeout=0 --stats_path=/home/roed/.sage/timings2.json --optional=pip,sage,sage_spkg src/sage/schemes/elliptic_curves/constructor.py GNU gdb 6.8-debian Copyright (C) 2008 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later diff --git a/src/doc/en/developer/packaging.rst b/src/doc/en/developer/packaging.rst index ec933c118bc..33ca56415f8 100644 --- a/src/doc/en/developer/packaging.rst +++ b/src/doc/en/developer/packaging.rst @@ -79,7 +79,21 @@ the following source types: (see :ref:`section-spkg-install`); - Sage records the version number of the package installed using a file in - ``$SAGE_LOCAL/var/lib/sage/installed/`` and will re-run the installation + ``$SAGE_LOCAL/var/lib/sage/installed/`` and will rerun the installation + if ``package-version.txt`` changes. + +#. A ``wheel`` package: + + - comes from the wheel file named in the required file ``checksums.ini`` + and hosted on the Sage mirrors; + + - per policy, only platform-independent wheels are allowed, i.e., + ``*-none-any.whl`` files; + + - its version number is defined by the required file ``package-version.txt``; + + - Sage records the version number of the package installed using a file in + ``$SAGE_LOCAL/var/lib/sage/installed/`` and will rerun the installation if ``package-version.txt`` changes. #. A ``pip`` package: @@ -107,7 +121,7 @@ the following source types: (see :ref:`section-spkg-install`); - Sage records the version number of the package installed using a file in - ``$SAGE_LOCAL/var/lib/sage/installed/`` and will re-run the installation + ``$SAGE_LOCAL/var/lib/sage/installed/`` and will rerun the installation if ``package-version.txt`` changes. #. A ``dummy`` package: @@ -119,7 +133,7 @@ the following source types: To summarize: the package source type is determined as follows: if there is a file ``requirements.txt``, it is a ``pip`` package. If not, -then if there is a ``checksums.ini`` file, it is ``normal``. +then if there is a ``checksums.ini`` file, it is ``normal`` or ``wheel``. Otherwise, if it has an ``spkg-install`` script, it is a ``script`` package, and if it does not, then it is a ``dummy`` package. @@ -584,7 +598,7 @@ For example, the ``scipy`` ``spkg-check.in`` file contains the line exec python3 spkg-check.py -All normal Python packages must have a file ``install-requires.txt``. +All normal Python packages and all wheel packages must have a file ``install-requires.txt``. If a Python package is available on PyPI, this file must contain the name of the package as it is known to PyPI. Optionally, ``install-requires.txt`` can encode version constraints (such as lower @@ -778,7 +792,7 @@ Where packages are installed The Sage distribution has the notion of several installation trees. - ``$SAGE_VENV`` is the default installation tree for all Python packages, i.e., - normal packages with an ``install-requires.txt`` and pip packages + normal packages with an ``install-requires.txt``, wheel packages, and pip packages with a ``requirements.txt``. - ``$SAGE_LOCAL`` is the default installation tree for all non-Python packages. @@ -1199,6 +1213,9 @@ must meet the following requirements: - **Build Support**. The code must build on all the fully supported platforms (Linux, macOS, Cygwin); see :ref:`chapter-portability_testing`. + It must be installed either from source as a normal package, + or as a Python (platform-independent) wheel package, see + :ref:`section-package-source-types`. - **Quality**. The code should be "better" than any other available code (that passes the two above criteria), and the authors need to diff --git a/src/doc/en/developer/portability_testing.rst b/src/doc/en/developer/portability_testing.rst index 9a126126d74..54facbfd1ce 100644 --- a/src/doc/en/developer/portability_testing.rst +++ b/src/doc/en/developer/portability_testing.rst @@ -70,7 +70,7 @@ example, to run the current stable (LTS) version of Ubuntu interactively, you can use the shell command:: [mkoeppe@sage sage]$ docker run -it ubuntu:latest - root@9f3398da43c2:/# + root@9f3398da43c2:/# Here ``ubuntu`` is referred to as the "image (name)" and ``latest`` as the "tag". Other releases of Ubuntu are available under different @@ -118,9 +118,9 @@ this time let's mount the current directory into it:: root@39d693b2a75d:/# cd sage root@39d693b2a75d:/sage# ls COPYING.txt ... Makefile ... config configure configure.ac ... src tox.ini - + Typical Docker images provide minimal installations of packages only:: - + root@39d693b2a75d:/sage# command -v python root@39d693b2a75d:/sage# command -v gcc root@39d693b2a75d:/sage# @@ -128,7 +128,7 @@ Typical Docker images provide minimal installations of packages only:: As you can see above, the image ``ubuntu:latest`` has neither a Python nor a GCC installed, which are among the build prerequisites of Sage. We need to install them using the distribution's package manager first. - + Sage facilitates testing various distributions on Docker as follows. Discovering the system's package system @@ -136,7 +136,7 @@ Discovering the system's package system :: - root@39d693b2a75d:/sage# build/bin/sage-guess-package-system + root@39d693b2a75d:/sage# build/bin/sage-guess-package-system debian Let's install gcc, hoping that the Ubuntu package providing it is @@ -191,7 +191,7 @@ on our container to install the necessary build prerequisites:: root@39d693b2a75d:/sage# apt-get install binutils make m4 perl python3 tar bc gcc g++ ca-certificates Reading package lists... Done - Building dependency tree + Building dependency tree Reading state information... Done tar is already the newest version (1.29b-2ubuntu0.1). The following additional packages will be installed: @@ -204,7 +204,7 @@ automatically generated from the database of package names.) Now we can start the build:: - root@39d693b2a75d:/sage# ./configure + root@39d693b2a75d:/sage# ./configure checking for a BSD-compatible install... /usr/bin/install -c checking for root user... yes configure: error: You cannot build Sage as root, switch to an unprivileged user. (If building in a container, use --enable-build-as-root.) @@ -236,7 +236,7 @@ packages. For example:: root@39d693b2a75d:/sage# ls build/pkgs/arb/distros/ arch.txt conda.txt debian.txt gentoo.txt - root@39d693b2a75d:/sage# cat build/pkgs/arb/distros/debian.txt + root@39d693b2a75d:/sage# cat build/pkgs/arb/distros/debian.txt libflint-arb-dev Note that these package equivalencies are based on a current stable or @@ -258,7 +258,7 @@ Let us install a subset of these packages:: Setting up zlib1g-dev:amd64 (1:1.2.11.dfsg-0ubuntu2) ... root@39d693b2a75d:/sage# - + Committing a container to disk ------------------------------ @@ -324,7 +324,7 @@ The ``Dockerfile`` instructs the command ``docker build`` to build a new Docker image. Let us take a quick look at the generated file; this is slightly simplified:: - [mkoeppe@sage sage]$ cat Dockerfile + [mkoeppe@sage sage]$ cat Dockerfile # Automatically generated by SAGE_ROOT/build/bin/write-dockerfile.sh # the :comments: separate the generated file into sections # to simplify writing scripts that customize this file @@ -419,7 +419,7 @@ We can now start a container using the image id shown in the last step:: -rw-r--r-- 1 root root 6025 Mar 26 22:27 ratpoints-2.1.3.p5.log root@fab59e09a641:/sage# ls -l local/lib/*rat* -rw-r--r-- 1 root root 177256 Mar 26 22:27 local/lib/libratpoints.a - + You can customize the image build process further by editing the ``Dockerfile``. For example, by default, the generated ``Dockerfile`` configures, builds, and tests Sage. By deleting or commenting out the @@ -545,13 +545,13 @@ create an image from the container:: Note: SAGE_ROOT=/sage (sage-buildsh) root@2d9ac65f4572:surf-1.0.6-gcc6$ ls /usr/lib/libfl* /usr/lib/libflint-2.5.2.so /usr/lib/libflint-2.5.2.so.13.5.2 /usr/lib/libflint.a /usr/lib/libflint.so - (sage-buildsh) root@2d9ac65f4572:surf-1.0.6-gcc6$ apt-get update && apt-get install apt-file + (sage-buildsh) root@2d9ac65f4572:surf-1.0.6-gcc6$ apt-get update && apt-get install apt-file (sage-buildsh) root@2d9ac65f4572:surf-1.0.6-gcc6$ apt-file update (sage-buildsh) root@2d9ac65f4572:surf-1.0.6-gcc6$ apt-file search "/usr/lib/libfl.a" flex-old: /usr/lib/libfl.a freebsd-buildutils: /usr/lib/libfl.a (sage-buildsh) root@2d9ac65f4572:surf-1.0.6-gcc6$ apt-get install flex-old - (sage-buildsh) root@2d9ac65f4572:surf-1.0.6-gcc6$ ./spkg-install + (sage-buildsh) root@2d9ac65f4572:surf-1.0.6-gcc6$ ./spkg-install checking for a BSD-compatible install... /usr/bin/install -c checking whether build environment is sane... yes ... @@ -564,7 +564,7 @@ create an image from the container:: [mkoeppe@sage sage]$ A standard case of bitrot. - + Automatic Docker-based build testing using tox ---------------------------------------------- @@ -633,11 +633,11 @@ To run an environment:: [mkoeppe@sage sage]$ tox -e docker-slackware-14.2-minimal [mkoeppe@sage sage]$ tox -e docker-ubuntu-bionic-standard - + Arbitrary extra arguments to ``docker build`` can be supplied through the environment variable ``EXTRA_DOCKER_BUILD_ARGS``. For example, for a non-silent build (``make V=1``), use:: - + [mkoeppe@sage sage]$ EXTRA_DOCKER_BUILD_ARGS="--build-arg USE_MAKEFLAGS=\"V=1\"" \ tox -e docker-ubuntu-bionic-standard @@ -867,7 +867,7 @@ an isolated copy of Homebrew with all prerequisites for bootstrapping:: ... local-homebrew-macos-minimal: commands succeeded congratulations :) - + The tox environment uses the subdirectory ``homebrew`` of the environment directory ``.tox/local-homebrew-macos-minimal`` as the Homebrew prefix. This installation does not interact in any way with @@ -945,19 +945,24 @@ options:: [mkoeppe@sage worktree-local]$ SKIP_SYSTEM_PKG_INSTALL=yes SKIP_BOOTSTRAP=1 SKIP_CONFIGURE=1 tox -e local-homebrew-macos-minimal -- bash -Automatic parallel tox runs on GitHub Actions ---------------------------------------------- +Automatic testing on multiple platforms on GitHub Actions +========================================================= The Sage source tree includes a default configuration for GitHub -Actions that runs tox on a multitude of platforms on every pull -request and on every push of a tag (but not of a branch) to a -repository for which GitHub Actions are enabled. +Actions that runs our portability tests with tox on a multitude of +platforms on every pull request and on every push of a tag (but not of +a branch) to a repository for which GitHub Actions are enabled. -This is defined in the file ``$SAGE_ROOT/.github/workflows/tox.yml``. +In particular, it automatically runs on our main repository on every +release tag. + +This is defined in the files `$SAGE_ROOT/.github/workflows/tox*.yml +`_. An additional GitHub Actions workflow for testing on Cygwin, not based -on tox, is defined in the file -``$SAGE_ROOT/.github/workflows/ci-cygwin.yml``. +on tox, is defined in the files +`$SAGE_ROOT/.github/workflows/ci-cygwin*.yml +`_. GitHub Actions runs these build jobs on 2-core machines with 7 GB of RAM memory and 14 GB of SSD disk space, cf. @@ -980,8 +985,8 @@ system configurations. For more information, see the `GitHub documentation `_. -Alternatively, you can create and push a custom tag in order to trigger a run of tests as follows. -Let's assume that ``github`` is the name of +Alternatively, you can create and push a custom tag in order to trigger a run of tests as follows. +Let's assume that ``my-github`` is the name of the remote corresponding to your GitHub fork of the Sage repository:: $ git remote -v | grep /my-github @@ -1079,3 +1084,210 @@ Now you can pull the image and run it:: $ docker pull ghcr.io/YOUR-GITHUB-USERNAME/sage/sage-docker-fedora-31-standard-configured:f4bd671 $ docker run -it ghcr.io/YOUR-GITHUB-USERNAME/sage/sage-docker-fedora-31-standard-configured:f4bd671 bash + + +Using our pre-built Docker images published on ghcr.io +====================================================== + +Our portability CI on GitHub Actions builds `Docker images +`_ +for all tested Linux platforms (and system package configurations) and +makes them available on `GitHub Packages +`_ (ghcr.io). + +This makes it easy for developers to debug problems that showed up in +the build logs for a given platform. + +The image version corresponding to the latest development release +receives the additional Docker tag ``dev``, see for example the Docker +image for the platform `ubuntu-focal-standard +`_. Thus, +for example, the following command will work:: + + $ docker run -it ghcr.io/sagemath/sage/sage-docker-ubuntu-focal-standard-with-targets-optional:dev bash + Unable to find image 'ghcr.io/sagemath/sage/sage-docker-ubuntu-focal-standard-with-targets-optional:dev' locally + dev: Pulling from sagemath/sage/sage-docker-ubuntu-focal-standard-with-targets-optional + d5fd17ec1767: Already exists + 67586203f0c7: Pull complete + b63c529f4777: Pull complete + ... + 159775d1a3d2: Pull complete + Digest: sha256:e6ba5e12f59c6c4668692ef4cfe4ae5f242556482664fb347bf260f32bf8e698 + Status: Downloaded newer image for ghcr.io/sagemath/sage/sage-docker-ubuntu-focal-standard-with-targets-optional:dev + root@8055a7ba0607:/sage# ./sage + ┌────────────────────────────────────────────────────────────────────┐ + │ SageMath version 9.6, Release Date: 2022-05-15 │ + │ Using Python 3.8.10. Type "help()" for help. │ + └────────────────────────────────────────────────────────────────────┘ + sage: + +Images whose names end with the suffix ``-with-targets-optional`` are +the results of full builds and a run of ``make ptest``. They also +contain a copy of the source tree and the full logs of the build and +test. + +Also `smaller images corresponding to earlier build stages +`_ +are available: + + * ``-with-system-packages`` provides a system installation with + system packages installed, no source tree, + + * ``-configured`` contains a partial source tree + (:envvar:`SAGE_ROOT`) and has completed the bootstrapping phase and + the run of the ``configure`` script, + + * ``-with-targets-pre`` contains the full source tree and a full + installation of all non-Python packages (:envvar:`SAGE_LOCAL`), + + * ``-with-targets`` contains the full source tree and a full + installation of Sage, including the HTML documentation, but ``make + ptest`` has not been run yet. + + +Using our pre-built Docker images for development in VS Code +============================================================ + +`VS Code `_ is very +convenient for developing with Docker containers thanks to the `Visual +Studio Code Remote - Containers +`_ extension. + +If the extension is not already installed, then in VS Code, click the +"Extension" icon on the left (or press :kbd:`Ctrl` + :kbd:`Shift` + :kbd:`X`; +on macOS, :kbd:`Command` + :kbd:`Shift` + :kbd:`X`) to open a list of +extensions. Search for "Remote - Containers" and install it. + +The extension needs a ``devcontainer.json`` configuration file to work. Sage +provides sample ``devcontainer.json`` configuration files +`$SAGE_ROOT/.devcontainer/*/devcontainer.json +`_ for this +purpose. + +To get started, symlink (or copy) one of the sample files to +``$SAGE_ROOT/.devcontainer/devcontainer.json``. For example, choose +`$SAGE_ROOT/.devcontainer/portability-ubuntu-jammy-standard/devcontainer.json +`_, which uses the Docker image based on ``ubuntu-jammy-standard``, +the most recent +development version of Sage (``dev`` tag), and a full installation of +the Sage distribution (``with-targets``). + +In macOS for example, you can do this using the shell as follows:: + + [mkoeppe@sage sage] $ (cd .devcontainer && ln -s portability-ubuntu-jammy-standard/devcontainer.json .) + +Now start VS Code:: + + [mkoeppe@sage sage] $ code . + +Then VS Code may prompt you whether you would like to open the current +directory in the dev container (yes). If it does not, use the command palette +(:kbd:`Ctrl` + :kbd:`Shift` + :kbd:`P`), enter the command "Remote-Containers: +Reopen Folder in Container" , and hit :kbd:`Enter`. + +If the above ``code .`` command does not work, start VS Code as a regular +application, then in the command palette of VS Code, enter "Remote-Containers: +Open Folder in Container", and hit :kbd:`Enter`, and choose the directory +``$SAGE_ROOT`` of your local Sage repository. + +Once VS Code starts configuring the dev container, by clicking on "show log", +you can see what it does: + +- It pulls the prebuilt image from ghcr.io (via + `$SAGE_ROOT/.devcontainer/portability-Dockerfile + `_); + note that these are multi-gigabyte images, so it may take a while. + +- As part of the "onCreateCommand", it installs additional system packages to + support VS Code and for development. + +- Then, as part of the "updateContentCommand", it bootstraps and + configures the source tree and starts to build Sage from source, + reusing the installation (:envvar:`SAGE_LOCAL`, :envvar:`SAGE_VENV`) + from the prebuilt image. + +After VS Code finished configuring the dev container (when the message "Done. +Press any key to close the terminal." appears in the terminal named +"Configuring"), your local Sage repository at ``$SAGE_ROOT`` is available in +the container at the directory ``/workspaces/``. To use Sage +in a terminal, `open a new terminal in VS Code +`_, type ``./sage`` and hit +:kbd:`Enter`. + +.. NOTE:: + + Your Sage at ``$SAGE_ROOT`` was configured and rebuilt inside the dev + container. In particular, ``$SAGE_ROOT/venv``, ``$SAGE_ROOT/prefix``, and + (possibly) ``$SAGE_ROOT/logs`` will be symbolic links that work inside the dev + container, but not in your local file system; and also the script + ``$SAGE_ROOT/sage`` will not work. Hence after working with the dev container, + you will want to remove ``logs`` if it is a symbolic link, and rerun the + ``configure`` script. + +You can edit a copy of the configuration file to change to a different platform, another +version, or build stage. After editing the configuration file (or changing the +symlink), run "Remote-Containers: Rebuild Container" from the command +palette. See the `VS Code devcontainer.json reference +`_ +and the `GitHub introduction to dev containers +`_ +for more information. + +In addition to the +``$SAGE_ROOT/.devcontainer/portability-.../devcontainer.json`` files, Sage also +provides several other sample ``devcontainer.json`` configuration files in the +directory ``$SAGE_ROOT/.devcontainer``. + +Files named ``$SAGE_ROOT/.devcontainer/develop-.../devcontainer.json`` configure +containers from a public Docker image that provides SageMath and then updates the +installation of SageMath in this container by building from the current source tree. + +- `develop-docker-computop/devcontainer.json + `_ + configures a container with the `Docker image from the 3-manifolds + project `_, providing + SnapPy, Regina, PHCPack, etc. + +If you want to use one of these ``devcontainer.json`` files, symlink (or copy) +it and start VS Code as explained above. After VS Code finished configuring the +dev container, to use Sage in a terminal, `open a new terminal in VS Code +`_, type ``./sage`` and hit +:kbd:`Enter`. + +Files named ``$SAGE_ROOT/.devcontainer/downstream-.../devcontainer.json`` configure +containers with an installation of downstream packages providing SageMath from a +package manager (``downstream-archlinux-...``, ``downstream-conda-forge``; +see also `the _sagemath dummy package <../reference/spkg/_sagemath.html>`_), +or from a public Docker image that provides SageMath (``docker-cocalc``, ``docker-computop``). +These ``devcontainer.json`` configuration files are useful for testing +user scripts on these deployments of SageMath. You may also find it +useful to copy these configurations into your own projects (they should +work without change) or to adapt them to your needs. + +- `downstream-archlinux-latest/devcontainer.json + `_ + configures a container with an installation of `Arch Linux + `_ and its SageMath package. (The suffix ``latest`` + indicates the most recent version of Arch Linux as available on Docker Hub.) + +- `downstream-conda-forge-latest/devcontainer.json + `_ + configures a container with an installation of conda-forge and its SageMath package. + +- `downstream-docker-cocalc/devcontainer.json + `_ + configures a container with `the CoCalc Docker image `_. + +- `downstream-docker-computop/devcontainer.json + `_ + configures a container with the `Docker image from the 3-manifolds + project `_, providing + SnapPy, Regina, PHCPack, etc. + +If you want to use one of these ``devcontainer.json`` files, symlink (or copy) +it and start VS Code as explained above. After VS Code finished configuring the +dev container, to use Sage in a terminal, `open a new terminal in VS Code +`_, type ``sage`` and hit +:kbd:`Enter`. (Do not use ``./sage``; this will not work because the source +tree is not configured.) + diff --git a/src/doc/en/developer/tools.rst b/src/doc/en/developer/tools.rst index ee2e8bf0b1c..becaad7f829 100644 --- a/src/doc/en/developer/tools.rst +++ b/src/doc/en/developer/tools.rst @@ -196,7 +196,12 @@ or a few related issues:: - Manual: Run ``pycodestyle path/to/the/file.py``. -- VS Code: Activate by adding the setting ``"python.linting.pycodestyleEnabled": true``, see `official VS Code documentation `__ for details. +- VS Code: The minimal version of pycodestyle is activated by default in + ``SAGE_ROOT/.vscode/settings.json`` (the corresponding setting is + ``"python.linting.pycodestyleEnabled": true``). Note that the + ``settings.json`` file is not ignored by git so be aware to keep it in sync + with the trac repo. For further details, see the + `official VS Code documentation `__. *Configuration:* ``[pycodestyle]`` block in ``SAGE_ROOT/src/tox.ini`` diff --git a/src/doc/en/installation/binary.rst b/src/doc/en/installation/binary.rst index db9c4d3e69a..5fcce7d5943 100644 --- a/src/doc/en/installation/binary.rst +++ b/src/doc/en/installation/binary.rst @@ -25,12 +25,9 @@ This has been discontinued, and the old binaries that are still available there are no longer supported. -Microsoft Windows (Cygwin) --------------------------- +Microsoft Windows +----------------- -SageMath on Windows requires a 64-bit Windows (which is likely to be the case -on a modern computer). If you happen to have a 32-bit Windows, you can consider -the alternatives mentioned at the end of :ref:`installation-guide`. - -To install SageMath on Windows, just download the installer (see the above -"Download Guide" section) and run it. +SageMath used to provide pre-built binaries for Windows based on Cygwin. +This has been discontinued, and the old binaries that can be found +are no longer supported. Use Windows Subsystem for Linux instead. diff --git a/src/doc/en/installation/index.rst b/src/doc/en/installation/index.rst index 6a0b439ac66..b8ad5522062 100644 --- a/src/doc/en/installation/index.rst +++ b/src/doc/en/installation/index.rst @@ -80,10 +80,6 @@ Windows can use any of the installation methods described below for Linux. - - Alternatively, in particular if you cannot use WSL, install - `Cygwin `_ and then build SageMath from source - as described in section :ref:`sec-installation-from-sources`. - Linux ===== @@ -130,8 +126,10 @@ In the cloud - `CoCalc `_: an online service that provides SageMath and many other tools. -- On any system that allows you to bring your own Docker images to run in - a container: Use the `Docker image sagemath/sagemath `_. +- On any system that allows you to bring your own Docker images to run + in a container: Use the `Docker image sagemathinc/cocalc + `_ or :trac:`another Docker + image providing SageMath `. - `Sage Cell Server `_: an online service for elementary SageMath computations. diff --git a/src/doc/en/installation/source.rst b/src/doc/en/installation/source.rst index f670cd925b9..ca362d68d3e 100644 --- a/src/doc/en/installation/source.rst +++ b/src/doc/en/installation/source.rst @@ -59,7 +59,7 @@ and the :wikipedia:`bash ` shell, the following standard command-line development tools must be installed on your computer: -- A **C/C++ compiler**: GCC versions 6.3 to 12.x are supported. +- A **C/C++ compiler**: GCC versions 8.x to 12.x are supported. Clang (LLVM) is also supported. See also `Using alternative compilers`_. - **make**: GNU make, version 3.80 or later. Version 3.82 or later is recommended. @@ -126,7 +126,7 @@ processor. On Linux, this means you need a recent version of Python for venv ^^^^^^^^^^^^^^^ -By default, Sage will try to use system's `python3` to set up a virtual +By default, Sage will try to use system's ``python3`` to set up a virtual environment, a.k.a. `venv `_ rather than building a Python 3 installation from scratch. Use the ``configure`` option ``--without-system-python3`` in case you want Python 3 @@ -168,7 +168,21 @@ On Redhat-derived systems not all perl components are installed by default and you might have to install the ``perl-ExtUtils-MakeMaker`` package. -On Cygwin, the ``lapack`` and ``liblapack-devel`` packages are required. +On Linux systems (e.g., Ubuntu, Redhat, etc), ``ar`` and ``ranlib`` are in the +`binutils `_ package. +The other programs are usually located in packages with their respective names. +Assuming you have sufficient privileges, you can install the ``binutils`` and +other necessary/standard components. The lists provided below are longer than +the minimal prerequisites, which are basically ``binutils``, ``gcc``/``clang``, ``make``, +``tar``, but there is no real need to build compilers and other standard tools +and libraries on a modern Linux system, in order to be able to build Sage. +If you do not have the privileges to do this, ask your system administrator to +do this, or build the components from source code. +The method of installing additional software varies from distribution to +distribution, but on a `Debian `_ based system (e.g. +`Ubuntu `_ or `Mint `_), +you would use +:wikipedia:`apt-get `. Installing prerequisites ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -188,55 +202,83 @@ either ``perl`` is not installed, or it is installed but not in your .. _sec-installation-from-sources-linux-recommended-installation: -Linux recommended installation -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -On Linux systems (e.g., Ubuntu, Redhat, etc), ``ar`` and ``ranlib`` are in the -`binutils `_ package. -The other programs are usually located in packages with their respective names. -Assuming you have sufficient privileges, you can install the ``binutils`` and -other necessary/standard components. The lists provided below are longer than -the minimal prerequisites, which are basically ``binutils``, ``gcc``/``clang``, ``make``, -``tar``, but there is no real need to build compilers and other standard tools -and libraries on a modern Linux system, in order to be able to build Sage. -If you do not have the privileges to do this, ask your system administrator to -do this, or build the components from source code. -The method of installing additional software varies from distribution to -distribution, but on a `Debian `_ based system (e.g. -`Ubuntu `_ or `Mint `_), -you would use -:wikipedia:`apt-get `. +Debian/Ubuntu prerequisite installation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ On Debian ("buster" or newer) or Ubuntu ("bionic" or newer): .. literalinclude:: debian.txt -On Fedora / Redhat / CentOS: +If you wish to do Sage development, additionally install the following: -.. literalinclude:: fedora.txt +.. literalinclude:: debian-develop.txt -On Arch Linux: +For all users, we recommend the following: -.. literalinclude:: arch.txt +.. literalinclude:: debian-recommended.txt In addition to these, if you don't want Sage to build optional packages that might be available from your OS, cf. the growing list of such packages on :trac:`27330`, -install on Debian ("buster" or newer) or Ubuntu ("bionic" or newer): +install: .. literalinclude:: debian-optional.txt -On Fedora / Redhat / CentOS: +Fedora/Redhat/CentOS prerequisite installation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. literalinclude:: fedora.txt + +If you wish to do Sage development, additionally install the following: + +.. literalinclude:: fedora-develop.txt + +For all users, we recommend the following: + +.. literalinclude:: fedora-recommended.txt + +In addition to these, if you don't want Sage to build optional packages that might +be available from your OS, cf. the growing list of such packages on :trac:`27330`, +install: .. literalinclude:: fedora-optional.txt -On Arch Linux: +Arch Linux prerequisite installation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. literalinclude:: arch.txt + +If you wish to do Sage development, additionally install the following: + +.. literalinclude:: arch-develop.txt + +For all users, we recommend the following: + +.. literalinclude:: arch-recommended.txt + +In addition to these, if you don't want Sage to build optional packages that might +be available from your OS, cf. the growing list of such packages on :trac:`27330`, +install: .. literalinclude:: arch-optional.txt -On other Linux systems, you might use -:wikipedia:`rpm `, -:wikipedia:`yum `, -or other package managers. +OpenSUSE prerequisite installation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. literalinclude:: opensuse.txt + +If you wish to do Sage development, additionally install the following: + +.. literalinclude:: opensuse-develop.txt + +For all users, we recommend the following: + +.. literalinclude:: opensuse-recommended.txt + +In addition to these, if you don't want Sage to build optional packages that might +be available from your OS, cf. the growing list of such packages on :trac:`27330`, +install: + +.. literalinclude:: opensuse-optional.txt .. _section_macprereqs: @@ -302,47 +344,18 @@ Sage, run :: command like this to your shell profile if you want the settings to persist between shell sessions. -Some additional optional packages are taken care of by: - -.. literalinclude:: homebrew-optional.txt - +If you wish to do Sage development, additionally install the following: -.. _section_cygwinprereqs: - -Cygwin prerequisite installation -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Sage can be built only on the 64-bit version of Cygwin. See -the file `README.md `_ -in ``SAGE_ROOT`` for the most up-to-date instructions for building Sage -on Cygwin. - -Although it is possible to install Sage's dependencies using the Cygwin -graphical installer, it is recommended to install the `apt-cyg -`_ command-line package -installer, which is used for the remainder of these instructions. To -run ``apt-cyg``, you must have already installed (using the graphical -installer) the following packages at a minimum:: - - bzip2 coreutils gawk gzip tar wget +.. literalinclude:: homebrew-develop.txt -With the exception of ``wget`` most of these are included in the default -package selection when you install Cygwin. Then, to install ``apt-cyg`` -run:: +For all users, we recommend the following: - $ curl -OL https://rawgit.com/transcode-open/apt-cyg/master/apt-cyg - $ install apt-cyg /usr/local/bin - $ rm -f apt-cyg +.. literalinclude:: homebrew-recommended.txt -To install the current set of system packages known to work for building -Sage, run: - -.. literalinclude:: cygwin.txt +Some additional optional packages are taken care of by: -Optional packages that are also known to be installable via system packages -include: +.. literalinclude:: homebrew-optional.txt -.. literalinclude:: cygwin-optional.txt Ubuntu on Windows Subsystem for Linux (WSL) prerequisite installation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -376,6 +389,133 @@ Also see the `related Github issue `_. + +As of Sage 9.7, we no longer recommend attempting to build Sage on +Cygwin and instead suggest that users on Windows 10 and 11 switch to +installing Sage using Windows Subsystem for Linux (WSL), which gives a +better performance and user/developer experience than Cygwin. + +Users on hardware configurations that do not support running WSL, as +well as users on legacy versions of Windows such as Windows 8 may find +it necessary to build Sage on Cygwin. + +.. WARNING:: + + As of Sage 9.7, :trac:`known issues with several packages + ` + will prevent a successful installation. Users need to be prepared + to contribute to Sage by fixing these issues. + +Use the following instructions to get started. + +1. Download `the 64-bit version of Cygwin `_ + (do not get the 32-bit version; it is not supported by Sage). + +2. Run the ``setup-x86_64.exe`` graphical installer. Pick the default + options in most cases. At the package selection screen, use the + search bar to find and select at least the following packages: + ``bzip2``, ``coreutils``, ``curl``, ``gawk``, ``gzip``, ``tar``, ``wget``, ``git``. + +3. Start the Cygwin terminal and ensure you get a working bash prompt. + +4. Make sure the path of your Cygwin home directory does not contain + space characters. Also avoid building in home directories of Windows domain + users or in paths with capital letters. + + By default, your username in Cygwin is the same as your username in + Windows. This might contain spaces and other traditionally + non-UNIX-friendly characters, e.g., if it is your full name. You + can check this as follows:: + + $ whoami + Erik M. Bray + + This means your default home directory on Cygwin contains this + username verbatim; in the above example, ``/home/Erik M. Bray``. + It will save some potential trouble if you change your Cygwin home + directory to contain only alphanumeric characters, for example, + ``/home/embray``. The easiest way to do this is to first create + the home directory you want to use instead, then create an + ``/etc/passwd`` file specifying that directory as your home, as follows:: + + $ whocanibe=embray + $ mkdir /home/$whocanibe + $ mkpasswd.exe -l -u "$(whoami)" | sed -r 's,/home/[^:]+,/home/'$whocanibe, > /etc/passwd + + After this, close all Cygwin terminals (ensure nothing in + ``C:\cygwin64`` is running), then start a new Cygwin terminal and + your home directory should have moved. + + There are `other ways to do + this `_, + but the above seems to be the simplest that's still supported. + +5. (Optional) Although it is possible to install Sage's dependencies using the + Cygwin graphical installer, it is recommended to install the + `apt-cyg `_ + command-line package installer, which is used for the remainder of + these instructions. To install ``apt-cyg``, run:: + + $ curl -OL https://rawgit.com/transcode-open/apt-cyg/master/apt-cyg + $ install apt-cyg /usr/local/bin + $ rm -f apt-cyg + +6. Then, to install the current set of system packages known to work for building + Sage, run the following command (or use the graphical installer to + select and install these packages): + + .. literalinclude:: cygwin.txt + + Optional packages that are also known to be installable via system packages + include: + + .. literalinclude:: cygwin-optional.txt + +.. NOTE:: + + On Cygwin, at any point in time after building/installing software, + it may be required to "rebase" ``dll`` files. + Sage provides some scripts, located in :file:`$SAGE_LOCAL/bin`, to do so: + + - ``sage-rebaseall.sh``, a shell script which calls Cygwin's + ``rebaseall`` program. It must be run within a ``dash`` shell + from the :envvar:`SAGE_ROOT` directory after all other Cygwin + processes have been shut down and needs write-access to the + system-wide rebase database located at + :file:`/etc/rebase.db.i386`, which usually means administrator + privileges. It updates the system-wide database and adds Sage + dlls to it, so that subsequent calls to ``rebaseall`` will take + them into account. + + - ``sage-rebase.sh``, a shell script which calls Cygwin's ``rebase`` program + together with the ``-O/--oblivious`` option. + It must be run within a shell from :envvar:`SAGE_ROOT` directory. + Contrary to the ``sage-rebaseall.sh`` script, it neither updates the + system-wide database, nor adds Sage dlls to it. + Therefore, subsequent calls to ``rebaseall`` will not take them into account. + + - ``sage-rebaseall.bat`` (respectively ``sage-rebase.bat``), an MS-DOS batch + file which calls the ``sage-rebaseall.sh`` (respectively ``sage-rebase.sh``) + script. + It must be run from a Windows command prompt, after adjusting + :envvar:`SAGE_ROOT` to the Windows location of Sage's home directory, and, if + Cygwin is installed in a non-standard location, adjusting + :envvar:`CYGWIN_ROOT` as well. + + Some systems may encounter this problem frequently enough to make building or + testing difficult. + If executing the above scripts or directly calling ``rebaseall`` does not solve + rebasing issues, deleting the system-wide database and then regenerating it + from scratch, e.g., by executing ``sage-rebaseall.sh``, might help. + + Other platforms ^^^^^^^^^^^^^^^ @@ -735,46 +875,6 @@ General procedure #. Have fun! Discover some amazing conjectures! -Rebasing issues on Cygwin -~~~~~~~~~~~~~~~~~~~~~~~~~ - -Building on Cygwin will occasionally require "rebasing" ``dll`` files. -Sage provides some scripts, located in :file:`$SAGE_LOCAL/bin`, to do so: - -- ``sage-rebaseall.sh``, a shell script which calls Cygwin's ``rebaseall`` - program. - It must be run within a ``dash`` shell from the :envvar:`SAGE_ROOT` directory - after all other Cygwin processes have been shut down and needs write-access - to the system-wide rebase database located at :file:`/etc/rebase.db.i386`, - which usually means administrator privileges. - It updates the system-wide database and adds Sage dlls to it, so that - subsequent calls to ``rebaseall`` will take them into account. -- ``sage-rebase.sh``, a shell script which calls Cygwin's ``rebase`` program - together with the ``-O/--oblivious`` option. - It must be run within a shell from :envvar:`SAGE_ROOT` directory. - Contrary to the ``sage-rebaseall.sh`` script, it neither updates the - system-wide database, nor adds Sage dlls to it. - Therefore, subsequent calls to ``rebaseall`` will not take them into account. -- ``sage-rebaseall.bat`` (respectively ``sage-rebase.bat``), an MS-DOS batch - file which calls the ``sage-rebaseall.sh`` (respectively ``sage-rebase.sh``) - script. - It must be run from a Windows command prompt, after adjusting - :envvar:`SAGE_ROOT` to the Windows location of Sage's home directory, and, if - Cygwin is installed in a non-standard location, adjusting - :envvar:`CYGWIN_ROOT` as well. - -Some systems may encounter this problem frequently enough to make building or -testing difficult. -If executing the above scripts or directly calling ``rebaseall`` does not solve -rebasing issues, deleting the system-wide database and then regenerating it -from scratch, e.g., by executing ``sage-rebaseall.sh``, might help. - -Finally, on Cygwin, one should also avoid the following: - -- building in home directories of Windows domain users; -- building in paths with capital letters - (see :trac:`13343`, although there has been some success doing so). - .. _section_make: diff --git a/src/doc/en/reference/algebras/cubic_hecke_algebra.rst b/src/doc/en/reference/algebras/cubic_hecke_algebra.rst new file mode 100644 index 00000000000..5ba74eb00f0 --- /dev/null +++ b/src/doc/en/reference/algebras/cubic_hecke_algebra.rst @@ -0,0 +1,10 @@ +Cubic Hecke Algebras +==================== + +.. toctree:: + :maxdepth: 2 + + sage/algebras/hecke_algebras/cubic_hecke_algebra + sage/algebras/hecke_algebras/cubic_hecke_base_ring + sage/algebras/hecke_algebras/cubic_hecke_matrix_rep + diff --git a/src/doc/en/reference/algebras/index.rst b/src/doc/en/reference/algebras/index.rst index 898d8ace33e..237343faf32 100644 --- a/src/doc/en/reference/algebras/index.rst +++ b/src/doc/en/reference/algebras/index.rst @@ -79,6 +79,7 @@ Hecke algebras sage/algebras/iwahori_hecke_algebra sage/algebras/nil_coxeter_algebra sage/algebras/yokonuma_hecke_algebra + cubic_hecke_algebra Graded algebras --------------- @@ -98,6 +99,7 @@ Various associative algebras sage/algebras/associated_graded sage/algebras/cellular_basis sage/algebras/q_system + sage/algebras/q_commuting_polynomials sage/algebras/splitting_algebra Non-associative algebras diff --git a/src/doc/en/reference/arithmetic_curves/index.rst b/src/doc/en/reference/arithmetic_curves/index.rst index 9fbc0495854..73f6f602490 100644 --- a/src/doc/en/reference/arithmetic_curves/index.rst +++ b/src/doc/en/reference/arithmetic_curves/index.rst @@ -20,8 +20,9 @@ Maps between them sage/schemes/elliptic_curves/hom sage/schemes/elliptic_curves/weierstrass_morphism sage/schemes/elliptic_curves/ell_curve_isogeny - sage/schemes/elliptic_curves/isogeny_small_degree + sage/schemes/elliptic_curves/hom_velusqrt sage/schemes/elliptic_curves/hom_composite + sage/schemes/elliptic_curves/isogeny_small_degree Elliptic curves over number fields diff --git a/src/doc/en/reference/databases/index.rst b/src/doc/en/reference/databases/index.rst index 34ae3ba3f1f..56289fa5236 100644 --- a/src/doc/en/reference/databases/index.rst +++ b/src/doc/en/reference/databases/index.rst @@ -63,5 +63,6 @@ database engine. sage/databases/db_class_polynomials sage/databases/db_modular_polynomials sage/databases/knotinfo_db + sage/databases/cubic_hecke_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 a5d9663c5d7..0289ea570d9 100644 --- a/src/doc/en/reference/discrete_geometry/index.rst +++ b/src/doc/en/reference/discrete_geometry/index.rst @@ -47,6 +47,20 @@ Lattice polyhedra sage/geometry/polyhedron/ppl_lattice_polygon sage/geometry/polyhedron/ppl_lattice_polytope +Combinatorial Polyhedra +~~~~~~~~~~~~~~~~~~~~~~~ + +.. toctree:: + :maxdepth: 1 + + sage/geometry/polyhedron/combinatorial_polyhedron/base + sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face + sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice + sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator + sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces + sage/geometry/polyhedron/combinatorial_polyhedron/conversions + + Polyhedral complexes ~~~~~~~~~~~~~~~~~~~~ @@ -76,6 +90,14 @@ Base classes for polyhedra .. toctree:: :maxdepth: 1 + sage/geometry/polyhedron/base0 + sage/geometry/polyhedron/base1 + sage/geometry/polyhedron/base2 + sage/geometry/polyhedron/base3 + sage/geometry/polyhedron/base4 + sage/geometry/polyhedron/base5 + sage/geometry/polyhedron/base6 + sage/geometry/polyhedron/base7 sage/geometry/polyhedron/base sage/geometry/polyhedron/base_QQ sage/geometry/polyhedron/base_ZZ @@ -88,6 +110,7 @@ Backends for Polyhedra :maxdepth: 1 sage/geometry/polyhedron/backend_cdd + sage/geometry/polyhedron/backend_cdd_rdf sage/geometry/polyhedron/backend_field sage/geometry/polyhedron/backend_normaliz sage/geometry/polyhedron/backend_polymake @@ -113,6 +136,7 @@ Miscellaneous .. toctree:: :maxdepth: 1 + sage/geometry/abc sage/geometry/convex_set sage/geometry/linear_expression sage/geometry/newton_polygon diff --git a/src/doc/en/reference/index.rst b/src/doc/en/reference/index.rst index 065bccac955..9008c78e157 100644 --- a/src/doc/en/reference/index.rst +++ b/src/doc/en/reference/index.rst @@ -92,18 +92,22 @@ Discrete Mathematics * :doc:`Symbolic Logic ` * :doc:`SAT solvers ` -Geometry, Topology, and Homological Algebra -------------------------------------------- +Geometry and Topology +--------------------- * :doc:`Euclidean Spaces and Vector Calculus ` * :doc:`Combinatorial and Discrete Geometry ` -* :doc:`Cell Complexes, Simplicial Complexes, and - Simplicial Sets ` +* :doc:`Cell Complexes, Simplicial Complexes, and Simplicial Sets ` * :doc:`Manifolds and Differential Geometry ` * :doc:`Hyperbolic Geometry ` * :doc:`Parametrized Surfaces ` * :doc:`Knot Theory ` + +Homological Algebra +------------------- + * :doc:`Chain Complexes and their Homology ` +* :doc:`Resolutions ` Number Fields, Function Fields, and Valuations ---------------------------------------------- diff --git a/src/doc/en/reference/polynomial_rings/polynomial_rings_multivar.rst b/src/doc/en/reference/polynomial_rings/polynomial_rings_multivar.rst index 46388f58f25..6147929ccf2 100644 --- a/src/doc/en/reference/polynomial_rings/polynomial_rings_multivar.rst +++ b/src/doc/en/reference/polynomial_rings/polynomial_rings_multivar.rst @@ -36,6 +36,8 @@ are implemented using the PolyBoRi library (cf. :mod:`sage.rings.polynomial.pbor sage/rings/polynomial/multi_polynomial_libsingular sage/rings/polynomial/multi_polynomial_ideal_libsingular + sage/rings/polynomial/msolve + sage/rings/polynomial/polydict sage/rings/polynomial/hilbert diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index bb77d546810..cc87e5490cb 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -95,7 +95,7 @@ REFERENCES: graphs and isoperimetric inequalities*, The Annals of Probability 32 (2004), no. 3A, 1727-1745. -.. [ASV2020] Federico Ardila, Mariel Supina, and Andrés R. Vindas-Meléndez, +.. [ASV2020] Federico Ardila, Mariel Supina, and Andrés R. Vindas-Meléndez, *The Equivariant Ehrhart Theory of the Permutahedron*, Proc. Amer. Math. Soc. Volume 148, Number 12, 2020, pp. 5091--5107. @@ -591,6 +591,11 @@ REFERENCES: for Computing p-Adic Polylogarithms." Mathematics of Computation (2008): 1105-1134. +.. [BDLS2020] Daniel J. Bernstein, Luca De Feo, Antonin Leroux, and Benjamin + Smith: Faster computation of isogenies of large prime degree. + ANTS XIV, Open Book Series Vol. 4, No. 1, 2020. + :arxiv:`2003.10118` + .. [BD1989] \R. J. Bradford and J. H. Davenport, *Effective tests for cyclotomic polynomials*, Symbolic and Algebraic Computation (1989), pp. 244--251, @@ -1578,6 +1583,10 @@ REFERENCES: \J. Algebr. Comb. **39** (2014) pp. 17-51. :doi:`10.1007/s10801-013-0437-x`, :arxiv:`1108.1776`. +.. [CM2012] \M. Cabanes, I. Marin, *On ternary quotients of cubic Hecke + algebras*, Comm. Math. Phys. (2012), Volume 314, Issue 1, + pp 57-92. :doi:`10.1007/s00220-012-1519-7`, :arxiv:`1010.1465`. + .. [CMN2014] David Coudert, Dorian Mazauric, and Nicolas Nisse, *Experimental Evaluation of a Branch and Bound Algorithm for computing Pathwidth*. In Symposium on Experimental Algorithms (SEA), volume @@ -2141,6 +2150,9 @@ REFERENCES: .. [Dy1993] \M. J. Dyer. *Hecke algebras and shellings of Bruhat intervals*. Compositio Mathematica, 1993, 89(1): 91-115. +.. [Dy1994] \M. J. Dyer. *Bruhat intervals, polyhedral cones and + Kazhdan-Lusztig-Stanley polynomials*. Math.Z., 215(2):223-236, 1994. + .. _ref-E: **E** @@ -2653,6 +2665,10 @@ REFERENCES: .. [Gop1981] \V. D. Goppa, “Codes on algebraic curves,” Sov. Math. Dokl., vol. 24, no. 1, pp. 170–172, 1981. +.. [Gor2016] \D. Gorodkov, *A 15-vertex triangulation of the quaternionic + projective plane*, Discrete & Computational Geometry, vol. 62, + pp. 348-373 (2019). :doi:`10.1007/s00454-018-00055-w` + .. [Gos1972] Bill Gosper, "Continued Fraction Arithmetic" https://perl.plover.com/classes/cftalk/INFO/gosper.txt @@ -3321,11 +3337,20 @@ REFERENCES: *Combinatorics of symmetric designs*. Cambridge University Press, 2006. +.. [JS2000] \A. Joellenbeck, M. Schocker. + "Cyclic Characters of Symmetric Groups". + J. Algebraic Combin., **12** (2000), 155-161. + :doi:`10.1023/A:1026592027019`. + .. [JS2010] \B. Jones, A. Schilling. "Affine structures and a tableau model for `E_6` crystals", J. Algebra. **324** (2010). 2512-2542. :doi:`10.1016/j.bbr.2011.03.031`, :arxiv:`0909.2442`. +.. [JS2021] \D. Jahn, C. Stump. + *Bruhat intervals, subword complexes and brick polyhedra for + finite Coxeter groups*, 2021, :arxiv:`2103.03715`. + .. [JV2000] \J. Justin, L. Vuillon, *Return words in Sturmian and episturmian words*, Theor. Inform. Appl. 34 (2000) 343--356. @@ -3405,6 +3430,9 @@ REFERENCES: cellular logic networks and machines, AFCRL-68-0668, SRI Project 7258, Final Rep., pp. 20-28, 1968. +.. [Kau1991] \Louis Kauffman. *Knots and Physics*, World Scientific, 1991 + :doi:`10.1142/4256`. + .. [Kaw2009] Kawahira, Tomoki. *An algorithm to draw external rays of the Mandelbrot set*, Nagoya University, 23 Apr. 2009. math.titech.ac.jp/~kawahira/programs/mandel-exray.pdf @@ -4192,6 +4220,14 @@ REFERENCES: .. [Mar2004] \S. Marcus, Quasiperiodic infinite words, Bull. Eur. Assoc. Theor. Comput. Sci. 82 (2004) 170-174. +.. [Mar2012] \I. Marin, *The cubic Hecke algebra on at most 5 strands*, + Journal of Pure and Applied Algebra 216 (2012) 2754-2782. + :doi:`10.1016/j.jpaa.2012.04.013`, :arxiv:`1110.6621`. + +.. [Mar2018] \I. Marin, *Maximal cubic quotient of the braid algebra*, + preprint, 2018. available at + http://www.lamfa.u-picardie.fr/marin/arts/GQ.pdf + .. [Mas1994] James L. Massey, *SAFER K-64: A byte-oriented block-ciphering algorithm*; in FSE’93, Volume 809 of LNCS, pages 1-17. @@ -4327,6 +4363,9 @@ REFERENCES: polynomials*. Trans. Amer. Math. Soc., 245 (1978), 89-118. +.. [MilStu2005] Ezra Miller and Bernd Sturmfels, *Combinatorial Commutative Algebra*, + GTM Vol. 227, Springer Science & Business Media, 2005. + .. [Mil2004] Victor S. Miller, "The Weil pairing, and its efficient calculation", J. Cryptol., 17(4):235-261, 2004 @@ -4947,6 +4986,10 @@ REFERENCES: linear codes, and cryptography. STOC, pp. 84--93, ACM, 2005. +.. [Ren2018] Joost Renes: Computing Isogenies Between Montgomery Curves + Using the Action of `(0,0)`. PQCrypto 2018, pp. 229--247. + https://eprint.iacr.org/2017/1198.pdf + .. [Reu1993] \C. Reutenauer. *Free Lie Algebras*. Number 7 in London Math. Soc. Monogr. (N.S.). Oxford University Press. (1993). @@ -5415,7 +5458,7 @@ REFERENCES: .. [St1986] Richard Stanley. *Two poset polytopes*, Discrete Comput. Geom. (1986), :doi:`10.1007/BF02187680` -.. [Stap2011] Alan Stapledon. *Equivariant Ehrhart Theory*. +.. [Stap2011] Alan Stapledon. *Equivariant Ehrhart Theory*. Advances in Mathematics 226 (2011), no. 4, 3622-3654 .. [Sta1973] \H. M. Stark, Class-Numbers of Complex Quadratic diff --git a/src/doc/en/reference/resolutions/conf.py b/src/doc/en/reference/resolutions/conf.py new file mode 120000 index 00000000000..2bdf7e68470 --- /dev/null +++ b/src/doc/en/reference/resolutions/conf.py @@ -0,0 +1 @@ +../conf_sub.py \ No newline at end of file diff --git a/src/doc/en/reference/resolutions/index.rst b/src/doc/en/reference/resolutions/index.rst new file mode 100644 index 00000000000..82f73cb94c8 --- /dev/null +++ b/src/doc/en/reference/resolutions/index.rst @@ -0,0 +1,13 @@ +Resolutions +=========== + +Free and graded resolutions are tools for commutative algebra and algebraic +geometry. + +.. toctree:: + :maxdepth: 2 + + sage/homology/free_resolution + sage/homology/graded_resolution + +.. include:: ../footer.txt diff --git a/src/doc/en/reference/tensor_free_modules/tensors.rst b/src/doc/en/reference/tensor_free_modules/tensors.rst index be87fc68ad1..434ea734191 100644 --- a/src/doc/en/reference/tensor_free_modules/tensors.rst +++ b/src/doc/en/reference/tensor_free_modules/tensors.rst @@ -6,6 +6,10 @@ Tensors sage/tensor/modules/tensor_free_module + sage/tensor/modules/tensor_free_submodule + sage/tensor/modules/free_module_tensor sage/tensor/modules/tensor_with_indices + + sage/tensor/modules/tensor_free_submodule_basis diff --git a/src/doc/en/thematic_tutorials/algebraic_combinatorics/n_cube.rst b/src/doc/en/thematic_tutorials/algebraic_combinatorics/n_cube.rst index a31dff543a2..307291b10cc 100644 --- a/src/doc/en/thematic_tutorials/algebraic_combinatorics/n_cube.rst +++ b/src/doc/en/thematic_tutorials/algebraic_combinatorics/n_cube.rst @@ -19,8 +19,8 @@ The vertices of the `n`-cube can be described by vectors in The distance function measures in how many slots two vectors in `\mathbb{Z}_2^n` differ:: - sage: u=(1,0,1,1,1,0) - sage: v=(0,0,1,1,0,0) + sage: u = (1,0,1,1,1,0) + sage: v = (0,0,1,1,0,0) sage: dist(u,v) 2 diff --git a/src/doc/en/thematic_tutorials/coercion_and_categories.rst b/src/doc/en/thematic_tutorials/coercion_and_categories.rst index bfb8247841e..4efe68a2617 100644 --- a/src/doc/en/thematic_tutorials/coercion_and_categories.rst +++ b/src/doc/en/thematic_tutorials/coercion_and_categories.rst @@ -109,9 +109,7 @@ This base class provides a lot more methods than a general parent:: '__ideal_monoid', '__iter__', '__len__', - '__rtruediv__', '__rxor__', - '__truediv__', '__xor__', '_an_element_impl', '_coerce_', @@ -160,9 +158,6 @@ This base class provides a lot more methods than a general parent:: 'order', 'prime_subfield', 'principal_ideal', - 'quo', - 'quotient', - 'quotient_ring', 'random_element', 'unit_ideal', 'zero', diff --git a/src/doc/en/thematic_tutorials/geometry/polyhedra_tutorial.rst b/src/doc/en/thematic_tutorials/geometry/polyhedra_tutorial.rst index cb4ad1faa2e..b7d689e5ca3 100644 --- a/src/doc/en/thematic_tutorials/geometry/polyhedra_tutorial.rst +++ b/src/doc/en/thematic_tutorials/geometry/polyhedra_tutorial.rst @@ -646,7 +646,7 @@ for sage is installed. :: - sage: p = Polyhedron(vertices=[(0,0),(1,0),(0,1)], # optional - polymake + sage: p = Polyhedron(vertices=[(0,0),(1,0),(0,1)], # optional - jupymake ....: rays=[(1,1)], lines=[], ....: backend='polymake', base_ring=QQ) @@ -657,7 +657,7 @@ An example with quadratic field: :: sage: V = polytopes.dodecahedron().vertices_list() # optional - sage.rings.number_field - sage: Polyhedron(vertices=V, backend='polymake') # optional - polymake # optional - sage.rings.number_field + sage: Polyhedron(vertices=V, backend='polymake') # optional - jupymake # optional - sage.rings.number_field A 3-dimensional polyhedron in (Number Field in sqrt5 with defining polynomial x^2 - 5 with sqrt5 = 2.236067977499790?)^3 diff --git a/src/doc/en/thematic_tutorials/geometry/polytope_tikz.rst b/src/doc/en/thematic_tutorials/geometry/polytope_tikz.rst index 9fb6be2700e..a0c98ea3836 100644 --- a/src/doc/en/thematic_tutorials/geometry/polytope_tikz.rst +++ b/src/doc/en/thematic_tutorials/geometry/polytope_tikz.rst @@ -15,8 +15,9 @@ paper. TikZ is a very versatile tool to draw in scientific documents and Sage can deal easily with 3-dimensional polytopes. Finally sagetex makes everything work together nicely between Sage, TikZ and LaTeX. Since version 6.3 of Sage, there is a function for (projection -of) polytopes to output a TikZ picture of the polytope. This short -tutorial shows how it all works. +of) polytopes to output a TikZ picture of the polytope. Since version 9.7 of +SageMath, the tikz output can be a ``TikzPicture`` object from the sage module +``sage.misc.latex_standalone``. This short tutorial shows how it all works. Instructions """""""""""" @@ -30,21 +31,23 @@ To put an image of a 3D-polytope in LaTeX using TikZ and Sage, simply follow the - Visualize the polytope P using the command ``P.show(aspect_ratio=1)`` - This will open an interactive view in your default browser, where you can rotate the polytope. - Once the desired view angle is found, click on the information icon in the lower right-hand corner and select *Get Viewpoint*. This will copy a string of the form '[x,y,z],angle' to your local clipboard. -- Go back to Sage and type ``Img = P.tikz([x,y,z],angle)``. You can paste the string here to save some typing. -- *Img* now contains a Sage object of type ``LatexExpr`` containing the raw TikZ picture of your polytope +- Go back to Sage and type ``Img = P.tikz([x,y,z],angle,output_type='LatexExpr')``. You can paste the string here to save some typing. +- *Img* now contains a Sage object of type ``LatexExpr`` containing the raw TikZ picture of your polytope. -Then, you can either copy-paste it to your article by typing ``Img`` in Sage or save it to a file, by doing +Alternatively, you can save the tikz image to a file, by doing .. CODE-BLOCK:: python - f = open('Img_poly.tex','w') - f.write(Img) - f.close() + Img = P.tikz([x,y,z], angle, output_type='TikzPicture') + Img.tex('Img_poly.tex') + Img.tex('Img_poly.tex', content_only=True) + Img.pdf('Img_poly.pdf') .. end of output Then in the pwd (present working directory of sage, the one of your article) -you will have a file named ``Img_poly.tex`` containing the tikzpicture of your polytope. +you will have two files named ``Img_poly.tex`` and ``Img_poly.pdf`` containing the +tikzpicture of the polytope ``P``. Customization """"""""""""" @@ -57,6 +60,9 @@ You can customize the polytope using the following options in the command ``P.ti - ``vertex_color`` : string (default: ``green``) representing colors which tikz recognize, - ``opacity`` : real number (default: ``0.8``) between 0 and 1 giving the opacity of the front facets, - ``axis`` : Boolean (default: ``False``) draw the axes at the origin or not. +- ``output_type`` : string (default: ``None``) ``None``, ``'LatexExpr'`` or + ``'TikzPicture'``, the type of the output. Since SageMath 9.7, the value ``None`` is deprecated + as the default value will soon be changed from ``'LatexExpr'`` to ``'TikzPicture'``. Examples """""""" @@ -80,15 +86,20 @@ When you found a good angle, follow the above procedure to obtain the values :: - Img = P.tikz([674,108,-731],112) + Img = P.tikz([674,108,-731], 112, output_type='TikzPicture') .. end of output +Note: the ``output_type='TikzPicture'`` is necessary since SagMath 9.7 to avoid +a deprecation warning message since the default output type will soon change +from a ``LatexExpr`` (Python str) to a ``TikzPicture`` object (allowing more +versatility, like being able to view it directly in the Jupyter notebook). + Or you may want to customize using the command :: - Img = P.tikz([674,108,-731],112,scale=2, edge_color='orange',facet_color='red',vertex_color='blue',opacity=0.4) + Img = P.tikz([674,108,-731],112,scale=2, edge_color='orange',facet_color='red',vertex_color='blue',opacity=0.4, output_type='TikzPicture') .. end of output @@ -134,7 +145,7 @@ some possibilities. .. CODE-BLOCK:: latex - \sagestr{(polytopes.permutahedron(4)).tikz([4,5,6],45,scale=0.75, facet_color='red',vertex_color='yellow',opacity=0.3)} + \sagestr{(polytopes.permutahedron(4)).tikz([4,5,6],45,scale=0.75, facet_color='red',vertex_color='yellow',opacity=0.3, output_type='LatexExpr')} .. end of output @@ -142,8 +153,8 @@ some possibilities. .. CODE-BLOCK:: latex - \newcommand{\polytopeimg}[4]{\sagestr{(#1).tikz(#2,#3,#4)}} - \newcommand{\polytopeimgopt}[9]{\sagestr{(#1).tikz(#2,#3,#4,#5,#6,#7,#8,#9)}} + \newcommand{\polytopeimg}[4]{\sagestr{(#1).tikz(#2,#3,#4,output_type='LatexExpr')}} + \newcommand{\polytopeimgopt}[9]{\sagestr{(#1).tikz(#2,#3,#4,#5,#6,#7,#8,#9,output_type='LatexExpr')}} .. end of output diff --git a/src/doc/en/thematic_tutorials/geometry/visualization.rst b/src/doc/en/thematic_tutorials/geometry/visualization.rst index ac7412e665c..438b6cff4c8 100644 --- a/src/doc/en/thematic_tutorials/geometry/visualization.rst +++ b/src/doc/en/thematic_tutorials/geometry/visualization.rst @@ -113,11 +113,22 @@ This method returns a tikz picture of the polytope (must be 2 or :: sage: c = polytopes.cube() - sage: c.tikz().splitlines()[:5] - ['\\begin{tikzpicture}%', - '\t[x={(1.000000cm, 0.000000cm)},', - '\ty={(-0.000000cm, 1.000000cm)},', - '\tz={(0.000000cm, -0.000000cm)},', - '\tscale=1.000000,'] + sage: c.tikz(output_type='TikzPicture') + \documentclass[tikz]{standalone} + \begin{document} + \begin{tikzpicture}% + [x={(1.000000cm, 0.000000cm)}, + y={(-0.000000cm, 1.000000cm)}, + z={(0.000000cm, -0.000000cm)}, + scale=1.000000, + ... + Use print to see the full content. + ... + \node[vertex] at (-1.00000, -1.00000, 1.00000) {}; + \node[vertex] at (-1.00000, 1.00000, 1.00000) {}; + %% + %% + \end{tikzpicture} + \end{document} .. end of output diff --git a/src/doc/en/thematic_tutorials/lie/affine.rst b/src/doc/en/thematic_tutorials/lie/affine.rst index 124bf1f2e8d..320ff381783 100644 --- a/src/doc/en/thematic_tutorials/lie/affine.rst +++ b/src/doc/en/thematic_tutorials/lie/affine.rst @@ -381,7 +381,7 @@ The column vector `a` with these entries spans the nullspace of `A`:: sage: RS = RootSystem(['E',6,2]); RS Root system of type ['F', 4, 1]^* - sage: A=RS.cartan_matrix(); A + sage: A = RS.cartan_matrix(); A [ 2 -1 0 0 0] [-1 2 -1 0 0] [ 0 -1 2 -2 0] @@ -471,4 +471,3 @@ It may be constructed in Sage as follows:: See the documentation in :mod:`~sage.combinat.root_system.extended_affine_weyl_group` if you need this. - diff --git a/src/doc/en/thematic_tutorials/lie/branching_rules.rst b/src/doc/en/thematic_tutorials/lie/branching_rules.rst index 8ae0a9591ff..40eaf488f1d 100644 --- a/src/doc/en/thematic_tutorials/lie/branching_rules.rst +++ b/src/doc/en/thematic_tutorials/lie/branching_rules.rst @@ -50,7 +50,7 @@ Sage has a class ``BranchingRule`` for branching rules. The function the natural embedding of `Sp(4)` into `SL(4)` corresponds to the branching rule that we may create as follows:: - sage: b=branching_rule("A3","C2",rule="symmetric"); b + sage: b = branching_rule("A3","C2",rule="symmetric"); b symmetric branching rule A3 => C2 The name "symmetric" of this branching rule will be @@ -62,10 +62,10 @@ Here ``A3`` and ``C2`` are the Cartan types of the groups Now we may see how representations of `SL(4)` decompose into irreducibles when they are restricted to `Sp(4)`:: - sage: A3=WeylCharacterRing("A3",style="coroots") - sage: chi=A3(1,0,1); chi.degree() + sage: A3 = WeylCharacterRing("A3",style="coroots") + sage: chi = A3(1,0,1); chi.degree() 15 - sage: C2=WeylCharacterRing("C2",style="coroots") + sage: C2 = WeylCharacterRing("C2",style="coroots") sage: chi.branch(C2,rule=b) C2(0,1) + C2(2,0) @@ -95,13 +95,13 @@ Observe that we do not have to build the intermediate :: - sage: C4=WeylCharacterRing("C4",style="coroots") - sage: b1=branching_rule("C4","A3","levi")*branching_rule("A3","C2","symmetric"); b1 + sage: C4 = WeylCharacterRing("C4",style="coroots") + sage: b1 = branching_rule("C4","A3","levi")*branching_rule("A3","C2","symmetric"); b1 composite branching rule C4 => (levi) A3 => (symmetric) C2 - sage: b2=branching_rule("C4","C2xC2","orthogonal_sum")*branching_rule("C2xC2","C2","proj1"); b2 + sage: b2 = branching_rule("C4","C2xC2","orthogonal_sum")*branching_rule("C2xC2","C2","proj1"); b2 composite branching rule C4 => (orthogonal_sum) C2xC2 => (proj1) C2 - sage: C2=WeylCharacterRing("C2",style="coroots") - sage: C4=WeylCharacterRing("C4",style="coroots") + sage: C2 = WeylCharacterRing("C2",style="coroots") + sage: C4 = WeylCharacterRing("C4",style="coroots") sage: [C4(2,0,0,1).branch(C2, rule=br) for br in [b1,b2]] [4*C2(0,0) + 7*C2(0,1) + 15*C2(2,0) + 7*C2(0,2) + 11*C2(2,1) + C2(0,3) + 6*C2(4,0) + 3*C2(2,2), 10*C2(0,0) + 40*C2(1,0) + 50*C2(0,1) + 16*C2(2,0) + 20*C2(1,1) + 4*C2(3,0) + 5*C2(2,1)] @@ -187,7 +187,7 @@ Sage has a database of maximal subgroups for every simple Cartan type of rank `\le 8`. You may access this with the ``maximal_subgroups`` method of the Weyl character ring:: - sage: E7=WeylCharacterRing("E7",style="coroots") + sage: E7 = WeylCharacterRing("E7",style="coroots") sage: E7.maximal_subgroups() A7:branching_rule("E7","A7","extended") E6:branching_rule("E7","E6","levi") @@ -212,7 +212,7 @@ as follows:: sage: b = E7.maximal_subgroup("A2"); b miscellaneous branching rule E7 => A2 - sage: [E7,A2]=[WeylCharacterRing(x,style="coroots") for x in ["E7","A2"]] + sage: E7, A2 = [WeylCharacterRing(x,style="coroots") for x in ["E7","A2"]] sage: E7(1,0,0,0,0,0,0).branch(A2,rule=b) A2(1,1) + A2(4,4) @@ -236,8 +236,8 @@ complete up to inner automorphisms. For example, `E_6` has a nontrivial Dynkin diagram automorphism so it has an outer automorphism that is not inner:: - sage: [E6,A2xG2]=[WeylCharacterRing(x,style="coroots") for x in ["E6","A2xG2"]] - sage: b=E6.maximal_subgroup("A2xG2"); b + sage: E6, A2xG2 = [WeylCharacterRing(x,style="coroots") for x in ["E6","A2xG2"]] + sage: b = E6.maximal_subgroup("A2xG2"); b miscellaneous branching rule E6 => A2xG2 sage: E6(1,0,0,0,0,0).branch(A2xG2,rule=b) A2xG2(0,1,1,0) + A2xG2(2,0,0,0) @@ -250,7 +250,7 @@ the `A_2\times G_2` subgroup is changed to a different one by the outer automorphism. To obtain the second branching rule, we compose the given one with this automorphism:: - sage: b1=branching_rule("E6","E6","automorphic")*b; b1 + sage: b1 = branching_rule("E6","E6","automorphic")*b; b1 composite branching rule E6 => (automorphic) E6 => (miscellaneous) A2xG2 .. _levi_branch_rules: @@ -533,8 +533,8 @@ construct the branching rule to `A_5` we may proceed as follows:: sage: b = branching_rule("E6","A5xA1","extended")*branching_rule("A5xA1","A5","proj1"); b composite branching rule E6 => (extended) A5xA1 => (proj1) A5 - sage: E6=WeylCharacterRing("E6",style="coroots") - sage: A5=WeylCharacterRing("A5",style="coroots") + sage: E6 = WeylCharacterRing("E6",style="coroots") + sage: A5 = WeylCharacterRing("A5",style="coroots") sage: E6(0,1,0,0,0,0).branch(A5,rule=b) 3*A5(0,0,0,0,0) + 2*A5(0,0,1,0,0) + A5(1,0,0,0,1) sage: b.describe() @@ -1006,7 +1006,7 @@ representation of `SO(8)` and the two eight-dimensional spin representations. These are permuted by triality:: - sage: D4=WeylCharacterRing("D4",style="coroots") + sage: D4 = WeylCharacterRing("D4",style="coroots") sage: D4(0,0,0,1).branch(D4,rule="triality") D4(1,0,0,0) sage: D4(0,0,0,1).branch(D4,rule="triality").branch(D4,rule="triality") @@ -1021,4 +1021,3 @@ spin representations, as it always does in type `D`:: D4(0,0,1,0) sage: D4(0,0,1,0).branch(D4,rule="automorphic") D4(0,0,0,1) - diff --git a/src/doc/en/thematic_tutorials/lie/weyl_character_ring.rst b/src/doc/en/thematic_tutorials/lie/weyl_character_ring.rst index a7052507284..ddc33a325a8 100644 --- a/src/doc/en/thematic_tutorials/lie/weyl_character_ring.rst +++ b/src/doc/en/thematic_tutorials/lie/weyl_character_ring.rst @@ -164,7 +164,7 @@ coefficients) through the usual free module accessors:: [((0, 0, 0), 1), ((1, 0, 0), 1), ((1, 1, 0), 1), ((1, 1, 1), 1)] sage: pprint(dict(chi)) {(0, 0, 0): 1, (1, 0, 0): 1, (1, 1, 0): 1, (1, 1, 1): 1} - sage: M = sorted(chi.monomials(), key=lambda x: x.support()); M + sage: M = sorted(chi.monomials(), key=lambda x: tuple(x.support())); M [B3(0,0,0), B3(1,0,0), B3(1,1,0), B3(1,1,1)] sage: sorted(chi.support()) [(0, 0, 0), (1, 0, 0), (1, 1, 0), (1, 1, 1)] @@ -485,7 +485,7 @@ itself, that is, the integral of `|tr(g)|^{10}`:: sage: tr^5 5*A2(2,2,1) + 6*A2(3,1,1) + 5*A2(3,2,0) + 4*A2(4,1,0) + A2(5,0,0) - sage: sorted((tr^5).monomials(), key=lambda x: x.support()) + sage: sorted((tr^5).monomials(), key=lambda x: tuple(x.support())) [A2(2,2,1), A2(3,1,1), A2(3,2,0), A2(4,1,0), A2(5,0,0)] sage: sorted((tr^5).coefficients()) [1, 4, 5, 5, 6] diff --git a/src/doc/en/thematic_tutorials/linear_programming.rst b/src/doc/en/thematic_tutorials/linear_programming.rst index fcb2a131e48..c8bf14c4eb4 100644 --- a/src/doc/en/thematic_tutorials/linear_programming.rst +++ b/src/doc/en/thematic_tutorials/linear_programming.rst @@ -13,7 +13,8 @@ to reformulate an optimization (or existence) problem through linear constraints. This is a translation of a chapter from the book -`Calcul mathematique avec Sage `_. +`Calcul mathematique avec Sage `_. +This book now exists in `English, too `_. Definition ---------- diff --git a/src/doc/en/thematic_tutorials/numerical_sage/cvxopt.rst b/src/doc/en/thematic_tutorials/numerical_sage/cvxopt.rst index 7b365f60ba4..ba6cfb5fb86 100644 --- a/src/doc/en/thematic_tutorials/numerical_sage/cvxopt.rst +++ b/src/doc/en/thematic_tutorials/numerical_sage/cvxopt.rst @@ -1,11 +1,11 @@ Cvxopt ====== -Cvxopt provides many routines for solving convex optimization +``Cvxopt`` provides many routines for solving convex optimization problems such as linear and quadratic programming packages. It also has a very nice sparse matrix library that provides an interface to -umfpack (the same sparse matrix solver that matlab uses), it also -has a nice interface to lapack. For more details on cvxopt please +``umfpack`` (the same sparse matrix solver that ``matlab`` uses), it also +has a nice interface to ``lapack``. For more details on ``cvxopt`` please refer to its documentation at ``_ Sparse matrices are represented in triplet notation that is as a @@ -32,7 +32,7 @@ by sage: from cvxopt.base import spmatrix # optional - cvxopt sage: from cvxopt.base import matrix as m # optional - cvxopt sage: from cvxopt import umfpack # optional - cvxopt - sage: Integer=int # optional - cvxopt + sage: Integer = int # optional - cvxopt sage: V = [2,3, 3,-1,4, 4,-3,1,2, 2, 6,1] # optional - cvxopt sage: I = [0,1, 0, 2,4, 1, 2,3,4, 2, 1,4] # optional - cvxopt sage: J = [0,0, 1, 1,1, 2, 2,2,2, 3, 4,4] # optional - cvxopt @@ -61,7 +61,7 @@ we could do the following. [ 0 -1.00e+00 -3.00e+00 2.00e+00 0 ] [ 0 0 1.00e+00 0 0 ] [ 0 4.00e+00 2.00e+00 0 1.00e+00] - sage: C=m(B) # optional - cvxopt + sage: C = m(B) # optional - cvxopt sage: umfpack.linsolve(A,C) # optional - cvxopt sage: print(C) # optional - cvxopt [ 5.79e-01] @@ -71,7 +71,7 @@ we could do the following. [-7.89e-01] Note the solution is stored in :math:`B` afterward. also note the -m(B), this turns our numpy array into a format cvxopt understands. +m(B), this turns our numpy array into a format ``cvxopt`` understands. You can directly create a cvxopt matrix using cvxopt's own matrix command, but I personally find numpy arrays nicer. Also note we explicitly set the shape of the numpy array to make it clear it was @@ -81,12 +81,12 @@ We could compute the approximate minimum degree ordering by doing :: - sage: RealNumber=float # optional - cvxopt - sage: Integer=int # optional - cvxopt + sage: RealNumber = float # optional - cvxopt + sage: Integer = int # optional - cvxopt sage: from cvxopt.base import spmatrix # optional - cvxopt sage: from cvxopt import amd # optional - cvxopt - sage: A=spmatrix([10,3,5,-2,5,2],[0,2,1,2,2,3],[0,0,1,1,2,3]) # optional - cvxopt - sage: P=amd.order(A) # optional - cvxopt + sage: A = spmatrix([10,3,5,-2,5,2],[0,2,1,2,2,3],[0,0,1,1,2,3]) # optional - cvxopt + sage: P = amd.order(A) # optional - cvxopt sage: print(P) # optional - cvxopt [ 1] [ 0] @@ -108,8 +108,8 @@ For a simple linear programming example, if we want to solve :: - sage: RealNumber=float # optional - cvxopt - sage: Integer=int # optional - cvxopt + sage: RealNumber = float # optional - cvxopt + sage: Integer = int # optional - cvxopt sage: from cvxopt.base import matrix as m # optional - cvxopt sage: from cvxopt import solvers # optional - cvxopt sage: c = m([-4., -5.]) # optional - cvxopt @@ -130,4 +130,3 @@ For a simple linear programming example, if we want to solve sage: print(sol['x']) # optional - cvxopt # ... below since can get -00 or +00 depending on architecture [ 1.00e...00] [ 1.00e+00] - diff --git a/src/doc/en/thematic_tutorials/numerical_sage/numpy.rst b/src/doc/en/thematic_tutorials/numerical_sage/numpy.rst index 6e2bdbfb0c6..dbc2de71d42 100644 --- a/src/doc/en/thematic_tutorials/numerical_sage/numpy.rst +++ b/src/doc/en/thematic_tutorials/numerical_sage/numpy.rst @@ -15,7 +15,7 @@ create an array. :: - sage: l=numpy.array([1,2,3]) + sage: l = numpy.array([1,2,3]) sage: l array([1, 2, 3]) @@ -34,10 +34,10 @@ hardware float or int. :: - sage: l=numpy.array([2**40, 3**40, 4**40]) + sage: l = numpy.array([2**40, 3**40, 4**40]) sage: l array([1099511627776, 12157665459056928801, 1208925819614629174706176], dtype=object) - sage: a=2.0000000000000000001 + sage: a = 2.0000000000000000001 sage: a.prec() # higher precision than hardware floating point numbers 67 sage: numpy.array([a,2*a,3*a]) @@ -55,7 +55,7 @@ can be operated on much faster. :: - sage: l=numpy.array([1.0, 2.0, 3.0]) + sage: l = numpy.array([1.0, 2.0, 3.0]) sage: l.dtype dtype('float64') @@ -68,7 +68,7 @@ an array. :: - sage: l=numpy.array([1,2,3], dtype=float) + sage: l = numpy.array([1,2,3], dtype=float) sage: l.dtype dtype('float64') @@ -80,7 +80,7 @@ well as take slices :: - sage: l=numpy.array(range(10),dtype=float) + sage: l = numpy.array(range(10),dtype=float) sage: l[3] 3.0 sage: l[3:6] @@ -128,7 +128,7 @@ This is basically equivalent to the following :: - sage: m=numpy.matrix([[1,2],[3,4]]) + sage: m = numpy.matrix([[1,2],[3,4]]) sage: m matrix([[1, 2], [3, 4]]) @@ -184,8 +184,8 @@ NumPy arrays can be sliced as well :: - sage: n=numpy.array(range(25),dtype=float) - sage: n.shape=(5,5) + sage: n = numpy.array(range(25),dtype=float) + sage: n.shape = (5,5) sage: n[2:4,1:3] array([[11., 12.], [16., 17.]]) @@ -197,8 +197,8 @@ the original :: - sage: m=n[2:4,1:3] - sage: m[0,0]=100 + sage: m = n[2:4,1:3] + sage: m[0,0] = 100 sage: n array([[ 0., 1., 2., 3., 4.], [ 5., 6., 7., 8., 9.], @@ -222,7 +222,7 @@ Some particularly useful commands are :: - sage: x=numpy.arange(0,2,.1,dtype=float) + sage: x = numpy.arange(0,2,.1,dtype=float) sage: x array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1. , 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9]) @@ -235,13 +235,13 @@ from 0 to 2. There is a useful command :meth:`numpy.r_` that is best explained b :: sage: from numpy import r_ - sage: j=complex(0,1) - sage: RealNumber=float - sage: Integer=int - sage: n=r_[0.0:5.0] + sage: j = complex(0,1) + sage: RealNumber = float + sage: Integer = int + sage: n = r_[0.0:5.0] sage: n array([0., 1., 2., 3., 4.]) - sage: n=r_[0.0:5.0, [0.0]*5] + sage: n = r_[0.0:5.0, [0.0]*5] sage: n array([0., 1., 2., 3., 4., 0., 0., 0., 0., 0.]) @@ -267,7 +267,7 @@ arrays. We can combine all of these techniques :: - sage: n=r_[0.0:5.0:11*j,int(5)*[0.0],-5.0:0.0] + sage: n = r_[0.0:5.0:11*j,int(5)*[0.0],-5.0:0.0] sage: n array([ 0. , 0.5, 1. , 1.5, 2. , 2.5, 3. , 3.5, 4. , 4.5, 5. , 0. , 0. , 0. , 0. , 0. , -5. , -4. , -3. , -2. , -1. ]) @@ -280,13 +280,13 @@ an equally spaced grid with `\Delta x = \Delta y = .25` for :: sage: import numpy - sage: j=complex(0,1) + sage: j = complex(0,1) sage: def f(x,y): ....: return x**2+y**2 sage: from numpy import meshgrid - sage: x=numpy.r_[0.0:1.0:5*j] - sage: y=numpy.r_[0.0:1.0:5*j] - sage: xx,yy= meshgrid(x,y) + sage: x = numpy.r_[0.0:1.0:5*j] + sage: y = numpy.r_[0.0:1.0:5*j] + sage: xx,yy = meshgrid(x,y) sage: xx array([[0. , 0.25, 0.5 , 0.75, 1. ], [0. , 0.25, 0.5 , 0.75, 1. ], @@ -321,9 +321,9 @@ equation `Ax=b` do sage: import numpy sage: from numpy import linalg - sage: A=numpy.random.randn(5,5) - sage: b=numpy.array(range(1,6)) - sage: x=linalg.solve(A,b) + sage: A = numpy.random.randn(5,5) + sage: b = numpy.array(range(1,6)) + sage: x = linalg.solve(A,b) sage: numpy.dot(A,x) array([1., 2., 3., 4., 5.]) diff --git a/src/doc/en/thematic_tutorials/numerical_sage/scipy.rst b/src/doc/en/thematic_tutorials/numerical_sage/scipy.rst index 8d3e788d87a..1cab99dda26 100644 --- a/src/doc/en/thematic_tutorials/numerical_sage/scipy.rst +++ b/src/doc/en/thematic_tutorials/numerical_sage/scipy.rst @@ -95,8 +95,8 @@ code. ....: return[y[1],-y[0]-10*y[1]*(y[0]**2-1)] sage: def j_1(y,t): ....: return [ [0, 1.0],[-2.0*10*y[0]*y[1]-1.0,-10*(y[0]*y[0]-1.0)] ] - sage: x= numpy.arange(0,100,.1) - sage: y=integrate.odeint(f_1,[1,0],x,Dfun=j_1) + sage: x = numpy.arange(0,100,.1) + sage: y = integrate.odeint(f_1,[1,0],x,Dfun=j_1) We could plot the solution if we wanted by doing diff --git a/src/doc/en/thematic_tutorials/numtheory_rsa.rst b/src/doc/en/thematic_tutorials/numtheory_rsa.rst index f321af0ddcd..90a6d9ab675 100644 --- a/src/doc/en/thematic_tutorials/numtheory_rsa.rst +++ b/src/doc/en/thematic_tutorials/numtheory_rsa.rst @@ -429,8 +429,8 @@ given above, we would get an error message similar to the following:: mod(m^e, n) Traceback (most recent call last) - /home/mvngu/ in () - /home/mvngu/usr/bin/sage-3.1.4/local/lib/python2.5/site-packages/sage/rings/integer.so + ... in () + .../sage/rings/integer.so in sage.rings.integer.Integer.__pow__ (sage/rings/integer.c:9650)() RuntimeError: exponent must be at most 2147483647 diff --git a/src/doc/en/thematic_tutorials/steenrod_algebra_modules.rst b/src/doc/en/thematic_tutorials/steenrod_algebra_modules.rst index 6d6513af258..f038ff7acaa 100644 --- a/src/doc/en/thematic_tutorials/steenrod_algebra_modules.rst +++ b/src/doc/en/thematic_tutorials/steenrod_algebra_modules.rst @@ -92,7 +92,7 @@ The generators are themselves elements of the module:: One can produce an element from a given set of algebra coefficients:: - sage: coeffs=[Sq(15), Sq(10)*Sq(1,1), Sq(8)] + sage: coeffs = [Sq(15), Sq(10)*Sq(1,1), Sq(8)] sage: x = M(coeffs); x Sq(15)*g[0] + (Sq(4,1,1)+Sq(7,0,1)+Sq(11,1))*g[1] + Sq(8)*g[7] @@ -772,4 +772,3 @@ re-ordering of list elements), so the following comparison is reassuring:: True sage: flift[3] == lifts[1] True - diff --git a/src/doc/en/tutorial/introduction.rst b/src/doc/en/tutorial/introduction.rst index c9683df4d78..10ebef9f5d6 100644 --- a/src/doc/en/tutorial/introduction.rst +++ b/src/doc/en/tutorial/introduction.rst @@ -89,9 +89,9 @@ computer. Here we merely make a few comments. will search. The documentation for using SageTeX is located in - ``$SAGE_ROOT/local/share/texmf/tex/latex/sagetex/``, where + ``$SAGE_ROOT/venv/share/texmf/tex/latex/sagetex/``, where "``$SAGE_ROOT``" refers to the directory where you installed Sage -- - for example, ``/opt/sage-4.2.1``. + for example, ``/opt/sage-9.6``. Ways to Use Sage ================ diff --git a/src/doc/en/tutorial/sagetex.rst b/src/doc/en/tutorial/sagetex.rst index 0fdc74b0640..17460be0b49 100644 --- a/src/doc/en/tutorial/sagetex.rst +++ b/src/doc/en/tutorial/sagetex.rst @@ -12,10 +12,10 @@ An example ---------- Here is a very brief example of using SageTeX. The full documentation -can be found in ``SAGE_ROOT/local/share/doc/sagetex``, +can be found in ``SAGE_ROOT/venv/share/doc/sagetex``, where ``SAGE_ROOT`` is the directory where your Sage installation is located. That directory contains the documentation and an example file. -See ``SAGE_ROOT/local/share/texmf/tex/latex/sagetex`` for +See ``SAGE_ROOT/venv/share/texmf/tex/latex/sagetex`` for some possibly useful Python scripts. To see how SageTeX works, follow the directions for installing SageTeX (in @@ -105,7 +105,7 @@ commands in your document. There's a lot more to SageTeX, and since both Sage and LaTeX are complex, powerful tools, it's a good idea to read the documentation for SageTeX, which is in -``SAGE_ROOT/local/share/doc/sagetex``. +``SAGE_ROOT/venv/share/doc/sagetex``. .. _sec-sagetex_install: @@ -122,7 +122,7 @@ installation aware of it before it will work. The key to this is that TeX needs to be able to find ``sagetex.sty``, which can be found in -``SAGE_ROOT/local/share/texmf/tex/latex/sagetex/``, where +``SAGE_ROOT/venv/share/texmf/tex/latex/sagetex/``, where ``SAGE_ROOT`` is the directory where you built or installed Sage. If TeX can find ``sagetex.sty``, then SageTeX will work. There are several ways to accomplish this. @@ -142,7 +142,7 @@ ways to accomplish this. .. CODE-BLOCK:: shell-session - $ export TEXINPUTS="SAGE_ROOT/local/share/texmf//:" + $ export TEXINPUTS="SAGE_ROOT/venv/share/texmf//:" where ``SAGE_ROOT`` is the location of your Sage installation. Note that the double slash and colon at the end of that line are important. @@ -173,12 +173,12 @@ ways to accomplish this. which will print out a directory, such as ``/home/drake/texmf`` or ``/Users/drake/Library/texmf``. Copy the ``tex/`` directory from - ``SAGE_ROOT/local/share/texmf/`` into your home ``texmf`` directory + ``SAGE_ROOT/venv/share/texmf/`` into your home ``texmf`` directory with a command like .. CODE-BLOCK:: shell-session - $ cp -R SAGE_ROOT/local/share/texmf/tex TEXMFHOME + $ cp -R SAGE_ROOT/venv/share/texmf/tex TEXMFHOME where ``SAGE_ROOT`` is, as usual, replaced with the location of your Sage installation and ``TEXMFHOME`` is the result of the @@ -229,7 +229,7 @@ SageTeX documentation While not strictly part of installation, it bears mentioning here that the documentation for SageTeX is maintained in -``SAGE_ROOT/local/share/doc/sagetex/sagetex.pdf``. There is also an +``SAGE_ROOT/venv/share/doc/sagetex/sagetex.pdf``. There is also an example file in the same directory -- see ``example.tex`` and ``example.pdf``, the pre-built result of typesetting that file with LaTeX and Sage. You can also get those files from the `SageTeX page `_. diff --git a/src/doc/es/tutorial/introduction.rst b/src/doc/es/tutorial/introduction.rst index 7eb55a8d740..fdd811d7f96 100644 --- a/src/doc/es/tutorial/introduction.rst +++ b/src/doc/es/tutorial/introduction.rst @@ -90,9 +90,9 @@ Sage en tu computador. Aquí hacemos simplemente dos comentarios: en un directorio en el que TeX va a buscar. La documentación para usar SageTeX se encuentra en - ``$SAGE_ROOT/local/share/texmf/tex/latex/sagetex/``, donde + ``$SAGE_ROOT/venv/share/texmf/tex/latex/sagetex/``, donde "``$SAGE_ROOT``" se refiere al directorio donde Sage está instalado -- - por ejemplo, ``/opt/sage-4.2.1``. + por ejemplo, ``/opt/sage-9.6``. Formas de usar Sage diff --git a/src/doc/fr/tutorial/introduction.rst b/src/doc/fr/tutorial/introduction.rst index 875fefbeaa1..6edb2362061 100644 --- a/src/doc/fr/tutorial/introduction.rst +++ b/src/doc/fr/tutorial/introduction.rst @@ -96,9 +96,9 @@ Nous nous limiterons ici à quelques remarques. d'environnement. La documentation de SageTeX se trouve dans le répertoire - ``$SAGE_ROOT/local/share/texmf/tex/latex/sagetex/``, où + ``$SAGE_ROOT/venv/share/texmf/tex/latex/sagetex/``, où "``$SAGE_ROOT``" est le répertoire où vous avez installé Sage, par - exemple ``/opt/sage-4.3.4``. + exemple ``/opt/sage-9.6``. Les différentes manières d'utiliser Sage ======================================== diff --git a/src/doc/fr/tutorial/sagetex.rst b/src/doc/fr/tutorial/sagetex.rst index 9aa52f3759a..b17f0ad56f7 100644 --- a/src/doc/fr/tutorial/sagetex.rst +++ b/src/doc/fr/tutorial/sagetex.rst @@ -16,7 +16,7 @@ locale) pour plus de détails. Voici un bref exemple d'utilisation de SageTeX. La documentation complète se trouve dans -``SAGE_ROOT/local/share/texmf/tex/latex/sagetex``, où ``SAGE_ROOT`` +``SAGE_ROOT/venv/share/texmf/tex/latex/sagetex``, où ``SAGE_ROOT`` désigne le répertoire racine de votre installation Sage. Elle est accompagnée d'un fichier exemple et de scripts Python potentiellement utiles. @@ -114,4 +114,4 @@ compilation précédente.) SageTeX offre bien d'autres possibilités. Puisque Sage comme LaTeX sont des outils complexes et puissants, le mieux est sans doute de consulter la documentation complète de SageTeX, qui se trouve -dans ``SAGE_ROOT/local/share/texmf/tex/latex/sagetex``. +dans ``SAGE_ROOT/venv/share/texmf/tex/latex/sagetex``. diff --git a/src/doc/ja/tutorial/introduction.rst b/src/doc/ja/tutorial/introduction.rst index 2c4ce235f6a..79d90c9d5c1 100644 --- a/src/doc/ja/tutorial/introduction.rst +++ b/src/doc/ja/tutorial/introduction.rst @@ -73,8 +73,8 @@ Sageを自分のコンピュータへインストールする手順について SageTeXの利用に関する解説は -``$SAGE_ROOT/local/share/texmf/tex/latex/sagetex/`` にある. -``$SAGE_ROOT`` はSageがインストールされているディレクトリで,例えば ``/opt/sage-4.2.1`` などとなっているはずだ. +``$SAGE_ROOT/venv/share/texmf/tex/latex/sagetex/`` にある. +``$SAGE_ROOT`` はSageがインストールされているディレクトリで,例えば ``/opt/sage-9.6`` などとなっているはずだ. diff --git a/src/doc/ja/tutorial/sagetex.rst b/src/doc/ja/tutorial/sagetex.rst index a6e2b40b5b2..dbbdc3ff7bd 100644 --- a/src/doc/ja/tutorial/sagetex.rst +++ b/src/doc/ja/tutorial/sagetex.rst @@ -12,8 +12,8 @@ SageTeXパッケージを使うと,Sageによる処理結果をLaTeX文書に ======= ここでは,ごく簡単な例題を通してSageTeXの利用手順を紹介する. -完全な解説ドキュメントと例題ファイルは,ディレクトリ ``SAGE_ROOT/local/share/doc/sagetex`` に置いてある. -``SAGE_ROOT/local/share/texmf/tex/latex/sagetex`` にあるPythonスクリプトは何か役に立つ場面があるはずだ. +完全な解説ドキュメントと例題ファイルは,ディレクトリ ``SAGE_ROOT/venv/share/doc/sagetex`` に置いてある. +``SAGE_ROOT/venv/share/texmf/tex/latex/sagetex`` にあるPythonスクリプトは何か役に立つ場面があるはずだ. 以上の ``SAGE_ROOT`` は,Sageをインストールしたディレクトリである. @@ -98,7 +98,7 @@ SageTeXの動作を体験するために,まずSageTeXのインストール手 SageTeXは到底以上で語り尽せるものでなく,SageとLaTeXは共に複雑で強力なツールだ. -``SAGE_ROOT/local/share/doc/sagetex`` にあるSageTeXのドキュメントを読むことを強くお勧めする. +``SAGE_ROOT/venv/share/doc/sagetex`` にあるSageTeXのドキュメントを読むことを強くお勧めする. .. _sec-sagetex_install: @@ -118,7 +118,7 @@ SageTeXはデフォルトでSageにインストールされるが,LaTeX文書 鍵になるのは, TeXが ``sagetex.sty`` を発見できるかどうかである. この ``sagetex.sty`` は, ``SAGE_ROOT`` をSageがビルトあるいはインストールされたディレクトリとすると, -``SAGE_ROOT/local/share/texmf/tex/latex/sagetex/`` に置かれているはずだ. +``SAGE_ROOT/venv/share/texmf/tex/latex/sagetex/`` に置かれているはずだ. TeXが ``sagetex.sty`` を読めるようにしてやらなければ,SageTeXも動作できないのである. これを実現するには何通りかのやり方がある. @@ -137,7 +137,7 @@ TeXが ``sagetex.sty`` を読めるようにしてやらなければ,SageTeX :: - export TEXINPUTS="SAGE_ROOT/local/share/texmf//:" + export TEXINPUTS="SAGE_ROOT/venv/share/texmf//:" と実行すればよい.ただし ``SAGE_ROOT`` はSageのインストール先ディレクトリである. 上の実行例では,行末にスラッシュ2個とコロンを付け忘れないでいただきたい. @@ -163,11 +163,11 @@ TeXが ``sagetex.sty`` を読めるようにしてやらなければ,SageTeX kpsewhich -var-value=TEXMFHOME - を実行する.すると ``/home/drake/texmf`` や ``/Users/drake/Library/texmf`` などと表示されるはずだから, ``SAGE_ROOT/local/share/texmf/`` 内の ``tex/`` ディレクトリをホームディレクトリの ``texmf`` にコピーするには + を実行する.すると ``/home/drake/texmf`` や ``/Users/drake/Library/texmf`` などと表示されるはずだから, ``SAGE_ROOT/venv/share/texmf/`` 内の ``tex/`` ディレクトリをホームディレクトリの ``texmf`` にコピーするには :: - cp -R SAGE_ROOT/local/share/texmf/tex TEXMFHOME + cp -R SAGE_ROOT/venv/share/texmf/tex TEXMFHOME などとする. もちろん, ``SAGE_ROOT`` を実際にSageをインストールしたディレクトリとするのはこれまでと同じことで, ``TEXMFHOME`` は上で見た ``kpsewhich`` コマンドの結果で置き換える. @@ -210,7 +210,7 @@ SageTeXドキュメント --------------------- 厳密にはSageのインストール一式には含まれないものの,ここで -SageTeXのドキュメントが ``SAGE_ROOT/local/share/doc/sagetex/sagetex.pdf`` に配置されていることに触れておきたい. +SageTeXのドキュメントが ``SAGE_ROOT/venv/share/doc/sagetex/sagetex.pdf`` に配置されていることに触れておきたい. 同じディレクトリには例題ファイルと,これをLaTeXとSageTeXによってすでに組版処理した結果も用意されている(``example.tex`` と ``example.pdf`` を参照). これらのファイルは `SageTeX ページ `_ からダンロードすることもできる. diff --git a/src/doc/pt/tutorial/introduction.rst b/src/doc/pt/tutorial/introduction.rst index 0a821b3c099..b0b2fac6e1b 100644 --- a/src/doc/pt/tutorial/introduction.rst +++ b/src/doc/pt/tutorial/introduction.rst @@ -90,9 +90,9 @@ computador. Aqui faremos apenas alguns comentários. encontrá-lo. A documentação para usar o SageTex está disponível em - ``$SAGE_ROOT/local/share/texmf/tex/latex/sagetex/``, onde + ``$SAGE_ROOT/venv/share/texmf/tex/latex/sagetex/``, onde ``$SAGE_ROOT`` refere-se ao diretório onde você instalou o Sage - -- por exemplo, ``/opt/sage-4.2.1``. + -- por exemplo, ``/opt/sage-9.6``. Formas de usar o Sage ===================== diff --git a/src/doc/pt/tutorial/sagetex.rst b/src/doc/pt/tutorial/sagetex.rst index 949b9b3e6db..8b0c47b6891 100644 --- a/src/doc/pt/tutorial/sagetex.rst +++ b/src/doc/pt/tutorial/sagetex.rst @@ -14,7 +14,7 @@ instalação do Sage `_ Aqui vai um breve exemplo de como usar o SageTeX. A documentação completa pode ser encontrada em -``SAGE_ROOT/local/share/texmf/tex/latex/sagetex``, onde +``SAGE_ROOT/venv/share/texmf/tex/latex/sagetex``, onde ``SAGE_ROOT`` é o diretório onde se encontra a sua instalação. Esse diretório contém a documentação, um arquivo de exemplo, e alguns scripts em Python possivelmente úteis. @@ -107,4 +107,4 @@ os comandos em Sage em seu documento. Há muito mais sobre o SageTeX, e como tanto o Sage como o LaTeX são ferramentas complexas e poderosas, é uma boa idéia ler a documentação para o SageTeX que se encontra em -``SAGE_ROOT/local/share/texmf/tex/latex/sagetex``. +``SAGE_ROOT/venv/share/texmf/tex/latex/sagetex``. diff --git a/src/doc/ru/tutorial/introduction.rst b/src/doc/ru/tutorial/introduction.rst index 3f62905cee9..445d17fb505 100644 --- a/src/doc/ru/tutorial/introduction.rst +++ b/src/doc/ru/tutorial/introduction.rst @@ -88,9 +88,9 @@ Sage в разделе документации: [SA]_ Здесь мы прив всего лишь скопировать один файл в директорию поиска TeX. Документация по использованию SageTeX находится в - ``$SAGE_ROOT/local/share/texmf/tex/latex/sagetex/``, где + ``$SAGE_ROOT/venv/share/texmf/tex/latex/sagetex/``, где "``$SAGE_ROOT``" соответствует директории, где установлен сам Sage, - например, ``/opt/sage-4.2.1``. + например, ``/opt/sage-9.6``. Работа в Sage ================ diff --git a/src/doc/ru/tutorial/sagetex.rst b/src/doc/ru/tutorial/sagetex.rst index d23ebc7fb31..8c930e2de2a 100644 --- a/src/doc/ru/tutorial/sagetex.rst +++ b/src/doc/ru/tutorial/sagetex.rst @@ -12,7 +12,7 @@ SageTeX known to TeX" `Руководства по установке Sage по установке). В этом уроке показан небольшой пример использования SageTeX. Полная документация -находится в ``SAGE_ROOT/local/share/texmf/tex/latex/sagetex``, где +находится в ``SAGE_ROOT/venv/share/texmf/tex/latex/sagetex``, где ``SAGE_ROOT`` - это директория, в которой установлен Sage. Эта папка содержит документацию, файл с примером и полезные скрипты Python. @@ -89,4 +89,4 @@ SageTeX known to TeX" `Руководства по установке Sage SageTeX предлагает много возможностей, и так как Sage и LaTeX являются мощными инструментами, то стоит изучить -``SAGE_ROOT/local/share/texmf/tex/latex/sagetex``. +``SAGE_ROOT/venv/share/texmf/tex/latex/sagetex``. diff --git a/src/sage/algebras/affine_nil_temperley_lieb.py b/src/sage/algebras/affine_nil_temperley_lieb.py index 0a35c89d753..492043ddf7d 100644 --- a/src/sage/algebras/affine_nil_temperley_lieb.py +++ b/src/sage/algebras/affine_nil_temperley_lieb.py @@ -41,9 +41,9 @@ class AffineNilTemperleyLiebTypeA(CombinatorialFreeModule): 2*a0 + 1 + 3*a1 + a0*a1*a2*a3 """ - def __init__(self, n, R = ZZ, prefix = 'a'): + def __init__(self, n, R=ZZ, prefix='a'): """ - Initiates the affine nilTemperley Lieb algebra over the ring `R`. + Initiate the affine nilTemperley Lieb algebra over the ring `R`. EXAMPLES:: diff --git a/src/sage/algebras/catalog.py b/src/sage/algebras/catalog.py index 4a6ee3b6985..cce33d64029 100644 --- a/src/sage/algebras/catalog.py +++ b/src/sage/algebras/catalog.py @@ -18,6 +18,8 @@ - :class:`algebras.Brauer ` - :class:`algebras.Clifford ` - :class:`algebras.ClusterAlgebra ` +- :class:`algebras.CubicHecke + ` - :class:`algebras.Descent ` - :class:`algebras.DifferentialWeyl ` @@ -58,6 +60,8 @@ - :class:`algebras.QSym ` - :class:`algebras.Partition ` - :class:`algebras.PlanarPartition ` +- :class:`algebras.qCommutingPolynomials + ` - :class:`algebras.QuantumGroup ` - :func:`algebras.Quaternion @@ -69,11 +73,11 @@ - :class:`algebras.Steenrod ` - :class:`algebras.TemperleyLieb ` +- :class:`algebras.Tensor ` - :class:`algebras.WQSym ` - :class:`algebras.Yangian ` - :class:`algebras.YokonumaHecke ` -- :class:`algebras.Tensor ` """ from sage.algebras.free_algebra import FreeAlgebra as Free @@ -99,6 +103,7 @@ lazy_import('sage.algebras.schur_algebra', 'SchurAlgebra', 'Schur') lazy_import('sage.algebras.commutative_dga', 'GradedCommutativeAlgebra', 'GradedCommutative') lazy_import('sage.algebras.hecke_algebras.ariki_koike_algebra', 'ArikiKoikeAlgebra', 'ArikiKoike') +lazy_import('sage.algebras.hecke_algebras.cubic_hecke_algebra', 'CubicHeckeAlgebra', 'CubicHecke') lazy_import('sage.algebras.rational_cherednik_algebra', 'RationalCherednikAlgebra', 'RationalCherednik') lazy_import('sage.algebras.yokonuma_hecke_algebra', 'YokonumaHeckeAlgebra', 'YokonumaHecke') lazy_import('sage.combinat.posets.incidence_algebras', 'IncidenceAlgebra', 'Incidence') @@ -121,9 +126,12 @@ lazy_import('sage.algebras.quantum_matrix_coordinate_algebra', 'QuantumMatrixCoordinateAlgebra', 'QuantumMatrixCoordinate') lazy_import('sage.algebras.quantum_matrix_coordinate_algebra', 'QuantumGL') +lazy_import('sage.algebras.q_commuting_polynomials', 'qCommutingPolynomials') lazy_import('sage.algebras.tensor_algebra', 'TensorAlgebra', 'Tensor') lazy_import('sage.algebras.quantum_groups.quantum_group_gap', 'QuantumGroup') lazy_import('sage.algebras.quantum_groups.ace_quantum_onsager', 'ACEQuantumOnsagerAlgebra', 'AlternatingCentralExtensionQuantumOnsager') lazy_import('sage.algebras.yangian', 'Yangian') + del lazy_import # We remove the object from here so it doesn't appear under tab completion + diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index 0c583b04f6c..8fc5d750dd6 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -1,14 +1,15 @@ -# -*- coding: utf-8 -*- r""" Clifford Algebras AUTHORS: - Travis Scrimshaw (2013-09-06): Initial version +- Trevor K. Karn (2022-07-27): Rewrite basis indexing using FrozenBitset """ #***************************************************************************** -# Copyright (C) 2013 Travis Scrimshaw +# Copyright (C) 2013-2022 Travis Scrimshaw +# (C) 2022 Trevor Karn # # 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 @@ -19,305 +20,246 @@ from sage.misc.cachefunc import cached_method from sage.structure.unique_representation import UniqueRepresentation -from copy import copy +from sage.structure.parent import Parent +from sage.structure.element import Element +from sage.structure.richcmp import (richcmp_method, op_EQ, op_NE, + op_LT, op_GT, op_LE, op_GE, rich_to_bool) +from sage.data_structures.bitset import Bitset, FrozenBitset +from sage.algebras.clifford_algebra_element import CliffordAlgebraElement, ExteriorAlgebraElement from sage.categories.algebras_with_basis import AlgebrasWithBasis from sage.categories.hopf_algebras_with_basis import HopfAlgebrasWithBasis +from sage.categories.fields import Fields +from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets from sage.modules.with_basis.morphism import ModuleMorphismByLinearity from sage.categories.poor_man_map import PoorManMap from sage.rings.integer_ring import ZZ +from sage.rings.noncommutative_ideals import Ideal_nc from sage.modules.free_module import FreeModule, FreeModule_generic from sage.matrix.constructor import Matrix from sage.matrix.args import MatrixArgs from sage.sets.family import Family from sage.combinat.free_module import CombinatorialFreeModule -from sage.combinat.subset import SubsetsSorted from sage.quadratic_forms.quadratic_form import QuadraticForm -from sage.algebras.weyl_algebra import repr_from_monomials from sage.misc.inherit_comparison import InheritComparisonClasscallMetaclass from sage.typeset.ascii_art import ascii_art from sage.typeset.unicode_art import unicode_art import unicodedata -class CliffordAlgebraElement(CombinatorialFreeModule.Element): - """ - An element in a Clifford algebra. - - TESTS:: - - sage: Q = QuadraticForm(ZZ, 3, [1, 2, 3, 4, 5, 6]) - sage: Cl. = CliffordAlgebra(Q) - sage: elt = ((x^3-z)*x + y)^2 - sage: TestSuite(elt).run() +class CliffordAlgebraIndices(UniqueRepresentation, Parent): + r""" + A facade parent for the indices of Clifford algebra. + Users should not create instances of this class directly. """ - def _repr_(self): - """ - Return a string representation of ``self``. - - TESTS:: - - sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) - sage: Cl. = CliffordAlgebra(Q) - sage: ((x^3-z)*x + y)^2 - -2*x*y*z - x*z + 5*x - 4*y + 2*z + 2 - sage: Cl.zero() - 0 - """ - return repr_from_monomials(self.list(), self.parent()._repr_term) - - def _latex_(self): + def __init__(self, Qdim): r""" - Return a `\LaTeX` representation of ``self``. - - TESTS:: - - sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) - sage: Cl. = CliffordAlgebra(Q) - sage: latex( ((x^3-z)*x + y)^2 ) - -2 x y z - x z + 5 x - 4 y + 2 z + 2 - sage: Cl. = CliffordAlgebra(Q) - sage: latex( (x1 - x2)*x0 + 5*x0*x1*x2 ) - 5 x_{0} x_{1} x_{2} - x_{0} x_{1} + x_{0} x_{2} - 1 - """ - return repr_from_monomials(self.list(), self.parent()._latex_term, True) - - def _mul_(self, other): - """ - Return ``self`` multiplied by ``other``. - - INPUT: - - - ``other`` -- element of the same Clifford algebra as ``self`` + Initialize ``self``. EXAMPLES:: - sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) - sage: Cl. = CliffordAlgebra(Q) - sage: (x^3 - z*y)*x*(y*z + x*y*z) - x*y*z + y*z - 24*x + 12*y + 2*z - 24 - sage: y*x - -x*y + 2 - sage: z*x - -x*z + 3 - sage: z*z - 6 - sage: x*0 - 0 - sage: 0*x - 0 - """ - Q = self.parent()._quadratic_form - zero = self.parent().base_ring().zero() - d = {} + sage: from sage.algebras.clifford_algebra import CliffordAlgebraIndices + sage: idx = CliffordAlgebraIndices(7) + sage: idx._nbits + 7 + sage: idx._cardinality + 128 + sage: i = idx.an_element(); i + 1111 + sage: type(i) + + """ + self._nbits = Qdim + self._cardinality = 2 ** Qdim + # the if statement here is in case Qdim is 0. + category = FiniteEnumeratedSets().Facade() + Parent.__init__(self, category=category, facade=True) - for ml,cl in self: - # Distribute the current term ``cl`` * ``ml`` over ``other``. - cur = copy(other._monomial_coefficients) # The current distribution of the term - for i in reversed(ml): - # Distribute the current factor ``e[i]`` (the ``i``-th - # element of the standard basis). - next = {} - # At the end of the following for-loop, ``next`` will be - # the dictionary describing the element - # ``e[i]`` * (the element described by the dictionary ``cur``) - # (where ``e[i]`` is the ``i``-th standard basis vector). - for mr,cr in cur.items(): - # Commute the factor as necessary until we are in order - pos = 0 - for j in mr: - if i <= j: - break - # Add the additional term from the commutation - t = list(mr) - t.pop(pos) - t = tuple(t) - next[t] = next.get(t, zero) + cr * Q[i,j] - # Note: ``Q[i,j] == Q(e[i]+e[j]) - Q(e[i]) - Q(e[j])`` for - # ``i != j``, where ``e[k]`` is the ``k``-th standard - # basis vector. - cr = -cr - if next[t] == zero: - del next[t] - pos += 1 - - # Check to see if we have a squared term or not - t = list(mr) - if i in t: - t.remove(i) - cr *= Q[i,i] - # Note: ``Q[i,i] == Q(e[i])`` where ``e[i]`` is the - # ``i``-th standard basis vector. - else: - t.insert(pos, i) - # Note that ``t`` is now sorted. - t = tuple(t) - next[t] = next.get(t, zero) + cr - if next[t] == zero: - del next[t] - cur = next - - # Add the distributed terms to the total - for index,coeff in cur.items(): - d[index] = d.get(index, zero) + cl * coeff - if d[index] == zero: - del d[index] - - return self.__class__(self.parent(), d) - - def list(self): - """ - Return the list of monomials and their coefficients in ``self`` - (as a list of `2`-tuples, each of which has the form - ``(monomial, coefficient)``). + def _element_constructor_(self, x): + r""" + Construct an element of ``self``. EXAMPLES:: - sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) - sage: Cl. = CliffordAlgebra(Q) - sage: elt = 5*x + y - sage: elt.list() - [((0,), 5), ((1,), 1)] - """ - return sorted(self._monomial_coefficients.items(), key=lambda m_c : (-len(m_c[0]), m_c[0])) - - def support(self): - """ - Return the support of ``self``. - - This is the list of all monomials which appear with nonzero - coefficient in ``self``. - + sage: from sage.algebras.clifford_algebra import CliffordAlgebraIndices + sage: idx = CliffordAlgebraIndices(7) + sage: idx([1,3,6]) + 0101001 + sage: for i in range(7): print(idx(i)) + 1 + 01 + 001 + 0001 + 00001 + 000001 + 0000001 + """ + if isinstance(x, (list, tuple, set, frozenset)): + if len(x) > self._nbits: + raise ValueError(f"{x=} is too long") + return FrozenBitset(x) + + if isinstance(x, int): + return FrozenBitset((x,)) + + def __call__(self, el): + r""" EXAMPLES:: - sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) - sage: Cl. = CliffordAlgebra(Q) - sage: elt = 5*x + y - sage: elt.support() - [(0,), (1,)] + sage: from sage.algebras.clifford_algebra import CliffordAlgebraIndices + sage: idx = CliffordAlgebraIndices(7) + sage: idx([1,3,6]) + 0101001 + sage: E = ExteriorAlgebra(QQ, 7) + sage: B = E.basis() """ - return sorted(self._monomial_coefficients.keys(), key=lambda x: (-len(x), x)) + if not isinstance(el, Element): + return self._element_constructor_(el) + else: + return Parent.__call__(self, el) - def reflection(self): + def cardinality(self): r""" - Return the image of the reflection automorphism on ``self``. - - The *reflection automorphism* of a Clifford algebra is defined - as the linear endomorphism of this algebra which maps - - .. MATH:: - - x_1 \wedge x_2 \wedge \cdots \wedge x_m \mapsto - (-1)^m x_1 \wedge x_2 \wedge \cdots \wedge x_m. - - It is an algebra automorphism of the Clifford algebra. - - :meth:`degree_negation` is an alias for :meth:`reflection`. + Return the cardinality of ``self``. EXAMPLES:: - sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) - sage: Cl. = CliffordAlgebra(Q) - sage: elt = 5*x + y + x*z - sage: r = elt.reflection(); r - x*z - 5*x - y - sage: r.reflection() == elt + sage: from sage.algebras.clifford_algebra import CliffordAlgebraIndices + sage: idx = CliffordAlgebraIndices(7) + sage: idx.cardinality() == 2^7 True - - TESTS: - - We check that the reflection is an involution:: - - sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) - sage: Cl. = CliffordAlgebra(Q) - sage: all(x.reflection().reflection() == x for x in Cl.basis()) + sage: len(idx) == 2^7 True """ - return self.__class__(self.parent(), {m: (-1)**len(m) * c for m,c in self}) + return self._cardinality - degree_negation = reflection + __len__ = cardinality - def transpose(self): + def _repr_(self): r""" - Return the transpose of ``self``. + Return a string representation of ``self``. - The transpose is an anti-algebra involution of a Clifford algebra - and is defined (using linearity) by + EXAMPLES:: - .. MATH:: + sage: from sage.algebras.clifford_algebra import CliffordAlgebraIndices + sage: CliffordAlgebraIndices(7) + Subsets of {0,1,...,6} + sage: CliffordAlgebraIndices(0) + Subsets of {} + sage: CliffordAlgebraIndices(1) + Subsets of {0} + sage: CliffordAlgebraIndices(2) + Subsets of {0,1} + """ + if self._nbits == 0: + return "Subsets of {}" + if self._nbits == 1: + return "Subsets of {0}" + if self._nbits == 2: + return "Subsets of {0,1}" + return f"Subsets of {{0,1,...,{self._nbits-1}}}" - x_1 \wedge x_2 \wedge \cdots \wedge x_m \mapsto - x_m \wedge \cdots \wedge x_2 \wedge x_1. + def _latex_(self): + r""" + Return a latex representation of ``self``. EXAMPLES:: - sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) - sage: Cl. = CliffordAlgebra(Q) - sage: elt = 5*x + y + x*z - sage: t = elt.transpose(); t - -x*z + 5*x + y + 3 - sage: t.transpose() == elt - True - sage: Cl.one().transpose() - 1 - - TESTS: + sage: from sage.algebras.clifford_algebra import CliffordAlgebraIndices + sage: latex(CliffordAlgebraIndices(7)) + \mathcal{P}({0,1,\ldots,6}) + sage: latex(CliffordAlgebraIndices(0)) + \mathcal{P}(\emptyset) + sage: latex(CliffordAlgebraIndices(1)) + \mathcal{P}({0}) + sage: latex(CliffordAlgebraIndices(2)) + \mathcal{P}({0,1}) + """ + if self._nbits == 0: + return "\\mathcal{P}(\\emptyset)" + if self._nbits == 1: + return "\\mathcal{P}({0})" + if self._nbits == 2: + return "\\mathcal{P}({0,1})" + return f"\\mathcal{{P}}({{0,1,\\ldots,{self._nbits-1}}})" + + def __iter__(self): + r""" + Iterate over ``self``. - We check that the transpose is an involution:: + EXAMPLES:: - sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) - sage: Cl. = CliffordAlgebra(Q) - sage: all(x.transpose().transpose() == x for x in Cl.basis()) - True + sage: from sage.algebras.clifford_algebra import CliffordAlgebraIndices + sage: idx = CliffordAlgebraIndices(3) + sage: for i in idx: + ....: print(i) + 0 + 1 + 01 + 001 + 11 + 101 + 011 + 111 + """ + import itertools + n = self._nbits + yield FrozenBitset() + k = 1 + while k <= n: + for C in itertools.combinations(range(n), k): + yield FrozenBitset(C) + k += 1 + + def __contains__(self, elt): + r""" + Check containment of ``elt`` in ``self``. - Zero is sent to zero:: + EXAMPLES:: - sage: Cl.zero().transpose() == Cl.zero() + sage: from sage.algebras.clifford_algebra import CliffordAlgebraIndices + sage: idx = CliffordAlgebraIndices(3) + sage: int(8) in idx # representing the set {4} + False + sage: int(5) in idx # representing the set {1,3} True + sage: FrozenBitset('1') in idx + True + sage: FrozenBitset('000001') in idx + False """ - P = self.parent() - if not self._monomial_coefficients: - return P.zero() - g = P.gens() - return P.sum(c * P.prod(g[i] for i in reversed(m)) for m,c in self) + if isinstance(elt, int): + return elt < self._cardinality and elt >= 0 + if not isinstance(elt, FrozenBitset): + return False + return elt.capacity() <= self._nbits - def conjugate(self): + def _an_element_(self): r""" - Return the Clifford conjugate of ``self``. - - The Clifford conjugate of an element `x` of a Clifford algebra is - defined as - - .. MATH:: - - \bar{x} := \alpha(x^t) = \alpha(x)^t - - where `\alpha` denotes the :meth:`reflection ` - automorphism and `t` the :meth:`transposition `. + Return an element of ``self``. EXAMPLES:: - sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) - sage: Cl. = CliffordAlgebra(Q) - sage: elt = 5*x + y + x*z - sage: c = elt.conjugate(); c - -x*z - 5*x - y + 3 - sage: c.conjugate() == elt - True - - TESTS: - - We check that the conjugate is an involution:: - - sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) - sage: Cl. = CliffordAlgebra(Q) - sage: all(x.conjugate().conjugate() == x for x in Cl.basis()) - True + sage: from sage.algebras.clifford_algebra import CliffordAlgebraIndices + sage: idx = CliffordAlgebraIndices(0) + sage: idx._an_element_() + 0 + sage: idx = CliffordAlgebraIndices(1) + sage: idx._an_element_() + 1 + sage: idx = CliffordAlgebraIndices(2) + sage: idx._an_element_() + 01 + sage: idx = CliffordAlgebraIndices(3) + sage: idx._an_element_() + 11 """ - return self.reflection().transpose() - - clifford_conjugate = conjugate + if not self._nbits: + return FrozenBitset() + from sage.combinat.subset import SubsetsSorted + X = SubsetsSorted(range(self._nbits)) + return FrozenBitset(X.an_element()) class CliffordAlgebra(CombinatorialFreeModule): r""" @@ -460,7 +402,7 @@ def __classcall_private__(cls, Q, names=None): names = tuple(names) if len(names) != Q.dim(): if len(names) == 1: - names = tuple( '{}{}'.format(names[0], i) for i in range(Q.dim()) ) + names = tuple('{}{}'.format(names[0], i) for i in range(Q.dim())) else: raise ValueError("the number of variables does not match the number of generators") return super().__classcall__(cls, Q, names) @@ -486,15 +428,14 @@ def __init__(self, Q, names, category=None): sage: Q = QuadraticForm(ZZ, 9) sage: Cl = CliffordAlgebra(Q) sage: ba = Cl.basis().keys() - sage: all( tuple(sorted(S)) in ba - ....: for S in Subsets(range(9)) ) + sage: all(FrozenBitset(format(i,'b')[::-1]) in ba for i in range(2**9)) True """ self._quadratic_form = Q R = Q.base_ring() category = AlgebrasWithBasis(R.category()).Super().Filtered().FiniteDimensional().or_subcategory(category) - indices = SubsetsSorted(range(Q.dim())) - CombinatorialFreeModule.__init__(self, R, indices, category=category) + indices = CliffordAlgebraIndices(Q.dim()) + CombinatorialFreeModule.__init__(self, R, indices, category=category, sorting_key=tuple) self._assign_names(names) def _repr_(self): @@ -523,6 +464,8 @@ def _repr_term(self, m): sage: Cl. = CliffordAlgebra(Q) sage: Cl._repr_term((0,2)) 'x*z' + sage: Cl._repr_term(FrozenBitset('101')) + 'x*z' sage: Cl._repr_term(()) '1' sage: Cl._repr_term((1,)) @@ -654,7 +597,7 @@ def _element_constructor_(self, x): sage: Cl(2/3) Traceback (most recent call last): ... - TypeError: do not know how to make x (= 2/3) an element of self ... + TypeError: do not know how to make x=2/3 an element of self sage: Clp(2/3) 2/3 sage: Clp(x) @@ -674,15 +617,60 @@ def _element_constructor_(self, x): if x in self.free_module(): R = self.base_ring() if x.parent().base_ring() is R: - return self.element_class(self, {(i,): c for i,c in x.items()}) - return self.element_class(self, {(i,): R(c) for i,c in x.items() if R(c) != R.zero()}) + return self.element_class(self, {FrozenBitset((i,)): c for i, c in x.items()}) + # if the base ring is different, attempt to coerce it into R + return self.element_class(self, {FrozenBitset((i,)): R(c) for i, c in x.items() if R(c) != R.zero()}) if (isinstance(x, CliffordAlgebraElement) - and self.has_coerce_map_from(x.parent())): + and self.has_coerce_map_from(x.parent())): R = self.base_ring() - return self.element_class(self, {i: R(c) for i,c in x if R(c) != R.zero()}) + return self.element_class(self, {i: R(c) for i, c in x if R(c) != R.zero()}) - return super()._element_constructor_(x) + if isinstance(x, tuple): + R = self.base_ring() + return self.element_class(self, {FrozenBitset((i,)): R.one() for i in x}) + + try: + return super(CliffordAlgebra, self)._element_constructor_(x) + except TypeError: + raise TypeError(f'do not know how to make {x=} an element of self') + + def _basis_index_function(self, x): + """ + Given an integer indexing the basis, return the correct + bitset. + + For backwards compatibility, tuples are also accepted. + + EXAMPLES:: + + sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) + sage: Cl = CliffordAlgebra(Q) + sage: Cl._basis_index_function(7) + 111 + sage: Cl._basis_index_function(5) + 101 + sage: Cl._basis_index_function(4) + 001 + + sage: Cl._basis_index_function((0, 1, 2)) + 111 + sage: Cl._basis_index_function((0, 2)) + 101 + sage: Cl._basis_index_function((2,)) + 001 + """ + Q = self._quadratic_form + format_style = f"0{Q.dim()}b" + + # if the input is a tuple, assume that it has + # entries in {0, ..., 2**Q.dim()-1} + if isinstance(x, tuple): + return FrozenBitset(x, capacity = Q.dim()) + + # slice the output of format in order to make conventions + # of format and FrozenBitset agree. + return FrozenBitset(format(x, format_style)[::-1], capacity=Q.dim()) def gen(self, i): """ @@ -699,7 +687,7 @@ def gen(self, i): sage: [Cl.gen(i) for i in range(3)] [x, y, z] """ - return self._from_dict({(i,): self.base_ring().one()}, remove_zeros=False) + return self._from_dict({FrozenBitset((i,)): self.base_ring().one()}, remove_zeros=False) def algebra_generators(self): """ @@ -712,7 +700,7 @@ def algebra_generators(self): sage: Cl.algebra_generators() Finite family {'x': x, 'y': y, 'z': z} """ - d = {x: self.gen(i) for i,x in enumerate(self.variable_names())} + d = {x: self.gen(i) for i, x in enumerate(self.variable_names())} return Family(self.variable_names(), lambda x: d[x]) def gens(self): @@ -728,6 +716,7 @@ def gens(self): """ return tuple(self.algebra_generators()) + @cached_method def ngens(self): """ Return the number of algebra generators of ``self``. @@ -744,16 +733,18 @@ def ngens(self): @cached_method def one_basis(self): """ - Return the basis index of the element `1`. + Return the basis index of the element ``1``. The element ``1`` + is indexed by the emptyset, which is represented by the + :class:`sage.data_structures.bitset.Bitset` ``0``. EXAMPLES:: sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) sage: Cl. = CliffordAlgebra(Q) sage: Cl.one_basis() - () + 0 """ - return () + return FrozenBitset() def is_commutative(self): """ @@ -1025,9 +1016,8 @@ def lift_module_morphism(self, m, names=None): Cl = CliffordAlgebra(Q, names) n = self._quadratic_form.dim() - f = lambda x: self.prod(self._from_dict( {(j,): m[j,i] for j in range(n)}, - remove_zeros=True ) - for i in x) + f = lambda x: self.prod(self._from_dict({FrozenBitset((j, )): m[j, i] for j in range(n)}, + remove_zeros=True) for i in x) cat = AlgebrasWithBasis(self.category().base_ring()).Super().FiniteDimensional() return Cl.module_morphism(on_basis=f, codomain=self, category=cat) @@ -1112,9 +1102,9 @@ def lift_isometry(self, m, names=None): Cl = CliffordAlgebra(Q, names) n = Q.dim() - f = lambda x: Cl.prod(Cl._from_dict( {(j,): m[j,i] for j in range(n)}, - remove_zeros=True ) - for i in x) + + f = lambda x: Cl.prod(Cl._from_dict({FrozenBitset((j, )): m[j, i] for j in range(n)}, + remove_zeros=True) for i in x) cat = AlgebrasWithBasis(self.category().base_ring()).Super().FiniteDimensional() return self.module_morphism(on_basis=f, codomain=Cl, category=cat) @@ -1186,16 +1176,16 @@ def center_basis(self): K = list(B.keys()) k = len(K) d = {} - for a,i in enumerate(K): + for a, i in enumerate(K): Bi = B[i] - for b,j in enumerate(K): + for b, j in enumerate(K): Bj = B[j] - for m,c in (Bi*Bj - Bj*Bi): + for m, c in (Bi*Bj - Bj*Bi): d[(a, K.index(m)+k*b)] = c m = Matrix(R, d, nrows=k, ncols=k*k, sparse=True) - from_vector = lambda x: self.sum_of_terms(((K[i], c) for i,c in x.items()), + from_vector = lambda x: self.sum_of_terms(((K[i], c) for i, c in x.items()), distinct=True) - return tuple(map( from_vector, m.kernel().basis() )) + return tuple(map(from_vector, m.kernel().basis())) # Same as center except for superalgebras @cached_method @@ -1265,23 +1255,24 @@ def supercenter_basis(self): K = list(B.keys()) k = len(K) d = {} - for a,i in enumerate(K): + for a, i in enumerate(K): Bi = B[i] - for b,j in enumerate(K): + for b, j in enumerate(K): Bj = B[j] if len(i) % 2 and len(j) % 2: supercommutator = Bi * Bj + Bj * Bi else: supercommutator = Bi * Bj - Bj * Bi - for m,c in supercommutator: - d[(a, K.index(m)+k*b)] = c - m = Matrix(R, d, nrows=k, ncols=k*k, sparse=True) - from_vector = lambda x: self.sum_of_terms(((K[i], c) for i,c in x.items()), + for m, c in supercommutator: + d[(a, K.index(m) + k * b)] = c + m = Matrix(R, d, nrows=k, ncols=k * k, sparse=True) + from_vector = lambda x: self.sum_of_terms(((K[i], c) for i, c in x.items()), distinct=True) - return tuple(map( from_vector, m.kernel().basis() )) + return tuple(map(from_vector, m.kernel().basis())) Element = CliffordAlgebraElement + class ExteriorAlgebra(CliffordAlgebra): r""" An exterior algebra of a free module over a commutative ring. @@ -1366,7 +1357,7 @@ def __classcall_private__(cls, R, names=None, n=None): names = tuple(names) if n is not None and len(names) != n: if len(names) == 1: - names = tuple( '{}{}'.format(names[0], i) for i in range(n) ) + names = tuple('{}{}'.format(names[0], i) for i in range(n)) else: raise ValueError("the number of variables does not match the number of generators") return super().__classcall__(cls, R, names) @@ -1436,7 +1427,7 @@ def _ascii_art_term(self, m): if len(m) == 0: return ascii_art('1') wedge = '/\\' - return ascii_art(*[self.variable_names()[i] for i in m], sep=wedge) + return ascii_art(*[repr(self.basis()[FrozenBitset((i, ))]) for i in m], sep=wedge) def _unicode_art_term(self, m): """ @@ -1580,9 +1571,8 @@ def lift_morphism(self, phi, names=None): n = phi.nrows() R = self.base_ring() E = ExteriorAlgebra(R, names, n) - f = lambda x: E.prod(E._from_dict( {(j,): phi[j,i] for j in range(n)}, - remove_zeros=True ) - for i in x) + f = lambda x: E.prod(E._from_dict({FrozenBitset((j, )): phi[j, i] for j in range(n)}, + remove_zeros=True) for i in x) cat = AlgebrasWithBasis(R).Super().FiniteDimensional() return self.module_morphism(on_basis=f, codomain=E, category=cat) @@ -1699,15 +1689,19 @@ def coproduct_on_basis(self, a): sage: E.coproduct_on_basis((0,)) 1 # x + x # 1 sage: E.coproduct_on_basis((0,1)) - 1 # x*y + x # y + x*y # 1 - y # x + 1 # x*y + x # y - y # x + x*y # 1 sage: E.coproduct_on_basis((0,1,2)) - 1 # x*y*z + x # y*z + x*y # z + x*y*z # 1 - - x*z # y - y # x*z + y*z # x + z # x*y + 1 # x*y*z + x # y*z - y # x*z + x*y # z + + z # x*y - x*z # y + y*z # x + x*y*z # 1 + """ from sage.combinat.combinat import unshuffle_iterator one = self.base_ring().one() - return self.tensor_square().sum_of_terms(unshuffle_iterator(a, one), - distinct=True) + L = unshuffle_iterator(tuple(a), one) + return self.tensor_square()._from_dict( + {tuple(FrozenBitset(e) if e else FrozenBitset() for e in t): c for t, c in L if c}, + coerce=False, + remove_zeros=False) def antipode_on_basis(self, m): r""" @@ -1914,9 +1908,7 @@ def lifted_form(x, y): m = len(my) if m != n: continue - matrix_list = [M[mx[i], my[j]] - for i in range(n) - for j in range(n)] + matrix_list = [M[i, j] for i in mx for j in my] MA = MatrixArgs(R, n, matrix_list) del matrix_list result += cx * cy * MA.matrix(False).determinant() @@ -1926,247 +1918,34 @@ def lifted_form(x, y): codomain=self.base_ring(), name="Bilinear Form") - class Element(CliffordAlgebraElement): - """ - An element of an exterior algebra. - """ - def _mul_(self, other): - """ - Return ``self`` multiplied by ``other``. - - INPUT: - - - ``other`` -- element of the same exterior algebra as ``self`` - - EXAMPLES:: - - sage: E. = ExteriorAlgebra(QQ) - sage: x*y - x*y - sage: y*x - -x*y - sage: z*y*x - -x*y*z - sage: (x*z)*y - -x*y*z - sage: (3*x + y)^2 - 0 - sage: (x - 3*y + z/3)^2 - 0 - sage: (x+y) * (y+z) - x*y + x*z + y*z - """ - zero = self.parent().base_ring().zero() - d = {} - - for ml,cl in self: - for mr,cr in other: - # Create the next term - t = list(mr) - for i in reversed(ml): - pos = 0 - for j in t: - if i == j: - pos = None - break - if i < j: - break - pos += 1 - cr = -cr - if pos is None: - t = None - break - t.insert(pos, i) - - if t is None: # The next term is 0, move along - continue + def _ideal_class_(self, n=0): + """ + Return the class that is used to implement ideals of ``self``. + + EXAMPLES:: - t = tuple(t) - d[t] = d.get(t, zero) + cl * cr - if d[t] == zero: - del d[t] + sage: E. = ExteriorAlgebra(QQ) + sage: type(E.ideal(x*y - z)) + + + TESTS:: - return self.__class__(self.parent(), d) + sage: E. = ExteriorAlgebra(QQ) + sage: E._ideal_class_() + + """ + return ExteriorAlgebraIdeal - def interior_product(self, x): - r""" - Return the interior product (also known as antiderivation) of - ``self`` with respect to ``x`` (that is, the element - `\iota_{x}(\text{self})` of the exterior algebra). + Element = ExteriorAlgebraElement - If `V` is an `R`-module, and if `\alpha` is a fixed element of - `V^*`, then the *interior product* with respect to `\alpha` is - an `R`-linear map - `i_{\alpha} \colon \Lambda(V) \to \Lambda(V)`, determined by - the following requirements: - - - `i_{\alpha}(v) = \alpha(v)` for all `v \in V = \Lambda^1(V)`, - - it is a graded derivation of degree `-1`: all `x` and `y` - in `\Lambda(V)` satisfy - - .. MATH:: - - i_{\alpha}(x \wedge y) = (i_{\alpha} x) \wedge y - + (-1)^{\deg x} x \wedge (i_{\alpha} y). - - It can be shown that this map `i_{\alpha}` is graded of - degree `-1` (that is, sends `\Lambda^k(V)` into - `\Lambda^{k-1}(V)` for every `k`). - - When `V` is a finite free `R`-module, the interior product can - also be defined by - - .. MATH:: - - (i_{\alpha} \omega)(u_1, \ldots, u_k) - = \omega(\alpha, u_1, \ldots, u_k), - - where `\omega \in \Lambda^k(V)` is thought of as an - alternating multilinear mapping from - `V^* \times \cdots \times V^*` to `R`. - - Since Sage is only dealing with exterior powers of modules - of the form `R^d` for some nonnegative integer `d`, the - element `\alpha \in V^*` can be thought of as an element of - `V` (by identifying the standard basis of `V = R^d` with its - dual basis). This is how `\alpha` should be passed to this - method. - - We then extend the interior product to all - `\alpha \in \Lambda (V^*)` by - - .. MATH:: - - i_{\beta \wedge \gamma} = i_{\gamma} \circ i_{\beta}. - - INPUT: - - - ``x`` -- element of (or coercing into) `\Lambda^1(V)` - (for example, an element of `V`); this plays the role of - `\alpha` in the above definition - - EXAMPLES:: - - sage: E. = ExteriorAlgebra(QQ) - sage: x.interior_product(x) - 1 - sage: (x + x*y).interior_product(2*y) - -2*x - sage: (x*z + x*y*z).interior_product(2*y - x) - -2*x*z - y*z - z - sage: x.interior_product(E.one()) - x - sage: E.one().interior_product(x) - 0 - sage: x.interior_product(E.zero()) - 0 - sage: E.zero().interior_product(x) - 0 - - REFERENCES: - - - :wikipedia:`Exterior_algebra#Interior_product` - """ - P = self.parent() - return P.sum([c * cx * P.interior_product_on_basis(m, mx) - for m,c in self for mx,cx in x]) - - antiderivation = interior_product - - def hodge_dual(self): - r""" - Return the Hodge dual of ``self``. - - The Hodge dual of an element `\alpha` of the exterior algebra is - defined as `i_{\alpha} \sigma`, where `\sigma` is the volume - form - (:meth:`~sage.algebras.clifford_algebra.ExteriorAlgebra.volume_form`) - and `i_{\alpha}` denotes the antiderivation function with - respect to `\alpha` (see :meth:`interior_product` for the - definition of this). - - .. NOTE:: - - The Hodge dual of the Hodge dual of a homogeneous element - `p` of `\Lambda(V)` equals `(-1)^{k(n-k)} p`, where - `n = \dim V` and `k = \deg(p) = |p|`. - - EXAMPLES:: - - sage: E. = ExteriorAlgebra(QQ) - sage: x.hodge_dual() - y*z - sage: (x*z).hodge_dual() - -y - sage: (x*y*z).hodge_dual() - 1 - sage: [a.hodge_dual().hodge_dual() for a in E.basis()] - [1, x, y, z, x*y, x*z, y*z, x*y*z] - sage: (x + x*y).hodge_dual() - y*z + z - sage: (x*z + x*y*z).hodge_dual() - -y + 1 - sage: E = ExteriorAlgebra(QQ, 'wxyz') - sage: [a.hodge_dual().hodge_dual() for a in E.basis()] - [1, -w, -x, -y, -z, w*x, w*y, w*z, x*y, x*z, y*z, - -w*x*y, -w*x*z, -w*y*z, -x*y*z, w*x*y*z] - """ - volume_form = self.parent().volume_form() - return volume_form.interior_product(self) - - def constant_coefficient(self): - """ - Return the constant coefficient of ``self``. - - .. TODO:: - - Define a similar method for general Clifford algebras once - the morphism to exterior algebras is implemented. - - EXAMPLES:: - - sage: E. = ExteriorAlgebra(QQ) - sage: elt = 5*x + y + x*z + 10 - sage: elt.constant_coefficient() - 10 - sage: x.constant_coefficient() - 0 - """ - return self._monomial_coefficients.get(self.parent().one_basis(), - self.base_ring().zero()) - - def scalar(self, other): - r""" - Return the standard scalar product of ``self`` with ``other``. - - The standard scalar product of `x, y \in \Lambda(V)` is - defined by `\langle x, y \rangle = \langle x^t y \rangle`, where - `\langle a \rangle` denotes the degree-0 term of `a`, and where - `x^t` denotes the transpose - (:meth:`~sage.algebras.clifford_algebra.CliffordAlgebraElement.transpose`) - of `x`. - - .. TODO:: - - Define a similar method for general Clifford algebras once - the morphism to exterior algebras is implemented. - - EXAMPLES:: - - sage: E. = ExteriorAlgebra(QQ) - sage: elt = 5*x + y + x*z - sage: elt.scalar(z + 2*x) - 0 - sage: elt.transpose() * (z + 2*x) - -2*x*y + 5*x*z + y*z - """ - return (self.transpose() * other).constant_coefficient() ##################################################################### -## Differentials +# Differentials + class ExteriorAlgebraDifferential(ModuleMorphismByLinearity, - UniqueRepresentation, metaclass=InheritComparisonClasscallMetaclass): + UniqueRepresentation, + metaclass=InheritComparisonClasscallMetaclass): r""" Internal class to store the data of a boundary or coboundary of an exterior algebra `\Lambda(L)` defined by the structure @@ -2194,7 +1973,11 @@ def __classcall__(cls, E, s_coeff): sage: par1 = ExteriorAlgebraDifferential(E, {(0,1): z, (1,2): x, (2,0): y}) sage: par2 = ExteriorAlgebraDifferential(E, {(0,1): z, (1,2): x, (0,2): -y}) sage: par3 = ExteriorAlgebraDifferential(E, {(1,0): {2:-1}, (1,2): {0:1}, (2,0):{1:1}}) - sage: par1 is par2 and par2 is par3 + sage: par1 is par2 + True + sage: par1 is par3 + True + sage: par2 is par3 True sage: par4 = ExteriorAlgebraDifferential(E, {}) @@ -2206,12 +1989,12 @@ def __classcall__(cls, E, s_coeff): d = {} for k, v in dict(s_coeff).items(): - if not v: # Strip terms with 0 + if not v: # Strip terms with 0 continue if isinstance(v, dict): R = E.base_ring() - v = E._from_dict({(i,): R(c) for i, c in v.items()}) + v = E._from_dict({FrozenBitset((i,)): R(c) for i, c in v.items()}) else: # Make sure v is in ``E`` v = E(v) @@ -2286,6 +2069,7 @@ def homology(self, deg=None, **kwds): """ return self.chain_complex().homology(deg, **kwds) + class ExteriorAlgebraBoundary(ExteriorAlgebraDifferential): r""" The boundary `\partial` of an exterior algebra `\Lambda(L)` defined @@ -2418,7 +2202,7 @@ def _on_basis(self, m): sage: E. = ExteriorAlgebra(QQ) sage: par = E.boundary({(0,1): z, (1,2): x, (2,0): y}) - sage: par._on_basis(()) + sage: par._on_basis(FrozenBitset()) 0 sage: par._on_basis((0,)) 0 @@ -2429,12 +2213,22 @@ def _on_basis(self, m): sage: par._on_basis((0,1,2)) 0 """ + from itertools import combinations E = self.domain() sc = self._s_coeff keys = sc.keys() - return E.sum((-1)**b * sc[(i,j)] - * E.monomial(m[:a] + m[a+1:a+b+1] + m[a+b+2:]) - for a,i in enumerate(m) for b,j in enumerate(m[a+1:]) if (i,j) in keys) + + s = E.zero() + + for b, (i, j) in enumerate(combinations(m, 2)): + if (i, j) not in keys: + continue + t = Bitset(m) + t.discard(i) + t.discard(j) + s += sc[i, j] * E.term(FrozenBitset(t), (-1)**b) + + return s @cached_method def chain_complex(self, R=None): @@ -2505,18 +2299,19 @@ def chain_complex(self, R=None): # Construct the transition matrices data = {} prev_basis = basis_by_deg[0] - for deg in range(1,n+1): + for deg in range(1, n+1): # Make sure within each basis we're sorted by lex basis = sorted(basis_by_deg[deg]) mat = [] for b in basis: ret = self._on_basis(b) - mat.append([ret[p] for p in prev_basis]) + mat.append([ret.coefficient(p) for p in prev_basis]) data[deg] = Matrix(mat).transpose().change_ring(R) prev_basis = basis return ChainComplex(data, degree=-1) + class ExteriorAlgebraCoboundary(ExteriorAlgebraDifferential): r""" The coboundary `d` of an exterior algebra `\Lambda(L)` defined @@ -2563,7 +2358,7 @@ class ExteriorAlgebraCoboundary(ExteriorAlgebraDifferential): cross product `\times` of `\RR^3`:: sage: E. = ExteriorAlgebra(QQ) - sage: d = E.coboundary({(0,1): z, (1,2): x, (2,0): y}) + sage: d = E.coboundary({(0,1): z, (1,2): x, (0, 2): -y}) sage: d(x) y*z sage: d(y) @@ -2650,8 +2445,12 @@ def __init__(self, E, s_coeff): zero = E.zero() B = E.basis() for k, v in dict(s_coeff).items(): - k = B[k] - for m,c in v: + if k[0] > k[1]: # k will have length 2 + k = sorted(k) + v = -v + + k = B[FrozenBitset(k)] + for m, c in v: self._cos_coeff[m] = self._cos_coeff.get(m, zero) + c * k ExteriorAlgebraDifferential.__init__(self, E, s_coeff) @@ -2676,7 +2475,7 @@ def _on_basis(self, m): cross product:: sage: E. = ExteriorAlgebra(QQ) - sage: d = E.coboundary({(0,1): z, (1,2): x, (2,0): y}) + sage: d = E.coboundary({(0,1): z, (1,2): x, (0,2): -y}) sage: d._on_basis(()) 0 sage: d._on_basis((0,)) @@ -2694,9 +2493,28 @@ def _on_basis(self, m): """ E = self.domain() cc = self._cos_coeff - keys = cc.keys() - return E.sum((-1)**a * E.monomial(m[:a]) * cc[(i,)] * E.monomial(m[a+1:]) - for a,i in enumerate(m) if (i,) in keys) + + tot = E.zero() + + for sgn, i in enumerate(m): + k = FrozenBitset((i,)) + if k in cc: + below = tuple([j for j in m if j < i]) + above = tuple([j for j in m if j > i]) + + # a hack to deal with empty bitsets + if not below: + below = E.one() + else: + below = E.monomial(FrozenBitset(below)) + if not above: + above = E.one() + else: + above = E.monomial(FrozenBitset(above)) + + tot += (-1)**sgn * below * cc[k] * above + + return tot @cached_method def chain_complex(self, R=None): @@ -2773,8 +2591,407 @@ def chain_complex(self, R=None): mat = [] for b in basis: ret = self._on_basis(b) - mat.append([ret[p] for p in next_basis]) + try: + mat.append([ret.coefficient(p) for p in next_basis]) + except AttributeError: # if ret is in E.base_ring() + mat.append([E.base_ring()(ret)]*len(next_basis)) data[deg] = Matrix(mat).transpose().change_ring(R) basis = next_basis return ChainComplex(data, degree=1) + +@richcmp_method +class ExteriorAlgebraIdeal(Ideal_nc): + """ + An ideal of the exterior algebra. + + EXAMPLES:: + + sage: E. = ExteriorAlgebra(QQ) + sage: I = E.ideal(x*y); I + Twosided Ideal (x*y) of The exterior algebra of rank 3 over Rational Field + + We can also use it to build a quotient:: + + sage: Q = E.quotient(I); Q + Quotient of The exterior algebra of rank 3 over Rational Field by the ideal (x*y) + sage: Q.inject_variables() + Defining xbar, ybar, zbar + sage: xbar * ybar + 0 + """ + def __init__(self, ring, gens, coerce=True, side="twosided"): + """ + Initialize ``self``. + + EXAMPLES: + + We skip the category test because the ideals are not a proper + element class of the monoid of all ideals:: + + sage: E. = ExteriorAlgebra(QQ) + sage: I = E.ideal([x*y - x, x*y - 1]) + sage: TestSuite(I).run(skip="_test_category") + + sage: I = E.ideal([x*y - 3, 0, 2*3]) + sage: TestSuite(I).run(skip="_test_category") + + sage: I = E.ideal([]) + sage: TestSuite(I).run(skip="_test_category") + """ + self._groebner_strategy = None + self._reduced = False + self._homogeneous = all(x.is_super_homogeneous() for x in gens if x) + if self._homogeneous: + side = "twosided" + Ideal_nc.__init__(self, ring, gens, coerce, side) + + def reduce(self, f): + """ + Reduce ``f`` modulo ``self``. + + EXAMPLES:: + + sage: E. = ExteriorAlgebra(QQ) + sage: I = E.ideal(x*y); + sage: I.reduce(x*y + x*y*z + z) + z + sage: I.reduce(x*y + x + y) + x + y + sage: I.reduce(x*y + x*y*z) + 0 + + sage: E. = ExteriorAlgebra(QQ) + sage: I = E.ideal([a+b*c]) + sage: I.reduce(I.gen(0) * d) + 0 + """ + if self._groebner_strategy is None: + self.groebner_basis() + R = self.ring() + return self._groebner_strategy.reduce(R(f)) + + def _contains_(self, f): + r""" + Return ``True`` if ``f`` is in this ideal, + ``False`` otherwise. + + EXAMPLES:: + + sage: E. = ExteriorAlgebra(QQ) + sage: I = E.ideal([x, x*y*z + 2*x*z + 3*y*z], side="left") + sage: I.groebner_basis() + (x, y*z) + sage: x in I + True + sage: y*z in I + True + sage: x + 3*y*z in I + True + sage: x + 3*y in I + False + sage: x*y in I + True + sage: x + x*y + y*z + x*z in I + True + + .. NOTE:: + + Requires computation of a Groebner basis, which can be a very + expensive operation. + """ + return not self.reduce(f) + + def __richcmp__(self, other, op): + """ + Compare ``self`` and ``other``. + + EXAMPLES:: + + sage: E. = ExteriorAlgebra(QQ) + sage: I = E.ideal([x, x*y*z + 2*x*z + 3*y*z]) + sage: I == I + True + sage: Ip = E.ideal([x, y*z]) + sage: Ip == I + True + sage: Ip <= I + True + sage: Ip < I + False + sage: Ip >= I + True + sage: Ip > I + False + sage: E.ideal([x]) < I + True + sage: E.ideal([x]) <= I + True + sage: I <= E.ideal([x]) + False + + sage: E. = ExteriorAlgebra(QQ) + sage: p = a + b*c + sage: IT = E.ideal([p], side="twosided") + sage: IR = E.ideal([p], side="right") + sage: IL = E.ideal([p], side="left") + sage: IR == IL + False + sage: IR <= IL + False + sage: IR >= IL + False + sage: IL.reduce(p * d) + 2*a*d + sage: IR.reduce(d * p) + -2*a*d + + sage: IR <= IT + True + sage: IL <= IT + True + sage: IT <= IL + False + sage: IT <= IR + False + """ + if not isinstance(other, ExteriorAlgebraIdeal): + if op == op_EQ: + return False + if op == op_NE: + return True + return NotImplemented + + if self is other: + return rich_to_bool(op, 0) + + # comparison for >= and > : swap the arguments + if op == op_GE: + return other.__richcmp__(self, op_LE) + elif op == op_GT: + return other.__richcmp__(self, op_LT) + + s_gens = set(g for g in self.gens() if g) + o_gens = set(g for g in other.gens() if g) + + if self.side() != other.side(): + if other.side() == "right": + X = set(t * f for t in self.ring().basis() for f in s_gens) + s_gens.update(X) + elif other.side() == "left": + X = set(f * t for t in self.ring().basis() for f in s_gens) + s_gens.update(X) + + if set(s_gens) == set(o_gens): + return rich_to_bool(op, 0) + + contained = all(f in other for f in s_gens) + if op == op_LE: + return contained + if op == op_NE and not contained: + return True + + if self.side() != other.side(): + if self.side() == "right": + X = set(t * f for t in self.ring().basis() for f in o_gens) + s_gens.update(X) + elif self.side() == "left": + X = set(f * t for t in self.ring().basis() for f in o_gens) + s_gens.update(X) + + contains = all(f in self for f in o_gens) + if op == op_EQ: + return contained and contains + if op == op_NE: + return not (contained and contains) + # remaining case < + return contained and not contains + + def __mul__(self, other): + """ + Return the product of ``self`` with ``other``. + + .. WARNING:: + + If ``self`` is a right ideal and ``other`` is a left ideal, + this returns a submodule rather than an ideal. + + EXAMPLES:: + + sage: E. = ExteriorAlgebra(QQ) + + sage: I = E.ideal([a + 1], side="left") + sage: J = I * I; J + Left Ideal (2*a + 1, a, b, c, d, a*b, a*c, a*d, 2*a*b*c + b*c, 2*a*b*d + b*d, + 2*a*c*d + c*d, a*b*c, a*b*d, a*c*d, b*c*d, a*b*c*d) + of The exterior algebra of rank 4 over Rational Field + sage: J.groebner_basis() + (1,) + sage: I.gen(0)^2 + 2*a + 1 + + sage: J = E.ideal([b+c]) + sage: I * J + Twosided Ideal (a*b + a*c + b + c) of The exterior algebra of rank 4 over Rational Field + sage: J * I + Left Ideal (-a*b - a*c + b + c) of The exterior algebra of rank 4 over Rational Field + + sage: K = J * I + sage: K + Left Ideal (-a*b - a*c + b + c) of The exterior algebra of rank 4 over Rational Field + sage: E.ideal([J.gen(0) * d * I.gen(0)], side="left") <= K + True + + sage: J = E.ideal([b + c*d], side="right") + sage: I * J + Twosided Ideal (a*c*d + a*b + c*d + b) of The exterior algebra of rank 4 over Rational Field + sage: X = J * I; X + Free module generated by {0, 1, 2, 3, 4, 5, 6, 7} over Rational Field + sage: [X.lift(b) for b in X.basis()] + [c*d + b, -a*c*d + a*b, b*c, b*d, a*b*c, a*b*d, b*c*d, a*b*c*d] + sage: p = X.lift(X.basis()[0]) + sage: p + c*d + b + sage: a * p # not a left ideal + a*c*d + a*b + + sage: I = E.ideal([a + 1], side="right") + sage: E.ideal([1]) * I + Twosided Ideal (a + 1) of The exterior algebra of rank 4 over Rational Field + sage: I * E.ideal([1]) + Right Ideal (a + 1) of The exterior algebra of rank 4 over Rational Field + """ + if not isinstance(other, ExteriorAlgebraIdeal) or self.ring() != other.ring(): + return super().__mul__(other) + + if self._homogeneous or other._homogeneous or (self.side() == "left" and other.side() == "right"): + gens = (x * y for x in self.gens() for y in other.gens()) + else: + gens = (x * t * y for t in self.ring().basis() for x in self.gens() for y in other.gens()) + gens = [z for z in gens if z] + + if self.side() == "right" and other.side() == "left": + return self.ring().submodule(gens) + + if self.side() == "left" or self.side() == "twosided": + if other.side() == "right" or other.side() == "twosided": + return self.ring().ideal(gens, side="twosided") + return self.ring().ideal(gens, side="left") + return self.ring().ideal(gens, side="right") + + def groebner_basis(self, term_order=None, reduced=True): + r""" + Return the (reduced) Gröbner basis of ``self``. + + INPUT: + + - ``term_order`` -- the term order used to compute the Gröbner basis; + must be one of the following: + + * ``"neglex"`` -- (default) negative (read right-to-left) lex order + * ``"degrevlex"`` -- degree reverse lex order + * ``"deglex"`` -- degree lex order + + - ``reduced`` -- (default: ``True``) whether or not to return the + reduced Gröbner basis + + EXAMPLES: + + We compute an example:: + + sage: E. = ExteriorAlgebra(QQ) + sage: rels = [c*d*e - b*d*e + b*c*e - b*c*d, + ....: c*d*e - a*d*e + a*c*e - a*c*d, + ....: b*d*e - a*d*e + a*b*e - a*b*d, + ....: b*c*e - a*c*e + a*b*e - a*b*c, + ....: b*c*d - a*c*d + a*b*d - a*b*c] + sage: I = E.ideal(rels) + sage: I.groebner_basis() + (-a*b*c + a*b*d - a*c*d + b*c*d, + -a*b*c + a*b*e - a*c*e + b*c*e, + -a*b*d + a*b*e - a*d*e + b*d*e, + -a*c*d + a*c*e - a*d*e + c*d*e) + + With different term orders:: + + sage: I.groebner_basis("degrevlex") + (b*c*d - b*c*e + b*d*e - c*d*e, + a*c*d - a*c*e + a*d*e - c*d*e, + a*b*d - a*b*e + a*d*e - b*d*e, + a*b*c - a*b*e + a*c*e - b*c*e) + + sage: I.groebner_basis("deglex") + (-a*b*c + a*b*d - a*c*d + b*c*d, + -a*b*c + a*b*e - a*c*e + b*c*e, + -a*b*d + a*b*e - a*d*e + b*d*e, + -a*c*d + a*c*e - a*d*e + c*d*e) + + The example above was computed first using M2, which agrees with + the ``"degrevlex"`` ordering:: + + E = QQ[a..e, SkewCommutative => true] + I = ideal( c*d*e - b*d*e + b*c*e - b*c*d, + c*d*e - a*d*e + a*c*e - a*c*d, + b*d*e - a*d*e + a*b*e - a*b*d, + b*c*e - a*c*e + a*b*e - a*b*c, + b*c*d - a*c*d + a*b*d - a*b*c) + groebnerBasis(I) + + returns: + o3 = | bcd-bce+bde-cde acd-ace+ade-cde abd-abe+ade-bde abc-abe+ace-bce | + + By default, the Gröbner basis is reduced, but we can get non-reduced + Gröber bases (which are not unique):: + + sage: E. = ExteriorAlgebra(QQ) + sage: I = E.ideal([x+y*z]) + sage: I.groebner_basis(reduced=False) + (x*y, x*z, y*z + x, x*y*z) + sage: I.groebner_basis(reduced=True) + (x*y, x*z, y*z + x) + + However, if we have already computed a reduced Gröbner basis (with + a given term order), then we return that:: + + sage: I = E.ideal([x+y*z]) # A fresh ideal + sage: I.groebner_basis() + (x*y, x*z, y*z + x) + sage: I.groebner_basis(reduced=False) + (x*y, x*z, y*z + x) + + TESTS:: + + sage: E. = ExteriorAlgebra(ZZ) + sage: I = E.ideal([a+1, b*c+d]) + sage: I.groebner_basis() + Traceback (most recent call last): + ... + NotImplementedError: only implemented over fields + """ + if self.ring().base_ring() not in Fields(): + raise NotImplementedError("only implemented over fields") + if term_order is None: + if self._groebner_strategy is not None: + strategy = type(self._groebner_strategy) + else: + from sage.algebras.exterior_algebra_groebner import GroebnerStrategyNegLex as strategy + else: + if term_order == "neglex": + from sage.algebras.exterior_algebra_groebner import GroebnerStrategyNegLex as strategy + elif term_order == "degrevlex": + from sage.algebras.exterior_algebra_groebner import GroebnerStrategyDegRevLex as strategy + elif term_order == "deglex": + from sage.algebras.exterior_algebra_groebner import GroebnerStrategyDegLex as strategy + else: + raise ValueError("invalid term order") + if isinstance(self._groebner_strategy, strategy): + if self._reduced or not reduced: + return self._groebner_strategy.groebner_basis + self._reduced = reduced + self._groebner_strategy.reduce_computed_gb() + return self._groebner_strategy.groebner_basis + self._groebner_strategy = strategy(self) + self._groebner_strategy.compute_groebner(reduced=reduced) + self._reduced = reduced + return self._groebner_strategy.groebner_basis + diff --git a/src/sage/algebras/clifford_algebra_element.pxd b/src/sage/algebras/clifford_algebra_element.pxd new file mode 100644 index 00000000000..64ac7253351 --- /dev/null +++ b/src/sage/algebras/clifford_algebra_element.pxd @@ -0,0 +1,17 @@ +""" +Clifford algebra elements +""" + +from sage.modules.with_basis.indexed_element cimport IndexedFreeModuleElement +from sage.data_structures.bitset cimport FrozenBitset + +cdef class CliffordAlgebraElement(IndexedFreeModuleElement): + cdef CliffordAlgebraElement _mul_self_term(self, FrozenBitset supp, coeff) + cdef CliffordAlgebraElement _mul_term_self(self, FrozenBitset supp, coeff) + +cdef class ExteriorAlgebraElement(CliffordAlgebraElement): + pass + +cdef class CohomologyRAAGElement(CliffordAlgebraElement): + pass + diff --git a/src/sage/algebras/clifford_algebra_element.pyx b/src/sage/algebras/clifford_algebra_element.pyx new file mode 100644 index 00000000000..e065b6b70b9 --- /dev/null +++ b/src/sage/algebras/clifford_algebra_element.pyx @@ -0,0 +1,985 @@ +""" +Clifford algebra elements + +AUTHORS: + +- Travis Scrimshaw (2013-09-06): Initial version +- Trevor Karn (2022-07-10): Rewrite multiplication using bitsets +""" + +#***************************************************************************** +# Copyright (C) 2022 Trevor K. Karn +# (C) 2022 Travis Scrimshaw +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.structure.parent cimport Parent +from sage.data_structures.bitset cimport Bitset +from sage.algebras.weyl_algebra import repr_from_monomials +from sage.data_structures.blas_dict cimport scal +from copy import copy + +cdef class CliffordAlgebraElement(IndexedFreeModuleElement): + """ + An element in a Clifford algebra. + + TESTS:: + + sage: Q = QuadraticForm(ZZ, 3, [1, 2, 3, 4, 5, 6]) + sage: Cl. = CliffordAlgebra(Q) + sage: elt = ((x^3-z)*x + y)^2 + sage: TestSuite(elt).run() + """ + def _repr_(self): + """ + Return a string representation of ``self``. + + TESTS:: + + sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) + sage: Cl. = CliffordAlgebra(Q) + sage: ((x^3-z)*x + y)^2 + -2*x*y*z - x*z + 5*x - 4*y + 2*z + 2 + sage: Cl.zero() + 0 + """ + return repr_from_monomials(self.list(), self._parent._repr_term) + + def _latex_(self): + r""" + Return a `\LaTeX` representation of ``self``. + + TESTS:: + + sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) + sage: Cl. = CliffordAlgebra(Q) + sage: latex( ((x^3-z)*x + y)^2 ) + -2 x y z - x z + 5 x - 4 y + 2 z + 2 + sage: Cl. = CliffordAlgebra(Q) + sage: latex( (x1 - x2)*x0 + 5*x0*x1*x2 ) + 5 x_{0} x_{1} x_{2} - x_{0} x_{1} + x_{0} x_{2} - 1 + """ + return repr_from_monomials(self.list(), self._parent._latex_term, True) + + cdef _mul_(self, other): + """ + Return ``self`` multiplied by ``other``. + + INPUT: + + - ``other`` -- element of the same Clifford algebra as ``self`` + + EXAMPLES:: + + sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) + sage: Cl. = CliffordAlgebra(Q) + sage: (x^3 - z*y)*x*(y*z + x*y*z) + x*y*z + y*z - 24*x + 12*y + 2*z - 24 + sage: y*x + -x*y + 2 + sage: z*x + -x*z + 3 + sage: z*z + 6 + sage: x*0 + 0 + sage: 0*x + 0 + """ + Q = self._parent._quadratic_form + zero = self._parent._base.zero() + cdef dict next_level, cur, d = {} + cdef FrozenBitset ml, mr, t + cdef Py_ssize_t i, j + cdef CliffordAlgebraElement rhs = other + + # Special case when multiplying by 0 + if not self._monomial_coefficients: + return self + if not rhs._monomial_coefficients: + return rhs + + # Special case when multiplying by an element of the base ring + if len(self._monomial_coefficients) == 1: + ml, cl = next(iter(self._monomial_coefficients.items())) + if ml.isempty(): + return rhs._mul_term_self(ml, cl) + if len(rhs._monomial_coefficients) == 1: + mr, cr = next(iter(self._monomial_coefficients.items())) + if mr.isempty(): + return self._mul_self_term(mr, cr) + + for ml, cl in self: + # Distribute the current term ``cl`` * ``ml`` over ``other``. + cur = copy(other._monomial_coefficients) # The current distribution of the term + for i in reversed(ml): + # Distribute the current factor ``e[i]`` (the ``i``-th + # element of the standard basis). + next_level = {} + # At the end of the following for-loop, ``next`` will be + # the dictionary describing the element + # ``e[i]`` * (the element described by the dictionary ``cur``) + # (where ``e[i]`` is the ``i``-th standard basis vector). + for mr,cr in cur.items(): + + # Commute the factor as necessary until we are in order + for j in mr: + if i <= j: + break + # Add the additional term from the commutation + # get a non-frozen bitset to manipulate + t = Bitset(mr) # a mutable copy + t.discard(j) + t = FrozenBitset(t) + next_level[t] = next_level.get(t, zero) + cr * Q[i,j] + # Note: ``Q[i,j] == Q(e[i]+e[j]) - Q(e[i]) - Q(e[j])`` for + # ``i != j``, where ``e[k]`` is the ``k``-th standard + # basis vector. + cr = -cr + if next_level[t] == zero: + del next_level[t] + + # Check to see if we have a squared term or not + mr = Bitset(mr) # temporarily mutable + if i in mr: + mr.discard(i) + cr *= Q[i,i] + # Note: ``Q[i,i] == Q(e[i])`` where ``e[i]`` is the + # ``i``-th standard basis vector. + else: + # mr is implicitly sorted + mr.add(i) + mr = FrozenBitset(mr) # refreeze it + next_level[mr] = next_level.get(mr, zero) + cr + if next_level[mr] == zero: + del next_level[mr] + cur = next_level + + # Add the distributed terms to the total + for index,coeff in cur.items(): + d[index] = d.get(index, zero) + cl * coeff + if d[index] == zero: + del d[index] + + return self.__class__(self.parent(), d) + + cdef CliffordAlgebraElement _mul_self_term(self, FrozenBitset supp, coeff): + r""" + Multiply ``self * term`` with the ``term`` having support ``supp`` + and coefficient ``coeff``. + + EXAMPLES:: + + sage: E. = ExteriorAlgebra(QQ) + sage: r = sum(E.basis()) + sage: x * y # indirect doctest + x*y + sage: y * x # indirect doctest + -x*y + sage: r * x # indirect doctest + x*y*z - x*y - x*z + x + sage: r * -x # indirect doctest + -x*y*z + x*y + x*z - x + sage: r * (2*x) # indirect doctest + 2*x*y*z - 2*x*y - 2*x*z + 2*x + sage: r * y # indirect doctest + -x*y*z + x*y - y*z + y + sage: r * z # indirect doctest + x*y*z + x*z + y*z + z + sage: r * (x*y) # indirect doctest + x*y*z + x*y + sage: r * (-x*y) # indirect doctest + -x*y*z - x*y + sage: r * (x*y*z) # indirect doctest + x*y*z + sage: r * 1 == r # indirect doctest + True + sage: r * -1 == -r # indirect doctest + True + sage: r * 2 # indirect doctest + 2*x*y*z + 2*x*y + 2*x*z + 2*y*z + 2*x + 2*y + 2*z + 2 + """ + cdef dict d + cdef list to_remove + cdef Py_ssize_t num_cross, tot_cross, i, j + cdef FrozenBitset ml + + if supp.isempty(): # Multiplication by a base ring element + if coeff == self._parent._base.one(): + return self + if coeff == -self._parent._base.one(): + return self._neg_() + + return type(self)(self._parent, + scal(coeff, self._monomial_coefficients, + factor_on_left=False)) + + return type(self)(self._parent, {supp: coeff}) * self + + cdef CliffordAlgebraElement _mul_term_self(self, FrozenBitset supp, coeff): + r""" + Multiply ``term * self`` with the ``term`` having support ``supp`` + and coefficient ``coeff``. + """ + if supp.isempty(): # Multiplication by a base ring element + if coeff == self._parent._base.one(): + return self + if coeff == -self._parent._base.one(): + return self._neg_() + + return type(self)(self._parent, + scal(coeff, self._monomial_coefficients, + factor_on_left=True)) + + return type(self)(self._parent, {supp: coeff}) * self + + def list(self): + """ + Return the list of monomials and their coefficients in ``self`` + (as a list of `2`-tuples, each of which has the form + ``(monomial, coefficient)``). + + EXAMPLES:: + + sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) + sage: Cl. = CliffordAlgebra(Q) + sage: elt = 5*x + y + sage: elt.list() + [(1, 5), (01, 1)] + """ + return sorted(self._monomial_coefficients.items(), key=lambda m: (-len(m[0]), list(m[0]))) + + def support(self): + """ + Return the support of ``self``. + + This is the list of all monomials which appear with nonzero + coefficient in ``self``. + + EXAMPLES:: + + sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) + sage: Cl. = CliffordAlgebra(Q) + sage: elt = 5*x + y + sage: elt.support() + [1, 01] + """ + return sorted(self._monomial_coefficients, key=lambda x: (-len(x), list(x))) + + def reflection(self): + r""" + Return the image of the reflection automorphism on ``self``. + + The *reflection automorphism* of a Clifford algebra is defined + as the linear endomorphism of this algebra which maps + + .. MATH:: + + x_1 \wedge x_2 \wedge \cdots \wedge x_m \mapsto + (-1)^m x_1 \wedge x_2 \wedge \cdots \wedge x_m. + + It is an algebra automorphism of the Clifford algebra. + + :meth:`degree_negation` is an alias for :meth:`reflection`. + + EXAMPLES:: + + sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) + sage: Cl. = CliffordAlgebra(Q) + sage: elt = 5*x + y + x*z + sage: r = elt.reflection(); r + x*z - 5*x - y + sage: r.reflection() == elt + True + + TESTS: + + We check that the reflection is an involution:: + + sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) + sage: Cl. = CliffordAlgebra(Q) + sage: all(x.reflection().reflection() == x for x in Cl.basis()) + True + """ + return self.__class__(self._parent, {m: (-1)**len(m) * c for m,c in self}) + + degree_negation = reflection + + def transpose(self): + r""" + Return the transpose of ``self``. + + The transpose is an anti-algebra involution of a Clifford algebra + and is defined (using linearity) by + + .. MATH:: + + x_1 \wedge x_2 \wedge \cdots \wedge x_m \mapsto + x_m \wedge \cdots \wedge x_2 \wedge x_1. + + EXAMPLES:: + + sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) + sage: Cl. = CliffordAlgebra(Q) + sage: elt = 5*x + y + x*z + sage: t = elt.transpose(); t + -x*z + 5*x + y + 3 + sage: t.transpose() == elt + True + sage: Cl.one().transpose() + 1 + + TESTS: + + We check that the transpose is an involution:: + + sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) + sage: Cl. = CliffordAlgebra(Q) + sage: all(x.transpose().transpose() == x for x in Cl.basis()) + True + + Zero is sent to zero:: + + sage: Cl.zero().transpose() == Cl.zero() + True + """ + P = self._parent + if not self._monomial_coefficients: + return P.zero() + g = P.gens() + return P.sum(c * P.prod(g[i] for i in reversed(m)) for m,c in self) + + def conjugate(self): + r""" + Return the Clifford conjugate of ``self``. + + The Clifford conjugate of an element `x` of a Clifford algebra is + defined as + + .. MATH:: + + \bar{x} := \alpha(x^t) = \alpha(x)^t + + where `\alpha` denotes the :meth:`reflection ` + automorphism and `t` the :meth:`transposition `. + + EXAMPLES:: + + sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) + sage: Cl. = CliffordAlgebra(Q) + sage: elt = 5*x + y + x*z + sage: c = elt.conjugate(); c + -x*z - 5*x - y + 3 + sage: c.conjugate() == elt + True + + TESTS: + + We check that the conjugate is an involution:: + + sage: Q = QuadraticForm(ZZ, 3, [1,2,3,4,5,6]) + sage: Cl. = CliffordAlgebra(Q) + sage: all(x.conjugate().conjugate() == x for x in Cl.basis()) + True + """ + return self.reflection().transpose() + + clifford_conjugate = conjugate + + +cdef class ExteriorAlgebraElement(CliffordAlgebraElement): + """ + An element of an exterior algebra. + """ + cdef _mul_(self, other): + """ + Return ``self`` multiplied by ``other``. + + INPUT: + + - ``other`` -- element of the same exterior algebra as ``self`` + + EXAMPLES:: + + sage: E. = ExteriorAlgebra(QQ) + sage: x*y + x*y + sage: y*x + -x*y + sage: z*y*x + -x*y*z + sage: (x*z)*y + -x*y*z + sage: (3*x + y)^2 + 0 + sage: (x - 3*y + z/3)^2 + 0 + sage: (x+y) * (y+z) + x*y + x*z + y*z + + sage: E. = ExteriorAlgebra(QQ) + sage: (x * y) * (w * z) + -x*y*z*w + sage: x * y * w * z + -x*y*z*w + sage: (z * w) * (x * y) + x*y*z*w + + sage: E. = ExteriorAlgebra(QQ) + sage: r = sum(E.basis()) + sage: r*r + 4*a*b*c*d + 4*a*b*c + 4*a*b*d + 4*a*c*d + 4*b*c*d + + 2*a*b + 2*a*c + 2*a*d + 2*b*c + 2*b*d + 2*c*d + + 2*a + 2*b + 2*c + 2*d + 1 + """ + cdef Parent P = self._parent + zero = P._base.zero() + cdef dict d + cdef ExteriorAlgebraElement rhs = other + cdef list to_remove + + cdef FrozenBitset ml, mr, t + cdef Py_ssize_t n, num_cross, tot_cross, i, j + + # Special case: one of them is zero + if not self._monomial_coefficients: + return self + if not rhs._monomial_coefficients: + return rhs + + # Special case: other is a single term + if len(rhs._monomial_coefficients) == 1: + mr, cr = next(iter(rhs._monomial_coefficients.items())) + return self._mul_self_term(mr, cr) + + # Special case: self is a single term + if len(self._monomial_coefficients) == 1: + ml, cl = next(iter(self._monomial_coefficients.items())) + return rhs._mul_term_self(ml, cl) + + # Do some special processing for the constant monomial in ml + ml = FrozenBitset() + if ml in self._monomial_coefficients: + const_coeff = self._monomial_coefficients[ml] + d = dict(rhs._monomial_coefficients) # Make a shallow copy + to_remove = [] + if const_coeff != P._base.one(): + for k in d: + d[k] *= const_coeff + if not d[k]: # there might be zero divisors + to_remove.append(k) + for k in to_remove: + del d[k] + else: + d = {} + + n = P.ngens() + for ml, cl in self._monomial_coefficients.items(): # ml for "monomial on the left" + if ml.isempty(): # We already handled the trivial element + continue + for mr,cr in rhs._monomial_coefficients.items(): # mr for "monomial on the right" + if mr.isempty(): + t = ml + else: + if not ml.isdisjoint(mr): + # if they intersect nontrivially, move along. + continue + t = ml._union(mr) + it = iter(mr) + j = next(it) + + num_cross = 0 # keep track of the number of signs + tot_cross = 0 + for i in ml: + while i > j: + num_cross += 1 + try: + j = next(it) + except StopIteration: + j = n + 1 + tot_cross += num_cross + if tot_cross % 2: + cr = -cr + + val = d.get(t, zero) + cl * cr + if not val: + del d[t] + else: + d[t] = val + + return self.__class__(P, d) + + cdef CliffordAlgebraElement _mul_self_term(self, FrozenBitset supp, coeff): + r""" + Multiply ``self * term`` with the ``term`` having support ``supp`` + and coefficient ``coeff``. + + EXAMPLES:: + + sage: E. = ExteriorAlgebra(QQ) + sage: r = sum(E.basis()) + sage: x * y # indirect doctest + x*y + sage: y * x # indirect doctest + -x*y + sage: r * x # indirect doctest + x*y*z - x*y - x*z + x + sage: r * -x # indirect doctest + -x*y*z + x*y + x*z - x + sage: r * (2*x) # indirect doctest + 2*x*y*z - 2*x*y - 2*x*z + 2*x + sage: r * y # indirect doctest + -x*y*z + x*y - y*z + y + sage: r * z # indirect doctest + x*y*z + x*z + y*z + z + sage: r * (x*y) # indirect doctest + x*y*z + x*y + sage: r * (-x*y) # indirect doctest + -x*y*z - x*y + sage: r * (x*y*z) # indirect doctest + x*y*z + sage: r * 1 == r # indirect doctest + True + sage: r * -1 == -r # indirect doctest + True + sage: r * 2 # indirect doctest + 2*x*y*z + 2*x*y + 2*x*z + 2*y*z + 2*x + 2*y + 2*z + 2 + """ + cdef dict d + cdef list to_remove + cdef Py_ssize_t num_cross, tot_cross, i, j + cdef FrozenBitset ml + + if supp.isempty(): # Multiplication by a base ring element + if coeff == self._parent._base.one(): + return self + if coeff == -self._parent._base.one(): + return self._neg_() + + return type(self)(self._parent, + scal(coeff, self._monomial_coefficients, + factor_on_left=False)) + + n = self._parent.ngens() + d = {} + for ml, cl in self._monomial_coefficients.items(): # ml for "monomial on the left" + if not ml.isdisjoint(supp): + # if they intersect nontrivially, move along. + continue + t = ml._union(supp) + it = iter(supp) + j = next(it) + + num_cross = 0 # keep track of the number of signs + tot_cross = 0 + for i in ml: + while i > j: + num_cross += 1 + try: + j = next(it) + except StopIteration: + j = n + 1 + tot_cross += num_cross + if tot_cross % 2: + d[t] = -cl + else: + d[t] = cl + + if coeff == -self._parent._base.one(): + for k in d: + d[k] = -d[k] + elif coeff != self._parent._base.one(): + to_remove = [] + for k in d: + d[k] *= coeff + if not d[k]: # there might be zero divisors + to_remove.append(k) + for k in to_remove: + del d[k] + return type(self)(self._parent, d) + + cdef CliffordAlgebraElement _mul_term_self(self, FrozenBitset supp, coeff): + r""" + Multiply ``term * self`` with the ``term`` having support ``supp`` + and coefficient ``coeff``. + + EXAMPLES:: + + sage: E. = ExteriorAlgebra(QQ) + sage: r = sum(E.basis()) + sage: x * r # indirect doctest + x*y*z + x*y + x*z + x + sage: (-x) * r # indirect doctest + -x*y*z - x*y - x*z - x + sage: (2*x) * r # indirect doctest + 2*x*y*z + 2*x*y + 2*x*z + 2*x + sage: y * r # indirect doctest + -x*y*z - x*y + y*z + y + sage: z * r # indirect doctest + x*y*z - x*z - y*z + z + sage: (x*y) * r # indirect doctest + x*y*z + x*y + sage: (-x*y) * r # indirect doctest + -x*y*z - x*y + sage: (x*y*z) * r # indirect doctest + x*y*z + sage: 1 * r == r # indirect doctest + True + sage: -1 * r == -r # indirect doctest + True + sage: 2 * r # indirect doctest + 2*x*y*z + 2*x*y + 2*x*z + 2*y*z + 2*x + 2*y + 2*z + 2 + """ + cdef dict d + cdef list to_remove + cdef Py_ssize_t n, num_cross, tot_cross, i, j + cdef FrozenBitset mr, t + + if supp.isempty(): # Multiplication by a base ring element + if coeff == self._parent._base.one(): + return self + if coeff == -self._parent._base.one(): + return self._neg_() + + return type(self)(self._parent, + scal(coeff, self._monomial_coefficients, + factor_on_left=True)) + + n = self._parent.ngens() + d = {} + mr = FrozenBitset() + # We need to special case the constant coefficient + const_coeff = None + if mr in self._monomial_coefficients: + const_coeff = self._monomial_coefficients.pop(mr) + d[supp] = const_coeff + + for mr, cr in self._monomial_coefficients.items(): # mr for "monomial on the right" + if not supp.isdisjoint(mr): + # if they intersect nontrivially, move along. + continue + t = supp._union(mr) + it = iter(mr) + j = next(it) # We assume mr is non-empty here + + num_cross = 0 # keep track of the number of signs + tot_cross = 0 + for i in supp: + while i > j: + num_cross += 1 + try: + j = next(it) + except StopIteration: + j = n + 1 + tot_cross += num_cross + if tot_cross % 2: + d[t] = -cr + else: + d[t] = cr + + if coeff == -self._parent._base.one(): + for k in d: + d[k] = -d[k] + elif coeff != self._parent._base.one(): + to_remove = [] + for k in d: + d[k] = coeff * d[k] # This will work for non-commutative base rings + if not d[k]: # there might be zero divisors + to_remove.append(k) + for k in to_remove: + del d[k] + + # Add back the constant coefficient since we removed it for the special case + if const_coeff is not None: + self._monomial_coefficients[FrozenBitset()] = const_coeff + return type(self)(self._parent, d) + + def reduce(self, I, left=True): + r""" + Reduce ``self`` with respect to the elements in ``I``. + + INPUT: + + - ``I`` -- a list of exterior algebra elements or an ideal + - ``left`` -- boolean; if reduce as a left ideal (``True``) + or right ideal (``False``), ignored if ``I`` is an ideal + + EXAMPLES:: + + sage: E. = ExteriorAlgebra(QQ) + sage: f = (a + b*c) * d + sage: f.reduce([a + b*c], True) + 2*a*d + sage: f.reduce([a + b*c], False) + 0 + + sage: I = E.ideal([a + b*c]) + sage: f.reduce(I) + 0 + """ + from sage.algebras.clifford_algebra import ExteriorAlgebraIdeal + if isinstance(I, ExteriorAlgebraIdeal): + return I.reduce(self) + + f = self + E = self._parent + + cdef FrozenBitset lm, s + for g in I: + lm = g.leading_support() + reduction = True + while reduction: + supp = f.support() + reduction = False + for s in supp: + if lm <= s: + reduction = True + mon = E.monomial(s - lm) + if left: + gp = mon * g + f = f - f[s] / gp[s] * gp + else: + gp = g * mon + f = f - f[s] / gp[s] * gp + break + return f + + def interior_product(self, x): + r""" + Return the interior product (also known as antiderivation) of + ``self`` with respect to ``x`` (that is, the element + `\iota_{x}(\text{self})` of the exterior algebra). + + If `V` is an `R`-module, and if `\alpha` is a fixed element of + `V^*`, then the *interior product* with respect to `\alpha` is + an `R`-linear map + `i_{\alpha} \colon \Lambda(V) \to \Lambda(V)`, determined by + the following requirements: + + - `i_{\alpha}(v) = \alpha(v)` for all `v \in V = \Lambda^1(V)`, + - it is a graded derivation of degree `-1`: all `x` and `y` + in `\Lambda(V)` satisfy + + .. MATH:: + + i_{\alpha}(x \wedge y) = (i_{\alpha} x) \wedge y + + (-1)^{\deg x} x \wedge (i_{\alpha} y). + + It can be shown that this map `i_{\alpha}` is graded of + degree `-1` (that is, sends `\Lambda^k(V)` into + `\Lambda^{k-1}(V)` for every `k`). + + When `V` is a finite free `R`-module, the interior product can + also be defined by + + .. MATH:: + + (i_{\alpha} \omega)(u_1, \ldots, u_k) + = \omega(\alpha, u_1, \ldots, u_k), + + where `\omega \in \Lambda^k(V)` is thought of as an + alternating multilinear mapping from + `V^* \times \cdots \times V^*` to `R`. + + Since Sage is only dealing with exterior powers of modules + of the form `R^d` for some nonnegative integer `d`, the + element `\alpha \in V^*` can be thought of as an element of + `V` (by identifying the standard basis of `V = R^d` with its + dual basis). This is how `\alpha` should be passed to this + method. + + We then extend the interior product to all + `\alpha \in \Lambda (V^*)` by + + .. MATH:: + + i_{\beta \wedge \gamma} = i_{\gamma} \circ i_{\beta}. + + INPUT: + + - ``x`` -- element of (or coercing into) `\Lambda^1(V)` + (for example, an element of `V`); this plays the role of + `\alpha` in the above definition + + EXAMPLES:: + + sage: E. = ExteriorAlgebra(QQ) + sage: x.interior_product(x) + 1 + sage: (x + x*y).interior_product(2*y) + -2*x + sage: (x*z + x*y*z).interior_product(2*y - x) + -2*x*z - y*z - z + sage: x.interior_product(E.one()) + x + sage: E.one().interior_product(x) + 0 + sage: x.interior_product(E.zero()) + 0 + sage: E.zero().interior_product(x) + 0 + + REFERENCES: + + - :wikipedia:`Exterior_algebra#Interior_product` + """ + P = self._parent + return P.sum([c * cx * P.interior_product_on_basis(m, mx) + for m,c in self for mx,cx in x]) + + antiderivation = interior_product + + def hodge_dual(self): + r""" + Return the Hodge dual of ``self``. + + The Hodge dual of an element `\alpha` of the exterior algebra is + defined as `i_{\alpha} \sigma`, where `\sigma` is the volume + form + (:meth:`~sage.algebras.clifford_algebra.ExteriorAlgebra.volume_form`) + and `i_{\alpha}` denotes the antiderivation function with + respect to `\alpha` (see :meth:`interior_product` for the + definition of this). + + .. NOTE:: + + The Hodge dual of the Hodge dual of a homogeneous element + `p` of `\Lambda(V)` equals `(-1)^{k(n-k)} p`, where + `n = \dim V` and `k = \deg(p) = |p|`. + + EXAMPLES:: + + sage: E. = ExteriorAlgebra(QQ) + sage: x.hodge_dual() + y*z + sage: (x*z).hodge_dual() + -y + sage: (x*y*z).hodge_dual() + 1 + sage: [a.hodge_dual().hodge_dual() for a in E.basis()] + [1, x, y, z, x*y, x*z, y*z, x*y*z] + sage: (x + x*y).hodge_dual() + y*z + z + sage: (x*z + x*y*z).hodge_dual() + -y + 1 + sage: E = ExteriorAlgebra(QQ, 'wxyz') + sage: [a.hodge_dual().hodge_dual() for a in E.basis()] + [1, -w, -x, -y, -z, w*x, w*y, w*z, x*y, x*z, y*z, + -w*x*y, -w*x*z, -w*y*z, -x*y*z, w*x*y*z] + """ + volume_form = self._parent.volume_form() + return volume_form.interior_product(self) + + def constant_coefficient(self): + """ + Return the constant coefficient of ``self``. + + .. TODO:: + + Define a similar method for general Clifford algebras once + the morphism to exterior algebras is implemented. + + EXAMPLES:: + + sage: E. = ExteriorAlgebra(QQ) + sage: elt = 5*x + y + x*z + 10 + sage: elt.constant_coefficient() + 10 + sage: x.constant_coefficient() + 0 + """ + return self._monomial_coefficients.get(self._parent.one_basis(), + self._parent._base.zero()) + + def scalar(self, other): + r""" + Return the standard scalar product of ``self`` with ``other``. + + The standard scalar product of `x, y \in \Lambda(V)` is + defined by `\langle x, y \rangle = \langle x^t y \rangle`, where + `\langle a \rangle` denotes the degree-0 term of `a`, and where + `x^t` denotes the transpose + (:meth:`~sage.algebras.clifford_algebra.CliffordAlgebraElement.transpose`) + of `x`. + + .. TODO:: + + Define a similar method for general Clifford algebras once + the morphism to exterior algebras is implemented. + + EXAMPLES:: + + sage: E. = ExteriorAlgebra(QQ) + sage: elt = 5*x + y + x*z + sage: elt.scalar(z + 2*x) + 0 + sage: elt.transpose() * (z + 2*x) + -2*x*y + 5*x*z + y*z + """ + return (self.transpose() * other).constant_coefficient() + +cdef class CohomologyRAAGElement(CliffordAlgebraElement): + """ + An element in the cohomology of a right-angled Artin group. + + .. SEEALSO:: + + :class:`~sage.groups.raag.CohomologyRAAG` + """ + cdef _mul_(self, other): + """ + Return ``self`` multiplied by ``other``. + + EXAMPLES:: + + sage: C4 = graphs.CycleGraph(4) + sage: A = groups.misc.RightAngledArtin(C4) + sage: H = A.cohomology() + sage: b = sum(H.basis()) + sage: b * b + 2*e0*e2 + 2*e1*e3 + 2*e0 + 2*e1 + 2*e2 + 2*e3 + 1 + """ + zero = self._parent._base.zero() + cdef frozenset I = frozenset(self._parent._indices) + cdef dict d = {} + cdef list t + cdef tuple tp + cdef tuple ml, mr + cdef Py_ssize_t pos, i, j + cdef bint negate + + for ml, cl in self._monomial_coefficients.items(): + for mr, cr in other._monomial_coefficients.items(): + # Create the next term + tp = tuple(sorted(mr + ml)) + if any(tp[i] == tp[i+1] for i in range(len(tp)-1)): # e_i ^ e_i = 0 + continue + if tp not in I: # not an independent set, so this term is also 0 + continue + + t = list(mr) + negate = False + for i in reversed(ml): + pos = 0 + for j in t: + assert i != j + if i < j: + break + pos += 1 + negate = not negate + t.insert(pos, i) + + if negate: + cr = -cr + + d[tp] = d.get(tp, zero) + cl * cr + if d[tp] == zero: + del d[tp] + + return self.__class__(self._parent, d) + diff --git a/src/sage/algebras/commutative_dga.py b/src/sage/algebras/commutative_dga.py index ff80254caf9..a05ede46e52 100644 --- a/src/sage/algebras/commutative_dga.py +++ b/src/sage/algebras/commutative_dga.py @@ -114,7 +114,7 @@ def sorting_keys(element): OUTPUT: - Its coordinates in the corresponding cohomology_raw quotoent vector space + Its coordinates in the corresponding cohomology_raw quotient vector space EXAMPLES:: diff --git a/src/sage/algebras/exterior_algebra_groebner.pxd b/src/sage/algebras/exterior_algebra_groebner.pxd new file mode 100644 index 00000000000..d00fb2e8560 --- /dev/null +++ b/src/sage/algebras/exterior_algebra_groebner.pxd @@ -0,0 +1,56 @@ +""" +Exterior algebras Gröbner bases +""" + +from sage.data_structures.bitset cimport FrozenBitset +from sage.rings.integer cimport Integer +from sage.algebras.clifford_algebra_element cimport CliffordAlgebraElement +from sage.structure.parent cimport Parent +from sage.structure.element cimport MonoidElement + +cdef long degree(FrozenBitset X) +cdef CliffordAlgebraElement build_monomial(Parent E, FrozenBitset supp) + +cdef class GBElement: + cdef CliffordAlgebraElement elt + cdef FrozenBitset ls # leading support as a bitset + cdef Integer lsi # leading support as an Integer + +# Grobner basis functions +cdef class GroebnerStrategy: + cdef Parent E # the exterior algebra + cdef int side + cdef MonoidElement ideal + cdef bint homogeneous + cdef Integer rank + cdef public tuple groebner_basis + + cdef inline GBElement build_elt(self, CliffordAlgebraElement f) + cdef inline GBElement prod_GB_term(self, GBElement f, FrozenBitset t) + cdef inline GBElement prod_term_GB(self, FrozenBitset t, GBElement f) + cdef inline bint build_S_poly(self, GBElement f, GBElement g) + + cdef inline FrozenBitset leading_support(self, CliffordAlgebraElement f) + cdef inline partial_S_poly_left(self, GBElement f, GBElement g) + cdef inline partial_S_poly_right(self, GBElement f, GBElement g) + cdef set preprocessing(self, list P, list G) + cdef list reduction(self, list P, list G) + + cpdef CliffordAlgebraElement reduce(self, CliffordAlgebraElement f) + cdef bint reduce_single(self, CliffordAlgebraElement f, CliffordAlgebraElement g) except -1 + cdef int reduced_gb(self, list G) except -1 + + # These are the methods that determine the ordering of the monomials. + # These must be implemented in subclasses. Declare them as "inline" there. + cdef Integer bitset_to_int(self, FrozenBitset X) + cdef FrozenBitset int_to_bitset(self, Integer n) + +cdef class GroebnerStrategyNegLex(GroebnerStrategy): + pass + +cdef class GroebnerStrategyDegRevLex(GroebnerStrategy): + pass + +cdef class GroebnerStrategyDegLex(GroebnerStrategy): + pass + diff --git a/src/sage/algebras/exterior_algebra_groebner.pyx b/src/sage/algebras/exterior_algebra_groebner.pyx new file mode 100644 index 00000000000..98e338d3468 --- /dev/null +++ b/src/sage/algebras/exterior_algebra_groebner.pyx @@ -0,0 +1,718 @@ +r""" +Exterior algebras Gröbner bases + +This contains the backend implementations in Cython for the Gröbner bases +of exterior algebra. + +AUTHORS: + +- Trevor K. Karn, Travis Scrimshaw (July 2022): Initial implementation +""" + +#***************************************************************************** +# Copyright (C) 2022 Trevor K. Karn +# (C) 2022 Travis Scrimshaw +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from cysignals.signals cimport sig_check +from sage.libs.gmp.mpz cimport mpz_sizeinbase, mpz_setbit, mpz_tstbit, mpz_cmp_si, mpz_sgn +from sage.data_structures.bitset_base cimport (bitset_t, bitset_init, bitset_first, + bitset_next, bitset_set_to, bitset_len) +from sage.structure.parent cimport Parent +from sage.structure.richcmp cimport richcmp, rich_to_bool +from sage.data_structures.blas_dict cimport iaxpy +from copy import copy + +cdef inline long degree(FrozenBitset X): + """ + Compute the degree of ``X``. + """ + return bitset_len(X._bitset) + + +cdef inline CliffordAlgebraElement build_monomial(Parent E, FrozenBitset supp): + """ + Helper function for the fastest way to build a monomial. + """ + return E.element_class(E, {supp: E._base.one()}) + +cdef class GBElement: + """ + Helper class for storing an element with its leading support both as + a :class:`FrozenBitset` and an :class:`Integer`. + """ + def __init__(self, CliffordAlgebraElement x, FrozenBitset ls, Integer n): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: from sage.algebras.exterior_algebra_groebner import GBElement + sage: E. = ExteriorAlgebra(QQ) + sage: X = GBElement(a, a.leading_support(), 1) + sage: TestSuite(X).run(skip="_test_pickling") + """ + self.elt = x + self.ls = ls + self.lsi = n + + def __hash__(self): + """ + Return the hash of ``self``. + + EXAMPLES:: + + sage: from sage.algebras.exterior_algebra_groebner import GBElement + sage: E. = ExteriorAlgebra(QQ) + sage: X = GBElement(a, a.leading_support(), 1) + sage: hash(X) == 1 + True + """ + return int(self.lsi) + + def __richcmp__(self, other, int op): + """ + Rich compare ``self`` with ``other`` by ``op``. + + EXAMPLES:: + + sage: from sage.algebras.exterior_algebra_groebner import GBElement + sage: E. = ExteriorAlgebra(QQ) + sage: X = GBElement(a, a.leading_support(), 1) + sage: Y = GBElement(a*b, (a*b).leading_support(), 3) + sage: X == X + True + sage: X == Y + False + sage: X != Y + True + """ + if self is other: + return rich_to_bool(op, 0) + return richcmp(self.elt, ( other).elt, op) + +cdef class GroebnerStrategy: + """ + A strategy for computing a Gröbner basis. + + INPUT: + + - ``I`` -- the ideal + """ + def __init__(self, I): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: from sage.algebras.exterior_algebra_groebner import GroebnerStrategy + sage: E. = ExteriorAlgebra(QQ) + sage: I = E.ideal([a + 1], side="left") + sage: G = GroebnerStrategy(I) + sage: TestSuite(G).run(skip="_test_pickling") + """ + self.ideal = I + self.groebner_basis = (None,) + self.E = I.ring() + self.homogeneous = I._homogeneous + self.rank = Integer(self.E.ngens()) + if self.homogeneous or I.side() == "left": + self.side = 0 + elif I.side() == "right": + self.side = 1 + else: + self.side = 2 + + cdef inline FrozenBitset leading_support(self, CliffordAlgebraElement f): + """ + Return the leading support of the exterior algebra element ``f``. + """ + cdef dict mc = f._monomial_coefficients + return self.int_to_bitset(max(self.bitset_to_int(k) for k in mc)) + + cdef inline partial_S_poly_left(self, GBElement f, GBElement g): + """ + Compute one half of the `S`-polynomial for ``f`` and ``g``. + + This computes: + + .. MATH:: + + LCM(LM(f), LM(g)) / LT(f) \cdot f. + """ + cdef FrozenBitset D = g.ls.difference(f.ls) + cdef GBElement ret = self.prod_term_GB(D, f) + inv = ~ret.elt[ret.ls] + for k in ret.elt._monomial_coefficients: + ret.elt._monomial_coefficients[k] *= inv + return ret + + cdef inline partial_S_poly_right(self, GBElement f, GBElement g): + """ + Compute one half of the `S`-polynomial for ``f`` and ``g``. + + This computes: + + .. MATH:: + + f \cdot LCM(LM(f), LM(g)) / LT(f). + """ + cdef FrozenBitset D = g.ls.difference(f.ls) + cdef GBElement ret = self.prod_GB_term(f, D) + inv = ~ret.elt[ret.ls] + for k in ret.elt._monomial_coefficients: + ret.elt._monomial_coefficients[k] *= inv + return ret + + cdef inline GBElement build_elt(self, CliffordAlgebraElement f): + """ + Convert ``f`` into a ``GBElement``. + """ + cdef dict mc = f._monomial_coefficients + #if not mc: + # return GBElement(f, FrozenBitset(), -1) + cdef Integer r = max(self.bitset_to_int(k) for k in mc) + return GBElement(f, self.int_to_bitset(r), r) + + cdef inline GBElement prod_GB_term(self, GBElement f, FrozenBitset t): + """ + Return the GBElement corresponding to ``f * t``. + + .. WARNING:: + + This assumes the leading support is ``f.ls._union(t)``. + """ + ret = f.elt._mul_self_term(self.E, self.E._base.one()) + cdef FrozenBitset ls = f.ls._union(t) + return GBElement( ret, ls, self.bitset_to_int(ls)) + + cdef inline GBElement prod_term_GB(self, FrozenBitset t, GBElement f): + """ + Return the GBElement corresponding to ``t * f``. + + .. WARNING:: + + This assumes the leading support is ``f.ls._union(t)``. + """ + ret = f.elt._mul_term_self(t, self.E._base.one()) + cdef FrozenBitset ls = f.ls._union(t) + return GBElement( ret, ls, self.bitset_to_int(ls)) + + cdef inline bint build_S_poly(self, GBElement f, GBElement g): + r""" + Check to see if we should build the `S`-polynomial. + + For homogeneous ideals, we throw out all those pairs `(f, g)` such that + + .. MATH:: + + LCM(LM(f), LM(g)) == LM(f) \cdot LM(g). + """ + if not self.homogeneous: + return True + + return ( f.ls.intersection(g.ls)).isempty() + + cdef inline set preprocessing(self, list P, list G): + """ + Perform the preprocessing step. + """ + cdef GBElement f, g, f0, f1 + cdef set additions + + cdef set L = set() + if self.side == 1: + for f0, f1 in P: + if self.build_S_poly(f0, f1): + L.add(self.partial_S_poly_right(f0, f1)) + L.add(self.partial_S_poly_right(f1, f0)) + else: # We compute a left Gröbner basis for two-sided ideals + for f0, f1 in P: + if self.build_S_poly(f0, f1): + L.add(self.partial_S_poly_left(f0, f1)) + L.add(self.partial_S_poly_left(f1, f0)) + + if self.side == 2 and not self.homogeneous: + # Add in all S-poly times positive degree monomials + one = self.E._base.one() + additions = set(( f).elt._mul_self_term(t, one) for t in self.E._indices for f in L) + L.update(self.build_elt(f) for f in additions if f) + + cdef set done = set(( f).ls for f in L) + cdef set monL = set() + for f in L: + monL.update(f.elt._monomial_coefficients) + monL.difference_update(done) + + while monL: + m = self.int_to_bitset(max(self.bitset_to_int(k) for k in monL)) + done.add(m) + monL.remove(m) + for g in G: + lm = g.ls + if lm <= m: + f = self.prod_term_GB( m.difference(lm), g) + if f in L: + break + monL.update(set(f.elt._monomial_coefficients) - done) + L.add(f) + break + return L + + cdef inline list reduction(self, list P, list G): + """ + Perform the reduction of ``P`` mod ``G`` in ``E``. + """ + cdef set L = self.preprocessing(P, G) + cdef Py_ssize_t i + from sage.matrix.constructor import matrix + cdef Integer r = Integer(2) ** self.rank - Integer(1) # r for "rank" or "reverso" + M = matrix({(i, r - self.bitset_to_int( m)): c + for i,f in enumerate(L) + for m,c in ( f).elt._monomial_coefficients.items()}, + sparse=True) + M.echelonize() # Do this in place + lead_supports = set(( f).lsi for f in L) + return [GBElement(self.E.element_class(self.E, {self.int_to_bitset(r - Integer(j)): c for j,c in M[i].iteritems()}), + self.int_to_bitset(Integer(r - p)), + Integer(r - p)) + for i, p in enumerate(M.pivots()) + if r - Integer(p) not in lead_supports] + + def compute_groebner(self, reduced=True): + r""" + Compute the (``reduced``) left/right Gröbner basis for the ideal ``I``. + + EXAMPLES:: + + sage: E. = ExteriorAlgebra(QQ) + sage: I = E.ideal([x*y - x, x*y - 1], side="left") + sage: I.groebner_basis() # indirect doctest + (1,) + sage: J = E.ideal([x*y - x, 2*x*y - 2], side="left") + sage: J.groebner_basis() # indirect doctest + (1,) + + sage: E. = ExteriorAlgebra(QQ) + sage: I = E.ideal([a+b*c], side="left") + sage: I.groebner_basis() # indirect doctest + (b*c + a,) + sage: I = E.ideal([a+b*c], side="twosided") + sage: I.groebner_basis() # indirect doctest + (a*b, a*c, b*c + a, a*d) + """ + cdef FrozenBitset p0, p1 + cdef long deg + cdef Py_ssize_t i, j, k + cdef set additions + cdef GBElement f0, f1 + cdef list G = [], Gp + cdef dict constructed = {} + cdef CliffordAlgebraElement f + + for f in self.ideal.gens(): + if not f: # Remove 0s + continue + f0 = self.build_elt(f) + if f0.lsi in constructed: + if f0 in constructed[f0.lsi]: # Already there + continue + constructed[f0.lsi].add(f0) + else: + constructed[f0.lsi] = set([f0]) + G.append(f0) + + if self.side == 2 and not self.homogeneous: + # Add in all S-poly times positive degree monomials + one = self.E._base.one() + for t in self.E._indices: + for f0 in G: + f = f0.elt._mul_self_term(t, one) + if not f: + continue + f1 = self.build_elt(f) + if f1.lsi in constructed: + if f1 in constructed[f1.lsi]: # Already there + continue + constructed[f1.lsi].add(f1) + else: + constructed[f1.lsi] = set([f1]) + G.append(f1) + + cdef Py_ssize_t n = len(G) + cdef dict P = {} + + for i in range(n): + f0 = G[i] + p0 = f0.ls + for j in range(i+1, n): + f1 = G[j] + p1 = f1.ls + deg = degree( (p0._union(p1))) + if deg in P: + P[deg].append((f0, f1)) + else: + P[deg] = [(f0, f1)] + + while P: + sig_check() + Pp = P.pop(min(P)) # The selection: lowest lcm degree + Gp = self.reduction(Pp, G) + # Add the elements Gp to G when a new element is found + for f0 in Gp: + if f0.lsi in constructed: + if f0 in constructed[f0.lsi]: # Already there + continue + constructed[f0.lsi].add(f0) + else: + constructed[f0.lsi] = set([f0]) + G.append(f0) + # Find the degress of the new pairs + for j in range(n, len(G)): + f1 = G[j] + p1 = f1.ls + for i in range(j): + f0 = G[i] + p0 = f0.ls + deg = degree( (p0._union(p1))) + if deg in P: + P[deg].append((f0, f1)) + else: + P[deg] = [(f0, f1)] + n = len(G) + + G.sort(key=lambda x: ( x).lsi) + + if not reduced: + self.groebner_basis = tuple([( f0).elt for f0 in G if ( f0).elt]) + return + self.reduced_gb(G) + + cdef int reduced_gb(self, list G) except -1: + """ + Convert the Gröbner basis ``G`` into a reduced Gröbner basis. + """ + cdef Py_ssize_t i, j, k + cdef GBElement f0, f1 + + # Now that we have a Gröbner basis, we make this into a reduced Gröbner basis + cdef tuple supp + cdef bint did_reduction + cdef FrozenBitset lm, s + cdef Integer r + cdef Py_ssize_t num_zeros = 0 + cdef Py_ssize_t n = len(G) + cdef set pairs = set((i, j) for i in range(n) for j in range(n) if i != j) + + while pairs: + sig_check() + i,j = pairs.pop() + f0 = G[i] + f1 = G[j] + assert f0.elt._monomial_coefficients is not f1.elt._monomial_coefficients, (i,j) + # We perform the classical reduction algorithm here on each pair + # TODO: Make this faster by using the previous technique? + if self.reduce_single(f0.elt, f1.elt): + if f0.elt: + G[i] = self.build_elt(f0.elt) + pairs.update((k, i) for k in range(n) if k != i) + else: + G[i] = GBElement(f0.elt, FrozenBitset(), Integer(2)**self.rank + 1) + num_zeros += 1 + pairs.difference_update((k, i) for k in range(n) if k != i) + pairs.difference_update((i, k) for k in range(n) if k != i) + + G.sort(key=lambda x: ( x).lsi) + for i in range(len(G)-num_zeros): + f0 = G[i] + if f0.elt: + inv = ~f0.elt[f0.ls] + for key in f0.elt._monomial_coefficients: + f0.elt._monomial_coefficients[key] *= inv + self.groebner_basis = tuple([f0.elt for f0 in G[:len(G)-num_zeros]]) + return 0 + + def reduce_computed_gb(self): + """ + Convert the computed Gröbner basis to a reduced Gröbner basis. + + sage: E. = ExteriorAlgebra(QQ) + sage: I = E.ideal([x+y*z]) + sage: I.groebner_basis(reduced=False) + (x*y, x*z, y*z + x, x*y*z) + sage: I._groebner_strategy.reduce_computed_gb() + sage: I._groebner_strategy.groebner_basis + (x*y, x*z, y*z + x) + """ + if self.groebner_basis == [(None,)]: + raise ValueError("Gröbner basis not yet computed") + cdef list G = [self.build_elt(f) for f in self.groebner_basis] + self.reduced_gb(G) + + cpdef CliffordAlgebraElement reduce(self, CliffordAlgebraElement f): + """ + Reduce ``f`` modulo the ideal with Gröbner basis ``G``. + + EXAMPLES:: + + sage: E. = ExteriorAlgebra(QQ) + sage: rels = [c*d*e - b*d*e + b*c*e - b*c*d, + ....: c*d*e - a*d*e + a*c*e - a*c*d, + ....: b*d*e - a*d*e + a*b*e - a*b*d, + ....: b*c*e - a*c*e + a*b*e - a*b*c, + ....: b*c*d - a*c*d + a*b*d - a*b*c] + sage: I = E.ideal(rels) + sage: GB = I.groebner_basis() + sage: I._groebner_strategy.reduce(a*b*e) + a*b*e + sage: I._groebner_strategy.reduce(b*d*e) + a*b*d - a*b*e + a*d*e + sage: I._groebner_strategy.reduce(c*d*e) + a*c*d - a*c*e + a*d*e + sage: I._groebner_strategy.reduce(a*b*c*d*e) + 0 + sage: I._groebner_strategy.reduce(a*b*c*d) + 0 + sage: I._groebner_strategy.reduce(E.zero()) + 0 + """ + if not f: + return f + # Make a copy to mutate + f = type(f)(f._parent, copy(f._monomial_coefficients)) + for g in self.groebner_basis: + self.reduce_single(f, g) + return f + + cdef bint reduce_single(self, CliffordAlgebraElement f, CliffordAlgebraElement g) except -1: + r""" + Reduce ``f`` by ``g``. + + .. WARNING:: + + This modifies the element ``f``. + """ + cdef FrozenBitset lm = self.leading_support(g), s, t + cdef bint did_reduction = True, was_reduced=False + cdef tuple supp + cdef CliffordAlgebraElement gp + + one = self.E._base.one() + while did_reduction: + did_reduction = False + for s in f._monomial_coefficients: + if lm.issubset(s): + t = s + did_reduction = True + was_reduced = True + break + if did_reduction: + if self.side == 0: + gp = g._mul_term_self(t - lm, one) + else: + gp = g._mul_self_term(t - lm, one) + coeff = f[t] / gp._monomial_coefficients[t] + iaxpy(-coeff, gp._monomial_coefficients, f._monomial_coefficients) + return was_reduced + + + cdef Integer bitset_to_int(self, FrozenBitset X): + raise NotImplementedError + + cdef FrozenBitset int_to_bitset(self, Integer n): + raise NotImplementedError + + def sorted_monomials(self, as_dict=False): + """ + Helper function to display the monomials in their term order + used by ``self``. + + EXAMPLES:: + + sage: from sage.algebras.exterior_algebra_groebner import * + sage: E. = ExteriorAlgebra(QQ) + sage: I = E.ideal([x, y]) + sage: GroebnerStrategyNegLex(I).sorted_monomials() + [1, x, y, x*y, z, x*z, y*z, x*y*z] + sage: GroebnerStrategyDegLex(I).sorted_monomials() + [1, x, y, z, x*y, x*z, y*z, x*y*z] + sage: GroebnerStrategyDegRevLex(I).sorted_monomials() + [1, z, y, x, y*z, x*z, x*y, x*y*z] + + sage: E. = ExteriorAlgebra(QQ) + sage: I = E.ideal([a, b]) + sage: GroebnerStrategyNegLex(I).sorted_monomials() + [1, + a, + b, a*b, + c, a*c, b*c, a*b*c, + d, a*d, b*d, a*b*d, c*d, a*c*d, b*c*d, a*b*c*d] + sage: GroebnerStrategyDegLex(I).sorted_monomials() + [1, + a, b, c, d, + a*b, a*c, a*d, b*c, b*d, c*d, + a*b*c, a*b*d, a*c*d, b*c*d, + a*b*c*d] + sage: GroebnerStrategyDegRevLex(I).sorted_monomials() + [1, + d, c, b, a, + c*d, b*d, a*d, b*c, a*c, a*b, + b*c*d, a*c*d, a*b*d, a*b*c, + a*b*c*d] + """ + cdef FrozenBitset X + cdef Integer i + cdef list D = [self.bitset_to_int(X) for X in self.E._indices] + D.sort() + if as_dict: + return {i: build_monomial(self.E, self.int_to_bitset(i)) for i in D} + return [build_monomial(self.E, self.int_to_bitset(i)) for i in D] + + def monomial_to_int(self): + """ + Helper function to display the monomials in their term order + used by ``self``. + + EXAMPLES:: + + sage: from sage.algebras.exterior_algebra_groebner import * + sage: E. = ExteriorAlgebra(QQ) + sage: I = E.ideal([a, b]) + sage: GroebnerStrategyDegLex(I).monomial_to_int() + {1: 0, + a: 1, b: 2, c: 3, d: 4, + a*b: 5, a*c: 6, a*d: 7, b*c: 8, b*d: 9, c*d: 10, + a*b*c: 11, a*b*d: 12, a*c*d: 13, b*c*d: 14, + a*b*c*d: 15} + sage: GroebnerStrategyDegRevLex(I).monomial_to_int() + {1: 0, + a: 4, b: 3, c: 2, d: 1, + a*b: 10, a*c: 9, a*d: 7, b*c: 8, b*d: 6, c*d: 5, + a*b*c: 14, a*b*d: 13, a*c*d: 12, b*c*d: 11, + a*b*c*d: 15} + """ + B = self.E.basis() + return {B[X]: self.bitset_to_int(X) for X in self.E._indices} + +cdef class GroebnerStrategyNegLex(GroebnerStrategy): + """ + Gröbner basis strategy implementing neglex ordering. + """ + cdef inline Integer bitset_to_int(self, FrozenBitset X): + """ + Convert ``X`` to an :class:`Integer`. + """ + cdef Integer ret = Integer(0) + cdef long elt = bitset_first(X._bitset) + while elt >= 0: + mpz_setbit(ret.value, elt) + elt = bitset_next(X._bitset, elt + 1) + return ret + + cdef inline FrozenBitset int_to_bitset(self, Integer n): + """ + Convert a nonnegative integer ``n`` to a :class:`FrozenBitset`. + """ + cdef size_t i + + if mpz_sgn(n.value) == 0: + return FrozenBitset() + + cdef FrozenBitset ret = FrozenBitset() + cdef size_t s = mpz_sizeinbase(n.value, 2) + bitset_init(ret._bitset, s) + for i in range(s): + bitset_set_to(ret._bitset, i, mpz_tstbit(n.value, i)) + return ret + +cdef class GroebnerStrategyDegRevLex(GroebnerStrategy): + """ + Gröbner basis strategy implementing degree revlex ordering. + """ + cdef inline Integer bitset_to_int(self, FrozenBitset X): + """ + Convert ``X`` to an :class:`Integer`. + """ + if X.isempty(): + return Integer(0) + + cdef Integer n = self.rank + cdef long i, deg = degree(X) + cdef long r = 1 + cdef Integer t = Integer(0) + + cdef long elt = bitset_first(X._bitset) + while elt >= 0: + t += Integer(elt).binomial(r) + r += 1 + elt = bitset_next(X._bitset, elt + 1) + return Integer(sum(n.binomial(i) for i in range(deg+1)) - t - 1) + + cdef inline FrozenBitset int_to_bitset(self, Integer n): + """ + Convert a nonnegative integer ``n`` to a :class:`FrozenBitset`. + """ + cdef size_t i + + if mpz_sgn(n.value) == 0: + return FrozenBitset() + + cdef Py_ssize_t deg = 0 + cdef Integer binom = Integer(1) + while n >= binom: + n -= binom + deg += 1 + binom = self.rank.binomial(deg) + + # TODO: Cythonize the from_rank + from sage.combinat.combination import from_rank + return FrozenBitset([self.rank - val - 1 for val in from_rank(n, self.rank, deg)]) + +cdef class GroebnerStrategyDegLex(GroebnerStrategy): + """ + Gröbner basis strategy implementing degree lex ordering. + """ + cdef inline Integer bitset_to_int(self, FrozenBitset X): + """ + Convert ``X`` to an :class:`Integer`. + """ + if X.isempty(): + return Integer(0) + + cdef Integer n = self.rank + cdef long i, deg = degree(X) + cdef long r = deg + cdef Integer t = Integer(0) + + cdef long elt = bitset_first(X._bitset) + while elt >= 0: + t += Integer(n - 1 - elt).binomial(r) + r -= 1 + elt = bitset_next(X._bitset, elt + 1) + return Integer(sum(n.binomial(i) for i in range(deg+1)) - t - 1) + + cdef inline FrozenBitset int_to_bitset(self, Integer n): + """ + Convert a nonnegative integer ``n`` to a :class:`FrozenBitset`. + """ + cdef size_t i + + if mpz_sgn(n.value) == 0: + return FrozenBitset() + + cdef Py_ssize_t deg = 0 + cdef Integer binom = Integer(1) + while n >= binom: + n -= binom + deg += 1 + binom = self.rank.binomial(deg) + + from sage.combinat.combination import from_rank + return FrozenBitset(from_rank(n, self.rank, deg)) + diff --git a/src/sage/algebras/free_algebra.py b/src/sage/algebras/free_algebra.py index c53ab5da0e2..7286f62ce27 100644 --- a/src/sage/algebras/free_algebra.py +++ b/src/sage/algebras/free_algebra.py @@ -880,7 +880,8 @@ def g_algebra(self, relations, names=None, order='degrevlex', check=True): if d_poly: dmat[v2_ind,v1_ind] = d_poly from sage.rings.polynomial.plural import g_Algebra - return g_Algebra(base_ring, cmat, dmat, names = names or self.variable_names(), + return g_Algebra(base_ring, cmat, dmat, + names=names or self.variable_names(), order=order, check=check) def poincare_birkhoff_witt_basis(self): diff --git a/src/sage/algebras/group_algebra.py b/src/sage/algebras/group_algebra.py index 2a903447f3d..6821a64f68f 100644 --- a/src/sage/algebras/group_algebra.py +++ b/src/sage/algebras/group_algebra.py @@ -156,9 +156,16 @@ def _coerce_map_from_(self, S): sage: QG = G.algebra(QQ) sage: ZG = G.algebra(ZZ) sage: ZG.coerce_map_from(H) - Coercion map: + Composite map: From: Cyclic group of order 3 as a permutation group To: Algebra of Dihedral group of order 6 as a permutation group over Integer Ring + Defn: Coercion map: + From: Cyclic group of order 3 as a permutation group + To: Dihedral group of order 6 as a permutation group + then + Coercion map: + From: Dihedral group of order 6 as a permutation group + To: Algebra of Dihedral group of order 6 as a permutation group over Integer Ring sage: QG.coerce_map_from(ZG) Generic morphism: From: Algebra of Dihedral group of order 6 as a permutation group over Integer Ring @@ -172,6 +179,14 @@ def _coerce_map_from_(self, S): From: Algebra of Cyclic group of order 3 as a permutation group over Integer Ring To: Algebra of Dihedral group of order 6 as a permutation group over Rational Field + sage: H = PermutationGroup([ [(1,2), (3,4)], [(5,6,7),(12,14,18)] ]) + sage: kH = H.algebra(GF(2)) + sage: [a, b] = kH.gens() + sage: x = kH(a) + kH(b) + kH.one(); print(x) + () + (5,6,7)(12,14,18) + (1,2)(3,4) + sage: x*x #checks :trac:34292 + (5,7,6)(12,18,14) + As expected, there is no coercion when restricting the field:: @@ -188,11 +203,16 @@ def _coerce_map_from_(self, S): G = self.basis().keys() K = self.base_ring() - if G.has_coerce_map_from(S): + G_coercion = G.coerce_map_from(S) + if G_coercion is not None: from sage.categories.groups import Groups # No coercion for additive groups because of ambiguity of + # being the group action or addition of a new term. - return self.category().is_subcategory(Groups().Algebras(K)) + if not self.category().is_subcategory(Groups().Algebras(K)): + return None + if S is G: + return True + return self.coerce_map_from(G) * G_coercion if S in Sets.Algebras: S_K = S.base_ring() diff --git a/src/sage/algebras/hecke_algebras/ariki_koike_algebra.py b/src/sage/algebras/hecke_algebras/ariki_koike_algebra.py index 42a8de981b3..c1596761c99 100644 --- a/src/sage/algebras/hecke_algebras/ariki_koike_algebra.py +++ b/src/sage/algebras/hecke_algebras/ariki_koike_algebra.py @@ -1125,7 +1125,7 @@ def inverse_T(self, i): return self._from_dict({m: ~self._q, self.one_basis(): c}) class Element(CombinatorialFreeModule.Element): - def inverse(self): + def __invert__(self): r""" Return the inverse if ``self`` is a basis element. @@ -1134,7 +1134,7 @@ def inverse(self): sage: LT = algebras.ArikiKoike(3, 4).LT() sage: t = LT.T(1) * LT.T(2) * LT.T(3); t T[1,2,3] - sage: t.inverse() + sage: t.inverse() # indirect doctest (q^-3-3*q^-2+3*q^-1-1) + (q^-3-2*q^-2+q^-1)*T[3] + (q^-3-2*q^-2+q^-1)*T[2] + (q^-3-q^-2)*T[3,2] + (q^-3-2*q^-2+q^-1)*T[1] + (q^-3-q^-2)*T[1,3] @@ -1148,8 +1148,6 @@ def inverse(self): H = self.parent() return ~self[l,w] * H.prod(H.inverse_T(i) for i in reversed(w.reduced_word())) - __invert__ = inverse - class T(_Basis): r""" The basis of the Ariki-Koike algebra given by monomials of the diff --git a/src/sage/algebras/hecke_algebras/cubic_hecke_algebra.py b/src/sage/algebras/hecke_algebras/cubic_hecke_algebra.py new file mode 100644 index 00000000000..29ed81ff47d --- /dev/null +++ b/src/sage/algebras/hecke_algebras/cubic_hecke_algebra.py @@ -0,0 +1,3538 @@ +# -*- coding: utf-8 -*- +r""" +Cubic Hecke Algebras + +We consider the factors of the group algebra of the Artin braid groups +such that the images `s_i` of the braid generators satisfy a cubic equation: + +.. MATH:: + + s_i^3 = u s_i^2 - v s_i + w. + +Here `u, v, w` are elements in an arbitrary integral domain and `i` is a +positive integer less than `n`, the number of the braid group's strands. +By the analogue to the *Iwahori Hecke algebras* (see +:class:`~sage.algebras.iwahori_hecke_algebra.IwahoriHeckeAlgebra`), in which the +braid generators satisfy a quadratic relation these algebras have been called +*cubic Hecke algebras*. The relations inherited from the braid group are: + +.. MATH:: + + s_i s_{i+1} s_i = s_{i+1} s_i s_{i+1} \text{ for } 1 \leq i < n - 1 + \mbox{ and } s_i s_j = s_j s_i \text{ for } 1 \leq i < j - 1 < n - 1. + +The algebra epimorphism from the braid group algebra over the same base ring is +realized inside the element constructor of the present class, for example in the +case of the 3 strand cubic Hecke algebra:: + + sage: CHA3 = algebras.CubicHecke(3) + sage: BG3 = CHA3.braid_group() + sage: braid = BG3((1,2,-1,2,2,-1)); braid + c0*c1*c0^-1*c1^2*c0^-1 + sage: braid_image = CHA3(braid); braid_image + u*c1*c0^-1*c1 + u*v*c0*c1^-1*c0^-1 + (-u^2)*c0^-1*c1 + + ((u^2*v-v^2)/w)*c0*c1*c0^-1 + ((u^2-v)/w)*c0*c1*c0 + + ((-u^3+u*v)/w)*c0*c1 + (-u*v+w)*c1^-1 + +If the ring elements `u, v, w` (which will be called the *cubic equation +parameters* in the sequel) are taken to be `u = v = 0, w = 1` the cubic Hecke +algebra specializes to the group algebra of the *cubic braid group*, which is +the factor group of the Artin braid group under setting the generators order +to be three. These groups can be obtained by +:meth:`CubicHeckeAlgebra.cubic_braid_group`. + +It is well known, that these algebras are free of finite rank as long as the +number of braid generators is less than six and infinite dimensional else wise. +In the former (non trivial) cases they are also known as *cyclotomic Hecke +algebras* corresponding to the complex reflection groups having Shepard-Todd +number `4`, `25` and `32`. + +Since the *Broué, Malle, Rouquiere* conjecture has been proved (for references +of these cases see [Mar2012]_) there exists a finite free basis of the cubic +Hecke algebra which is in bijection to the cubic braid group and compatible +with the specialization to the cubic braid group algebra as explained above. + +For the algebras corresponding to braid groups of less than five strands such +a basis has been calculated by Ivan Marin. This one is used here. In the case +of 5 strands such a basis is not available, right now. Instead the elements +of the cubic braid group class themselves are used as basis elements. This +is also the case when the cubic braid group is infinite, even though it is +not known if these elements span all of the cubic Hecke algebra. + +Accordingly, be aware that the module embedding of the group algebra of the +cubicbraid groups is known to be an isomorphism of free modules only in the +cases of less than five strands. + +EXAMPLES: + +Consider the obstruction ``b`` of the *triple quadratic algebra* from Section 2.6 +of [Mar2018]_. We verify that the third power of it is a scalar multiple +of itself (explicitly ``2*w^2`` times the *Schur element* of the three +dimensional irreducible representation):: + + sage: CHA3 = algebras.CubicHecke(3) + sage: c1, c2 = CHA3.gens() + sage: b = c1^2*c2 - c2*c1^2 - c1*c2^2 + c2^2*c1; b + w*c0^-1*c1 + (-w)*c0*c1^-1 + (-w)*c1*c0^-1 + w*c1^-1*c0 + sage: b2 = b*b + sage: b3 = b2*b + sage: BR = CHA3.base_ring() + sage: ER = CHA3.extension_ring() + sage: u, v, w = BR.gens() + sage: f = BR(b3.coefficients()[0]/w) + sage: try: + ....: sh = CHA3.schur_element(CHA3.irred_repr.W3_111) + ....: except NotImplementedError: # for the case GAP3 / CHEVIE not available + ....: sh = ER(f/(2*w^2)) + sage: ER(f/(2*w^2)) == sh + True + sage: b3 == f*b + True + +Defining the cubic Hecke algebra on 6 strands will need some seconds for +initializing. However, you can do calculations inside the infinite +algebra as well:: + + sage: CHA6 = algebras.CubicHecke(6) # optional - database_cubic_hecke + sage: CHA6.inject_variables() # optional - database_cubic_hecke + Defining c0, c1, c2, c3, c4 + sage: s = c0*c1*c2*c3*c4; s # optional - database_cubic_hecke + c0*c1*c2*c3*c4 + sage: s^2 # optional - database_cubic_hecke + (c0*c1*c2*c3*c4)^2 + sage: t = CHA6.an_element() * c4; t # optional - database_cubic_hecke + (-w)*c0*c1^-1*c4 + v*c0*c2^-1*c4 + u*c2*c1*c4 + ((-v*w+u)/w)*c4 + +REFERENCES: + +- [Mar2012]_ +- [Mar2018]_ +- [CM2012]_ + +AUTHORS: + +- Sebastian Oehms May 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/ +############################################################################## + +from warnings import warn + +from sage.combinat.free_module import CombinatorialFreeModule +from sage.misc.cachefunc import cached_method +from sage.misc.verbose import verbose +from sage.groups.cubic_braid import CubicBraidGroup +from sage.rings.integer_ring import ZZ +from sage.algebras.splitting_algebra import solve_with_extension +from sage.modules.free_module_element import vector +from sage.matrix.matrix_space import MatrixSpace +from sage.algebras.hecke_algebras.cubic_hecke_base_ring import CubicHeckeRingOfDefinition +from sage.algebras.hecke_algebras.cubic_hecke_matrix_rep import CubicHeckeMatrixSpace, AbsIrreducibeRep, RepresentationType + + +############################################################################## +# +# Class CubicHeckeElement (for elements) +# +############################################################################## +class CubicHeckeElement(CombinatorialFreeModule.Element): + r""" + An element of a :class:`CubicHeckeAlgebra`. + + For more information see :class:`CubicHeckeAlgebra`. + + EXAMPLES:: + + sage: CHA3s = algebras.CubicHecke('s1, s2'); CHA3s.an_element() + (-w)*s1*s2^-1 + v*s1 + u*s2 + ((-v*w+u)/w) + sage: CHA3. = algebras.CubicHecke(3) + sage: c1**3*~c2 + u*w*c1^-1*c2^-1 + (u^2-v)*c1*c2^-1 + (-u*v+w)*c2^-1 + """ + # -------------------------------------------------------------------------- + # Overloading inherited methods + # -------------------------------------------------------------------------- + def __invert__(self): + r""" + Return inverse of ``self`` (if possible). + + EXAMPLES:: + + sage: CHA3 = algebras.CubicHecke(3) + sage: ele1 = CHA3((1,-2,1)); ele1 + c0*c1^-1*c0 + sage: ~ele1 # indirect doctest + c0^-1*c1*c0^-1 + + sage: CHA2 = algebras.CubicHecke(2) + sage: x = CHA2.an_element(); x + v*c + ((-v*w+u)/w) + sage: ~x + Traceback (most recent call last): + ... + ValueError: cannot invert self (= v*c + ((-v*w+u)/w)) + """ + self_Tietze = self.Tietze() + + if self_Tietze is None: + return super().__invert__() + + inverse_Tietze = () + len_self = len(self_Tietze) + + inverse_Tietze = tuple([-1*self_Tietze[len_self - i - 1] for i in range(len_self)]) + P = self.parent() + return P(inverse_Tietze) + + def Tietze(self): + r""" + Return the Tietze presentation of ``self`` if ``self`` belongs to the + basis of its parent and ``None`` otherwise. + + OUTPUT: + + A tuple representing the pre image braid of ``self`` if ``self`` is a + monomial from the basis ``None`` else-wise + + EXAMPLES:: + + sage: CHA3 = algebras.CubicHecke(3) + sage: ele = CHA3.an_element(); ele + (-w)*c0*c1^-1 + v*c0 + u*c1 + ((-v*w+u)/w) + sage: ele.Tietze() is None + True + sage: [CHA3(sp).Tietze() for sp in ele.support()] + [(), (1,), (1, -2), (2,)] + """ + vecd = self._monomial_coefficients + if len(vecd) != 1: + return None + ind, coeff = next(iter(vecd.items())) + if coeff.is_one(): + return ind.Tietze() + + def max_len(self): + r""" + Return the maximum of the length of Tietze expressions among the + support of ``self``. + + EXAMPLES:: + + sage: CHA3 = algebras.CubicHecke(3) + sage: ele = CHA3.an_element(); ele + (-w)*c0*c1^-1 + v*c0 + u*c1 + ((-v*w+u)/w) + sage: ele.max_len() + 2 + """ + return max(len(bas_ele.Tietze()) for bas_ele in self.support()) + + def braid_group_algebra_pre_image(self): + r""" + Return a pre image of ``self`` in the group algebra of the braid group + (with respect to the basis given by Ivan Marin). + + OUTPUT: + + The pre image of ``self`` as instance of the element class of the group + algebra of the BraidGroup + + EXAMPLES:: + + sage: CHA3 = algebras.CubicHecke(3) + sage: ele = CHA3.an_element(); ele + (-w)*c0*c1^-1 + v*c0 + u*c1 + ((-v*w+u)/w) + sage: b_ele = ele.braid_group_algebra_pre_image(); b_ele + ((-v*w+u)/w) + v*c0 + u*c1 + (-w)*c0*c1^-1 + sage: ele in CHA3 + True + sage: b_ele in CHA3 + False + sage: b_ele in CHA3.braid_group_algebra() + True + """ + ch_algebra = self.parent() + braid_group_algebra = ch_algebra.braid_group_algebra() + braid_group = ch_algebra.braid_group() + + def phi(bas_ele): + return braid_group_algebra(braid_group(bas_ele)) + return ch_algebra._apply_module_morphism(self, phi, + codomain=braid_group_algebra) + + def cubic_braid_group_algebra_pre_image(self): + r""" + Return a pre image of ``self`` in the group algebra of the cubic braid + group. + + OUTPUT: + + The pre image of ``self`` as instance of the element class of the group + algebra of the :class:`CubicBraidGroup`. + + EXAMPLES:: + + sage: CHA3 = algebras.CubicHecke(3) + sage: ele = CHA3.an_element(); ele + (-w)*c0*c1^-1 + v*c0 + u*c1 + ((-v*w+u)/w) + sage: cb_ele = ele.cubic_braid_group_algebra_pre_image(); cb_ele + (-w)*c0*c1^-1 + v*c0 + u*c1 + ((-v*w+u)/w) + sage: ele in CHA3 + True + sage: cb_ele in CHA3 + False + sage: cb_ele in CHA3.cubic_braid_group_algebra() + True + """ + ch_algebra = self.parent() + cbraid_group_algebra = ch_algebra.cubic_braid_group_algebra() + cbraid_group = ch_algebra.cubic_braid_group() + + def phi(bas_ele): + return cbraid_group_algebra(cbraid_group(bas_ele)) + return ch_algebra._apply_module_morphism(self, phi, + codomain=cbraid_group_algebra) + + @cached_method + def matrix(self, subdivide=False, representation_type=None, original=False): + r""" + Return certain types of matrix representations of ``self``. + + The absolutely irreducible representations of the cubic Hecke algebra + are constructed using the ``GAP3`` interface and the ``CHEVIE`` package + if ``GAP3`` and ``CHEVIE`` are installed on the system. Furthermore, + the representations given on `Ivan Marin's homepage + `__ + are used: + + INPUT: + + - ``subdivide`` -- boolean (default: ``False``): this boolean is passed + to the block_matrix function + - ``representation_type`` -- instance of enum :class:`RepresentationType`; + this can be obtained by the attribute :attr:`CubicHeckeAlgebra.repr_type` + of ``self``; the following values are possible: + + - ``RegularLeft`` -- (regular left repr. from the above URL) + - ``RegularRight`` -- (regular right repr. from the above URL) + - ``SplitIrredChevie`` -- (split irred. repr. via CHEVIE) + - ``SplitIrredMarin`` -- (split irred. repr. from the above URL) + - default: ``SplitIrredChevie`` taken if GAP3 and CHEVIE are installed + on the system, otherwise the default will be ``SplitIrredMarin`` + + - ``original`` -- boolean (default: ``False``): if set to true the base + ring of the matrix will be the generic base_ring resp. generic extension + ring (for the split versions) of the parent of ``self`` + + OUTPUT: + + An instance of :class:`~sage.algebras.hecke_algebras.cubic_hecke_matrix_rep.CubicHeckeMatrixRep`, + which is inherited from :class:`~sage.matrix.matrix_generic_dense.Matrix_generic_dense`. + In the case of the irreducible representations the matrix is given as a + block matrix. Each single irreducible can be obtained as item indexed by + the members of the enum :class:`AbsIrreducibeRep` available via + :attr:`CubicHeckeAlgebra.irred_repr`. + For details type: ``CubicHeckeAlgebra.irred_repr?``. + + EXAMPLES:: + + sage: CHA3 = algebras.CubicHecke(3) + sage: CHA3.inject_variables() + Defining c0, c1 + sage: c0m = c0.matrix() + sage: c0m[CHA3.irred_repr.W3_111] + [ -b - a + u 0 0] + [(-2*a + u)*b - 2*a^2 + 2*u*a - v b 0] + [ b 1 a] + + using the the ``representation_type`` option:: + + sage: CHA3. = algebras.CubicHecke(3) # optional gap3 + sage: chevie = CHA3.repr_type.SplitIrredChevie # optional gap3 + sage: c0m_ch = c0.matrix(representation_type=chevie) # optional gap3 + sage: c0m_ch[CHA3.irred_repr.W3_011] # optional gap3 + [ b 0] + [ -b -b - a + u] + sage: c0m[CHA3.irred_repr.W3_011] + [ b 0] + [a^2 - u*a + v -b - a + u] + + using the the ``original`` option:: + + sage: c0mo = c0.matrix(original=True) + sage: c0mo_ch = c0.matrix(representation_type=chevie, original=True) # optional gap3 + sage: c0mo[CHA3.irred_repr.W3_011] + [ b 0] + [b*c c] + sage: c0mo_ch[CHA3.irred_repr.W3_011] # optional gap3 + [ b 0] + [-b c] + + specialized matrices:: + + sage: t = (3,7,11) + sage: CHA4 = algebras.CubicHecke(4, cubic_equation_roots=t) # optional database_cubic_hecke + sage: e = CHA4.an_element(); e # optional database_cubic_hecke + -231*c0*c1^-1 + 131*c0*c2^-1 + 21*c2*c1 - 1440/11 + sage: em = e.matrix() # optional database_cubic_hecke + sage: em.base_ring() # optional database_cubic_hecke + Splitting Algebra of T^2 + T + 1 with roots [E3, -E3 - 1] + over Integer Ring localized at (3, 7, 11) + sage: em.dimensions() # optional database_cubic_hecke + (108, 108) + sage: em_irr24 = em[23] # optional database_cubic_hecke + sage: em_irr24.dimensions() # optional database_cubic_hecke + (9, 9) + sage: em_irr24[3,2] # optional database_cubic_hecke + -131*E3 - 393/7 + sage: emg = e.matrix(representation_type=chevie) # optional gap3 database_cubic_hecke + sage: emg_irr24 = emg[23] # optional gap3 database_cubic_hecke + sage: emg_irr24[3,2] # optional gap3 database_cubic_hecke + -131*E3 - 393/7 + """ + parent = self.parent() + MS = CubicHeckeMatrixSpace(parent, representation_type=representation_type, subdivide=subdivide, original=original) + return MS(self) + + def revert_garside(self): + r""" + Return the image of ``self`` under the Garside involution. + + .. SEEALSO:: + + :meth:`CubicHeckeAlgebra.garside_involution` + + EXAMPLES:: + + sage: roots = (E(3), ~E(3), 1) + sage: CHA3. = algebras.CubicHecke(3, cubic_equation_roots=roots) + sage: e = CHA3.an_element(); e + -c1*c2^-1 + sage: _.revert_garside() + -c2*c1^-1 + sage: _.revert_garside() + -c1*c2^-1 + """ + return self.parent().garside_involution(self) + + def revert_mirror(self): + r""" + Return the image of ``self`` under the mirror isomorphism. + + .. SEEALSO:: + + :meth:`CubicHeckeAlgebra.mirror_isomorphism` + + EXAMPLES:: + + sage: CHA3. = algebras.CubicHecke(3) + sage: e = CHA3.an_element() + sage: e.revert_mirror() + -1/w*c0^-1*c1 + u/w*c0^-1 + v/w*c1^-1 + ((v*w-u)/w) + sage: _.revert_mirror() == e + True + """ + return self.parent().mirror_isomorphism(self) + + def revert_orientation(self): + r""" + Return the image of ``self`` under the anti involution reverting the + orientation of braids. + + .. SEEALSO:: + + :meth:`CubicHeckeAlgebra.orientation_antiinvolution` + + EXAMPLES:: + + sage: CHA3. = algebras.CubicHecke(3) + sage: e = CHA3.an_element() + sage: e.revert_orientation() + (-w)*c2^-1*c1 + v*c1 + u*c2 + ((-v*w+u)/w) + sage: _.revert_orientation() == e + True + """ + return self.parent().orientation_antiinvolution(self) + + def formal_markov_trace(self, extended=False, field_embedding=False): + r""" + Return a formal expression which can be specialized to Markov traces + which factor through the cubic Hecke algebra. + + This covers Markov traces corresponding to the + + - HOMFLY-PT polynomial, + - Kauffman polynomial, + - Links-Gould polynomial. + + These expressions are elements of a sub-module of the module of linear + forms on ``self`` the base ring of which is an extension of the + generic base ring of ``self`` by an additional variable ``s`` + representing the writhe factor. All variables of this base ring + extension are invertible. + + A Markov trace is a family of class functions `tr_n` on the family + of braid groups `B_n` into some commutative ring `R` depending on + a unit `s \in R` such that for all `b \in B_n` the following two + conditions are satisfied (see [Kau1991]_, section 7): + + .. MATH:: + + \begin{array}{lll} + tr_{n+1}(b g_n) & = & s tr_n(b), \\ + tr_{n+1}(b g^{-1}_n) & = & s^{-1} tr_n(b). + \end{array} + + The unit `s` is often called the writhe factor and corresponds to the + additional variable mentioned above. + + .. NOTE:: + + Currently it is not known if all linear forms of this sub-module + belong to a Markov trace, i.e. can be extended to the full tower + of cubic Hecke algebras. Anyway, at least the four basis elements + (``U1``, ``U2``, ``U3`` and ``K4``) can be reconstructed form + the HOMFLY-PT and Kauffman polynomial. + + INPUT: + + - ``extended`` -- boolean (default: ``False``); if set to ``True`` the + base ring of the Markov trace module is constructed as an extension + of generic extension ring of ``self``; per default it is constructed + upon the generic base ring + - ``field_embedding`` -- boolean (default: ``False``); if set to ``True`` + the base ring of the module is the smallest field containing the + generic extension ring of ``self``; ignored if ``extended=False`` + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: CHA2 = algebras.CubicHecke(2) + sage: K3_1 = KnotInfo.K3_1 + sage: b3_1 = CHA2(K3_1.braid()) + sage: mt3_1 = b3_1.formal_markov_trace(); mt3_1 + ((u^2*s^2-v*s^2+u*w)/s)*B[U1] + (-u*v+w)*B[U2] + sage: mt3_1.parent() + Free module generated by {U1, U2} + over Multivariate Polynomial Ring in u, v, w, s + over Integer Ring localized at (s, w, v, u) + + sage: f = b3_1.formal_markov_trace(extended=True); f + (a^2*b*c*s^-1+a*b^2*c*s^-1+a*b*c^2*s^-1+a^2*s+a*b*s+b^2*s+a*c*s+b*c*s+c^2*s)*B[U1] + + (-a^2*b-a*b^2-a^2*c+(-2)*a*b*c-b^2*c-a*c^2-b*c^2)*B[U2] + sage: f.parent().base_ring() + Multivariate Laurent Polynomial Ring in a, b, c, s + over Splitting Algebra of x^2 + x + 1 with roots [e3, -e3 - 1] + over Integer Ring + + sage: f = b3_1.formal_markov_trace(extended=True, field_embedding=True); f + ((a^2*b*c+a*b^2*c+a*b*c^2+a^2*s^2+a*b*s^2+b^2*s^2+a*c*s^2+b*c*s^2+c^2*s^2)/s)*B[U1] + + (-a^2*b-a*b^2-a^2*c-2*a*b*c-b^2*c-a*c^2-b*c^2)*B[U2] + sage: f.parent().base_ring() + Fraction Field of Multivariate Polynomial Ring in a, b, c, s + over Cyclotomic Field of order 3 and degree 2 + + Obtaining the well known link invariants from it:: + + sage: MT = mt3_1.base_ring() + sage: sup = mt3_1.support() + sage: u, v, w, s = mt3_1.base_ring().gens() + sage: LK3_1 = mt3_1*s**-3 # since the writhe of K3_1 is 3 + sage: f = MT.specialize_homfly() + sage: g = sum(f(LK3_1.coefficient(b)) * b.regular_homfly_polynomial() for b in sup); g + L^-2*M^2 - 2*L^-2 - L^-4 + sage: g == K3_1.link().homfly_polynomial() + True + + sage: f = MT.specialize_kauffman() + sage: g = sum(f(LK3_1.coefficient(b)) * b.regular_kauffman_polynomial() for b in sup); g + a^-2*z^2 - 2*a^-2 + a^-3*z + a^-4*z^2 - a^-4 + a^-5*z + sage: g == K3_1.kauffman_polynomial() + True + + sage: f = MT.specialize_links_gould() + sage: g = sum(f(LK3_1.coefficient(b)) * b.links_gould_polynomial() for b in sup); g + -t0^2*t1 - t0*t1^2 + t0^2 + 2*t0*t1 + t1^2 - t0 - t1 + 1 + sage: g == K3_1.link().links_gould_polynomial() + True + """ + cha = self.parent() + vs = self.to_vector() + mtcf = cha._markov_trace_coeffs() + M = cha._markov_trace_module(extended=extended, field_embedding=field_embedding) + if M != mtcf[0].parent(): + if field_embedding: + # intermediate step needed since internal coercion to the field + # maps (u, v, w ) -> (a, b, c) + MI = cha._markov_trace_module(extended=extended, field_embedding=False) + RI = MI.base_ring() + mtcf = [MI.from_vector(cf.to_vector()) for cf in mtcf] + vs = vs.change_ring(RI) + mtcf = [M.from_vector(cf.to_vector()) for cf in mtcf] + + R = M.base_ring() + return M.linear_combination((mtcf[i], R(val)) for i, val in vs.iteritems()) + + +class CubicHeckeAlgebra(CombinatorialFreeModule): + r""" + Return the Cubic-Hecke algebra with respect to the Artin braid group on + `n` strands. + + This is a quotient of the group algebra of the Artin braid group, such that + the images `s_i` (`1 \leq i < n`) of the braid generators satisfy a cubic + equation (see :mod:`~sage.algebras.hecke_algebras.cubic_hecke_algebra` + for more information, in a session type + ``sage.algebras.hecke_algebras.cubic_hecke_algebra?``): + + .. MATH:: + + s_i^3 = u s_i^2 - v s_i + w. + + The base ring of this algebra can be specified by giving optional keywords + described below. If no keywords are given, the base ring will be a + :class:`CubicHeckeRingOfDefinition`, which is constructed as the + polynomial ring in `u, v, w` over the integers localized at `w`. + This ring will be called the *ring of definition* or sometimes for short + *generic base ring*. However note, that in this context the word *generic* + should not remind in a generic point of the corresponding scheme. + + In addition to the base ring, another ring containing the roots (`a`, `b` + and `c`) of the cubic equation will be needed to handle the split + irreducible representations. This ring will be called the *extension ring*. + Generically, the extension ring will be a + :class:`~sage.algebras.hecke_algebras.cubic_hecke_base_ring.CubicHeckeExtensionRing`, + which is constructed as the Laurent polynomial ring in `a, b` and `c` over + the integers adjoined with a primitive third root of unity. A special form + of this *generic extension ring* is constructed as a + :class:`~sage.algebras.splitting_algebra.SplittingAlgebra` for the roots of + the cubic equation and a primitive third root of unity over the ring of + definition. This ring will be called the *default extension ring*. + + This class uses a static and a dynamic data library. The first one is defined + as instance of :class:`~sage.databases.cubic_hecke_db.CubicHeckeDataBase` + and contains the complete basis for the algebras with less than 5 strands + and various types of representation matrices of the generators. These data + have been calculated by `Ivan Marin `__ + and have been imported from his corresponding + `web page `__. + + Note that just the data for the cubic Hecke algebras on less than four + strands is available in Sage by default. To deal with four strands and + more you need to install the optional package + `database_cubic_hecke `__ + by typing + + - ``sage -i database_cubic_hecke`` (first time installation) or + - ``sage -f database_cubic_hecke`` (reinstallation) respective + - ``sage -i -c database_cubic_hecke`` (for running all test in concern) + - ``sage -f -c database_cubic_hecke`` + + This will add a `Python wrapper `__ + around Ivan Marin's data to the Sage library. For more installation hints + see the documentation of this wrapper. + + Furthermore, representation matrices can be obtained from the ``CHEVIE`` + package of ``GAP3`` via the ``GAP3`` interface if ``GAP3`` is installed + inside Sage. For more information on how to obtain representation matrices + to elements of this class, see the documentation of the element class + :class:`~sage.algebras.hecke_algebras.cubic_hecke_algebra.CubicHeckeElement` + or its method + :meth:`~sage.algebras.hecke_algebras.cubic_hecke_algebra.CubicHeckeElement.matrix`: + + ``algebras.CubicHecke.Element?`` or ``algebras.CubicHecke.Element.matrix?`` + + The second library is created as instance of + :class:`~sage.databases.cubic_hecke_db.CubicHeckeFileCache` and used while + working with the class to achieve a better performance. This file cache + contains images of braids and representation matrices of basis elements + from former calculations. A refresh of the file cache can be done using + the :meth:`reset_filecache`. + + INPUT: + + - ``names`` -- string containing the names of the generators as images of + the braid group generators + - ``cubic_equation_parameters`` -- tuple ``(u, v, w)`` of three elements + in an integral domain used as coefficients in the cubic equation. If this + argument is given the base ring will be set to the common parent of + ``u, v, w``. In addition a conversion map from the generic base ring is + supplied. This keyword can also be used to change the variable names of + the generic base ring (see example 3 below) + - ``cubic_equation_roots`` -- tuple ``(a, b, c)`` of three elements in an + integral domain which stand for the roots of the cubic equation. If this + argument is given the extension ring will be set to the common parent of + ``a, b, c``. In addition a conversion map from the generic extension ring + and the generic base ring is supplied. This keyword can also be used to + change the variable names of the generic extension ring (see example 3 + below) + + EXAMPLES: + + Cubic Hecke algebra over the ring of definition:: + + sage: CHA3 = algebras.CubicHecke('s1, s2'); CHA3 + Cubic Hecke algebra on 3 strands over Multivariate Polynomial Ring + in u, v, w + over Integer Ring localized at (w,) + with cubic equation: h^3 - u*h^2 + v*h - w = 0 + sage: CHA3.gens() + (s1, s2) + sage: GER = CHA3.extension_ring(generic=True); GER + Multivariate Laurent Polynomial Ring in a, b, c + over Splitting Algebra of x^2 + x + 1 + with roots [e3, -e3 - 1] over Integer Ring + sage: ER = CHA3.extension_ring(); ER + Splitting Algebra of T^2 + T + 1 with roots [E3, -E3 - 1] + over Splitting Algebra of h^3 - u*h^2 + v*h - w + with roots [a, b, -b - a + u] + over Multivariate Polynomial Ring in u, v, w + over Integer Ring localized at (w,) + + Element construction:: + + sage: ele = CHA3.an_element(); ele + (-w)*s1*s2^-1 + v*s1 + u*s2 + ((-v*w+u)/w) + sage: ele2 = ele**2; ele2 + w^2*(s1^-1*s2)^2 + (-u*w^2)*s1^-1*s2*s1^-1 + (-v*w)*s2*s1^-1*s2 + + (-v*w^2)*s1^-1*s2^-1 + u*w*s1*s2*s1^-1*s2 + (-u*w)*s1^-1*s2*s1 + + (-u*v*w+2*v*w-2*u)*s1*s2^-1 + u*v*w*s2*s1^-1 + u*v*s2*s1 + v^2*w*s1^-1 + + (-u^2*w)*s1*s2*s1^-1 + ((u*v^2*w-2*v^2*w-u*w^2+2*u*v)/w)*s1 + + u*v*s1*s2 + (u^2*w+v^2*w)*s2^-1 + ((u^3*w-2*u*v*w+2*u^2)/w)*s2 + + ((-u^2*v*w^2-v^3*w^2+v^2*w^2-2*u*v*w+u^2)/w^2) + sage: B3 = CHA3.braid_group() + sage: braid = B3((2,-1, 2, 1)); braid + s2*s1^-1*s2*s1 + sage: ele3 = CHA3(braid); ele3 + s1*s2*s1^-1*s2 + u*s1^-1*s2*s1 + (-v)*s1*s2^-1 + v*s2^-1*s1 + (-u)*s1*s2*s1^-1 + sage: ele3t = CHA3((2,-1, 2, 1)) + sage: ele3 == ele3t + True + sage: CHA4 = algebras.CubicHecke(4) # optional database_cubic_hecke + sage: ele4 = CHA4(ele3); ele4 # optional database_cubic_hecke + c0*c1*c0^-1*c1 + u*c0^-1*c1*c0 + (-v)*c0*c1^-1 + v*c1^-1*c0 + (-u)*c0*c1*c0^-1 + + Cubic Hecke algebra over the ring of definition using different variable + names:: + + sage: algebras.CubicHecke(3, cubic_equation_parameters='u, v, w', cubic_equation_roots='p, q, r') + Cubic Hecke algebra on 3 strands over Multivariate Polynomial Ring + in u, v, w + over Integer Ring localized at (w,) + with cubic equation: h^3 - u*h^2 + v*h - w = 0 + sage: _.extension_ring() + Splitting Algebra of T^2 + T + 1 with roots [E3, -E3 - 1] + over Splitting Algebra of h^3 - u*h^2 + v*h - w + with roots [p, q, -q - p + u] + over Multivariate Polynomial Ring in u, v, w + over Integer Ring localized at (w,) + + Cubic Hecke algebra over a special base ring with respect to a special + cubic equation:: + + sage: algebras.CubicHecke('s1, s2', cubic_equation_parameters=(QQ(1),3,1)) + Cubic Hecke algebra on 3 strands over Rational Field + with cubic equation: h^3 - h^2 + 3*h - 1 = 0 + sage: CHA3 = _ + sage: ER = CHA3.extension_ring(); ER + Number Field in T with defining polynomial T^12 + 4*T^11 + 51*T^10 + + 154*T^9 + 855*T^8 + 1880*T^7 + 5805*T^6 + 8798*T^5 + 15312*T^4 + + 14212*T^3 + 13224*T^2 + 5776*T + 1444 + sage: CHA3.cubic_equation_roots()[0] + -4321/1337904*T^11 - 4181/445968*T^10 - 4064/27873*T^9 - 51725/167238*T^8 + - 2693189/1337904*T^7 - 1272907/445968*T^6 - 704251/74328*T^5 + - 591488/83619*T^4 - 642145/83619*T^3 + 252521/111492*T^2 + 45685/5868*T + + 55187/17604 + + sage: F = GF(25,'u') + sage: algebras.CubicHecke('s1, s2', cubic_equation_parameters=(F(1), F.gen(), F(3))) + Cubic Hecke algebra on 3 strands over Finite Field in u of size 5^2 + with cubic equation: h^3 + 4*h^2 + u*h + 2 = 0 + sage: CHA3 = _ + sage: ER = CHA3.extension_ring(); ER + Finite Field in S of size 5^4 + sage: CHA3.cubic_equation_roots() + [2*S^3 + 2*S^2 + 2*S + 1, 2*S^3 + 3*S^2 + 3*S + 2, S^3 + 3] + + + Cubic Hecke algebra over a special extension ring with respect to special + roots of the cubic equation:: + + sage: UCF = UniversalCyclotomicField() + sage: e3=UCF.gen(3); e5=UCF.gen(5) + sage: algebras.CubicHecke('s1, s2', cubic_equation_roots=(1, e5, e3)) + Cubic Hecke algebra on 3 strands over Universal Cyclotomic Field + with cubic equation: + h^3 + (-E(15) - E(15)^4 - E(15)^7 + E(15)^8)*h^2 + (-E(15)^2 - E(15)^8 + - E(15)^11 - E(15)^13 - E(15)^14)*h - E(15)^8 = 0 + + TESTS:: + + sage: CHA3 = algebras.CubicHecke(3) + sage: TestSuite(CHA3).run() + + Note, that the ``TestSuite`` run on the cubic Hecke algebra on four strands + would take up to half an hour if the file cache is empty. A repetition takes + less than half a minute. + """ + Element = CubicHeckeElement + repr_type = RepresentationType + irred_repr = AbsIrreducibeRep + + ############################################################################ + # private methods + ############################################################################ + @staticmethod + def __classcall_private__(cls, n=None, names='c', cubic_equation_parameters=None, cubic_equation_roots=None): + r""" + Normalize input to ensure a unique representation. + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2, 'd', cubic_equation_roots=(3,5,7)); CHA2 + Cubic Hecke algebra on 2 strands + over Integer Ring localized at (3, 5, 7) + with cubic equation: + h^3 - 15*h^2 + 71*h - 105 = 0 + sage: CHA2.inject_variables() + Defining d + sage: CHA3 = algebras.CubicHecke(3, cubic_equation_parameters=(3,5,7)); CHA3 + Cubic Hecke algebra on 3 strands + over Integer Ring localized at (7,) + with cubic equation: + h^3 - 3*h^2 + 5*h - 7 = 0 + sage: CHA3.cubic_equation_roots() + [a, b, -b - a + 3] + """ + # Support Freegroup('a,b') syntax + if n is not None: + try: + n = ZZ(n) - 1 + except TypeError: + names = n + n = None + + # derive n from counting names + if n is None: + if type(names) is str: + n = len(names.split(',')) + else: + names = list(names) + n = len(names) + + from sage.structure.category_object import normalize_names + names = tuple(normalize_names(n, names)) + return super(CubicHeckeAlgebra, cls).__classcall__(cls, names, + cubic_equation_parameters=cubic_equation_parameters, + cubic_equation_roots=cubic_equation_roots) + + def __init__(self, names, cubic_equation_parameters=None, cubic_equation_roots=None): + r""" + Initialize ``self``. + + TESTS:: + + sage: CHA2 = algebras.CubicHecke(2, 'd', cubic_equation_roots=(3,5,7)) + sage: TestSuite(CHA2).run() + sage: CHA2 = algebras.CubicHecke(2, cubic_equation_parameters=(3,5,7)) + sage: TestSuite(CHA2).run() + """ + # ---------------------------------------------------------------------- + # Define underlying group + # ---------------------------------------------------------------------- + self._cubic_braid_group = CubicBraidGroup(names) + self._braid_group = self._cubic_braid_group.braid_group() + n = len(self._cubic_braid_group.gens()) + self._nstrands = n + 1 + self._dim_irr_rep = sum([irr.dimension() for irr in AbsIrreducibeRep if irr.number_gens() == n]) + + # ---------------------------------------------------------------------- + # preparing use of data base anf file cache + # ---------------------------------------------------------------------- + from sage.databases.cubic_hecke_db import CubicHeckeDataBase, CubicHeckeFileCache + self._database = CubicHeckeDataBase() + self._filecache = CubicHeckeFileCache(self._nstrands) + + # ---------------------------------------------------------------------- + # interpretation of keywords and type verifications + # ---------------------------------------------------------------------- + # cubic_equation_parameters + # ---------------------------------------------------------------------- + ring_of_definition_names = ('u', 'v', 'w') + if cubic_equation_parameters is not None: + if isinstance(cubic_equation_parameters, str): + # -------------------------------------------------------------- + # Input specifies names for the generic base ring + # -------------------------------------------------------------- + ring_of_definition_names = tuple(cubic_equation_parameters.split(',')) + if len(ring_of_definition_names) != 3: + raise ValueError('cubic_equation_parameters must consist of exactly 3 elements') + cubic_equation_parameters = None + else: + # -------------------------------------------------------------- + # Input specifies a specialized base ring + # -------------------------------------------------------------- + if isinstance(cubic_equation_parameters, list): + cubic_equation_parameters = tuple(cubic_equation_parameters) + if not isinstance(cubic_equation_parameters, tuple): + raise TypeError('cubic_equation_parameters must be a tuple or list') + if len(cubic_equation_parameters) != 3: + raise ValueError('cubic_equation_parameters must consist of exactly 3 elements') + + # ---------------------------------------------------------------------- + # cubic_equation_roots + # ---------------------------------------------------------------------- + generic_extension_ring_names = ('a', 'b', 'c') + if cubic_equation_roots is not None: + if isinstance(cubic_equation_roots, str): + # -------------------------------------------------------------- + # Input specifies names for the generic extension ring + # -------------------------------------------------------------- + generic_extension_ring_names = tuple(cubic_equation_roots.split(',')) + if len(generic_extension_ring_names) != 3: + raise ValueError('cubic_equation_roots must consist of exactly 3 elements') + cubic_equation_roots = None + else: + # -------------------------------------------------------------- + # Input specifies a specialized base ring + # -------------------------------------------------------------- + if isinstance(cubic_equation_roots, list): + cubic_equation_roots = tuple(cubic_equation_roots) + if not isinstance(cubic_equation_roots, tuple): + raise TypeError('cubic_equation_roots must be a tuple or list') + if len(cubic_equation_roots) != 3: + raise ValueError('cubic_equation_roots must consist of exactly 3 elements') + + if len(set(ring_of_definition_names + generic_extension_ring_names)) < 6: + raise ValueError('there is an overlap of names between cubic equation ' + 'parameters (%s) and cubic equation roots (%s)' + % (ring_of_definition_names, generic_extension_ring_names)) + + # ---------------------------------------------------------------------- + # setting the generic rings + # ---------------------------------------------------------------------- + ring_of_definition = CubicHeckeRingOfDefinition(names=ring_of_definition_names) + u, v, w = ring_of_definition.gens() + + generic_extension_ring = ring_of_definition.extension_ring(names=generic_extension_ring_names) + a, b, c = generic_extension_ring.gens() + + # ---------------------------------------------------------------------- + # registering generic items as variables + # ---------------------------------------------------------------------- + self._ring_of_definition = ring_of_definition + self._generic_extension_ring = generic_extension_ring + self._generic_cubic_equation_parameters = [u, v, w] + self._generic_cubic_equation_roots = [a, b, c] + + # ---------------------------------------------------------------------- + # interpreting user given cubic equation parameters to define the + # corresponding specialized base ring + # ---------------------------------------------------------------------- + if cubic_equation_parameters is None and cubic_equation_roots is not None: + pa, pb, pc = cubic_equation_roots + cubic_equation_parameters = [pa+pb+pc, pa*pb+pb*pc+pa*pc, pa*pb*pc] + verbose('cubic_equation_parameters %s set according to ' + 'cubic_equation_roots %s' % (cubic_equation_parameters, + cubic_equation_roots), level=2) + + if cubic_equation_parameters is not None: + base_ring = ring_of_definition.create_specialization(cubic_equation_parameters) + cubic_equation_parameters = [base_ring(para) for para in cubic_equation_parameters] + verbose('base_ring %s set according to cubic_equation_parameters %s' + % (base_ring, cubic_equation_parameters), level=2) + else: + base_ring = self._ring_of_definition + cubic_equation_parameters = self._generic_cubic_equation_parameters + + verbose('base_ring %s and cubic_equation_parameters %s defined' + % (base_ring, cubic_equation_parameters), level=2) + + # ---------------------------------------------------------------------- + # defining the cubic equation + # ---------------------------------------------------------------------- + pu, pv, pw = cubic_equation_parameters + pol_bas_ring = base_ring['h'] + cubic_equation = pol_bas_ring([-pw, pv, -pu, 1]) + + verbose('cubic_equation %s defined' % cubic_equation, level=2) + + # ---------------------------------------------------------------------- + # defining cubic_equation_roots if not given using the cubic_equation + # ---------------------------------------------------------------------- + if base_ring != ring_of_definition: + if cubic_equation_roots is None: + # -------------------------------------------------------------- + # No roots given + # -------------------------------------------------------------- + ext_ring_names = list(generic_extension_ring_names) + cubic_equation_roots = solve_with_extension(cubic_equation, + ext_ring_names, + var='S', flatten=True) + + # ---------------------------------------------------------------------- + # interpreting user given cubic equation roots to define the + # corresponding specialized extension ring + # ---------------------------------------------------------------------- + if cubic_equation_roots is not None: + extension_ring = generic_extension_ring.create_specialization(cubic_equation_roots) + cubic_equation_roots = [extension_ring(root) for root in cubic_equation_roots] + verbose('extension_ring %s set according to cubic_equation_roots %s' + % (base_ring, cubic_equation_roots), level=2) + + else: + extension_ring = generic_extension_ring.as_splitting_algebra() + cubic_equation_roots = [extension_ring(a), extension_ring(b), extension_ring(c)] + + verbose('cubic roots %s and extension ring %s defined' + % (cubic_equation_roots, extension_ring), level=2) + pa, pb, pc = cubic_equation_roots + + # ---------------------------------------------------------------------- + # check keywords plausibility + # ---------------------------------------------------------------------- + if base_ring == extension_ring: + val_a = cubic_equation.substitute(h=pa) + val_b = cubic_equation.substitute(h=pb) + val_c = cubic_equation.substitute(h=pc) + if val_a != 0 or val_b != 0 or val_c != 0: + raise ValueError('cubic equation does not vanish on cubic equation roots') + + # ---------------------------------------------------------------------- + # defining the base ring embedding into the extension ring + # ---------------------------------------------------------------------- + im_base_gens = [pa+pb+pc, pa*pb+pa*pc+pb*pc, pa*pb*pc] + base_ring_embedding = extension_ring.coerce_map_from(base_ring) + + def check_base_ring_embedding(base_ring_embedding): + if base_ring_embedding is None: + return False + try: + ipu = base_ring_embedding(pu) + ipv = base_ring_embedding(pv) + ipw = base_ring_embedding(pw) + if [ipu, ipv, ipw] != im_base_gens: + return False + except (TypeError, ValueError): + return False + return True + + if check_base_ring_embedding(base_ring_embedding): + verbose('base_ring_embedding defined via coercion', level=2) + else: + base_ring_embedding = extension_ring.convert_map_from(base_ring) + if check_base_ring_embedding(base_ring_embedding): + verbose('base_ring_embedding defined via conversion', level=2) + else: + try: + if base_ring.gens() == cubic_equation_parameters: + base_ring_embedding = base_ring.hom(im_base_gens, codomain=extension_ring) + except (TypeError, ValueError): + base_ring_embedding = None + + if base_ring_embedding is None: + warn('Warning: no base_ring_embedding found') + + # ---------------------------------------------------------------------- + # registering variables + # ---------------------------------------------------------------------- + self._extension_ring = extension_ring + self._base_ring_embedding = base_ring_embedding + self._ring_of_definition_map = base_ring.convert_map_from(ring_of_definition) + self._generic_extension_ring_map = extension_ring.convert_map_from(generic_extension_ring) + self._cubic_equation_parameters = cubic_equation_parameters + self._cubic_equation_roots = cubic_equation_roots + + # ---------------------------------------------------------------------- + # defining the associated group algebras + # ---------------------------------------------------------------------- + from sage.algebras.group_algebra import GroupAlgebra + self._cubic_braid_group_algebra = GroupAlgebra(self._cubic_braid_group, R=base_ring) + self._braid_group_algebra = GroupAlgebra(self._braid_group, R=base_ring) + + # ---------------------------------------------------------------------- + # Setup of Basis + # ---------------------------------------------------------------------- + # Fetch Ivan Marin's basis for the algebras on at most 4 strands. + # An explicit list of basis element which represents a flat deformation + # of the cubic braid group is only available in the cases where the + # number of strands is less than 5. In the case of exactly 5 strands + # it is known that such a basis exist by work of Ivan Marin in + # [Marin2012] but it can not be calculated, right now. In the (infinite + # dimensional) cases of more than 5 strands it is even an open problem + # if the cubic Hecke algebra is a flat deformation of the group algebra + # of the corresponding cubic braid group. + # + # But anyway, we will take the elements of the cubic braid group as a + # basis of the cubic Hecke algebra in all cases. Beware that this might + # not cover the whole cubic Hecke algebra if the number of strands is + # larger than 4 + # + # Internally the basis is implemented using two lists one of which + # consists of fixed braid pre images of the basis elements and the other + # (redundant) of the corresponding Tietze expressions. + # + # In the case of less than 5 strands these lists are directly obtained + # from the list calculated by Ivan Marin available at + # + # In the other cases these lists are implemented as growing list which + # is initialized with Marin's list and is extended on demand. + # ---------------------------------------------------------------------- + db = self._database + ns = min(self._nstrands, 4) + self._basis_static = db.read(db.section.basis, nstrands=ns) + + # ---------------------------------------------------------------------- + # defining the algebra itself + # ---------------------------------------------------------------------- + if self._cubic_braid_group.is_finite(): + from sage.categories.finite_dimensional_algebras_with_basis import FiniteDimensionalAlgebrasWithBasis + category = FiniteDimensionalAlgebrasWithBasis(base_ring) + else: + from sage.categories.algebras_with_basis import AlgebrasWithBasis + category = AlgebrasWithBasis(base_ring) + + CombinatorialFreeModule.__init__(self, base_ring, self._cubic_braid_group, + prefix='', names=names, bracket=False, + category=category) + + # ---------------------------------------------------------------------- + # init the attributes being set on demand + # ---------------------------------------------------------------------- + self._cubic_hecke_subalgebra = None + self._mirror_image = None + self._is_mirror = False + self._base_ring_mirror = None + self._gens_reg_repres_matrix = {} + + # ---------------------------------------------------------------------- + # initializing the basis extension (in case of more than 4 strands) + # ---------------------------------------------------------------------- + self._init_basis_extension() + return + + ############################################################################ + # -------------------------------------------------------------------------- + # overloaded inherited methods + # -------------------------------------------------------------------------- + ############################################################################ + def _repr_(self): + r""" + Return a string representation + + OUTPUT: + + String describing ``self`` + + TESTS:: + + sage: CHA3 = algebras.CubicHecke(3) + sage: CHA3 # indirect doctest + Cubic Hecke algebra on 3 strands + over Multivariate Polynomial Ring in u, v, w + over Integer Ring localized at (w,) + with cubic equation: h^3 - u*h^2 + v*h - w = 0 + """ + s = 'Cubic Hecke algebra on %s strands over %s with cubic equation: %s = 0' + return s % (self._nstrands, self.base_ring(), self.cubic_equation()) + + def _element_constructor_(self, x): + r""" + Extensions to the element constructor of class :class:`CombinatorialFreeModule`. + + New functionalities are: + + - constructing element from a braid (group homomorphism) + - constructing element from a braid giving in Tietze form + - constructing element from an element of the braid group algebra + (algebra homomorphism) + - constructing element from an element of the cubic braid group + algebra (module homomorphism) + - constructing element from an element of an other cubic Hecke + algebra over an other base ring or with less strands + - constructing element from an element of the mirror image of + ``self`` (see method mirror_image) + + INPUT: + + - ``x`` -- can be one of the following: + + * an instance of the element class of ``self`` (but possible + to a different parent) + * an instance of the element class of the braid group + * an instance of the element class of the braid group algebra + over the base ring of ``self`` + * an instance of the element class of the cubic braid group + * an instance of the element class of the cubic braid group + algebra over the base ring of ``self`` + * an instance of the element class of the mirror image of + ``self`` + * a tuple representing a braid in Tietze form + * any other object which works for the element constructor + of :class:`CombinatorialFreeModule` + + EXAMPLES:: + + sage: B2 = BraidGroup(2) + sage: b, = B2.gens() + sage: b2 = b**2 + sage: CB2 = CubicBraidGroup(2) + sage: cb, = CB2.gens() + sage: cb2 = cb**2 + sage: CHA2 = algebras.CubicHecke(2) + sage: CHA2(b2) + w*c^-1 + u*c + (-v) + sage: CHA2(cb2) + c^-1 + sage: CHA3 = algebras.CubicHecke(3) + sage: B3 = CHA3.braid_group() + sage: CB3 = CHA3.cubic_braid_group() + sage: CB3GA = CHA3.cubic_braid_group_algebra() + sage: braid = B3((1,2,2,-1,2,1,1,-1)); braid + c0*c1^2*c0^-1*c1*c0 + sage: img_braid = CHA3(braid); img_braid + u*w*(c0^-1*c1)^2 + u*v*c0*c1^-1*c0 + (-u^2*w)*c0^-1*c1*c0^-1 + + (-u*v)*c1*c0^-1*c1 + (-u*v*w+w^2)*c0^-1*c1^-1 + u^2*c0*c1*c0^-1*c1 + + (-u^2*v+u*w)*c0*c1^-1 + u^2*v*c1*c0^-1 + (u^2-v)*c1*c0 + + (-u^3)*c0*c1*c0^-1 + (u*v^2-v*w)*c1^-1 + sage: cbraid = CB3(braid); cbraid + c0*c1^2*c0^-1*c1*c0 + sage: img_cbraid = CHA3(cbraid); img_cbraid + c0^-1*c1^-1 + sage: img_cbraid_back = img_cbraid.cubic_braid_group_algebra_pre_image() + sage: img_cbraid_back in CB3GA + True + sage: img_cbraid_back == CB3GA(cbraid) + True + """ + braid_grp = self.braid_group() + braid_grp_alg = self.braid_group_algebra() + braid_img = self._braid_image + + cbraid_grp = self.cubic_braid_group() + cbraid_grp_alg = self.cubic_braid_group_algebra() + cbraid_img = self._cubic_braid_image + + base_ring = self.base_ring() + ngens = self.ngens() + params = self.cubic_equation_parameters() + + # ---------------------------------------------------------------------- + # if x is a tuple we may interpret it as a braid in Tietze form + # ---------------------------------------------------------------------- + xb = x + if type(x) in (tuple, list): + x = tuple(x) + result = self._tietze_to_finite_sub_basis_monomial(x) + if result is not None: + # x represents a monomial + verbose('end from tuple %s: %s' % (x, result), level=2) + return result + + try: + xb = braid_grp(x) + except (TypeError, ValueError, NotImplementedError): + pass + + # ---------------------------------------------------------------------- + # embedding of an element of an other cubic Hecke algebra with lower + # number of strands but same base ring + # ---------------------------------------------------------------------- + if isinstance(xb, CubicHeckeElement): + other_cha = xb.parent() + other_base_ring = other_cha.base_ring() + other_ngens = other_cha.ngens() + other_params = other_cha.cubic_equation_parameters() + if other_base_ring != base_ring: + if other_ngens == ngens: + xbv = xb.to_vector() + img_xbv = vector([self.base_ring()(cf) for cf in xbv]) + return self.from_vector(img_xbv) + elif other_ngens < ngens: + sub_alg = self.cubic_hecke_subalgebra(other_ngens+1) + return self(sub_alg(xb)) + + elif other_ngens < ngens and other_params == params: + cbraid_preimg = xb.cubic_braid_group_algebra_pre_image() + other_cbga = other_cha.cubic_braid_group_algebra() + + def fc(ele): + return cbraid_img(cbraid_grp(ele)) + result = other_cbga._apply_module_morphism(cbraid_preimg, fc, codomain=self) + verbose('end from smaller cubic Hecke algebra %s: %s' % (xb, result), level=2) + return result + + elif other_cha == self._mirror_image: + result = other_cha.mirror_isomorphism(xb) + verbose('end from mirror image %s: %s' % (xb, result), level=2) + return result + + # ---------------------------------------------------------------------- + # if xb is an element of the braid group or its group algebra over the + # same base ring the algebra morphism self._braid_image is applied + # ---------------------------------------------------------------------- + if isinstance(xb, braid_grp_alg.element_class) and xb in braid_grp_alg: + + def fb(ele): + return braid_img(ele) + result = braid_grp_alg._apply_module_morphism(xb, fb, codomain=self) + verbose('end from braid_group algebra %s: %s' % (xb, result), level=2) + return result + + from sage.groups.braid import Braid + if isinstance(xb, Braid) and xb.strands() == self._nstrands: + result = braid_img(xb) + verbose('end from braid_group %s: %s' % (xb, result), level=2) + return result + + # ---------------------------------------------------------------------- + # if xb is an element of the cubic_braid group or its group algebra over + # the same base ring xb the module morphism self._braid_image is applied + # ---------------------------------------------------------------------- + if isinstance(xb, cbraid_grp_alg.element_class) and xb in cbraid_grp_alg: + result = cbraid_grp_alg._apply_module_morphism(xb, cbraid_img, codomain=self) + verbose('end from cubic braid_group algebra %s: %s' % (xb, result), level=2) + return result + + from sage.groups.cubic_braid import CubicBraidElement + if isinstance(xb, CubicBraidElement) and xb.parent().strands() == self._nstrands: + result = cbraid_img(xb) + verbose('end from cubic braid_group %s: %s' % (xb, result), level=2) + return result + + # ---------------------------------------------------------------------- + # doing the default construction by inheritance + # ---------------------------------------------------------------------- + result = CombinatorialFreeModule._element_constructor_(self, x) + verbose('end (default) %s: %s' % (xb, result), level=2) + return result + + def get_order(self): + r""" + Return an ordering of the basis of ``self``. + + EXAMPLES:: + + sage: CHA3 = algebras.CubicHecke(3) + sage: len(CHA3.get_order()) + 24 + """ + # The reason we have overriden this is that we have to care about + # the dynamical growth of thefinite sub basis used for the + # calculation in case of more than 4 strands. + + if self._nstrands < 5: + return self._order + + # detect change of _order of sub algebra + sub_alg = self.cubic_hecke_subalgebra() + former_len_sub = len(sub_alg._order) + sub_order = sub_alg.get_order() + if former_len_sub == len(sub_order): + if len(self._order) == former_len_sub + len(self._basis_extension): + return self._order + # _order has changed! re-calculation necessary: + cbg = self.cubic_braid_group() + sub_order = [cbg(cb) for cb in sub_order] + self._order = sub_order + [cbg(tup) for tup in self._basis_extension] + return self._order + + def _order_key(self, x): + """ + Return a key for `x` compatible with the term order. + + INPUT: + + - ``x`` -- indices of the basis of ``self`` + + EXAMPLES:: + + sage: A = CombinatorialFreeModule(QQ, ['x','y','a','b']) + sage: A.set_order(['x', 'y', 'a', 'b']) + sage: A._order_key('x') + 0 + sage: A._order_key('y') + 1 + sage: A._order_key('a') + 2 + """ + try: + return self._rank_basis(x) + except AttributeError: + from sage.combinat.ranker import rank_from_list + self._rank_basis = rank_from_list(self._order) + return self._rank_basis(x) + + def _dense_free_module(self, base_ring=None): + r""" + Return a dense free module with the same dimension as ``self``. + + This overwrites the corresponding method of :class:`CombinatorialFreeModule`. + The only difference is, that the dimension is not the dimension of + ``self`` but the dimension of the sub-module generated by the dynamically + growing basis given by the :meth:`get_order`. In particular there is + no difference if the number of strands is less than 5. + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2) + sage: CHA2._dense_free_module() + Ambient free module of rank 3 + over the integral domain Multivariate Polynomial Ring in u, v, w + over Integer Ring localized at (w,) + """ + if base_ring is None: + base_ring = self.base_ring() + from sage.modules.free_module import FreeModule + return FreeModule(base_ring, len(self.get_order())) + + def ngens(self): + r""" + The number of generators of the algebra. + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2) + sage: CHA2.ngens() + 1 + """ + return self._nstrands - 1 + + def algebra_generators(self): + r""" + Return the algebra generators of ``self``. + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2) + sage: CHA2.algebra_generators() + Finite family {c: c} + """ + from sage.sets.family import Family + return Family(self._cubic_braid_group.gens(), self.monomial) + + def gens(self): + r""" + Return the generators of ``self``. + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2) + sage: CHA2.gens() + (c,) + """ + return tuple(self.algebra_generators()) + + def gen(self, i): + r""" + The ``i``-th generator of the algebra. + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2) + sage: CHA2.gen(0) + c + """ + return self.gens()[i] + + def one_basis(self): + r""" + Return the index of the basis element for the identity element + in the cubic braid group. + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2) + sage: CHA2.one_basis() + 1 + """ + return self.cubic_braid_group().one() + + def _an_element_(self): + r""" + Overwrite the original method from :mod:`~sage.combinat.free_module` + to obtain an more interesting element for ``TestSuite``. + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2) + sage: CHA2.an_element() # indirect doctest + v*c + ((-v*w+u)/w) + """ + n = self.ngens() + 1 + base_ring = self.base_ring() + u, v, w = [base_ring(para) for para in self._cubic_equation_parameters] + const = (u*~w - v) * self.one() + + gens = self.gens() + first_gens = [gen for gen in gens if gens.index(gen) < 3] + if n == 2: + c1, = first_gens + return const + v*c1 + elif n == 3: + c1, c2 = first_gens + return const + v*c1 - w*c1*~c2 + u*c2 + else: + c1, c2, c3 = first_gens + return const + v*c1*~c3 - w*c1*~c2 + u*c3*c2 + + @cached_method + def chevie(self): + r""" + Return the ``GAP3``-``CHEVIE`` realization of the corresponding + cyclotomic Hecke algebra in the finite-dimensional case. + + EXAMPLES:: + + sage: CHA3 = algebras.CubicHecke(3) # optional gap3 + sage: CHA3.chevie() # optional gap3 + Hecke(G4,[[a,b,c]]) + """ + from sage.combinat.root_system.reflection_group_real import is_chevie_available + if not is_chevie_available(): + raise NotImplementedError('this functionality needs GAP3 with package CHEVIE') + + n = self._nstrands + if n == 3: + st_number = 4 + elif n == 4: + st_number = 25 + elif n == 5: + st_number = 32 + else: + raise NotImplementedError('CHEVIE version doesn\'t exist for this cubic Hecke algebra') + + gap3_function_str = """function(st_number, na, nb,nc) + local a, b, c, # embedded Indeterminates + ReflGroup, # Reflection group + HeckeAlg; # Hecke algebra + + a := Mvp(na); b := Mvp(nb); c := Mvp(nc); + ReflGroup := ComplexReflectionGroup(st_number); + HeckeAlg := Hecke(ReflGroup, [[a, b, c]] ); + return HeckeAlg; + end;""" + + from sage.interfaces.gap3 import gap3 + gap3_function = gap3(gap3_function_str) + na, nb, nc = ['\"%s\"' % indet for indet in self.extension_ring(generic=True).variable_names()] + return gap3_function(st_number, na, nb, nc) + + @cached_method + def product_on_basis(self, g1, g2): + r""" + Return product on basis elements indexed by ``g1`` and ``g2``. + + EXAMPLES:: + + sage: CHA3 = algebras.CubicHecke(3) + sage: g = CHA3.basis().keys().an_element(); g + c0*c1 + sage: CHA3.product_on_basis(g, ~g) + 1 + sage: CHA3.product_on_basis(g, g) + w*c0^-1*c1*c0 + (-v)*c1*c0 + u*c0*c1*c0 + """ + # ---------------------------------------------------------------------- + # short way for multiplications with one + # ---------------------------------------------------------------------- + if g1 == g1.parent().one(): + return self.monomial(g2) + + if g2 == g2.parent().one(): + return self.monomial(g1) + + # ---------------------------------------------------------------------- + # convert to monomials + # ---------------------------------------------------------------------- + g1 = self.monomial(g1) + g2 = self.monomial(g2) + + result = None + + g1_Tietze = g1.Tietze() + g2_Tietze = g2.Tietze() + + verbose('Tietze established (%s, %s)' % (g1_Tietze, g2_Tietze), level=2) + + # ---------------------------------------------------------------------- + # The product is calculated from the corresponding product of the braids + # ---------------------------------------------------------------------- + braid_group = self.braid_group() + braid_product = braid_group(g1_Tietze+g2_Tietze) + result = self._braid_image(braid_product) + return result + + ############################################################################ + # -------------------------------------------------------------------------- + # local methods + # -------------------------------------------------------------------------- + ############################################################################ + def _basis_tietze(self): + r""" + Return the complete finite sub basis as list of Tietze tuples + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2) + sage: CHA2._basis_tietze() + [[], [1], [-1]] + """ + if self._nstrands > 4: + self_sub = self.cubic_hecke_subalgebra() + result_list = self_sub._basis_tietze() + self._basis_extension + else: + result_list = self._basis_static + return result_list + + def _tietze_to_finite_sub_basis_monomial(self, tietze_tup): + r""" + Return the monomial corresponding to a Tietze tuple + if it is in the finite sub basis, otherwise return ``None``. + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2) + sage: CHA2._tietze_to_finite_sub_basis_monomial([-1]) + c^-1 + sage: CHA2._tietze_to_finite_sub_basis_monomial([-2]) is None + True + """ + tietze_list = list(tietze_tup) + in_basis = False + if tietze_list in self._basis_tietze(): + in_basis = True + elif self._nstrands > 4: + fsb_dict = self._finite_sub_basis_tuples + if tietze_list in list(fsb_dict.values()): + in_basis = True + elif self.cubic_hecke_subalgebra()._tietze_to_finite_sub_basis_monomial(tietze_tup) is not None: + in_basis = True + + if in_basis: + # tietze_tup represents a monomial + B = self.basis() + cb = self.cubic_braid_group()(tietze_tup) + return B[cb] + + return None + + @cached_method + def _create_matrix_list_for_one(self, representation_type): + r""" + Return the matrix list for the given representation type + for ``self.one()``. + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2) + sage: CHA2._create_matrix_list_for_one(CHA2.repr_type.SplitIrredMarin) + [[1], [1], [1]] + sage: CHA2._create_matrix_list_for_one(CHA2.repr_type.RegularLeft) + [ + [1 0 0] + [0 1 0] + [0 0 1] + ] + """ + n = self._nstrands + if representation_type.is_split(): + gen_base_ring = self.extension_ring(generic=True) + rep_ind = [rep.internal_index() for rep in AbsIrreducibeRep if rep.number_gens() == n - 1] + rep_dim = [rep.dimension() for rep in AbsIrreducibeRep if rep.number_gens() == n - 1] + dim_sort = [rep_dim[rep_ind.index(i)] for i in range(len(rep_ind))] + matrix_list = [MatrixSpace(gen_base_ring, dim_sort[i]).one() for i in range(len(rep_ind))] + else: + gen_base_ring = self.base_ring(generic=True) + matrix_list = [MatrixSpace(gen_base_ring, self.dimension()).one()] + return matrix_list + + @cached_method + def _fetch_matrix_list_from_chevie(self, number): + r""" + This method reads irreducible representation of the cubic Hecke algebra + via the *GAP3* interface from *CHEVIE*. + + INPUT: + + - ``number`` -- integer; number of the representation according to + *CHEVIE* + + OUTPUT: + + A list of representing matrices over the generic extension ring, one + matrix for each generators. + + EXAMPLES:: + + sage: CHA3 = algebras.CubicHecke(3) # optional gap3 + sage: CHA3._fetch_matrix_list_from_chevie(5) # optional gap3 + [ + [ a 0] [c c] + [-a c], [0 a] + ] + """ + GER = self.extension_ring(generic=True) + gap3_result = self.chevie().Representations(number) + from sage.matrix.constructor import matrix + matrix_list_gens = [matrix(GER, mat_gap) for mat_gap in gap3_result] + for m in matrix_list_gens: + m.set_immutable() + return matrix_list_gens + + # -------------------------------------------------------------------------- + # Methods for test_suite + # -------------------------------------------------------------------------- + # _test_ring_constructions + # -------------------------------------------------------------------------- + def _test_ring_constructions(self, **options): + r""" + Method called by :class:`TestSuite`. + + The following is checked: + + - construction of base_ring and extension ring + - construction of maps between generic base and extension ring and + the user defined rings + - application of the ring homomorphisms + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2, cubic_equation_roots=(3,7,11)) + sage: CHA2._test_ring_constructions() + """ + # ------------------------------------------------------------------------ + # testing ring constructions + # ------------------------------------------------------------------------ + br = self.base_ring() + er = self.extension_ring() + A, B, C = self.cubic_equation_roots() + a, b, c = self.cubic_equation_roots(generic=True) + U, V, W = self.cubic_equation_parameters() + u, v, w = self.cubic_equation_parameters(generic=True) + eleB = U*V - W**2 + eleBgen = u*v - w**2 + eleE = A*B - C**2 + eleEgen = a*b - c**2 + + mbr = self._ring_of_definition_map + mer = self._generic_extension_ring_map + bri = self._base_ring_embedding + + eleBgenEmb = 0 + eleEgenEmb = 0 + eleBembE = 0 + + try: + eleBgenEmb = br(eleBgen) + except (TypeError, ValueError, NotImplementedError): + verbose('generic base ring map not registered') + try: + eleBgenEmb = mbr(eleBgen) + except (TypeError, ValueError, NotImplementedError): + raise RuntimeError('fatal: generic base ring map %s does not work' % mbr) + try: + eleEgenEmb = er(eleEgen) + except (TypeError, ValueError, NotImplementedError): + verbose('generic extension ring map not registered') + try: + eleEgenEmb = mer(eleEgen) + except (TypeError, ValueError, NotImplementedError): + raise RuntimeError('fatal: generic extension ring map %s does not work' % mer) + + try: + eleBembE = er(eleB) + except (TypeError, ValueError, NotImplementedError): + verbose('base ring embedding map not registered') + try: + eleBembE = bri(eleB) + except (TypeError, ValueError, NotImplementedError): + raise RuntimeError('fatal: base ring embedding %s does not work' % bri) + + test_eleBgenEmb = self._tester(**options) + test_eleBgenEmb.assertTrue(eleBgenEmb == eleB) + test_eleEgenEmb = self._tester(**options) + test_eleEgenEmb.assertTrue(eleEgenEmb == eleE) + test_eleBembE = self._tester(**options) + test_eleBembE.assertTrue(eleBembE == eleB) + + # -------------------------------------------------------------------------- + # _test_matrix_constructions + # -------------------------------------------------------------------------- + def _test_matrix_constructions(self, **options): + r""" + Test that the matrix constructions are valid. + + The following is checked: + + - construction of matrices of the following types: + + * ``RepresentationType.SplitIrredChevie`` + * ``RepresentationType.SplitIrredMarin`` + * ``RepresentationType.RegularLeft`` + + - multiplication of matrices and compare with the matrix of the + corresponding product of elements + - construction of maps between generic base and extension ring and + the user defined rings + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2, cubic_equation_roots=(3,7,11)) + sage: CHA2._test_matrix_constructions() + """ + # ---------------------------------------------------------------------- + # testing matrix constructions + # ---------------------------------------------------------------------- + if self.ngens() > 4: + return + + gens = self.gens() + b1 = gens[0] + b2 = self.an_element() + b12 = b1*b2 + verbose('b12 %s' % b12) + + def check_matrix(representation_type): + m1 = b1.matrix(representation_type=representation_type) + m2 = b2.matrix(representation_type=representation_type) + m12mult = m1*m2 + m12mat = b12.matrix(representation_type=representation_type) + test_matrix = self._tester(**options) + test_matrix.assertTrue(m12mult == m12mat) + + from sage.combinat.root_system.reflection_group_real import is_chevie_available + + if is_chevie_available(): + check_matrix(RepresentationType.SplitIrredChevie) + if self.ngens() < 3: + check_matrix(RepresentationType.SplitIrredMarin) + elif self.ngens() < 4: + check_matrix(RepresentationType.SplitIrredMarin) + + if self.ngens() < 3: + check_matrix(RepresentationType.RegularLeft) + return + + # -------------------------------------------------------------------------- + # _init_basis_extension + # -------------------------------------------------------------------------- + def _init_basis_extension(self): + r""" + Return the extension of the basis for more than 4 strands hold + in file cache. + + The basis elements from the file are added to the elements of + the Marin basis. + + EXAMPLES:: + + sage: CHA5 = algebras.CubicHecke(5) # optional - database_cubic_hecke # indirect doctest + sage: fc = CHA5._filecache # optional - database_cubic_hecke + sage: be = fc.section.basis_extensions # optional - database_cubic_hecke + sage: CHA5.reset_filecache(be) # optional - database_cubic_hecke + sage: fc.read(be) # optional - database_cubic_hecke + [[4], [-4]] + sage: ele = CHA5.an_element() # optional - database_cubic_hecke + sage: CHA5.inject_variables() # optional - database_cubic_hecke + Defining c0, c1, c2, c3 + sage: ele2 = ele * c3 # optional - database_cubic_hecke + sage: bex = fc.read(be) # optional - database_cubic_hecke + sage: bex.sort(); bex # optional - database_cubic_hecke + [[-4], [1, -3, 4], [1, -2, 4], [3, 2, 4], [4]] + """ + self._basis_extension = [] + tietze_list = self._basis_tietze() + cbg = self._cubic_braid_group + self._finite_sub_basis_tuples = {} + + order_list = [cbg(tup) for tup in tietze_list] + # We avoid the call to set_order for speed. + # This avoids hashing the keys, which can become expensive. + self._order = order_list + verbose('finite sub basis length: %s' % (len(order_list)), level=2) + + if self._nstrands < 5: + return + + # ---------------------------------------------------------------------- + # loading the extension of the basis from data file + # ---------------------------------------------------------------------- + fc = self._filecache + former_bas_ext = fc.read(fc.section.basis_extensions) + + # ---------------------------------------------------------------------- + # pre definition of additional basis elements + # ---------------------------------------------------------------------- + cub_braid_group = self.cubic_braid_group() + if not former_bas_ext: + gens = cub_braid_group.gens() + last_gen = gens[len(gens)-1] + self._cubic_braid_image(last_gen, check=False) + self._cubic_braid_image(~last_gen, check=False) + self._filecache.update_basis_extensions(self._basis_extension) + return + + # ---------------------------------------------------------------------- + # Installing the additional basis elements from filecache via the + # embedding of the corresponding cubic braid + # ---------------------------------------------------------------------- + for bas_Tietze in former_bas_ext: + cub_braid = cub_braid_group(bas_Tietze) + self._cubic_braid_image(cub_braid, check=False) + + verbose('finite sub basis (extended) length: %s' % (len(self.get_order())), level=2) + self._filecache.update_basis_extensions(self._basis_extension) + return + + # -------------------------------------------------------------------------- + # _braid_image_from_filecache + # -------------------------------------------------------------------------- + def _braid_image_from_filecache(self, braid): + r""" + Return the image of the given braid in ``self`` from file cache (if + contained). + + INPUT: + + - ``braid`` -- braid as instance of the braid group of ``self`` + + OUTPUT: + + Image of braid as element of ``self``. ``None`` if the product has + not been stored. + + EXAMPLES:: + + sage: CHA3 = algebras.CubicHecke(3) + sage: b1, b2 = CHA3.braid_group().gens(); br = ~b2*b1*~b2 + sage: CHA3._braid_image_from_filecache(br) + 1/w*c0*c1*c0^-1*c1 + v/w*c1^-1*c0 + ((-u)/w)*c0*c1*c0^-1 + sage: F = CHA3.base_ring().fraction_field() + sage: par = tuple([F(p) for p in CHA3.cubic_equation_parameters()]) + sage: CHA3F = algebras.CubicHecke(3, cubic_equation_parameters=par) + sage: CHA3F._braid_image_from_filecache(br) + 1/w*c0*c1*c0^-1*c1 + v/w*c1^-1*c0 + ((-u)/w)*c0*c1*c0^-1 + sage: section = CHA3.filecache_section().braid_images + sage: CHA3.reset_filecache(section) + sage: CHA3._braid_image_from_filecache(br) + """ + base_ring = self.base_ring() + gen_base_ring = self.base_ring(generic=True) + result_vect = self._filecache.read_braid_image(braid.Tietze(), gen_base_ring) + if result_vect is not None: + if gen_base_ring != base_ring: + base_map = self._ring_of_definition_map + result_vect = vector(base_ring, [base_map(cf) for cf in result_vect]) + return self.from_vector(result_vect) + return None + + # -------------------------------------------------------------------------- + # _braid_image_to_filecache + # -------------------------------------------------------------------------- + def _braid_image_to_filecache(self, braid_tietze, braid_image_vect): + r""" + Write the given braid image of to file cache. + + INPUT: + + - ``braid_tietze`` -- braid in Tietze form + - ``braid_image_vect`` -- image of the given braid in ``self`` in vector + representation + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2) + sage: br, = CHA2.braid_group().gens(); br2 = br**2 + sage: section = CHA2.filecache_section().braid_images + sage: CHA2.is_filecache_empty(section) # note: 2-strand images are not automatically cached in file system + True + sage: CHA2._braid_image_to_filecache(br2.Tietze(), CHA2(br2).to_vector()) + sage: CHA2._braid_image_from_filecache(br2) + w*c^-1 + u*c + (-v) + sage: CHA2.reset_filecache(CHA2.filecache_section().braid_images) + sage: CHA2._braid_image_from_filecache(br2) == None + True + """ + if self.base_ring() != self.base_ring(generic=True): + # this should not be done for specialized base rings + return + + self._filecache.write_braid_image(braid_tietze, braid_image_vect) + return + + # -------------------------------------------------------------------------- + # _braid_image + # -------------------------------------------------------------------------- + @cached_method + def _braid_image(self, braid): + r""" + Return the image of the given braid in ``self``. + + INPUT: + + - ``braid`` -- :class:`~sage.groups.braid.Braid` whose image + in ``self`` should be calculated + + OUTPUT: + + An instance of the element class of ``self``. + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2) + sage: br, = CHA2.braid_group().gens(); br2 = br**2 + sage: CHA2._braid_image(br2) + w*c^-1 + u*c + (-v) + """ + # ---------------------------------------------------------------------- + # first use the cubic equation to express the braid as a linear + # combination of braids having no other exponent as 1 and -1 in their + # defining word in the braid generators + # ---------------------------------------------------------------------- + coeffs, braids = self._reduce_all_gen_powers(braid.Tietze()) + + # ---------------------------------------------------------------------- + # in the second step the images of these "reduced" braids is calculated + # ---------------------------------------------------------------------- + result = self.zero() + for i in range(len(coeffs)): + braid_image = self._braid_image_from_reduced_powers(braids[i]) + result += coeffs[i]*braid_image + + return result + + # -------------------------------------------------------------------------- + # _braid_image_from_reduced_powers + # -------------------------------------------------------------------------- + @cached_method + def _braid_image_from_reduced_powers(self, braid_tietze): + r""" + Return the image of a braid in ``self`` assuming that no successive + repetitions occur in the Tietze form of the braid. + + INPUT: + + - ``braid_tietze`` -- tuple representing the Braid whose image in + ``self`` should be computed; it is assumed that no successive + repetitions occur among the entries (i.e. ``(1, 1)`` is not allowed + but ``(1, -2, 1)`` is) + + OUTPUT: + + The image of the braid as an element of ``self``. + + EXAMPLES:: + + sage: CHA3 = algebras.CubicHecke(3) + sage: CHA3._braid_image_from_reduced_powers((1, -2, 1)) + c0*c1^-1*c0 + sage: CHA3._braid_image_from_reduced_powers((1, -2, 1, 2)) + w*c0^-1*c1*c0^-1 + (-v)*c1*c0^-1 + u*c0*c1*c0^-1 + """ + n = self.ngens() + braid_list = list(braid_tietze) + len_braid = len(braid_list) + + # ---------------------------------------------------------------------- + # in the case of two strands we calculate the power of the generator + # ---------------------------------------------------------------------- + if n == 1: + if len_braid == 0: + return self.one() + k = braid_tietze[0]*len_braid + result_vect = self._reduce_gen_power(k) + result = self.from_vector(result_vect) + return result + + # ---------------------------------------------------------------------- + # Try to use former calculations (from dynamic library) to obtain the + # braid image + # ---------------------------------------------------------------------- + result, word_decomposition = self._braid_image_from_former_calculations(braid_tietze) + + if word_decomposition is None: + return result + + # ---------------------------------------------------------------------- + # proceed the calculation by use of the regular representation matrices + # (given by Ivan Marin) or in case of more than 4 strands by extension + # of the finite sub basis + # ---------------------------------------------------------------------- + + if n > 3 and (n in braid_tietze or -n in braid_tietze): + # ------------------------------------------------------------------ + # matrices for the regular representation are at the moment just + # available in the case of less than five strands. In the higher + # cases the basis is realized to grow up from the basis on 4 strands + # to use the recursion, only those cubic braids are stored as new + # basis elements if they involve the generator with largest index + # ------------------------------------------------------------------ + return self._braid_image_by_basis_extension(braid_tietze) + + word_left, word_result, word_right = word_decomposition + result_vect = None + + if word_left is not None: + # ------------------------------------------------------------------ + # Operating from the left on the pre-calculated result + # ------------------------------------------------------------------ + vect = result.to_vector() + braid_preimage = tuple(word_result) + result_vect = self._mult_by_regular_rep(vect, tuple(word_left), RepresentationType.RegularLeft, braid_preimage) + + if word_right is not None: + # ------------------------------------------------------------------ + # Operating from the right on the pre-calculated result + # ------------------------------------------------------------------ + if result_vect is not None: + vect = result_vect + braid_preimage = tuple(word_left + word_result) + else: + vect = result.to_vector() + braid_preimage = tuple(word_result) + result_vect = self._mult_by_regular_rep(vect, tuple(word_right), RepresentationType.RegularRight, braid_preimage) + + result = self.from_vector(result_vect) + return result + + # -------------------------------------------------------------------------- + # _braid_image_from_former_calculations + # -------------------------------------------------------------------------- + def _braid_image_from_former_calculations(self, braid_tietze): + r""" + Return the image of a braid in ``self`` as far as this can be done by + use of former calculations and is sure not to go into an endless + recursion, that is + + - using the cubic Hecke sub-algebra on one strand less + - using the file cache. + + If the image can not be calculated from former registered results this + method returns None. Therefore, it is just intended to be used as as + step in the complete calculation. + + INPUT: + + - ``braid_tietze`` -- tuple representing the braid whose image in + ``self`` should be computed; he generator exponents in the braid + word are assumed to be ``1`` or ``-1`` + + OUTPUT: + + A pair (image, basis_factors) where result is an element of ``self`` + representing the image of the input if calculation was possible and + ``None`` else-wise. If ``image == None`` the output basis_factors is + given as a list of basis element whose product equals the input. + + EXAMPLES:: + + sage: CHA3 = algebras.CubicHecke(3) + sage: CHA3._braid_image_from_former_calculations((1, -2, 1)) + (c0*c1^-1*c0, None) + sage: CHA3._braid_image_from_former_calculations((1, -2, 1, 2)) + (c0*c1^-1*c0, ([], [1, -2, 1], [2])) + """ + braid_list = list(braid_tietze) + len_braid = len(braid_list) + result = None + n = self.ngens() + + # ---------------------------------------------------------------------- + # if the braid is in the basis take its image to be the corresponding + # monomial + # ---------------------------------------------------------------------- + result = self._tietze_to_finite_sub_basis_monomial(braid_tietze) + if result is not None: + return result, None + + # ---------------------------------------------------------------------- + # if the braid lies in a sub-algebra take its image from there. + # ---------------------------------------------------------------------- + sub_alg = self.cubic_hecke_subalgebra() + if n not in braid_list and -n not in braid_list: + result = self(sub_alg(braid_tietze)) + verbose('end (%s): %s in smaller algebra' % (braid_list, result), level=2) + return result, None + + # ---------------------------------------------------------------------- + # proceed the calculation by splitting self into a product of basis + # elements and try to simplify. + # ---------------------------------------------------------------------- + braid_group = self.braid_group() + braid = braid_group(braid_tietze) + result = self._braid_image_from_filecache(braid) + if result is not None: + verbose('end from file cache (%s)' % (list(braid_tietze)), level=2) + return result, None + + # ---------------------------------------------------------------------- + # If we come here len_braid must be larger than 1 (otherwise we already + # have found in in the basis). By recursion we check if the subwords + # with one generator removed on the left (respectively on the right) + # side contain a subword whose image has already been calculated. We + # choose the longest such subword as our result. + # ---------------------------------------------------------------------- + braid_list_red_left = [braid_tietze[j] for j in range(1, len_braid)] + braid_list_red_right = [braid_tietze[j] for j in range(len_braid - 1)] + + result_left, word_decomp_left = self._braid_image_from_former_calculations(tuple(braid_list_red_left)) + result_right, word_decomp_right = self._braid_image_from_former_calculations(tuple(braid_list_red_right)) + if word_decomp_left is None: + return result_left, ([braid_tietze[0]], braid_list_red_left, []) + + if word_decomp_right is None: + return result_right, ([], braid_list_red_right, [braid_tietze[len_braid - 1]]) + + word_decomp_left_left, word_decomp_left_result, word_decomp_left_right = word_decomp_left + word_decomp_right_left, word_decomp_right_result, word_decomp_right_right = word_decomp_right + + if len(word_decomp_left_result) >= len(word_decomp_right_result): + return result_left, ([braid_tietze[0]] + word_decomp_left_left, word_decomp_left_result, word_decomp_left_right) + + return result_right, (word_decomp_right_left, word_decomp_right_result, word_decomp_right_right + [braid_tietze[len_braid - 1]]) + + # -------------------------------------------------------------------------- + # _braid_image_by_basis_expansion_ + # -------------------------------------------------------------------------- + @cached_method + def _braid_image_by_basis_extension(self, braid_tietze): + r""" + Return the given braid as a new basis element of ``self`` expanding the + incomplete order (which is just a part of the whole basis) in the case + of more than 4 strands. + + INPUT: + + - ``braid_tietze`` -- tuple representing the braid whose image in + ``self`` should be computed; he generator exponents in the braid + word are assumed to be ``1`` or ``-1`` + + OUTPUT: + + An instance of the element class of ``self``. + + EXAMPLES:: + + sage: CHA5 = algebras.CubicHecke(5) # optional - database_cubic_hecke + sage: be = CHA5.filecache_section().basis_extensions # optional - database_cubic_hecke + sage: CHA5.reset_filecache(be) # optional - database_cubic_hecke + sage: CHA5._basis_extension # optional - database_cubic_hecke + [[4], [-4]] + sage: CHA5._braid_image_by_basis_extension((4,1)) # optional - database_cubic_hecke + c3*c0 + sage: CHA5._basis_extension # optional - database_cubic_hecke + [[4], [-4], [4, 1]] + + case where the braid already has an corresponding basis element:: + + sage: CHA5._braid_image_by_basis_extension((1,)) # optional - database_cubic_hecke + c0 + sage: CHA5._basis_extension # optional - database_cubic_hecke + [[4], [-4], [4, 1]] + + case where the braid doesn't have corresponding basis element but depends + on them:: + + sage: CHA5._braid_image_by_basis_extension((1,1)) # optional - database_cubic_hecke + Traceback (most recent call last): + ... + NotImplementedError: no algorithm available to calculate braid image of (1, 1) + """ + cubic_braid = self._cubic_braid_group(braid_tietze) + tup = self._cubic_braid_basis_tuple(cubic_braid) + if tup is not None: + bgrp = self.braid_group() + if bgrp(braid_tietze) != bgrp(tup): + raise NotImplementedError('no algorithm available to calculate braid image of %s' % str(braid_tietze)) + B = self.basis() + verbose('braid-image %s in Basis' % str(braid_tietze), level=2) + return self.monomial(B[cubic_braid]) + + return self._cubic_braid_append_to_basis(cubic_braid) + + # -------------------------------------------------------------------------- + # _reduce_all_gen_powers + # -------------------------------------------------------------------------- + @cached_method + def _reduce_all_gen_powers(self, braid_tietze): + r""" + Return a linear combination of braids that have no higher powers in the + braid generators having the same image in ``self`` than the given braid. + + This linear combination is returned as a pair of lists of braids and + corresponding coefficients. + + INPUT: + + - ``braid_tietze`` -- tuple representing the braid whose powers should + be reduced given in Tietze form + + OUTPUT: + + A pair of two lists: ``coeffs``, ``braids``. The fist one contains the + coefficients corresponding to the braids in Tietze form from the second + list of braids. + + EXAMPLES:: + + sage: CHA3 = algebras.CubicHecke(3) + sage: CHA3._reduce_all_gen_powers((1, 1, -2, -2)) + ([u*v/w, (-v)/w, (-v^2)/w, (-u^2)/w, u/w, u*v/w, -u, 1, v], + [(), (2,), (-2,), (1,), (1, 2), (1, -2), (-1,), (-1, 2), (-1, -2)]) + """ + braid_list = list(braid_tietze) + len_braid = len(braid_list) + + # ---------------------------------------------------------------------- + # find a higher power position in braid_list + # ---------------------------------------------------------------------- + power = 0 + pos = 0 + for i in range(len_braid - 1): + if braid_list[i] != braid_list[i + 1]: + continue + pos = i + for power in range(1, len_braid - pos + 1): + if pos+power == len_braid: + break + if braid_list[pos] != braid_list[pos+power]: + break + break + + if power == 0: + verbose('end (%s) no powers' % braid_list, level=2) + return [self.base_ring().one()], [braid_tietze] + + # ---------------------------------------------------------------------- + # eliminate this power from braid_tietze. + # ---------------------------------------------------------------------- + val = braid_list[pos] + if val > 0: + gen_ind = val + exp = power + else: + gen_ind = -val + exp = -power + + braid_list_start = [braid_list[i] for i in range(pos)] + braid_list_end = [braid_list[i] for i in range(pos+power, len_braid)] + + # ---------------------------------------------------------------------- + # merging the new reduced tuple. Note that all the new tuples are + # smaller than the given one, which will make the recursion terminate. + # ---------------------------------------------------------------------- + tuple_one = tuple(braid_list_start + braid_list_end) + tuple_gen = tuple(braid_list_start + [gen_ind] + braid_list_end) + tuple_gen_inv = tuple(braid_list_start + [-gen_ind] + braid_list_end) + + # ---------------------------------------------------------------------- + # convert them to braids (to reduce cancellation of inverses and obvious + # braid relations) Note that this will not increase the length of the + # word. Thus the recursion still must terminate. + # ---------------------------------------------------------------------- + braid_group = self.braid_group() + braid_one = braid_group(tuple_one) + braid_gen = braid_group(tuple_gen) + braid_gen_inv = braid_group(tuple_gen_inv) + + # ---------------------------------------------------------------------- + # eliminate all powers from braid_tietze by recursion. The recursion + # will terminate by the length reduction of the Tietze tuple (but not + # necessarily by the number of generators whose exponent must be reduced). + # ---------------------------------------------------------------------- + one_coeffs, one_braids = self._reduce_all_gen_powers(braid_one.Tietze()) + gen_coeffs, gen_braids = self._reduce_all_gen_powers(braid_gen.Tietze()) + gen_inv_coeffs, gen_inv_braids = self._reduce_all_gen_powers(braid_gen_inv.Tietze()) + + cf_one, cf_gen, cf_gen_inv = self._reduce_gen_power(exp) + + one_coeffs = [cf*cf_one for cf in one_coeffs] + gen_coeffs = [cf*cf_gen for cf in gen_coeffs] + gen_inv_coeffs = [cf*cf_gen_inv for cf in gen_inv_coeffs] + + return one_coeffs + gen_coeffs + gen_inv_coeffs, one_braids + gen_braids + gen_inv_braids + + # -------------------------------------------------------------------------- + # _reduce_gen_power + # -------------------------------------------------------------------------- + @cached_method + def _reduce_gen_power(self, k): + r""" + Return the `k`-th power on an arbitrary generator, + for example `c_0^k`. + + INPUT: + + - ``k`` -- integer giving the power + + OUTPUT: + + A list ``[coeff_one, coeff_gen, coeff_gen_inverse]`` of the three + coefficients of the generators power in the span of the generator. + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2) + sage: CHA2._reduce_gen_power(5) + (-u^3*v + 2*u*v^2 + u^2*w - 2*v*w, u^4 - 3*u^2*v + + v^2 + 2*u*w, u^3*w - 2*u*v*w + w^2) + """ + n = self.ngens() + + # ---------------------------------------------------------------------- + # take it from smaller sub-algebras if possible + # ---------------------------------------------------------------------- + if n > 1: + sub_alg = self.cubic_hecke_subalgebra() + return sub_alg._reduce_gen_power(k) + + # ---------------------------------------------------------------------- + # calculate it in the sub-algebra on 2 strands + # ---------------------------------------------------------------------- + + if k == 0: + result_ele = self.one() + result = result_ele.to_vector() + + elif abs(k) == 1: + result_ele = self._tietze_to_finite_sub_basis_monomial(tuple([k])) + result = result_ele.to_vector() + + else: + if k < 0: + right_vect = self._reduce_gen_power(k + 1) + genTietze = (-1,) + else: + right_vect = self._reduce_gen_power(k - 1) + genTietze = (1,) + result = self._mult_by_regular_rep(right_vect, genTietze, RepresentationType.RegularLeft) + return result + + # -------------------------------------------------------------------------- + # _mult_by_regular_rep + # -------------------------------------------------------------------------- + @cached_method + def _mult_by_regular_rep(self, vect, gen_tuple, representation_type, braid_preimage=None): + r""" + Return the product of an`element of ``self`` given as a coefficient + vector with a sequence (tuple) of generators (that is a braid word) + using regular representation matrices. + + The multiplication will be performed form left or right according + to the given ``representation_type``. + + INPUT: + + - ``vect`` -- element of ``self`` in vector form (obtained by + :meth:`to_vector`) + - ``gen_tuple`` -- list of generators (that is a braid in Tietze form) + which operates on ``vect`` + - ``representation_type`` -- instance of :class:`RepresentationType` + (one of `RegularLeft` or `RegularRight`) + - ``braid_preimage`` -- (optional) a word representing a braid whose + image is vect (if it exist). This is used to record intermediate + results to the dynamic library + + OUTPUT: + + The coefficient vector resulting after applying the multiplication of all + matrices corresponding to the generators from gen_tuple. + + EXAMPLES:: + + sage: CHA3 = algebras.CubicHecke(3) + sage: CHA3.inject_variables() + Defining c0, c1 + sage: repr_type = CHA3.repr_type.RegularRight + sage: CHA3._mult_by_regular_rep(c0.to_vector(), (1, -2, -2), repr_type) + (u*v/w, (-u^2)/w, -u, (-v)/w, (-v^2)/w, u/w, u*v/w, 1, v, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + """ + verbose('multiply %s (pre-image %s) by %s using %s' + % (vect, braid_preimage, gen_tuple, representation_type), level=2) + m = len(gen_tuple) + braid_list = None + if braid_preimage: + braid_list = list(braid_preimage) + + result = vect + for i in range(m): + verbose('multiply image of %s with position %s' % (braid_list, i), level=2) + + if representation_type == RepresentationType.RegularLeft: + gen_ind = gen_tuple[m - i - 1] + if braid_list: + braid_list = [gen_ind] + braid_list + else: + gen_ind = gen_tuple[i] + if braid_list: + braid_list = braid_list + [gen_ind] + + if (gen_ind, representation_type) in list(self._gens_reg_repres_matrix.keys()): + mat = self._gens_reg_repres_matrix[(gen_ind, representation_type)] + else: + if gen_ind > 0: + gen = self.gen(gen_ind - 1) + mat = gen.matrix(representation_type=representation_type) + else: + # data of inverse of generators is stored under negative strand-index + gen = self.gen(-gen_ind - 1)**(-1) + mat = gen.matrix(representation_type=representation_type) + + self._gens_reg_repres_matrix[(gen_ind, representation_type)] = mat + + result = mat * result + + # ------------------------------------------------------------------ + # save this intermediate result to the dynamic library + # ------------------------------------------------------------------ + if braid_list: + verbose('save image of %s to file cache' % braid_list, level=2) + self._braid_image_to_filecache(tuple(braid_list), result) + + verbose('multiply %s by %s using %s result %s' % (vect, gen_tuple, representation_type, result), level=2) + return result + + # -------------------------------------------------------------------------- + # _cubic_braid_append_to_basis + # -------------------------------------------------------------------------- + + def _cubic_braid_append_to_basis(self, cubic_braid): + r""" + Append the given cubic braid to the finite sub basis which is used for + calculation of products and representation matrices. + + This only makes sense if the ``cubic_braid`` is not in this finite + sub-basis, before. This can happen if the number of strands is more + than 4. + + INPUT: + + - ``cubic_braid`` -- :class:`~sage.groups.cubic_braid.CubicBraid` + whose image in ``self`` should be appended + + OUTPUT: + + The new monomial of ``self``. + + EXAMPLES:: + + sage: CHA5 = algebras.CubicHecke(5) # optional - database_cubic_hecke + sage: be = CHA5.filecache_section().basis_extensions # optional - database_cubic_hecke + sage: CHA5.reset_filecache(be) # optional - database_cubic_hecke + sage: CHA5._basis_extension # optional - database_cubic_hecke + [[4], [-4]] + sage: CBG = CHA5.cubic_braid_group() # optional - database_cubic_hecke + sage: CHA5._cubic_braid_append_to_basis(CBG((4,1))) # optional - database_cubic_hecke + c3*c0 + sage: CHA5._basis_extension # optional - database_cubic_hecke + [[4], [-4], [4, 1]] + + """ + cbTietze = list(cubic_braid.Tietze()) + order = self.get_order() + next_index = len(order) + self._basis_extension.append(cbTietze) + try: + self._rank_basis.update({cubic_braid: next_index}) # supporting :meth:`get_order_key` + except AttributeError: + pass + order.append(cubic_braid) + monomial = self.monomial(cubic_braid) + self._finite_sub_basis_tuples.update({cubic_braid: cbTietze}) + + verbose('registering new basis element: %s (par %s ind %s)' + % (cubic_braid, cubic_braid.parent(), next_index), level=2) + self._filecache.update_basis_extensions(self._basis_extension) + return monomial + + # -------------------------------------------------------------------------- + # _cubic_braid_basis_tuple + # -------------------------------------------------------------------------- + def _cubic_braid_basis_tuple(self, cubic_braid): + r""" + Return the Tietze tuple that represents the given cubic_braid in the + basis of ``self``. + + In the case ``self`` has more than 4 strands it may happen that the + given cubic braid is not contained in the finite subbasis, so far. + In this case it is automatically added to it. + + INPUT: + + - ``cubic_braid`` -- :class:`~sage.groups.cubic_braid.CubicBraid` + + OUTPUT: + + A tuple from the basis representing the cubic braid. + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2) + sage: CBG = CHA2.cubic_braid_group() + sage: CHA2._cubic_braid_basis_tuple(CBG((1, 1))) + (-1,) + """ + tietze_list = self._basis_tietze() + cubic_braid_tietze = cubic_braid.Tietze() + if list(cubic_braid_tietze) in tietze_list: + verbose('cubic_braid_tietze: %s in basis' % str(cubic_braid_tietze), level=2) + return cubic_braid_tietze + else: + if cubic_braid in self._finite_sub_basis_tuples.keys(): + verbose('cubic_braid: %s in finite_sub_basis' % cubic_braid, level=2) + return self._finite_sub_basis_tuples[cubic_braid] + + for tup in tietze_list: + cb_tup = self.cubic_braid_group()(tup) + if cubic_braid == cb_tup: + self._finite_sub_basis_tuples.update({cb_tup: tup}) + verbose('cubic_braid: %s added to finite_sub_basis with tuple %s' + % (cubic_braid, tup), level=2) + return tuple(tup) + return None + + # -------------------------------------------------------------------------- + # _cubic_braid_image + # -------------------------------------------------------------------------- + def _cubic_braid_image(self, cubic_braid, check=True): + r""" + Return the given cubic braid as monomial of ``self``, that is the image + under the map onto the basis. + + If the number of strands is larger than 4, the corresponding basis + element may not be contained in the order of ``self``. In this + case it will be appended here. + + INPUT: + + - ``cubic_braid`` -- :class:`~sage.groups.cubic_braid.CubicBraid` + whose image in ``self`` should be returned + - ``check`` -- boolean (default: ``True``); check if the given cubic + braid is already registered in the finite sub basis; if set to + ``False`` duplicate entries can occur + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2) + sage: CBG = CHA2.cubic_braid_group() + sage: CHA2._cubic_braid_image(CBG((1,1))) + c^-1 + """ + if check: + tup = self._cubic_braid_basis_tuple(cubic_braid) + if tup is not None: + return self._tietze_to_finite_sub_basis_monomial(tup) + return self._cubic_braid_append_to_basis(cubic_braid) + + # -------------------------------------------------------------------------- + # _extend_braid_automorphism + # -------------------------------------------------------------------------- + def _extend_braid_automorphism(self, element, braid_automorphism): + r""" + Return the image of element under the extension of the given braid group + automorphism to ``self``. It is assumed that the given + ``braid_automorphism`` factors through ``self``. + + INPUT: + + - ``element`` -- an element class of ``self`` + - ``braid_automorphism`` -- braid group automorphism factoring through + ``self`` + + OUTPUT: + + Element class of ``self`` representing the image of element + under the extension of the given braid group automorphism. + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2) + sage: br, = CHA2.gens() + sage: CHA2.mirror_isomorphism(br) # indirect doctest + c^-1 + """ + + result = self.zero() + for braid in element.support(): + autom_braid = braid_automorphism(braid) + img_braid = self._braid_image_from_reduced_powers(autom_braid.Tietze()) + result += element[braid] * img_braid + + return result + + # -------------------------------------------------------------------------- + # _markov_trace_module + # -------------------------------------------------------------------------- + def _markov_trace_module(self, extended=False, field_embedding=False): + r""" + Return the module that contains the formal Markov trace as elements. + + INPUT: + + - ``extended`` -- boolean (default: ``False``); if set to ``True`` the + base ring of the module is the Markov trace version of the generic + extension ring of ``self``. + + - ``field_embedding`` -- boolean (default: ``False``); if set to ``True` + the base ring of the module is the smallest field containing the + generic extension ring of ``self``. The keyword is meaningless if + ``extended=False``. + + OUTPUT: + + A :class:`~sage.combinat.free_module.CombinatorialFreeModule`. + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2) + sage: CHA2._markov_trace_module() + Free module generated by {U1, U2} + over Multivariate Polynomial Ring in u, v, w, s + over Integer Ring localized at (s, w, v, u) + + sage: CHA2._markov_trace_module(extended=True) + Free module generated by {U1, U2} + over Multivariate Laurent Polynomial Ring in a, b, c, s + over Splitting Algebra of x^2 + x + 1 with roots [e3, -e3 - 1] + over Integer Ring + + sage: CHA2._markov_trace_module(extended=True, field_embedding=True) + Free module generated by {U1, U2} + over Fraction Field of Multivariate Polynomial Ring in a, b, c, s + over Cyclotomic Field of order 3 and degree 2 + """ + from sage.modules.free_module import FreeModule + from sage.databases.cubic_hecke_db import MarkovTraceModuleBasis + basis = [b for b in MarkovTraceModuleBasis if b.strands() <= self._nstrands] + BRM = self.base_ring(generic=True).markov_trace_version() + if extended: + BRM = BRM.extension_ring() + if field_embedding: + emb = BRM.field_embedding() + BRM = emb.codomain() + return FreeModule(BRM, basis) + + # -------------------------------------------------------------------------- + # _markov_trace_coeffs + # -------------------------------------------------------------------------- + @cached_method + def _markov_trace_coeffs(self): + r""" + Return a list of formal Markov traces of the basis elements of ``self``. + + OUTPUT: + + A list of elements of the Markov trace module (over the Markov trace + version of the generic base ring). Each entry of the list corresponds + to the according basis element of ``self``. + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2) + sage: CHA2._markov_trace_coeffs() + [B[U2], s*B[U1], 1/s*B[U1]] + sage: M = _[0].parent(); M + Free module generated by {U1, U2} + over Multivariate Polynomial Ring in u, v, w, s + over Integer Ring localized at (s, w, v, u) + """ + M = self._markov_trace_module() + Mbas = M.basis().keys() + db = self._database + sec = db.section.markov_tr_cfs + cfs = db.read(sec, variables=M.base_ring().gens(), nstrands=self._nstrands) + d = self.dimension() + return [sum(cfs[bas_ele][i]*M(bas_ele) for bas_ele in Mbas) for i in range(d)] + + ############################################################################ + # -------------------------------------------------------------------------- + # public methods + # -------------------------------------------------------------------------- + ############################################################################ + def filecache_section(self): + r""" + Return the ``enum`` to select a section in the file cache. + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2) + sage: list(CHA2.filecache_section()) + [, + , + , + ] + """ + return self._filecache.section + + def is_filecache_empty(self, section=None): + r""" + Return ``True`` if the file cache of the given ``section`` is empty. + If no ``section`` is given the answer is given for the complete + file cache. + + INPUT: + + - ``section`` -- (default: all sections) an element of enum + :class:`~sage.databases.cubic_hecke_db.CubicHeckeFileCache.section` + that can be selected using :meth:`filecache_section` + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2) + sage: CHA2.is_filecache_empty() + False + """ + return self._filecache.is_empty(section=section) + + def reset_filecache(self, section=None, commit=True): + r""" + Reset the file cache of the given ``section`` resp. the complete + file cache if no ``section`` is given. + + INPUT: + + - ``section`` -- (default: all sections) an element of enum + :class:`~sage.databases.cubic_hecke_db.CubicHeckeFileCache.section` + that can be selected using :meth:`filecache_section` + - ``commit`` -- boolean (default: ``True``); if set to ``False`` the + reset is not written to the filesystem + + EXAMPLES:: + + sage: CHA5 = algebras.CubicHecke(5) # optional - database_cubic_hecke + sage: be = CHA5.filecache_section().basis_extensions # optional - database_cubic_hecke + sage: CHA5.is_filecache_empty(be) # optional - database_cubic_hecke + False + sage: CHA5.reset_filecache(be) # optional - database_cubic_hecke + sage: CHA5.is_filecache_empty(be) # optional - database_cubic_hecke + True + """ + fc = self._filecache + if section == fc.section.basis_extensions: + if self._nstrands < 5: + raise ValueError('not allowed for less than 5 strand') + fc.reset_library(section=section) + + if section == fc.section.basis_extensions: + self._init_basis_extension() + if commit: + fc.write(section=section) + + def strands(self): + r""" + Return the number of strands of the braid group whose group algebra + image is ``self``. + + EXAMPLES:: + + sage: CHA4 = algebras.CubicHecke(2) + sage: CHA4.strands() + 2 + """ + return self._nstrands + + # -------------------------------------------------------------------------- + # Garside involution + # -------------------------------------------------------------------------- + def garside_involution(self, element): + r""" + Return the image of the given element of ``self`` under the extension of + the Garside involution of braids to ``self``. + + This method may be invoked by the ``revert_garside`` method of the + element class of ``self``, alternatively. + + INPUT: + + - ``element`` -- instance of the element class of ``self`` + + OUTPUT: + + Instance of the element class of ``self`` representing the image of + ``element`` under the extension of the Garside involution to ``self``. + + EXAMPLES:: + + sage: CHA3 = algebras.CubicHecke(3) + sage: ele = CHA3.an_element() + sage: ele_gar = CHA3.garside_involution(ele); ele_gar + (-w)*c1*c0^-1 + u*c0 + v*c1 + ((-v*w+u)/w) + sage: ele == CHA3.garside_involution(ele_gar) + True + """ + braid_group = self.braid_group() + reverse_gens = [g for g in braid_group.gens()] + reverse_gens.reverse() + brgrp_garside_involution = braid_group.hom(reverse_gens, check=False) + return self._extend_braid_automorphism(element, brgrp_garside_involution) + + # -------------------------------------------------------------------------- + # orientation anti involution + # -------------------------------------------------------------------------- + def orientation_antiinvolution(self, element): + r""" + Return the image of the given element of ``self`` under the extension of + the orientation anti involution of braids to ``self``. The orientation + anti involution of a braid is given by reversing the order of generators + in the braid word. + + This method may be invoked by the ``revert_orientation`` method of the + element class of ``self``, alternatively. + + INPUT: + + - ``element`` -- instance of the element class of ``self`` + + OUTPUT: + + Instance of the element class of ``self`` representing the image of + ``element`` under the extension of the orientation reversing braid + involution to ``self``. + + EXAMPLES:: + + sage: CHA3 = algebras.CubicHecke(3) + sage: ele = CHA3.an_element() + sage: ele_ori = CHA3.orientation_antiinvolution(ele); ele_ori + (-w)*c1^-1*c0 + v*c0 + u*c1 + ((-v*w+u)/w) + sage: ele == CHA3.orientation_antiinvolution(ele_ori) + True + """ + braid_group = self.braid_group() + + def brgrp_orientation_antiinvolution(braid): + braid_list = list(braid.Tietze()) + braid_list.reverse() + return braid_group(tuple(braid_list)) + return self._extend_braid_automorphism(element, brgrp_orientation_antiinvolution) + + # -------------------------------------------------------------------------- + # mirror isomorphism + # -------------------------------------------------------------------------- + def mirror_isomorphism(self, element): + r""" + Return the image of the given element of ``self`` under the extension + of the mirror involution of braids to ``self``. The mirror involution + of a braid is given by inverting all generators in the braid word. It + does not factor through ``self`` over the base ring but it factors + through ``self`` considered as a `\ZZ`-module relative to the mirror + automorphism of the generic base ring. Considering ``self`` as algebra + over its base ring this involution defines an isomorphism of ``self`` + onto a different cubic Hecke algebra with a different cubic equation. + This is defined over a different base (and extension) ring than + ``self``. It can be obtained by the method ``mirror_image`` or as + parent of the output of this method. + + This method may be invoked by the ``CubicHeckeElelemnt.revert_mirror`` + method of the element class of ``self``, alternatively. + + INPUT: + + - ``element`` -- instance of the element class of ``self`` + + OUTPUT: + + Instance of the element class of the mirror image of ``self`` + representing the image of element under the extension of the braid + mirror involution to ``self``. + + EXAMPLES:: + + sage: CHA3 = algebras.CubicHecke(3) + sage: ele = CHA3.an_element() + sage: ele_mirr = CHA3.mirror_isomorphism(ele); ele_mirr + -1/w*c0^-1*c1 + u/w*c0^-1 + v/w*c1^-1 + ((v*w-u)/w) + sage: ele_mirr2 = ele.revert_mirror() # indirect doctest + sage: ele_mirr == ele_mirr2 + True + sage: par_mirr = ele_mirr.parent() + sage: par_mirr == CHA3 + False + sage: par_mirr == CHA3.mirror_image() + True + sage: ele == par_mirr.mirror_isomorphism(ele_mirr) + True + """ + mirror_image = self.mirror_image() + braid_group = self.braid_group() + mirror_involution = braid_group.hom([~g for g in braid_group.gens()], check=False) + # Todo: have mirror_involution be a method of :class:`BraidGroup_class` + base_ring_mirror = self._base_ring_mirror + element_vec = vector([base_ring_mirror(cf) for cf in list(element.to_vector())]) + element_mirr = mirror_image.from_vector(element_vec) + return mirror_image._extend_braid_automorphism(element_mirr, mirror_involution) + + # -------------------------------------------------------------------------- + # cubic_equation + # -------------------------------------------------------------------------- + def cubic_equation(self, var='h', as_coefficients=False, generic=False): + r""" + Return the cubic equation attached to ``self``. + + INPUT: + + - ``var`` -- string (default ``h``) setting the indeterminate of the + equation + - ``as_coefficients`` -- boolean (default: ``False``); if set to ``True`` + the list of coefficients is returned + - ``generic`` -- boolean (default: ``False``); if set to ``True`` the + cubic equation will be given over the generic base ring + + OUTPUT: + + A polynomial over the base ring (resp. generic base ring if ``generic`` + is set to True). In case ``as_coefficients`` is set to ``True`` a list + of them is returned. + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2, cubic_equation_roots=(E(3), ~E(3), 1)) + sage: CHA2.cubic_equation() + h^3 - 1 + sage: CHA2.cubic_equation(generic=True) + h^3 - u*h^2 + v*h - w + sage: CHA2.cubic_equation(as_coefficients=True, generic=True) + [-w, v, -u, 1] + sage: CHA2.cubic_equation(as_coefficients=True) + [-1, 0, 0, 1] + """ + BaseRing = self.base_ring(generic=generic) + if generic: + u, v, w = BaseRing.gens() + else: + u, v, w = self._cubic_equation_parameters + cf = [-w, v, -u, 1] + if as_coefficients: + return cf + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + P = PolynomialRing(BaseRing, var) + return P(cf) + + # -------------------------------------------------------------------------- + # cubic_equation_roots + # -------------------------------------------------------------------------- + def cubic_equation_roots(self, generic=False): + r""" + Return the roots of the underlying cubic equation. + + INPUT: + + - ``generic`` -- boolean (default: ``False``); if set to ``True`` the + roots are returned as elements of the generic extension ring + + OUTPUT: + + A triple consisting of the roots. + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2, cubic_equation_roots=(3, 4, 5)) + sage: CHA2.cubic_equation() + h^3 - 12*h^2 + 47*h - 60 + sage: CHA2.cubic_equation_roots() + [3, 4, 5] + sage: CHA2.cubic_equation_roots(generic=True) + [a, b, c] + """ + if generic: + return self._generic_cubic_equation_roots + else: + return self._cubic_equation_roots + + # -------------------------------------------------------------------------- + # cubic_equation_roots + # -------------------------------------------------------------------------- + def cubic_equation_parameters(self, generic=False): + r""" + Return the coefficients of the underlying cubic equation. + + INPUT: + + - ``generic`` -- boolean (default: ``False``); if set to ``True`` the + coefficients are returned as elements of the generic base ring + + OUTPUT: + + A tripple consisting of the coefficients. + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2, cubic_equation_roots=(3, 4, 5)) + sage: CHA2.cubic_equation() + h^3 - 12*h^2 + 47*h - 60 + sage: CHA2.cubic_equation_parameters() + [12, 47, 60] + sage: CHA2.cubic_equation_parameters(generic=True) + [u, v, w] + """ + if generic: + return self._generic_cubic_equation_parameters + else: + return self._cubic_equation_parameters + + # -------------------------------------------------------------------------- + # base_ring + # -------------------------------------------------------------------------- + def base_ring(self, generic=False): + r""" + Return the base ring of ``self``. + + INPUT: + + - ``generic`` -- boolean (default: ``False``); if ``True`` the ring + of definition (here often called the generic base ring) is returned + + EXAMMPLES:: + + sage: CHA2 = algebras.CubicHecke(2, cubic_equation_roots=(3, 4, 5)) + sage: CHA2.base_ring() + Integer Ring localized at (2, 3, 5) + sage: CHA2.base_ring(generic=True) + Multivariate Polynomial Ring in u, v, w + over Integer Ring localized at (w,) + """ + if generic: + return self._ring_of_definition + else: + return super().base_ring() + + # -------------------------------------------------------------------------- + # extension_ring + # -------------------------------------------------------------------------- + def extension_ring(self, generic=False): + r""" + Return the extension ring of ``self``. + + This is an extension of its base ring containing the roots + of the cubic equation. + + INPUT: + + - ``generic`` -- boolean (default: ``False``); if ``True`` the + extension ring of definition (here often called the generic + extension ring) is returned + + EXAMMPLES:: + + sage: CHA2 = algebras.CubicHecke(2, cubic_equation_roots=(3, 4, 5)) + sage: CHA2.extension_ring() + Splitting Algebra of T^2 + T + 1 with roots [E3, -E3 - 1] + over Integer Ring localized at (2, 3, 5) + sage: CHA2.extension_ring(generic=True) + Multivariate Laurent Polynomial Ring in a, b, c + over Splitting Algebra of x^2 + x + 1 + with roots [e3, -e3 - 1] over Integer Ring + """ + if generic: + return self._generic_extension_ring + else: + return self._extension_ring + + # -------------------------------------------------------------------------- + # cyclotomic_generator + # -------------------------------------------------------------------------- + def cyclotomic_generator(self, generic=False): + r""" + Return the third root of unity as element of the extension ring. + + The only thing where this is needed is in the nine dimensional + irreducible representations of the cubic Hecke algebra on four strands + (see the examples of :meth:`CubicHeckeElement.matrix` for instance). + + INPUT: + + - ``generic`` -- boolean (default: ``False``); if ``True`` the + cyclotomic generator is returned as an element extension ring of + definition + + EXAMMPLES:: + + sage: CHA2 = algebras.CubicHecke(2, cubic_equation_roots=(3, 4, 5)) + sage: CHA2.cyclotomic_generator() + E3 + sage: CHA2.cyclotomic_generator(generic=True) + e3 + """ + e3gen = self.extension_ring(generic=True).cyclotomic_generator() + if generic: + return e3gen + else: + return self._generic_extension_ring_map(e3gen) + + # -------------------------------------------------------------------------- + # braid_group + # -------------------------------------------------------------------------- + def braid_group(self): + r""" + Return the braid group attached to ``self``. + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2) + sage: CHA2.braid_group() + Braid group on 2 strands + """ + return self._braid_group + + # -------------------------------------------------------------------------- + # cubic_braid_group + # -------------------------------------------------------------------------- + def cubic_braid_group(self): + r""" + Return the cubic braid group attached to ``self``. + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2) + sage: CHA2.cubic_braid_group() + Cubic Braid group on 2 strands + """ + return self._cubic_braid_group + + # -------------------------------------------------------------------------- + # braid_group_algebra + # -------------------------------------------------------------------------- + def braid_group_algebra(self): + r""" + Return the group algebra of braid group attached to ``self`` over the + base ring of ``self``. + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2) + sage: CHA2.braid_group_algebra() + Algebra of Braid group on 2 strands + over Multivariate Polynomial Ring in u, v, w + over Integer Ring localized at (w,) + """ + return self._braid_group_algebra + + # -------------------------------------------------------------------------- + # cubic_braid_group_algebra + # -------------------------------------------------------------------------- + def cubic_braid_group_algebra(self): + r""" + Return the group algebra of cubic braid group attached to ``self`` over + the base ring of ``self``. + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2) + sage: CHA2.cubic_braid_group_algebra() + Algebra of Cubic Braid group on 2 strands + over Multivariate Polynomial Ring in u, v, w + over Integer Ring localized at (w,) + """ + return self._cubic_braid_group_algebra + + # -------------------------------------------------------------------------- + # creating a CubicHeckeAlgebra as sub-algebra of self on less strands + # -------------------------------------------------------------------------- + def cubic_hecke_subalgebra(self, nstrands=None): + r""" + Return a :class:`CubicHeckeAlgebra` that realizes a sub-algebra + of ``self`` on the first ``n_strands`` strands. + + INPUT: + + - ``nstrands`` -- integer at least 1 and at most :meth:`strands` giving + the number of strands for the subgroup; the default is one strand + less than ``self`` has + + OUTPUT: + + An instance of this class realizing the sub-algebra. + + EXAMPLES:: + + sage: CHA3 = algebras.CubicHecke(3, cubic_equation_roots=(3, 4, 5)) + sage: CHA3.cubic_hecke_subalgebra() + Cubic Hecke algebra on 2 strands + over Integer Ring localized at (2, 3, 5) + with cubic equation: h^3 - 12*h^2 + 47*h - 60 = 0 + """ + if nstrands is None: + nstrands = self._nstrands - 1 + n = self._nstrands + nstrands = ZZ(nstrands) + + if nstrands >= n or nstrands <= 0: + raise ValueError('nstrands must be positive and less than %s' % self._nstrands) + + names = self.variable_names() + if nstrands == self._nstrands - 1 and self._cubic_hecke_subalgebra is not None: + return self._cubic_hecke_subalgebra + + names_red = names[:nstrands-1] + if self.base_ring() == self.base_ring(generic=True): + SubHeckeAlg = CubicHeckeAlgebra(names=names_red) + else: + SubHeckeAlg = CubicHeckeAlgebra(names=names_red, + cubic_equation_parameters=tuple(self._cubic_equation_parameters), + cubic_equation_roots=tuple(self._cubic_equation_roots)) + + if nstrands == self._nstrands - 1: + self._cubic_hecke_subalgebra = SubHeckeAlg + return SubHeckeAlg + + # -------------------------------------------------------------------------- + # mirror image + # -------------------------------------------------------------------------- + def mirror_image(self): + r""" + Return a copy of ``self`` with the mirrored cubic equation, that is: + the cubic equation has the inverse roots to the roots with respect + to ``self``. + + This is needed since the mirror involution of the braid group does + not factor through ``self`` (considered as an algebra over the base + ring, just considered as `\ZZ`-algebra). Therefore, the mirror + involution of an element of ``self`` belongs to ``mirror_image``. + + OUTPUT: + + A cubic Hecke algebra over the same base and extension ring, + but whose cubic equation is transformed by the mirror involution + applied to its coefficients and roots. + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2) + sage: ce = CHA2.cubic_equation(); ce + h^3 - u*h^2 + v*h - w + sage: CHA2m = CHA2.mirror_image() + sage: cem = CHA2m.cubic_equation(); cem + h^3 + ((-v)/w)*h^2 + u/w*h + (-1)/w + sage: mi = CHA2.base_ring().mirror_involution(); mi + Ring endomorphism of Multivariate Polynomial Ring in u, v, w + over Integer Ring localized at (w,) + Defn: u |--> v/w + v |--> u/w + w |--> 1/w + sage: cem == cem.parent()([mi(cf) for cf in ce.coefficients()]) + True + + Note that both cubic Hecke algebras have the same ring of definition + and identical generic cubic equation:: + + sage: cemg = CHA2m.cubic_equation(generic=True) + sage: CHA2.cubic_equation(generic=True) == cemg + True + sage: CHA2.cubic_equation() == cemg + True + sage: a, b, c = CHA2.cubic_equation_roots() + sage: CHA2m.cubic_equation_roots(generic=True) == [a, b, c] + True + sage: CHA2m.cubic_equation_roots() + [((-1)/(-w))*a^2 + (u/(-w))*a + (-v)/(-w), + ((1/(-w))*a)*b + (1/(-w))*a^2 + ((-u)/(-w))*a, + (((-1)/(-w))*a)*b] + sage: ai, bi, ci = _ + sage: ai == ~a, bi == ~b, ci == ~c + (True, True, True) + sage: CHA2.extension_ring(generic=True).mirror_involution() + Ring endomorphism of Multivariate Laurent Polynomial Ring in a, b, c + over Splitting Algebra of x^2 + x + 1 + with roots [e3, -e3 - 1] over Integer Ring + Defn: a |--> a^-1 + b |--> b^-1 + c |--> c^-1 + with map of base ring + + The mirror image can not be obtained for specialized cubic Hecke + algebras if the specialization does not factor through the mirror + involution on the ring if definition:: + + sage: CHA2s = algebras.CubicHecke(2, cubic_equation_roots=(3, 4, 5)) + sage: CHA2s + Cubic Hecke algebra on 2 strands + over Integer Ring localized at (2, 3, 5) + with cubic equation: h^3 - 12*h^2 + 47*h - 60 = 0 + + In the next example it is not clear what the mirror image of ``7`` + should be:: + + sage: CHA2s.mirror_image() + Traceback (most recent call last): + ... + RuntimeError: base ring Integer Ring localized at (2, 3, 5) + does not factor through mirror involution + """ + base_ring = self.base_ring() + base_gen = self.base_ring(generic=True) + + base_gen_mirror = base_gen.mirror_involution() + base_ring_mirror = self._base_ring_mirror + cepg = self.cubic_equation_parameters(generic=True) + cerg = self.cubic_equation_roots(generic=True) + if not base_ring_mirror: + mirr_paras_gen = [base_gen_mirror(par) for par in cepg] + mirr_paras = [base_ring(mirr_para) for mirr_para in mirr_paras_gen] + try: + base_ring_mirror = base_ring.hom(mirr_paras) + except (TypeError, ValueError, NotImplementedError): + raise RuntimeError('base ring %s does not factor through mirror involution' % base_ring) + + # check for involution + mirr_paras_back = [base_ring_mirror(mirr_para) for mirr_para in mirr_paras] + if mirr_paras_back != self.cubic_equation_parameters(): + raise RuntimeError('base ring %s does not factor through mirror involution' % base_ring) + self._base_ring_mirror = base_ring_mirror + + mirror_image = self._mirror_image + if mirror_image is None: + extension_ring = self.extension_ring() + extension_gen = self.extension_ring(generic=True) + extension_gen_mirror = extension_gen.mirror_involution() + + mirr_paras_gen = [base_gen_mirror(par) for par in cepg] + mirr_roots_gen = [extension_gen_mirror(root) for root in cerg] + + mirr_paras = tuple([base_ring(par) for par in mirr_paras_gen]) + mirr_roots = tuple([extension_ring(root) for root in mirr_roots_gen]) + n = self._nstrands + + mirror_image = CubicHeckeAlgebra(n, cubic_equation_parameters=mirr_paras, cubic_equation_roots=mirr_roots) + + # go back by involution property + mirror_image._mirror_image = self + mirror_image._base_ring_mirror = base_ring_mirror + mirror_image._ring_of_definition._mirror_ = base_gen_mirror + mirror_image._is_mirror = True + + self._mirror_image = mirror_image + + return mirror_image + + # -------------------------------------------------------------------------- + # Schur elements + # -------------------------------------------------------------------------- + def schur_elements(self, generic=False): + r""" + Return the list of Schur elements of ``self`` as elements + of the extension ring of ``self``. + + .. NOTE:: + + This method needs ``GAP3`` installed with package ``CHEVIE``. + + INPUT: + + - ``generic`` -- boolean (default: ``False``); if ``True``, + the element is returned as element of the generic + extension ring + + EXAMPLES:: + + sage: CHA3 = algebras.CubicHecke(3) # optional gap3 + sage: sch_eles = CHA3.schur_elements() # optional gap3 + sage: sch_eles[6] # optional gap3 + (u^3*w + v^3 - 6*u*v*w + 8*w^2)/w^2 + """ + gap3_result = self.chevie().SchurElements() + GER = self.extension_ring(generic=True) + generic_result = [GER(s) for s in gap3_result] + if generic: + return [s for s in generic_result] + else: + ER = self.extension_ring() + return [ER(s) for s in generic_result] + + # -------------------------------------------------------------------------- + # Schur element + # -------------------------------------------------------------------------- + def schur_element(self, item, generic=False): + r""" + Return a single Schur element of ``self`` as elements + of the extension ring of ``self``. + + .. NOTE:: + + This method needs ``GAP3`` installed with package ``CHEVIE``. + + INPUT: + + - ``item`` -- an element of :class:`AbsIrreducibeRep` to give + the irreducible representation of ``self`` to which the Schur + element should be returned + - ``generic`` -- boolean (default: ``False``); if ``True``, + the element is returned as element of the generic + extension ring + + EXAMPLES:: + + sage: CHA3 = algebras.CubicHecke(3) # optional gap3 + sage: CHA3.schur_element(CHA3.irred_repr.W3_111) # optional gap3 + (u^3*w + v^3 - 6*u*v*w + 8*w^2)/w^2 + """ + if not isinstance(item, AbsIrreducibeRep): + raise ValueError('item must be an instance of %s' % AbsIrreducibeRep) + return self.schur_elements(generic=generic)[item.gap_index()] + + # -------------------------------------------------------------------------- + # characters + # -------------------------------------------------------------------------- + def characters(self, irr=None, original=True): + r""" + Return the irreducible characters of ``self``. + + By default the values are given in the generic extension ring. + Setting the keyword ``original`` to ``False`` you can obtain + the values in the (non generic) extension ring (compare the + same keyword for :meth:`CubicHeckeElement.matrix`). + + INPUT: + + - ``irr`` -- (optional) instance of :class:`AbsIrreducibeRep` + selecting the irreducible representation corresponding to the + character; if not given a list of all characters is returned + - ``original`` -- (default: ``True``) see description above + + OUTPUT: + + Function or list of Functions from the element class of ``self`` to + the (generic or non generic) extension ring depending on the given + keyword arguments. + + EXAMPLES:: + + sage: CHA3 = algebras.CubicHecke(3) + sage: ch = CHA3.characters() + sage: e = CHA3.an_element() + sage: ch[0](e) + a^2*b + a^2*c + a^2 - b*c + b^-1*c^-1 + a^-1*c^-1 + a^-1*b^-1 + sage: _.parent() + Multivariate Laurent Polynomial Ring in a, b, c + over Splitting Algebra of x^2 + x + 1 with roots [e3, -e3 - 1] + over Integer Ring + sage: ch_w3_100 = CHA3.characters(irr=CHA3.irred_repr.W3_100) + sage: ch_w3_100(e) == ch[0](e) + True + sage: ch_x = CHA3.characters(original=False) + sage: ch_x[0](e) + (u + v)*a + (-v*w - w^2 + u)/w + sage: _.parent() + Splitting Algebra of T^2 + T + 1 with roots [E3, -E3 - 1] + over Splitting Algebra of h^3 - u*h^2 + v*h - w + with roots [a, b, -b - a + u] + over Multivariate Polynomial Ring in u, v, w + over Integer Ring localized at (w,) + """ + def char_function(ele): + if isinstance(ele, self.element_class): + m = ele.matrix(original=original) + return m[irr].trace() + if irr: + return char_function + irrs = [irr for irr in self.irred_repr if irr.number_gens() == self._nstrands - 1] + return [self.characters(irrs[i], original=original) for i in range(len(irrs))] + diff --git a/src/sage/algebras/hecke_algebras/cubic_hecke_base_ring.py b/src/sage/algebras/hecke_algebras/cubic_hecke_base_ring.py new file mode 100644 index 00000000000..d30633cad55 --- /dev/null +++ b/src/sage/algebras/hecke_algebras/cubic_hecke_base_ring.py @@ -0,0 +1,1489 @@ +r""" +Cubic Hecke Base Rings + +This module contains special classes of polynomial rings +(:class:`CubicHeckeRingOfDefinition` and :class:`CubicHeckeExtensionRing`) +used in the context of +:class:`cubic Hecke algebras +`. + +AUTHORS: + +- Sebastian Oehms May 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/ +############################################################################## + +from sage.structure.category_object import normalize_names +from sage.structure.element import get_coercion_model +from sage.categories.action import Action +from sage.misc.verbose import verbose +from sage.misc.functional import cyclotomic_polynomial +from sage.misc.cachefunc import cached_method +from sage.rings.integer_ring import ZZ +from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing_mpair +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.localization import Localization +from sage.algebras.splitting_algebra import solve_with_extension, SplittingAlgebra + + +# --------------------------------------------------------------------------------- +# local helper functions +# --------------------------------------------------------------------------------- +def normalize_names_markov(names, markov_trace_version): + r""" + Return a tuple of strings of variable names of length 3 resp. 4 (if + ``markov_trace_version`` is ``True``) according to the given input names. + + INPUT: + + - ``names`` -- passed to :func:`~sage.structure.category_object.normalize_names` + - ``markov_trace_version`` -- boolean; if set to ``True`` four names are + expected the last of which corresponds to the writhe factor of the + Markov trace + + EXAMPLES:: + + sage: from sage.algebras.hecke_algebras import cubic_hecke_base_ring as chbr + sage: chbr.normalize_names_markov('a, b, c', False) + ('a', 'b', 'c') + sage: chbr.normalize_names_markov(('u', 'v', 'w', 's'), False) + ('u', 'v', 'w') + """ + if markov_trace_version: + names = normalize_names(4, names) + else: + if type(names) == tuple: + names = list(names) + if type(names) == list and len(names) > 3: + names = normalize_names(3, names[0:3]) + else: + names = normalize_names(3, names) + return names + + +def register_ring_hom(ring_hom): + r""" + Register the given ring homomorphism as conversion map. + + EXAMPLES:: + + sage: from sage.algebras.hecke_algebras import cubic_hecke_base_ring as chbr + sage: BR = chbr.CubicHeckeRingOfDefinition() + sage: BR.create_specialization([E(5), E(7), E(3)]) # indirect doctest + Universal Cyclotomic Field + sage: _.convert_map_from(BR) + Ring morphism: + From: Multivariate Polynomial Ring in u, v, w + over Integer Ring localized at (w,) + To: Universal Cyclotomic Field + Defn: u |--> E(5) + v |--> E(7) + w |--> E(3) + """ + domain = ring_hom.domain() + codomain = ring_hom.codomain() + conversion_cached = codomain._is_conversion_cached(domain) + + if conversion_cached: + test_map = codomain.convert_map_from(domain) + try: + if test_map != ring_hom: + verbose('\nConversion:\n%s\n already exists and is different from:\n%s\n' % (test_map, ring_hom)) + except TypeError: + verbose('\n Conversion:\n%s\n already exists and is not comparable to:\n%s\n' % (test_map, ring_hom)) + else: + try: + codomain.register_conversion(ring_hom) + except ValueError: + verbose('\nthe map:\n%s\ncannot be registerd as conversion\n' % ring_hom) + + return + + +# ------------------------------------------------------------------------------ +# class for the Galois Group action on the generic extension ring corresponding +# to the cubic equation +# ------------------------------------------------------------------------------ +class GaloisGroupAction(Action): + r""" + Action on a multivariate polynomial ring by permuting the generators. + + EXAMPLES:: + + sage: from sage.algebras.hecke_algebras import cubic_hecke_base_ring as chbr + sage: from operator import mul + sage: R. = ZZ[] + sage: G = SymmetricGroup(3) + sage: p = 5*x*y + 3*z**2 + sage: R._unset_coercions_used() + sage: R.register_action(chbr.GaloisGroupAction(G, R, op=mul)) + sage: s = G([2,3,1]) + sage: s*p + 3*x^2 + 5*y*z + """ + def _act_(self, perm, pol): + r""" + Application of the action. + + EXAMPLES:: + + sage: from sage.algebras.hecke_algebras import cubic_hecke_base_ring as chbr + sage: from operator import mul + sage: R. = QQ[] + sage: G = SymmetricGroup(2) + sage: A = chbr.GaloisGroupAction(G, R, op=mul) + sage: p = ~5*x*y**2 + 3*x**2 + sage: s = G([2,1]) + sage: A._act_(s, p) + 1/5*x^2*y + 3*y^2 + """ + if not self.is_left(): + perm, pol = pol, perm + pol_dict = {} + for key, value in pol.dict().items(): + newkey = [0] * len(key) + for pos, k in enumerate(key): + newkey[perm(pos+1)-1] = k + pol_dict[tuple(newkey)] = value + return self.domain()(pol_dict) + + +################################################################################ +# EXTENSION RING +# ------------------------------------------------------------------------------ +# Definition of the generic extension ring for the cubic Hecke algebra as +# Laurent polynomial ring in 3 indeterminates over the cyclotomic field of a +# third root of unity This is the most general ring over which the cubic Hecke +# algebra is semi-simple. In opposite to the generic base ring class, this class +# does not inherits from UniqueRepresentation since _test_pickling fails +# ------------------------------------------------------------------------------ +class CubicHeckeExtensionRing(LaurentPolynomialRing_mpair): + r""" + The generic splitting algebra for the irreducible representations of + the cubic Hecke algebra. + + This ring must contain three invertible indeterminates (representing + the roots of the cubic equation) together with a third root of unity + (needed for the 18-dimensional irreducibles of the cubic Hecke algebra + on 4 strands). + + Therefore this ring is constructed as a multivariate Laurent polynomial + ring in three indeterminates over a polynomial quotient ring over the + integers with respect to the minimal polynomial of a third root of unity. + + The polynomial quotient ring is constructed as instance of + :class:`SplittingAlgebra`. + + INPUT: + + - ``names`` -- (default: ``'u,v,w'``) string containing the names of the + indeterminates separated by ``,`` or a triple of strings each of which + are the names of one of the three indeterminates + - ``order`` -- string (default: ``'degrevlex'``); the term order; see also + :class:`~sage.rings.polynomial.laurent_polynomial_ring.LaurentPolynomialRing_mpair` + - ``ring_of_definition`` -- (optional) a :class:`CubicHeckeRingOfDefinition` + to specify the generic cubic Hecke base ring over which ``self`` may be + realized as splitting ring via the ``as_splitting_algebra`` method + - ``third_unity_root_name`` -- string (default: ``'e3'``); for setting the + name of the third root if unity of ``self`` + - ``markov_trace_version`` -- boolean (default: ``False``) if this is + set to ``True`` then ``self`` contains one invertible indeterminate in + addition which is meant to represent the writhe factor of a Markov trace + on the cubic Hecke algebra and which default name is ``s`` + + EXAMPLES:: + + sage: from sage.algebras.hecke_algebras import cubic_hecke_base_ring as chbr + sage: chbr.CubicHeckeExtensionRing('a, b, c') + Multivariate Laurent Polynomial Ring in a, b, c + over Splitting Algebra of x^2 + x + 1 + with roots [e3, -e3 - 1] + over Integer Ring + sage: _.an_element() + b^2*c^-1 + e3*a + """ + def __init__(self, names, order='degrevlex', ring_of_definition=None, third_unity_root_name='e3', markov_trace_version=False): + r""" + Initialize ``self``. + + TESTS:: + + sage: from sage.algebras.hecke_algebras import cubic_hecke_base_ring as chbr + sage: ER = chbr.CubicHeckeExtensionRing('a, b, c') + sage: TestSuite(ER).run() + """ + # ---------------------------------------------------------------------- + # Setting connection with generic base ring (if given) + # ---------------------------------------------------------------------- + self._ring_of_definition = None + self._splitting_algebra = None + + if ring_of_definition is not None: + if not isinstance(ring_of_definition, CubicHeckeRingOfDefinition): + raise TypeError("generic base ring must be an instance of CubicHeckeRingOfDefinition") + self._ring_of_definition = ring_of_definition + + # ---------------------------------------------------------------------- + # defining the base ring + # note that we can't use ZZ.extension since it isn't possible to define + # homomorphisms from orders in number fields, yet + # ---------------------------------------------------------------------- + base_ring = SplittingAlgebra(cyclotomic_polynomial(3), [third_unity_root_name]) + + # ---------------------------------------------------------------------- + # defining the ring itself + # ---------------------------------------------------------------------- + self._names = normalize_names_markov(names, markov_trace_version) + self._order = order + + pol_ring = PolynomialRing(base_ring, names=self._names, order=self._order, implementation=None) + LaurentPolynomialRing_mpair.__init__(self, pol_ring) + + # ---------------------------------------------------------------------- + # setting Galois group action + # ---------------------------------------------------------------------- + from sage.groups.perm_gps.permgroup_named import SymmetricGroup + from operator import mul + self._galois_group = SymmetricGroup(3) + galois_group_action = GaloisGroupAction(self._galois_group, self, op=mul) + self._unset_coercions_used() + self.register_action(galois_group_action) + + # ---------------------------------------------------------------------- + # Init of data used on demand + # ---------------------------------------------------------------------- + self._mirror = None + return + + ############################################################################ + # overloaded inherited methods + ############################################################################ + def construction(self): + r""" + Return ``None`` since this construction is not functorial. + + EXAMPLES:: + + sage: from sage.algebras.hecke_algebras import cubic_hecke_base_ring as chbr + sage: ER = chbr.CubicHeckeExtensionRing('a, b, c') + sage: ER._test_category() # indirect doctest + """ + return None + + def __reduce__(self): + r""" + Used in pickling. + + TESTS:: + + sage: from sage.algebras.hecke_algebras import cubic_hecke_base_ring as chbr + sage: ER = chbr.CubicHeckeExtensionRing('a, b, c') + sage: loads(dumps(ER)) == ER + True + """ + return CubicHeckeExtensionRing, (self._names, self._order, self._ring_of_definition) + + def _element_constructor_(self, x, mon=None): + r""" + Inherited element constructor overloaded to allow construction from + ``GAP3`` ``MVP`` expressions. + + EXAMPLES:: + + sage: CHA3 = algebras.CubicHecke(3) # optional gap3 + sage: GER = CHA3.extension_ring(generic=True) # optional gap3 + sage: sch7 = CHA3.chevie().SchurElements()[7] # optional gap3 + sage: GER(sch7) # optional gap3 + a*b*c^-2 + a^2*b^-1*c^-1 + a^-1*b^2*c^-1 + 2 + + a*b^-2*c + a^-2*b*c + a^-1*b^-1*c^2 + sage: rep4_gap3 = CHA3.chevie().Representations(4) # optional gap3 + sage: matrix(GER, rep4_gap3[1]) # optional gap3 + [ b 0] + [-b c] + """ + from sage.interfaces.gap3 import GAP3Element + if isinstance(x, GAP3Element): + return self._convert_from_gap3_mvp(x) + return super(CubicHeckeExtensionRing, self)._element_constructor_(x, mon=mon) + + def _coerce_map_from_(self, R): + r""" + The rings that canonically coerce to ``self`` ar the ones from + inheritence and the base ring of definition of the cubic Hecke algebra. + + EXAMPLES:: + + sage: from sage.algebras.hecke_algebras import cubic_hecke_base_ring as chbr + sage: BR = chbr.CubicHeckeRingOfDefinition() + sage: ER = BR.extension_ring() + sage: ER(BR.an_element()) + a*b + a*c + b*c + a*b^-1*c^-1 + 2*c^-1 + a^-1*b*c^-1 + 2*b^-1 + + 2*a^-1 + a^-1*b^-1*c + sage: MBR = chbr.CubicHeckeRingOfDefinition(markov_trace_version=True) + sage: MER = MBR.extension_ring() + sage: MER(MBR.an_element()) + a*b*s^-1 + a*c*s^-1 + b*c*s^-1 + a*b^-1*c^-1 + 2*c^-1 + + a^-1*b*c^-1 + 2*b^-1 + 2*a^-1 + a^-1*b^-1*c + """ + if isinstance(R, CubicHeckeRingOfDefinition): + markov = R.markov_trace_version() + a, b, c, *rem = self.gens() + iu = a + b + c + iv = a*b + a*c + b*c + iw = a*b*c + im_gens = [iu, iv, iw] + if markov: + if self.markov_trace_version(): + im_gens += rem + # check of embedding fails in this case as long as the images of + # ``iu`` and ``iv`` need to be invertible (see comment in + # :meth:`__init__`). # :class:`CubicHeckeRingOfDefinition`). + embedding_into_extension_ring = R.hom(im_gens, check=False) + else: + embedding_into_extension_ring = R.hom(im_gens) + return embedding_into_extension_ring + return super(CubicHeckeExtensionRing, self)._coerce_map_from_(R) + + def hom(self, im_gens, codomain=None, check=True, base_map=None): + r""" + Return a homomorphism of ``self``. + + INPUT: + + - ``im_gens`` -- tuple for the image of the generators of ``self`` + - ``codomain`` -- (optional) the codomain of the homomorphism + + EXAMPLES:: + + sage: from sage.algebras.hecke_algebras import cubic_hecke_base_ring as chbr + sage: ER = chbr.CubicHeckeExtensionRing('a, b, c') + sage: UCF = UniversalCyclotomicField() + sage: map = ER.hom((UCF.gen(3),) + (UCF(3),UCF(4),UCF(5))) + sage: ER.an_element() + b^2*c^-1 + e3*a + sage: map(_) + -1/5*E(3) - 16/5*E(3)^2 + """ + gens = self.gens() + num_gens = len(gens) + + if not isinstance(im_gens, (list, tuple)): + im_gens = [im_gens] + + if len(im_gens) == num_gens + 1: + e3, *im_remain = im_gens + hom_cycl_gen = self.base_ring().hom([e3], codomain=e3.parent(), check=check, base_map=base_map) + verbose("hom_cycl_gen %s" % hom_cycl_gen, level=2) + return super(CubicHeckeExtensionRing, self).hom(im_remain, codomain=codomain, check=check, base_map=hom_cycl_gen) + else: + if base_map is None: + raise ValueError('number of images must be four (inculding a ' + 'third root of unity at first position) or a ' + 'base_map (on %s) must be given' % self.base_ring()) + return super(CubicHeckeExtensionRing, self).hom(im_gens, codomain=codomain, check=check, base_map=base_map) + + def _an_element_(self): + r""" + Return an element of ``self``. + + EXAMPLES:: + + sage: from sage.algebras.hecke_algebras import cubic_hecke_base_ring as chbr + sage: ER = chbr.CubicHeckeExtensionRing('x, y, z') + sage: ER.an_element() # indirect doctest + y^2*z^-1 + e3*x + sage: MER = chbr.CubicHeckeExtensionRing('x, y, z, s', markov_trace_version=True) + sage: MER.an_element() # indirect doctest + y^2*z^-1 + e3*x*s^-1 + """ + a, b, c, *rem = self.gens() + e3 = self.cyclotomic_generator() + s = self.one() + if rem: + s = rem[0] + return b**2/c+a*e3/s + + ############################################################################ + # local methods + ############################################################################ + def _is_markov_trace_version(self): + r""" + Return whether ``self`` is the version containing the writhe parameter + ``s`` for the Markov trace. + + EXAMPLES:: + + sage: from sage.algebras.hecke_algebras import cubic_hecke_base_ring as chbr + sage: ER = chbr.CubicHeckeExtensionRing('a, b, c') + sage: ER._is_markov_trace_version() + False + sage: MER = chbr.CubicHeckeExtensionRing('a, b, c, s', markov_trace_version=True) + sage: MER._is_markov_trace_version() + True + """ + return len(self.gens()) == 4 + + # -------------------------------------------------------------------------- + # helper for element construction + # -------------------------------------------------------------------------- + def _convert_from_gap3_mvp(self, mvp_expression): + r""" + Convert a string produced via ``GAP3`` interface and containing Jean + Michel's ``MVP`` (multivariate polynomials) to an element of ``self``. + + INPUT: + + - ``string`` -- string produced via GAP3 interface and containing + Jean Michel's ``MVP`` (multivariate polynomials) + + EXAMPLES:: + + sage: from sage.algebras.hecke_algebras import cubic_hecke_base_ring as chbr + sage: ER = chbr.CubicHeckeExtensionRing('a, b, c') + sage: gap3_string = '2+a^-2bc+a^-1b^-1c^2+a^-1b^2c^-1+ab^-2E3c' + sage: ER._convert_from_gap3_mvp(gap3_string) + a^-1*b^2*c^-1 + 2 + e3*a*b^-2*c + a^-2*b*c + a^-1*b^-1*c^2 + """ + E3 = self.cyclotomic_generator() + a, b, c, *rem = self.gens() + na, nb, nc = self.variable_names() + lc = {na: a, nb: b, nc: c, 'e': E3} + var_names = list(lc.keys()) + + from sage.repl.preparse import implicit_mul + # since implicit_mul does not know about the choice of variable names + # we have to insert * between them separately + string = str(mvp_expression) + string = string.replace('E3', 'e') + for i in var_names: + for j in var_names: + string = string.replace('%s%s' % (i, j), '%s*%s' % (i, j)) + sage_expression = implicit_mul(string) + from sage.misc.sage_eval import sage_eval + return sage_eval(sage_expression, locals=lc) + + ############################################################################ + # global methods + ############################################################################ + def cyclotomic_generator(self): + r""" + Return the third root of unity as generator of the base ring + of ``self``. + + EXAMPLES:: + + sage: from sage.algebras.hecke_algebras import cubic_hecke_base_ring as chbr + sage: ER = chbr.CubicHeckeExtensionRing('a, b, c') + sage: ER.cyclotomic_generator() + e3 + sage: _**3 == 1 + True + """ + return self(self.base_ring().gen()) + + def conjugation(self): + r""" + Return an involution that performs *complex conjugation* with respect + to base ring considered as order in the complex field. + + EXAMPLES:: + + sage: from sage.algebras.hecke_algebras import cubic_hecke_base_ring as chbr + sage: ER = chbr.CubicHeckeExtensionRing('x, y, z') + sage: conj = ER.conjugation() + sage: conj(ER.an_element()) + y^2*z^-1 + (-e3 - 1)*x + sage: MER = chbr.CubicHeckeExtensionRing('x, y, z, s', markov_trace_version=True) + sage: conj = MER.conjugation() + sage: conj(MER.an_element()) + y^2*z^-1 + (-e3 - 1)*x*s^-1 + """ + e3 = self.cyclotomic_generator() + return self.hom(tuple([e3**2] + list(self.gens()))) + + def cubic_equation_galois_group(self): + r""" + Return the Galois group of the cubic equation, which is the permutation + group on the three generators together with its action on ``self``. + + EXAMPLES:: + + sage: from sage.algebras.hecke_algebras import cubic_hecke_base_ring as chbr + sage: ER = chbr.CubicHeckeExtensionRing('a, b, c') + sage: G = ER.cubic_equation_galois_group() + sage: t = ER.an_element() + sage: [(g ,g*t) for g in G] + [((), b^2*c^-1 + e3*a), + ((1,3,2), a^2*b^-1 + e3*c), + ((1,2,3), e3*b + a^-1*c^2), + ((2,3), e3*a + b^-1*c^2), + ((1,3), a^-1*b^2 + e3*c), + ((1,2), a^2*c^-1 + e3*b)] + """ + return self._galois_group + + def mirror_involution(self): + r""" + Return the involution of ``self`` corresponding to the involution of + the cubic Hecke algebra (with the same name). + + This means that it maps the generators of ``self`` to their inverses. + + .. NOTE:: + + The mirror involution of the braid group does not factor through the + cubic Hecke algebra over its base ring, but it does if it is + considered as `\ZZ`-algebra. The base ring elements are transformed + by this automorphism. + + OUTPUT: + + The involution as automorphism of ``self``. + + EXAMPLES:: + + sage: from sage.algebras.hecke_algebras import cubic_hecke_base_ring as chbr + sage: ER = chbr.CubicHeckeExtensionRing('p, q, r') + sage: ER.mirror_involution() + Ring endomorphism of Multivariate Laurent Polynomial Ring in p, q, r + over Splitting Algebra of x^2 + x + 1 + with roots [e3, -e3 - 1] + over Integer Ring + Defn: p |--> p^-1 + q |--> q^-1 + r |--> r^-1 + with map of base ring + sage: _(ER.an_element()) + e3*p^-1 + q^-2*r + + sage: MER = chbr.CubicHeckeExtensionRing('p, q, r, s', markov_trace_version=True) + sage: MER.mirror_involution() + Ring endomorphism of Multivariate Laurent Polynomial Ring in p, q, r, s + over Splitting Algebra of x^2 + x + 1 + with roots [e3, -e3 - 1] over Integer Ring + Defn: p |--> p^-1 + q |--> q^-1 + r |--> r^-1 + s |--> s^-1 + with map of base ring + sage: _(MER.an_element()) + e3*p^-1*s + q^-2*r + """ + if self._mirror is None: + e3 = self.base_ring().gen() + if self._is_markov_trace_version(): + a, b, c, s = self.gens() + self._mirror = self.hom([e3, ~a, ~b, ~c, ~s]) + else: + a, b, c = self.gens() + self._mirror = self.hom([e3, ~a, ~b, ~c]) + + return self._mirror + + def create_specialization(self, im_cubic_equation_roots, im_writhe_parameter=None, var='T', third_unity_root_name='E3'): + r""" + Return an appropriate ring containing the elements from the list + ``im_cubic_equation_roots`` defining a conversion map from self mapping + the cubic equation roots of ``self`` to ``im_cubic_equation_roots``. + + INPUT: + + - ``im_cubic_equation_roots`` -- list or tuple of three ring elements + such that there exists a ring homomorphism from the corresponding + elements of ``self`` to them + + OUTPUT: + + A common parent containing the elements of ``im_cubic_equation_roots`` + together with their inverses. + + EXAMPLES:: + + sage: from sage.algebras.hecke_algebras import cubic_hecke_base_ring as chbr + sage: ER = chbr.CubicHeckeExtensionRing('a, b, c') + sage: t = ER.an_element(); t + b^2*c^-1 + e3*a + sage: Sp1 = ER.create_specialization([E(5), E(7), E(3)]); Sp1 + Universal Cyclotomic Field + sage: Sp1(t) + -E(105)^11 - E(105)^16 - E(105)^26 - E(105)^37 - E(105)^41 + - E(105)^58 - E(105)^71 - E(105)^79 - E(105)^86 - E(105)^101 + sage: MER = chbr.CubicHeckeExtensionRing('a, b, c, s', markov_trace_version=True) + sage: MER.create_specialization([E(5), E(7), E(3)], im_writhe_parameter=E(4)) + Universal Cyclotomic Field + sage: a, b, c, s = MER.gens() + sage: Sp1(MER(t)/s) + E(420) + E(420)^29 + E(420)^89 + E(420)^149 + E(420)^169 + E(420)^209 + + E(420)^253 + E(420)^269 + E(420)^337 + E(420)^389 + + sage: Z3 = CyclotomicField(3); E3=Z3.gen() + sage: Sp2 = ER.create_specialization([E3, E3**2, Z3(1)]) + sage: Sp2(t) + -1 + sage: MER.create_specialization([E3, E3**2, 1], im_writhe_parameter=2) + Cyclotomic Field of order 3 and degree 2 + sage: Sp2(MER(t)*s) + -2 + + sage: Sp3 = ER.create_specialization([5, 7, 11]) + sage: Sp3(t) + 5*E3 + 49/11 + """ + # ---------------------------------------------------------------------- + # interpreting user given cubic equation roots and define the + # corresponding specialized extension ring. + # ---------------------------------------------------------------------- + + if type(im_cubic_equation_roots) == tuple: + im_cubic_equation_roots = list(im_cubic_equation_roots) + + if type(im_cubic_equation_roots) != list: + raise TypeError('cubic_equation_roots must be a list of three elements') + + if len(im_cubic_equation_roots) != 3: + raise ValueError('there must be exactly three cubic_equation_roots') + + gens = self.gens() + num_gens = len(gens) + if im_writhe_parameter: + if num_gens < 4: + raise ValueError('im_writhe_parameter only possible for Markov-trace extension') + im_gens = im_cubic_equation_roots + [im_writhe_parameter] + a, b, c, s = im_gens + else: + if num_gens == 4: + raise ValueError('im_writhe_parameter must be given for Markov-trace extension') + im_gens = im_cubic_equation_roots + a, b, c = im_gens + + image_ring = get_coercion_model().common_parent(*(im_gens)) + + # ---------------------------------------------------------------------- + # make sure that all given cubic equation roots and their inverses + # belong to image_ring + # ---------------------------------------------------------------------- + try: + image_ring = image_ring.localization(tuple(im_gens)) + except ValueError: + pass + + im_gens = [image_ring(root) for root in im_gens] + verbose('common parent of roots and inverses: %s' % (image_ring), level=2) + + image_ring_base = image_ring.base_ring() + image_ring_map = None + + verbose('first choice: image_ring %s, image_ring_base %s' % (image_ring, image_ring_base), level=2) + + # ---------------------------------------------------------------------- + # make sure that a third root of unity belongs to image_ring + # ---------------------------------------------------------------------- + + E3 = None + cp3 = cyclotomic_polynomial(3, var=var).change_ring(image_ring) + cyclotomic_roots = solve_with_extension(cp3, [third_unity_root_name], var=var, flatten=True, warning=False) + + if len(cyclotomic_roots) > 0: + E3 = cyclotomic_roots[0] + verbose('thrird root of unity %s found in %s' % (E3, E3.parent()), level=2) + + if E3 is None: + raise RuntimeError('cannot find a ring containing a third root of unity for the this choice of cubic roots!') + + hom_gens = [E3] + im_gens + verbose('hom_gens %s' % hom_gens, level=2) + + image_ring = get_coercion_model().common_parent(*(hom_gens)) + verbose('common parent of roots and third root: %s' % image_ring, level=2) + + hom_gens = [image_ring(gen) for gen in hom_gens] + + image_ring_base = image_ring.base_ring() + + verbose('second choice: image_ring %s, image_ring_base %s' % (image_ring, image_ring_base), level=2) + + try: + image_ring_map = self.hom(hom_gens, codomain=image_ring) + except (ValueError, NotImplementedError): + image_ring_map = self.hom(hom_gens, codomain=image_ring, check=False) + verbose('check failed for embedding as ring morphism') + + verbose('specializing map defined %s' % image_ring_map, level=2) + + register_ring_hom(image_ring_map) + return image_ring + + def as_splitting_algebra(self): + r""" + Return ``self`` as a :class:`SplittingAlgebra`; that is as an + extension ring of the corresponding cubic Hecke algebra base ring + (``self._ring_of_definition``, as a :class:`CubicHeckeRingOfDefinition`) + splitting its cubic equation into linear factors, such that the roots + are images of the generators of ``self``. + + EXAMPLES:: + + sage: from sage.algebras.hecke_algebras import cubic_hecke_base_ring as chbr + sage: GBR = chbr.CubicHeckeRingOfDefinition() + sage: GER = GBR.extension_ring() + sage: ER = GER.as_splitting_algebra(); ER + Splitting Algebra of T^2 + T + 1 with roots [E3, -E3 - 1] + over Splitting Algebra of h^3 - u*h^2 + v*h - w + with roots [a, b, -b - a + u] + over Multivariate Polynomial Ring in u, v, w + over Integer Ring localized at (w,) + sage: ER(GER.an_element()) + a*E3 + ((u/(-w))*a^2 + ((u^2 - v)/w)*a)*b + a - u + sage: ER(GBR.an_element()) + (u^2 + v*w)/w + + sage: MBR = chbr.CubicHeckeRingOfDefinition(markov_trace_version=True) + sage: MER = MBR.extension_ring() + sage: ES = MER.as_splitting_algebra(); ES + Splitting Algebra of T^2 + T + 1 with roots [E3, -E3 - 1] + over Splitting Algebra of h^3 - u*h^2 + v*h - w + with roots [a, b, -b - a + u] + over Multivariate Polynomial Ring in u, v, w, s + over Integer Ring localized at (s, w, v, u) + sage: ES(MER.an_element()) + (((-1)/(-s))*a)*E3 + ((u/(-w))*a^2 + ((u^2 - v)/w)*a)*b + a - u + sage: ES(MBR.an_element()) + (u^2*s + v*w)/(w*s) + """ + if self._splitting_algebra is not None: + verbose("End (short)", level=2) + return self._splitting_algebra + + if self._ring_of_definition is None: + verbose("constructing generic base ring", level=2) + self._ring_of_definition = CubicHeckeRingOfDefinition() + + markov = self._is_markov_trace_version() + + BR = self._ring_of_definition + root_names = list(self._names) + var_s = None + if markov: + var_s = root_names.pop() # s not needed as root + a, b, c, s = self.gens() + else: + a, b, c = self.gens() + + root_names.pop() # c not needed as root + + FSR = SplittingAlgebra(BR.cubic_equation(), root_names, warning=False) + splitting_roots = FSR.splitting_roots() + verbose('splitting roots %s' % splitting_roots, level=2) + + A, B, C = splitting_roots + e3 = self.cyclotomic_generator() + if var_s: + fsr_s = FSR.scalar_base_ring().gens_dict()[var_s] + S = self.create_specialization([A, B, C], im_writhe_parameter=fsr_s) + # check of embedding fails in this case as long as the images of + # ``iu`` and ``iv`` need to be invertible (see comment in + # :meth:`__init__` of :class:`CubicHeckeRingOfDefinition`). + map_back = S.hom([e3, b, a, a + b + c, a*b+a*c+b*c, a*b*c, s], check=False) + else: + S = self.create_specialization([A, B, C]) + map_back = S.hom([e3, b, a, a + b + c, a*b+a*c+b*c, a*b*c]) + self.register_coercion(map_back) + self._splitting_algebra = S + return self._splitting_algebra + + def field_embedding(self, characteristic=0): + r""" + Return a field embedding of ``self``. + + INPUT: + + - ``characteristic`` -- integer (default: ``0``); the characteristic + of the field + + EXAMPLES:: + + sage: from sage.algebras.hecke_algebras import cubic_hecke_base_ring as chbr + sage: BR = chbr.CubicHeckeRingOfDefinition() + sage: ER = BR.extension_ring() + sage: ER.field_embedding() + Ring morphism: + From: Multivariate Laurent Polynomial Ring in a, b, c + over Splitting Algebra of x^2 + x + 1 + with roots [e3, -e3 - 1] + over Integer Ring + To: Fraction Field of Multivariate Polynomial Ring in a, b, c + over Cyclotomic Field of order 3 and degree 2 + Defn: a |--> a + b |--> b + c |--> c + with map of base ring + + sage: ER.field_embedding(characteristic=5) + Ring morphism: + From: Multivariate Laurent Polynomial Ring in a, b, c + over Splitting Algebra of x^2 + x + 1 + with roots [e3, -e3 - 1] + over Integer Ring + To: Fraction Field of Multivariate Polynomial Ring in a, b, c + over Finite Field in a of size 5^2 + Defn: a |--> a + b |--> b + c |--> c + with map of base ring + + sage: MER = ER.markov_trace_version() + sage: MER.field_embedding() + Ring morphism: + From: Multivariate Laurent Polynomial Ring in a, b, c, s + over Splitting Algebra of x^2 + x + 1 + with roots [e3, -e3 - 1] + over Integer Ring + To: Fraction Field of Multivariate Polynomial Ring in a, b, c, s + over Cyclotomic Field of order 3 and degree 2 + Defn: a |--> a + b |--> b + c |--> c + s |--> s + with map of base ring + """ + if characteristic == 0: + from sage.rings.number_field.number_field import CyclotomicField + C3 = CyclotomicField(3) + E3 = C3.gen() + else: + if not ZZ(characteristic).is_prime(): + raise ValueError('characteristic must be a prime integer') + from sage.rings.finite_rings.finite_field_constructor import GF + from sage.misc.functional import cyclotomic_polynomial + G = GF(characteristic) + c3 = cyclotomic_polynomial(3).change_ring(G) + C3 = c3.splitting_field('a') + E3 = c3.change_ring(C3).roots()[0][0] + + B = self.base_ring() + embBase = B.hom((E3,)) + if not C3.has_coerce_map_from(B): + C3._unset_coercions_used() + C3.register_coercion(embBase) + P = C3[self.variable_names()] + F = P.fraction_field() + emb = self.hom((F(E3),) + F.gens()) + F = emb.codomain() + if not F.has_coerce_map_from(self): + F._unset_coercions_used() + F.register_coercion(emb) + return emb + + def markov_trace_version(self): + r""" + Return the Markov trace version of ``self``. + + EXAMPLES:: + + sage: from sage.algebras.hecke_algebras import cubic_hecke_base_ring as chbr + sage: ER = chbr.CubicHeckeExtensionRing('a, b, c') + sage: ER.markov_trace_version() + Multivariate Laurent Polynomial Ring in a, b, c, s + over Splitting Algebra of x^2 + x + 1 + with roots [e3, -e3 - 1] over Integer Ring + """ + if self._is_markov_trace_version(): + return self + names = self.variable_names() + ('s',) + return self.__class__(names=names, order=self._order, markov_trace_version=True) + + +################################################################################ +# Ring of Definition +# ------------------------------------------------------------------------------ +# Definition of the ring of definition for the cubic Hecke algebra as polynomial +# ring in 2 indeterminates over univariate Laurent polynomial ring over the +# integers. This is the most general ring over which the cubic Hecke algebra may +# be defined. +# ------------------------------------------------------------------------------ +class CubicHeckeRingOfDefinition(Localization): + r""" + The *ring of definition* of the cubic Hecke algebra. + + It contains one invertible indeterminate (representing the product of the + roots of the cubic equation) and two non invertible indeterminates. + + .. NOTE:: + + We follow a suggestion by Ivan Marin in the name *ring of definition*. + We avoid alternative names like *generic* or *universal* base ring + as these have some issues. The first option could be misleading + in view of the term *generic point* used in algebraic geometry, which + would mean the function field in ``u, v, w``, here. + + The second option is problematic since the base ring itself is not a + universal object. Rather, the universal object is the cubic Hecke algebra + considered as a `\ZZ`-algebra including ``u, v, w`` as pairwise commuting + indeterminates. From this point of view the base ring appears to be a + subalgebra of this universal object generated by ``u, v, w``. + + INPUT: + + - ``names`` -- (default: ``'u,v,w'``) string containing the names of the + indeterminates separated by ``,`` or a triple of strings each of which + are the names of one of the three indeterminates + - ``order`` -- string (default: ``'degrevlex'``); the term order; see also + :class:`~sage.rings.polynomial.laurent_polynomial_ring.LaurentPolynomialRing_mpair` + - ``ring_of_definition`` -- (optional) a :class:`CubicHeckeRingOfDefinition` + to specify the generic cubic Hecke base ring over which ``self`` may be + realized as splitting ring via the ``as_splitting_algebra`` method + - ``markov_trace_version`` -- boolean (default: ``False``) if this is + set to ``True`` then ``self`` contains one invertible indeterminate in + addition which is meant to represent the writhe factor of a Markov trace + on the cubic Hecke algebra and which default name is ``s`` + + EXAMPLES:: + + sage: from sage.algebras.hecke_algebras import cubic_hecke_base_ring as chbr + sage: BR = chbr.CubicHeckeRingOfDefinition() + sage: u, v, w = BR.gens() + sage: ele = 3*u*v-5*w**(-2) + sage: ER = BR.extension_ring() + sage: ER(ele) + 3*a^2*b + 3*a*b^2 + 3*a^2*c + 9*a*b*c + 3*b^2*c + + 3*a*c^2 + 3*b*c^2 + (-5)*a^-2*b^-2*c^-2 + sage: phi1 = BR.hom( [4,3,1/1] ) + sage: phi1(ele) + 31 + + sage: LL. = LaurentPolynomialRing(ZZ) + sage: phi2=BR.hom( [LL(4),LL(3),t] ) + sage: phi2(ele) + -5*t^-2 + 36 + + sage: BR.create_specialization( [E(5), E(7), E(3)] ) + Universal Cyclotomic Field + sage: _(ele) + -3*E(105) - 5*E(105)^2 - 5*E(105)^8 - 5*E(105)^11 - 5*E(105)^17 + - 5*E(105)^23 - 5*E(105)^26 - 5*E(105)^29 - 5*E(105)^32 - 5*E(105)^38 + - 5*E(105)^41 - 5*E(105)^44 - 5*E(105)^47 - 5*E(105)^53 - 5*E(105)^59 + - 5*E(105)^62 - 5*E(105)^68 - 8*E(105)^71 - 5*E(105)^74 - 5*E(105)^83 + - 5*E(105)^86 - 5*E(105)^89 - 5*E(105)^92 - 5*E(105)^101 - 5*E(105)^104 + """ + def __init__(self, names=('u', 'v', 'w', 's'), order='degrevlex', markov_trace_version=False): + r""" + Initialize ``self``. + + TESTS:: + + sage: from sage.algebras.hecke_algebras import cubic_hecke_base_ring as chbr + sage: BR = chbr.CubicHeckeRingOfDefinition() + sage: TestSuite(BR).run() + """ + # ---------------------------------------------------------------------- + # ---------------------------------------------------------------------- + # Saving class-globals + # ---------------------------------------------------------------------- + # ---------------------------------------------------------------------- + names = normalize_names_markov(names, markov_trace_version) + if len(names) == 4: + # invertible_names = names[2:4] # s must be invertible, too + # + # because the formal Markov trace of the both basis elements + # ``self.get_order()[568]`` (``KnotInfo.L8a7_1``) and + # ``self.get_order()[596]`` (mirror image of ``KnotInfo.K9_46``) + # have the indeterminate ``v`` in their denominator we need to have + # all indeterminates invertible (``u`` as well for the mirror images) + invertible_names = names + else: + invertible_names = names[2] + + self._order = order + + # ---------------------------------------------------------------------- + # --------------------------------------------------------------------- + # Init of self + # ---------------------------------------------------------------------- + # ---------------------------------------------------------------------- + base_ring = PolynomialRing(ZZ, names, order=order) + Localization.__init__(self, base_ring, invertible_names) + + # ---------------------------------------------------------------------- + # Init of data used on demand + # ---------------------------------------------------------------------- + self._mirror = None + return + + ############################################################################ + # overloaded inherited methods + ############################################################################ + def _defining_names(self): + r""" + Return the generators of ``self`` as the defining names. + + This method is cached in the parent class. + This causes trouble if a second instance of ``self`` is used for another + cubic Hecke algebra in the same session. To avoid this it is overloaded + without ``cached_method`` decorator. + + EXAMPLES:: + + sage: from sage.algebras.hecke_algebras import cubic_hecke_base_ring as chbr + sage: BR = chbr.CubicHeckeRingOfDefinition() + sage: BR._defining_names() + (u, v, w) + """ + return self.gens() + + def _an_element_(self): + r""" + Return an element of ``self``. + + EXAMPLES:: + + sage: from sage.algebras.hecke_algebras import cubic_hecke_base_ring as chbr + sage: BR = chbr.CubicHeckeRingOfDefinition() + sage: BR.an_element() # indirect doctest + (u^2 + v*w)/w + sage: MBR = chbr.CubicHeckeRingOfDefinition(markov_trace_version=True) + sage: MBR.an_element() # indirect doctest + (u^2*s + v*w)/(w*s) + """ + u, v, w, *rem = self.gens() + s = self.one() + if rem: + s = rem[0] + return u**2/w+v/s + + ############################################################################ + # Local Methods + ############################################################################ + def _is_markov_trace_version(self): + r""" + Return whether ``self`` is the version containing the writhe parameter + ``s`` for the Markov trace. + + EXAMPLES:: + + sage: from sage.algebras.hecke_algebras import cubic_hecke_base_ring as chbr + sage: BR = chbr.CubicHeckeRingOfDefinition() + sage: BR._is_markov_trace_version() + False + sage: MBR = chbr.CubicHeckeRingOfDefinition(markov_trace_version=True) + sage: MBR._is_markov_trace_version() + True + """ + return len(self.gens()) == 4 + + ############################################################################ + # Global Methods + ############################################################################ + def cubic_equation(self, var='h', as_coefficients=False): + r""" + Return the cubic equation over which the cubic Hecke algebra is defined. + + EXAMPLES:: + + sage: from sage.algebras.hecke_algebras import cubic_hecke_base_ring as chbr + sage: BR = chbr.CubicHeckeRingOfDefinition() + sage: BR.cubic_equation() + h^3 - u*h^2 + v*h - w + sage: BR.cubic_equation(var='t') + t^3 - u*t^2 + v*t - w + sage: BR.cubic_equation(as_coefficients=True) + [-w, v, -u, 1] + """ + u, v, w, *rem = self.gens() + cf = [-w, v, -u, 1] + if as_coefficients: + return cf + P = PolynomialRing(self, var) + + return P(cf) + + def mirror_involution(self): + r""" + Return the involution of ``self`` corresponding to the involution of the + cubic Hecke algebra (with the same name). + + This means that it maps the last generator of ``self`` to its inverse + and both others to their product with the image of the former. + + From the cubic equation for a braid generator `\beta_i`: + + .. MATH:: + + \beta_i^3 - u \beta_i^2 + v\beta_i -w = 0. + + One deduces the following cubic equation for `\beta_i^{-1}`: + + .. MATH:: + + \beta_i^{-3} -\frac{v}{w} \beta_i^{-2} + \frac{u}{w}\beta_i^{-1} + - \frac{1}{w} = 0. + + .. NOTE:: + + The mirror involution of the braid group does not factor through + the cubic Hecke algebra over its base ring, but it does if it is + considered as `\ZZ`-algebra. The base ring elements are transformed + by this automorphism. + + OUTPUT: + + The involution as automorphism of ``self``. + + EXAMPLES:: + + sage: from sage.algebras.hecke_algebras import cubic_hecke_base_ring as chbr + sage: BR = chbr.CubicHeckeRingOfDefinition() + sage: BR.mirror_involution() + Ring endomorphism of Multivariate Polynomial Ring in u, v, w + over Integer Ring localized at (w,) + Defn: u |--> v/w + v |--> u/w + w |--> 1/w + sage: _(BR.an_element()) + (v^2 + u)/w + + sage: MBR = chbr.CubicHeckeRingOfDefinition(markov_trace_version=True) + sage: MBR.mirror_involution() + Ring endomorphism of Multivariate Polynomial Ring in u, v, w, s + over Integer Ring localized at (s, w, v, u) + Defn: u |--> v/w + v |--> u/w + w |--> 1/w + s |--> 1/s + sage: _(MBR.an_element()) + (v^2 + u*s)/w + """ + if self._mirror is None: + if self._is_markov_trace_version(): + u, v, w, s = self.gens() + self._mirror = self.hom([v/w, u/w, ~w, ~s]) + else: + u, v, w = self.gens() + self._mirror = self.hom([v/w, u/w, ~w]) + return self._mirror + + def create_specialization(self, im_cubic_equation_parameters, im_writhe_parameter=None): + r""" + Return an appropriate Ring containing the elements from the list + ``im_cubic_equation_parameters`` having a conversion map from ``self`` + mapping the cubic equation parameters of ``self`` to + ``im_cubic_equation_parameters``. + + INPUT: + + - ``im_cubic_equation_parameters`` -- list or tuple of three ring + elements such that there exists a ring homomorphism from the + corresponding elements of ``self`` to them + + OUTPUT: + + A common parent containing the elements of ``im_cubic_equation_parameters`` + together with an inverse of the third element. + + EXAMPLES:: + + sage: from sage.algebras.hecke_algebras import cubic_hecke_base_ring as chbr + sage: BR = chbr.CubicHeckeRingOfDefinition() + sage: t = BR.an_element(); t + (u^2 + v*w)/w + sage: Sp1 = BR.create_specialization([E(5), E(7), E(3)]); Sp1 + Universal Cyclotomic Field + sage: Sp1(t) + E(105) + E(105)^8 + E(105)^29 - E(105)^37 + E(105)^43 - E(105)^52 + + E(105)^64 - E(105)^67 + E(105)^71 - E(105)^82 + E(105)^92 + - E(105)^97 + + sage: MBR = chbr.CubicHeckeRingOfDefinition(markov_trace_version=True) + sage: MBR.create_specialization([E(5), E(7), E(3)], im_writhe_parameter=E(4)) + Universal Cyclotomic Field + sage: u, v, w, s = MBR.gens() + sage: Sp1(MBR(t)/s) + E(420)^13 - E(420)^53 + E(420)^73 - E(420)^109 - E(420)^137 + - E(420)^221 + E(420)^253 - E(420)^277 + E(420)^313 - E(420)^361 + + E(420)^373 - E(420)^389 + + sage: Z3 = CyclotomicField(3); E3=Z3.gen() + sage: Sp2 = BR.create_specialization([E3, E3**2, 1]); Sp2 + Cyclotomic Field of order 3 and degree 2 + sage: Sp2(t) + -2*zeta3 - 2 + sage: MBR.create_specialization([E3, E3**2, 1], im_writhe_parameter=2) + Cyclotomic Field of order 3 and degree 2 + sage: Sp2(MBR(t)/s) + -zeta3 - 1 + + sage: Sp3 = BR.create_specialization([5, 7, 11]); Sp3 + Integer Ring localized at (11,) + sage: Sp3(t) + 102/11 + """ + # ---------------------------------------------------------------------- + # setting the base_ring according to the cubic_equation_parameters + # ---------------------------------------------------------------------- + if type(im_cubic_equation_parameters) == tuple: + im_cubic_equation_parameters = list(im_cubic_equation_parameters) + + if type(im_cubic_equation_parameters) != list: + raise TypeError('cubic_equation_parameters must be a list of three elements') + + if len(im_cubic_equation_parameters) != 3: + raise ValueError('there must be exactly three cubic_equation_parameters') + + gens = self.gens() + num_gens = len(gens) + if im_writhe_parameter: + if num_gens < 4: + raise ValueError('im_writhe_parameter only possible for Markov-trace extension') + im_gens = im_cubic_equation_parameters + [im_writhe_parameter] + u, v, w, s = im_gens + else: + if num_gens == 4: + raise ValueError('im_writhe_parameter must be given for Markov-trace extension') + im_gens = im_cubic_equation_parameters + u, v, w = im_gens + + image_ring = None + image_ring_map = None + image_ring_base = w.parent() + + # ---------------------------------------------------------------------- + # short exit on trivial invocation + # ---------------------------------------------------------------------- + if image_ring_base is self and im_gens == gens: + return self + + image_ring = get_coercion_model().common_parent(*(im_gens)) + + # ---------------------------------------------------------------------- + # make sure that the inverse of w belongs to image_ring + # ---------------------------------------------------------------------- + try: + image_ring = image_ring.localization(w) + except ValueError: + pass + + im_gens = [image_ring(para) for para in im_gens] + + verbose('common parent of parameters and inverses: %s' % image_ring, level=2) + + try: + image_ring_map = self.hom(im_gens, codomain=image_ring) + except ValueError: + image_ring_map = self.hom(im_gens, codomain=image_ring, check=False) + verbose('Warning: check failed for embedding as ring morphism') + + register_ring_hom(image_ring_map) + return image_ring + + # -------------------------------------------------------------------------- + # Definition of the generic extension ring for the cubic hecke algebra as + # Laurent polynomial ring in 3 indeterminates over cyclotomic field of order + # 3. The generic extension ring guarantees semisimplicity of the cubic Hecke + # algebra. + # -------------------------------------------------------------------------- + @cached_method + def extension_ring(self, names=('a', 'b', 'c', 's')): + r""" + Return the generic extension ring attached to ``self``. + + EXAMPLES:: + + sage: from sage.algebras.hecke_algebras import cubic_hecke_base_ring as chbr + sage: BR = chbr.CubicHeckeRingOfDefinition() + sage: BR.extension_ring() + Multivariate Laurent Polynomial Ring in a, b, c + over Splitting Algebra of x^2 + x + 1 + with roots [e3, -e3 - 1] + over Integer Ring + """ + markov = self._is_markov_trace_version() + return CubicHeckeExtensionRing(names, ring_of_definition=self, markov_trace_version=markov) + + def markov_trace_version(self): + r""" + Return the extension of the ring of definition needed to treat the + formal Markov traces. + + This appends an additional variable ``s`` to measure the writhe + of knots and makes ``u`` and ``v`` invertible. + + EXAMPLES:: + + sage: from sage.algebras.hecke_algebras import cubic_hecke_base_ring as chbr + sage: GBR = chbr.CubicHeckeRingOfDefinition() + sage: GBR.markov_trace_version() + Multivariate Polynomial Ring in u, v, w, s + over Integer Ring localized at (s, w, v, u) + """ + if self._is_markov_trace_version(): + return self + names = self.base_ring().variable_names() + ('s',) + return self.__class__(names=names, order=self._order, markov_trace_version=True) + + def specialize_homfly(self): + r""" + Return a map to the two variable Laurent polynomial ring that is + the parent of the HOMFLY-PT polynomial. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: CHA2 = algebras.CubicHecke(2) + sage: K5_1 = KnotInfo.K5_1.link() + sage: br = CHA2(K5_1.braid()) + sage: mt = br.formal_markov_trace() + sage: MT = mt.base_ring() + sage: f = MT.specialize_homfly(); f + Composite map: + From: Multivariate Polynomial Ring in u, v, w, s over Integer Ring + localized at (s, w, v, u) + To: Multivariate Laurent Polynomial Ring in L, M over Integer Ring + Defn: Ring morphism: + From: Multivariate Polynomial Ring in u, v, w, s + over Integer Ring localized at (s, w, v, u) + To: Multivariate Polynomial Ring in L, M + over Integer Ring localized at (M, M - 1, L) + Defn: u |--> -M + 1 + v |--> -M + 1 + w |--> 1 + s |--> L + then + Conversion map: + From: Multivariate Polynomial Ring in L, M + over Integer Ring localized at (M, M - 1, L) + To: Multivariate Laurent Polynomial Ring in L, M + over Integer Ring + sage: sup = mt.support() + sage: h1 = sum(f(mt.coefficient(b)) * b.regular_homfly_polynomial() for b in sup) + sage: L, M = f.codomain().gens() + sage: h2 = K5_1.homfly_polynomial() + sage: h1*L**(-5) == h2 # since the writhe of K5_1 is 5 + True + """ + if not self._is_markov_trace_version(): + raise ValueError('Functionality available for Markov trace version, only') + from sage.knots.link import Link + H = Link([]).homfly_polynomial().parent() + L, M = H.gens() + HL = H.localization(1 - M) + u = HL(1 - M) + phi = self.hom((u, u, HL.one(), HL(L))) + inc = H.convert_map_from(HL) + return inc * phi + + def specialize_kauffman(self): + r""" + Return a map to the two variable Laurent polynomial ring that is + the parent of the Kauffman polynomial. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: CHA2 = algebras.CubicHecke(2) + sage: K5_1 = KnotInfo.K5_1.link() + sage: br = CHA2(K5_1.braid()) + sage: mt = br.formal_markov_trace() + sage: MT = mt.base_ring() + sage: f = MT.specialize_kauffman(); f + Composite map: + From: Multivariate Polynomial Ring in u, v, w, s over Integer Ring + localized at (s, w, v, u) + To: Multivariate Laurent Polynomial Ring in a, z over Integer Ring + Defn: Ring morphism: + From: Multivariate Polynomial Ring in u, v, w, s + over Integer Ring localized at (s, w, v, u) + To: Multivariate Polynomial Ring in a, z + over Integer Ring localized at (z, a, a + z, a*z + 1) + Defn: u |--> (a*z + 1)/a + v |--> (a + z)/a + w |--> 1/a + s |--> a + then + Conversion map: + From: Multivariate Polynomial Ring in a, z over Integer Ring + localized at (z, a, a + z, a*z + 1) + To: Multivariate Laurent Polynomial Ring in a, z + over Integer Ring + sage: sup = mt.support() + sage: k1 = sum(f(mt.coefficient(b)) * b.regular_kauffman_polynomial() for b in sup) + sage: a, z = f.codomain().gens() + sage: k2 = KnotInfo.K5_1.kauffman_polynomial() + sage: k1*a**(-5) == k2 # since the writhe of K5_1 is 5 + True + """ + if not self._is_markov_trace_version(): + raise ValueError('Functionality available for Markov trace version, only') + from sage.knots.knotinfo import KnotInfo + K = KnotInfo.L2a1_1.kauffman_polynomial().parent() + a, z = K.gens() + ku = z * a + 1 + kv = z + a + KL = K.localization((ku, kv)) + u = KL(ku / a) + v = KL(kv / a) + phi = self.hom((u, v, KL(~a), KL(a))) + inc = K.convert_map_from(KL) + return inc * phi + + def specialize_links_gould(self): + r""" + Return a map to the two variable Laurent polynomial ring that is + the parent of the Links-Gould polynomial. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: CHA2 = algebras.CubicHecke(2) + sage: K5_1 = KnotInfo.K5_1.link() + sage: br = CHA2(K5_1.braid()) + sage: mt = br.formal_markov_trace() + sage: MT = mt.base_ring() + sage: f = MT.specialize_links_gould(); f + Composite map: + From: Multivariate Polynomial Ring in u, v, w, s over Integer Ring + localized at (s, w, v, u) + To: Multivariate Laurent Polynomial Ring in t0, t1 over Integer Ring + Defn: Ring morphism: + From: Multivariate Polynomial Ring in u, v, w, s + over Integer Ring localized at (s, w, v, u) + To: Multivariate Polynomial Ring in t0, t1 over Integer Ring + localized at (t1, t0, t0 + t1 - 1, t0*t1 - t0 - t1) + Defn: u |--> t0 + t1 - 1 + v |--> t0*t1 - t0 - t1 + w |--> -t0*t1 + s |--> 1 + then + Conversion map: + From: Multivariate Polynomial Ring in t0, t1 over Integer Ring + localized at (t1, t0, t0 + t1 - 1, t0*t1 - t0 - t1) + To: Multivariate Laurent Polynomial Ring in t0, t1 over Integer Ring + sage: sup = mt.support() + sage: sum(f(mt.coefficient(b)) * b.links_gould_polynomial() for b in sup) + -t0^4*t1 - t0^3*t1^2 - t0^2*t1^3 - t0*t1^4 + t0^4 + 2*t0^3*t1 + 2*t0^2*t1^2 + + 2*t0*t1^3 + t1^4 - t0^3 - 2*t0^2*t1 - 2*t0*t1^2 - t1^3 + t0^2 + 2*t0*t1 + + t1^2 - t0 - t1 + 1 + """ + if not self._is_markov_trace_version(): + raise ValueError('Functionality available for Markov trace version, only') + from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing + L = LaurentPolynomialRing(ZZ, 't0, t1') + t0, t1 = L.gens() + lu = t0 + t1 - 1 + lv = t0*t1 - t0 - t1 + lw = -t0 * t1 + LL = L.localization((lu, lv)) + u = LL(lu) + v = LL(lv) + w = LL(lw) + phi = self.hom((u, v, w, LL.one())) + inc = L.convert_map_from(LL) + return inc * phi + diff --git a/src/sage/algebras/hecke_algebras/cubic_hecke_matrix_rep.py b/src/sage/algebras/hecke_algebras/cubic_hecke_matrix_rep.py new file mode 100644 index 00000000000..01c72e26098 --- /dev/null +++ b/src/sage/algebras/hecke_algebras/cubic_hecke_matrix_rep.py @@ -0,0 +1,1082 @@ +# -*- coding: utf-8 -*- +r""" +Cubic Hecke matrix representations + +This module contains the class :class:`CubicHeckeMatrixRep` which is used to +treat the matrix representations of the elements of the cubic Hecke algebra +(:class:`~sage.algebras.hecke_algebras.cubic_hecke_algebra.CubicHeckeAlgebra`) +together with its parent class :class:`CubicHeckeMatrixSpace`. Furthermore, +it contains enums for their types (:class:`RepresentationType`) and names +(:class:`AbsIrreducibeRep`). + + +AUTHORS: + +- Sebastian Oehms May 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/ +############################################################################## + +from enum import Enum +from sage.misc.cachefunc import cached_method +from sage.misc.verbose import verbose +from sage.rings.integer import Integer +from sage.matrix.matrix_generic_dense import Matrix_generic_dense +from sage.matrix.matrix_space import MatrixSpace +from sage.matrix.constructor import matrix +from sage.matrix.special import block_diagonal_matrix +from sage.databases.cubic_hecke_db import CubicHeckeDataSection as sc + + +# ------------------------------------------- +# Enum for the generators sign (of exponent) +# ------------------------------------------- +class GenSign(Enum): + r""" + Enum class to select the braid generators sign. + + EXAMPLES:: + + sage: import sage.algebras.hecke_algebras.cubic_hecke_matrix_rep as chmr + sage: chmr.GenSign.pos + + sage: chmr.GenSign.neg + + """ + pos = 1 + neg = -1 + + +# ------------------------------------------- +# Enum for typ of matrix representation +# ------------------------------------------- +class RepresentationType(Enum): + r""" + Enum class to select a representation type for the cubic Hecke algebra. + + - ``RegularLeft`` -- left regular representations + - ``RegularRight`` -- right regular representations + - ``SplitIrredMarin`` -- split irreducible representations obtained from + Ivan Marin's data + - ``SplitIrredChevie`` -- the split irreducible representations obtained + from CHEVIE via the ``GAP3`` interface + + EXAMPLES:: + + sage: import sage.algebras.hecke_algebras.cubic_hecke_matrix_rep as chmr + sage: chmr.RepresentationType.RegularLeft.is_regular() + True + """ + def is_split(self): + r""" + Return ``True`` if this representation type is absolutely split, + ``False`` else-wise. + + EXAMPLES:: + + sage: import sage.algebras.hecke_algebras.cubic_hecke_matrix_rep as chmr + sage: chevie = chmr.RepresentationType.SplitIrredChevie + sage: chevie.is_split() + True + """ + return self.value['split'] + + def is_regular(self): + r""" + Return ``True`` if this representation type is regular, ``False`` + else-wise. + + EXAMPLES:: + + sage: import sage.algebras.hecke_algebras.cubic_hecke_matrix_rep as chmr + sage: reg_left = chmr.RepresentationType.RegularLeft + sage: reg_left.is_regular() + True + """ + return self.value['regular'] + + def data_section(self): + r""" + Return the name of the data file. For more information see + :class:`~sage.databases.cubic_hecke_db.CubicHeckeDataBase`. + + EXAMPLES:: + + sage: import sage.algebras.hecke_algebras.cubic_hecke_matrix_rep as chmr + sage: reg_left = chmr.RepresentationType.RegularLeft + sage: reg_left.data_section() + + """ + return self.value['data'] + + def number_of_representations(self, nstrands): + r""" + Return the number of representations existing to that type. + + EXAMPLES:: + + sage: import sage.algebras.hecke_algebras.cubic_hecke_matrix_rep as chmr + sage: chmr.RepresentationType.SplitIrredChevie.number_of_representations(4) + 24 + sage: chmr.RepresentationType.SplitIrredMarin.number_of_representations(4) + 24 + """ + if self.value['data'] is None: + if nstrands < 1 or nstrands > 5: + raise ValueError("nstrands must be between 1 and 5") + elif nstrands < 1 or nstrands > 4: + raise ValueError("nstrands must be between 1 and 4") + return self.value['num_rep'][nstrands - 1] + + RegularLeft = {'split': False, 'regular': True, 'data': sc.regular_left, 'num_rep': [1, 1, 1, 1]} + RegularRight = {'split': False, 'regular': True, 'data': sc.regular_right, 'num_rep': [1, 1, 1, 1]} + SplitIrredMarin = {'split': True, 'regular': False, 'data': sc.split_irred, 'num_rep': [1, 3, 7, 24]} + SplitIrredChevie = {'split': True, 'regular': False, 'data': None, 'num_rep': [1, 3, 7, 24, 30]} + + +# --------------------------------------------- +# Enum for absolute irreducible representations +# --------------------------------------------- +class AbsIrreducibeRep(Enum): + r""" + Enum class to select an absolutely irreducible representation for the cubic + Hecke algebra (``CHAn``) on `n`-strands. + + The names are build as follows: Take the determinant of one of the + generators of the ``CHAn``. This is a monomial in the generic extension + ring (``GER``) of ``CHA``, say ``a^ib^jc^k`` where ``a, b`` and ``c`` are + the generators of ``GER``. This does not depend on the choice of the + generator of ``CHA``, since these are conjugated to each other. This + monomial might be looked as the weight of the representation. Therefore we + use it as a name: + + ``Wn_ijk`` + + The only ambiguity among the available irreducible representations occurs for the two nine-dimensional modules, which + are conjugated to each other and distinguished by these names: + + ``W4_333`` and ``W4_333bar`` + + Examples of names: + + - ``W2_100`` -- one dimensional representation of the cubic Hecke algebra on 2 strands corresponding to the first root + of the cubic equation + - ``W3_111`` -- three dimensional irreducible representation of the cubic Hecke algebra on 3 strands + - ``W4_242`` -- eight dimensional irreducible representation of the cubic Hecke algebra on 4 strands having the second + root of the cubic equation as weight of dimension 4 + + Alternative names are taken from [MW2012]_ and can be shown by + :meth:`alternative_name`. + + EXAMPLES:: + + sage: import sage.algebras.hecke_algebras.cubic_hecke_matrix_rep as chmr + sage: [irr.name for irr in chmr.AbsIrreducibeRep] + ['W2_100', 'W2_001', 'W2_010', 'W3_100', 'W3_001', 'W3_010', 'W3_011', 'W3_110', + 'W3_101', 'W3_111', 'W4_100', 'W4_001', 'W4_010', 'W4_011', 'W4_110', 'W4_101', + 'W4_111', 'W4_120', 'W4_201', 'W4_012', 'W4_102', 'W4_210', 'W4_021', 'W4_213', + 'W4_132', 'W4_321', 'W4_231', 'W4_123', 'W4_312', 'W4_422', 'W4_224', 'W4_242', + 'W4_333', 'W4_333bar', 'W5_100', 'W5_001', 'W5_010', 'W5_013', 'W5_130', 'W5_301', + 'W5_031', 'W5_103', 'W5_310', 'W5_203', 'W5_032', 'W5_320', 'W5_230', 'W5_023', + 'W5_302', 'W5_033', 'W5_330', 'W5_303', 'W5_163', 'W5_631', 'W5_316', 'W5_136', + 'W5_613', 'W5_361', 'W5_366', 'W5_663', 'W5_636', 'W5_933', 'W5_339', 'W5_393'] + + REFERENCES: + + - [MW2012]_ + """ + def alternative_name(self): + r""" + Return the name of the split irreducible representation for cubic Hecke + algebras for up to four strands as given in [MW2012]_. + + EXAMPLES:: + + sage: import sage.algebras.hecke_algebras.cubic_hecke_matrix_rep as chmr + sage: chmr.AbsIrreducibeRep.W3_011.alternative_name() + 'Tbc' + """ + return self.value['alt_name'] + + def dimension(self): + r""" + Return the dimension of the representation. + + EXAMPLES:: + + sage: import sage.algebras.hecke_algebras.cubic_hecke_matrix_rep as chmr + sage: chmr.AbsIrreducibeRep.W3_111.dimension() + 3 + """ + return self.value['dim'] + + def number_gens(self): + r""" + Return the number of generators of the underlying cubic Hecke algebra. + + EXAMPLES:: + + sage: import sage.algebras.hecke_algebras.cubic_hecke_matrix_rep as chmr + sage: chmr.AbsIrreducibeRep.W3_001.number_gens() + 2 + sage: chmr.AbsIrreducibeRep.W4_001.number_gens() + 3 + """ + return self.value['ngens'] + + def length_orbit(self): + r""" + Return the length of the orbit of this representation under the action + of the Galois group of the cubic equation. + + EXAMPLES:: + + sage: import sage.algebras.hecke_algebras.cubic_hecke_matrix_rep as chmr + sage: chmr.AbsIrreducibeRep.W3_001.length_orbit() + 3 + sage: chmr.AbsIrreducibeRep.W3_111.length_orbit() + 1 + """ + return self.value['len_orbit'] + + def gap_index(self): + r""" + Return the array index of this representation for the access + to the ``GAP3`` package ``CHEVIE``. + + EXAMPLES:: + + sage: import sage.algebras.hecke_algebras.cubic_hecke_matrix_rep as chmr + sage: chmr.AbsIrreducibeRep.W3_111.gap_index() + 6 + """ + return self.value['gap_ind'] + + def internal_index(self): + r""" + Return the array index of this representation for the internal access. + + EXAMPLES:: + + sage: import sage.algebras.hecke_algebras.cubic_hecke_matrix_rep as chmr + sage: chmr.AbsIrreducibeRep.W3_111.internal_index() + 6 + """ + return self.value['intern_ind'] + + # ------------------------------------------------------------------------------------------------- + # absolutely irreducible representations corresponding to braids on 2 strands + # ------------------------------------------------------------------------------------------------- + W2_100 = {'alt_name': 'Sa', 'dim': 1, 'ngens': 1, 'len_orbit': 3, 'gap_ind': 0, 'intern_ind': 0} + W2_001 = {'alt_name': 'Sc', 'dim': 1, 'ngens': 1, 'len_orbit': 3, 'gap_ind': 1, 'intern_ind': 1} + W2_010 = {'alt_name': 'Sb', 'dim': 1, 'ngens': 1, 'len_orbit': 3, 'gap_ind': 2, 'intern_ind': 2} + + # ------------------------------------------------------------------------------------------------- + # absolutely irreducible representations corresponding to braids on 3 strands + # ------------------------------------------------------------------------------------------------- + W3_100 = {'alt_name': 'Sa', 'dim': 1, 'ngens': 2, 'len_orbit': 3, 'gap_ind': 0, 'intern_ind': 0} + W3_001 = {'alt_name': 'Sc', 'dim': 1, 'ngens': 2, 'len_orbit': 3, 'gap_ind': 1, 'intern_ind': 1} + W3_010 = {'alt_name': 'Sb', 'dim': 1, 'ngens': 2, 'len_orbit': 3, 'gap_ind': 2, 'intern_ind': 2} + + W3_011 = {'alt_name': 'Tbc', 'dim': 2, 'ngens': 2, 'len_orbit': 3, 'gap_ind': 3, 'intern_ind': 3} + W3_110 = {'alt_name': 'Tab', 'dim': 2, 'ngens': 2, 'len_orbit': 3, 'gap_ind': 4, 'intern_ind': 4} + W3_101 = {'alt_name': 'Tac', 'dim': 2, 'ngens': 2, 'len_orbit': 3, 'gap_ind': 5, 'intern_ind': 5} + + W3_111 = {'alt_name': 'V', 'dim': 3, 'ngens': 2, 'len_orbit': 1, 'gap_ind': 6, 'intern_ind': 6} + + # ------------------------------------------------------------------------------------------------- + # absolutely irreducible representations corresponding to braids on 4 strands + # ------------------------------------------------------------------------------------------------- + W4_100 = {'alt_name': 'Sa', 'dim': 1, 'ngens': 3, 'len_orbit': 3, 'gap_ind': 0, 'intern_ind': 0} + W4_001 = {'alt_name': 'Sc', 'dim': 1, 'ngens': 3, 'len_orbit': 3, 'gap_ind': 1, 'intern_ind': 1} + W4_010 = {'alt_name': 'Sb', 'dim': 1, 'ngens': 3, 'len_orbit': 3, 'gap_ind': 2, 'intern_ind': 2} + + W4_011 = {'alt_name': 'Tbc', 'dim': 2, 'ngens': 3, 'len_orbit': 3, 'gap_ind': 3, 'intern_ind': 3} + W4_110 = {'alt_name': 'Tab', 'dim': 2, 'ngens': 3, 'len_orbit': 3, 'gap_ind': 4, 'intern_ind': 4} + W4_101 = {'alt_name': 'Tac', 'dim': 2, 'ngens': 3, 'len_orbit': 3, 'gap_ind': 5, 'intern_ind': 5} + + W4_111 = {'alt_name': 'V', 'dim': 3, 'ngens': 3, 'len_orbit': 1, 'gap_ind': 6, 'intern_ind': 6} + + W4_120 = {'alt_name': 'Uba', 'dim': 3, 'ngens': 3, 'len_orbit': 6, 'gap_ind': 7, 'intern_ind': 7} + W4_201 = {'alt_name': 'Uac', 'dim': 3, 'ngens': 3, 'len_orbit': 6, 'gap_ind': 8, 'intern_ind': 8} + W4_012 = {'alt_name': 'Ucb', 'dim': 3, 'ngens': 3, 'len_orbit': 6, 'gap_ind': 9, 'intern_ind': 9} + W4_102 = {'alt_name': 'Uca', 'dim': 3, 'ngens': 3, 'len_orbit': 6, 'gap_ind': 10, 'intern_ind': 10} + W4_210 = {'alt_name': 'Uab', 'dim': 3, 'ngens': 3, 'len_orbit': 6, 'gap_ind': 11, 'intern_ind': 11} + W4_021 = {'alt_name': 'Ubc', 'dim': 3, 'ngens': 3, 'len_orbit': 6, 'gap_ind': 12, 'intern_ind': 12} + + W4_213 = {'alt_name': 'Vcab', 'dim': 6, 'ngens': 3, 'len_orbit': 6, 'gap_ind': 13, 'intern_ind': 13} + W4_132 = {'alt_name': 'Vbca', 'dim': 6, 'ngens': 3, 'len_orbit': 6, 'gap_ind': 14, 'intern_ind': 14} + W4_321 = {'alt_name': 'Vabc', 'dim': 6, 'ngens': 3, 'len_orbit': 6, 'gap_ind': 15, 'intern_ind': 15} + W4_231 = {'alt_name': 'Vbac', 'dim': 6, 'ngens': 3, 'len_orbit': 6, 'gap_ind': 16, 'intern_ind': 16} + W4_123 = {'alt_name': 'Vcba', 'dim': 6, 'ngens': 3, 'len_orbit': 6, 'gap_ind': 17, 'intern_ind': 17} + W4_312 = {'alt_name': 'Vacb', 'dim': 6, 'ngens': 3, 'len_orbit': 6, 'gap_ind': 18, 'intern_ind': 18} + + W4_422 = {'alt_name': 'Wa', 'dim': 8, 'ngens': 3, 'len_orbit': 3, 'gap_ind': 19, 'intern_ind': 19} + W4_224 = {'alt_name': 'Wc', 'dim': 8, 'ngens': 3, 'len_orbit': 3, 'gap_ind': 20, 'intern_ind': 20} + W4_242 = {'alt_name': 'Wb', 'dim': 8, 'ngens': 3, 'len_orbit': 3, 'gap_ind': 21, 'intern_ind': 21} + + W4_333 = {'alt_name': 'X', 'dim': 9, 'ngens': 3, 'len_orbit': 2, 'gap_ind': 22, 'intern_ind': 22} + W4_333bar = {'alt_name': 'Xbar', 'dim': 9, 'ngens': 3, 'len_orbit': 2, 'gap_ind': 23, 'intern_ind': 23} + + # ------------------------------------------------------------------------------------------------- + # absolutely irreducible representations corresponding to braids on 5 strands + # ------------------------------------------------------------------------------------------------- + W5_100 = {'alt_name': None, 'dim': 1, 'ngens': 4, 'len_orbit': 3, 'gap_ind': 0, 'intern_ind': 0} + W5_001 = {'alt_name': None, 'dim': 1, 'ngens': 4, 'len_orbit': 3, 'gap_ind': 1, 'intern_ind': 1} + W5_010 = {'alt_name': None, 'dim': 1, 'ngens': 4, 'len_orbit': 3, 'gap_ind': 2, 'intern_ind': 2} + + W5_013 = {'alt_name': None, 'dim': 4, 'ngens': 4, 'len_orbit': 6, 'gap_ind': 3, 'intern_ind': 3} + W5_130 = {'alt_name': None, 'dim': 4, 'ngens': 4, 'len_orbit': 6, 'gap_ind': 4, 'intern_ind': 4} + W5_301 = {'alt_name': None, 'dim': 4, 'ngens': 4, 'len_orbit': 6, 'gap_ind': 5, 'intern_ind': 5} + W5_031 = {'alt_name': None, 'dim': 4, 'ngens': 4, 'len_orbit': 6, 'gap_ind': 6, 'intern_ind': 6} + W5_103 = {'alt_name': None, 'dim': 4, 'ngens': 4, 'len_orbit': 6, 'gap_ind': 7, 'intern_ind': 7} + W5_310 = {'alt_name': None, 'dim': 4, 'ngens': 4, 'len_orbit': 6, 'gap_ind': 8, 'intern_ind': 8} + + W5_203 = {'alt_name': None, 'dim': 5, 'ngens': 4, 'len_orbit': 6, 'gap_ind': 9, 'intern_ind': 9} + W5_032 = {'alt_name': None, 'dim': 5, 'ngens': 4, 'len_orbit': 6, 'gap_ind': 10, 'intern_ind': 10} + W5_320 = {'alt_name': None, 'dim': 5, 'ngens': 4, 'len_orbit': 6, 'gap_ind': 11, 'intern_ind': 11} + W5_230 = {'alt_name': None, 'dim': 5, 'ngens': 4, 'len_orbit': 6, 'gap_ind': 12, 'intern_ind': 12} + W5_023 = {'alt_name': None, 'dim': 5, 'ngens': 4, 'len_orbit': 6, 'gap_ind': 13, 'intern_ind': 13} + W5_302 = {'alt_name': None, 'dim': 5, 'ngens': 4, 'len_orbit': 6, 'gap_ind': 14, 'intern_ind': 14} + + W5_033 = {'alt_name': None, 'dim': 6, 'ngens': 4, 'len_orbit': 3, 'gap_ind': 15, 'intern_ind': 15} + W5_330 = {'alt_name': None, 'dim': 6, 'ngens': 4, 'len_orbit': 3, 'gap_ind': 16, 'intern_ind': 16} + W5_303 = {'alt_name': None, 'dim': 6, 'ngens': 4, 'len_orbit': 3, 'gap_ind': 17, 'intern_ind': 17} + + W5_163 = {'alt_name': None, 'dim': 10, 'ngens': 4, 'len_orbit': 6, 'gap_ind': 18, 'intern_ind': 18} + W5_631 = {'alt_name': None, 'dim': 10, 'ngens': 4, 'len_orbit': 6, 'gap_ind': 19, 'intern_ind': 19} + W5_316 = {'alt_name': None, 'dim': 10, 'ngens': 4, 'len_orbit': 6, 'gap_ind': 20, 'intern_ind': 20} + W5_136 = {'alt_name': None, 'dim': 10, 'ngens': 4, 'len_orbit': 6, 'gap_ind': 21, 'intern_ind': 21} + W5_613 = {'alt_name': None, 'dim': 10, 'ngens': 4, 'len_orbit': 6, 'gap_ind': 22, 'intern_ind': 22} + W5_361 = {'alt_name': None, 'dim': 10, 'ngens': 4, 'len_orbit': 6, 'gap_ind': 23, 'intern_ind': 23} + + W5_366 = {'alt_name': None, 'dim': 15, 'ngens': 4, 'len_orbit': 3, 'gap_ind': 24, 'intern_ind': 24} + W5_663 = {'alt_name': None, 'dim': 15, 'ngens': 4, 'len_orbit': 3, 'gap_ind': 26, 'intern_ind': 25} + W5_636 = {'alt_name': None, 'dim': 15, 'ngens': 4, 'len_orbit': 3, 'gap_ind': 27, 'intern_ind': 26} + + W5_933 = {'alt_name': None, 'dim': 15, 'ngens': 4, 'len_orbit': 3, 'gap_ind': 25, 'intern_ind': 27} + W5_339 = {'alt_name': None, 'dim': 15, 'ngens': 4, 'len_orbit': 3, 'gap_ind': 28, 'intern_ind': 28} + W5_393 = {'alt_name': None, 'dim': 15, 'ngens': 4, 'len_orbit': 3, 'gap_ind': 29, 'intern_ind': 29} + + +# ------------------------------------------------------------------------------------------------------------------ +# Definition of CubicHeckeMatrixRep +# -------------------------------------------------------------------------------------------------------- +class CubicHeckeMatrixRep(Matrix_generic_dense): + r""" + Class to supervise the diagonal block matrix structure arising from + cubic Hecke algebra-representations. + + EXAMPLES:: + + sage: import sage.algebras.hecke_algebras.cubic_hecke_matrix_rep as chmr + sage: CHA2. = algebras.CubicHecke(2) + sage: MS = chmr.CubicHeckeMatrixSpace(CHA2) + sage: m1 = MS(c1); m1 + [ a 0 0] + [ 0 b 0] + [ 0 0 -b - a + u] + sage: type(m1) + + sage: m1.block_diagonal_list() + [[a], [b], [-b - a + u]] + + sage: MSo = chmr.CubicHeckeMatrixSpace(CHA2, original=True) + sage: MSo(c1) + [a 0 0] + [0 b 0] + [0 0 c] + + sage: reg_left = chmr.RepresentationType.RegularLeft + sage: MSreg = chmr.CubicHeckeMatrixSpace(CHA2, representation_type=reg_left) + sage: MSreg(c1) + [ 0 -v 1] + [ 1 u 0] + [ 0 w 0] + sage: len(_.block_diagonal_list()) + 1 + + TESTS: + + The minpoly does not work over more generic rings:: + + sage: TestSuite(m1).run(skip='_test_minpoly') + """ + + @cached_method + def _get_block(self, ind): + r""" + Return the ``ind``-th sub-matrix block of ``self`` considered + as block diagonal matrix. + + INPUT: + + - ``ind`` -- integer specifying the list index according to + :meth:`internal_index` respectively :meth:`gap_index` + + OUTPUT: + + An instance of :class:`Matrix_generic_dense` representing + the specified block of ``self``. + + EXAMPLES:: + + sage: CHA2. = algebras.CubicHecke(2) + sage: c1.matrix()._get_block(0) # indirect doctest + [a] + """ + representation_type = self.parent()._representation_type + if not representation_type.is_split(): + return matrix(self) + n = self.parent()._cubic_hecke_algebra.ngens() + s = sum(irr_rep.dimension() for irr_rep in AbsIrreducibeRep if irr_rep.number_gens() == n and irr_rep.internal_index() < ind) + for irr_rep in AbsIrreducibeRep: + if irr_rep.number_gens() == n and irr_rep.internal_index() == ind: + d = irr_rep.dimension() + return matrix(self.submatrix(s, s, d, d)) + raise ValueError('no irreducible representation for this index') + + @cached_method + def _irr_to_ind(self, irr): + r""" + Return the index if the given split irreducible representation + of ``self``. + + INPUT: + + - ``irr`` -- an instance of :class:`AbsIrreducibeRep` specifying an + absolute irreducible representation of the cubic Hecke algebra + + EXAMPLES:: + + sage: CHA2. = algebras.CubicHecke(2) + sage: m1 = c1.matrix() + sage: m1._irr_to_ind(CHA2.irred_repr.W2_001) + 1 + sage: m1._irr_to_ind(CHA2.irred_repr.W3_001) + Traceback (most recent call last): + ... + TypeError: representation must have 1 generators + """ + representation_type = self.parent()._representation_type + if not representation_type.is_split(): + raise TypeError('representation type is non split') + + ch_algebra = self.parent()._cubic_hecke_algebra + if ch_algebra.strands() != irr.number_gens() + 1: + raise TypeError('representation must have %s generators' % (ch_algebra.strands() - 1)) + + ind = irr.gap_index() + if representation_type == RepresentationType.SplitIrredMarin: + ind = irr.internal_index() + return ind + + @cached_method + def __getitem__(self, item): + r""" + Return the sub-matrix block of ``self`` considered as block diagonal + matrix specified by `item`. + + Overloading builtin-method to select a list-item. + + INPUT: + + - ``item`` -- an :class:`AbsIrreducibeRep` specifying an + absolute irreducible representation of the cubic Hecke algebra; + alternatively, it can be specified by list index + (see :meth:`internal_index` repectively :meth:`gap_index`) + + OUTPUT: + + An instance of :class:`Matrix_generic_dense` representing + the specified block of ``self``. + + EXAMPLES:: + + sage: CHA2. = algebras.CubicHecke(2) + sage: m1 = c1.matrix() + sage: m1[0] # indirect doctest + [a] + sage: m1[CHA2.irred_repr.W2_001] # indirect doctest + [b] + """ + if isinstance(item, AbsIrreducibeRep): + return self._get_block(self._irr_to_ind(item)) + elif isinstance(item, (Integer, int)): + return self._get_block(item) + + return super(CubicHeckeMatrixRep, self).__getitem__(item) + + @cached_method + def block_diagonal_list(self): + r""" + Return the list of sub-matrix blocks of ``self`` considered + as block diagonal matrix. + + OUTPUT: + + A list of instances of :class:`Matrix_generic_dense` each of + which represents a diagonal block of ``self``. + + EXAMPLES:: + + sage: CHA2. = algebras.CubicHecke(2) + sage: c1.matrix().block_diagonal_list() + [[a], [b], [-b - a + u]] + """ + representation_type = self.parent()._representation_type + n = self.parent()._cubic_hecke_algebra.strands() + m = representation_type.number_of_representations(n) + return [self._get_block(i) for i in range(m)] + + @cached_method + def reduce_to_irr_block(self, irr): + r""" + Return a copy of ``self`` with zeroes outside the block corresponding to + ``irr`` but the block according to the input identical to that of ``self``. + + INPUT: + + - ``irr`` -- an :class:`AbsIrreducibeRep` specifying an + absolute irreducible representation of the cubic Hecke algebra; + alternatively, it can be specified by list index (see + :meth:`internal_index` respectively :meth:`gap_index`) + + OUTPUT: + + An instance of :class:`Matrix_generic_dense` with exactly one non zero block + according to ``irr``. + + EXAMPLES:: + + sage: CHA2. = algebras.CubicHecke(2) + sage: m1 = c1.matrix() + sage: m1.reduce_to_irr_block(0) + [a 0 0] + [0 0 0] + [0 0 0] + sage: m1.reduce_to_irr_block(CHA2.irred_repr.W2_001) + [0 0 0] + [0 b 0] + [0 0 0] + """ + if isinstance(irr, AbsIrreducibeRep): + ind = self._irr_to_ind(irr) + else: + ind = Integer(irr) + from copy import copy + mat_list = copy(self.parent().zero().block_diagonal_list()) + mat_list[ind] = self[ind] + return block_diagonal_matrix(mat_list, subdivide=self.parent()._subdivide, sparse=True) + + +# ------------------------------------------------------------------------------------------------------------------ +# Definition of CubicHeckeMatrixSpace +# -------------------------------------------------------------------------------------------------------- +class CubicHeckeMatrixSpace(MatrixSpace): + r""" + The matrix space of cubic Hecke algebra representations. + + INPUT: + + - ``cubic_hecke_algebra`` -- (optional) + :class:`~sage.algebras.hecke_algebras.cubic_hecke_algebra.CubicHeckeAlgebra` + must be given if ``element`` fails to be an instance of its element class + - ``representation_type`` -- (default: ``RepresentationType.SplitIrredChevie``) + :class:`RepresentationType` specifying the type of the representation + - ``subdivide`` -- boolean (default: ``False``); whether or not to subdivide + the resulting matrices + + - ``original`` -- boolean (default: ``False``) if ``True``, the matrix + will have coefficients in the generic base / extension ring + + EXAMPLES:: + + sage: CHA2. = algebras.CubicHecke(2) + sage: c1.matrix() # indirect doctest + [ a 0 0] + [ 0 b 0] + [ 0 0 -b - a + u] + sage: c1.matrix(original=True) + [a 0 0] + [0 b 0] + [0 0 c] + sage: c1.matrix(representation_type = CHA2.repr_type.RegularLeft) # indirect doctest + [ 0 -v 1] + [ 1 u 0] + [ 0 w 0] + """ + @staticmethod + def __classcall_private__(cls, cubic_hecke_algebra, representation_type=None, subdivide=False, original=False): + r""" + Normalize the arguments to call the ``__init__`` constructor. + + See the documentation in ``__init__``. + + TESTS:: + + sage: import sage.algebras.hecke_algebras.cubic_hecke_matrix_rep as chmr + sage: CHA2. = algebras.CubicHecke(2) + sage: MS = chmr.CubicHeckeMatrixSpace(CHA2) + sage: MS2 = chmr.CubicHeckeMatrixSpace(CHA2, representation_type=CHA2.repr_type.SplitIrredMarin, subdivide=False) + sage: MS is MS2 + True + """ + from sage.algebras.hecke_algebras.cubic_hecke_algebra import CubicHeckeAlgebra + + if not isinstance(cubic_hecke_algebra, CubicHeckeAlgebra): + raise TypeError('cubic_hecke_algebra must be an instance of CubicHeckeAlgebra') + + if representation_type is None: + representation_type = RepresentationType.SplitIrredMarin + + if representation_type == RepresentationType.SplitIrredChevie: + from sage.combinat.root_system.reflection_group_real import is_chevie_available + if not is_chevie_available(): + raise ValueError('CHEVIE is not available') + + base_ring = cubic_hecke_algebra.base_ring(generic=original) + dimension = cubic_hecke_algebra.dimension() + if representation_type.is_split(): + dimension = cubic_hecke_algebra._dim_irr_rep + base_ring = cubic_hecke_algebra.extension_ring(generic=original) + # Bypass the MatrixSpace.__classcall__ + return super(MatrixSpace, cls).__classcall__(cls, base_ring, int(dimension), + cubic_hecke_algebra=cubic_hecke_algebra, + representation_type=representation_type, + subdivide=subdivide) + + def __init__(self, base_ring, + dimension, + cubic_hecke_algebra, + representation_type, + subdivide): + r""" + Initialize ``self``. + + TESTS:: + + sage: import sage.algebras.hecke_algebras.cubic_hecke_matrix_rep as chmr + sage: CHA3. = algebras.CubicHecke(3) + sage: MS = chmr.CubicHeckeMatrixSpace(CHA3, original=True) + + The minpoly does not work over more generic rings:: + + sage: TestSuite(MS).run(skip='_test_elements') # long time + """ + from sage.algebras.hecke_algebras.cubic_hecke_algebra import CubicHeckeAlgebra + + if not isinstance(cubic_hecke_algebra, CubicHeckeAlgebra): + raise TypeError('cubic_hecke_algebra must be an instance of CubicHeckeAlgebra') + + # ------------------------------------------------------------------------------------------------- + # saving input parameters + # ------------------------------------------------------------------------------------------------- + self._cubic_hecke_algebra = cubic_hecke_algebra + self._representation_type = representation_type + self._subdivide = subdivide + + original_base_ring = cubic_hecke_algebra.base_ring(generic=True) + + if representation_type.is_split(): + original_base_ring = cubic_hecke_algebra.extension_ring(generic=True) + specialize = cubic_hecke_algebra._generic_extension_ring_map + else: + specialize = cubic_hecke_algebra._ring_of_definition_map + + verbose("original_base_ring %s base_ring %s" % (original_base_ring, base_ring), level=2) + + self._original_base_ring = original_base_ring + self._specialize = specialize + + super().__init__(base_ring, dimension, dimension, sparse=True, implementation=CubicHeckeMatrixRep) + + def construction(self): + r""" + Return ``None`` since this construction is not functorial. + + EXAMPLES:: + + sage: CHA2. = algebras.CubicHecke(2) + sage: MS = c1.matrix().parent() + sage: MS._test_category() # indirect doctest + """ + return None + + def __reduce__(self): + r""" + Used for pickling. + + EXAMPLES:: + + sage: CHA2. = algebras.CubicHecke(2) + sage: MS = c1.matrix().parent() + sage: loads(dumps(MS)) == MS # indirect doctest + True + """ + original = self.base_ring() == self._original_base_ring + return CubicHeckeMatrixSpace, (self._cubic_hecke_algebra, self._representation_type, self._subdivide, original) + + def _element_constructor_(self, x): + r""" + INPUT: + + - ``x`` -- an element of a + :class:`~sage.algebras.hecke_algebras.cubic_hecke_algebra.CubicHeckeAlgebra` + or an element whose parent is a :class:`MatrixSpace` + + EXAMLPES:: + + sage: import sage.algebras.hecke_algebras.cubic_hecke_matrix_rep as chmr + sage: CHA3. = algebras.CubicHecke(3) + sage: MS = chmr.CubicHeckeMatrixSpace(CHA3, original=True) + sage: m1 = MS._element_constructor_(c1) + sage: isinstance(m1, MS.element_class) + True + sage: isinstance(MS._element_constructor_(m1), MS.element_class) + True + + sage: m = matrix(MS.base_ring(), 12, 12, lambda i, j: 1) + sage: MS._element_constructor_(m) + Traceback (most recent call last): + ... + TypeError: incompatible block structure + """ + # ------------------------------------------------------------------------------------------------- + # checking input and setting the self._cubic_hecke_algebra + # ------------------------------------------------------------------------------------------------- + ch_algebra = self._cubic_hecke_algebra + ele_parent = x.parent() + ori_base_ring = self._original_base_ring + if isinstance(ele_parent, MatrixSpace): + # TODO: Find preimage in cubic hecke algebra + d1, d2 = x.dimensions() + if d1 != self.ncols() or d2 != self.nrows(): + raise ValueError('incompatible dimensions!') + + if ele_parent.base_ring() == ori_base_ring: + x = self._specialize_matrix(x) + elif ele_parent.base_ring() != self.base_ring(): + raise ValueError('incompatible base ring!') + x_in_self = self.element_class(self, x) + matrix_list = x_in_self.block_diagonal_list() + matrix = block_diagonal_matrix(matrix_list, subdivide=self._subdivide, sparse=True) + if matrix != x: + raise TypeError('incompatible block structure') + return self.element_class(self, matrix) + + if ele_parent == ch_algebra: + mat = ch_algebra._apply_module_morphism(x, self._image_on_basis) + return self(mat) + + raise TypeError('element must be an instance of CubicHeckeElement or a matrix') + + @cached_method + def __call__(self, entries=None, coerce=True, copy=None): + r""" + Construct an element of ``self``. + + This method needs to be overloaded here since + :class:`MatrixSpace` has an own implementation of it. + + EXAMLPES:: + + sage: import sage.algebras.hecke_algebras.cubic_hecke_matrix_rep as chmr + sage: CHA2. = algebras.CubicHecke(2) + sage: MS = chmr.CubicHeckeMatrixSpace(CHA2) + sage: MS(c1) + [ a 0 0] + [ 0 b 0] + [ 0 0 -b - a + u] + """ + from sage.algebras.hecke_algebras.cubic_hecke_algebra import CubicHeckeAlgebra + if entries is None: + return super(CubicHeckeMatrixSpace, self).__call__(entries=entries, coerce=coerce, copy=copy) + if not hasattr(entries, 'parent'): + return super(CubicHeckeMatrixSpace, self).__call__(entries=entries, coerce=coerce, copy=copy) + ele_parent = entries.parent() + if not isinstance(ele_parent, (CubicHeckeAlgebra, MatrixSpace)): + return super(CubicHeckeMatrixSpace, self).__call__(entries=entries, coerce=coerce, copy=copy) + return self._element_constructor_(entries) + + @cached_method + def _specialize_matrix(self, mat): + r""" + Return the given matrix specializing the original coefficients + from data import to the base ring of ``self``. + + INPUT: + + - ``mat`` -- matrix over the original base ring + + OUTPUT: + + ``mat`` over the base ring of ``self`` + + EXAMPLES:: + + sage: import sage.algebras.hecke_algebras.cubic_hecke_matrix_rep as chmr + sage: CHA2. = algebras.CubicHecke(2) + sage: MS = chmr.CubicHeckeMatrixSpace(CHA2) + sage: B = MS._original_base_ring + sage: a, b, c = B.gens() + sage: mat = matrix(B, [[a, b], [0, c]]) + sage: MS._specialize_matrix(mat) + [ a b] + [ 0 -b - a + u] + """ + base_ring = self.base_ring() + original_base_ring = self._original_base_ring + specialize = self._specialize + + if base_ring == original_base_ring: + return mat + + mat_dict = {k: specialize(original_base_ring(v)) for k, v in mat.dict().items()} + return matrix(base_ring, mat_dict) + + @cached_method + def _image_on_gen(self, gen_ind): + r""" + Return the matrix list corresponding to the generator given by + ``(gen_ind,)`` in Tietze form under the representation_type of + ``self`` from the data-file or via the ``GAP3`` interface + + INPUT: + + - ``gen_ind`` -- integer; index of a generator of the cubic Hecke + algebra attached to ``self + 1``; negative values correspond to + the according inverses + + EXAMPLES:: + + sage: import sage.algebras.hecke_algebras.cubic_hecke_matrix_rep as chmr + sage: CHA3. = algebras.CubicHecke(3) + sage: MS = chmr.CubicHeckeMatrixSpace(CHA3) + sage: MS._image_on_gen(1) + [ + [ b 0] [ a 0] [ a 0] + [a], [c], [b], [b*c c], [a*b b], [a*c c], + + [ c 0 0] + [b^2 + a*c b 0] + [ b 1 a] + ] + + sage: CHA2 = CHA3.cubic_hecke_subalgebra() + sage: MSreg = chmr.CubicHeckeMatrixSpace(CHA2, representation_type=CHA2.repr_type.RegularRight) + sage: MSreg._image_on_gen(-1) + [ + [ 0 1 (-u)/w] + [ 0 0 1/w] + [ 1 0 v/w] + ] + """ + representation_type = self._representation_type + original_base_ring = self._original_base_ring + ch_algebra = self._cubic_hecke_algebra + n = ch_algebra.strands() + + def invert_gen(matr): + r""" + Return the inverse matrix of generators. + """ + cfs = ch_algebra.cubic_equation(as_coefficients=True, generic=True) + fac = - 1/cfs[0] + cf0, cf1, cf2, cf3 = [original_base_ring(cf*fac) for cf in cfs] + + matri = cf1*matr.parent().one() + matri += cf2*matr + matri += cf3*matr**2 + d1, d2 = matr.dimensions() + matrI = matrix(original_base_ring, d1, d2, lambda i, j: original_base_ring(matri[i, j])) + return matrI + + if n == 2: + if representation_type.is_split(): + # Split representations for n == 2 are missing in CHEVIE and data files + a, b, c = original_base_ring.gens() + matrix_list = [matrix(1, 1, [a]), matrix(1, 1, [b]), matrix(1, 1, [c])] + if gen_ind < 0: + matrix_list = [invert_gen(mat) for mat in matrix_list] + return matrix_list + + num_rep = representation_type.number_of_representations(n) + + if representation_type == RepresentationType.SplitIrredChevie: + rep_list = [ch_algebra._fetch_matrix_list_from_chevie(i+1) for i in range(num_rep)] + if gen_ind > 0: + matrix_list = [rep[gen_ind - 1] for rep in rep_list] + else: + matrix_list = [invert_gen(rep[-gen_ind - 1]) for rep in rep_list] + else: + database = ch_algebra._database + matrix_list = database.read_matrix_representation(representation_type, gen_ind, n, original_base_ring) + return matrix_list + + @cached_method + def _image_on_basis(self, basis_element): + r""" + Return the image of the given basis element of the cubic Hecke algebra + in ``self``. + + INPUT: + + - ``basis_element`` -- a + :class:`~sage.algebras.hecke_algebras.cubic_hecke_algebra.CubicHeckeElement` + that is a monomial + + EXAMPLES:: + + sage: import sage.algebras.hecke_algebras.cubic_hecke_matrix_rep as chmr + sage: CHA3. = algebras.CubicHecke(3) + sage: MS = chmr.CubicHeckeMatrixSpace(CHA3, original=True) + sage: MS._image_on_basis(c1) + [ a 0 0 0 0 0 0 0 0 0 0 0] + [ 0 c 0 0 0 0 0 0 0 0 0 0] + [ 0 0 b 0 0 0 0 0 0 0 0 0] + [ 0 0 0 b 0 0 0 0 0 0 0 0] + [ 0 0 0 b*c c 0 0 0 0 0 0 0] + [ 0 0 0 0 0 a 0 0 0 0 0 0] + [ 0 0 0 0 0 a*b b 0 0 0 0 0] + [ 0 0 0 0 0 0 0 a 0 0 0 0] + [ 0 0 0 0 0 0 0 a*c c 0 0 0] + [ 0 0 0 0 0 0 0 0 0 c 0 0] + [ 0 0 0 0 0 0 0 0 0 b^2 + a*c b 0] + [ 0 0 0 0 0 0 0 0 0 b 1 a] + """ + representation_type = self._representation_type + ch_algebra = self._cubic_hecke_algebra + filecache = ch_algebra._filecache + + original_base_ring = self._original_base_ring + + ele_Tietze = basis_element.Tietze() + matrix_list = filecache.read_matrix_representation(representation_type, ele_Tietze, original_base_ring) + if matrix_list is None: + verbose('not in memory %s (Tietze %s)' % (basis_element, ele_Tietze), level=2) + if len(ele_Tietze) == 0: + matrix_list = ch_algebra._create_matrix_list_for_one(representation_type) + else: + for gen_ind in ele_Tietze: + gen_matrix_list = self._image_on_gen(gen_ind) + if matrix_list is None: + matrix_list = [m for m in gen_matrix_list] + else: + for i in range(len(matrix_list)): + matrix_list[i] *= gen_matrix_list[i] + + filecache.write_matrix_representation(representation_type, ele_Tietze, matrix_list) + verbose('%s saved to memory' % basis_element, level=2) + + mat = block_diagonal_matrix(matrix_list, subdivide=self._subdivide, sparse=True) + return self._specialize_matrix(mat) + + @cached_method + def zero(self): + r""" + Return the zero element of ``self``. + + EXAMPLES:: + + sage: CHA2. = algebras.CubicHecke(2) + sage: m1 = c1.matrix() + sage: m1rl = c1.matrix(representation_type = CHA2.repr_type.RegularLeft) + sage: z = m1.parent().zero() + sage: zrl = m1rl.parent().zero() + sage: matrix(z) == matrix(zrl), z.is_zero(), zrl.is_zero() + (True, True, True) + sage: z.block_diagonal_list() + [[0], [0], [0]] + sage: zrl.block_diagonal_list() + [ + [0 0 0] + [0 0 0] + [0 0 0] + ] + """ + z = self.element_class(self, super(CubicHeckeMatrixSpace, self).zero()) + z._cubic_hecke_element = self._cubic_hecke_algebra.zero() + z.set_immutable() + return z + + @cached_method + def one(self): + r""" + Return the one element of ``self``. + + EXAMPLES:: + + sage: CHA2. = algebras.CubicHecke(2) + sage: m1 = c1.matrix() + sage: m1rl = c1.matrix(representation_type = CHA2.repr_type.RegularLeft) + sage: o = m1.parent().one() + sage: orl = m1rl.parent().one() + sage: matrix(o) == matrix(orl), o.is_one(), orl.is_one() + (True, True, True) + sage: o.block_diagonal_list() + [[1], [1], [1]] + sage: orl.block_diagonal_list() + [ + [1 0 0] + [0 1 0] + [0 0 1] + ] + """ + o = self.element_class(self, super(CubicHeckeMatrixSpace, self).one()) + o._cubic_hecke_element = self._cubic_hecke_algebra.one() + o.set_immutable() + return o + + @cached_method + def _an_element_(self): + r""" + Return an element of ``self``. + + EXAMPLES:: + + sage: CHA2. = algebras.CubicHecke(2, cubic_equation_roots=(2, 3, 5)) + sage: c1.matrix() + [2 0 0] + [0 3 0] + [0 0 5] + sage: _.parent()._an_element_() + [ 94/3 0 0] + [ 0 187/3 0] + [ 0 0 373/3] + """ + x = self._cubic_hecke_algebra.an_element() + return self(x) + + @cached_method + def some_elements(self): + r""" + Return a generator of elements of ``self``. + + EXAMPLES:: + + sage: CHA2. = algebras.CubicHecke(2, cubic_equation_roots=(2, 3, 5)) + sage: M = c1.matrix(); M + [2 0 0] + [0 3 0] + [0 0 5] + sage: MS = M.parent() + sage: MS.some_elements() + ( + [ 94/3 0 0] + [ 0 187/3 0] + [ 0 0 373/3] + ) + sage: MS.some_elements() == tuple(MS(x) for x in CHA2.some_elements()) + True + """ + return tuple([self(x) for x in self._cubic_hecke_algebra.some_elements()]) + diff --git a/src/sage/algebras/iwahori_hecke_algebra.py b/src/sage/algebras/iwahori_hecke_algebra.py index ebbeb565341..671cd52fecf 100644 --- a/src/sage/algebras/iwahori_hecke_algebra.py +++ b/src/sage/algebras/iwahori_hecke_algebra.py @@ -1732,7 +1732,7 @@ class Element(CombinatorialFreeModule.Element): sage: T1.parent() Iwahori-Hecke algebra of type A2 in 1,-1 over Integer Ring in the T-basis """ - def inverse(self): + def __invert__(self): r""" Return the inverse if ``self`` is a basis element. @@ -1746,7 +1746,7 @@ def inverse(self): sage: R. = LaurentPolynomialRing(QQ) sage: H = IwahoriHeckeAlgebra("A2", q).T() sage: [T1,T2] = H.algebra_generators() - sage: x = (T1*T2).inverse(); x + sage: x = (T1*T2).inverse(); x # indirect doctest (q^-2)*T[2,1] + (q^-2-q^-1)*T[1] + (q^-2-q^-1)*T[2] + (q^-2-2*q^-1+1) sage: x*T1*T2 1 @@ -1771,8 +1771,6 @@ def inverse(self): return H.prod(H.inverse_generator(i) for i in reversed(w.reduced_word())) - __invert__ = inverse - standard = T class _KLHeckeBasis(_Basis): diff --git a/src/sage/algebras/letterplace/letterplace_ideal.pyx b/src/sage/algebras/letterplace/letterplace_ideal.pyx index c16803280b2..56ec1c3d399 100644 --- a/src/sage/algebras/letterplace/letterplace_ideal.pyx +++ b/src/sage/algebras/letterplace/letterplace_ideal.pyx @@ -161,7 +161,7 @@ class LetterplaceIdeal(Ideal_nc): -y*x*z + z*z """ - def __init__(self, ring, gens, coerce=True, side = "twosided"): + def __init__(self, ring, gens, coerce=True, side="twosided"): """ INPUT: diff --git a/src/sage/algebras/lie_algebras/subalgebra.py b/src/sage/algebras/lie_algebras/subalgebra.py index baa633c1cb7..90779eb91fc 100644 --- a/src/sage/algebras/lie_algebras/subalgebra.py +++ b/src/sage/algebras/lie_algebras/subalgebra.py @@ -5,7 +5,6 @@ - Eero Hakavuori (2018-08-29): initial version """ - # **************************************************************************** # Copyright (C) 2018 Eero Hakavuori # @@ -15,7 +14,6 @@ # (at your option) any later version. # https://www.gnu.org/licenses/ # **************************************************************************** - from sage.algebras.lie_algebras.lie_algebra_element import LieSubalgebraElementWrapper from sage.categories.lie_algebras import LieAlgebras from sage.categories.homset import Hom @@ -975,4 +973,3 @@ def adjoint_matrix(self, sparse=False): return matrix(self.base_ring(), [M.coordinate_vector(P.bracket(self, b).to_vector(sparse=sparse)) for b in basis], sparse=sparse).transpose() - diff --git a/src/sage/algebras/lie_conformal_algebras/freely_generated_lie_conformal_algebra.py b/src/sage/algebras/lie_conformal_algebras/freely_generated_lie_conformal_algebra.py index f462a933a38..8fa5460d29a 100644 --- a/src/sage/algebras/lie_conformal_algebras/freely_generated_lie_conformal_algebra.py +++ b/src/sage/algebras/lie_conformal_algebras/freely_generated_lie_conformal_algebra.py @@ -80,7 +80,7 @@ def lie_conformal_algebra_generators(self): """ F = Family(self._generators, lambda i: self.monomial((i,Integer(0))), - name = "generator map") + name="generator map") from sage.categories.sets_cat import Sets if F in Sets().Finite(): return tuple(F) @@ -100,5 +100,5 @@ def central_elements(self): (B['K'],) """ return Family(self._central_elements, - lambda i: self.monomial((i,Integer(0))), - name = "central_element map") + lambda i: self.monomial((i, Integer(0))), + name="central_element map") diff --git a/src/sage/algebras/lie_conformal_algebras/lie_conformal_algebra_element.py b/src/sage/algebras/lie_conformal_algebras/lie_conformal_algebra_element.py index 55d9e3dab67..b11c912f36d 100644 --- a/src/sage/algebras/lie_conformal_algebras/lie_conformal_algebra_element.py +++ b/src/sage/algebras/lie_conformal_algebras/lie_conformal_algebra_element.py @@ -227,11 +227,11 @@ def _latex_(self): else("T{}".format(names[p._index_to_pos[k[0]]]),v)\ if k[1] == 1\ else ("{}".format(names[p._index_to_pos[k[0]]]),v)\ - for k,v in self.monomial_coefficients().items()] + for k, v in self.monomial_coefficients().items()] else: terms = [("T^{{({0})}}{1}".format(k[1], latex(k[0])),v) if k[1] > 1 \ else("T{}".format(latex(k[0])),v) if k[1] == 1 \ else ("{}".format(latex(k[0])),v)\ - for k,v in self.monomial_coefficients().items()] + for k, v in self.monomial_coefficients().items()] - return repr_lincomb(terms, is_latex=True, strip_one = True) + return repr_lincomb(terms, is_latex=True, strip_one=True) diff --git a/src/sage/algebras/lie_conformal_algebras/virasoro_lie_conformal_algebra.py b/src/sage/algebras/lie_conformal_algebras/virasoro_lie_conformal_algebra.py index 26ac0951167..cc11ec06a0a 100644 --- a/src/sage/algebras/lie_conformal_algebras/virasoro_lie_conformal_algebra.py +++ b/src/sage/algebras/lie_conformal_algebras/virasoro_lie_conformal_algebra.py @@ -59,10 +59,11 @@ def __init__(self, R): sage: V = lie_conformal_algebras.Virasoro(QQ) sage: TestSuite(V).run() """ - virdict = {('L','L'):{0:{('L',1):1}, 1:{('L',0): 2}, - 3:{('C', 0):R(2).inverse_of_unit()}}} - GradedLieConformalAlgebra.__init__(self,R, virdict, - names = ('L',), central_elements = ('C',), weights = (2,)) + virdict = {('L', 'L'): {0: {('L', 1): 1}, + 1: {('L', 0): 2}, + 3: {('C', 0): R(2).inverse_of_unit()}}} + GradedLieConformalAlgebra.__init__(self, R, virdict, + names=('L',), central_elements=('C',), weights=(2,)) def _repr_(self): """ diff --git a/src/sage/algebras/nil_coxeter_algebra.py b/src/sage/algebras/nil_coxeter_algebra.py index f44039253ac..5d255b9786d 100644 --- a/src/sage/algebras/nil_coxeter_algebra.py +++ b/src/sage/algebras/nil_coxeter_algebra.py @@ -44,7 +44,7 @@ class NilCoxeterAlgebra(IwahoriHeckeAlgebra.T): u[0,1,2,3] + 2*u[0] + 3*u[1] + 1 """ - def __init__(self, W, base_ring = QQ, prefix='u'): + def __init__(self, W, base_ring=QQ, prefix='u'): r""" Initiate the affine nil-Coxeter algebra corresponding to the Weyl group `W` over the base ring. diff --git a/src/sage/algebras/orlik_solomon.py b/src/sage/algebras/orlik_solomon.py index ffbbbb6a5af..4f256fe7777 100644 --- a/src/sage/algebras/orlik_solomon.py +++ b/src/sage/algebras/orlik_solomon.py @@ -576,7 +576,7 @@ class OrlikSolomonInvariantAlgebra(FiniteDimensionalInvariantModule): .. NOTE:: The algebra structure only exists when the action on the - groundset yeilds an equivariant matroid, in the sense that + groundset yields an equivariant matroid, in the sense that `g \cdot I \in \mathcal{I}` for every `g \in G` and for every `I \in \mathcal{I}`. """ @@ -638,9 +638,25 @@ def action(g, m): *args, **kwargs) # To subclass FiniteDimensionalInvariant module, we also need a - # self._semigroup method. + # self._semigroup attribute. self._semigroup = G + def construction(self): + r""" + Return the functorial construction of ``self``. + + This implementation of the method only returns ``None``. + + TESTS:: + + sage: M = matroids.Wheel(3) + sage: from sage.algebras.orlik_solomon import OrlikSolomonAlgebra + sage: OS1 = OrlikSolomonAlgebra(QQ, M) + sage: OS1.construction() is None + True + """ + return None + def _basis_action(self, g, f): r""" Return the action of the group element ``g`` on the n.b.c. set ``f`` diff --git a/src/sage/algebras/orlik_terao.py b/src/sage/algebras/orlik_terao.py index 458bb07b926..55fd2bdb641 100644 --- a/src/sage/algebras/orlik_terao.py +++ b/src/sage/algebras/orlik_terao.py @@ -628,7 +628,7 @@ def action_on_groundset(g, x): self._side = kwargs.pop('side', 'left') - # the action on the Orlik-Terao is not neccesarily by ring automorphim, + # the action on the Orlik-Terao is not necessarily by ring automorphism, # so the best we can assume is a finite dimensional module with basis. if 'category' in kwargs: category = kwargs.pop('category') @@ -660,6 +660,25 @@ def action(g, m): self._semigroup = G + def construction(self): + r""" + Return the functorial construction of ``self``. + + This implementation of the method only returns ``None``. + + TESTS:: + + sage: A = matrix([[1,1,0],[-1,0,1],[0,-1,-1]]) + sage: M = Matroid(A) + sage: G = SymmetricGroup(3) + sage: def on_groundset(g,x): + ....: return g(x+1)-1 + sage: OTG = M.orlik_terao_algebra(QQ, invariant=(G,on_groundset)) + sage: OTG.construction() is None + True + """ + return None + def _basis_action(self, g, f): r""" Let ``f`` be an n.b.c. set so that it indexes a basis diff --git a/src/sage/algebras/q_commuting_polynomials.py b/src/sage/algebras/q_commuting_polynomials.py new file mode 100644 index 00000000000..b772f04a57e --- /dev/null +++ b/src/sage/algebras/q_commuting_polynomials.py @@ -0,0 +1,351 @@ +r""" +`q`-Commuting Polynomials + +AUTHORS: + +- Travis Scrimshaw (2022-08-23): Initial version +""" + +# **************************************************************************** +# Copyright (C) 2022 Travis Scrimshaw +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from sage.misc.cachefunc import cached_method +from sage.sets.family import Family +from sage.rings.infinity import infinity +from sage.rings.integer_ring import ZZ +from sage.categories.algebras import Algebras +from sage.combinat.free_module import CombinatorialFreeModule +from sage.monoids.free_abelian_monoid import FreeAbelianMonoid +from sage.matrix.constructor import matrix +from sage.structure.element import Matrix + +class qCommutingPolynomials(CombinatorialFreeModule): + r""" + The algebra of `q`-commuting polynomials. + + Let `R` be a commutative ring, and fix an element `q \in R`. Let + B = (B_{xy})_{x,y \in I}` be a skew-symmetric bilinear form with + index set `I`. Let `R[I]_{q,B}` denote the polynomial ring in the variables + `I` such that we have the `q`-*commuting* relation for `x, y \in I`: + + .. MATH:: + + y x = q^{B_{xy}} \cdot x y. + + This is a graded `R`-algebra with a natural basis given by monomials + written in increasing order with respect to some total order on `I`. + + When `B_{xy} = 1` and `B_{yx} = -1` for all `x < y`, then we have + a `q`-analog of the classical binomial coefficient theorem: + + .. MATH:: + + (x + y)^n = \sum_{k=0}^n \binom{n}{k}_q x^k y^{n-k}. + + EXAMPLES:: + + sage: q = ZZ['q'].fraction_field().gen() + sage: R. = algebras.qCommutingPolynomials(q) + + We verify a case of the `q`-binomial theorem:: + + sage: f = (x + y)^10 + sage: all(f[b] == q_binomial(10, b.list()[0]) for b in f.support()) + True + + We now do a computation with a non-standard `B` matrix:: + + sage: B = matrix([[0,1,2],[-1,0,3],[-2,-3,0]]) + sage: B + [ 0 1 2] + [-1 0 3] + [-2 -3 0] + sage: q = ZZ['q'].gen() + sage: R. = algebras.qCommutingPolynomials(q, B) + sage: y * x + q*x*y + sage: z * x + q^2*x*z + sage: z * y + q^3*y*z + + sage: f = (x + z)^10 + sage: all(f[b] == q_binomial(10, b.list()[0], q^2) for b in f.support()) + True + + sage: f = (y + z)^10 + sage: all(f[b] == q_binomial(10, b.list()[1], q^3) for b in f.support()) + True + """ + @staticmethod + def __classcall_private__(cls, q, n=None, B=None, base_ring=None, names=None): + r""" + Normalize input to ensure a unique representation. + + EXAMPLES:: + + sage: q = ZZ['q'].fraction_field().gen() + sage: R1. = algebras.qCommutingPolynomials(q) + sage: R2 = algebras.qCommutingPolynomials(q, base_ring=q.parent(), names='x,y,z') + sage: R3 = algebras.qCommutingPolynomials(q, names=['x', 'y', 'z']) + sage: R1 is R2 is R3 + True + """ + if base_ring is not None: + q = base_ring(q) + + if B is None and isinstance(n, Matrix): + n, B = B, n + + if names is None: + raise ValueError("the names of the variables must be given") + from sage.structure.category_object import normalize_names + if n is None: + if isinstance(names, str): + n = names.count(',') + 1 + else: + n = len(names) + names = normalize_names(n, names) + n = len(names) + if B is None: + B = matrix.zero(ZZ, n) + for i in range(n): + for j in range(i+1, n): + B[i,j] = 1 + B[j,i] = -1 + B.set_immutable() + else: + if not B.is_skew_symmetric(): + raise ValueError("the matrix must be skew symmetric") + B = B.change_ring(ZZ) + B.set_immutable() + return super().__classcall__(cls, q=q, B=B, names=names) + + def __init__(self, q, B, names): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: q = ZZ['q'].fraction_field().gen() + sage: R. = algebras.qCommutingPolynomials(q) + sage: TestSuite(R).run() + """ + self._q = q + self._B = B + base_ring = q.parent() + indices = FreeAbelianMonoid(len(names), names) + category = Algebras(base_ring).WithBasis().Graded() + CombinatorialFreeModule.__init__(self, base_ring, indices, + bracket=False, prefix='', + sorting_key=qCommutingPolynomials._term_key, + names=indices.variable_names(), category=category) + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: q = ZZ['q'].fraction_field().gen() + sage: R. = algebras.qCommutingPolynomials(q) + sage: R + q-commuting polynomial ring in x, y, z over Fraction Field of + Univariate Polynomial Ring in q over Integer Ring with matrix: + [ 0 1 1] + [-1 0 1] + [-1 -1 0] + """ + names = ", ".join(self.variable_names()) + return "{}-commuting polynomial ring in {} over {} with matrix:\n{}".format(self._q, names, self.base_ring(), self._B) + + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: q = ZZ['q'].fraction_field().gen() + sage: R. = algebras.qCommutingPolynomials(q) + sage: latex(R) + \mathrm{Frac}(\Bold{Z}[q])[x, y, z]_{q} + """ + from sage.misc.latex import latex + names = ", ".join(self.variable_names()) + return "{}[{}]_{{{}}}".format(latex(self.base_ring()), names, self._q) + + @staticmethod + def _term_key(x): + r""" + Compute a key for ``x`` for comparisons. + + EXAMPLES:: + + sage: q = ZZ['q'].fraction_field().gen() + sage: R. = algebras.qCommutingPolynomials(q) + sage: elt = (x*y^3*z^2).leading_support() + sage: R._term_key(elt) + (6, [2, 3, 1]) + """ + L = x.list() + L.reverse() + return (sum(L), L) + + def gen(self, i): + r""" + Return the ``i``-generator of ``self``. + + EXAMPLES:: + + sage: q = ZZ['q'].fraction_field().gen() + sage: R. = algebras.qCommutingPolynomials(q) + sage: R.gen(0) + x + sage: R.gen(2) + z + """ + return self.monomial(self._indices.gen(i)) + + @cached_method + def gens(self): + r""" + Return the generators of ``self``. + + EXAMPLES:: + + sage: q = ZZ['q'].fraction_field().gen() + sage: R. = algebras.qCommutingPolynomials(q) + sage: R.gens() + (x, y, z) + """ + return tuple([self.monomial(g) for g in self._indices.gens()]) + + @cached_method + def algebra_generators(self): + r""" + Return the algebra generators of ``self``. + + EXAMPLES:: + + sage: q = ZZ['q'].fraction_field().gen() + sage: R. = algebras.qCommutingPolynomials(q) + sage: R.algebra_generators() + Finite family {'x': x, 'y': y, 'z': z} + """ + d = {v: self.gen(i) for i,v in enumerate(self.variable_names())} + return Family(self.variable_names(), d.__getitem__, name="generator") + + @cached_method + def one_basis(self): + r""" + Return the basis index of the element `1`. + + EXAMPLES:: + + sage: q = ZZ['q'].fraction_field().gen() + sage: R. = algebras.qCommutingPolynomials(q) + sage: R.one_basis() + 1 + """ + return self._indices.one() + + def degree_on_basis(self, m): + r""" + Return the degree of the monomial index by ``m``. + + EXAMPLES:: + + sage: q = ZZ['q'].fraction_field().gen() + sage: R. = algebras.qCommutingPolynomials(q) + sage: R.degree_on_basis(R.one_basis()) + 0 + sage: f = (x + y)^3 + z^3 + sage: f.degree() + 3 + """ + return sum(m.list()) + + def dimension(self): + r""" + Return the dimension of ``self``, which is `\infty`. + + EXAMPLES:: + + sage: q = ZZ['q'].fraction_field().gen() + sage: R. = algebras.qCommutingPolynomials(q) + sage: R.dimension() + +Infinity + """ + return infinity + + @cached_method + def product_on_basis(self, x, y): + r""" + Return the product of two monomials given by ``x`` and ``y``. + + EXAMPLES:: + + sage: q = ZZ['q'].fraction_field().gen() + sage: R. = algebras.qCommutingPolynomials(q) + sage: R.product_on_basis(x.leading_support(), y.leading_support()) + x*y + sage: R.product_on_basis(y.leading_support(), x.leading_support()) + q*x*y + + sage: x * y + x*y + sage: y * x + q*x*y + sage: y^2 * x + q^2*x*y^2 + sage: y * x^2 + q^2*x^2*y + sage: x * y * x + q*x^2*y + sage: y^2 * x^2 + q^4*x^2*y^2 + sage: (x + y)^2 + x^2 + (q+1)*x*y + y^2 + sage: (x + y)^3 + x^3 + (q^2+q+1)*x^2*y + (q^2+q+1)*x*y^2 + y^3 + sage: (x + y)^4 + x^4 + (q^3+q^2+q+1)*x^3*y + (q^4+q^3+2*q^2+q+1)*x^2*y^2 + (q^3+q^2+q+1)*x*y^3 + y^4 + + With a non-standard `B` matrix:: + + sage: B = matrix([[0,1,2],[-1,0,3],[-2,-3,0]]) + sage: q = ZZ['q'].fraction_field().gen() + sage: R. = algebras.qCommutingPolynomials(q, B=B) + sage: x * y + x*y + sage: y * x^2 + q^2*x^2*y + sage: z^2 * x + q^4*x*z^2 + sage: z^2 * x^3 + q^12*x^3*z^2 + sage: z^2 * y + q^6*y*z^2 + sage: z^2 * y^3 + q^18*y^3*z^2 + """ + # Special case for multiplying by 1 + if x == self.one_basis(): + return self.monomial(y) + if y == self.one_basis(): + return self.monomial(x) + + Lx = x.list() + Ly = y.list() + + # This could be made more efficient + qpow = sum(exp * sum(self._B[j,i] * val for j, val in enumerate(Ly[:i])) for i,exp in enumerate(Lx)) + return self.term(x * y, self._q ** qpow) + diff --git a/src/sage/algebras/quantum_clifford.py b/src/sage/algebras/quantum_clifford.py index 8d11f400478..f646b0c2a52 100644 --- a/src/sage/algebras/quantum_clifford.py +++ b/src/sage/algebras/quantum_clifford.py @@ -24,8 +24,10 @@ from sage.combinat.free_module import CombinatorialFreeModule from sage.categories.cartesian_product import cartesian_product from sage.sets.family import Family +from sage.rings.fraction_field import FractionField from sage.sets.finite_enumerated_set import FiniteEnumeratedSet from itertools import product +from sage.misc.misc import powerset class QuantumCliffordAlgebra(CombinatorialFreeModule): r""" @@ -34,7 +36,7 @@ class QuantumCliffordAlgebra(CombinatorialFreeModule): The *quantum Clifford algebra*, or `q`-Clifford algebra, of rank `n` and twist `k` is the unital associative algebra `\mathrm{Cl}_{q}(n, k)` over a field `F` with generators - `\psi_a, \psi_a^*, \omega_a` for `a = 1, \dotsc, n` that + `\psi_a, \psi_a^*, \omega_a` for `a = 1, \ldots, n` that satisfy the following relations: .. MATH:: @@ -46,14 +48,12 @@ class QuantumCliffordAlgebra(CombinatorialFreeModule): & \omega_a \psi^*_b & = \psi^*_b \omega_a, \\ \psi_a \psi_b & + \psi_b \psi_a = 0, & \psi^*_a \psi^*_b & + \psi^*_b \psi^*_a = 0, - \\ \psi_a \psi^*_a & = \frac{q^k \omega_a^{3k} - q^{-k} \omega_a^k}{q^k - q^{-k}}, - & \psi^*_a \psi_a & = \frac{q^{2k} (\omega_a - \omega_a^{3k})}{q^k - q^{-k}}, + \\ \psi_a \psi^*_a & + q^k \psi^*_a \psi_a = \omega_a^{-k}, + & \psi^*_a \psi_a & + q^{-k} \psi^*_a \psi_a = \omega_a^k, \\ \psi_a \psi^*_b & + \psi_b^* \psi_a = 0 - & & \text{if } a \neq b, + & & \text{if } a \neq b. \end{aligned} - where `q \in F` such that `q^{2k} \neq 1`. - When `k = 2`, we recover the original definition given by Hayashi in [Hayashi1990]_. The `k = 1` version was used in [Kwon2014]_. @@ -127,6 +127,19 @@ class QuantumCliffordAlgebra(CombinatorialFreeModule): 0 sage: f1 * f1 * f2 - (q^1 + q^-1) * f1 * f2 * f1 + f2 * f1 * f1 0 + + This also can be constructed at the special point when `q^{2k} = 1`, + but the basis used is different:: + + sage: Cl = algebras.QuantumClifford(1, 1, -1) + sage: Cl.inject_variables() + Defining psi0, psid0, w0 + sage: psi0 * psid0 + psi0*psid0 + sage: psid0 * psi0 + -w0 + psi0*psid0 + sage: w0^2 + 1 """ @staticmethod def __classcall_private__(cls, n, k=1, q=None, F=None): @@ -148,40 +161,30 @@ def __classcall_private__(cls, n, k=1, q=None, F=None): F = q.parent() q = F(q) if F not in Fields(): - raise TypeError("base ring must be a field") - return super().__classcall__(cls, n, k, q, F) + F = FractionField(F) + q = F(q) - def __init__(self, n, k, q, F): + if bool(q**(2*k) == 1): + return QuantumCliffordAlgebraRootUnity(n, k, q, F) + return QuantumCliffordAlgebraGeneric(n, k, q, F) + + def __init__(self, n, k, q, F, psi, indices): r""" Initialize ``self``. EXAMPLES:: - sage: Cl = algebras.QuantumClifford(1,2) + sage: Cl = algebras.QuantumClifford(1, 2) sage: TestSuite(Cl).run(elements=Cl.basis()) - - sage: Cl = algebras.QuantumClifford(1,3) - sage: TestSuite(Cl).run(elements=Cl.basis()) # long time - - sage: Cl = algebras.QuantumClifford(3) # long time - sage: elts = Cl.some_elements() + list(Cl.algebra_generators()) # long time - sage: TestSuite(Cl).run(elements=elts) # long time - - sage: Cl = algebras.QuantumClifford(2,4) # long time - sage: elts = Cl.some_elements() + list(Cl.algebra_generators()) # long time - sage: TestSuite(Cl).run(elements=elts) # long time """ self._n = n self._k = k self._q = q - self._psi = cartesian_product([(-1,0,1)]*n) self._w_poly = PolynomialRing(F, n, 'w') - indices = [(tuple(psi), tuple(w)) - for psi in self._psi - for w in product(*[list(range((4-2*abs(psi[i]))*k)) for i in range(n)])] + self._psi = psi indices = FiniteEnumeratedSet(indices) - cat = Algebras(F).FiniteDimensional().WithBasis() + cat = Algebras(F).FiniteDimensional().Semisimple().WithBasis() CombinatorialFreeModule.__init__(self, F, indices, category=cat) self._assign_names(self.algebra_generators().keys()) @@ -210,67 +213,6 @@ def _latex_(self): """ return "\\operatorname{Cl}_{%s}(%s, %s)" % (self._q, self._n, self._k) - def _repr_term(self, m): - r""" - Return a string representation of the basis element indexed by ``m``. - - EXAMPLES:: - - sage: Cl = algebras.QuantumClifford(3, 3) - sage: Cl._repr_term( ((1, 0, -1), (0, -2, 5)) ) - 'psi0*psid2*w1^-2*w2^5' - sage: Cl._repr_term( ((1, 0, -1), (0, 0, 0)) ) - 'psi0*psid2' - sage: Cl._repr_term( ((0, 0, 0), (0, -2, 5)) ) - 'w1^-2*w2^5' - sage: Cl._repr_term( ((0, 0, 0), (0, 0, 0)) ) - '1' - - sage: Cl(5) - 5 - """ - p, v = m - rp = '*'.join('psi%s'%i if p[i] > 0 else 'psid%s'%i - for i in range(self._n) if p[i] != 0) - gen_str = lambda e: '' if e == 1 else '^%s'%e - rv = '*'.join('w%s'%i + gen_str(v[i]) for i in range(self._n) if v[i] != 0) - if rp: - if rv: - return rp + '*' + rv - return rp - if rv: - return rv - return '1' - - def _latex_term(self, m): - r""" - Return a latex representation for the basis element indexed by ``m``. - - EXAMPLES:: - - sage: Cl = algebras.QuantumClifford(3, 3) - sage: Cl._latex_term( ((1, 0, -1), (0, -2, 5)) ) - '\\psi_{0}\\psi^{\\dagger}_{2}\\omega_{1}^{-2}\\omega_{2}^{5}' - sage: Cl._latex_term( ((1, 0, -1), (0, 0, 0)) ) - '\\psi_{0}\\psi^{\\dagger}_{2}' - sage: Cl._latex_term( ((0, 0, 0), (0, -2, 5)) ) - '\\omega_{1}^{-2}\\omega_{2}^{5}' - sage: Cl._latex_term( ((0, 0, 0), (0, 0, 0)) ) - '1' - - sage: latex(Cl(5)) - 5 - """ - p, v = m - rp = ''.join('\\psi_{%s}'%i if p[i] > 0 else '\\psi^{\\dagger}_{%s}'%i - for i in range(self._n) if p[i] != 0) - gen_str = lambda e: '' if e == 1 else '^{%s}'%e - rv = ''.join('\\omega_{%s}'%i + gen_str(v[i]) - for i in range(self._n) if v[i] != 0) - if not rp and not rv: - return '1' - return rp + rv - def q(self): r""" Return the `q` of ``self``. @@ -321,8 +263,8 @@ def dimension(self): sage: Cl.dimension() 512 - sage: Cl = algebras.QuantumClifford(4, 2) - sage: Cl.dimension() + sage: Cl = algebras.QuantumClifford(4, 2) # long time + sage: Cl.dimension() # long time 65536 """ return ZZ(8*self._k) ** self._n @@ -382,6 +324,120 @@ def one_basis(self): """ return (self._psi([0]*self._n), (0,)*self._n) +class QuantumCliffordAlgebraGeneric(QuantumCliffordAlgebra): + r""" + The quantum Clifford algebra when `q^{2k} \neq 1`. + + The *quantum Clifford algebra*, or `q`-Clifford algebra, + of rank `n` and twist `k` is the unital associative algebra + `\mathrm{Cl}_{q}(n, k)` over a field `F` with generators + `\psi_a, \psi_a^*, \omega_a` for `a = 1, \ldots, n` that + satisfy the following relations: + + .. MATH:: + + \begin{aligned} + \omega_a \omega_b & = \omega_b \omega_a, + & \omega_a^{4k} & = (1 + q^{-2k}) \omega_a^{2k} - q^{-2k}, + \\ \omega_a \psi_b & = q^{\delta_{ab}} \psi_b \omega_a, + & \omega_a \psi^*_b & = \psi^*_b \omega_a, + \\ \psi_a \psi_b & + \psi_b \psi_a = 0, + & \psi^*_a \psi^*_b & + \psi^*_b \psi^*_a = 0, + \\ \psi_a \psi^*_a & = \frac{q^k \omega_a^{3k} - q^{-k} \omega_a^k}{q^k - q^{-k}}, + & \psi^*_a \psi_a & = \frac{q^{2k} (\omega_a - \omega_a^{3k})}{q^k - q^{-k}}, + \\ \psi_a \psi^*_b & + \psi_b^* \psi_a = 0 + & & \text{if } a \neq b, + \end{aligned} + + where `q \in F` such that `q^{2k} \neq 1`. + + When `k = 2`, we recover the original definition given by Hayashi in + [Hayashi1990]_. The `k = 1` version was used in [Kwon2014]_. + """ + def __init__(self, n, k, q, F): + r""" + Initialize ``self``. + + TESTS:: + + sage: Cl = algebras.QuantumClifford(1,3) + sage: TestSuite(Cl).run(elements=Cl.basis()) # long time + + sage: Cl = algebras.QuantumClifford(3) + sage: elts = Cl.some_elements() + list(Cl.algebra_generators()) + sage: TestSuite(Cl).run(elements=elts) # long time + + sage: Cl = algebras.QuantumClifford(2, 4) + sage: elts = Cl.some_elements() + list(Cl.algebra_generators()) + sage: TestSuite(Cl).run(elements=elts) # long time + """ + psi = cartesian_product([(-1,0,1)]*n) + indices = [(tuple(p), tuple(w)) + for p in psi + for w in product(*[list(range((4-2*abs(p[i]))*k)) for i in range(n)])] + super().__init__(n, k, q, F, psi, indices) + + def _repr_term(self, m): + r""" + Return a string representation of the basis element indexed by ``m``. + + EXAMPLES:: + + sage: Cl = algebras.QuantumClifford(3, 3) + sage: Cl._repr_term( ((1, 0, -1), (0, 2, 5)) ) + 'psi0*psid2*w1^2*w2^5' + sage: Cl._repr_term( ((1, 0, -1), (0, 0, 0)) ) + 'psi0*psid2' + sage: Cl._repr_term( ((0, 0, 0), (0, 2, 5)) ) + 'w1^2*w2^5' + sage: Cl._repr_term( ((0, 0, 0), (0, 0, 0)) ) + '1' + + sage: Cl(5) + 5 + """ + p, v = m + rp = '*'.join('psi%s'%i if p[i] > 0 else 'psid%s'%i + for i in range(self._n) if p[i] != 0) + gen_str = lambda e: '' if e == 1 else '^%s'%e + rv = '*'.join('w%s'%i + gen_str(v[i]) for i in range(self._n) if v[i] != 0) + if rp: + if rv: + return rp + '*' + rv + return rp + if rv: + return rv + return '1' + + def _latex_term(self, m): + r""" + Return a latex representation for the basis element indexed by ``m``. + + EXAMPLES:: + + sage: Cl = algebras.QuantumClifford(3, 3) + sage: Cl._latex_term( ((1, 0, -1), (0, -2, 5)) ) + '\\psi_{0}\\psi^{\\dagger}_{2}\\omega_{1}^{-2}\\omega_{2}^{5}' + sage: Cl._latex_term( ((1, 0, -1), (0, 0, 0)) ) + '\\psi_{0}\\psi^{\\dagger}_{2}' + sage: Cl._latex_term( ((0, 0, 0), (0, -2, 5)) ) + '\\omega_{1}^{-2}\\omega_{2}^{5}' + sage: Cl._latex_term( ((0, 0, 0), (0, 0, 0)) ) + '1' + + sage: latex(Cl(5)) + 5 + """ + p, v = m + rp = ''.join('\\psi_{%s}'%i if p[i] > 0 else '\\psi^{\\dagger}_{%s}'%i + for i in range(self._n) if p[i] != 0) + gen_str = lambda e: '' if e == 1 else '^{%s}'%e + rv = ''.join('\\omega_{%s}'%i + gen_str(v[i]) + for i in range(self._n) if v[i] != 0) + if not rp and not rv: + return '1' + return rp + rv + @cached_method def product_on_basis(self, m1, m2): r""" @@ -407,6 +463,7 @@ def product_on_basis(self, m1, m2): """ p1, w1 = m1 p2, w2 = m2 + # Check for \psi_i^2 == 0 and for the dagger version if any(p1[i] != 0 and p1[i] == p2[i] for i in range(self._n)): return self.zero() @@ -486,9 +543,9 @@ def inverse(self): EXAMPLES:: - sage: Cl = algebras.QuantumClifford(3) + sage: Cl = algebras.QuantumClifford(2) sage: Cl.inject_variables() - Defining psi0, psi1, psi2, psid0, psid1, psid2, w0, w1, w2 + Defining psi0, psi1, psid0, psid1, w0, w1 sage: w0^-1 (q^2+1)*w0 - q^2*w0^3 sage: w0^-1 * w0 @@ -508,14 +565,23 @@ def inverse(self): sage: w * w^-1 1 + sage: (2*w0)^-1 + ((q^2+1)/2)*w0 - q^2/2*w0^3 + sage: (w0 + w1)^-1 Traceback (most recent call last): ... - NotImplementedError: inverse only implemented for basis elements + ValueError: cannot invert self (= w1 + w0) sage: (psi0 * w0)^-1 Traceback (most recent call last): ... - NotImplementedError: inverse only implemented for product of w generators + ValueError: cannot invert self (= psi0*w0) + + sage: Cl = algebras.QuantumClifford(1, 2) + sage: Cl.inject_variables() + Defining psi0, psid0, w0 + sage: (psi0 + psid0).inverse() + psid0*w0^2 + q^2*psi0*w0^2 sage: Cl = algebras.QuantumClifford(2, 2) sage: Cl.inject_variables() @@ -528,12 +594,11 @@ def inverse(self): if not self: raise ZeroDivisionError if len(self) != 1: - raise NotImplementedError("inverse only implemented for basis elements") + return super().__invert__() Cl = self.parent() - p, w = self.support_of_term() + ((p, w), coeff), = list(self._monomial_coefficients.items()) if any(p[i] != 0 for i in range(Cl._n)): - raise NotImplementedError("inverse only implemented for" - " product of w generators") + return super().__invert__() poly = Cl._w_poly.monomial(*w) wp = Cl._w_poly.gens() q = Cl._q @@ -543,7 +608,348 @@ def inverse(self): poly = poly.reduce([wi**(4*k) - (1 + q**(-2*k)) * wi**(2*k) + q**(-2*k) for wi in wp]) pdict = poly.dict() - ret = {(p, tuple(e)): c for e, c in pdict.items()} - return Cl._from_dict(ret) + coeff = coeff.inverse_of_unit() + ret = {(p, tuple(e)): coeff * c for e, c in pdict.items()} + return Cl.element_class(Cl, ret) + + __invert__ = inverse + +class QuantumCliffordAlgebraRootUnity(QuantumCliffordAlgebra): + r""" + The quantum Clifford algebra when `q^{2k} = 1`. + + The *quantum Clifford algebra*, or `q`-Clifford algebra, + of rank `n` and twist `k` is the unital associative algebra + `\mathrm{Cl}_{q}(n, k)` over a field `F` with generators + `\psi_a, \psi_a^*, \omega_a` for `a = 1, \ldots, n` that + satisfy the following relations: + + .. MATH:: + + \begin{aligned} + \omega_a \omega_b & = \omega_b \omega_a, + & \omega_a^{2k} & = 1, + \\ \omega_a \psi_b & = q^{\delta_{ab}} \psi_b \omega_a, + & \omega_a \psi^*_b & = \psi^*_b \omega_a, + \\ \psi_a \psi_b & + \psi_b \psi_a = 0, + & \psi^*_a \psi^*_b & + \psi^*_b \psi^*_a = 0, + \\ \psi_a \psi^*_a & + q^k \psi^*_a \psi_a = \omega_a^k + & \psi_a \psi^*_b & + \psi_b^* \psi_a = 0 \quad (a \neq b), + \end{aligned} + + where `q \in F` such that `q^{2k} = 1`. This has further relations of + + .. MATH:: + + \begin{aligned} + \psi^*_a \psi_a \psi^*_a & = \psi^*_a \omega_a^k, + \\ + \psi_a \psi^*_a \psi_a & = q^k \psi_a \omega_a^k, + \\ + (\psi_a \psi^*_a)^2 & = \psi_a \psi^*_a \omega_a^k. + \end{aligned} + """ + def __init__(self, n, k, q, F): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: Cl = algebras.QuantumClifford(1,2,-1) + sage: TestSuite(Cl).run(elements=Cl.basis()) + + sage: z = CyclotomicField(3).gen() + sage: Cl = algebras.QuantumClifford(1,3,z) + sage: TestSuite(Cl).run(elements=Cl.basis()) + + sage: Cl = algebras.QuantumClifford(3,1,-1) + sage: elts = Cl.some_elements() + list(Cl.algebra_generators()) + sage: TestSuite(Cl).run(elements=elts) # long time + + sage: Cl = algebras.QuantumClifford(2,4,-1) + sage: elts = Cl.some_elements() + list(Cl.algebra_generators()) + sage: TestSuite(Cl).run(elements=elts) # long time + """ + psi = cartesian_product([(-1,0,1,2)]*n) + indices = [(tuple(p), tuple(w)) + for p in psi + for w in product(list(range(2*k)), repeat=n)] + super().__init__(n, k, q, F, psi, indices) + + def _repr_term(self, m): + r""" + Return a string representation of the basis element indexed by ``m``. + + EXAMPLES:: + + sage: Cl = algebras.QuantumClifford(3, 3, -1) + sage: Cl._repr_term( ((1, 0, -1), (0, 2, 5)) ) + 'psi0*psid2*w1^2*w2^5' + sage: Cl._repr_term( ((1, 0, 2), (0, 0, 0)) ) + 'psi0*psi2*psid2' + sage: Cl._repr_term( ((0, 0, 0), (0, 2, 5)) ) + 'w1^2*w2^5' + sage: Cl._repr_term( ((0, 0, 0), (0, 0, 0)) ) + '1' + + sage: Cl(5) + 5 + """ + p, v = m + + def ppr(i): + val = p[i] + if val == -1: + return 'psid%s'%i + elif val == 1: + return 'psi%s'%i + elif val == 2: + return 'psi%s*psid%s'%(i,i) + + rp = '*'.join(ppr(i) for i in range(self._n) if p[i] != 0) + gen_str = lambda e: '' if e == 1 else '^%s'%e + rv = '*'.join('w%s'%i + gen_str(v[i]) for i in range(self._n) if v[i] != 0) + if rp: + if rv: + return rp + '*' + rv + return rp + if rv: + return rv + return '1' + + def _latex_term(self, m): + r""" + Return a latex representation for the basis element indexed by ``m``. + + EXAMPLES:: + + sage: Cl = algebras.QuantumClifford(3, 3, -1) + sage: Cl._latex_term( ((1, 0, -1), (0, 2, 5)) ) + '\\psi_{0}\\psi^{\\dagger}_{2}\\omega_{1}^{2}\\omega_{2}^{5}' + sage: Cl._latex_term( ((1, 0, 2), (0, 0, 0)) ) + '\\psi_{0}\\psi_{2}\\psi^{\\dagger}_{2}' + sage: Cl._latex_term( ((0, 0, 0), (0, 2, 5)) ) + '\\omega_{1}^{2}\\omega_{2}^{5}' + sage: Cl._latex_term( ((0, 0, 0), (0, 0, 0)) ) + '1' + + sage: latex(Cl(5)) + 5 + """ + p, v = m + + def ppr(i): + val = p[i] + if val == -1: + return '\\psi^{\\dagger}_{%s}'%i + elif val == 1: + return '\\psi_{%s}'%i + elif val == 2: + return '\\psi_{%s}\\psi^{\\dagger}_{%s}' % (i, i) + + rp = ''.join(ppr(i) for i in range(self._n) if p[i] != 0) + gen_str = lambda e: '' if e == 1 else '^{%s}'%e + rv = ''.join('\\omega_{%s}'%i + gen_str(v[i]) + for i in range(self._n) if v[i] != 0) + if not rp and not rv: + return '1' + return rp + rv + + @cached_method + def product_on_basis(self, m1, m2): + r""" + Return the product of the basis elements indexed by ``m1`` and ``m2``. + + EXAMPLES:: + + sage: z = CyclotomicField(3).gen() + sage: Cl = algebras.QuantumClifford(3, 3, z) + sage: Cl.inject_variables() + Defining psi0, psi1, psi2, psid0, psid1, psid2, w0, w1, w2 + sage: psi0^2 # indirect doctest + 0 + sage: psid0^2 + 0 + sage: w0 * psi0 + -(-zeta3)*psi0*w0 + sage: w0 * psid0 + -(zeta3+1)*psid0*w0 + sage: psi0 * psid0 + psi0*psid0 + sage: psid0 * psi0 + w0^3 - psi0*psid0 + sage: w2 * w0 + w0*w2 + sage: w0^6 + 1 + sage: psi0 * psi1 + psi0*psi1 + sage: psi1 * psi0 + -psi0*psi1 + sage: psi1 * (psi0 * psi2) + -psi0*psi1*psi2 + + sage: z = CyclotomicField(6).gen() + sage: Cl = algebras.QuantumClifford(3, 3, z) + sage: Cl.inject_variables() + Defining psi0, psi1, psi2, psid0, psid1, psid2, w0, w1, w2 + + sage: psid1 * (psi1 * psid1) + psid1*w1^3 + sage: (psi1* psid1) * (psi1 * psid1) + psi1*psid1*w1^3 + sage: (psi1 * psid1) * psi1 + -psi1*w1^3 + """ + p1, w1 = m1 + p2, w2 = m2 + k = self._k + tk = 2 * k + + # \psi_i is represented by a 1 in p1[i] and p2[i] + # \psi_i^{\dagger} is represented by a -1 in p1[i] and p2[i] + # \psi_i \psi_i^{\dagger} is a 2 in p1[i] and p2[i] + + # Check for \psi_i^2 == 0 and for the dagger version + if any((p1[i] % 2 != 0 and p1[i] == p2[i]) + or (p1[i] == 2 and p2[i] == -1) or (p2[i] == 2 and p1[i] == 1) + for i in range(self._n)): + return self.zero() + + # Reduce any v_i^{2k} = 1 + v = [(w1[i] + w2[i]) % tk for i in range(self._n)] + + q_power = 0 + sign = 1 + pairings = [] + p = [0] * self._n + # Move w1 * p2 to q^q_power * p2 * w1 + # Also find pairs \psi_i \psi_i^{\dagger} (or vice versa) and + # count the sign from moving \psi_i and \psi_i^{\dagger} into position + num_cross = 0 + total_cross = 0 + for i in range(self._n): + num_cross += p2[i] + if p2[i] == 2: + # By the above check, we cannot have p1[i] == 1 + if p1[i] != 0: + v[i] = (v[i] + k) % tk + p[i] = p1[i] + else: + p[i] = p2[i] + elif p2[i] != 0: # == +1, -1 + q_power += w1[i] * p2[i] + # By the above check, we cannot have p1[i] == p2[i] + if p1[i] == -1: + pairings.append(i) + total_cross -= 1 # correction + p[i] = None + elif p1[i] == 1: + total_cross -= 1 # correction + p[i] = 2 + elif p1[i] == 2: + q_power += k + v[i] = (v[i] + k) % tk + p[i] = p2[i] + else: + p[i] = p2[i] + else: + p[i] = p1[i] # since p2[i] == 0 + + if abs(p1[i]) == 1: + total_cross += num_cross + + # total_cross does not need to actually be the total number of crossings; just correct mod 2 + if total_cross % 2: + sign = -sign + + # Replace \psi_i^{\dagger} \psi_i = q^k ( w^k - \psi_i \psi_i^{\dagger} ) + def key(X): + e = list(v) # Make a copy + for i in pairings: + if i in X: + p[i] = 2 + else: + p[i] = 0 + e[i] = (e[i] + k) % tk + return (self._psi(p), tuple(e)) + + q = self._q + ret = {key(X): (-1)**len(X) * sign * q**(q_power+k*(len(pairings)%2)) + for X in powerset(pairings)} + + return self._from_dict(ret) + + class Element(QuantumCliffordAlgebra.Element): + def inverse(self): + r""" + Return the inverse if ``self`` is a basis element. + + EXAMPLES:: + + sage: Cl = algebras.QuantumClifford(3, 3, -1) + sage: Cl.inject_variables() + Defining psi0, psi1, psi2, psid0, psid1, psid2, w0, w1, w2 + sage: w0^-1 + w0^5 + sage: w0^-1 * w0 + 1 + sage: w0^-2 + w0^4 + sage: w0^-2 * w0^2 + 1 + sage: w0^-2 * w0 == w0^-1 + True + sage: w = w0 * w1^3 + sage: w^-1 + w0^5*w1^3 + sage: w^-1 * w + 1 + sage: w * w^-1 + 1 + + sage: (2*w0)^-1 + 1/2*w0^5 + + sage: Cl = algebras.QuantumClifford(3, 1, -1) + sage: Cl.inject_variables() + Defining psi0, psi1, psi2, psid0, psid1, psid2, w0, w1, w2 + + sage: (w0 + w1)^-1 + Traceback (most recent call last): + ... + ValueError: cannot invert self (= w1 + w0) + sage: (psi0 * w0)^-1 + Traceback (most recent call last): + ... + ValueError: cannot invert self (= psi0*w0) + + sage: z = CyclotomicField(6).gen() + sage: Cl = algebras.QuantumClifford(1, 3, z) + sage: Cl.inject_variables() + Defining psi0, psid0, w0 + sage: (psi0 + psid0).inverse() + psid0*w0^3 - psi0*w0^3 + + sage: Cl = algebras.QuantumClifford(2, 2, -1) + sage: Cl.inject_variables() + Defining psi0, psi1, psid0, psid1, w0, w1 + sage: w0^-1 + w0^3 + sage: w0 * w0^-1 + 1 + """ + if not self: + raise ZeroDivisionError + if len(self) != 1: + return super().__invert__() + Cl = self.parent() + ((p, w), coeff), = list(self._monomial_coefficients.items()) + if any(p[i] != 0 for i in range(Cl._n)): + return super().__invert__() + tk = 2 * Cl._k + w = tuple([tk-val if val else 0 for val in w]) + return Cl.element_class(Cl, {(p, w) : coeff.inverse_of_unit()}) __invert__ = inverse + diff --git a/src/sage/algebras/quantum_groups/fock_space.py b/src/sage/algebras/quantum_groups/fock_space.py index dea28fd1760..3352f143a7c 100644 --- a/src/sage/algebras/quantum_groups/fock_space.py +++ b/src/sage/algebras/quantum_groups/fock_space.py @@ -1354,9 +1354,8 @@ def _G_to_fock_basis(self, la): return fock.sum_of_terms((fock._indices([[]]*k + list(pt)), c) for pt,c in cur) cur = R.A()._A_to_fock_basis(la) - s = cur.support() - s.sort() # Sort lex, which respects dominance order - s.pop() # Remove the largest + s = sorted(cur.support()) # Sort lex, which respects dominance order + s.pop() # Remove the largest q = R._q while s: @@ -2189,9 +2188,8 @@ def add_cols(nu): # Perform the triangular reduction cur = self.realization_of().A(algorithm)._A_to_fock_basis(la) - s = cur.support() - s.sort() # Sort lex, which respects dominance order - s.pop() # Remove the largest + s = sorted(cur.support()) # Sort lex, which respects dominance order + s.pop() # Remove the largest q = self.realization_of()._q while s: diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 3bde7b21536..45a4d512c0a 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1232,7 +1232,7 @@ def modp_splitting_data(self, p): raise NotImplementedError("algorithm for computing local splittings not implemented in general (currently require the first invariant to be coprime to p)") i2inv = ~i2 a = None - for b in list(F): + for b in F: if not b: continue c = j2 + i2inv * b*b @@ -2690,15 +2690,13 @@ def multiply_by_conjugate(self, J): R = self.quaternion_algebra() return R.ideal(basis, check=False) - def is_equivalent(I, J, B=10): + def is_equivalent(self, J, B=10) -> bool: """ - Return ``True`` if ``I`` and ``J`` are equivalent as right ideals. + Return ``True`` if ``self`` and ``J`` are equivalent as right ideals. INPUT: - - ``I`` -- a fractional quaternion ideal (self) - - - ``J`` -- a fractional quaternion ideal with same order as ``I`` + - ``J`` -- a fractional quaternion ideal with same order as ``self`` - ``B`` -- a bound to compute and compare theta series before doing the full equivalence test @@ -2718,15 +2716,16 @@ def is_equivalent(I, J, B=10): sage: R[0].is_equivalent(S) True """ - if not isinstance(I, QuaternionFractionalIdeal_rational): + # shorthand: let I be self + if not isinstance(self, QuaternionFractionalIdeal_rational): return False - if I.right_order() != J.right_order(): - raise ValueError("I and J must be right ideals") + if self.right_order() != J.right_order(): + raise ValueError("self and J must be right ideals") # Just test theta series first. If the theta series are # different, the ideals are definitely not equivalent. - if B > 0 and I.theta_series_vector(B) != J.theta_series_vector(B): + if B > 0 and self.theta_series_vector(B) != J.theta_series_vector(B): return False # The theta series are the same, so perhaps the ideals are @@ -2734,7 +2733,7 @@ def is_equivalent(I, J, B=10): # 1. Compute I * Jbar # see Prop. 1.17 in Pizer. Note that we use IJbar instead of # JbarI since we work with right ideals - IJbar = I.multiply_by_conjugate(J) + IJbar = self.multiply_by_conjugate(J) # 2. Determine if there is alpha in K such # that N(alpha) = N(I)*N(J) as explained by Pizer. diff --git a/src/sage/algebras/schur_algebra.py b/src/sage/algebras/schur_algebra.py index ba606ded999..b716db0ce94 100644 --- a/src/sage/algebras/schur_algebra.py +++ b/src/sage/algebras/schur_algebra.py @@ -454,6 +454,19 @@ def _repr_(self): msg += " over {}" return msg.format(self._r, self._n, self.base_ring()) + def construction(self): + """ + Return ``None``. + + There is no functorial construction for ``self``. + + EXAMPLES:: + + sage: T = SchurTensorModule(QQ, 2, 3) + sage: T.construction() + """ + return None + def _monomial_product(self, xi, v): """ Result of acting by the basis element ``xi`` of the corresponding @@ -581,11 +594,11 @@ def GL_irreducible_character(n, mu, KK): A = M._schur SGA = M._sga - #make ST the superstandard tableau of shape mu + # make ST the superstandard tableau of shape mu from sage.combinat.tableau import from_shape_and_word ST = from_shape_and_word(mu, list(range(1, r + 1)), convention='English') - #make ell the reading word of the highest weight tableau of shape mu + # make ell the reading word of the highest weight tableau of shape mu ell = [i + 1 for i, l in enumerate(mu) for dummy in range(l)] e = M.basis()[tuple(ell)] # the element e_l @@ -607,17 +620,17 @@ def GL_irreducible_character(n, mu, KK): y = A.basis()[schur_rep] * e # M.action_by_Schur_alg(A.basis()[schur_rep], e) carter_lusztig.append(y.to_vector()) - #Therefore, we now have carter_lusztig as a list giving the basis - #of `V_\mu` + # Therefore, we now have carter_lusztig as a list giving the basis + # of `V_\mu` - #We want to think of expressing this character as a sum of monomial - #symmetric functions. + # We want to think of expressing this character as a sum of monomial + # symmetric functions. - #We will determine a basis element for each m_\lambda in the - #character, and we want to keep track of them by \lambda. + # We will determine a basis element for each m_\lambda in the + # character, and we want to keep track of them by \lambda. - #That means that we only want to pick out the basis elements above for - #those semistandard words whose content is a partition. + # That means that we only want to pick out the basis elements above for + # those semistandard words whose content is a partition. contents = Partitions(r, max_length=n).list() # all partitions of r, length at most n @@ -648,15 +661,15 @@ def GL_irreducible_character(n, mu, KK): except ValueError: pass - #There is an inner product on the Carter-Lusztig module V_\mu; its - #maximal submodule is exactly the kernel of the inner product. + # There is an inner product on the Carter-Lusztig module V_\mu; its + # maximal submodule is exactly the kernel of the inner product. - #Now, for each possible partition content, we look at the graded piece of - #that degree, and we record how these elements pair with each of the - #elements of carter_lusztig. + # Now, for each possible partition content, we look at the graded piece of + # that degree, and we record how these elements pair with each of the + # elements of carter_lusztig. - #The kernel of this pairing is the part of this graded piece which is - #not in the irreducible module for \mu. + # The kernel of this pairing is the part of this graded piece which is + # not in the irreducible module for \mu. length = len(carter_lusztig) diff --git a/src/sage/algebras/shuffle_algebra.py b/src/sage/algebras/shuffle_algebra.py index 7ef009fd61f..26c8a40f485 100644 --- a/src/sage/algebras/shuffle_algebra.py +++ b/src/sage/algebras/shuffle_algebra.py @@ -58,10 +58,10 @@ class ShuffleAlgebra(CombinatorialFreeModule): Shuffle Algebra on 3 generators ['x', 'y', 'z'] over Rational Field sage: mul(F.gens()) - B[word: xyz] + B[word: xzy] + B[word: yxz] + B[word: yzx] + B[word: zxy] + B[word: zyx] + B[xyz] + B[xzy] + B[yxz] + B[yzx] + B[zxy] + B[zyx] sage: mul([ F.gen(i) for i in range(2) ]) + mul([ F.gen(i+1) for i in range(2) ]) - B[word: xy] + B[word: yx] + B[word: yz] + B[word: zy] + B[xy] + B[yx] + B[yz] + B[zy] sage: S = ShuffleAlgebra(ZZ, 'abcabc'); S Shuffle Algebra on 3 generators ['a', 'b', 'c'] over Integer Ring @@ -84,11 +84,11 @@ class ShuffleAlgebra(CombinatorialFreeModule): sage: L.is_commutative() True sage: s = a*b^2 * c^3; s - (12*B[word:abb]+12*B[word:bab]+12*B[word:bba])*B[word: ccc] + (12*B[abb]+12*B[bab]+12*B[bba])*B[ccc] sage: parent(s) Shuffle Algebra on 2 generators ['c', 'd'] over Shuffle Algebra on 2 generators ['a', 'b'] over Rational Field sage: c^3 * a * b^2 - (12*B[word:abb]+12*B[word:bab]+12*B[word:bba])*B[word: ccc] + (12*B[abb]+12*B[bab]+12*B[bba])*B[ccc] Shuffle algebras are commutative:: @@ -101,11 +101,11 @@ class ShuffleAlgebra(CombinatorialFreeModule): sage: F = ShuffleAlgebra(QQ, 'abc') sage: B = F.basis() sage: B[Word('bb')] * B[Word('ca')] - B[word: bbca] + B[word: bcab] + B[word: bcba] + B[word: cabb] - + B[word: cbab] + B[word: cbba] + B[bbca] + B[bcab] + B[bcba] + B[cabb] + + B[cbab] + B[cbba] sage: 1 - B[Word('bb')] * B[Word('ca')] / 2 - B[word: ] - 1/2*B[word: bbca] - 1/2*B[word: bcab] - 1/2*B[word: bcba] - - 1/2*B[word: cabb] - 1/2*B[word: cbab] - 1/2*B[word: cbba] + B[] - 1/2*B[bbca] - 1/2*B[bcab] - 1/2*B[bcba] + - 1/2*B[cabb] - 1/2*B[cbab] - 1/2*B[cbba] TESTS:: @@ -123,7 +123,7 @@ class ShuffleAlgebra(CombinatorialFreeModule): sage: W = A.basis().keys() sage: x = A(W([0,1,0])) sage: A_d(x) - -2*S[word: 001] + S[word: 010] + -2*S[001] + S[010] """ @staticmethod def __classcall_private__(cls, R, names, prefix=None): @@ -163,7 +163,7 @@ def __init__(self, R, names, prefix): sage: F = ShuffleAlgebra(QQ, 'xyz', prefix='f'); F Shuffle Algebra on 3 generators ['x', 'y', 'z'] over Rational Field sage: F.gens() - Family (f[word: x], f[word: y], f[word: z]) + Family (f[x], f[y], f[z]) """ if R not in Rings(): raise TypeError("argument R must be a ring") @@ -186,6 +186,18 @@ def variable_names(self): """ return self._alphabet + def _repr_term(self, t): + """ + Return a string representation of the basis element indexed by ``t``. + + EXAMPLES:: + + sage: R = ShuffleAlgebra(QQ,'xyz') + sage: R._repr_term(R._indices('xyzxxy')) + 'B[xyzxxy]' + """ + return "{!s}[{!s}]".format(self._print_options['prefix'], repr(t)[6:]) + def _repr_(self): r""" Text representation of this shuffle algebra. @@ -218,7 +230,7 @@ def one_basis(self): sage: A.one_basis() word: sage: A.one() - B[word: ] + B[] """ return self.basis().keys()([]) @@ -236,20 +248,20 @@ def product_on_basis(self, w1, w2): sage: A = ShuffleAlgebra(QQ,'abc') sage: W = A.basis().keys() sage: A.product_on_basis(W("acb"), W("cba")) - B[word: acbacb] + B[word: acbcab] + 2*B[word: acbcba] - + 2*B[word: accbab] + 4*B[word: accbba] + B[word: cabacb] - + B[word: cabcab] + B[word: cabcba] + B[word: cacbab] - + 2*B[word: cacbba] + 2*B[word: cbaacb] + B[word: cbacab] - + B[word: cbacba] + B[acbacb] + B[acbcab] + 2*B[acbcba] + + 2*B[accbab] + 4*B[accbba] + B[cabacb] + + B[cabcab] + B[cabcba] + B[cacbab] + + 2*B[cacbba] + 2*B[cbaacb] + B[cbacab] + + B[cbacba] sage: (a,b,c) = A.algebra_generators() sage: a * (1-b)^2 * c - 2*B[word: abbc] - 2*B[word: abc] + 2*B[word: abcb] + B[word: ac] - - 2*B[word: acb] + 2*B[word: acbb] + 2*B[word: babc] - - 2*B[word: bac] + 2*B[word: bacb] + 2*B[word: bbac] - + 2*B[word: bbca] - 2*B[word: bca] + 2*B[word: bcab] - + 2*B[word: bcba] + B[word: ca] - 2*B[word: cab] + 2*B[word: cabb] - - 2*B[word: cba] + 2*B[word: cbab] + 2*B[word: cbba] + 2*B[abbc] - 2*B[abc] + 2*B[abcb] + B[ac] + - 2*B[acb] + 2*B[acbb] + 2*B[babc] + - 2*B[bac] + 2*B[bacb] + 2*B[bbac] + + 2*B[bbca] - 2*B[bca] + 2*B[bcab] + + 2*B[bcba] + B[ca] - 2*B[cab] + 2*B[cabb] + - 2*B[cba] + 2*B[cbab] + 2*B[cbba] """ return sum(self.basis()[u] for u in w1.shuffle(w2)) @@ -262,7 +274,7 @@ def antipode_on_basis(self, w): sage: A = ShuffleAlgebra(QQ,'abc') sage: W = A.basis().keys() sage: A.antipode_on_basis(W("acb")) - -B[word: bca] + -B[bca] """ mone = -self.base_ring().one() return self.term(w.reversal(), mone**len(w)) @@ -279,7 +291,7 @@ def gen(self, i): sage: F = ShuffleAlgebra(ZZ,'xyz') sage: F.gen(0) - B[word: x] + B[x] sage: F.gen(4) Traceback (most recent call last): @@ -299,7 +311,7 @@ def some_elements(self): sage: F = ShuffleAlgebra(ZZ,'xyz') sage: F.some_elements() - [0, B[word: ], B[word: x], B[word: y], B[word: z], B[word: xz] + B[word: zx]] + [0, B[], B[x], B[y], B[z], B[xz] + B[zx]] """ gens = list(self.algebra_generators()) if gens: @@ -321,26 +333,26 @@ def coproduct_on_basis(self, w): sage: F = ShuffleAlgebra(QQ,'ab') sage: F.coproduct_on_basis(Word('a')) - B[word: ] # B[word: a] + B[word: a] # B[word: ] + B[] # B[a] + B[a] # B[] sage: F.coproduct_on_basis(Word('aba')) - B[word: ] # B[word: aba] + B[word: a] # B[word: ba] - + B[word: ab] # B[word: a] + B[word: aba] # B[word: ] + B[] # B[aba] + B[a] # B[ba] + + B[ab] # B[a] + B[aba] # B[] sage: F.coproduct_on_basis(Word()) - B[word: ] # B[word: ] + B[] # B[] TESTS:: sage: F = ShuffleAlgebra(QQ,'ab') sage: S = F.an_element(); S - B[word: ] + 2*B[word: a] + 3*B[word: b] + B[word: bab] + B[] + 2*B[a] + 3*B[b] + B[bab] sage: F.coproduct(S) - B[word: ] # B[word: ] + 2*B[word: ] # B[word: a] - + 3*B[word: ] # B[word: b] + B[word: ] # B[word: bab] - + 2*B[word: a] # B[word: ] + 3*B[word: b] # B[word: ] - + B[word: b] # B[word: ab] + B[word: ba] # B[word: b] - + B[word: bab] # B[word: ] + B[] # B[] + 2*B[] # B[a] + + 3*B[] # B[b] + B[] # B[bab] + + 2*B[a] # B[] + 3*B[b] # B[] + + B[b] # B[ab] + B[ba] # B[b] + + B[bab] # B[] sage: F.coproduct(F.one()) - B[word: ] # B[word: ] + B[] # B[] """ TS = self.tensor_square() return TS.sum_of_monomials((w[:i], w[i:]) for i in range(len(w) + 1)) @@ -353,7 +365,7 @@ def counit(self, S): sage: F = ShuffleAlgebra(QQ,'ab') sage: S = F.an_element(); S - B[word: ] + 2*B[word: a] + 3*B[word: b] + B[word: bab] + B[] + 2*B[a] + 3*B[b] + B[bab] sage: F.counit(S) 1 """ @@ -382,17 +394,17 @@ def algebra_generators(self): sage: A = ShuffleAlgebra(ZZ,'fgh'); A Shuffle Algebra on 3 generators ['f', 'g', 'h'] over Integer Ring sage: A.algebra_generators() - Family (B[word: f], B[word: g], B[word: h]) + Family (B[f], B[g], B[h]) sage: A = ShuffleAlgebra(QQ, ['x1','x2']) sage: A.algebra_generators() - Family (B[word: x1], B[word: x2]) + Family (B[x1], B[x2]) TESTS:: sage: A = ShuffleAlgebra(ZZ,[0,1]) sage: A.algebra_generators() - Family (B[word: 0], B[word: 1]) + Family (B[0], B[1]) """ Words = self.basis().keys() return Family([self.monomial(Words([a])) for a in self._alphabet]) @@ -411,13 +423,13 @@ def _element_constructor_(self, x): sage: R = ShuffleAlgebra(QQ,'xy') sage: x, y = R.gens() sage: R(3) - 3*B[word: ] + 3*B[] sage: R(x) - B[word: x] + B[x] sage: R('xyy') - B[word: xyy] + B[xyy] sage: R(Word('xyx')) - B[word: xyx] + B[xyx] """ if isinstance(x, (str, FiniteWord_class)): W = self.basis().keys() @@ -464,13 +476,13 @@ def _coerce_map_from_(self, R): sage: x, y, z = F.gens() sage: F.coerce(x*y) # indirect doctest - B[word: xy] + B[word: yx] + B[xy] + B[yx] Elements of the integers coerce in, since there is a coerce map from `\ZZ` to GF(7):: sage: F.coerce(1) # indirect doctest - B[word: ] + B[] There is no coerce map from `\QQ` to `\GF{7}`:: @@ -482,7 +494,7 @@ def _coerce_map_from_(self, R): Elements of the base ring coerce in:: sage: F.coerce(GF(7)(5)) - 5*B[word: ] + 5*B[] The shuffle algebra over `\ZZ` on `x, y, z` coerces in, since `\ZZ` coerces to `\GF{7}`:: @@ -490,7 +502,7 @@ def _coerce_map_from_(self, R): sage: G = ShuffleAlgebra(ZZ,'xyz') sage: Gx,Gy,Gz = G.gens() sage: z = F.coerce(Gx**2 * Gy);z - 2*B[word: xxy] + 2*B[word: xyx] + 2*B[word: yxx] + 2*B[xxy] + 2*B[xyx] + 2*B[yxx] sage: z.parent() is F True @@ -561,20 +573,20 @@ def to_dual_pbw_element(self, w): sage: A = ShuffleAlgebra(QQ, 'ab') sage: f = 2 * A(Word()) + A(Word('ab')); f - 2*B[word: ] + B[word: ab] + 2*B[] + B[ab] sage: A.to_dual_pbw_element(f) - 2*S[word: ] + S[word: ab] + 2*S[] + S[ab] sage: A.to_dual_pbw_element(A.one()) - S[word: ] + S[] sage: S = A.dual_pbw_basis() sage: elt = S.expansion_on_basis(Word('abba')); elt - 2*B[word: aabb] + B[word: abab] + B[word: abba] + 2*B[aabb] + B[abab] + B[abba] sage: A.to_dual_pbw_element(elt) - S[word: abba] + S[abba] sage: A.to_dual_pbw_element(2*A(Word('aabb')) + A(Word('abab'))) - S[word: abab] + S[abab] sage: S.expansion(S('abab')) - 2*B[word: aabb] + B[word: abab] + 2*B[aabb] + B[abab] """ D = self.dual_pbw_basis() l = {} @@ -628,13 +640,13 @@ class DualPBWBasis(CombinatorialFreeModule): sage: S The dual Poincare-Birkhoff-Witt basis of Shuffle Algebra on 2 generators ['a', 'b'] over Rational Field sage: S.one() - S[word: ] + S[] sage: S.one_basis() word: sage: T = ShuffleAlgebra(QQ, 'abcd').dual_pbw_basis(); T The dual Poincare-Birkhoff-Witt basis of Shuffle Algebra on 4 generators ['a', 'b', 'c', 'd'] over Rational Field sage: T.algebra_generators() - (S[word: a], S[word: b], S[word: c], S[word: d]) + (S[a], S[b], S[c], S[d]) TESTS: @@ -678,6 +690,18 @@ def __init__(self, R, names): CombinatorialFreeModule.__init__(self, R, Words(names), prefix='S', category=cat) + def _repr_term(self, t): + """ + Return a string representation of the basis element indexed by ``t``. + + EXAMPLES:: + + sage: R = ShuffleAlgebra(QQ,'xyz').dual_pbw_basis() + sage: R._repr_term(R._indices('xyzxxy')) + 'S[xyzxxy]' + """ + return "{!s}[{!s}]".format(self._print_options['prefix'], repr(t)[6:]) + def _repr_(self): """ Return a string representation of ``self``. @@ -698,11 +722,11 @@ def _element_constructor_(self, x): sage: A = ShuffleAlgebra(QQ, 'ab') sage: S = A.dual_pbw_basis() sage: S('abaab') - S[word: abaab] + S[abaab] sage: S(Word('aba')) - S[word: aba] + S[aba] sage: S(A('ab')) - S[word: ab] + S[ab] """ if isinstance(x, (str, FiniteWord_class)): W = self.basis().keys() @@ -776,7 +800,7 @@ def algebra_generators(self): sage: S = ShuffleAlgebra(QQ, 'ab').dual_pbw_basis() sage: S.algebra_generators() - (S[word: a], S[word: b]) + (S[a], S[b]) """ W = self.basis().keys() return tuple(self.monomial(W([a])) for a in self._alphabet) @@ -791,9 +815,9 @@ def gen(self, i): sage: S = ShuffleAlgebra(QQ, 'ab').dual_pbw_basis() sage: S.gen(0) - S[word: a] + S[a] sage: S.gen(1) - S[word: b] + S[b] """ return self.algebra_generators()[i] @@ -805,7 +829,7 @@ def some_elements(self): sage: F = ShuffleAlgebra(QQ,'xyz').dual_pbw_basis() sage: F.some_elements() - [0, S[word: ], S[word: x], S[word: y], S[word: z], S[word: zx]] + [0, S[], S[x], S[y], S[z], S[zx]] """ gens = list(self.algebra_generators()) if gens: @@ -833,11 +857,11 @@ def product(self, u, v): sage: S = ShuffleAlgebra(QQ, 'ab').dual_pbw_basis() sage: a,b = S.gens() sage: S.product(a, b) - S[word: ba] + S[ba] sage: S.product(b, a) - S[word: ba] + S[ba] sage: S.product(b^2*a, a*b*a) - 36*S[word: bbbaaa] + 36*S[bbbaaa] TESTS: @@ -848,9 +872,9 @@ def product(self, u, v): sage: S = A.dual_pbw_basis() sage: a,b = S.gens() sage: A(a*b) - B[word: ab] + B[word: ba] + B[ab] + B[ba] sage: A(a*b*a) - 2*B[word: aab] + 2*B[word: aba] + 2*B[word: baa] + 2*B[aab] + 2*B[aba] + 2*B[baa] sage: S(A(a)*A(b)*A(a)) == a*b*a True """ @@ -865,10 +889,10 @@ def antipode(self, elt): sage: A = ShuffleAlgebra(QQ, 'ab') sage: S = A.dual_pbw_basis() sage: w = S('abaab').antipode(); w - S[word: abaab] - 2*S[word: ababa] - S[word: baaba] - + 3*S[word: babaa] - 6*S[word: bbaaa] + S[abaab] - 2*S[ababa] - S[baaba] + + 3*S[babaa] - 6*S[bbaaa] sage: w.antipode() - S[word: abaab] + S[abaab] """ return self(self.expansion(elt).antipode()) @@ -881,11 +905,11 @@ def coproduct(self, elt): sage: A = ShuffleAlgebra(QQ, 'ab') sage: S = A.dual_pbw_basis() sage: S('ab').coproduct() - S[word: ] # S[word: ab] + S[word: a] # S[word: b] - + S[word: ab] # S[word: ] + S[] # S[ab] + S[a] # S[b] + + S[ab] # S[] sage: S('ba').coproduct() - S[word: ] # S[word: ba] + S[word: a] # S[word: b] - + S[word: b] # S[word: a] + S[word: ba] # S[word: ] + S[] # S[ba] + S[a] # S[b] + + S[b] # S[a] + S[ba] # S[] TESTS:: @@ -921,7 +945,7 @@ def expansion(self): sage: S = ShuffleAlgebra(QQ, 'ab').dual_pbw_basis() sage: f = S('ab') + S('aba') sage: S.expansion(f) - 2*B[word: aab] + B[word: ab] + B[word: aba] + 2*B[aab] + B[ab] + B[aba] """ return self.module_morphism(self.expansion_on_basis, codomain=self._alg) @@ -938,15 +962,15 @@ def expansion_on_basis(self, w): sage: S = ShuffleAlgebra(QQ, 'ab').dual_pbw_basis() sage: S.expansion_on_basis(Word()) - B[word: ] + B[] sage: S.expansion_on_basis(Word()).parent() Shuffle Algebra on 2 generators ['a', 'b'] over Rational Field sage: S.expansion_on_basis(Word('abba')) - 2*B[word: aabb] + B[word: abab] + B[word: abba] + 2*B[aabb] + B[abab] + B[abba] sage: S.expansion_on_basis(Word()) - B[word: ] + B[] sage: S.expansion_on_basis(Word('abab')) - 2*B[word: aabb] + B[word: abab] + 2*B[aabb] + B[abab] """ from sage.arith.all import factorial if not w: @@ -981,6 +1005,6 @@ def expand(self): sage: S = ShuffleAlgebra(QQ, 'ab').dual_pbw_basis() sage: f = S('ab') + S('bab') sage: f.expand() - B[word: ab] + 2*B[word: abb] + B[word: bab] + B[ab] + 2*B[abb] + B[bab] """ return self.parent().expansion(self) diff --git a/src/sage/algebras/steenrod/steenrod_algebra.py b/src/sage/algebras/steenrod/steenrod_algebra.py index 9e9ad450358..8feec0d6d98 100644 --- a/src/sage/algebras/steenrod/steenrod_algebra.py +++ b/src/sage/algebras/steenrod/steenrod_algebra.py @@ -402,13 +402,13 @@ (1, (2, 1)) sage: c.monomial_coefficients() == {(2, 1): 1, (5,): 1} True - sage: sorted(c.monomials(), key=lambda x: x.support()) + sage: sorted(c.monomials(), key=lambda x: tuple(x.support())) [Sq(2,1), Sq(5)] sage: sorted(c.support()) [(2, 1), (5,)] sage: Adem = SteenrodAlgebra(basis='adem') sage: elt = Adem.Sq(10) + Adem.Sq(9) * Adem.Sq(1) - sage: sorted(elt.monomials(), key=lambda x: x.support()) + sage: sorted(elt.monomials(), key=lambda x: tuple(x.support())) [Sq^9 Sq^1, Sq^10] sage: A7 = SteenrodAlgebra(p=7) @@ -3100,7 +3100,7 @@ class Element(CombinatorialFreeModule.Element): (1, (2, 1)) sage: c.monomial_coefficients() == {(2, 1): 1, (5,): 1} True - sage: sorted(c.monomials(), key=lambda x: x.support()) + sage: sorted(c.monomials(), key=lambda x: tuple(x.support())) [Sq(2,1), Sq(5)] sage: sorted(c.support()) [(2, 1), (5,)] @@ -3458,7 +3458,7 @@ def excess(self): sage: (Sq(0,0,1) + Sq(4,1) + Sq(7)).excess() 1 sage: elt = Sq(0,0,1) + Sq(4,1) + Sq(7) - sage: M = sorted(elt.monomials(), key=lambda x: x.support()) + sage: M = sorted(elt.monomials(), key=lambda x: tuple(x.support())) sage: [m.excess() for m in M] [1, 5, 7] sage: [m for m in M] diff --git a/src/sage/algebras/steenrod/steenrod_algebra_bases.py b/src/sage/algebras/steenrod/steenrod_algebra_bases.py index 34be27e2d58..4cfbe2f83e4 100644 --- a/src/sage/algebras/steenrod/steenrod_algebra_bases.py +++ b/src/sage/algebras/steenrod/steenrod_algebra_bases.py @@ -629,7 +629,7 @@ def milnor_basis(n, p=2, **kwds): # with distinct parts. for sigma in restricted_partitions(deg, q_degrees_decrease, - no_repeats = True): + no_repeats=True): index = 0 q_mono = [] for q in q_degrees: @@ -713,7 +713,7 @@ def serre_cartan_basis(n, p=2, bound=1, **kwds): # This means that 2 last <= n - last, or 3 last <= n. result = [(n,)] for last in range(bound, 1+n//3): - for vec in serre_cartan_basis(n - last, bound = 2*last): + for vec in serre_cartan_basis(n - last, bound=2 * last): new = vec + (last,) result.append(new) else: # p odd @@ -1057,13 +1057,9 @@ def sorting_pair(s,t,basis): # pair used for sorting the basis # with distinct parts. for sigma in restricted_partitions(deg, q_degrees_decrease, - no_repeats = True): - index = 0 - q_mono = [] - for q in q_degrees: - if q in sigma: - q_mono.append(index) - index += 1 + no_repeats=True): + q_mono = [index for index, q in enumerate(q_degrees) + if q in sigma] # check profile: okay = True if profile is not None and profile != ((), ()): diff --git a/src/sage/algebras/yokonuma_hecke_algebra.py b/src/sage/algebras/yokonuma_hecke_algebra.py index 6ccd9c985a6..700395a2d84 100644 --- a/src/sage/algebras/yokonuma_hecke_algebra.py +++ b/src/sage/algebras/yokonuma_hecke_algebra.py @@ -454,7 +454,7 @@ def inverse_g(self, i): return self.g(i) + (~self._q + self._q) * self.e(i) class Element(CombinatorialFreeModule.Element): - def inverse(self): + def __invert__(self): r""" Return the inverse if ``self`` is a basis element. @@ -463,7 +463,7 @@ def inverse(self): sage: Y = algebras.YokonumaHecke(3, 3) sage: t = prod(Y.t()); t t1*t2*t3 - sage: ~t + sage: t.inverse() # indirect doctest t1^2*t2^2*t3^2 sage: [3*~(t*g) for g in Y.g()] [(q^-1+q)*t2*t3^2 + (q^-1+q)*t1*t3^2 @@ -494,5 +494,3 @@ def inverse(self): c = ~self.coefficients()[0] telt = H.monomial( (tuple((H._d - e) % H._d for e in t), H._Pn.one()) ) return c * telt * H.prod(H.inverse_g(i) for i in reversed(w.reduced_word())) - - __invert__ = inverse diff --git a/src/sage/all.py b/src/sage/all.py index a1a740a0018..6aef26c42a9 100644 --- a/src/sage/all.py +++ b/src/sage/all.py @@ -38,6 +38,10 @@ sage: interacts + +Check that :trac:`34506` is resolved:: + + sage: x = int('1'*4301) """ # **************************************************************************** # Copyright (C) 2005-2012 William Stein @@ -290,6 +294,12 @@ def quit_sage(verbose=True): sage.misc.lazy_import.finish_startup() +### Python broke large ints; see trac #34506 + +if hasattr(sys, "set_int_max_str_digits"): + sys.set_int_max_str_digits(0) + + def sage_globals(): r""" Return the Sage namespace. diff --git a/src/sage/all_cmdline.py b/src/sage/all_cmdline.py index 644d3f2c863..81c56b018ab 100644 --- a/src/sage/all_cmdline.py +++ b/src/sage/all_cmdline.py @@ -3,21 +3,18 @@ This is all.py (load all sage functions) plus set-up for the Sage commandline. """ - -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2007 William Stein # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** - +# https://www.gnu.org/licenses/ +# **************************************************************************** sage_mode = 'cmdline' from sage.all import * from sage.calculus.predefined import x sage.misc.session.init() - diff --git a/src/sage/calculus/calculus.py b/src/sage/calculus/calculus.py index b1c9d1f81d7..76cd8986123 100644 --- a/src/sage/calculus/calculus.py +++ b/src/sage/calculus/calculus.py @@ -1140,7 +1140,7 @@ def minpoly(ex, var='x', algorithm=None, bits=None, degree=None, epsilon=0): raise ValueError("Could not find minimal polynomial (%s bits, degree %s)." % (bits, degree)) if algorithm is None or algorithm == 'algebraic': - from sage.rings.all import QQbar + from sage.rings.qqbar import QQbar return QQ[var](QQbar(ex).minpoly()) raise ValueError("Unknown algorithm: %s" % algorithm) @@ -2519,10 +2519,10 @@ def _find_func(name, create_when_missing=True): parser_make_var = LookupNameMaker({}, fallback=_find_var) parser_make_function = LookupNameMaker({}, fallback=_find_func) -SR_parser = Parser(make_int = lambda x: SR(Integer(x)), - make_float = lambda x: SR(create_RealNumber(x)), - make_var = parser_make_var, - make_function = parser_make_function) +SR_parser = Parser(make_int=lambda x: SR(Integer(x)), + make_float=lambda x: SR(create_RealNumber(x)), + make_var=parser_make_var, + make_function=parser_make_function) def symbolic_expression_from_string(s, syms=None, accept_sequence=False, *, parser=None): @@ -2581,12 +2581,12 @@ def symbolic_expression_from_string(s, syms=None, accept_sequence=False, *, pars 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) +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) +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/predefined.py b/src/sage/calculus/predefined.py index ea677fe15da..8e7f499b1ae 100644 --- a/src/sage/calculus/predefined.py +++ b/src/sage/calculus/predefined.py @@ -48,4 +48,3 @@ X = _var('X') Y = _var('Y') Z = _var('Z') - diff --git a/src/sage/calculus/riemann.pyx b/src/sage/calculus/riemann.pyx index 97493b9d17c..4dc18b964f2 100644 --- a/src/sage/calculus/riemann.pyx +++ b/src/sage/calculus/riemann.pyx @@ -31,7 +31,7 @@ from sage.misc.decorators import options from sage.ext.fast_callable import fast_callable -from sage.rings.all import CDF +from sage.rings.complex_double import CDF from sage.arith.srange import srange diff --git a/src/sage/calculus/test_sympy.py b/src/sage/calculus/test_sympy.py index 7cf7f3f6bfd..927e6ee4fb6 100644 --- a/src/sage/calculus/test_sympy.py +++ b/src/sage/calculus/test_sympy.py @@ -193,7 +193,7 @@ sage: u = Function('u') sage: n = Symbol('n', integer=True) sage: f = u(n+2) - u(n+1) + u(n)/4 - sage: 2**n * rsolve(f,u(n)) - C1*n + C0 + sage: expand(2**n * rsolve(f,u(n))) + 2*C1*n + C0 """ diff --git a/src/sage/calculus/transforms/dft.py b/src/sage/calculus/transforms/dft.py index c71eedb23b3..9fd396f1915 100644 --- a/src/sage/calculus/transforms/dft.py +++ b/src/sage/calculus/transforms/dft.py @@ -330,7 +330,7 @@ def dft(self, chi=lambda x: x): N = len(J) S = self.list() F = self.base_ring() # elements must be coercible into QQ(zeta_N) - if not(J[0] in ZZ): + if J[0] not in ZZ: G = J[0].parent() # if J is not a range it is a group G if J[0] in ZZ and F.base_ring().fraction_field() == QQ: # assumes J is range(N) diff --git a/src/sage/categories/algebras_with_basis.py b/src/sage/categories/algebras_with_basis.py index 459dc7f53be..dbd7a9a43a1 100644 --- a/src/sage/categories/algebras_with_basis.py +++ b/src/sage/categories/algebras_with_basis.py @@ -104,7 +104,7 @@ class AlgebrasWithBasis(CategoryWithAxiom_over_base_ring): sage: TestSuite(AlgebrasWithBasis(QQ)).run() """ - def example(self, alphabet = ('a','b','c')): + def example(self, alphabet=('a','b','c')): """ Return an example of algebra with basis. diff --git a/src/sage/categories/cartesian_product.py b/src/sage/categories/cartesian_product.py index 33f5c5b9b02..e6fbf670750 100644 --- a/src/sage/categories/cartesian_product.py +++ b/src/sage/categories/cartesian_product.py @@ -153,7 +153,7 @@ def __call__(self, args, **kwds): sage: cartesian_product([set([0,1,2]), [0,1]]) The Cartesian product of ({0, 1, 2}, {0, 1}) sage: _.category() - Category of Cartesian products of sets + Category of Cartesian products of finite enumerated sets Check that the empty product is handled correctly: diff --git a/src/sage/categories/category.py b/src/sage/categories/category.py index 60e45a11260..eef5e7acc70 100644 --- a/src/sage/categories/category.py +++ b/src/sage/categories/category.py @@ -474,7 +474,7 @@ def __init__(self, s=None): assert s is None self.__class__ = dynamic_class("{}_with_category".format(self.__class__.__name__), (self.__class__, self.subcategory_class, ), - cache = False, reduction = None, + cache=False, reduction=None, doccls=self.__class__) @lazy_attribute @@ -870,7 +870,7 @@ def _all_super_categories(self): for cat in self._super_categories] + [self._super_categories], category_sort_key) - if not sorted(result, key = category_sort_key, reverse=True) == result: + if not sorted(result, key=category_sort_key, reverse=True) == result: warn("Inconsistent sorting results for all super categories of {}".format( self.__class__)) self._super_categories_for_classes = bases @@ -1009,7 +1009,7 @@ def _super_categories(self): sage: Rings()._super_categories [Category of rngs, Category of semirings] """ - return sorted(_flatten_categories(self.super_categories(),JoinCategory), key = category_sort_key, reverse=True) + return sorted(_flatten_categories(self.super_categories(), JoinCategory), key=category_sort_key, reverse=True) @lazy_attribute def _super_categories_for_classes(self): @@ -1597,10 +1597,9 @@ def _make_named_class(self, name, method_provider, cache=False, picklable=True): else: reduction = None return dynamic_class(class_name, - tuple(getattr(cat,name) for cat in self._super_categories_for_classes), - method_provider_cls, prepend_cls_bases = False, doccls = doccls, - reduction = reduction, cache = cache) - + tuple(getattr(cat, name) for cat in self._super_categories_for_classes), + method_provider_cls, prepend_cls_bases=False, + doccls=doccls, reduction=reduction, cache=cache) @lazy_attribute def subcategory_class(self): @@ -1822,7 +1821,7 @@ def is_subcategory(self, c): return c in self._set_of_super_categories return subcat_hook - def or_subcategory(self, category = None, join = False): + def or_subcategory(self, category=None, join=False): """ Return ``category`` or ``self`` if ``category`` is ``None``. @@ -2618,7 +2617,8 @@ def category_sample(): for cls in sage.categories.all.__dict__.values() if isinstance(cls, type) and issubclass(cls, Category) and cls not in abstract_classes_for_categories) -def category_graph(categories = None): + +def category_graph(categories=None): """ Return the graph of the categories in Sage. @@ -2707,7 +2707,7 @@ class CategoryWithParameters(Category): .. automethod:: Category._make_named_class """ - def _make_named_class(self, name, method_provider, cache = False, **options): + def _make_named_class(self, name, method_provider, cache=False, **options): """ Return the parent/element/... class of ``self``. @@ -3215,7 +3215,7 @@ def _repr_object_names(self): from sage.categories.category_with_axiom import CategoryWithAxiom return CategoryWithAxiom._repr_object_names_static(self._without_axioms(named=True), self.axioms()) - def _repr_(self, as_join = False): + def _repr_(self, as_join=False): """ Print representation. diff --git a/src/sage/categories/category_cy_helper.pyx b/src/sage/categories/category_cy_helper.pyx index 804db9cce89..135b015fc1c 100644 --- a/src/sage/categories/category_cy_helper.pyx +++ b/src/sage/categories/category_cy_helper.pyx @@ -318,6 +318,5 @@ cpdef tuple canonicalize_axioms(AxiomContainer all_axioms, axioms): ('Finite', 'Connected', 'WithBasis', 'Commutative') """ cdef list L = list(set(axioms)) - L.sort(key = (all_axioms).__getitem__) + L.sort(key=(all_axioms).__getitem__) return tuple(L) - diff --git a/src/sage/categories/category_with_axiom.py b/src/sage/categories/category_with_axiom.py index e9faa85ab57..e8811e9f25e 100644 --- a/src/sage/categories/category_with_axiom.py +++ b/src/sage/categories/category_with_axiom.py @@ -2177,8 +2177,8 @@ def super_categories(self): for category in base_category._super_categories for cat in category._with_axiom_as_tuple(axiom)) + tuple(self.extra_super_categories()), - ignore_axioms = ((base_category, axiom),), - as_list = True) + ignore_axioms=((base_category, axiom),), + as_list=True) def additional_structure(self): r""" diff --git a/src/sage/categories/classical_crystals.py b/src/sage/categories/classical_crystals.py index 5bb4b5c4a1b..490337d0bda 100644 --- a/src/sage/categories/classical_crystals.py +++ b/src/sage/categories/classical_crystals.py @@ -110,7 +110,7 @@ def additional_structure(self): class ParentMethods: - def demazure_character(self, w, f = None): + def demazure_character(self, w, f=None): r""" Return the Demazure character associated to ``w``. diff --git a/src/sage/categories/coalgebras_with_basis.py b/src/sage/categories/coalgebras_with_basis.py index 5f781613944..b672c37dd27 100644 --- a/src/sage/categories/coalgebras_with_basis.py +++ b/src/sage/categories/coalgebras_with_basis.py @@ -17,6 +17,7 @@ from sage.categories.super_modules import SuperModulesCategory from sage.categories.filtered_modules import FilteredModulesCategory + class CoalgebrasWithBasis(CategoryWithAxiom_over_base_ring): """ The category of coalgebras with a distinguished basis. @@ -43,7 +44,7 @@ class Filtered(FilteredModulesCategory): class ParentMethods: - @abstract_method(optional = True) + @abstract_method(optional=True) def coproduct_on_basis(self, i): """ The coproduct of the algebra on the basis (optional). @@ -86,14 +87,14 @@ def coproduct(self): """ if self.coproduct_on_basis is not NotImplemented: - # TODO: if self is a hopf algebra, then one would want + # TODO: if self is a Hopf algebra, then one would want # to create a morphism of algebras with basis instead # should there be a method self.coproduct_homset_category? - return Hom(self, tensor([self, self]), ModulesWithBasis(self.base_ring()))(on_basis = self.coproduct_on_basis) + return Hom(self, tensor([self, self]), ModulesWithBasis(self.base_ring()))(on_basis=self.coproduct_on_basis) elif hasattr(self, "coproduct_by_coercion"): return self.coproduct_by_coercion - @abstract_method(optional = True) + @abstract_method(optional=True) def counit_on_basis(self, i): """ The counit of the algebra on the basis (optional). diff --git a/src/sage/categories/commutative_algebras.py b/src/sage/categories/commutative_algebras.py index ec4037f9a84..0f24e90c524 100644 --- a/src/sage/categories/commutative_algebras.py +++ b/src/sage/categories/commutative_algebras.py @@ -10,8 +10,11 @@ # http://www.gnu.org/licenses/ #****************************************************************************** +from sage.misc.cachefunc import cached_method from sage.categories.category_with_axiom import CategoryWithAxiom_over_base_ring from sage.categories.algebras import Algebras +from sage.categories.commutative_rings import CommutativeRings +from sage.categories.tensor import TensorProductsCategory class CommutativeAlgebras(CategoryWithAxiom_over_base_ring): """ @@ -36,7 +39,7 @@ class CommutativeAlgebras(CategoryWithAxiom_over_base_ring): True sage: TestSuite(CommutativeAlgebras(ZZ)).run() - Todo: + .. TODO:: - product ( = Cartesian product) - coproduct ( = tensor product over base ring) @@ -58,3 +61,32 @@ def __contains__(self, A): """ return super().__contains__(A) or \ (A in Algebras(self.base_ring()) and hasattr(A, "is_commutative") and A.is_commutative()) + + class TensorProducts(TensorProductsCategory): + """ + The category of commutative algebras constructed by tensor product of commutative algebras. + """ + + @cached_method + def extra_super_categories(self): + """ + EXAMPLES:: + + sage: Algebras(QQ).Commutative().TensorProducts().extra_super_categories() + [Category of commutative rings] + sage: Algebras(QQ).Commutative().TensorProducts().super_categories() + [Category of tensor products of algebras over Rational Field, + Category of commutative algebras over Rational Field] + + TESTS:: + + sage: X = algebras.Shuffle(QQ, 'ab') + sage: Y = algebras.Shuffle(QQ, 'bc') + sage: X in Algebras(QQ).Commutative() + True + sage: T = tensor([X, Y]) + sage: T in CommutativeRings() + True + """ + return [CommutativeRings()] + diff --git a/src/sage/categories/complex_reflection_or_generalized_coxeter_groups.py b/src/sage/categories/complex_reflection_or_generalized_coxeter_groups.py index 497f29a433b..6ddd62e0099 100644 --- a/src/sage/categories/complex_reflection_or_generalized_coxeter_groups.py +++ b/src/sage/categories/complex_reflection_or_generalized_coxeter_groups.py @@ -1142,7 +1142,7 @@ def _mul_(self, other): """ return self.apply_simple_reflections(other.reduced_word()) - def inverse(self): + def __invert__(self): """ Return the inverse of ``self``. @@ -1150,7 +1150,7 @@ def inverse(self): sage: W = WeylGroup(['B',7]) sage: w = W.an_element() - sage: u = w.inverse() + sage: u = w.inverse() # indirect doctest sage: u == ~w True sage: u * w == w * u @@ -1166,8 +1166,6 @@ def inverse(self): """ return self.parent().one().apply_simple_reflections(self.reduced_word_reverse_iterator()) - __invert__ = inverse - def apply_conjugation_by_simple_reflection(self, i): r""" Conjugate ``self`` by the ``i``-th simple reflection. diff --git a/src/sage/categories/covariant_functorial_construction.py b/src/sage/categories/covariant_functorial_construction.py index 96dbf891bc9..4ed3a678986 100644 --- a/src/sage/categories/covariant_functorial_construction.py +++ b/src/sage/categories/covariant_functorial_construction.py @@ -480,7 +480,7 @@ def super_categories(self): """ return Category.join([self.__class__.default_super_categories(self.base_category(), *self._args)] + self.extra_super_categories(), - as_list = True) + as_list=True) def _repr_object_names(self): """ diff --git a/src/sage/categories/coxeter_groups.py b/src/sage/categories/coxeter_groups.py index 5e8a0d8b567..0aacd2aeda5 100644 --- a/src/sage/categories/coxeter_groups.py +++ b/src/sage/categories/coxeter_groups.py @@ -2176,12 +2176,25 @@ def bruhat_lower_covers_reflections(self): sage: w.bruhat_lower_covers_reflections() [(s1*s2*s1, s1*s2*s3*s2*s1), (s3*s2*s1, s2), (s3*s1*s2, s1)] + TESTS: + + Check bug discovered in :trac:`32669` is fixed:: + + sage: W = CoxeterGroup(['A',3], implementation='permutation') + sage: W.w0.bruhat_lower_covers_reflections() + [((1,3,7,9)(2,11,6,10)(4,8,5,12), (2,5)(3,9)(4,6)(8,11)(10,12)), + ((1,11)(3,10)(4,9)(5,7)(6,12), (1,4)(2,8)(3,5)(7,10)(9,11)), + ((1,9,7,3)(2,10,6,11)(4,12,5,8), (1,7)(2,4)(5,6)(8,10)(11,12))] """ - i = self.first_descent() + i = self.first_descent(side='right') if i is None: return [] - wi = self.apply_simple_reflection(i) - return [(u.apply_simple_reflection(i), r.apply_conjugation_by_simple_reflection(i)) for u, r in wi.bruhat_lower_covers_reflections() if not u.has_descent(i)] + [(wi, self.parent().simple_reflection(i))] + wi = self.apply_simple_reflection(i, side='right') + return [(u.apply_simple_reflection(i, side='right'), + r.apply_conjugation_by_simple_reflection(i)) + for u,r in wi.bruhat_lower_covers_reflections() + if not u.has_descent(i, side='right')] + [ + (wi, self.parent().simple_reflection(i))] def lower_cover_reflections(self, side='right'): r""" diff --git a/src/sage/categories/crystals.py b/src/sage/categories/crystals.py index 995dd7e63e6..1cfbf9175ab 100644 --- a/src/sage/categories/crystals.py +++ b/src/sage/categories/crystals.py @@ -19,6 +19,7 @@ # https://www.gnu.org/licenses/ #***************************************************************************** +import collections.abc from sage.misc.cachefunc import cached_method from sage.misc.abstract_method import abstract_method @@ -779,10 +780,10 @@ def crystal_morphism(self, on_gens, codomain=None, if codomain is None: if hasattr(on_gens, 'codomain'): codomain = on_gens.codomain() - elif isinstance(on_gens, (list, tuple)): + elif isinstance(on_gens, collections.abc.Sequence): if on_gens: codomain = on_gens[0].parent() - elif isinstance(on_gens, dict): + elif isinstance(on_gens, collections.abc.Mapping): if on_gens: codomain = next(iter(on_gens.values())).parent() else: @@ -1515,9 +1516,10 @@ def s(self, i): b = b.e(i) return b - def is_highest_weight(self, index_set = None): + def is_highest_weight(self, index_set=None): r""" - Returns ``True`` if ``self`` is a highest weight. + Return ``True`` if ``self`` is a highest weight. + Specifying the option ``index_set`` to be a subset `I` of the index set of the underlying crystal, finds all highest weight vectors for arrows in `I`. @@ -1536,7 +1538,7 @@ def is_highest_weight(self, index_set = None): index_set = self.index_set() return all(self.e(i) is None for i in index_set) - def is_lowest_weight(self, index_set = None): + def is_lowest_weight(self, index_set=None): r""" Returns ``True`` if ``self`` is a lowest weight. Specifying the option ``index_set`` to be a subset `I` of the @@ -1557,7 +1559,7 @@ def is_lowest_weight(self, index_set = None): index_set = self.index_set() return all(self.f(i) is None for i in index_set) - def to_highest_weight(self, index_set = None): + def to_highest_weight(self, index_set=None): r""" Return the highest weight element `u` and a list `[i_1,...,i_k]` such that `self = f_{i_1} ... f_{i_k} u`, where `i_1,...,i_k` are @@ -1582,21 +1584,21 @@ def to_highest_weight(self, index_set = None): sage: t.to_highest_weight() Traceback (most recent call last): ... - ValueError: This is not a highest weight crystals! + ValueError: this is not a highest weight crystal """ from sage.categories.highest_weight_crystals import HighestWeightCrystals if index_set is None: if HighestWeightCrystals() not in self.parent().categories(): - raise ValueError("This is not a highest weight crystals!") + raise ValueError("this is not a highest weight crystal") index_set = self.index_set() for i in index_set: next = self.e(i) if next is not None: - hw = next.to_highest_weight(index_set = index_set) + hw = next.to_highest_weight(index_set=index_set) return [hw[0], [i] + hw[1]] return [self, []] - def to_lowest_weight(self, index_set = None): + def to_lowest_weight(self, index_set=None): r""" Return the lowest weight element `u` and a list `[i_1,...,i_k]` such that `self = e_{i_1} ... e_{i_k} u`, where `i_1,...,i_k` are @@ -1623,17 +1625,17 @@ def to_lowest_weight(self, index_set = None): sage: t.to_lowest_weight() Traceback (most recent call last): ... - ValueError: This is not a highest weight crystals! + ValueError: this is not a highest weight crystal """ from sage.categories.highest_weight_crystals import HighestWeightCrystals if index_set is None: if HighestWeightCrystals() not in self.parent().categories(): - raise ValueError("This is not a highest weight crystals!") + raise ValueError("this is not a highest weight crystal") index_set = self.index_set() for i in index_set: next = self.f(i) if next is not None: - lw = next.to_lowest_weight(index_set = index_set) + lw = next.to_lowest_weight(index_set=index_set) return [lw[0], [i] + lw[1]] return [self, []] @@ -1844,7 +1846,7 @@ def __init__(self, parent, cartan_type=None, scaling_factors = {i: 1 for i in index_set} if virtualization is None: virtualization = {i: (i,) for i in index_set} - elif not isinstance(virtualization, dict): + elif not isinstance(virtualization, collections.abc.Mapping): try: virtualization = dict(virtualization) except (TypeError, ValueError): @@ -2056,16 +2058,16 @@ def __init__(self, parent, on_gens, cartan_type=None, virtualization, scaling_factors) if gens is None: - if isinstance(on_gens, dict): + if isinstance(on_gens, collections.abc.Mapping): gens = on_gens.keys() else: gens = parent.domain().module_generators self._gens = tuple(gens) # Make sure on_gens is a function - if isinstance(on_gens, dict): + if isinstance(on_gens, collections.abc.Mapping): f = lambda x: on_gens[x] - elif isinstance(on_gens, (list, tuple)): + elif isinstance(on_gens, collections.abc.Sequence): if len(self._gens) != len(on_gens): raise ValueError("invalid generator images") d = {x: y for x, y in zip(self._gens, on_gens)} @@ -2578,7 +2580,7 @@ def __call__(self, on_gens, cartan_type=None, index_set=None, generators=None, if automorphism is not None: if virtualization is not None: raise ValueError("the automorphism and virtualization cannot both be specified") - if not isinstance(automorphism, dict): + if not isinstance(automorphism, collections.abc.Mapping): try: automorphism = dict(automorphism) virtualization = {i: (automorphism[i],) for i in automorphism} diff --git a/src/sage/categories/enumerated_sets.py b/src/sage/categories/enumerated_sets.py index 563a8025e6d..695f4982bd8 100644 --- a/src/sage/categories/enumerated_sets.py +++ b/src/sage/categories/enumerated_sets.py @@ -46,11 +46,15 @@ class EnumeratedSets(CategoryWithAxiom): - ``iter(S)``: an iterator for the elements of the set; - - ``S.list()``: the list of the elements of the set, when - possible; raises a NotImplementedError if the list is + - ``S.list()``: a fresh list of the elements of the set, when + possible; raises a :class:`NotImplementedError` if the list is predictably too large to be expanded in memory. - - ``S.unrank(n)``: the ``n-th`` element of the set when ``n`` is a sage + - ``S.tuple()``: a tuple of the elements of the set, when + possible; raises a :class:`NotImplementedError` if the tuple is + predictably too large to be expanded in memory. + + - ``S.unrank(n)``: the ``n``-th element of the set when ``n`` is a sage ``Integer``. This is the equivalent for ``l[n]`` on a list. - ``S.rank(e)``: the position of the element ``e`` in the set; @@ -61,8 +65,8 @@ class EnumeratedSets(CategoryWithAxiom): - ``S.first()``: the first object of the set; it is equivalent to ``S.unrank(0)``. - - ``S.next(e)``: the object of the set which follows ``e``; It is - equivalent to ``S.unrank(S.rank(e)+1)``. + - ``S.next(e)``: the object of the set which follows ``e``; it is + equivalent to ``S.unrank(S.rank(e) + 1)``. - ``S.random_element()``: a random generator for an element of the set. Unless otherwise stated, and for finite enumerated @@ -137,7 +141,7 @@ def _call_(self, X): sage: S = EnumeratedSets()(Set([1, 2, 3])); S {1, 2, 3} sage: S.category() - Category of facade finite enumerated sets + Category of finite enumerated sets Also Python3 range are now accepted:: @@ -312,8 +316,7 @@ def iterator_range(self, start=None, stop=None, step=None): return start = 0 elif start < 0: - for x in self.list()[start::step]: - yield x + yield from self.tuple()[start::step] return if step is None: step = 1 @@ -325,8 +328,7 @@ def iterator_range(self, start=None, stop=None, step=None): start += step elif stop < 0: - for x in self.list()[start:stop:step]: - yield x + yield from self.tuple()[start:stop:step] return if start is None: @@ -337,9 +339,7 @@ def iterator_range(self, start=None, stop=None, step=None): return start = 0 elif start < 0: - for x in self.list()[start:stop:step]: - yield x - return + yield from self.tuple()[start:stop:step] if step is None: step = 1 for j in range(start, stop, step): @@ -381,13 +381,13 @@ def unrank_range(self, start=None, stop=None, step=None): NotImplementedError: cannot list an infinite set """ if stop is None: - return self.list()[start::step] + return list(self.tuple()[start::step]) if stop < 0: - return self.list()[start:stop:step] + return list(self.tuple()[start:stop:step]) if start is not None and start < 0: - return self.list()[start:stop:step] + return list(self.tuple()[start:stop:step]) return list(self.iterator_range(start, stop, step)) @@ -479,13 +479,94 @@ def __len__(self): raise NotImplementedError('infinite set') return int(c) except AttributeError: - return len(self.list()) + return len(self.tuple()) + + def tuple(self): + r""" + Return a tuple of the elements of ``self``. + + The tuple of elements of ``x`` is created and cached on the first call + of ``x.tuple()``. Each following call of ``x.tuple()`` returns the same tuple. + + For looping, it may be better to do ``for e in x:``, not ``for e in x.tuple():``. + + If ``x`` is not known to be finite, then an exception is raised. + + EXAMPLES:: + + sage: (GF(3)^2).tuple() + ((0, 0), (1, 0), (2, 0), (0, 1), (1, 1), (2, 1), (0, 2), (1, 2), (2, 2)) + sage: R = Integers(11) + sage: l = R.tuple(); l + (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + sage: l is R.tuple() + True + """ + try: # shortcut + if self._list is not None: + return self._tuple_from_list() + except AttributeError: + pass + + if self.list != self._list_default: + return tuple(self.list()) + + from sage.rings.infinity import Infinity + try: + if self.cardinality() is Infinity: + raise NotImplementedError('cannot list an infinite set') + else: # finite cardinality + return self._tuple_from_iterator() + except AttributeError: + raise NotImplementedError('unknown cardinality') + _tuple_default = tuple + + def _tuple_from_iterator(self): + r""" + Return a tuple of the elements of ``self``. + + This implementation of :meth:`tuple` creates the tuple of elements and caches it for + later uses. + + TESTS:: + + sage: R = Integers(11) + sage: R._list is None + False + sage: R._tuple_from_iterator() + (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + sage: _ is R._list + True + """ + # This creates one throw-away list. + self._list_from_iterator() + return self._tuple_from_list() + + def _tuple_from_list(self): + r""" + Return a tuple of the elements of ``self``. + + This implementation of :meth:`tuple` assumes that the tuple of elements is already + cached and just returns it. + + TESTS:: + + sage: R = Integers(11) + sage: R.tuple() + (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + sage: R._tuple_from_list() is R.tuple() + True + """ + # Implementation classes may put any Sequence type in self._list. + # Traditionally, self._list was an actual list. + # When self._list is already a tuple, calling tuple on it is a no-op. + return tuple(self._list) def list(self): r""" Return a list of the elements of ``self``. - The elements of set ``x`` are created and cached on the fist call + The elements of set ``x`` are created and cached on the first call of ``x.list()``. Then each call of ``x.list()`` returns a new list from the cached result. Thus in looping, it may be better to do ``for e in x:``, not ``for e in x.list():``. @@ -506,21 +587,11 @@ def list(self): sage: R.list() [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + sage: C = FiniteEnumeratedSets().example() + sage: C.list() + [1, 2, 3] """ - try: # shortcut - if self._list is not None: - return list(self._list) - except AttributeError: - pass - - from sage.rings.infinity import Infinity - try: - if self.cardinality() is Infinity: - raise NotImplementedError('cannot list an infinite set') - else: # finite cardinality - return self._list_from_iterator() - except AttributeError: - raise NotImplementedError('unknown cardinality') + return list(self.tuple()) _list_default = list # needed by the check system. def _list_from_iterator(self): @@ -579,7 +650,7 @@ def _list_from_iterator(self): return list(self._list) except AttributeError: pass - result = list(self.__iter__()) + result = tuple(self.__iter__()) try: self._list = result except AttributeError: @@ -653,14 +724,21 @@ def _unrank_from_iterator(self, r): sage: C._unrank_from_iterator(5) Traceback (most recent call last): ... - ValueError: the value must be between 0 and 2 inclusive + ValueError: the rank must be in the range from 0 to 2 + sage: ZZ._unrank_from_iterator(-1) + Traceback (most recent call last): + ... + ValueError: the rank must be greater than or equal to 0 """ - counter = 0 - for u in self: + from sage.rings.integer_ring import ZZ + if r < 0: + raise ValueError("the rank must be greater than or equal to 0") + if r not in ZZ: + raise ValueError(f"{r=} must be an integer") + for counter, u in enumerate(self): if counter == r: return u - counter += 1 - raise ValueError("the value must be between %s and %s inclusive"%(0,counter-1)) + raise ValueError("the rank must be in the range from %s to %s"%(0,counter)) unrank = _unrank_from_iterator def _rank_from_iterator(self, x): @@ -711,7 +789,7 @@ def _iterator_from_list(self): sage: [next(it), next(it), next(it)] [1, 2, 3] """ - for x in self.list(): + for x in self.tuple(): yield x def _iterator_from_next(self): diff --git a/src/sage/categories/examples/algebras_with_basis.py b/src/sage/categories/examples/algebras_with_basis.py index 066c72aee4f..7ad85cb82ed 100644 --- a/src/sage/categories/examples/algebras_with_basis.py +++ b/src/sage/categories/examples/algebras_with_basis.py @@ -21,7 +21,7 @@ class FreeAlgebra(CombinatorialFreeModule): This class illustrates a minimal implementation of an algebra with basis. """ - def __init__(self, R, alphabet = ("a", "b", "c")): + def __init__(self, R, alphabet=("a", "b", "c")): """ EXAMPLES:: @@ -31,7 +31,9 @@ def __init__(self, R, alphabet = ("a", "b", "c")): """ self._alphabet = alphabet - CombinatorialFreeModule.__init__(self, R, Words(alphabet, infinite=False), category = AlgebrasWithBasis(R)) + CombinatorialFreeModule.__init__(self, R, + Words(alphabet, infinite=False), + category=AlgebrasWithBasis(R)) def _repr_(self): """ diff --git a/src/sage/categories/examples/commutative_additive_monoids.py b/src/sage/categories/examples/commutative_additive_monoids.py index 229f66ae7e0..c0930bec5d5 100644 --- a/src/sage/categories/examples/commutative_additive_monoids.py +++ b/src/sage/categories/examples/commutative_additive_monoids.py @@ -84,7 +84,7 @@ def __init__(self, alphabet=('a','b','c','d')): """ self.alphabet = alphabet - Parent.__init__(self, category = CommutativeAdditiveMonoids()) + Parent.__init__(self, category=CommutativeAdditiveMonoids()) def _repr_(self): r""" diff --git a/src/sage/categories/examples/commutative_additive_semigroups.py b/src/sage/categories/examples/commutative_additive_semigroups.py index 88cdeeee55d..ea2fde99bb4 100644 --- a/src/sage/categories/examples/commutative_additive_semigroups.py +++ b/src/sage/categories/examples/commutative_additive_semigroups.py @@ -81,10 +81,9 @@ def __init__(self, alphabet=('a','b','c','d')): TESTS:: sage: TestSuite(M).run() - """ self.alphabet = alphabet - Parent.__init__(self, category = CommutativeAdditiveSemigroups()) + Parent.__init__(self, category=CommutativeAdditiveSemigroups()) def _repr_(self): r""" diff --git a/src/sage/categories/examples/crystals.py b/src/sage/categories/examples/crystals.py index 0e5fd353d9b..02121cd3c02 100644 --- a/src/sage/categories/examples/crystals.py +++ b/src/sage/categories/examples/crystals.py @@ -96,7 +96,7 @@ class HighestWeightCrystalOfTypeA(UniqueRepresentation, Parent): running ._test_stembridge_local_axioms() . . . pass """ - def __init__(self, n = 3): + def __init__(self, n=3): """ EXAMPLES:: @@ -104,10 +104,10 @@ def __init__(self, n = 3): sage: C == Crystals().example(n=4) True """ - Parent.__init__(self, category = ClassicalCrystals()) + Parent.__init__(self, category=ClassicalCrystals()) self.n = n - self._cartan_type = CartanType(['A',n]) - self.module_generators = [ self(1) ] + self._cartan_type = CartanType(['A', n]) + self.module_generators = [self(1)] def _repr_(self): """ @@ -183,12 +183,12 @@ def __init__(self): sage: C == Crystals().example(choice='naive') True """ - Parent.__init__(self, category = ClassicalCrystals()) + Parent.__init__(self, category=ClassicalCrystals()) self.n = 2 - self._cartan_type = CartanType(['A',2]) + self._cartan_type = CartanType(['A', 2]) self.G = DiGraph(5) self.G.add_edges([ [0,1,1], [1,2,1], [2,3,1], [3,5,1], [0,4,2], [4,5,2] ]) - self.module_generators = [ self(0) ] + self.module_generators = [self(0)] def __repr__(self): """ diff --git a/src/sage/categories/examples/facade_sets.py b/src/sage/categories/examples/facade_sets.py index 5eed585c62c..bff442e2c5c 100644 --- a/src/sage/categories/examples/facade_sets.py +++ b/src/sage/categories/examples/facade_sets.py @@ -70,9 +70,8 @@ def __init__(self): TESTS:: sage: TestSuite(S).run() - """ - Parent.__init__(self, facade = ZZ, category = Monoids()) + Parent.__init__(self, facade=ZZ, category=Monoids()) def _repr_(self): r""" @@ -170,12 +169,11 @@ def __init__(self): TESTS:: sage: TestSuite(S).run() - """ # We can't use InfinityRing, because this ring contains 3 - # elements besides +-infinity. We can't use Set either for the + # elements besides +-infinity. We can not use Set either for the # moment, because Set([1,2])(1) raises an error - Parent.__init__(self, facade = (ZZ, FiniteEnumeratedSet([-infinity, +infinity])), category = Sets()) + Parent.__init__(self, facade=(ZZ, FiniteEnumeratedSet([-infinity, +infinity])), category=Sets()) def _repr_(self): r""" @@ -183,6 +181,5 @@ def _repr_(self): EXAMPLES:: sage: S = Sets().Facade().example() # indirect doctest - """ return "An example of a facade set: the integers completed by +-infinity" diff --git a/src/sage/categories/examples/finite_coxeter_groups.py b/src/sage/categories/examples/finite_coxeter_groups.py index 83b60ea6d1c..c135c877b70 100644 --- a/src/sage/categories/examples/finite_coxeter_groups.py +++ b/src/sage/categories/examples/finite_coxeter_groups.py @@ -85,7 +85,7 @@ class DihedralGroup(UniqueRepresentation, Parent): ((2, 1), (2,), 1)] """ - def __init__(self, n = 5): + def __init__(self, n=5): r""" INPUT: - ``n`` - an integer with `n>=2` @@ -97,10 +97,9 @@ def __init__(self, n = 5): sage: from sage.categories.examples.finite_coxeter_groups import DihedralGroup sage: DihedralGroup(3) The 3-th dihedral group of order 6 - """ assert n >= 2 - Parent.__init__(self, category = FiniteCoxeterGroups()) + Parent.__init__(self, category=FiniteCoxeterGroups()) self.n = n def _repr_(self): @@ -112,7 +111,7 @@ def _repr_(self): sage: FiniteCoxeterGroups().example(6) The 6-th dihedral group of order 12 """ - return "The %s-th dihedral group of order %s"%(self.n, 2*self.n) + return "The %s-th dihedral group of order %s" % (self.n, 2 * self.n) def __contains__(self, x): r""" @@ -184,7 +183,7 @@ class Element(ElementWrapper): wrapped_class = tuple __lt__ = ElementWrapper._lt_by_value - def has_right_descent(self, i, positive = False, side = "right"): + def has_right_descent(self, i, positive=False, side="right"): r""" Implements :meth:`SemiGroups.ElementMethods.has_right_descent`. diff --git a/src/sage/categories/examples/finite_monoids.py b/src/sage/categories/examples/finite_monoids.py index 52e473ee6a1..f61c28a4032 100644 --- a/src/sage/categories/examples/finite_monoids.py +++ b/src/sage/categories/examples/finite_monoids.py @@ -17,6 +17,7 @@ from sage.rings.integer import Integer from sage.rings.integer_ring import ZZ + class IntegerModMonoid(UniqueRepresentation, Parent): r""" An example of a finite monoid: the integers mod `n` @@ -63,7 +64,7 @@ class IntegerModMonoid(UniqueRepresentation, Parent): running ._test_some_elements() . . . pass """ - def __init__(self, n = 12): + def __init__(self, n=12): r""" EXAMPLES:: @@ -73,7 +74,6 @@ def __init__(self, n = 12): TESTS:: sage: TestSuite(M).run() - """ self.n = n Parent.__init__(self, category=Monoids().Finite().FinitelyGenerated()) diff --git a/src/sage/categories/examples/finite_semigroups.py b/src/sage/categories/examples/finite_semigroups.py index 25ffa464bd3..e21a9713815 100644 --- a/src/sage/categories/examples/finite_semigroups.py +++ b/src/sage/categories/examples/finite_semigroups.py @@ -117,7 +117,8 @@ def __init__(self, alphabet=('a','b','c','d')): sage: TestSuite(S).run() """ self.alphabet = alphabet - Parent.__init__(self, category = Semigroups().Finite().FinitelyGenerated()) + Parent.__init__(self, + category=Semigroups().Finite().FinitelyGenerated()) def _repr_(self): r""" diff --git a/src/sage/categories/examples/finite_weyl_groups.py b/src/sage/categories/examples/finite_weyl_groups.py index d3d50c9af89..a17a48c142e 100644 --- a/src/sage/categories/examples/finite_weyl_groups.py +++ b/src/sage/categories/examples/finite_weyl_groups.py @@ -72,7 +72,7 @@ class SymmetricGroup(UniqueRepresentation, Parent): sage: TestSuite(S).run() """ - def __init__(self, n = 4): + def __init__(self, n=4): """ EXAMPLES:: diff --git a/src/sage/categories/examples/hopf_algebras_with_basis.py b/src/sage/categories/examples/hopf_algebras_with_basis.py index fc3ef19d914..16bd866c9c6 100644 --- a/src/sage/categories/examples/hopf_algebras_with_basis.py +++ b/src/sage/categories/examples/hopf_algebras_with_basis.py @@ -32,7 +32,8 @@ def __init__(self, R, G): sage: TestSuite(A).run() """ self._group = G - CombinatorialFreeModule.__init__(self, R, G, category = HopfAlgebrasWithBasis(R)) + CombinatorialFreeModule.__init__(self, R, G, + category=HopfAlgebrasWithBasis(R)) def _repr_(self): """ diff --git a/src/sage/categories/examples/infinite_enumerated_sets.py b/src/sage/categories/examples/infinite_enumerated_sets.py index d606c9274da..c4bb3d8e55d 100644 --- a/src/sage/categories/examples/infinite_enumerated_sets.py +++ b/src/sage/categories/examples/infinite_enumerated_sets.py @@ -89,7 +89,7 @@ def __init__(self): Category of infinite enumerated sets sage: TestSuite(NN).run() """ - Parent.__init__(self, category = InfiniteEnumeratedSets()) + Parent.__init__(self, category=InfiniteEnumeratedSets()) def _repr_(self): """ diff --git a/src/sage/categories/examples/monoids.py b/src/sage/categories/examples/monoids.py index 9de0d2f06e1..c2919a8cc28 100644 --- a/src/sage/categories/examples/monoids.py +++ b/src/sage/categories/examples/monoids.py @@ -93,7 +93,7 @@ def __init__(self, alphabet=('a','b','c','d')): """ self.alphabet = alphabet - Parent.__init__(self, category = Monoids()) + Parent.__init__(self, category=Monoids()) def _repr_(self): r""" diff --git a/src/sage/categories/examples/posets.py b/src/sage/categories/examples/posets.py index 4d864c9cef5..5041df19835 100644 --- a/src/sage/categories/examples/posets.py +++ b/src/sage/categories/examples/posets.py @@ -64,7 +64,7 @@ def __init__(self): sage: TestSuite(P).run() """ - Parent.__init__(self, category = Posets()) + Parent.__init__(self, category=Posets()) def _repr_(self): r""" @@ -146,7 +146,7 @@ def __init__(self): sage: TestSuite(P).run() """ - Parent.__init__(self, facade = (PositiveIntegers(),), category = Posets()) + Parent.__init__(self, facade=(PositiveIntegers(),), category=Posets()) def _repr_(self): r""" diff --git a/src/sage/categories/examples/semigroups.py b/src/sage/categories/examples/semigroups.py index 4d7767a3c51..4c4d9a1f4df 100644 --- a/src/sage/categories/examples/semigroups.py +++ b/src/sage/categories/examples/semigroups.py @@ -78,9 +78,8 @@ def __init__(self): TESTS:: sage: TestSuite(S).run() - """ - Parent.__init__(self, category = Semigroups()) + Parent.__init__(self, category=Semigroups()) def _repr_(self): r""" @@ -197,10 +196,9 @@ def __init__(self, alphabet=('a','b','c','d')): sage: F == loads(dumps(F)) True - """ self.alphabet = alphabet - Parent.__init__(self, category = Semigroups().FinitelyGenerated()) + Parent.__init__(self, category=Semigroups().FinitelyGenerated()) def _repr_(self): r""" @@ -372,7 +370,7 @@ def _element_constructor_(self, x): """ return self.retract(self.ambient()(x)) - def __init__(self, category = None): + def __init__(self, category=None): r""" This quotient of the left zero semigroup of integers obtained by setting `x=42` for any `x\geq 42`. @@ -396,7 +394,7 @@ def __init__(self, category = None): """ if category is None: category = Semigroups().Quotients() - Parent.__init__(self, category = category) + Parent.__init__(self, category=category) def _repr_(self): r""" @@ -526,14 +524,14 @@ def retract(self, x): assert x in self.ambient() and x.value in ZZ if x.value > 42: return self.the_answer() - else: - return self.element_class(self, x) + return self.element_class(self, x) class Element(ElementWrapper): pass + class IncompleteSubquotientSemigroup(UniqueRepresentation,Parent): - def __init__(self, category = None): + def __init__(self, category=None): r""" An incompletely implemented subquotient semigroup, for testing purposes diff --git a/src/sage/categories/examples/semigroups_cython.pyx b/src/sage/categories/examples/semigroups_cython.pyx index ffd8dad8342..ee7a03ddc82 100644 --- a/src/sage/categories/examples/semigroups_cython.pyx +++ b/src/sage/categories/examples/semigroups_cython.pyx @@ -57,7 +57,7 @@ cdef class LeftZeroSemigroupElement(Element): sage: x = S(3) sage: TestSuite(x).run() """ - Element.__init__(self, parent = parent) + Element.__init__(self, parent=parent) self._value = value def _repr_(self): @@ -214,6 +214,6 @@ class LeftZeroSemigroup(LeftZeroSemigroupPython): Category of idempotent semigroups sage: TestSuite(S).run() """ - Parent.__init__(self, category = IdempotentSemigroups()) + Parent.__init__(self, category=IdempotentSemigroups()) Element = LeftZeroSemigroupElement diff --git a/src/sage/categories/examples/sets_cat.py b/src/sage/categories/examples/sets_cat.py index a9e8933901d..eee40b1bfbe 100644 --- a/src/sage/categories/examples/sets_cat.py +++ b/src/sage/categories/examples/sets_cat.py @@ -91,7 +91,7 @@ def __init__(self): sage: P is Sets().example() True """ - Parent.__init__(self, facade = IntegerRing(), category = Sets()) + Parent.__init__(self, facade=IntegerRing(), category=Sets()) def _repr_(self): """ @@ -172,7 +172,7 @@ def __init__(self): sage: P = Sets().example("inherits") """ - Parent.__init__(self, category = Sets()) + Parent.__init__(self, category=Sets()) def _repr_(self): """ @@ -496,7 +496,7 @@ def __init__(self): sage: P(13) + 1 == 14 True """ - Parent.__init__(self, category = Sets()) + Parent.__init__(self, category=Sets()) from sage.rings.integer_ring import IntegerRing from sage.categories.homset import Hom self.mor = Hom(self, IntegerRing())(lambda z: z.value) @@ -665,7 +665,7 @@ def __init__(self): sage: P = Sets().example("inherits") """ - Parent.__init__(self, facade = IntegerRing(), category = Sets()) + Parent.__init__(self, facade=IntegerRing(), category=Sets()) def _repr_(self): """ diff --git a/src/sage/categories/examples/with_realizations.py b/src/sage/categories/examples/with_realizations.py index d322aba065a..e72b2f5b3d0 100644 --- a/src/sage/categories/examples/with_realizations.py +++ b/src/sage/categories/examples/with_realizations.py @@ -169,7 +169,7 @@ def __init__(self, R, S): assert(R in Rings()) self._base = R # Won't be needed when CategoryObject won't override anymore base_ring self._S = S - Parent.__init__(self, category = Algebras(R).Commutative().WithRealizations()) + Parent.__init__(self, category=Algebras(R).Commutative().WithRealizations()) # Initializes the bases and change of bases of ``self`` diff --git a/src/sage/categories/filtered_modules_with_basis.py b/src/sage/categories/filtered_modules_with_basis.py index bd75b8f8e8a..336c1c326c8 100644 --- a/src/sage/categories/filtered_modules_with_basis.py +++ b/src/sage/categories/filtered_modules_with_basis.py @@ -178,9 +178,9 @@ def basis(self, d=None): sage: E. = ExteriorAlgebra(QQ) sage: E.basis() - Lazy family (Term map from Subsets of {0, 1} to + Lazy family (Term map from Subsets of {0,1} to The exterior algebra of rank 2 over Rational Field(i))_{i in - Subsets of {0, 1}} + Subsets of {0,1}} """ if d is None: from sage.sets.family import Family diff --git a/src/sage/categories/finite_coxeter_groups.py b/src/sage/categories/finite_coxeter_groups.py index 1cf0d84a1e7..270341c2262 100644 --- a/src/sage/categories/finite_coxeter_groups.py +++ b/src/sage/categories/finite_coxeter_groups.py @@ -14,6 +14,7 @@ from sage.categories.category_with_axiom import CategoryWithAxiom from sage.categories.coxeter_groups import CoxeterGroups + class FiniteCoxeterGroups(CategoryWithAxiom): r""" The category of finite Coxeter groups. @@ -474,11 +475,12 @@ def weak_poset(self, side="right", facade=False): from sage.combinat.posets.posets import Poset from sage.combinat.posets.lattices import LatticePoset if side == "twosided": - covers = tuple([u, v] for u in self for v in u.upper_covers(side="left")+u.upper_covers(side="right") ) - return Poset((self, covers), cover_relations = True, facade = facade) - else: - covers = tuple([u, v] for u in self for v in u.upper_covers(side=side) ) - return LatticePoset((self, covers), cover_relations = True, facade = facade) + covers = tuple([u, v] for u in self for v in u.upper_covers(side="left") + u.upper_covers(side="right")) + return Poset((self, covers), cover_relations=True, + facade=facade) + covers = tuple([u, v] for u in self for v in u.upper_covers(side=side)) + return LatticePoset((self, covers), cover_relations=True, + facade=facade) weak_lattice = weak_poset @@ -827,11 +829,11 @@ def coxeter_knuth_neighbor(self, w): sage: w.coxeter_knuth_neighbor(word) Traceback (most recent call last): ... - NotImplementedError: This has only been implemented in finite type A so far! + NotImplementedError: this has only been implemented in finite type A so far """ C = self.parent().cartan_type() if not C[0] == 'A': - raise NotImplementedError("This has only been implemented in finite type A so far!") + raise NotImplementedError("this has only been implemented in finite type A so far") d = [] for i in range(2,len(w)): v = [j for j in w] @@ -889,7 +891,7 @@ def coxeter_knuth_graph(self): sage: w.coxeter_knuth_graph() Traceback (most recent call last): ... - NotImplementedError: This has only been implemented in finite type A so far! + NotImplementedError: this has only been implemented in finite type A so far """ from sage.graphs.graph import Graph R = [tuple(v) for v in self.reduced_words()] diff --git a/src/sage/categories/finite_crystals.py b/src/sage/categories/finite_crystals.py index cf78401065d..072df1f9c53 100644 --- a/src/sage/categories/finite_crystals.py +++ b/src/sage/categories/finite_crystals.py @@ -71,7 +71,7 @@ def extra_super_categories(self): """ return [FiniteEnumeratedSets()] - def example(self, n = 3): + def example(self, n=3): """ Returns an example of highest weight crystals, as per :meth:`Category.example`. diff --git a/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py b/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py index ab72f20f970..2edc78c26e3 100644 --- a/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py +++ b/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py @@ -971,7 +971,7 @@ def lower_central_series(self, submodule=False): else: L = [self] while L[-1].dimension() > 0: - s = self.product_space(L[-1], submodule = submodule) + s = self.product_space(L[-1], submodule=submodule) if L[-1].dimension() == s.dimension(): break L.append(s) diff --git a/src/sage/categories/finite_enumerated_sets.py b/src/sage/categories/finite_enumerated_sets.py index e35a55c65b4..4a872cf2fbf 100644 --- a/src/sage/categories/finite_enumerated_sets.py +++ b/src/sage/categories/finite_enumerated_sets.py @@ -187,13 +187,7 @@ def _cardinality_from_list(self, *ignored_args, **ignored_kwds): sage: C._cardinality_from_list(algorithm='testing') 3 """ - # We access directly the cache self._list to bypass the - # copy that self.list() currently does each time. - try: - lst = self._list - except AttributeError: - lst = self.list() - return Integer(len(lst)) + return Integer(len(self.tuple())) def _unrank_from_list(self, r): """ @@ -217,42 +211,36 @@ def _unrank_from_list(self, r): sage: C._unrank_from_list(1) 2 """ - # We access directly the cache self._list to bypass the - # copy that self.list() currently does each time. - try: - lst = self._list - except AttributeError: - lst = self.list() + lst = self.tuple() try: return lst[r] except IndexError: - raise ValueError("the value must be between %s and %s inclusive"%(0,len(lst)-1)) + raise ValueError("the value must be in the range from %s to %s" % (0, len(lst) - 1)) - def list(self): + def tuple(self): r""" - Return a list of the elements of ``self``. - - The elements of set ``x`` is created and cashed on the fist call - of ``x.list()``. Then each call of ``x.list()`` returns a new list - from the cashed result. Thus in looping, it may be better to do - ``for e in x:``, not ``for e in x.list():``. - - .. SEEALSO:: :meth:`_list_from_iterator`, :meth:`_cardinality_from_list`, - :meth:`_iterator_from_list`, and :meth:`_unrank_from_list` + Return a :class:`tuple`of the elements of ``self``. EXAMPLES:: sage: C = FiniteEnumeratedSets().example() - sage: C.list() - [1, 2, 3] + sage: C.tuple() + (1, 2, 3) + sage: C.tuple() is C.tuple() + True """ + # Simpler implementation because it does not have to check whether cardinality is finite try: # shortcut if self._list is not None: - return list(self._list) + return self._tuple_from_list() except AttributeError: pass - return self._list_from_iterator() - _list_default = list # needed by the check system. + + if self.list != self._list_default: + return tuple(self.list()) + + return self._tuple_from_iterator() + _tuple_default = tuple def _list_from_iterator(self): r""" @@ -323,7 +311,7 @@ def _list_from_iterator(self): sage: class FreshExample(Example): pass sage: C = FreshExample(); C.rename("FreshExample") sage: C.list - + sage: C.unrank sage: C.cardinality @@ -331,7 +319,7 @@ def _list_from_iterator(self): sage: l1 = C.list(); l1 [1, 2, 3] sage: C.list - + sage: C.unrank sage: C.cardinality @@ -355,11 +343,12 @@ def _list_from_iterator(self): return list(self._list) except AttributeError: pass - result = list(self.__iter__()) + result = tuple(self.__iter__()) try: self._list = result self.__iter__ = self._iterator_from_list self.cardinality = self._cardinality_from_list + self.tuple = self._tuple_from_list self.unrank = self._unrank_from_list except AttributeError: pass @@ -391,12 +380,12 @@ def unrank_range(self, start=None, stop=None, step=None): [1, 2, 3, 4] """ try: - return self._list[start:stop:step] + return list(self._list[start:stop:step]) except AttributeError: pass card = self.cardinality() # This may set the list try: - return self._list[start:stop:step] + return list(self._list[start:stop:step]) except AttributeError: pass if start is None and stop is not None and stop >= 0 and step is None: @@ -404,7 +393,7 @@ def unrank_range(self, start=None, stop=None, step=None): it = self.__iter__() return [next(it) for j in range(stop)] return self.list() - return self.list()[start:stop:step] + return list(self.tuple()[start:stop:step]) def iterator_range(self, start=None, stop=None, step=None): r""" @@ -461,9 +450,8 @@ def iterator_range(self, start=None, stop=None, step=None): yield x return if L is None: - L = self.list() - for x in L[start:stop:step]: - yield x + L = self.tuple() + yield from L[start:stop:step] def _random_element_from_unrank(self): """ @@ -492,7 +480,7 @@ def _random_element_from_unrank(self): c = self.cardinality() r = randint(0, c-1) return self.unrank(r) - #Set the default implementation of random + # Set the default implementation of random_element random_element = _random_element_from_unrank @cached_method diff --git a/src/sage/categories/finite_groups.py b/src/sage/categories/finite_groups.py index 0f1ecbc0a9c..42415233567 100644 --- a/src/sage/categories/finite_groups.py +++ b/src/sage/categories/finite_groups.py @@ -134,7 +134,7 @@ def cayley_graph_disabled(self, connecting_set=None): else: for g in connecting_set: if g not in self: - raise RuntimeError("Each element of the connecting set must be in the group!") + raise RuntimeError("each element of the connecting set must be in the group") connecting_set = [self(g) for g in connecting_set] from sage.graphs.digraph import DiGraph arrows = {} diff --git a/src/sage/categories/finite_permutation_groups.py b/src/sage/categories/finite_permutation_groups.py index 3f5fcfd1fe7..9269bc8ac1c 100644 --- a/src/sage/categories/finite_permutation_groups.py +++ b/src/sage/categories/finite_permutation_groups.py @@ -104,7 +104,7 @@ class ParentMethods: # - Port features from MuPAD-Combinat, lib/DOMAINS/CATEGORIES/PermutationGroup.mu # - Move here generic code from sage/groups/perm_gps/permgroup.py - def cycle_index(self, parent = None): + def cycle_index(self, parent=None): r""" Return the *cycle index* of ``self``. @@ -274,14 +274,13 @@ def profile_series(self, variable='z'): sage: u = var('u') # optional - sage.symbolic sage: D8.profile_series(u).parent() # optional - sage.symbolic Symbolic Ring - """ from sage.rings.integer_ring import ZZ if isinstance(variable, str): variable = ZZ[variable].gen() cycle_poly = self.cycle_index() - return cycle_poly.expand(2).subs(x0 = 1, x1 = variable) + return cycle_poly.expand(2).subs(x0=1, x1=variable) profile_polynomial = profile_series diff --git a/src/sage/categories/finite_posets.py b/src/sage/categories/finite_posets.py index 1f6b49d2e15..caf76e44893 100644 --- a/src/sage/categories/finite_posets.py +++ b/src/sage/categories/finite_posets.py @@ -419,7 +419,7 @@ def order_ideal_complement_generators(self, antichain, direction='up'): else: I = self.order_filter(antichain) I_comp = set(self).difference(I) - return set(self.order_ideal_generators(I_comp, direction = direction)) + return set(self.order_ideal_generators(I_comp, direction=direction)) panyushev_complement = order_ideal_complement_generators @@ -1314,7 +1314,7 @@ def birational_rowmotion(self, labelling): l = self.birational_toggle(v, l) return l - def panyushev_orbits(self, element_constructor = set): + def panyushev_orbits(self, element_constructor=set): r""" Return the Panyushev orbits of antichains in ``self``. @@ -1362,21 +1362,21 @@ def panyushev_orbits(self, element_constructor = set): """ # TODO: implement a generic function taking a set and # bijections on this set, and returning the orbits. - AC = set(self.antichains(element_constructor = frozenset)) + AC = set(self.antichains(element_constructor=frozenset)) orbits = [] while AC: A = AC.pop() - orbit = [ A ] + orbit = [A] while True: A = frozenset(self.order_ideal_complement_generators(A)) if A not in AC: break - orbit.append( A ) - AC.remove( A ) - orbits.append([element_constructor(_) for _ in orbit]) + orbit.append(A) + AC.remove(A) + orbits.append([element_constructor(elt) for elt in orbit]) return orbits - def rowmotion_orbits(self, element_constructor = set): + def rowmotion_orbits(self, element_constructor=set): r""" Return the rowmotion orbits of order ideals in ``self``. @@ -1415,8 +1415,9 @@ def rowmotion_orbits(self, element_constructor = set): sage: P.rowmotion_orbits(element_constructor=tuple) [[()]] """ - pan_orbits = self.panyushev_orbits(element_constructor = list) - return [[element_constructor(self.order_ideal(oideal)) for oideal in orbit] for orbit in pan_orbits] + pan_orbits = self.panyushev_orbits(element_constructor=list) + return [[element_constructor(self.order_ideal(oideal)) + for oideal in orbit] for orbit in pan_orbits] def rowmotion_orbits_plots(self): r""" @@ -1446,10 +1447,9 @@ def rowmotion_orbits_plots(self): oiplot = self.order_ideal_plot(oi) orb_plots.append(oiplot) plot_of_orb_plots.append(orb_plots) - return graphics_array(plot_of_orb_plots, ncols = max_orbit_size) - + return graphics_array(plot_of_orb_plots, ncols=max_orbit_size) - def toggling_orbits(self, vs, element_constructor = set): + def toggling_orbits(self, vs, element_constructor=set): r""" Return the orbits of order ideals in ``self`` under the operation of toggling the vertices ``vs[0], vs[1], ...`` @@ -1533,9 +1533,10 @@ def toggling_orbits_plots(self, vs): oiplot = self.order_ideal_plot(oi) orb_plots.append(oiplot) plot_of_orb_plots.append(orb_plots) - return graphics_array(plot_of_orb_plots, ncols = max_orbit_size) + return graphics_array(plot_of_orb_plots, ncols=max_orbit_size) - def panyushev_orbit_iter(self, antichain, element_constructor=set, stop=True, check=True): + def panyushev_orbit_iter(self, antichain, element_constructor=set, + stop=True, check=True): r""" Iterate over the Panyushev orbit of an antichain ``antichain`` of ``self``. diff --git a/src/sage/categories/graded_algebras_with_basis.py b/src/sage/categories/graded_algebras_with_basis.py index e80c1b00bf4..f992228ba81 100644 --- a/src/sage/categories/graded_algebras_with_basis.py +++ b/src/sage/categories/graded_algebras_with_basis.py @@ -126,6 +126,23 @@ def free_graded_module(self, generator_degrees, names=None): from sage.modules.fp_graded.free_module import FreeGradedModule return FreeGradedModule(self, generator_degrees, names=names) + def formal_series_ring(self): + r""" + Return the completion of all formal linear combinations of + ``self`` with finite linear combinations in each homogeneous + degree (computed lazily). + + EXAMPLES:: + + sage: NCSF = NonCommutativeSymmetricFunctions(QQ) + sage: S = NCSF.Complete() + sage: L = S.formal_series_ring() + sage: L + Lazy completion of Non-Commutative Symmetric Functions over + the Rational Field in the Complete basis + """ + from sage.rings.lazy_series_ring import LazyCompletionGradedAlgebra + return LazyCompletionGradedAlgebra(self) class ElementMethods: pass @@ -168,10 +185,10 @@ def one_basis(self): sage: A. = ExteriorAlgebra(QQ) sage: A.one_basis() - () + 0 sage: B = tensor((A, A, A)) sage: B.one_basis() - ((), (), ()) + (0, 0, 0) sage: B.one() 1 # 1 # 1 """ diff --git a/src/sage/categories/groupoid.py b/src/sage/categories/groupoid.py index d9a26227995..4c6e3ae6042 100644 --- a/src/sage/categories/groupoid.py +++ b/src/sage/categories/groupoid.py @@ -27,10 +27,9 @@ class Groupoid(CategoryWithParameters): sage: Groupoid(DihedralGroup(3)) Groupoid with underlying set Dihedral group of order 6 as a permutation group - """ - def __init__(self, G = None): + def __init__(self, G=None): """ TESTS:: diff --git a/src/sage/categories/groups.py b/src/sage/categories/groups.py index 809ef93e3e1..ef681dbb07a 100644 --- a/src/sage/categories/groups.py +++ b/src/sage/categories/groups.py @@ -150,7 +150,7 @@ def _test_inverse(self, **options): tester.assertEqual(x * ~x, self.one()) tester.assertEqual(~x * x, self.one()) - def semidirect_product(self, N, mapping, check = True): + def semidirect_product(self, N, mapping, check=True): r""" The semi-direct product of two groups @@ -162,7 +162,7 @@ def semidirect_product(self, N, mapping, check = True): ... NotImplementedError: semidirect product of General Linear Group of degree 4 over Rational Field and General Linear Group of degree 4 over Rational Field not yet implemented """ - raise NotImplementedError("semidirect product of %s and %s not yet implemented"%(self, N)) + raise NotImplementedError("semidirect product of %s and %s not yet implemented" % (self, N)) def holomorph(self): r""" @@ -640,27 +640,6 @@ def order(self): from sage.misc.misc_c import prod return prod(c.cardinality() for c in self.cartesian_factors()) - class ElementMethods: - def multiplicative_order(self): - r""" - Return the multiplicative order of this element. - - EXAMPLES:: - - sage: G1 = SymmetricGroup(3) - sage: G2 = SL(2,3) - sage: G = cartesian_product([G1,G2]) - sage: G((G1.gen(0), G2.gen(1))).multiplicative_order() - 12 - """ - from sage.rings.infinity import Infinity - orders = [x.multiplicative_order() for x in self.cartesian_factors()] - if any(o is Infinity for o in orders): - return Infinity - else: - from sage.arith.functions import LCM_list - return LCM_list(orders) - class Topological(TopologicalSpacesCategory): """ Category of topological groups. diff --git a/src/sage/categories/hecke_modules.py b/src/sage/categories/hecke_modules.py index aa2ce2aea7b..88331973496 100644 --- a/src/sage/categories/hecke_modules.py +++ b/src/sage/categories/hecke_modules.py @@ -149,7 +149,7 @@ def _Hom_(self, Y, category): if category is not None and not category.is_subcategory(HeckeModules(self.base_ring())): raise TypeError("%s is not a subcategory of %s"%(category, HeckeModules(self.base_ring()))) from sage.modular.hecke.homspace import HeckeModuleHomspace - return HeckeModuleHomspace(self, Y, category = category) + return HeckeModuleHomspace(self, Y, category=category) class Homsets(HomsetsCategory): """ diff --git a/src/sage/categories/highest_weight_crystals.py b/src/sage/categories/highest_weight_crystals.py index 266a1cab6fb..e063e388998 100644 --- a/src/sage/categories/highest_weight_crystals.py +++ b/src/sage/categories/highest_weight_crystals.py @@ -180,9 +180,9 @@ def lowest_weight_vectors(self): """ return tuple(g for g in self if g.is_lowest_weight()) - def __iter__(self, index_set=None, max_depth = float("inf")): + def __iter__(self, index_set=None, max_depth=float("inf")): """ - Returns the iterator of ``self``. + Return the iterator of ``self``. INPUT: diff --git a/src/sage/categories/homset.py b/src/sage/categories/homset.py index bfd78e8d8a5..4fe021b86ca 100644 --- a/src/sage/categories/homset.py +++ b/src/sage/categories/homset.py @@ -580,7 +580,7 @@ class Homset(Set_generic): sage: loads(dumps(H)) == H True """ - def __init__(self, X, Y, category=None, base = None, check=True): + def __init__(self, X, Y, category=None, base=None, check=True): r""" TESTS:: @@ -649,8 +649,8 @@ def __init__(self, X, Y, category=None, base = None, check=True): # See also #15801. base = X.base_ring() - Parent.__init__(self, base = base, - category = category.Endsets() if X is Y else category.Homsets()) + Parent.__init__(self, base=base, + category=category.Endsets() if X is Y else category.Homsets()) def __reduce__(self): """ @@ -828,7 +828,7 @@ def _element_constructor_(self, x, check=None, **options): sage: H = Hom(Set([1,2,3]), Set([1,2,3])) sage: f = H( lambda x: 4-x ) sage: f.parent() - Set of Morphisms from {1, 2, 3} to {1, 2, 3} in Category of finite sets + Set of Morphisms from {1, 2, 3} to {1, 2, 3} in Category of finite enumerated sets sage: f(1), f(2), f(3) # todo: not implemented sage: H = Hom(ZZ, QQ, Sets()) @@ -1224,7 +1224,8 @@ def reversed(self): sage: type(H.reversed()) """ - return Hom(self.codomain(), self.domain(), category = self.homset_category()) + return Hom(self.codomain(), self.domain(), + category=self.homset_category()) # Really needed??? @@ -1249,7 +1250,8 @@ def __init__(self, X, Y, category=None, check=True, base=None): """ if base is None: base = X.base_ring() - Homset.__init__(self, X, Y, check=check, category=category, base = base) + Homset.__init__(self, X, Y, check=check, category=category, base=base) + def is_Homset(x): """ diff --git a/src/sage/categories/hopf_algebras_with_basis.py b/src/sage/categories/hopf_algebras_with_basis.py index 161ea71f2b9..79a0b69ce14 100644 --- a/src/sage/categories/hopf_algebras_with_basis.py +++ b/src/sage/categories/hopf_algebras_with_basis.py @@ -115,7 +115,7 @@ class HopfAlgebrasWithBasis(CategoryWithAxiom_over_base_ring): sage: TestSuite(C).run() """ - def example(self, G = None): + def example(self, G=None): """ Returns an example of algebra with basis:: @@ -212,7 +212,8 @@ def antipode(self): """ if self.antipode_on_basis is not NotImplemented: # Should give the information that this is an anti-morphism of algebra - return self._module_morphism(self.antipode_on_basis, codomain = self) + return self._module_morphism(self.antipode_on_basis, + codomain=self) elif hasattr(self, "antipode_by_coercion"): return self.antipode_by_coercion diff --git a/src/sage/categories/infinite_enumerated_sets.py b/src/sage/categories/infinite_enumerated_sets.py index cc3a09e9409..581faab5ff4 100644 --- a/src/sage/categories/infinite_enumerated_sets.py +++ b/src/sage/categories/infinite_enumerated_sets.py @@ -47,7 +47,7 @@ class ParentMethods: def random_element(self): """ - Returns an error since self is an infinite enumerated set. + Raise an error because ``self`` is an infinite enumerated set. EXAMPLES:: @@ -61,9 +61,23 @@ def random_element(self): """ raise NotImplementedError("infinite set") + def tuple(self): + """ + Raise an error because ``self`` is an infinite enumerated set. + + EXAMPLES:: + + sage: NN = InfiniteEnumeratedSets().example() + sage: NN.tuple() + Traceback (most recent call last): + ... + NotImplementedError: cannot list an infinite set + """ + raise NotImplementedError("cannot list an infinite set") + def list(self): """ - Returns an error since self is an infinite enumerated set. + Raise an error because ``self`` is an infinite enumerated set. EXAMPLES:: diff --git a/src/sage/categories/integral_domains.py b/src/sage/categories/integral_domains.py index 9daa68ff25e..8cf87ffe94f 100644 --- a/src/sage/categories/integral_domains.py +++ b/src/sage/categories/integral_domains.py @@ -96,17 +96,23 @@ def _contains_helper(cls): return Category_contains_method_by_parent_class(cls()) class ParentMethods: - def is_integral_domain(self): - """ - Return True, since this in an object of the category of integral domains. + def is_integral_domain(self, proof=True): + r""" + Return ``True``, since this in an object of the category + of integral domains. EXAMPLES:: sage: QQ.is_integral_domain() True - sage: Parent(QQ,category=IntegralDomains()).is_integral_domain() + sage: Parent(QQ, category=IntegralDomains()).is_integral_domain() True + sage: L. = LazyLaurentSeriesRing(QQ) + sage: L.is_integral_domain() + True + sage: L.is_integral_domain(proof=True) + True """ return True @@ -138,3 +144,4 @@ def _test_fraction_field(self, **options): class ElementMethods: pass + diff --git a/src/sage/categories/loop_crystals.py b/src/sage/categories/loop_crystals.py index 00bdf0a911d..e065b7ef4f6 100644 --- a/src/sage/categories/loop_crystals.py +++ b/src/sage/categories/loop_crystals.py @@ -59,7 +59,7 @@ def super_categories(self): """ return [Crystals()] - def example(self, n = 3): + def example(self, n=3): """ Return an example of Kirillov-Reshetikhin crystals, as per :meth:`Category.example`. @@ -456,7 +456,7 @@ def b_sharp(self): bsharp = None for b in self: phi = b.Phi() - if phi.support() == [0] and phi[0] < ell: + if list(phi.support()) == [0] and phi[0] < ell: bsharp = b ell = phi[0] return bsharp diff --git a/src/sage/categories/magmas.py b/src/sage/categories/magmas.py index c4a35fe6f1a..c1f255a6eb9 100644 --- a/src/sage/categories/magmas.py +++ b/src/sage/categories/magmas.py @@ -671,9 +671,7 @@ def __invert__(self): ZeroDivisionError: rational division by zero sage: ~C([2,2,2,2]) - Traceback (most recent call last): - ... - TypeError: no conversion of this rational to integer + (1/2, 1/2, 0.500000000000000, 3) """ # variant without coercion: # return self.parent()._cartesian_product_of_elements( diff --git a/src/sage/categories/magmatic_algebras.py b/src/sage/categories/magmatic_algebras.py index 0a7cd2755a3..ef82ba4319b 100644 --- a/src/sage/categories/magmatic_algebras.py +++ b/src/sage/categories/magmatic_algebras.py @@ -144,7 +144,7 @@ def algebra_generators(self): """ return self.basis() - @abstract_method(optional = True) + @abstract_method(optional=True) def product_on_basis(self, i, j): """ The product of the algebra on the basis (optional). diff --git a/src/sage/categories/modules.py b/src/sage/categories/modules.py index d37b4812209..2e3eb65dc3d 100644 --- a/src/sage/categories/modules.py +++ b/src/sage/categories/modules.py @@ -12,6 +12,7 @@ # ***************************************************************************** from sage.misc.cachefunc import cached_method +from sage.misc.abstract_method import abstract_method from sage.misc.lazy_import import LazyImport from sage.categories.category_with_axiom import CategoryWithAxiom_over_base_ring from sage.categories.morphism import SetMorphism @@ -19,7 +20,7 @@ from sage.categories.homset import Hom from .category import Category from .category_types import Category_module -from sage.categories.tensor import TensorProductsCategory, tensor +from sage.categories.tensor import TensorProductsCategory, TensorProductFunctor, tensor from .dual import DualObjectsCategory from sage.categories.cartesian_product import CartesianProductsCategory from sage.categories.sets_cat import Sets @@ -675,6 +676,37 @@ def module_morphism(self, *, function, category=None, codomain, **keywords): category = Modules(self.base_ring()) return SetMorphism(Hom(self, codomain, category), function) + def quotient(self, submodule, check=True, **kwds): + r""" + Construct the quotient module ``self`` / ``submodule``. + + INPUT: + + - ``submodule`` -- a submodule with basis of ``self``, or + something that can be turned into one via + ``self.submodule(submodule)`` + + - ``check``, other keyword arguments: passed on to + :meth:`quotient_module`. + + This method just delegates to :meth:`quotient_module`. + Classes implementing modules should override that method. + + Parents in categories with additional structure may override + :meth:`quotient`. For example, in algebras, :meth:`quotient` will + be the same as :meth:`quotient_ring`. + + EXAMPLES:: + + sage: C = CombinatorialFreeModule(QQ, ['a','b','c']) + sage: TA = TensorAlgebra(C) + sage: TA.quotient + + + """ + return self.quotient_module(submodule, check=check, **kwds) + class ElementMethods: pass @@ -891,3 +923,56 @@ def extra_super_categories(self): [Category of modules over Integer Ring] """ return [self.base_category()] + + class ParentMethods: + """ + Implement operations on tensor products of modules. + """ + def construction(self): + """ + Return the construction of ``self``. + + EXAMPLES:: + + sage: A = algebras.Free(QQ,2) + sage: T = A.tensor(A) + sage: T.construction() + (The tensor functorial construction, + (Free Algebra on 2 generators (None0, None1) over Rational Field, + Free Algebra on 2 generators (None0, None1) over Rational Field)) + """ + try: + factors = self.tensor_factors() + except (TypeError, NotImplementedError): + from sage.misc.superseded import deprecation + deprecation(34393, "implementations of Modules().TensorProducts() now must define the method tensor_factors") + return None + return (TensorProductFunctor(), + factors) + + @abstract_method(optional=True) + def tensor_factors(self): + """ + Return the tensor factors of this tensor product. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(ZZ, [1,2]) + sage: F.rename("F") + sage: G = CombinatorialFreeModule(ZZ, [3,4]) + sage: G.rename("G") + sage: T = tensor([F, G]); T + F # G + sage: T.tensor_factors() + (F, G) + + TESTS:: + + sage: M = CombinatorialFreeModule(ZZ, ((1, 1), (1, 2), (2, 1), (2, 2)), + ....: category=ModulesWithBasis(ZZ).FiniteDimensional().TensorProducts()) + sage: M.construction() + doctest:warning... + DeprecationWarning: implementations of Modules().TensorProducts() now must define the method tensor_factors + See https://trac.sagemath.org/34393 for details. + (VectorFunctor, Integer Ring) + """ diff --git a/src/sage/categories/modules_with_basis.py b/src/sage/categories/modules_with_basis.py index 49d91c17049..26b51ef4b87 100644 --- a/src/sage/categories/modules_with_basis.py +++ b/src/sage/categories/modules_with_basis.py @@ -1064,7 +1064,7 @@ def sum_of_monomials(self): A map to Free module generated by {'a', 'b', 'c'} over Rational Field """ # domain = iterables of basis indices of self. - return PoorManMap(self._sum_of_monomials, codomain = self) + return PoorManMap(self._sum_of_monomials, codomain=self) def monomial_or_zero_if_none(self, i): """ @@ -1506,9 +1506,7 @@ def __len__(self): sage: len(z) 4 """ - zero = self.parent().base_ring().zero() - return len([key for key, coeff in self.monomial_coefficients(copy=False).items() - if coeff != zero]) + return len(self.support()) def length(self): """ @@ -1534,7 +1532,7 @@ def length(self): def support(self): """ - Return a list of the objects indexing the basis of + Return an iterable of the objects indexing the basis of ``self.parent()`` whose corresponding coefficients of ``self`` are non-zero. @@ -1555,9 +1553,19 @@ def support(self): sage: sorted(z.support()) [[1], [1, 1, 1], [2, 1], [4]] """ - zero = self.parent().base_ring().zero() - return [key for key, coeff in self.monomial_coefficients(copy=False).items() - if coeff != zero] + try: + return self._support_view + except AttributeError: + from sage.structure.support_view import SupportView + zero = self.parent().base_ring().zero() + mc = self.monomial_coefficients(copy=False) + support_view = SupportView(mc, zero=zero) + try: + # Try to cache it for next time, but this may fail for Cython classes + self._support_view = support_view + except AttributeError: + pass + return support_view def monomials(self): """ @@ -2186,7 +2194,7 @@ def __call_on_basis__(self, **options): sage: H.zero().category_for() Category of finite dimensional vector spaces with basis over Rational Field """ - return self.domain().module_morphism(codomain = self.codomain(), + return self.domain().module_morphism(codomain=self.codomain(), **options) class MorphismMethods: @@ -2307,7 +2315,7 @@ class ElementMethods: with basis. """ - def apply_multilinear_morphism(self, f, codomain = None): + def apply_multilinear_morphism(self, f, codomain=None): r""" Return the result of applying the morphism induced by ``f`` to ``self``. diff --git a/src/sage/categories/monoids.py b/src/sage/categories/monoids.py index b630ee04ee5..93638b04078 100644 --- a/src/sage/categories/monoids.py +++ b/src/sage/categories/monoids.py @@ -252,18 +252,18 @@ def _div_(left, right): sage: c1._div_(c2) (x0*x1^-1, x1*x0^-1) - With this implementation, division will fail as soon - as ``right`` is not invertible, even if ``right`` + With this default implementation, division will fail as + soon as ``right`` is not invertible, even if ``right`` actually divides ``left``:: - sage: x = cartesian_product([2, 1]) + sage: x = cartesian_product([2, 0]) sage: y = cartesian_product([1, 1]) sage: x / y - (2, 1) - sage: x / x + (2, 0) + sage: y / x Traceback (most recent call last): ... - TypeError: no conversion of this rational to integer + ZeroDivisionError: rational division by zero TESTS:: @@ -354,6 +354,37 @@ def powers(self, n): l.append(x) return l + def __invert__(self): + r""" + Return the multiplicative inverse of ``self``. + + There is no default implementation, to avoid conflict + with the default implementation of ``_div_``. + + EXAMPLES:: + + sage: A = Matrix([[1, 0], [1, 1]]) + sage: ~A + [ 1 0] + [-1 1] + """ + raise NotImplementedError("please implement __invert__") + + def inverse(self): + """ + Return the multiplicative inverse of ``self``. + + This is an alias for inversion, which can also be invoked + by ``~x`` for an element ``x``. + + EXAMPLES:: + + sage: AA(sqrt(~2)).inverse() + 1.414213562373095? + """ + # Nota Bene: Element classes should implement ``__invert__`` only. + return self.__invert__() + class Commutative(CategoryWithAxiom): r""" Category of commutative (abelian) monoids. @@ -639,3 +670,40 @@ def lift(i, gen): lambda g: (i, g)) for i, M in enumerate(F)]) return Family(gens_prod, lift, name="gen") + + class ElementMethods: + def multiplicative_order(self): + r""" + Return the multiplicative order of this element. + + EXAMPLES:: + + sage: G1 = SymmetricGroup(3) + sage: G2 = SL(2,3) + sage: G = cartesian_product([G1,G2]) + sage: G((G1.gen(0), G2.gen(1))).multiplicative_order() + 12 + """ + from sage.rings.infinity import Infinity + orders = [x.multiplicative_order() for x in self.cartesian_factors()] + if any(o is Infinity for o in orders): + return Infinity + else: + from sage.arith.functions import LCM_list + return LCM_list(orders) + + def __invert__(self): + """ + Return the inverse. + + EXAMPLES:: + + sage: a1 = Permutation((4,2,1,3)) + sage: a2 = SL(2,3)([2,1,1,1]) + sage: h = cartesian_product([a1,a2]) + sage: ~h + ([2, 4, 1, 3], [1 2] + [2 2]) + """ + build = self.parent()._cartesian_product_of_elements + return build([x.__invert__() for x in self.cartesian_factors()]) diff --git a/src/sage/categories/poor_man_map.py b/src/sage/categories/poor_man_map.py index 73c8032f065..507705fb9f9 100644 --- a/src/sage/categories/poor_man_map.py +++ b/src/sage/categories/poor_man_map.py @@ -57,7 +57,7 @@ class PoorManMap(sage.structure.sage_object.SageObject): True """ - def __init__(self, function, domain = None, codomain = None, name = None): + def __init__(self, function, domain=None, codomain=None, name=None): """ TESTS:: diff --git a/src/sage/categories/posets.py b/src/sage/categories/posets.py index 6ab5657e929..b09d920c863 100644 --- a/src/sage/categories/posets.py +++ b/src/sage/categories/posets.py @@ -100,7 +100,7 @@ def super_categories(self): """ return [Sets()] - def example(self, choice = None): + def example(self, choice=None): r""" Return examples of objects of ``Posets()``, as per :meth:`Category.example() @@ -241,7 +241,7 @@ def gt(self, x, y): """ return self.lt(y,x) - @abstract_method(optional = True) + @abstract_method(optional=True) def upper_covers(self, x): r""" Return the upper covers of `x`, that is, the elements `y` @@ -254,7 +254,7 @@ def upper_covers(self, x): [6, 15] """ - @abstract_method(optional = True) + @abstract_method(optional=True) def lower_covers(self, x): r""" Return the lower covers of `x`, that is, the elements `y` @@ -267,7 +267,7 @@ def lower_covers(self, x): [3, 5] """ - @abstract_method(optional = True) + @abstract_method(optional=True) def order_ideal(self, elements): r""" Return the order ideal in ``self`` generated by the elements @@ -285,7 +285,7 @@ def order_ideal(self, elements): [0, 1, 2, 3, 4, 5, 6, 7, 8, 10] """ - @abstract_method(optional = True) + @abstract_method(optional=True) def order_filter(self, elements): r""" Return the order filter generated by a list of elements. diff --git a/src/sage/categories/pushout.py b/src/sage/categories/pushout.py index c48ec6ec672..3136160f39d 100644 --- a/src/sage/categories/pushout.py +++ b/src/sage/categories/pushout.py @@ -2,6 +2,32 @@ Coercion via construction functors """ +# **************************************************************************** +# Copyright (C) 2007-2014 Robert Bradshaw +# 2007-2018 David Roe +# 2009-2013 Simon King +# 2010 John Cremona +# 2010-2011 Mike Hansen +# 2012 Julian Rueth +# 2013-2016 Peter Bruin +# 2014 Wilfried Luebbe +# 2015 Benjamin Hackl +# 2015 Daniel Krenn +# 2016-2020 Frédéric Chapoton +# 2017 Jori Mäntysalo +# 2018 Vincent Delecroix +# 2020 Marc Mezzarobba +# 2020-2022 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/ +# **************************************************************************** + +import operator + from sage.misc.lazy_import import lazy_import from sage.structure.coerce_exceptions import CoercionException from .functor import Functor, IdentityFunctor_generic @@ -1822,7 +1848,8 @@ class VectorFunctor(ConstructionFunctor): rank = 10 # ranking of functor, not rank of module. # This coincides with the rank of the matrix construction functor, but this is OK since they cannot both be applied in any order - def __init__(self, n, is_sparse=False, inner_product_matrix=None): + def __init__(self, n=None, is_sparse=False, inner_product_matrix=None, *, + with_basis='standard', basis_keys=None, name_mapping=None, latex_name_mapping=None): """ INPUT: @@ -1830,6 +1857,8 @@ def __init__(self, n, is_sparse=False, inner_product_matrix=None): - ``is_sparse`` (optional bool, default ``False``), create sparse implementation of modules - ``inner_product_matrix``: ``n`` by ``n`` matrix, used to compute inner products in the to-be-created modules + - ``name_mapping``, ``latex_name_mapping``: Dictionaries from base rings to names + - other keywords: see :func:`~sage.modules.free_module.FreeModule` TESTS:: @@ -1860,14 +1889,22 @@ def __init__(self, n, is_sparse=False, inner_product_matrix=None): self.n = n self.is_sparse = is_sparse self.inner_product_matrix = inner_product_matrix + self.with_basis = with_basis + self.basis_keys = basis_keys + if name_mapping is None: + name_mapping = {} + self.name_mapping = name_mapping + if latex_name_mapping is None: + latex_name_mapping = {} + self.latex_name_mapping = latex_name_mapping def _apply_functor(self, R): - """ + r""" Apply the functor to an object of ``self``'s domain. TESTS:: - sage: from sage.categories.pushout import VectorFunctor + sage: from sage.categories.pushout import VectorFunctor, pushout sage: F1 = VectorFunctor(3, inner_product_matrix = Matrix(3,3,range(9))) sage: M1 = F1(ZZ) # indirect doctest sage: M1.is_sparse() @@ -1885,9 +1922,31 @@ def _apply_functor(self, R): sage: v.inner_product(v) 14 + sage: M = FreeModule(ZZ, 4, with_basis=None, name='M') + sage: latex(M) + M + sage: M_QQ = pushout(M, QQ) + sage: latex(M_QQ) + M \otimes \Bold{Q} + """ from sage.modules.free_module import FreeModule - return FreeModule(R, self.n, sparse=self.is_sparse, inner_product_matrix=self.inner_product_matrix) + name = self.name_mapping.get(R, None) + latex_name = self.latex_name_mapping.get(R, None) + if name is None: + for base_ring, name in self.name_mapping.items(): + name = f'{name}_base_ext' + break + if latex_name is None: + from sage.misc.latex import latex + for base_ring, latex_name in self.latex_name_mapping.items(): + latex_name = fr'{latex_name} \otimes {latex(R)}' + break + if name is None and latex_name is None: + return FreeModule(R, self.n, sparse=self.is_sparse, inner_product_matrix=self.inner_product_matrix, + with_basis=self.with_basis, basis_keys=self.basis_keys) + return FreeModule(R, self.n, sparse=self.is_sparse, inner_product_matrix=self.inner_product_matrix, + with_basis=self.with_basis, basis_keys=self.basis_keys, name=name, latex_name=latex_name) def _apply_functor_to_morphism(self, f): """ @@ -1924,7 +1983,11 @@ def __eq__(self, other): """ if isinstance(other, VectorFunctor): return (self.n == other.n and - self.inner_product_matrix == other.inner_product_matrix) + self.inner_product_matrix == other.inner_product_matrix and + self.with_basis == other.with_basis and + self.basis_keys == other.basis_keys and + self.name_mapping == other.name_mapping and + self.latex_name_mapping == other.latex_name_mapping) return False def __ne__(self, other): @@ -1997,19 +2060,73 @@ def merge(self, other): [3 4 5] [6 7 8]' + Names are removed when they conflict:: + + sage: from sage.categories.pushout import VectorFunctor, pushout + sage: M_ZZx = FreeModule(ZZ['x'], 4, with_basis=None, name='M_ZZx') + sage: N_ZZx = FreeModule(ZZ['x'], 4, with_basis=None, name='N_ZZx') + sage: pushout(M_ZZx, QQ) + Rank-4 free module M_ZZx_base_ext over the Univariate Polynomial Ring in x over Rational Field + sage: pushout(M_ZZx, N_ZZx) + Rank-4 free module over the Univariate Polynomial Ring in x over Integer Ring + sage: pushout(pushout(M_ZZx, N_ZZx), QQ) + Rank-4 free module over the Univariate Polynomial Ring in x over Rational Field """ if not isinstance(other, VectorFunctor): return None + + if self.with_basis != other.with_basis: + return None + else: + with_basis = self.with_basis + + if self.basis_keys != other.basis_keys: + # TODO: If both are enumerated families, should we try to take the union of the families? + return None + else: + basis_keys = self.basis_keys + + is_sparse = self.is_sparse and other.is_sparse + if self.inner_product_matrix is None: - return VectorFunctor(self.n, self.is_sparse and other.is_sparse, other.inner_product_matrix) - if other.inner_product_matrix is None: - return VectorFunctor(self.n, self.is_sparse and other.is_sparse, self.inner_product_matrix) - # At this point, we know that the user wants to take care of the inner product. - # So, we only merge if both coincide: - if self.inner_product_matrix != other.inner_product_matrix: + inner_product_matrix = other.inner_product_matrix + elif other.inner_product_matrix is None: + inner_product_matrix = self.inner_product_matrix + elif self.inner_product_matrix != other.inner_product_matrix: + # At this point, we know that the user wants to take care of the inner product. + # So, we only merge if both coincide: return None else: - return VectorFunctor(self.n, self.is_sparse and other.is_sparse, self.inner_product_matrix) + inner_product_matrix = None + + if self.n != other.n: + return None + else: + n = self.n + + name_mapping = dict() + for base_ring, name in self.name_mapping.items(): + try: + other_name = other.name_mapping[base_ring] + except KeyError: + name_mapping[base_ring] = name + else: + if name == other_name: + name_mapping[base_ring] = name + + latex_name_mapping = dict() + for base_ring, latex_name in self.latex_name_mapping.items(): + try: + other_latex_name = other.latex_name_mapping[base_ring] + except KeyError: + latex_name_mapping[base_ring] = latex_name + else: + if latex_name == other_latex_name: + latex_name_mapping[base_ring] = latex_name + + return VectorFunctor(n, is_sparse, inner_product_matrix, + with_basis=with_basis, basis_keys=basis_keys, + name_mapping=name_mapping, latex_name_mapping=latex_name_mapping) class SubspaceFunctor(ConstructionFunctor): @@ -3588,6 +3705,134 @@ def merge(self, other): new_domain) +class EquivariantSubobjectConstructionFunctor(ConstructionFunctor): + r""" + Constructor for subobjects invariant or equivariant under given semigroup actions. + + Let `S` be a semigroup that + - acts on a parent `X` as `s \cdot x` (``action``, ``side='left'``) or + - acts on `X` as `x \cdot s` (``action``, ``side='right'``), + and (possibly trivially) + - acts on `X` as `s * x` (``other_action``, ``other_side='left'``) or + - acts on `X` as `x * s` (``other_action``, ``other_side='right'``). + + The `S`-equivariant subobject is the subobject + + .. MATH:: + + X^S := \{x \in X : s \cdot x = s * x,\, \forall s \in S \} + + when ``side = other_side = 'left'`` and mutatis mutandis for the other values + of ``side`` and ``other_side``. + + When ``other_action`` is trivial, `X^S` is called the `S`-invariant subobject. + + EXAMPLES: + + Monoterm symmetries of a tensor, here only for matrices: row (index 0), + column (index 1); the order of the extra element 2 in a permutation determines + whether it is a symmetry or an antisymmetry:: + + sage: GSym01 = PermutationGroup([[(0,1),(2,),(3,)]]); GSym01 + Permutation Group with generators [(0,1)] + sage: GASym01 = PermutationGroup([[(0,1),(2,3)]]); GASym01 + Permutation Group with generators [(0,1)(2,3)] + sage: from sage.categories.action import Action + sage: from sage.structure.element import Matrix + sage: class TensorIndexAction(Action): + ....: def _act_(self, g, x): + ....: if isinstance(x, Matrix): + ....: if g(0) == 1: + ....: if g(2) == 2: + ....: return x.transpose() + ....: else: + ....: return -x.transpose() + ....: else: + ....: return x + ....: raise NotImplementedError + sage: M = matrix([[1, 2], [3, 4]]); M + [1 2] + [3 4] + sage: GSym01_action = TensorIndexAction(GSym01, M.parent()) + sage: GASym01_action = TensorIndexAction(GASym01, M.parent()) + sage: GSym01_action.act(GSym01.0, M) + [1 3] + [2 4] + sage: GASym01_action.act(GASym01.0, M) + [-1 -3] + [-2 -4] + sage: Sym01 = M.parent().invariant_module(GSym01, action=GSym01_action); Sym01 + (Permutation Group with generators [(0,1)])-invariant submodule + of Full MatrixSpace of 2 by 2 dense matrices over Integer Ring + sage: list(Sym01.basis()) + [B[0], B[1], B[2]] + sage: list(Sym01.basis().map(Sym01.lift)) + [ + [1 0] [0 1] [0 0] + [0 0], [1 0], [0 1] + ] + sage: ASym01 = M.parent().invariant_module(GASym01, action=GASym01_action); ASym01 + (Permutation Group with generators [(0,1)(2,3)])-invariant submodule + of Full MatrixSpace of 2 by 2 dense matrices over Integer Ring + sage: list(ASym01.basis()) + [B[0]] + sage: list(ASym01.basis().map(ASym01.lift)) + [ + [ 0 1] + [-1 0] + ] + sage: from sage.categories.pushout import pushout + sage: pushout(Sym01, QQ) + (Permutation Group with generators [(0,1)])-invariant submodule + of Full MatrixSpace of 2 by 2 dense matrices over Rational Field + """ + def __init__(self, S, action=operator.mul, side='left', + other_action=None, other_side='left'): + """ + EXAMPLES:: + + sage: G = SymmetricGroup(3); G.rename('S3') + sage: M = FreeModule(ZZ, [1,2,3], prefix='M'); M.rename('M') + sage: action = lambda g, x: M.term(g(x)) + sage: I = M.invariant_module(G, action_on_basis=action); I + (S3)-invariant submodule of M + sage: I.construction() + (EquivariantSubobjectConstructionFunctor, + Representation of S3 indexed by {1, 2, 3} over Integer Ring) + """ + from sage.categories.sets_cat import Sets + super().__init__(Sets(), Sets()) + self.S = S + self.action = action + self.side = side + self.other_action = other_action + self.other_side = other_side + + def _apply_functor(self, X): + """ + Apply the functor to an object of ``self``'s domain. + + TESTS:: + + sage: from sage.categories.pushout import EquivariantSubobjectConstructionFunctor + sage: M2 = MatrixSpace(QQ, 2); M2 + Full MatrixSpace of 2 by 2 dense matrices over Rational Field + sage: F = EquivariantSubobjectConstructionFunctor(M2, + ....: operator.mul, 'left', + ....: operator.mul, 'right'); F + EquivariantSubobjectConstructionFunctor + sage: F(M2) + Traceback (most recent call last): + ... + NotImplementedError: non-trivial other_action= is not implemented + """ + other_action = self.other_action + if other_action is not None: + raise NotImplementedError(f'non-trivial {other_action=} is not implemented') + # Currently only implemented for FiniteDimensionalModulesWithBasis + return X.invariant_module(self.S, action=self.action, side=self.side) + + class BlackBoxConstructionFunctor(ConstructionFunctor): """ Construction functor obtained from any callable object. diff --git a/src/sage/categories/regular_crystals.py b/src/sage/categories/regular_crystals.py index 26cb0540dbc..f5412fe6ab1 100644 --- a/src/sage/categories/regular_crystals.py +++ b/src/sage/categories/regular_crystals.py @@ -93,7 +93,7 @@ def super_categories(self): """ return [Crystals()] - def example(self, n = 3): + def example(self, n=3): """ Returns an example of highest weight crystals, as per :meth:`Category.example`. @@ -493,7 +493,7 @@ def phi(self, i): phi = 0 while x is not None: x = x.f(i) - phi = phi + 1 + phi += 1 return phi def weight(self): @@ -508,7 +508,7 @@ def weight(self): """ return self.Phi() - self.Epsilon() - def demazure_operator_simple(self, i, ring = None): + def demazure_operator_simple(self, i, ring=None): r""" Return the Demazure operator `D_i` applied to ``self``. diff --git a/src/sage/categories/rings.py b/src/sage/categories/rings.py index efac793478e..88ce6ef5bc0 100644 --- a/src/sage/categories/rings.py +++ b/src/sage/categories/rings.py @@ -417,9 +417,9 @@ def _Hom_(self, Y, category): if category is not None and not category.is_subcategory(Rings()): raise TypeError("%s is not a subcategory of Rings()"%category) if Y not in Rings(): - raise TypeError("%s is not a ring"%Y) + raise TypeError("%s is not a ring" % Y) from sage.rings.homset import RingHomset - return RingHomset(self, Y, category = category) + return RingHomset(self, Y, category=category) # this is already in sage.rings.ring.Ring, # but not all rings descend from that class, @@ -781,16 +781,16 @@ def _ideal_class_(self,n=0): ## # Quotient rings - # Again, this is defined in sage.rings.ring.pyx def quotient(self, I, names=None, **kwds): """ Quotient of a ring by a two-sided ideal. INPUT: - - ``I``: A twosided ideal of this ring. - - ``names``: a list of strings to be used as names - for the variables in the quotient ring. + - ``I`` -- A twosided ideal of this ring. + - ``names`` -- (optional) names of the generators of the quotient (if + there are multiple generators, you can specify a single character + string and the generators are named in sequence starting with 0). - further named arguments that may be passed to the quotient ring constructor. @@ -823,6 +823,23 @@ def quotient(self, I, names=None, **kwds): xbar*ybar sage: Q.0*Q.1*Q.0 0 + + An example with polynomial rings:: + + sage: R. = PolynomialRing(ZZ) + sage: I = R.ideal([4 + 3*x + x^2, 1 + x^2]) + sage: S = R.quotient(I, 'a') + sage: S.gens() + (a,) + + sage: R. = PolynomialRing(QQ,2) + sage: S. = R.quotient((x^2, y)) + sage: S + Quotient of Multivariate Polynomial Ring in x, y over Rational Field by the ideal (x^2, y) + sage: S.gens() + (a, 0) + sage: a == b + False """ from sage.rings.quotient_ring import QuotientRing return QuotientRing(self, I, names=names, **kwds) @@ -866,6 +883,16 @@ def quo(self, I, names=None, **kwds): [0 1] ) + A test with a subclass of :class:`~sage.rings.ring.Ring`:: + + sage: R. = PolynomialRing(QQ,2) + sage: S. = R.quo((x^2, y)) + sage: S + Quotient of Multivariate Polynomial Ring in x, y over Rational Field by the ideal (x^2, y) + sage: S.gens() + (a, 0) + sage: a == b + False """ return self.quotient(I,names=names,**kwds) @@ -875,7 +902,22 @@ def quotient_ring(self, I, names=None, **kwds): NOTE: - This is a synonyme for :meth:`quotient`. + This is a synonym for :meth:`quotient`. + + INPUT: + + - ``I`` -- an ideal of `R` + + - ``names`` -- (optional) names of the generators of the quotient. (If + there are multiple generators, you can specify a single character + string and the generators are named in sequence starting with 0.) + + - further named arguments that may be passed to the quotient ring + constructor. + + OUTPUT: + + - ``R/I`` -- the quotient ring of `R` by the ideal `I` EXAMPLES:: @@ -906,8 +948,24 @@ def quotient_ring(self, I, names=None, **kwds): [0 1] ) + A test with a subclass of :class:`~sage.rings.ring.Ring`:: + + sage: R. = PolynomialRing(ZZ) + sage: I = R.ideal([4 + 3*x + x^2, 1 + x^2]) + sage: S = R.quotient_ring(I, 'a') + sage: S.gens() + (a,) + + sage: R. = PolynomialRing(QQ,2) + sage: S. = R.quotient_ring((x^2, y)) + sage: S + Quotient of Multivariate Polynomial Ring in x, y over Rational Field by the ideal (x^2, y) + sage: S.gens() + (a, 0) + sage: a == b + False """ - return self.quotient(I,names=names, **kwds) + return self.quotient(I, names=names, **kwds) def __truediv__(self, I): """ @@ -923,6 +981,11 @@ def __truediv__(self, I): Traceback (most recent call last): ... TypeError: Use self.quo(I) or self.quotient(I) to construct the quotient ring. + + sage: QQ['x'] / ZZ + Traceback (most recent call last): + ... + TypeError: Use self.quo(I) or self.quotient(I) to construct the quotient ring. """ raise TypeError("Use self.quo(I) or self.quotient(I) to construct the quotient ring.") diff --git a/src/sage/categories/schemes.py b/src/sage/categories/schemes.py index 45ef3943239..bfd7eb4369b 100644 --- a/src/sage/categories/schemes.py +++ b/src/sage/categories/schemes.py @@ -52,7 +52,7 @@ class Schemes(Category): """ @staticmethod - def __classcall_private__(cls, X = None): + def __classcall_private__(cls, X=None): """ Implement the dispatching ``Schemes(ZZ)`` -> ``Schemes_over_base``. diff --git a/src/sage/categories/semigroups.py b/src/sage/categories/semigroups.py index fb8ae29f30f..007401e5474 100644 --- a/src/sage/categories/semigroups.py +++ b/src/sage/categories/semigroups.py @@ -171,7 +171,8 @@ def prod(self, args): assert len(args) > 0, "Cannot compute an empty product in a semigroup" return prod(args[1:], args[0]) - def cayley_graph(self, side="right", simple=False, elements = None, generators = None, connecting_set = None): + def cayley_graph(self, side="right", simple=False, elements=None, + generators=None, connecting_set=None): r""" Return the Cayley graph for this finite semigroup. @@ -791,7 +792,7 @@ def example(self): An example of a (sub)quotient semigroup: a quotient of the left zero semigroup """ from sage.categories.examples.semigroups import QuotientOfLeftZeroSemigroup - return QuotientOfLeftZeroSemigroup(category = self.Subquotients()) + return QuotientOfLeftZeroSemigroup(category=self.Subquotients()) class Quotients(QuotientsCategory): diff --git a/src/sage/categories/sets_cat.py b/src/sage/categories/sets_cat.py index 09a57770a3b..d096a950773 100644 --- a/src/sage/categories/sets_cat.py +++ b/src/sage/categories/sets_cat.py @@ -247,7 +247,7 @@ def _call_(self, X, enumerated_set=False): {1, 2, 3} sage: S = Sets()([1, 2, 3]); S.category() - Category of finite sets + Category of finite enumerated sets sage: S = Sets()([1, 2, 3], enumerated_set=True); S.category() Category of facade finite enumerated sets @@ -1162,16 +1162,17 @@ def _test_elements(self, tester=None, **options): # The intention is to raise an exception only if this is # run as a sub-testsuite of a larger testsuite. is_sub_testsuite = (tester is not None) - tester = self._tester(tester = tester, **options) + tester = self._tester(tester=tester, **options) # Or do we want to run the test on some_elements? try: an_element = self.an_element() except EmptySetError: return tester.info("\n Running the test suite of self.an_element()") - TestSuite(an_element).run(verbose = tester._verbose, prefix = tester._prefix+" ", - raise_on_failure = is_sub_testsuite) - tester.info(tester._prefix+" ", newline = False) + TestSuite(an_element).run(verbose=tester._verbose, + prefix=tester._prefix + " ", + raise_on_failure=is_sub_testsuite) + tester.info(tester._prefix + " ", newline=False) def _test_elements_eq_reflexive(self, **options): """ @@ -1361,7 +1362,7 @@ def some_elements(self): sage: S.some_elements() [47] sage: S = Set([]) - sage: S.some_elements() + sage: list(S.some_elements()) [] This method should return an iterable, *not* an iterator. @@ -2217,7 +2218,14 @@ def example(self): class ParentMethods: def __iter__(self): r""" - Return a lexicographic iterator for the elements of this Cartesian product. + Return an iterator for the elements of this Cartesian product. + + If all factors (except possibly the first factor) are known to be finite, + it uses the lexicographic order. + + Otherwise, the iterator enumerates the elements in increasing + order of sum-of-ranks, refined by the reverse lexicographic order + (see :func:`~sage.misc.mrange.cantor_product`). EXAMPLES: @@ -2260,29 +2268,38 @@ def __iter__(self): [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 10, 9]) - .. WARNING:: - - The elements are returned in lexicographic order, - which gives a valid enumeration only if all - factors, but possibly the first one, are - finite. So the following one is fine:: - - sage: it = iter(cartesian_product([ZZ, GF(2)])) - sage: [next(it) for _ in range(10)] - [(0, 0), (0, 1), (1, 0), (1, 1), - (-1, 0), (-1, 1), (2, 0), (2, 1), - (-2, 0), (-2, 1)] - - But this one is not:: - - sage: it = iter(cartesian_product([GF(2), ZZ])) - sage: [next(it) for _ in range(10)] - doctest:...: UserWarning: Sage is not able to determine - whether the factors of this Cartesian product are - finite. The lexicographic ordering might not go through - all elements. - [(0, 0), (0, 1), (0, -1), (0, 2), (0, -2), - (0, 3), (0, -3), (0, 4), (0, -4), (0, 5)] + When all factors (except possibly the first factor) are known to be finite, it + uses the lexicographic order:: + + sage: it = iter(cartesian_product([ZZ, GF(2)])) + sage: [next(it) for _ in range(10)] + [(0, 0), (0, 1), + (1, 0), (1, 1), + (-1, 0), (-1, 1), + (2, 0), (2, 1), + (-2, 0), (-2, 1)] + + When other factors are infinite (or not known to be finite), it enumerates + the elements in increasing order of sum-of-ranks:: + + sage: NN = NonNegativeIntegers() + sage: it = iter(cartesian_product([NN, NN])) + sage: [next(it) for _ in range(10)] + [(0, 0), + (1, 0), (0, 1), + (2, 0), (1, 1), (0, 2), + (3, 0), (2, 1), (1, 2), (0, 3)] + + An example with the first factor finite, the second infinite:: + + sage: it = iter(cartesian_product([GF(2), ZZ])) + sage: [next(it) for _ in range(11)] + [(0, 0), + (1, 0), (0, 1), + (1, 1), (0, -1), + (1, -1), (0, 2), + (1, 2), (0, -2), + (1, -2), (0, 3)] .. NOTE:: @@ -2292,17 +2309,18 @@ def __iter__(self): ALGORITHM: - Recipe 19.9 in the Python Cookbook by Alex Martelli - and David Ascher. + The lexicographic enumeration follows Recipe 19.9 in the Python Cookbook + by Alex Martelli and David Ascher. """ - if any(f not in Sets().Finite() for f in self.cartesian_factors()[1:]): - from warnings import warn - warn("Sage is not able to determine whether the factors of " - "this Cartesian product are finite. The lexicographic " - "ordering might not go through all elements.") + factors = list(self.cartesian_factors()) + if any(f not in Sets().Finite() for f in factors[1:]): + from sage.misc.mrange import cantor_product + for t in cantor_product(*factors): + yield self._cartesian_product_of_elements(t) + return + # Lexicographic enumeration: # visualize an odometer, with "wheels" displaying "digits"...: - factors = list(self.cartesian_factors()) wheels = [iter(f) for f in factors] try: digits = [next(it) for it in wheels] @@ -2500,6 +2518,19 @@ def cartesian_projection(self, i): 42 """ + def construction(self): + """ + The construction functor and the list of Cartesian factors. + + EXAMPLES:: + + sage: C = cartesian_product([QQ, ZZ, ZZ]) + sage: C.construction() + (The cartesian_product functorial construction, + (Rational Field, Integer Ring, Integer Ring)) + """ + return cartesian_product, self.cartesian_factors() + @abstract_method def _cartesian_product_of_elements(self, elements): """ @@ -2682,7 +2713,7 @@ def extra_super_categories(self): """ return [Sets().Facade()] - def example(self, base_ring = None, set = None): + def example(self, base_ring=None, set=None): r""" Return an example of set with multiple realizations, as per :meth:`Category.example`. diff --git a/src/sage/categories/super_hopf_algebras_with_basis.py b/src/sage/categories/super_hopf_algebras_with_basis.py index c0716ed2fc1..4fd09867b45 100644 --- a/src/sage/categories/super_hopf_algebras_with_basis.py +++ b/src/sage/categories/super_hopf_algebras_with_basis.py @@ -55,7 +55,8 @@ def antipode(self): """ if self.antipode_on_basis is not NotImplemented: # Should give the information that this is an anti-morphism of algebra - return self._module_morphism(self.antipode_on_basis, codomain = self) + return self._module_morphism(self.antipode_on_basis, + codomain=self) elif hasattr(self, "antipode_by_coercion"): return self.antipode_by_coercion diff --git a/src/sage/categories/unital_algebras.py b/src/sage/categories/unital_algebras.py index 8d28187d97f..43ffa76d47c 100644 --- a/src/sage/categories/unital_algebras.py +++ b/src/sage/categories/unital_algebras.py @@ -263,7 +263,7 @@ class WithBasis(CategoryWithAxiom_over_base_ring): class ParentMethods: - @abstract_method(optional = True) + @abstract_method(optional=True) def one_basis(self): """ When the one of an algebra with basis is an element of diff --git a/src/sage/categories/vector_spaces.py b/src/sage/categories/vector_spaces.py index 7568c705326..66534879b5a 100644 --- a/src/sage/categories/vector_spaces.py +++ b/src/sage/categories/vector_spaces.py @@ -134,7 +134,7 @@ def super_categories(self): [Category of modules over Rational Field] """ R = self.base_field() - return [Modules(R, dispatch = False)] + return [Modules(R, dispatch=False)] def additional_structure(self): r""" diff --git a/src/sage/categories/weyl_groups.py b/src/sage/categories/weyl_groups.py index 683729405b1..47ca72306f0 100644 --- a/src/sage/categories/weyl_groups.py +++ b/src/sage/categories/weyl_groups.py @@ -495,7 +495,7 @@ def stanley_symmetric_function_as_polynomial(self, max_length=None): if self.is_one(): return R.one() - return R(sum(2**(pieri_factors.stanley_symm_poly_weight(u))*x[u.length()-1] * v.stanley_symmetric_function_as_polynomial(max_length = u.length()) + return R(sum(2**(pieri_factors.stanley_symm_poly_weight(u))*x[u.length()-1] * v.stanley_symmetric_function_as_polynomial(max_length=u.length()) for (u,v) in self.left_pieri_factorizations(max_length) if u != W.one())) @@ -629,14 +629,14 @@ def reflection_to_coroot(self): raise ValueError("{} is not a reflection".format(self)) return rsi.apply_simple_reflection(i, side='left').reflection_to_coroot().simple_reflection(i) - def inversions(self, side = 'right', inversion_type = 'reflections'): + def inversions(self, side='right', inversion_type='reflections'): """ - Returns the set of inversions of ``self``. + Return the set of inversions of ``self``. INPUT: - ``side`` -- 'right' (default) or 'left' - - ``inversion_type`` -- 'reflections' (default), 'roots', or 'coroots'. + - ``inversion_type`` -- 'reflections' (default), 'roots', or 'coroots' OUTPUT: diff --git a/src/sage/coding/binary_code.pyx b/src/sage/coding/binary_code.pyx index 7021531eedd..66192643aae 100644 --- a/src/sage/coding/binary_code.pyx +++ b/src/sage/coding/binary_code.pyx @@ -777,7 +777,8 @@ cdef class BinaryCode: self.nwords = 2 * other_nwords nrows = self.nrows nwords = self.nwords - else: raise NotImplementedError("!") + else: + raise NotImplementedError if self.nrows >= self.radix or self.ncols > self.radix: raise NotImplementedError("Columns and rows are stored as ints. This code is too big.") diff --git a/src/sage/coding/codecan/codecan.pyx b/src/sage/coding/codecan/codecan.pyx index e88b560fef4..5320a49b179 100644 --- a/src/sage/coding/codecan/codecan.pyx +++ b/src/sage/coding/codecan/codecan.pyx @@ -369,7 +369,7 @@ cdef class InnerGroup: return self.transporter def __repr__(self): - """ + r""" EXAMPLES:: sage: from sage.coding.codecan.codecan import InnerGroup @@ -378,7 +378,7 @@ cdef class InnerGroup: frobenius power = 1 and partition = 0 -> 0 1 -> 1 2 -> 2 3 -> 3 4 -> 4 5 -> 5 6 -> 6 7 -> 7 8 -> 8 9 -> 9 """ - return "Subgroup of (GL(k,q) times \GF{q}^n ) rtimes Aut(\GF{q}) " + \ + return r"Subgroup of (GL(k,q) times \GF{q}^n ) rtimes Aut(\GF{q}) " + \ "with rank = %s, frobenius power = %s and partition =%s" % (self.rank, self.frob_pow, OP_string(self.row_partition)) @@ -741,7 +741,7 @@ cdef class PartitionRefinementLinearCode(PartitionRefinement_generic): return self._inner_group_stabilizer_order cdef _init_point_hyperplane_incidence(self): - """ + r""" Compute a set of codewords `W` of `C` (generated by self) which is compatible with the group action, i.e. if we start with some other code `(g,\pi)C` the result should be `(g,\pi)W`. diff --git a/src/sage/coding/grs_code.py b/src/sage/coding/grs_code.py index 1b40afc6f8b..efbc1d32c5b 100644 --- a/src/sage/coding/grs_code.py +++ b/src/sage/coding/grs_code.py @@ -2389,4 +2389,3 @@ def decoding_radius(self): GRSErrorErasureDecoder._decoder_type = {"error-erasure", "always-succeed"} GeneralizedReedSolomonCode._registered_decoders["KeyEquationSyndrome"] = GRSKeyEquationSyndromeDecoder GRSKeyEquationSyndromeDecoder._decoder_type = {"hard-decision", "always-succeed"} - diff --git a/src/sage/coding/linear_code_no_metric.py b/src/sage/coding/linear_code_no_metric.py index 50b507a2011..9610c4e31ce 100644 --- a/src/sage/coding/linear_code_no_metric.py +++ b/src/sage/coding/linear_code_no_metric.py @@ -1,5 +1,5 @@ r""" -Generic structures for linear codes of any metirc +Generic structures for linear codes of any metric Class supporting methods available for linear codes over any metric (Hamming, rank). diff --git a/src/sage/coding/self_dual_codes.py b/src/sage/coding/self_dual_codes.py index 02dd5b953f0..49125e8fb01 100644 --- a/src/sage/coding/self_dual_codes.py +++ b/src/sage/coding/self_dual_codes.py @@ -81,7 +81,7 @@ - [HP2003] \W. C. Huffman, V. Pless, Fundamentals of Error-Correcting Codes, Cambridge Univ. Press, 2003. -- [P] \V. Pless, "A classification of self-orthogonal codes over GF(2)", +- [P] \V. Pless, *A classification of self-orthogonal codes over GF(2)*, Discrete Math 3 (1972) 209-246. """ @@ -97,6 +97,7 @@ _F = GF(2) + def _MS(n): r""" For internal use; returns the floor(n/2) x n matrix space over GF(2). @@ -162,6 +163,7 @@ def _matId(n): Id.append(MSn.identity_matrix()) return Id + def _MS2(n): r""" For internal use; returns the floor(n/2) x floor(n/2) matrix space over GF(2). @@ -933,7 +935,3 @@ def self_dual_binary_codes(n): "3":self_dual_codes_22_3,"4":self_dual_codes_22_4,"5":self_dual_codes_22_5,\ "6":self_dual_codes_22_6} return self_dual_codes - - - - diff --git a/src/sage/combinat/affine_permutation.py b/src/sage/combinat/affine_permutation.py index b87167bb487..9ee4e166abe 100644 --- a/src/sage/combinat/affine_permutation.py +++ b/src/sage/combinat/affine_permutation.py @@ -161,21 +161,19 @@ def __mul__(self, q): return self.__rmul__(q) @cached_method - def inverse(self): + def __invert__(self): r""" Return the inverse affine permutation. EXAMPLES:: sage: p = AffinePermutationGroup(['A',7,1])([3, -1, 0, 6, 5, 4, 10, 9]) - sage: p.inverse() + sage: p.inverse() # indirect doctest Type A affine permutation with window [0, -1, 1, 6, 5, 4, 10, 11] """ - inv = [self.position(i) for i in range(1,len(self)+1)] + inv = [self.position(i) for i in range(1, len(self) + 1)] return type(self)(self.parent(), inv, check=False) - __invert__=inverse - def apply_simple_reflection(self, i, side='right'): r""" Apply a simple reflection. @@ -855,8 +853,8 @@ def to_lehmer_code(self, typ='decreasing', side='right'): a=self(i) for j in range(i-self.k, i): b=self(j) - #A small rotation is necessary for the reduced word from - #the lehmer code to match the element. + # A small rotation is necessary for the reduced word from + # the Lehmer code to match the element. if a < b: code[i-1]+=((b-a)//(self.k+1)+1) elif typ[0] == 'i' and side[0] == 'l': @@ -2330,6 +2328,7 @@ def from_lehmer_code(self, C, typ='decreasing', side='right'): Element = AffinePermutationTypeA + class AffinePermutationGroupTypeC(AffinePermutationGroupGeneric): #------------------------ #Type-specific methods. diff --git a/src/sage/combinat/colored_permutations.py b/src/sage/combinat/colored_permutations.py index d3bfadcccfd..ff01032147e 100644 --- a/src/sage/combinat/colored_permutations.py +++ b/src/sage/combinat/colored_permutations.py @@ -109,7 +109,7 @@ def _mul_(self, other): p = self._perm._left_to_right_multiply_on_right(other._perm) return self.__class__(self.parent(), colors, p) - def inverse(self): + def __invert__(self): """ Return the inverse of ``self``. @@ -117,7 +117,7 @@ def inverse(self): sage: C = ColoredPermutations(4, 3) sage: s1,s2,t = C.gens() - sage: ~t + sage: ~t # indirect doctest [[0, 0, 3], [1, 2, 3]] sage: all(x * ~x == C.one() for x in C.gens()) True @@ -127,8 +127,6 @@ def inverse(self): tuple(-self._colors[i - 1] for i in ip), # -1 for indexing ip) - __invert__ = inverse - def __eq__(self, other): """ Check equality. @@ -1030,7 +1028,7 @@ def _mul_(self, other): p = self._perm._left_to_right_multiply_on_right(other._perm) return self.__class__(self.parent(), colors, p) - def inverse(self): + def __invert__(self): """ Return the inverse of ``self``. @@ -1039,7 +1037,7 @@ def inverse(self): sage: S = SignedPermutations(4) sage: s1,s2,s3,s4 = S.gens() sage: x = s4*s1*s2*s3*s4 - sage: ~x + sage: ~x # indirect doctest [2, 3, -4, -1] sage: x * ~x == S.one() True @@ -1049,8 +1047,6 @@ def inverse(self): tuple(self._colors[i - 1] for i in ip), # -1 for indexing ip) - __invert__ = inverse - def __iter__(self): """ Iterate over ``self``. diff --git a/src/sage/combinat/composition.py b/src/sage/combinat/composition.py index cc709bc5091..ee711e52f9f 100644 --- a/src/sage/combinat/composition.py +++ b/src/sage/combinat/composition.py @@ -30,6 +30,7 @@ # **************************************************************************** from __future__ import annotations from itertools import accumulate +from collections.abc import Sequence from sage.categories.infinite_enumerated_sets import InfiniteEnumeratedSets from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets @@ -109,6 +110,25 @@ class Composition(CombinatorialElement): sage: Composition(descents=({0,1,3},5)) [1, 1, 2, 1] + An integer composition may be regarded as a sequence. Thus it is an + instance of the Python abstract base class ``Sequence`` allows us to check if objects + behave "like" sequences based on implemented methods. Note that + ``collections.abc.Sequence`` is not the same as + :class:`sage.structure.sequence.Sequence`:: + + sage: import collections.abc + sage: C = Composition([3,2,3]) + sage: isinstance(C, collections.abc.Sequence) + True + sage: issubclass(C.__class__, collections.abc.Sequence) + True + + Typically, instances of ``collections.abc.Sequence`` have a ``.count`` method. + ``Composition.count`` counts the number of parts of a specified size:: + + sage: C.count(3) + 2 + EXAMPLES:: sage: C = Composition([3,1,2]) @@ -1350,6 +1370,23 @@ def wll_gt(self, co2) -> bool: return False return False + def count(self, n): + r""" + Return the number of parts of size ``n``. + + EXAMPLES:: + + sage: C = Composition([3,2,3]) + sage: C.count(3) + 2 + sage: C.count(2) + 1 + sage: C.count(1) + 0 + """ + return sum(i == n for i in self) + +Sequence.register(Composition) ############################################################## diff --git a/src/sage/combinat/crystals/affine.py b/src/sage/combinat/crystals/affine.py index 65df64542b0..813492aad80 100644 --- a/src/sage/combinat/crystals/affine.py +++ b/src/sage/combinat/crystals/affine.py @@ -78,7 +78,7 @@ def __classcall__(cls, cartan_type, *args, **options): True """ ct = CartanType(cartan_type) - return super(AffineCrystalFromClassical, cls).__classcall__(cls, ct, *args, **options) + return super().__classcall__(cls, ct, *args, **options) def __init__(self, cartan_type, classical_crystal, category=None): """ @@ -397,7 +397,7 @@ def epsilon0(self): sage: [x.epsilon0() for x in A.list()] [1, 0, 0] """ - return super(AffineCrystalFromClassicalElement, self).epsilon(0) + return super().epsilon(0) def epsilon(self, i): """ @@ -436,7 +436,7 @@ def phi0(self): sage: [x.phi0() for x in A.list()] [0, 0, 1] """ - return super(AffineCrystalFromClassicalElement, self).phi(0) + return super().phi(0) def phi(self, i): r""" diff --git a/src/sage/combinat/crystals/affine_factorization.py b/src/sage/combinat/crystals/affine_factorization.py index 1bc568aeb71..2499a05a5f9 100644 --- a/src/sage/combinat/crystals/affine_factorization.py +++ b/src/sage/combinat/crystals/affine_factorization.py @@ -117,7 +117,7 @@ def __classcall_private__(cls, w, n, x = None, k = None): w0 = W.from_reduced_word(w[0].from_kbounded_to_reduced_word(k)) w1 = W.from_reduced_word(w[1].from_kbounded_to_reduced_word(k)) w = w0*(w1.inverse()) - return super(AffineFactorizationCrystal, cls).__classcall__(cls, w, n, x) + return super().__classcall__(cls, w, n, x) def __init__(self, w, n, x = None): r""" diff --git a/src/sage/combinat/crystals/alcove_path.py b/src/sage/combinat/crystals/alcove_path.py index 8fad37c525e..64b727e9d51 100644 --- a/src/sage/combinat/crystals/alcove_path.py +++ b/src/sage/combinat/crystals/alcove_path.py @@ -276,10 +276,8 @@ def __classcall_private__(cls, starting_weight, cartan_type=None, if not starting_weight.is_dominant(): raise ValueError("{0} is not a dominant weight".format(starting_weight)) - - return super(CrystalOfAlcovePaths, cls).__classcall__(cls, - starting_weight, highest_weight_crystal) - + return super().__classcall__(cls, starting_weight, + highest_weight_crystal) def __init__(self, starting_weight, highest_weight_crystal): r""" @@ -1153,7 +1151,7 @@ def __classcall_private__(cls, cartan_type): True """ cartan_type = CartanType(cartan_type) - return super(InfinityCrystalOfAlcovePaths, cls).__classcall__(cls, cartan_type) + return super().__classcall__(cls, cartan_type) def __init__(self, cartan_type): """ @@ -1394,6 +1392,7 @@ def projection(self, k=None): A = CrystalOfAlcovePaths(self.parent()._cartan_type, [k]*n) return A(tuple([A._R(rt, h + k*s(rt)) for rt,h in self.value])) + class RootsWithHeight(UniqueRepresentation, Parent): r""" Data structure of the ordered pairs `(\beta,k)`, @@ -1462,8 +1461,7 @@ def __classcall_private__(cls, starting_weight, cartan_type = None): offset = R.index_set()[Integer(0)] starting_weight = P.sum(starting_weight[j-offset]*Lambda[j] for j in R.index_set()) - return super(RootsWithHeight, cls).__classcall__(cls, starting_weight) - + return super().__classcall__(cls, starting_weight) def __init__(self, weight): r""" diff --git a/src/sage/combinat/crystals/bkk_crystals.py b/src/sage/combinat/crystals/bkk_crystals.py index 622fbb9e350..15e795ca484 100644 --- a/src/sage/combinat/crystals/bkk_crystals.py +++ b/src/sage/combinat/crystals/bkk_crystals.py @@ -61,7 +61,7 @@ def __classcall_private__(cls, ct, shape): shape = _Partitions(shape) if len(shape) > ct.m + 1 and shape[ct.m+1] > ct.n + 1: raise ValueError("invalid hook shape") - return super(CrystalOfBKKTableaux, cls).__classcall__(cls, ct, shape) + return super().__classcall__(cls, ct, shape) def __init__(self, ct, shape): r""" @@ -135,7 +135,7 @@ def genuine_highest_weight_vectors(self, index_set=None): """ if index_set is None or index_set == self.index_set(): return self.module_generators - return super(CrystalOfBKKTableaux, self).genuine_highest_weight_vectors(index_set) + return super().genuine_highest_weight_vectors(index_set) class Element(CrystalOfBKKTableauxElement): pass diff --git a/src/sage/combinat/crystals/direct_sum.py b/src/sage/combinat/crystals/direct_sum.py index 84676264822..da2ce05bee2 100644 --- a/src/sage/combinat/crystals/direct_sum.py +++ b/src/sage/combinat/crystals/direct_sum.py @@ -22,6 +22,7 @@ from sage.structure.element_wrapper import ElementWrapper from sage.structure.element import get_coercion_model + class DirectSumOfCrystals(DisjointUnionEnumeratedSets): r""" Direct sum of crystals. @@ -114,7 +115,7 @@ def __classcall_private__(cls, crystals, facade=True, keepkey=False, category=No else: ret.append(x) category = Category.meet([Category.join(c.categories()) for c in ret]) - return super(DirectSumOfCrystals, cls).__classcall__(cls, + return super().__classcall__(cls, Family(ret), facade=facade, keepkey=keepkey, category=category) def __init__(self, crystals, facade, keepkey, category, **options): diff --git a/src/sage/combinat/crystals/elementary_crystals.py b/src/sage/combinat/crystals/elementary_crystals.py index b3d01c09249..607206c52f6 100644 --- a/src/sage/combinat/crystals/elementary_crystals.py +++ b/src/sage/combinat/crystals/elementary_crystals.py @@ -258,7 +258,7 @@ def __classcall_private__(cls, cartan_type, weight=None): weight = cartan_type cartan_type = weight.parent().cartan_type() cartan_type = CartanType(cartan_type) - return super(TCrystal, cls).__classcall__(cls, cartan_type, weight) + return super().__classcall__(cls, cartan_type, weight) def __init__(self, cartan_type, weight): r""" @@ -514,7 +514,7 @@ def __classcall_private__(cls, cartan_type, weight=None, dual=False): weight = cartan_type cartan_type = weight.parent().cartan_type() cartan_type = CartanType(cartan_type) - return super(RCrystal, cls).__classcall__(cls, cartan_type, weight, dual) + return super().__classcall__(cls, cartan_type, weight, dual) def __init__(self, cartan_type, weight, dual): r""" @@ -789,7 +789,7 @@ def __classcall_private__(cls, cartan_type, i): cartan_type = CartanType(cartan_type) if i not in cartan_type.index_set(): raise ValueError('i must an element of the index set') - return super(ElementaryCrystal, cls).__classcall__(cls, cartan_type, i) + return super().__classcall__(cls, cartan_type, i) def __init__(self, cartan_type, i): r""" @@ -1091,7 +1091,7 @@ def __classcall_private__(cls, cartan_type, P=None): P = cartan_type.root_system().ambient_space() if P is None: P = cartan_type.root_system().weight_lattice() - return super(ComponentCrystal, cls).__classcall__(cls, cartan_type, P) + return super().__classcall__(cls, cartan_type, P) def __init__(self, cartan_type, P): r""" diff --git a/src/sage/combinat/crystals/fast_crystals.py b/src/sage/combinat/crystals/fast_crystals.py index 2f964bb60de..874f278ee93 100644 --- a/src/sage/combinat/crystals/fast_crystals.py +++ b/src/sage/combinat/crystals/fast_crystals.py @@ -87,6 +87,17 @@ class FastCrystal(UniqueRepresentation, Parent): sage: C.cardinality() 35 sage: TestSuite(C).run() + + sage: C = crystals.FastRankTwo(['A',2],shape=[2,1]) + sage: C.list() + [[0, 0, 0], + [1, 0, 0], + [0, 1, 1], + [0, 2, 1], + [1, 2, 1], + [0, 1, 0], + [1, 1, 0], + [2, 1, 0]] """ @staticmethod def __classcall__(cls, cartan_type, shape, format = "string"): @@ -105,7 +116,7 @@ def __classcall__(cls, cartan_type, shape, format = "string"): if len(shape) > 2: raise ValueError("The shape must have length <=2") shape = shape + (0,)*(2-len(shape)) - return super(FastCrystal, cls).__classcall__(cls, cartan_type, shape, format) + return super().__classcall__(cls, cartan_type, shape, format) def __init__(self, ct, shape, format): """ @@ -116,7 +127,7 @@ def __init__(self, ct, shape, format): sage: TestSuite(C).run() """ Parent.__init__(self, category = ClassicalCrystals()) -# super(FastCrystal, self).__init__(category = FiniteEnumeratedSets()) +# super().__init__(category = FiniteEnumeratedSets()) self._cartan_type = ct if ct[1] != 2: raise NotImplementedError @@ -166,10 +177,8 @@ def __init__(self, ct, shape, format): l2_str = "%d/2"%int(2*l2) self.rename("The fast crystal for %s2 with shape [%s,%s]"%(ct[0],l1_str,l2_str)) self.module_generators = [self(0)] -# self._list = ClassicalCrystal.list(self) - self._list = super(FastCrystal, self).list() -# self._digraph = ClassicalCrystal.digraph(self) - self._digraph = super(FastCrystal, self).digraph() + # self._digraph = ClassicalCrystal.digraph(self) + self._digraph = super().digraph() self._digraph_closure = self.digraph().transitive_closure() def _type_a_init(self, l1, l2): @@ -249,25 +258,6 @@ def __call__(self, value): return value return self.element_class(self, value, self.format) - def list(self): - """ - Return a list of the elements of self. - - EXAMPLES:: - - sage: C = crystals.FastRankTwo(['A',2],shape=[2,1]) - sage: C.list() - [[0, 0, 0], - [1, 0, 0], - [0, 1, 1], - [0, 2, 1], - [1, 2, 1], - [0, 1, 0], - [1, 1, 0], - [2, 1, 0]] - """ - return self._list - def digraph(self): """ Return the digraph associated to self. diff --git a/src/sage/combinat/crystals/fully_commutative_stable_grothendieck.py b/src/sage/combinat/crystals/fully_commutative_stable_grothendieck.py index e2f9d09bd45..df9805e07b3 100644 --- a/src/sage/combinat/crystals/fully_commutative_stable_grothendieck.py +++ b/src/sage/combinat/crystals/fully_commutative_stable_grothendieck.py @@ -309,6 +309,7 @@ def to_increasing_hecke_biword(self): L[0] += [j+1]*len(self.value[-j-1]) return L + class DecreasingHeckeFactorizations(UniqueRepresentation, Parent): """ Set of decreasing factorizations in the 0-Hecke monoid. @@ -358,7 +359,7 @@ def __classcall_private__(cls, w, factors, excess): w = H.from_reduced_word(w.reduced_word()) if (not w.reduced_word()) and excess!=0: raise ValueError("excess must be 0 for the empty word") - return super(DecreasingHeckeFactorizations, cls).__classcall__(cls, w, factors, excess) + return super().__classcall__(cls, w, factors, excess) def __init__(self, w, factors, excess): """ @@ -520,7 +521,7 @@ def __classcall_private__(cls, w, factors, excess, shape=False): w = H.from_reduced_word(w.reduced_word()) if (not w.reduced_word()) and excess!=0: raise ValueError("excess must be 0 for the empty word") - return super(FullyCommutativeStableGrothendieckCrystal, cls).__classcall__(cls, w, factors, excess) + return super().__classcall__(cls, w, factors, excess) def __init__(self, w, factors, excess): """ diff --git a/src/sage/combinat/crystals/generalized_young_walls.py b/src/sage/combinat/crystals/generalized_young_walls.py index 60c47154c8e..0d30991e00e 100644 --- a/src/sage/combinat/crystals/generalized_young_walls.py +++ b/src/sage/combinat/crystals/generalized_young_walls.py @@ -834,7 +834,7 @@ def __classcall_private__(cls, n, category=None): sage: Yinf is Yinf2 True """ - return super(InfinityCrystalOfGeneralizedYoungWalls,cls).__classcall__(cls,n,category) + return super().__classcall__(cls,n,category) def __init__(self, n, category): r""" @@ -1028,7 +1028,7 @@ def __classcall_private__(cls, n, La): True """ La = RootSystem(['A',n,1]).weight_lattice(extended=True)(La) - return super(CrystalOfGeneralizedYoungWalls, cls).__classcall__(cls, n, La) + return super().__classcall__(cls, n, La) def __init__(self, n, La): r""" @@ -1067,6 +1067,6 @@ def __iter__(self): sage: next(x) [0] """ - for c in super(CrystalOfGeneralizedYoungWalls, self).__iter__(): + for c in super().__iter__(): if c.in_highest_weight_crystal(self.hw): yield c diff --git a/src/sage/combinat/crystals/induced_structure.py b/src/sage/combinat/crystals/induced_structure.py index 1e89220f3ea..a2265e8df71 100644 --- a/src/sage/combinat/crystals/induced_structure.py +++ b/src/sage/combinat/crystals/induced_structure.py @@ -27,6 +27,7 @@ from sage.structure.parent import Parent from sage.structure.element_wrapper import ElementWrapper + class InducedCrystal(UniqueRepresentation, Parent): r""" A crystal induced from an injection. @@ -120,7 +121,7 @@ def __classcall_private__(cls, X, phi, inverse=None, from_crystal=False): if from_crystal: return InducedFromCrystal(X, phi, inverse) - return super(InducedCrystal, cls).__classcall__(cls, X, phi, inverse) + return super().__classcall__(cls, X, phi, inverse) def __init__(self, X, phi, inverse): """ diff --git a/src/sage/combinat/crystals/infinity_crystals.py b/src/sage/combinat/crystals/infinity_crystals.py index 0edf826c73e..7fa7e5f7a2d 100644 --- a/src/sage/combinat/crystals/infinity_crystals.py +++ b/src/sage/combinat/crystals/infinity_crystals.py @@ -206,7 +206,7 @@ def __classcall_private__(cls, cartan_type): return InfinityCrystalOfTableauxTypeD(cartan_type) if cartan_type.type() == 'Q': return DualInfinityQueerCrystalOfTableaux(cartan_type) - return super(InfinityCrystalOfTableaux, cls).__classcall__(cls, cartan_type) + return super().__classcall__(cls, cartan_type) def __init__(self, cartan_type): """ @@ -288,7 +288,7 @@ def _coerce_map_from_(self, P): or isinstance(P, InfinityCrystalOfNonSimplyLacedRC))): from sage.combinat.rigged_configurations.bij_infinity import FromRCIsomorphism return FromRCIsomorphism(Hom(P, self)) - return super(InfinityCrystalOfTableaux, self)._coerce_map_from_(P) + return super()._coerce_map_from_(P) class Element(InfinityCrystalOfTableauxElement): r""" @@ -605,7 +605,7 @@ def __classcall_private__(cls, cartan_type): sage: B is B2 True """ - return super(InfinityCrystalOfTableauxTypeD, cls).__classcall__(cls, CartanType(cartan_type)) + return super().__classcall__(cls, CartanType(cartan_type)) @cached_method def module_generator(self): @@ -633,6 +633,7 @@ class Element(InfinityCrystalOfTableauxElementTypeD, InfinityCrystalOfTableaux.E """ pass + ######################################################### ## Queer superalgebra @@ -650,7 +651,7 @@ def __classcall_private__(cls, cartan_type): True """ cartan_type = CartanType(cartan_type) - return super(DualInfinityQueerCrystalOfTableaux, cls).__classcall__(cls, cartan_type) + return super().__classcall__(cls, cartan_type) def __init__(self, cartan_type): """ diff --git a/src/sage/combinat/crystals/kac_modules.py b/src/sage/combinat/crystals/kac_modules.py index f6244cd4158..90edd27d93d 100644 --- a/src/sage/combinat/crystals/kac_modules.py +++ b/src/sage/combinat/crystals/kac_modules.py @@ -62,7 +62,7 @@ def __classcall_private__(cls, cartan_type): sage: S1 is S2 True """ - return super(CrystalOfOddNegativeRoots, cls).__classcall__(cls, CartanType(cartan_type)) + return super().__classcall__(cls, CartanType(cartan_type)) def __init__(self, cartan_type): """ @@ -430,6 +430,7 @@ def weight(self): e = WLR.basis() return WLR.sum(-e[i]+e[j] for (i,j) in self.value) + class CrystalOfKacModule(UniqueRepresentation, Parent): r""" Crystal of a Kac module. @@ -530,7 +531,7 @@ def __classcall_private__(cls, cartan_type, la, mu): cartan_type = CartanType(cartan_type) la = _Partitions(la) mu = _Partitions(mu) - return super(CrystalOfKacModule, cls).__classcall__(cls, cartan_type, la, mu) + return super().__classcall__(cls, cartan_type, la, mu) def __init__(self, cartan_type, la, mu): """ diff --git a/src/sage/combinat/crystals/kyoto_path_model.py b/src/sage/combinat/crystals/kyoto_path_model.py index 12d3af264a6..6ee4d0e58a4 100644 --- a/src/sage/combinat/crystals/kyoto_path_model.py +++ b/src/sage/combinat/crystals/kyoto_path_model.py @@ -227,7 +227,7 @@ def __classcall_private__(cls, crystals, weight, P=None): enumerate(P.simple_coroots()) ) != level: raise ValueError( "{} is not a level {} weight".format(weight, level) ) - return super(KyotoPathModel, cls).__classcall__(cls, crystals, weight, P) + return super().__classcall__(cls, crystals, weight, P) def __init__(self, crystals, weight, P): """ diff --git a/src/sage/combinat/crystals/letters.pyx b/src/sage/combinat/crystals/letters.pyx index 8a06939ce07..29ca6086c57 100644 --- a/src/sage/combinat/crystals/letters.pyx +++ b/src/sage/combinat/crystals/letters.pyx @@ -104,7 +104,7 @@ def CrystalOfLetters(cartan_type, element_print_style=None, dual=None): else: return ClassicalCrystalOfLetters(ct, Crystal_of_letters_type_E6_element_dual, - element_print_style, dual = True) + element_print_style, dual=True) elif ct.letter == 'E' and ct.rank() == 7: return ClassicalCrystalOfLetters(ct, Crystal_of_letters_type_E7_element) elif ct.letter == 'E' and ct.rank() == 8 or ct.letter == 'F': @@ -116,6 +116,7 @@ def CrystalOfLetters(cartan_type, element_print_style=None, dual=None): else: raise NotImplementedError + class ClassicalCrystalOfLetters(UniqueRepresentation, Parent): r""" A generic class for classical crystals of letters. @@ -136,7 +137,8 @@ class ClassicalCrystalOfLetters(UniqueRepresentation, Parent): time: ``list``, ``cmp``, (todo: ``phi``, ``epsilon``, ``e``, and ``f`` with caching) """ - def __init__(self, cartan_type, element_class, element_print_style = None, dual = None): + def __init__(self, cartan_type, element_class, + element_print_style=None, dual=None): """ EXAMPLES:: @@ -146,7 +148,7 @@ class ClassicalCrystalOfLetters(UniqueRepresentation, Parent): sage: TestSuite(C).run() """ self.Element = element_class - Parent.__init__(self, category = ClassicalCrystals()) + Parent.__init__(self, category=ClassicalCrystals()) self._cartan_type = CartanType(cartan_type) self.rename("The crystal of letters for type %s" % self._cartan_type) if cartan_type.type() == 'E': @@ -166,7 +168,7 @@ class ClassicalCrystalOfLetters(UniqueRepresentation, Parent): C = CrystalOfNakajimaMonomials(cartan_type, la) hw = C.highest_weight_vector() self.module_generators = (self._element_constructor_(hw),) - self._list = [x for x in super(ClassicalCrystalOfLetters, self).__iter__()] + self._list = list(super(ClassicalCrystalOfLetters, self).__iter__()) elif cartan_type.type() == 'F': from sage.combinat.crystals.monomial_crystals import CrystalOfNakajimaMonomials from sage.combinat.root_system.root_system import RootSystem @@ -174,7 +176,7 @@ class ClassicalCrystalOfLetters(UniqueRepresentation, Parent): C = CrystalOfNakajimaMonomials(cartan_type, la) hw = C.highest_weight_vector() self.module_generators = (self._element_constructor_(hw),) - self._list = [x for x in super(ClassicalCrystalOfLetters, self).__iter__()] + self._list = list(super(ClassicalCrystalOfLetters, self).__iter__()) else: self.module_generators = (self._element_constructor_(1),) if cartan_type.type() == 'G': @@ -2520,7 +2522,7 @@ class CrystalOfBKKLetters(ClassicalCrystalOfLetters): if dual is None: dual = False ct = CartanType(ct) - return super(CrystalOfBKKLetters, cls).__classcall__(cls, ct, dual) + return super().__classcall__(cls, ct, dual) def __init__(self, ct, dual): """ @@ -2613,7 +2615,7 @@ class CrystalOfQueerLetters(ClassicalCrystalOfLetters): The queer crystal of letters for q(3) """ ct = CartanType(ct) - return super(CrystalOfQueerLetters, cls).__classcall__(cls, ct) + return super().__classcall__(cls, ct) def __init__(self, ct): """ diff --git a/src/sage/combinat/crystals/littelmann_path.py b/src/sage/combinat/crystals/littelmann_path.py index 9eb8ecd6dcd..2ddd6f3c461 100644 --- a/src/sage/combinat/crystals/littelmann_path.py +++ b/src/sage/combinat/crystals/littelmann_path.py @@ -702,7 +702,8 @@ def __classcall_private__(cls, weight): raise ValueError("The weight should be in the non-extended weight lattice!") La = weight.parent().basis() weight = weight - weight.level() * La[0] / La[0].level() - return super(CrystalOfLSPaths, cls).__classcall__(cls, weight, starting_weight_parent = weight.parent()) + return super().__classcall__(cls, weight, + starting_weight_parent=weight.parent()) @cached_method def maximal_vector(self): @@ -1208,7 +1209,7 @@ def __classcall_private__(cls, cartan_type): True """ cartan_type = CartanType(cartan_type) - return super(InfinityCrystalOfLSPaths, cls).__classcall__(cls, cartan_type) + return super().__classcall__(cls, cartan_type) def __init__(self, cartan_type): """ diff --git a/src/sage/combinat/crystals/monomial_crystals.py b/src/sage/combinat/crystals/monomial_crystals.py index 43d71f16747..5179ffd1a76 100644 --- a/src/sage/combinat/crystals/monomial_crystals.py +++ b/src/sage/combinat/crystals/monomial_crystals.py @@ -836,7 +836,7 @@ def __classcall_private__(cls, ct, c=None): cartan_type = CartanType(ct) n = len(cartan_type.index_set()) c = InfinityCrystalOfNakajimaMonomials._normalize_c(c, n) - M = super(InfinityCrystalOfNakajimaMonomials, cls).__classcall__(cls, cartan_type, c) + M = super().__classcall__(cls, cartan_type, c) M.set_variables('Y') return M @@ -1074,7 +1074,7 @@ def f(self, i): """ if self.phi(i) == 0: return None - return super(CrystalOfNakajimaMonomialsElement, self).f(i) + return super().f(i) def weight(self): r""" @@ -1197,7 +1197,7 @@ def __classcall_private__(cls, cartan_type, La=None, c=None): La = RootSystem(cartan_type).weight_lattice()(La) n = len(cartan_type.index_set()) c = InfinityCrystalOfNakajimaMonomials._normalize_c(c, n) - return super(CrystalOfNakajimaMonomials, cls).__classcall__(cls, cartan_type, La, c) + return super().__classcall__(cls, cartan_type, La, c) def __init__(self, ct, La, c): r""" diff --git a/src/sage/combinat/crystals/pbw_crystal.py b/src/sage/combinat/crystals/pbw_crystal.py index 1803b501f9e..4a313c8fa78 100644 --- a/src/sage/combinat/crystals/pbw_crystal.py +++ b/src/sage/combinat/crystals/pbw_crystal.py @@ -401,7 +401,7 @@ def __classcall__(cls, cartan_type): cartan_type = CartanType(cartan_type) if not cartan_type.is_finite(): raise NotImplementedError("only implemented for finite types") - return super(PBWCrystal, cls).__classcall__(cls, cartan_type) + return super().__classcall__(cls, cartan_type) def __init__(self, cartan_type): """ diff --git a/src/sage/combinat/crystals/polyhedral_realization.py b/src/sage/combinat/crystals/polyhedral_realization.py index 2860eec417c..a709284149f 100644 --- a/src/sage/combinat/crystals/polyhedral_realization.py +++ b/src/sage/combinat/crystals/polyhedral_realization.py @@ -25,6 +25,7 @@ from sage.combinat.crystals.elementary_crystals import ElementaryCrystal from sage.combinat.root_system.cartan_type import CartanType + class InfinityCrystalAsPolyhedralRealization(TensorProductOfCrystals): r""" The polyhedral realization of `B(\infty)`. @@ -156,7 +157,7 @@ def __classcall_private__(cls, cartan_type, seq=None): seq = tuple(seq) if set(seq) != set(cartan_type.index_set()): raise ValueError("the support of seq is not the index set") - return super(InfinityCrystalAsPolyhedralRealization, cls).__classcall__(cls, cartan_type, seq) + return super().__classcall__(cls, cartan_type, seq) def __init__(self, cartan_type, seq): """ diff --git a/src/sage/combinat/crystals/subcrystal.py b/src/sage/combinat/crystals/subcrystal.py index 9881a021e27..ed1c62f4cf5 100644 --- a/src/sage/combinat/crystals/subcrystal.py +++ b/src/sage/combinat/crystals/subcrystal.py @@ -23,6 +23,8 @@ # http://www.gnu.org/licenses/ #**************************************************************************** +import collections.abc + from sage.misc.lazy_attribute import lazy_attribute from sage.structure.unique_representation import UniqueRepresentation from sage.structure.parent import Parent @@ -126,7 +128,7 @@ def __classcall_private__(cls, ambient, contained=None, generators=None, sage: S1 is S2 True """ - if isinstance(contained, (list, tuple, set, frozenset)): + if isinstance(contained, (collections.abc.Sequence, collections.abc.Set)): contained = frozenset(contained) #elif contained in Sets(): @@ -159,11 +161,11 @@ def __classcall_private__(cls, ambient, contained=None, generators=None, generators, cartan_type, index_set, category) # We need to give these as optional arguments so it unpickles correctly - return super(Subcrystal, cls).__classcall__(cls, ambient, contained, - tuple(generators), - cartan_type=cartan_type, - index_set=tuple(index_set), - category=category) + return super().__classcall__(cls, ambient, contained, + tuple(generators), + cartan_type=cartan_type, + index_set=tuple(index_set), + category=category) def __init__(self, ambient, contained, generators, cartan_type, index_set, category): """ @@ -291,7 +293,7 @@ def cardinality(self): if self in FiniteCrystals(): return Integer(len(self.list())) try: - card = super(Subcrystal, self).cardinality() + card = super().cardinality() except AttributeError: raise NotImplementedError("unknown cardinality") if card == infinity: diff --git a/src/sage/combinat/crystals/tensor_product.py b/src/sage/combinat/crystals/tensor_product.py index c15eac03795..18d80008ccb 100644 --- a/src/sage/combinat/crystals/tensor_product.py +++ b/src/sage/combinat/crystals/tensor_product.py @@ -689,6 +689,7 @@ class FullTensorProductOfQueerSuperCrystals(FullTensorProductOfCrystals, QueerSu class Element(TensorProductOfQueerSuperCrystalsElement): pass + ######################################################### ## Crystal of tableaux @@ -931,7 +932,7 @@ def __classcall_private__(cls, cartan_type, shapes = None, shape = None): raise ValueError("shapes should all be partitions or half-integer partitions") if spin_shapes == shapes: shapes = tuple(_Partitions(shape) if shape[n1-1] in NN else shape for shape in shapes) - return super(CrystalOfTableaux, cls).__classcall__(cls, cartan_type, shapes) + return super().__classcall__(cls, cartan_type, shapes) # Handle the construction of a crystals of spin tableaux # Caveat: this currently only supports all shapes being half @@ -979,7 +980,7 @@ def __init__(self, cartan_type, shapes): sage: T = crystals.Tableaux(['A',3], shape = [2,2]) sage: TestSuite(T).run() """ -# super(CrystalOfTableaux, self).__init__(category = FiniteEnumeratedSets()) +# super().__init__(category = FiniteEnumeratedSets()) Parent.__init__(self, category = ClassicalCrystals()) self.letters = CrystalOfLetters(cartan_type) self.shapes = shapes diff --git a/src/sage/combinat/crystals/tensor_product_element.pyx b/src/sage/combinat/crystals/tensor_product_element.pyx index b7f60016ecc..fad1ced578d 100644 --- a/src/sage/combinat/crystals/tensor_product_element.pyx +++ b/src/sage/combinat/crystals/tensor_product_element.pyx @@ -478,6 +478,7 @@ cdef class TensorProductOfCrystalsElement(ImmutableListWithParent): return self._set_index(-k, crystal) return None + cdef class TensorProductOfRegularCrystalsElement(TensorProductOfCrystalsElement): """ Element class for a tensor product of regular crystals. @@ -1651,6 +1652,7 @@ cdef class TensorProductOfQueerSuperCrystalsElement(TensorProductOfRegularCrysta x = x.f(i) return string_length + cdef class InfinityQueerCrystalOfTableauxElement(TensorProductOfQueerSuperCrystalsElement): def __init__(self, parent, list, row_lengths=[]): """ @@ -1673,7 +1675,7 @@ cdef class InfinityQueerCrystalOfTableauxElement(TensorProductOfQueerSuperCrysta row_lengths.append(len(row)) list = ret self._row_lengths = row_lengths - super(InfinityQueerCrystalOfTableauxElement, self).__init__(parent, list) + super().__init__(parent, list) def _repr_(self): r""" @@ -1768,7 +1770,7 @@ cdef class InfinityQueerCrystalOfTableauxElement(TensorProductOfQueerSuperCrysta [[4, 4, 4, 4, 4, 3, 2, 1], [3, 3, 3, 3], [2, 2, 1], [1]] sage: t.e(-1) """ - ret = super(InfinityQueerCrystalOfTableauxElement, self).e(i) + ret = super().e(i) if ret is None: return None ( ret)._row_lengths = self._row_lengths diff --git a/src/sage/combinat/crystals/virtual_crystal.py b/src/sage/combinat/crystals/virtual_crystal.py index a537a64bfb8..0283b4a9543 100644 --- a/src/sage/combinat/crystals/virtual_crystal.py +++ b/src/sage/combinat/crystals/virtual_crystal.py @@ -23,14 +23,13 @@ # # http://www.gnu.org/licenses/ #**************************************************************************** - - from sage.categories.crystals import Crystals from sage.categories.finite_crystals import FiniteCrystals from sage.combinat.root_system.cartan_type import CartanType from sage.combinat.crystals.subcrystal import Subcrystal from sage.sets.family import Family + class VirtualCrystal(Subcrystal): r""" A virtual crystal `V` of an ambient crystal `\widehat{B}` is a crystal @@ -195,9 +194,10 @@ def __classcall_private__(cls, ambient, virtualization, scaling_factors, if ambient in FiniteCrystals() or isinstance(contained, frozenset): category = category.Finite() - return super(Subcrystal, cls).__classcall__(cls, ambient, virtualization, scaling_factors, - contained, tuple(generators), cartan_type, - tuple(index_set), category) + return super().__classcall__(cls, ambient, virtualization, + scaling_factors, contained, + tuple(generators), cartan_type, + tuple(index_set), category) def __init__(self, ambient, virtualization, scaling_factors, contained, generators, cartan_type, index_set, category): diff --git a/src/sage/combinat/decorated_permutation.py b/src/sage/combinat/decorated_permutation.py index 2632364aff2..03d3e2fb495 100644 --- a/src/sage/combinat/decorated_permutation.py +++ b/src/sage/combinat/decorated_permutation.py @@ -86,6 +86,17 @@ def check(self): if self not in self.parent(): raise ValueError("{} is not a decorated permutation".format(self)) + def __hash__(self): + r""" + Return a hash of ``self``. + + TESTS:: + + sage: len(set(hash(pi) for pi in DecoratedPermutations(3))) + 16 + """ + return hash(tuple(self)) + def __eq__(self, other): """ Check whether ``self`` is equal to ``other``. diff --git a/src/sage/combinat/designs/bibd.py b/src/sage/combinat/designs/bibd.py index ac6a869a98d..dc9b6c39f3f 100644 --- a/src/sage/combinat/designs/bibd.py +++ b/src/sage/combinat/designs/bibd.py @@ -260,7 +260,7 @@ def balanced_incomplete_block_design(v, k, lambd=1, existence=False, use_LJCR=Fa return False raise EmptySetError("There exists no ({},{},{})-BIBD".format(v, k, lambd)) - # Non-esistence by BRC Theoerem + # Non-existence by BRC Theorem if BruckRyserChowla_check(v, k, lambd) is False: if existence: return False diff --git a/src/sage/combinat/designs/incidence_structures.py b/src/sage/combinat/designs/incidence_structures.py index 5bbe6030ffb..49b564ce8c3 100644 --- a/src/sage/combinat/designs/incidence_structures.py +++ b/src/sage/combinat/designs/incidence_structures.py @@ -1383,7 +1383,7 @@ def relabel(self, perm=None, inplace=True): raise ValueError("perm argument must be None, a list or a dictionary") if len(set(perm.values())) != len(perm): - raise ValueError("Two points are getting relabelled with the same name !") + raise ValueError("two points are getting relabelled with the same name") self._points = [perm[x] for x in self._points] if self._points == list(range(self.num_points())): diff --git a/src/sage/combinat/designs/steiner_quadruple_systems.py b/src/sage/combinat/designs/steiner_quadruple_systems.py index e022000bd61..2a8713fe323 100644 --- a/src/sage/combinat/designs/steiner_quadruple_systems.py +++ b/src/sage/combinat/designs/steiner_quadruple_systems.py @@ -741,10 +741,10 @@ def steiner_quadruple_system(n, check = False): nn = (n+10) // 12 sqs = twelve_n_minus_ten(steiner_quadruple_system(nn, check = False)) else: - raise ValueError("This shouldn't happen !") + raise ValueError("this should never happen") if check and not sqs.is_t_design(3,n,4,1): - raise RuntimeError("Something is very very wrong.") + raise RuntimeError("something is very very wrong") return sqs diff --git a/src/sage/combinat/designs/subhypergraph_search.pyx b/src/sage/combinat/designs/subhypergraph_search.pyx index dd762e9bc7a..0b48819e15a 100644 --- a/src/sage/combinat/designs/subhypergraph_search.pyx +++ b/src/sage/combinat/designs/subhypergraph_search.pyx @@ -147,7 +147,7 @@ cdef inline void bs_set(uint64_t * bitset, int index, int bit): cdef inline int bs_issubset64(uint64_t * b1, uint64_t b2, int limbs): r""" - Test whether bistet ``b1`` (on ``limbs`` blocks) is a subset of b2 (one block). + Test whether bitset ``b1`` (on ``limbs`` blocks) is a subset of b2 (one block). It implies in particular that all last `limbs-1` blocks of ``b1`` are equal to zero. diff --git a/src/sage/combinat/free_dendriform_algebra.py b/src/sage/combinat/free_dendriform_algebra.py index 04b8cf1208e..1bdcd1dbe31 100644 --- a/src/sage/combinat/free_dendriform_algebra.py +++ b/src/sage/combinat/free_dendriform_algebra.py @@ -12,7 +12,7 @@ # 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.categories.hopf_algebras import HopfAlgebras @@ -30,6 +30,7 @@ from sage.misc.cachefunc import cached_method from sage.sets.family import Family from sage.structure.coerce_exceptions import CoercionException +from sage.rings.infinity import Infinity class FreeDendriformAlgebra(CombinatorialFreeModule): @@ -115,6 +116,16 @@ class FreeDendriformAlgebra(CombinatorialFreeModule): sage: w * w * w B[[., [., [., .]]]] + B[[., [[., .], .]]] + B[[[., .], [., .]]] + B[[[., [., .]], .]] + B[[[[., .], .], .]] + The set `E` can be infinite:: + + sage: F = algebras.FreeDendriform(QQ, ZZ) + sage: w = F.gen(1); w + B[1[., .]] + sage: x = F.gen(2); x + B[-1[., .]] + sage: w*x + B[-1[1[., .], .]] + B[1[., -1[., .]]] + REFERENCES: - [LR1998]_ @@ -193,14 +204,20 @@ def _repr_(self): Free Dendriform algebra on one generator ['@'] over Rational Field """ n = self.algebra_generators().cardinality() - if n == 1: + finite = bool(n < Infinity) + if not finite: + gen = "generators indexed by" + elif n == 1: gen = "one generator" else: gen = "{} generators".format(n) s = "Free Dendriform algebra on {} {} over {}" - try: - return s.format(gen, self._alphabet.list(), self.base_ring()) - except NotImplementedError: + if finite: + try: + return s.format(gen, self._alphabet.list(), self.base_ring()) + except NotImplementedError: + return s.format(gen, self._alphabet, self.base_ring()) + else: return s.format(gen, self._alphabet, self.base_ring()) def gen(self, i): diff --git a/src/sage/combinat/free_module.py b/src/sage/combinat/free_module.py index a2f789a4202..d6042d6facc 100644 --- a/src/sage/combinat/free_module.py +++ b/src/sage/combinat/free_module.py @@ -290,8 +290,9 @@ def __classcall_private__(cls, base_ring, basis_keys=None, category=None, sage: F3 = CombinatorialFreeModule(QQ, ['a','b'], category = (ModulesWithBasis(QQ),)) sage: F4 = CombinatorialFreeModule(QQ, ['a','b'], category = (ModulesWithBasis(QQ),CommutativeAdditiveSemigroups())) sage: F5 = CombinatorialFreeModule(QQ, ['a','b'], category = (ModulesWithBasis(QQ),Category.join((LeftModules(QQ), RightModules(QQ))))) - sage: F1 is F, F2 is F, F3 is F, F4 is F, F5 is F - (True, True, True, True, True) + sage: F6 = CombinatorialFreeModule(QQ, ['a','b'], category=ModulesWithBasis(QQ).FiniteDimensional()) + sage: F1 is F, F2 is F, F3 is F, F4 is F, F5 is F, F6 is F + (True, True, True, True, True, True) sage: G = CombinatorialFreeModule(QQ, ['a','b'], category = AlgebrasWithBasis(QQ)) sage: F is G @@ -302,6 +303,8 @@ def __classcall_private__(cls, base_ring, basis_keys=None, category=None, if isinstance(basis_keys, (list, tuple)): basis_keys = FiniteEnumeratedSet(basis_keys) category = ModulesWithBasis(base_ring).or_subcategory(category) + if basis_keys in Sets().Finite(): + category = category.FiniteDimensional() # bracket or latex_bracket might be lists, so convert # them to tuples so that they're hashable. bracket = keywords.get('bracket', None) @@ -458,6 +461,27 @@ def __init__(self, R, basis_keys=None, element_class=None, category=None, self._order = None + def construction(self): + """ + The construction functor and base ring for self. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a','b','c'], category=AlgebrasWithBasis(QQ)) + sage: F.construction() + (VectorFunctor, Rational Field) + """ + # Try to take it from the category + c = super().construction() + if c is not None: + return c + if self.__class__.__base__ != CombinatorialFreeModule: + # The construction is not suitable for subclasses + return None + from sage.categories.pushout import VectorFunctor + return VectorFunctor(None, True, None, with_basis='standard', + basis_keys=self.basis().keys()), self.base_ring() + # For backwards compatibility _repr_term = IndexedGenerators._repr_generator _latex_term = IndexedGenerators._latex_generator @@ -1159,8 +1183,8 @@ class CombinatorialFreeModule_Tensor(CombinatorialFreeModule): We construct two free modules, assign them short names, and construct their tensor product:: - sage: F = CombinatorialFreeModule(ZZ, [1,2]); F.__custom_name = "F" - sage: G = CombinatorialFreeModule(ZZ, [3,4]); G.__custom_name = "G" + sage: F = CombinatorialFreeModule(ZZ, [1,2]); F.rename("F") + sage: G = CombinatorialFreeModule(ZZ, [3,4]); G.rename("G") sage: T = tensor([F, G]); T F # G @@ -1306,6 +1330,23 @@ def _repr_(self): return symb.join("%s" % module for module in self._sets) # TODO: make this overridable by setting _name + def tensor_factors(self): + """ + Return the tensor factors of this tensor product. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(ZZ, [1,2]) + sage: F.rename("F") + sage: G = CombinatorialFreeModule(ZZ, [3,4]) + sage: G.rename("G") + sage: T = tensor([F, G]); T + F # G + sage: T.tensor_factors() + (F, G) + """ + return self._sets + def _ascii_art_(self, term): """ TESTS:: @@ -1430,8 +1471,8 @@ def tensor_constructor(self, modules): EXAMPLES:: - sage: F = CombinatorialFreeModule(ZZ, [1,2]); F.__custom_name = "F" - sage: G = CombinatorialFreeModule(ZZ, [3,4]); G.__custom_name = "G" + sage: F = CombinatorialFreeModule(ZZ, [1,2]); F.rename("F") + sage: G = CombinatorialFreeModule(ZZ, [3,4]); G.rename("G") sage: H = CombinatorialFreeModule(ZZ, [5,6]); H.rename("H") sage: f = F.monomial(1) + 2 * F.monomial(2) @@ -1470,8 +1511,8 @@ def _tensor_of_elements(self, elements): EXAMPLES:: - sage: F = CombinatorialFreeModule(ZZ, [1,2]); F.__custom_name = "F" - sage: G = CombinatorialFreeModule(ZZ, [3,4]); G.__custom_name = "G" + sage: F = CombinatorialFreeModule(ZZ, [1,2]); F.rename("F") + sage: G = CombinatorialFreeModule(ZZ, [3,4]); G.rename("G") sage: H = CombinatorialFreeModule(ZZ, [5,6]); H.rename("H") sage: f = F.monomial(1) + 2 * F.monomial(2) @@ -1605,9 +1646,9 @@ class CombinatorialFreeModule_CartesianProduct(CombinatorialFreeModule): We construct two free modules, assign them short names, and construct their Cartesian product:: - sage: F = CombinatorialFreeModule(ZZ, [4,5]); F.__custom_name = "F" - sage: G = CombinatorialFreeModule(ZZ, [4,6]); G.__custom_name = "G" - sage: H = CombinatorialFreeModule(ZZ, [4,7]); H.__custom_name = "H" + sage: F = CombinatorialFreeModule(ZZ, [4,5]); F.rename("F") + sage: G = CombinatorialFreeModule(ZZ, [4,6]); G.rename("G") + sage: H = CombinatorialFreeModule(ZZ, [4,7]); H.rename("H") sage: S = cartesian_product([F, G]) sage: S F (+) G @@ -1679,7 +1720,7 @@ def _repr_(self): sage: F = CombinatorialFreeModule(ZZ, [2,4,5]) sage: CP = cartesian_product([F, F]); CP # indirect doctest Free module generated by {2, 4, 5} over Integer Ring (+) Free module generated by {2, 4, 5} over Integer Ring - sage: F.__custom_name = "F"; CP + sage: F.rename("F"); CP F (+) F """ from sage.categories.cartesian_product import cartesian_product @@ -1699,8 +1740,8 @@ def cartesian_embedding(self, i): EXAMPLES:: - sage: F = CombinatorialFreeModule(ZZ, [4,5]); F.__custom_name = "F" - sage: G = CombinatorialFreeModule(ZZ, [4,6]); G.__custom_name = "G" + sage: F = CombinatorialFreeModule(ZZ, [4,5]); F.rename("F") + sage: G = CombinatorialFreeModule(ZZ, [4,6]); G.rename("G") sage: S = cartesian_product([F, G]) sage: phi = S.cartesian_embedding(0) sage: phi(F.monomial(4) + 2 * F.monomial(5)) @@ -1733,8 +1774,8 @@ def cartesian_projection(self, i): EXAMPLES:: - sage: F = CombinatorialFreeModule(ZZ, [4,5]); F.__custom_name = "F" - sage: G = CombinatorialFreeModule(ZZ, [4,6]); G.__custom_name = "G" + sage: F = CombinatorialFreeModule(ZZ, [4,5]); F.rename("F") + sage: G = CombinatorialFreeModule(ZZ, [4,6]); G.rename("G") sage: S = cartesian_product([F, G]) sage: x = S.monomial((0,4)) + 2 * S.monomial((0,5)) + 3 * S.monomial((1,6)) sage: S.cartesian_projection(0)(x) @@ -1763,8 +1804,8 @@ def _cartesian_product_of_elements(self, elements): EXAMPLES:: - sage: F = CombinatorialFreeModule(ZZ, [4,5]); F.__custom_name = "F" - sage: G = CombinatorialFreeModule(ZZ, [4,6]); G.__custom_name = "G" + sage: F = CombinatorialFreeModule(ZZ, [4,5]); F.rename("F") + sage: G = CombinatorialFreeModule(ZZ, [4,6]); G.rename("G") sage: S = cartesian_product([F, G]) sage: f = F.monomial(4) + 2 * F.monomial(5) sage: g = 2*G.monomial(4) + G.monomial(6) @@ -1802,12 +1843,11 @@ def cartesian_factors(self): EXAMPLES:: - sage: F = CombinatorialFreeModule(ZZ, [4,5]); F.__custom_name = "F" - sage: G = CombinatorialFreeModule(ZZ, [4,6]); G.__custom_name = "G" + sage: F = CombinatorialFreeModule(ZZ, [4,5]); F.rename("F") + sage: G = CombinatorialFreeModule(ZZ, [4,6]); G.rename("G") sage: S = cartesian_product([F, G]) sage: S.cartesian_factors() (F, G) - """ return self._sets diff --git a/src/sage/combinat/integer_vector.py b/src/sage/combinat/integer_vector.py index ee73284a59c..e0cd57a3eec 100644 --- a/src/sage/combinat/integer_vector.py +++ b/src/sage/combinat/integer_vector.py @@ -30,6 +30,7 @@ from sage.combinat.integer_lists import IntegerListsLex from itertools import product +from collections.abc import Sequence import numbers from sage.structure.parent import Parent @@ -453,9 +454,29 @@ def check(self): sage: IV = IntegerVectors() sage: elt = IV([1,2,1]) sage: elt.check() + + Check :trac:`34510`:: + + sage: IV3 = IntegerVectors(n=3) + sage: IV3([2,2]) + Traceback (most recent call last): + ... + ValueError: [2, 2] doesn't satisfy correct constraints + sage: IVk3 = IntegerVectors(k=3) + sage: IVk3([2,2]) + Traceback (most recent call last): + ... + ValueError: [2, 2] doesn't satisfy correct constraints + sage: IV33 = IntegerVectors(n=3, k=3) + sage: IV33([2,2]) + Traceback (most recent call last): + ... + ValueError: [2, 2] doesn't satisfy correct constraints """ if any(x < 0 for x in self): raise ValueError("all entries must be non-negative") + if self not in self.parent(): + raise ValueError(f"{self} doesn't satisfy correct constraints") class IntegerVectors(Parent, metaclass=ClasscallMetaclass): @@ -676,7 +697,7 @@ def __contains__(self, x): if isinstance(x, IntegerVector): return True - if not isinstance(x, (list, tuple)): + if not isinstance(x, Sequence): return False for i in x: @@ -1015,10 +1036,15 @@ def __contains__(self, x): False sage: [3,2,2,1] in IntegerVectors(8, 4) True - """ - if isinstance(x, IntegerVector) and x.parent() is self: - return True + Check :trac:`34510`:: + + sage: IV33 = IntegerVectors(n=3, k=3) + sage: IV33([0]) + Traceback (most recent call last): + ... + ValueError: [0] doesn't satisfy correct constraints + """ if not IntegerVectors.__contains__(self, x): return False diff --git a/src/sage/combinat/interval_posets.py b/src/sage/combinat/interval_posets.py index 3882bf26436..cd0846e3d6e 100644 --- a/src/sage/combinat/interval_posets.py +++ b/src/sage/combinat/interval_posets.py @@ -1482,8 +1482,8 @@ def _richcmp_(self, other, op) -> bool: return (self.size() == other.size() and self._cover_relations == other._cover_relations) if op == op_NE: - return not(self.size() == other.size() and - self._cover_relations == other._cover_relations) + return not (self.size() == other.size() and + self._cover_relations == other._cover_relations) return richcmp((self.size(), self.cubical_coordinates()), (other.size(), other.cubical_coordinates()), op) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index ae5e573fd0b..2046814a92e 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -959,7 +959,7 @@ def from_recurrence(self, *args, **kwds): - `f(k^M n + r) = c_{r,l} f(k^m n + l) + c_{r,l + 1} f(k^m n + l + 1) + ... + c_{r,u} f(k^m n + u)` for some integers `0 \leq r < k^M`, `M > m \geq 0` and `l \leq u`, and some - coefficients `c_{r,j}` from the (semi)ring ``coefficents`` + coefficients `c_{r,j}` from the (semi)ring ``coefficients`` of the corresponding :class:`kRegularSequenceSpace`, valid for all integers `n \geq \text{offset}` for some integer `\text{offset} \geq \max(-l/k^m, 0)` (default: ``0``), and @@ -1002,9 +1002,14 @@ def from_recurrence(self, *args, **kwds): Optional keyword-only argument: - - ``offset`` -- an integer (default: ``0``). See explanation of + - ``offset`` -- (default: ``0``) an integer. See explanation of ``equations`` above. + - ``inhomogeneities`` -- (default: ``{}``) a dictionary + mapping integers ``r`` to the inhomogeneity `g_r` as given + in [HKL2021]_, Corollary D. All inhomogeneities have to be + regular sequences from ``self`` or elements of ``coefficient_ring``. + OUTPUT: a :class:`kRegularSequence` EXAMPLES: @@ -1016,9 +1021,10 @@ def from_recurrence(self, *args, **kwds): n sage: function('f') f - sage: Seq2.from_recurrence([ + sage: SB = Seq2.from_recurrence([ ....: f(2*n) == f(n), f(2*n + 1) == f(n) + f(n + 1), ....: f(0) == 0, f(1) == 1], f, n) + sage: SB 2-regular sequence 0, 1, 1, 2, 1, 3, 2, 3, 1, 4, ... Number of Odd Entries in Pascal's Triangle:: @@ -1030,7 +1036,7 @@ def from_recurrence(self, *args, **kwds): Number of Unbordered Factors in the Thue--Morse Sequence:: - sage: Seq2.from_recurrence([ + sage: UB = Seq2.from_recurrence([ ....: f(8*n) == 2*f(4*n), ....: f(8*n + 1) == f(4*n + 1), ....: f(8*n + 2) == f(4*n + 1) + f(4*n + 3), @@ -1044,28 +1050,65 @@ def from_recurrence(self, *args, **kwds): ....: f(10) == 4, f(11) == 4, f(12) == 12, f(13) == 0, f(14) == 4, ....: f(15) == 4, f(16) == 8, f(17) == 4, f(18) == 8, f(19) == 0, ....: f(20) == 8, f(21) == 4, f(22) == 4, f(23) == 8], f, n, offset=3) + sage: UB 2-regular sequence 1, 2, 2, 4, 2, 4, 6, 0, 4, 4, ... + Binary sum of digits `S(n)`, characterized by the recurrence relations + `S(4n) = S(2n)`, `S(4n + 1) = S(2n + 1)`, `S(4n + 2) = S(2n + 1)` and + `S(4n + 3) = -S(2n) + 2S(2n + 1)`:: + + sage: S = Seq2.from_recurrence([ + ....: f(4*n) == f(2*n), + ....: f(4*n + 1) == f(2*n + 1), + ....: f(4*n + 2) == f(2*n + 1), + ....: f(4*n + 3) == -f(2*n) + 2*f(2*n + 1), + ....: f(0) == 0, f(1) == 1], f, n) + sage: S + 2-regular sequence 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, ... + + In order to check if this sequence is indeed the binary sum of digits, + we construct it directly via its linear representation and compare it + with ``S``:: + + sage: S2 = Seq2( + ....: (Matrix([[1, 0], [0, 1]]), Matrix([[1, 0], [1, 1]])), + ....: left=vector([0, 1]), right=vector([1, 0])) + sage: (S - S2).is_trivial_zero() + True + + Alternatively, we can also use the simpler but inhomogeneous recurrence relations + `S(2n) = S(n)` and `S(2n+1) = S(n) + 1` via direct parameters:: + + sage: S3 = Seq2.from_recurrence(M=1, m=0, + ....: coeffs={(0, 0): 1, (1, 0): 1}, + ....: initial_values={0: 0, 1: 1}, + ....: inhomogeneities={1: 1}) + sage: S3 + 2-regular sequence 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, ... + sage: (S3 - S2).is_trivial_zero() + True + Number of Non-Zero Elements in the Generalized Pascal's Triangle (see [LRS2017]_):: sage: Seq2 = kRegularSequenceSpace(2, QQ) - sage: Seq2.from_recurrence([ + sage: P = Seq2.from_recurrence([ ....: f(4*n) == 5/3*f(2*n) - 1/3*f(2*n + 1), ....: f(4*n + 1) == 4/3*f(2*n) + 1/3*f(2*n + 1), ....: f(4*n + 2) == 1/3*f(2*n) + 4/3*f(2*n + 1), ....: f(4*n + 3) == -1/3*f(2*n) + 5/3*f(2*n + 1), ....: f(0) == 1, f(1) == 2], f, n) + sage: P 2-regular sequence 1, 2, 3, 3, 4, 5, 5, 4, 5, 7, ... Finally, the same sequence can also be obtained via direct parameters without symbolic equations:: - sage: Seq2.from_recurrence(2, 1, - ....: {(0, 0): 5/3, (0, 1): -1/3, - ....: (1, 0): 4/3, (1, 1): 1/3, - ....: (2, 0): 1/3, (2, 1): 4/3, - ....: (3, 0): -1/3, (3, 1): 5/3}, - ....: {0: 1, 1: 2}) + sage: Seq2.from_recurrence(M=2, m=1, + ....: coeffs={(0, 0): 5/3, (0, 1): -1/3, + ....: (1, 0): 4/3, (1, 1): 1/3, + ....: (2, 0): 1/3, (2, 1): 4/3, + ....: (3, 0): -1/3, (3, 1): 5/3}, + ....: initial_values={0: 1, 1: 2}) 2-regular sequence 1, 2, 3, 3, 4, 5, 5, 4, 5, 7, ... TESTS:: @@ -1137,7 +1180,7 @@ def from_recurrence(self, *args, **kwds): ....: g(22) == 22, g(23) == 23, g(24) == 24, g(25) == 25, ....: g(26) == 26, g(27) == 27, g(28) == 28, g(29) == 29, ....: g(30) == 30, g(31) == 31], g, m, offset=8) - sage: all([S[i] == T[i] for i in srange(1000)]) + sage: (S - T).is_trivial_zero() True Zero-sequence with non-zero initial values:: @@ -1155,6 +1198,69 @@ def from_recurrence(self, *args, **kwds): ....: f(2*n) == 0, f(2*n + 1) == 0, ....: f(0) == 1, f(1) == 1, f(2) == 2, f(3) == 3], f, n, offset=2) 2-regular sequence 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, ... + + Check if inhomogeneities `0` do not change the sequence:: + + sage: Seq2.from_recurrence([ + ....: f(2*n) == 0, f(2*n + 1) == 0, + ....: f(0) == 1, f(1) == 1, f(2) == 2, f(3) == 3], f, n, offset=2, + ....: inhomogeneities={0: 0, 1: Seq2.zero()}) + 2-regular sequence 1, 1, 2, 3, 0, 0, 0, 0, 0, 0, ... + + :: + + sage: S = Seq2([matrix([[3/2, -1, 1], [0, 1/2, 1/2], [0, -1, 2]]), + ....: matrix([[-1, 0, 1], [1, 5, -5], [-4, 0, 0]])], + ....: left=vector([1, 2, 3]), + ....: right=vector([0, 1, 1])) + sage: T = Seq2.from_recurrence(M=3, m=2, + ....: coeffs={}, + ....: initial_values={0: S[0]}, + ....: inhomogeneities={i: S.subsequence(2**3, i) for i in srange(2**3)}) + sage: (S - T).is_trivial_zero() + True + + Connection between the Stern--Brocot sequence and the number + of non-zero elements in the generalized Pascal's triangle (see + [LRS2017]_):: + + sage: U = Seq2.from_recurrence(M=1, m=0, + ....: coeffs={(0, 0): 1}, + ....: initial_values={0: 0, 1: 1}, + ....: inhomogeneities={1: P}) + sage: (U - Seq2(SB)).is_trivial_zero() + True + + :: + + sage: U = Seq2.from_recurrence(M=1, m=0, + ....: coeffs={}, + ....: initial_values={0: 0, 1: 1}, + ....: inhomogeneities={0: SB, 1: P}) + sage: (U - Seq2(SB)).is_trivial_zero() + True + + Number of Unbordered Factors in the Thue--Morse Sequence, but partly + encoded with inhomogeneities:: + + sage: UB2 = Seq2.from_recurrence([ + ....: f(8*n) == 2*f(4*n), + ....: f(8*n + 1) == f(4*n + 1), + ....: f(8*n + 2) == f(4*n + 1), + ....: f(8*n + 3) == f(4*n + 2), + ....: f(8*n + 4) == 2*f(4*n + 2), + ....: f(8*n + 5) == f(4*n + 3), + ....: f(8*n + 6) == -f(4*n + 1), + ....: f(8*n + 7) == 2*f(4*n + 1) + f(4*n + 3), + ....: f(0) == 1, f(1) == 2, f(2) == 2, f(3) == 4, f(4) == 2, + ....: f(5) == 4, f(6) == 6, f(7) == 0, f(8) == 4, f(9) == 4, + ....: f(10) == 4, f(11) == 4, f(12) == 12, f(13) == 0, f(14) == 4, + ....: f(15) == 4, f(16) == 8, f(17) == 4, f(18) == 8, f(19) == 0, + ....: f(20) == 8, f(21) == 4, f(22) == 4, f(23) == 8], f, n, offset=3, + ....: inhomogeneities={2: UB.subsequence(4, 3), 3: -UB.subsequence(4, 1), + ....: 6: UB.subsequence(4, 2) + UB.subsequence(4, 3)}) + sage: (UB2 - Seq2(UB)).is_trivial_zero() + True """ RP = RecurrenceParser(self.k, self.coefficient_ring()) mu, left, right = RP(*args, **kwds) @@ -1955,7 +2061,7 @@ def parse_direct_arguments(self, M, m, coeffs, initial_values): return (M, m, coeffs, initial_values) - def parameters(self, M, m, coeffs, initial_values, offset=0): + def parameters(self, M, m, coeffs, initial_values, offset=0, inhomogeneities={}): r""" Determine parameters from recurrence relations as admissible in :meth:`kRegularSequenceSpace.from_recurrence`. @@ -1981,6 +2087,9 @@ def parameters(self, M, m, coeffs, initial_values, offset=0): - ``initial_values`` -- a dictionary mapping integers ``n`` to the ``n``-th value of the sequence + - ``inhomogeneities`` -- a dictionary mapping integers ``r`` + to the inhomogeneity `g_r` as given in [HKL2021]_, Corollary D. + EXAMPLES:: sage: from sage.combinat.k_regular_sequence import RecurrenceParser @@ -1988,14 +2097,15 @@ def parameters(self, M, m, coeffs, initial_values, offset=0): sage: RP.parameters(2, 1, ....: {(0, -2): 3, (0, 0): 1, (0, 1): 2, (1, -2): 6, (1, 0): 4, ....: (1, 1): 5, (2, -2): 9, (2, 0): 7, (2, 1): 8, (3, -2): 12, - ....: (3, 0): 10, (3, 1): 11}, {0: 1, 1: 2, 2: 1, 3: 4}, 0) + ....: (3, 0): 10, (3, 1): 11}, {0: 1, 1: 2, 2: 1, 3: 4}, 0, {0: 1}) recurrence_rules(M=2, m=1, l=-2, u=1, ll=-6, uu=3, dim=14, coeffs={(0, -2): 3, (0, 0): 1, (0, 1): 2, (1, -2): 6, (1, 0): 4, (1, 1): 5, (2, -2): 9, (2, 0): 7, (2, 1): 8, (3, -2): 12, (3, 0): 10, (3, 1): 11}, initial_values={0: 1, 1: 2, 2: 1, 3: 4, - 4: 12, 5: 30, 6: 48, 7: 66, 8: 75, 9: 204, 10: 333, 11: 462, - 12: 216, 13: 594, -6: 0, -5: 0, -4: 0, -3: 0, -2: 0, -1: 0}, - offset=1, n1=3) + 4: 13, 5: 30, 6: 48, 7: 66, 8: 77, 9: 208, 10: 340, 11: 472, + 12: 220, 13: 600, -6: 0, -5: 0, -4: 0, -3: 0, -2: 0, -1: 0}, + offset=1, n1=3, inhomogeneities={0: 2-regular sequence 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, ...}) .. SEEALSO:: @@ -2003,6 +2113,37 @@ def parameters(self, M, m, coeffs, initial_values, offset=0): TESTS:: + sage: var('n') + n + sage: RP.parameters(1, 0, {(0, 0): 1}, {}, 0, + ....: {-1: 0, 1: 0, 10: 0, I: 0, n: 0}) + Traceback (most recent call last): + ... + ValueError: Indices [-1, 10, I, n] for inhomogeneities are + no integers between 0 and 1. + + :: + + sage: RP.parameters(1, 0, {(0, 0): 1}, {}, 0, + ....: {0: n}) + Traceback (most recent call last): + ... + ValueError: Inhomogeneities {0: n} are neither 2-regular sequences + nor elements of Integer Ring. + + :: + + sage: Seq3 = kRegularSequenceSpace(3, ZZ) + sage: RP.parameters(1, 0, {(0, 0): 1}, {}, 0, + ....: {0: Seq3.zero()}) + Traceback (most recent call last): + ... + ValueError: Inhomogeneities {0: 3-regular sequence 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, ...} are neither 2-regular sequences nor elements of + Integer Ring. + + :: + sage: RP.parameters(1, 0, {(0, 0): 1}, {}, 0) Traceback (most recent call last): ... @@ -2021,13 +2162,14 @@ def parameters(self, M, m, coeffs, initial_values, offset=0): sage: RP.parameters(1, 0, {(0, 0): 1}, ....: {0: 1, 1: 0}, 0) recurrence_rules(M=1, m=0, l=0, u=0, ll=0, uu=0, dim=1, - coeffs={(0, 0): 1}, initial_values={0: 1, 1: 0}, offset=0, n1=0) + coeffs={(0, 0): 1}, initial_values={0: 1, 1: 0}, offset=0, n1=0, + inhomogeneities={}) Finally, also for the zero-sequence the output is as expected:: sage: RP.parameters(1, 0, {}, {0: 0}, 0) recurrence_rules(M=1, m=0, l=0, u=0, ll=0, uu=0, dim=1, - coeffs={}, initial_values={0: 0}, offset=0, n1=0) + coeffs={}, initial_values={0: 0}, offset=0, n1=0, inhomogeneities={}) :: @@ -2035,10 +2177,11 @@ def parameters(self, M, m, coeffs, initial_values, offset=0): ....: {(0, 0): 0, (1, 1): 0}, {0: 0}, 0) recurrence_rules(M=1, m=0, l=0, u=0, ll=0, uu=0, dim=1, coeffs={(0, 0): 0, (1, 1): 0}, initial_values={0: 0}, - offset=0, n1=0) + offset=0, n1=0, inhomogeneities={}) """ from collections import namedtuple + from sage.arith.srange import srange from sage.functions.other import ceil, floor coefficient_ring = self.coefficient_ring @@ -2061,6 +2204,24 @@ def parameters(self, M, m, coeffs, initial_values, offset=0): n1 = offset - floor(ll/k**M) dim = (k**M - 1)/(k - 1) + (M - m)*(uu - ll - k**m + 1) + n1 + if inhomogeneities: + invalid_indices = [i for i in inhomogeneities + if i not in srange(k**M)] + if invalid_indices: + raise ValueError(f"Indices {invalid_indices} for inhomogeneities are no " + f"integers between 0 and {k**M - 1}.") + + Seq = kRegularSequenceSpace(k, coefficient_ring) + inhomogeneities.update({i: inhomogeneities[i] * Seq.one_hadamard() + for i in inhomogeneities + if inhomogeneities[i] in coefficient_ring}) + invalid = {i: inhomogeneities[i] for i in inhomogeneities + if not (isinstance(inhomogeneities[i].parent(), kRegularSequenceSpace) and + inhomogeneities[i].parent().k == k)} + if invalid: + raise ValueError(f"Inhomogeneities {invalid} are neither {k}-regular " + f"sequences nor elements of {coefficient_ring}.") + if not initial_values: raise ValueError("No initial values are given.") keys_initial = initial_values.keys() @@ -2084,18 +2245,19 @@ def converted_value(n, v): initial_values = self.values( M=M, m=m, l=l, u=u, ll=ll, coeffs=coeffs, initial_values=initial_values, last_value_needed=last_value_needed, - offset=offset) + offset=offset, inhomogeneities=inhomogeneities) recurrence_rules = namedtuple('recurrence_rules', ['M', 'm', 'l', 'u', 'll', 'uu', 'dim', - 'coeffs', 'initial_values', 'offset', 'n1']) + 'coeffs', 'initial_values', 'offset', 'n1', + 'inhomogeneities']) return recurrence_rules(M=M, m=m, l=l, u=u, ll=ll, uu=uu, dim=dim, coeffs=coeffs, initial_values=initial_values, - offset=offset, n1=n1) + offset=offset, n1=n1, inhomogeneities=inhomogeneities) def values(self, *, M, m, l, u, ll, coeffs, - initial_values, last_value_needed, offset): + initial_values, last_value_needed, offset, inhomogeneities): r""" Determine enough values of the corresponding recursive sequence by applying the recurrence relations given in :meth:`kRegularSequenceSpace.from_recurrence` @@ -2120,6 +2282,9 @@ def values(self, *, M, m, l, u, ll, coeffs, - ``last_value_needed`` -- last initial value which is needed to determine the linear representation + - ``inhomogeneities`` -- a dictionary mapping integers ``r`` + to the inhomogeneity `g_r` as given in [HKL2021]_, Corollary D. + OUTPUT: A dictionary mapping integers ``n`` to the ``n``-th value of the @@ -2134,7 +2299,7 @@ def values(self, *, M, m, l, u, ll, coeffs, sage: RP.values(M=1, m=0, l=0, u=1, ll=0, ....: coeffs={(0, 0): 1, (1, 0): 1, (1, 1): 1}, ....: initial_values={0: 0, 1: 1, 2: 1}, last_value_needed=20, - ....: offset=0) + ....: offset=0, inhomogeneities={}) {0: 0, 1: 1, 2: 1, 3: 2, 4: 1, 5: 3, 6: 2, 7: 3, 8: 1, 9: 4, 10: 3, 11: 5, 12: 2, 13: 5, 14: 3, 15: 4, 16: 1, 17: 5, 18: 4, 19: 7, 20: 3} @@ -2149,7 +2314,7 @@ def values(self, *, M, m, l, u, ll, coeffs, sage: RP.values(M=1, m=0, l=0, u=1, ll=0, ....: coeffs={(0, 0): 1, (1, 0): 1, (1, 1): 1}, ....: initial_values={0: 0, 1: 2}, last_value_needed=20, - ....: offset=0) + ....: offset=0, inhomogeneities={}) {0: 0, 1: 2, 2: 2, 3: 4, 4: 2, 5: 6, 6: 4, 7: 6, 8: 2, 9: 8, 10: 6, 11: 10, 12: 4, 13: 10, 14: 6, 15: 8, 16: 2, 17: 10, 18: 8, 19: 14, 20: 6} @@ -2158,7 +2323,8 @@ def values(self, *, M, m, l, u, ll, coeffs, sage: RP.values(M=1, m=0, l=0, u=1, ll=0, ....: coeffs={(0, 0): 1, (1, 0): 1, (1, 1): 1}, - ....: initial_values={}, last_value_needed=20, offset=0) + ....: initial_values={}, last_value_needed=20, offset=0, + ....: inhomogeneities={}) Traceback (most recent call last): ... ValueError: Initial values for arguments in [0, 1] are missing. @@ -2167,7 +2333,8 @@ def values(self, *, M, m, l, u, ll, coeffs, sage: RP.values(M=1, m=0, l=0, u=1, ll=0, ....: coeffs={(0, 0): 1, (1, 0): 1, (1, 1): 1}, - ....: initial_values={0: 0}, last_value_needed=20, offset=0) + ....: initial_values={0: 0}, last_value_needed=20, offset=0, + ....: inhomogeneities={}) Traceback (most recent call last): ... ValueError: Initial values for arguments in [1] are missing. @@ -2177,7 +2344,7 @@ def values(self, *, M, m, l, u, ll, coeffs, sage: RP.values(M=1, m=0, l=0, u=1, ll=0, ....: coeffs={(0, 0): 1, (1, 0): 1, (1, 1): 1}, ....: initial_values={0: 0, 2: 1}, last_value_needed=20, - ....: offset=0) + ....: offset=0, inhomogeneities={}) Traceback (most recent call last): ... ValueError: Initial values for arguments in [1] are missing. @@ -2187,7 +2354,7 @@ def values(self, *, M, m, l, u, ll, coeffs, sage: RP.values(M=1, m=0, l=0, u=1, ll=0, ....: coeffs={(0, 0): 1, (1, 0): 1, (1, 1): 1}, ....: initial_values={0: 0, 1: 2, 2:0}, last_value_needed=20, - ....: offset=0) + ....: offset=0, inhomogeneities={}) Traceback (most recent call last): ... ValueError: Initial value for argument 2 does not match with the given @@ -2198,7 +2365,7 @@ def values(self, *, M, m, l, u, ll, coeffs, sage: RP.values(M=1, m=0, l=-2, u=2, ll=-2, ....: coeffs={(0, -2): 1, (0, 2): 1, (1, -2): 1, (1, 2): 1}, ....: initial_values={0: 0, 1: 2, 2: 4, 3: 3, 4: 2}, - ....: last_value_needed=20, offset=2) + ....: last_value_needed=20, offset=2, inhomogeneities={}) {-2: 0, -1: 0, 0: 0, 1: 2, 2: 4, 3: 3, 4: 2, 5: 2, 6: 4, 7: 4, 8: 8, 9: 8, 10: 7, 11: 7, 12: 10, 13: 10, 14: 10, 15: 10, 16: 11, 17: 11, 18: 11, 19: 11, 20: 18} @@ -2207,15 +2374,24 @@ def values(self, *, M, m, l, u, ll, coeffs, sage: RP.values(M=1, m=0, l=0, u=0, ll=0, ....: coeffs={}, initial_values={}, last_value_needed=10, - ....: offset=0) + ....: offset=0, inhomogeneities={}) {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0} :: sage: RP.values(M=1, m=0, l=0, u=0, ll=0, ....: coeffs={(0, 0): 0, (1, 1): 0}, initial_values={}, - ....: last_value_needed=10, offset=0) + ....: last_value_needed=10, offset=0, inhomogeneities={}) {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0} + + :: + + sage: Seq2 = kRegularSequenceSpace(2, ZZ) + sage: RP.values(M=1, m=0, l=0, u=0, ll=0, + ....: coeffs={(0, 0): 0, (1, 1): 0}, initial_values={}, + ....: last_value_needed=10, offset=0, + ....: inhomogeneities={0: Seq2.one_hadamard()}) + {0: 1, 1: 0, 2: 1, 3: 0, 4: 1, 5: 0, 6: 1, 7: 0, 8: 1, 9: 0, 10: 1} """ from sage.arith.srange import srange from sage.rings.integer_ring import ZZ @@ -2234,6 +2410,13 @@ def coeff(r, k): except KeyError: return 0 + @cached_function + def inhomogeneity(r, n): + try: + return inhomogeneities[r][n] + except KeyError: + return 0 + def f(n): f_n = values[n] if f_n is not None and f_n != "pending": @@ -2248,7 +2431,7 @@ def f(n): missing_values.append(n) return sum([coeff(r, j)*f(k**m*q + j) for j in srange(l, u + 1) - if coeff(r, j)]) + if coeff(r, j)]) + inhomogeneity(r, q) for n in srange(last_value_needed + 1): values.update({n: f(n)}) @@ -2260,8 +2443,8 @@ def f(n): for n in keys_initial: q, r = ZZ(n).quo_rem(k**M) if (q >= offset and - values[n] != sum([coeff(r, j)*values[k**m*q + j] - for j in srange(l, u + 1)])): + values[n] != (sum([coeff(r, j)*values[k**m*q + j] + for j in srange(l, u + 1)])) + inhomogeneity(r, q)): raise ValueError("Initial value for argument %s does not match with " "the given recurrence relations." % (n,)) @@ -2329,6 +2512,124 @@ def ind(self, M, m, ll, uu): return ind + @cached_method(key=lambda self, recurrence_rules: + (recurrence_rules.M, + recurrence_rules.m, + recurrence_rules.ll, + recurrence_rules.uu, + tuple(recurrence_rules.inhomogeneities.items()))) + def shifted_inhomogeneities(self, recurrence_rules): + r""" + Return a dictionary of all needed shifted inhomogeneities as described + in the proof of Coroallary D in [HKL2021]_. + + INPUT: + + - ``recurrence_rules`` -- a namedtuple generated by + :meth:`parameters` + + OUTPUT: + + A dictionary mapping `r` to the regular sequence + `\sum_i g_r(n + i)` for `g_r` as given in [HKL2021]_, Corollary D, + and `i` between `\lfloor\ell'/k^{M}\rfloor` and + `\lfloor (k^{M-1} - k^{m} + u')/k^{M}\rfloor + 1`; see [HKL2021]_, + proof of Corollary D. The first blocks of the corresponding + vector-valued sequence (obtained from its linear + representation) correspond to the sequences `g_r(n + i)` where + `i` is as in the sum above; the remaining blocks consist of + other shifts which are required for the regular sequence. + + EXAMPLES:: + + sage: from collections import namedtuple + sage: from sage.combinat.k_regular_sequence import RecurrenceParser + sage: RP = RecurrenceParser(2, ZZ) + 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: RR = namedtuple('recurrence_rules', + ....: ['M', 'm', 'll', 'uu', 'inhomogeneities']) + sage: recurrence_rules = RR(M=3, m=0, ll=-14, uu=14, + ....: inhomogeneities={0: S, 1: S}) + sage: SI = RP.shifted_inhomogeneities(recurrence_rules) + sage: SI + {0: 2-regular sequence 4, 5, 7, 9, 11, 11, 11, 12, 13, 13, ..., + 1: 2-regular sequence 4, 5, 7, 9, 11, 11, 11, 12, 13, 13, ...} + + The first blocks of the corresponding vector-valued sequence correspond + to the corresponding shifts of the inhomogeneity. In this particular + case, there are no other blocks:: + + sage: lower = -2 + sage: upper = 3 + sage: SI[0].dimension() == S.dimension() * (upper - lower + 1) + True + sage: all( + ....: Seq2( + ....: SI[0].mu, + ....: vector((i - lower)*[0, 0] + list(S.left) + (upper - i)*[0, 0]), + ....: SI[0].right) + ....: == S.subsequence(1, i) + ....: for i in range(lower, upper+1)) + True + + TESTS:: + + sage: Seq2 = kRegularSequenceSpace(2, ZZ) + sage: var('n') + n + sage: function('f') + f + sage: UB = Seq2.from_recurrence([ + ....: f(8*n) == 2*f(4*n), + ....: f(8*n + 1) == f(4*n + 1), + ....: f(8*n + 2) == f(4*n + 1) + f(4*n + 3), + ....: f(8*n + 3) == -f(4*n + 1) + f(4*n + 2), + ....: f(8*n + 4) == 2*f(4*n + 2), + ....: f(8*n + 5) == f(4*n + 3), + ....: f(8*n + 6) == -f(4*n + 1) + f(4*n + 2) + f(4*n + 3), + ....: f(8*n + 7) == 2*f(4*n + 1) + f(4*n + 3), + ....: f(0) == 1, f(1) == 2, f(2) == 2, f(3) == 4, f(4) == 2, + ....: f(5) == 4, f(6) == 6, f(7) == 0, f(8) == 4, f(9) == 4, + ....: f(10) == 4, f(11) == 4, f(12) == 12, f(13) == 0, f(14) == 4, + ....: f(15) == 4, f(16) == 8, f(17) == 4, f(18) == 8, f(19) == 0, + ....: f(20) == 8, f(21) == 4, f(22) == 4, f(23) == 8], f, n, offset=3) + sage: inhomogeneities={2: UB.subsequence(4, 3), 3: -UB.subsequence(4, 1), + ....: 6: UB.subsequence(4, 2) + UB.subsequence(4, 3)} + sage: recurrence_rules_UB = RR(M=3, m=2, ll=0, uu=9, + ....: inhomogeneities=inhomogeneities) + sage: shifted_inhomog = RP.shifted_inhomogeneities(recurrence_rules_UB) + sage: shifted_inhomog + {2: 2-regular sequence 8, 8, 8, 12, 12, 16, 12, 16, 12, 24, ..., + 3: 2-regular sequence -10, -8, -8, -8, -8, -8, -8, -8, -8, -12, ..., + 6: 2-regular sequence 20, 22, 24, 28, 28, 32, 28, 32, 32, 48, ...} + sage: shifted_inhomog[2].mu[0].ncols() == 3*inhomogeneities[2].mu[0].ncols() + True + + .. SEEALSO:: + + :meth:`kRegularSequenceSpace.from_recurrence` + """ + from sage.arith.srange import srange + from sage.functions.other import floor + + k = self.k + M = recurrence_rules.M + m = recurrence_rules.m + ll = recurrence_rules.ll + uu = recurrence_rules.uu + inhomogeneities = recurrence_rules.inhomogeneities + + lower = floor(ll/k**M) + upper = floor((k**(M-1) - k**m + uu)/k**M) + 1 + + return {i: inhomogeneities[i].subsequence(1, {b: 1 for b in srange(lower, upper + 1)}, + minimize=False) + for i in inhomogeneities} + def v_eval_n(self, recurrence_rules, n): r""" Return the vector `v(n)` as given in [HKL2021]_, Theorem A. @@ -2358,8 +2659,11 @@ def v_eval_n(self, recurrence_rules, n): :meth:`kRegularSequenceSpace.from_recurrence` """ + from itertools import chain + from sage.arith.srange import srange from sage.modules.free_module_element import vector + from sage.rings.integer_ring import ZZ k = self.k M = recurrence_rules.M @@ -2368,10 +2672,20 @@ def v_eval_n(self, recurrence_rules, n): uu = recurrence_rules.uu dim = recurrence_rules.dim - recurrence_rules.n1 initial_values = recurrence_rules.initial_values + inhomogeneities = recurrence_rules.inhomogeneities ind = self.ind(M, m, ll, uu) - return vector( - [initial_values[k**ind[i][0]*n + ind[i][1]] for i in srange(dim)]) + v = vector([initial_values[k**ind[i][0]*n + ind[i][1]] for i in srange(dim)]) + + if not all(S.is_trivial_zero() for S in inhomogeneities.values()): + Seq = list(inhomogeneities.values())[0].parent() + W = Seq.indices() + shifted_inhomogeneities = self.shifted_inhomogeneities(recurrence_rules) + vv = [(S.coefficient_of_word(W(ZZ(n).digits(k)), multiply_left=False)) + for S in shifted_inhomogeneities.values()] + v = vector(chain(v, *vv)) + + return v def matrix(self, recurrence_rules, rem, correct_offset=True): r""" @@ -2524,9 +2838,12 @@ def matrix(self, recurrence_rules, rem, correct_offset=True): :meth:`kRegularSequenceSpace.from_recurrence` """ + from itertools import chain + from sage.arith.srange import srange + from sage.functions.other import floor from sage.matrix.constructor import Matrix - from sage.matrix.special import block_matrix, zero_matrix + from sage.matrix.special import block_matrix, block_diagonal_matrix, zero_matrix from sage.modules.free_module_element import vector coefficient_ring = self.coefficient_ring @@ -2540,6 +2857,7 @@ def matrix(self, recurrence_rules, rem, correct_offset=True): n1 = recurrence_rules.n1 dim_without_corr = dim - n1 coeffs = recurrence_rules.coeffs + inhomogeneities = recurrence_rules.inhomogeneities ind = self.ind(M, m, ll, uu) @cached_function @@ -2565,9 +2883,46 @@ def entry(i, kk): mat = Matrix(coefficient_ring, dim_without_corr, dim_without_corr, entry) - if n1 == 0 or not correct_offset: - return mat - else: + if not all(S.is_trivial_zero() for S in inhomogeneities.values()): + shifted_inhomogeneities = self.shifted_inhomogeneities(recurrence_rules) + lower = floor(ll/k**M) + upper = floor((k**(M-1) - k**m + uu)/k**M) + 1 + + def wanted_inhomogeneity(row): + j, d = ind[row] + if j != M - 1: + return (None, None) + rem_d = k**(M-1)*rem + (d%k**M) + dd = d // k**M + if rem_d < k**M: + return (rem_d, dd) + elif rem_d >= k**M: + return (rem_d - k**M, dd + 1) + else: + return (None, None) + + def left_for_inhomogeneity(wanted): + return list(chain(*[(wanted == (r, i))*inhomogeneity.left + for r, inhomogeneity in inhomogeneities.items() + for i in srange(lower, upper + 1)])) + + def matrix_row(row): + wanted = wanted_inhomogeneity(row) + return left_for_inhomogeneity(wanted) + + mat_upper_right = Matrix([matrix_row(row) for row in srange(dim_without_corr)]) + mat_inhomog = block_diagonal_matrix([S.mu[rem] + for S in shifted_inhomogeneities.values()], + subdivide=False) + + mat = block_matrix([[mat, mat_upper_right], + [zero_matrix(mat_inhomog.nrows(), dim_without_corr), + mat_inhomog]], subdivide=False) + + dim_without_corr = mat.ncols() + dim = dim_without_corr + n1 + + if n1 > 0 and correct_offset: W = Matrix(coefficient_ring, dim_without_corr, 0) for i in srange(n1): W = W.augment( @@ -2579,7 +2934,9 @@ def entry(i, kk): J = J.stack(vector([int(j*k == i - rem) for j in srange(n1)])) Z = zero_matrix(coefficient_ring, n1, dim_without_corr) - return block_matrix([[mat, W], [Z, J]], subdivide=False) + mat = block_matrix([[mat, W], [Z, J]], subdivide=False) + + return mat def left(self, recurrence_rules): r""" @@ -2599,17 +2956,35 @@ def left(self, recurrence_rules): sage: from collections import namedtuple sage: from sage.combinat.k_regular_sequence import RecurrenceParser sage: RP = RecurrenceParser(2, ZZ) - sage: RRD = namedtuple('recurrence_rules_dim', ['dim']) - sage: recurrence_rules = RRD(dim=5) + sage: RRD = namedtuple('recurrence_rules_dim', + ....: ['dim', 'inhomogeneities']) + sage: recurrence_rules = RRD(dim=5, inhomogeneities={}) sage: RP.left(recurrence_rules) (1, 0, 0, 0, 0) + :: + + sage: Seq2 = kRegularSequenceSpace(2, ZZ) + sage: RRD = namedtuple('recurrence_rules_dim', + ....: ['M', 'm', 'll', 'uu', 'dim', 'inhomogeneities']) + sage: recurrence_rules = RRD(M=3, m=2, ll=0, uu=9, dim=5, + ....: inhomogeneities={0: Seq2.one_hadamard()}) + sage: RP.left(recurrence_rules) + (1, 0, 0, 0, 0, 0, 0, 0) + .. SEEALSO:: :meth:`kRegularSequenceSpace.from_recurrence` """ from sage.modules.free_module_element import vector + dim = recurrence_rules.dim + inhomogeneities = recurrence_rules.inhomogeneities + + if not all(S.is_trivial_zero() for S in inhomogeneities.values()): + shifted_inhomogeneities = self.shifted_inhomogeneities(recurrence_rules) + dim += sum(shifted_inhomogeneities[i].mu[0].ncols() + for i in shifted_inhomogeneities) return vector([1] + (dim - 1)*[0]) diff --git a/src/sage/combinat/ncsf_qsym/generic_basis_code.py b/src/sage/combinat/ncsf_qsym/generic_basis_code.py index 98ebd37a0de..87364ac04c6 100644 --- a/src/sage/combinat/ncsf_qsym/generic_basis_code.py +++ b/src/sage/combinat/ncsf_qsym/generic_basis_code.py @@ -472,7 +472,7 @@ def skew(self, x, y, side='left'): y = self.dual()(y) v = 1 if side == 'left' else 0 return self.sum(coeff * y[IJ[1-v]] * self[IJ[v]] \ - for (IJ, coeff) in x.coproduct() if IJ[1-v] in y) + for (IJ, coeff) in x.coproduct() if IJ[1-v] in y.support()) else: return self._skew_by_coercion(x, y, side=side) diff --git a/src/sage/combinat/ncsf_qsym/tutorial.py b/src/sage/combinat/ncsf_qsym/tutorial.py index 5efc8d43728..78978507cb3 100644 --- a/src/sage/combinat/ncsf_qsym/tutorial.py +++ b/src/sage/combinat/ncsf_qsym/tutorial.py @@ -111,7 +111,7 @@ sage: sorted(z.coefficients()) [1, 2, 3] - sage: sorted(z.monomials(), key=lambda x: x.support()) + sage: sorted(z.monomials(), key=lambda x: tuple(x.support())) [M[1, 2], M[3, 3], M[6]] sage: z.monomial_coefficients() diff --git a/src/sage/combinat/partition.py b/src/sage/combinat/partition.py index 811679ede16..49c93865c11 100644 --- a/src/sage/combinat/partition.py +++ b/src/sage/combinat/partition.py @@ -318,8 +318,8 @@ from . import permutation from . import composition from sage.combinat.partitions import ZS1_iterator, ZS1_iterator_nk -from sage.combinat.integer_vector import IntegerVectors from sage.combinat.integer_lists import IntegerListsLex +from sage.combinat.integer_lists.invlex import IntegerListsBackend_invlex from sage.combinat.integer_vector_weighted import iterator_fast as weighted_iterator_fast from sage.combinat.combinat_cython import conjugate from sage.combinat.root_system.weyl_group import WeylGroup @@ -4027,15 +4027,12 @@ def outside_corners(self): sage: Partition([]).outside_corners() [(0, 0)] """ - p = self - if p.is_empty(): + p = self._list + if not p: return [(0,0)] - res = [ (0, p[0]) ] - for i in range(1, len(p)): - if p[i-1] != p[i]: - res.append((i,p[i])) + res = [(0, p[0])] + res.extend((n, j) for n, (i, j) in enumerate(zip(p[:-1], p[1:]), start=1) if i != j) res.append((len(p), 0)) - return res addable_cells = outside_corners # for compatibility with partition tuples @@ -4655,6 +4652,8 @@ def add_vertical_border_strip(self, k): sage: Partition([]).add_vertical_border_strip(0) [[]] + sage: Partition([3,2,1]).add_vertical_border_strip(0) + [[3, 2, 1]] sage: Partition([]).add_vertical_border_strip(2) [[1, 1]] sage: Partition([2,2]).add_vertical_border_strip(2) @@ -4662,7 +4661,43 @@ def add_vertical_border_strip(self, k): sage: Partition([3,2,2]).add_vertical_border_strip(2) [[4, 3, 2], [4, 2, 2, 1], [3, 3, 3], [3, 3, 2, 1], [3, 2, 2, 1, 1]] """ - return [p.conjugate() for p in self.conjugate().add_horizontal_border_strip(k)] + if k == 0: + return [self] + + shelf = [] + res = [] + i = 0 + ell = len(self._list) + while i < ell: + tmp = 1 + while i+1 < ell and self._list[i] == self._list[i+1]: + tmp += 1 + i += 1 + if i == ell-1 and i > 0 and self._list[i] != self._list[i-1]: + tmp = 1 + shelf.append(tmp) + i += 1 + + # added the last shelf on the right side of + # the first line + shelf.append(k) + + # list all of the positions for cells + # filling each self from the left to the right + for iv in IntegerListsBackend_invlex(k, length=len(shelf), ceiling=shelf, check=False)._iter(): + tmp = self._list + [0]*k + j = 0 + for t in range(len(iv)): + for _ in range(iv[t]): + tmp[j] += 1 + j += 1 + j = sum(shelf[:t+1]) + # This should never return the empty partition. + # So tmp should never be [0, ..., 0]. + while not tmp[-1]: + tmp.pop() + res.append(_Partitions(tmp)) + return res def add_horizontal_border_strip(self, k): """ @@ -4673,50 +4708,157 @@ def add_horizontal_border_strip(self, k): sage: Partition([]).add_horizontal_border_strip(0) [[]] + sage: Partition([3,2,1]).add_horizontal_border_strip(0) + [[3, 2, 1]] sage: Partition([]).add_horizontal_border_strip(2) [[2]] sage: Partition([2,2]).add_horizontal_border_strip(2) - [[2, 2, 2], [3, 2, 1], [4, 2]] + [[4, 2], [3, 2, 1], [2, 2, 2]] sage: Partition([3,2,2]).add_horizontal_border_strip(2) - [[3, 2, 2, 2], [3, 3, 2, 1], [4, 2, 2, 1], [4, 3, 2], [5, 2, 2]] + [[5, 2, 2], [4, 3, 2], [4, 2, 2, 1], [3, 3, 2, 1], [3, 2, 2, 2]] + """ + if k == 0: + return [self] - .. TODO:: + L = self._list + res = [] + mapping = [0] + shelf = [k] + for i in range(len(L)-1): + val = L[i] - L[i+1] + if not val: + continue + mapping.append(i+1) + shelf.append(val) + + # add the last shelf + if L: + mapping.append(len(L)) + shelf.append(L[-1]) + + # list all of the positions for cells + # filling each self from the top to bottom + for iv in IntegerListsBackend_invlex(k, length=len(shelf), ceiling=shelf, check=False)._iter(): + tmp = self._list + [0] + for i, val in enumerate(iv): + if val: + tmp[mapping[i]] += val + # Only the last row is possibly empty + if not tmp[-1]: + tmp.pop() + res.append(_Partitions(tmp)) + return res - Reimplement like ``remove_horizontal_border_strip`` using - :class:`IntegerListsLex` + def vertical_border_strip_cells(self, k): """ - conj = self.conjugate().to_list() + Return a list of all the vertical border strips of length ``k`` + which can be added to ``self``, where each horizontal border strip is + a ``generator`` of cells. + + EXAMPLES:: + + sage: list(Partition([]).vertical_border_strip_cells(0)) + [] + sage: list(Partition([3,2,1]).vertical_border_strip_cells(0)) + [] + sage: list(Partition([]).vertical_border_strip_cells(2)) + [[(0, 0), (1, 0)]] + sage: list(Partition([2,2]).vertical_border_strip_cells(2)) + [[(0, 2), (1, 2)], + [(0, 2), (2, 0)], + [(2, 0), (3, 0)]] + sage: list(Partition([3,2,2]).vertical_border_strip_cells(2)) + [[(0, 3), (1, 2)], + [(0, 3), (3, 0)], + [(1, 2), (2, 2)], + [(1, 2), (3, 0)], + [(3, 0), (4, 0)]] + """ + if k == 0: + return [] + shelf = [] res = [] i = 0 - while i < len(conj): + ell = len(self._list) + while i < ell: tmp = 1 - while i+1 < len(conj) and conj[i] == conj[i+1]: + while i+1 < ell and self._list[i] == self._list[i+1]: tmp += 1 i += 1 - if i == len(conj)-1 and i > 0 and conj[i] != conj[i-1]: + if i == ell-1 and i > 0 and self._list[i] != self._list[i-1]: tmp = 1 shelf.append(tmp) i += 1 - #added the last shelf on the right side of - #the first line + # added the last shelf on the right side of + # the first line shelf.append(k) - - #list all of the positions for cells - #filling each self from the left to the right - for iv in IntegerVectors(k, len(shelf), outer=shelf): - iv = list(iv) # Make a mutable list - tmp = conj + [0]*k + # list all of the positions for cells + tmp = self._list + [0]*k + for iv in IntegerListsBackend_invlex(k, length=len(shelf), + ceiling=shelf, + check=False)._iter(): j = 0 + current_strip = [] for t in range(len(iv)): - while iv[t] > 0: - tmp[j] += 1 - iv[t] -= 1 + for _ in range(iv[t]): + current_strip.append((j, tmp[j])) j += 1 - j = sum(shelf[:t+1]) - res.append(Partition([u for u in tmp if u != 0]).conjugate()) - return res + j = sum(shelf[:t+1]) + yield current_strip + + def horizontal_border_strip_cells(self, k): + """ + Return a list of all the horizontal border strips of length ``k`` + which can be added to ``self``, where each horizontal border strip is + a ``generator`` of cells. + + EXAMPLES:: + + sage: list(Partition([]).horizontal_border_strip_cells(0)) + [] + sage: list(Partition([3,2,1]).horizontal_border_strip_cells(0)) + [] + sage: list(Partition([]).horizontal_border_strip_cells(2)) + [[(0, 0), (0, 1)]] + sage: list(Partition([2,2]).horizontal_border_strip_cells(2)) + [[(0, 2), (0, 3)], [(0, 2), (2, 0)], [(2, 0), (2, 1)]] + sage: list(Partition([3,2,2]).horizontal_border_strip_cells(2)) + [[(0, 3), (0, 4)], + [(0, 3), (1, 2)], + [(0, 3), (3, 0)], + [(1, 2), (3, 0)], + [(3, 0), (3, 1)]] + """ + if k == 0: + return list() + + L = self._list + res = [] + shelf = [k] # the number of boxes which will fit in a row + mapping = [0] # a record of the rows + for i in range(len(L)-1): + val = L[i] - L[i+1] + if not val: + continue + mapping.append(i+1) + shelf.append(val) + + # add the last shelf + if L: + mapping.append(len(L)) + shelf.append(L[-1]) + + L.append(0) # add room on the bottom + # list all of the positions for cells + # filling each self from the top to bottom + for iv in IntegerListsBackend_invlex(k, length=len(shelf), ceiling=shelf, check=False)._iter(): + tmp = [] + # mapping[i] is the row index, val is the number of cells added to the row. + for i, val in enumerate(iv): + tmp.extend((mapping[i], L[mapping[i]] + j) for j in range(val)) + yield tmp def remove_horizontal_border_strip(self, k): """ @@ -4839,7 +4981,7 @@ def atom(self): return res def k_atom(self, k): - """ + r""" Return a list of the standard tableaux of size ``self.size()`` whose ``k``-atom is equal to ``self``. @@ -4849,12 +4991,12 @@ def k_atom(self, k): sage: p.k_atom(1) [] sage: p.k_atom(3) - [[[1, 1, 1], [2, 2], [3]], - [[1, 1, 1, 2], [2], [3]], + [[[1, 1, 1, 2, 3], [2]], [[1, 1, 1, 3], [2, 2]], - [[1, 1, 1, 2, 3], [2]]] + [[1, 1, 1, 2], [2], [3]], + [[1, 1, 1], [2, 2], [3]]] sage: Partition([3,2,1]).k_atom(4) - [[[1, 1, 1], [2, 2], [3]], [[1, 1, 1, 3], [2, 2]]] + [[[1, 1, 1, 3], [2, 2]], [[1, 1, 1], [2, 2], [3]]] TESTS:: diff --git a/src/sage/combinat/perfect_matching.py b/src/sage/combinat/perfect_matching.py index 7d8ddf1bcca..383f6522903 100644 --- a/src/sage/combinat/perfect_matching.py +++ b/src/sage/combinat/perfect_matching.py @@ -36,7 +36,7 @@ REFERENCES: .. [MV] combinatorics of orthogonal polynomials (A. de Medicis et - X.Viennot, Moments des q-polynomes de Laguerre et la bijection de + X.Viennot, Moments des q-polynômes de Laguerre et la bijection de Foata-Zeilberger, Adv. Appl. Math., 15 (1994), 262-304) .. [McD] combinatorics of hyperoctahedral group, double coset algebra and @@ -44,8 +44,8 @@ polynomials, Oxford University Press, second edition, 1995, chapter VII). -.. [CM] Benoit Collins, Sho Matsumoto, On some properties of - orthogonal Weingarten functions, :arxiv:`0903.5143`. +.. [CM] Benoit Collins, Sho Matsumoto, *On some properties of + orthogonal Weingarten functions*, :arxiv:`0903.5143`. """ # **************************************************************************** # Copyright (C) 2010 Valentin Feray diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index a2e7df2b130..f6d802e2beb 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -221,12 +221,18 @@ finite Weyl group to make it more uniform with :class:`SymmetricGroup`. Added ability to compute the conjugacy classes. +- Amrutha P, Shriya M, Divya Aggarwal (2022-08-16): Added Multimajor Index. + Classes and methods =================== """ # **************************************************************************** # Copyright (C) 2007 Mike Hansen +# 2022 Amrutha P +# 2022 Shriya M <25shriya@gmail.com> +# 2022 Divya Aggarwal +# # # 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 @@ -3365,6 +3371,52 @@ def major_index(self, final_descent=False) -> Integer: descents = self.descents(final_descent) return sum(descents) + def multi_major_index(self, composition): + r""" + Return the multimajor index of this permutation with respect to ``composition``. + + INPUT: + + - ``composition`` -- a composition of the :meth:`size` of this permutation + + EXAMPLES:: + + sage: p = Permutation([5, 6, 2, 1, 3, 7, 4]) + sage: p.multi_major_index([3, 2, 2]) + [2, 0, 1] + sage: p.multi_major_index([7]) == [p.major_index()] + True + sage: p.multi_major_index([1]*7) + [0, 0, 0, 0, 0, 0, 0] + sage: Permutation([]).multi_major_index([]) + [] + + TESTS:: + + sage: p.multi_major_index([1, 3, 3, 7]) + Traceback (most recent call last): + ... + ValueError: size of the composition should be equal to size of the permutation + + REFERENCES: + + - [JS2000]_ + """ + composition = Composition(composition) + if self.size() != composition.size(): + raise ValueError("size of the composition should be equal to size of the permutation") + descents = self.descents() + partial_sum = [0] + composition.partial_sums() + multimajor_index = [] + for j in range(1, len(partial_sum)): + a = partial_sum[j-1] + b = partial_sum[j] + from bisect import bisect_right, bisect_left + start = bisect_right(descents, a) + end = bisect_left(descents, b) + multimajor_index.append(sum(descents[start: end])-(end-start)*a) + return multimajor_index + def imajor_index(self, final_descent=False) -> Integer: """ Return the inverse major index of the permutation ``self``, which is diff --git a/src/sage/combinat/posets/poset_examples.py b/src/sage/combinat/posets/poset_examples.py index 38d5fc64c25..e415dc300f2 100644 --- a/src/sage/combinat/posets/poset_examples.py +++ b/src/sage/combinat/posets/poset_examples.py @@ -65,10 +65,20 @@ :meth:`~Posets.YoungsLatticePrincipalOrderIdeal` | Return the principal order ideal of the partition `lam` in Young's Lattice. :meth:`~Posets.YoungFibonacci` | Return the Young-Fibonacci lattice up to rank `n`. +**Other available posets:** + +.. csv-table:: + :class: contentstable + :widths: 30, 70 + :delim: | + + :meth:`~sage.geometry.polyhedron.base4.Polyhedron_base4.face_lattice` | Return the face lattice of a polyhedron. + :meth:`~sage.geometry.polyhedron.combinatorial_polyhedron.base.CombinatorialPolyhedron.face_lattice` | Return the face lattice of a combinatorial polyhedron. + Constructions ------------- """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2008 Peter Jipsen , # Franco Saliola # @@ -81,8 +91,8 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from sage.misc.classcall_metaclass import ClasscallMetaclass import sage.categories.posets @@ -130,7 +140,7 @@ class Posets(metaclass=ClasscallMetaclass): sage: TestSuite(P).run() """ @staticmethod - def __classcall__(cls, n = None): + def __classcall__(cls, n=None): r""" Return either the category of all posets, or the finite enumerated set of all finite posets on ``n`` elements up to an diff --git a/src/sage/combinat/recognizable_series.py b/src/sage/combinat/recognizable_series.py index 808f5436e6e..6652872cb6c 100644 --- a/src/sage/combinat/recognizable_series.py +++ b/src/sage/combinat/recognizable_series.py @@ -1068,6 +1068,12 @@ def minimized(self): This method implements the minimization algorithm presented in Chapter 2 of [BR2010a]_. + .. NOTE:: + + Due to the algorithm, the left vector of the result + is always `(1, 0, \ldots, 0)`, i.e., the first vector of the + standard basis. + EXAMPLES:: sage: from itertools import islice @@ -1084,6 +1090,8 @@ def minimized(self): [3 0] [ 0 1] [6 1], [-6 5], (1, 0), (0, 1) ) + sage: M.left == vector([1, 0]) + True sage: all(c == d and v == w ....: for (c, v), (d, w) in islice(zip(iter(S), iter(M)), 20)) True diff --git a/src/sage/combinat/root_system/associahedron.py b/src/sage/combinat/root_system/associahedron.py index 66b134a35e7..c6765e71eb9 100644 --- a/src/sage/combinat/root_system/associahedron.py +++ b/src/sage/combinat/root_system/associahedron.py @@ -288,7 +288,7 @@ def Associahedra(base_ring, ambient_dim, backend='ppl'): sage: Associahedra(QQ, 4, 'normaliz').parent() # optional - pynormaliz - sage: Associahedra(QQ, 4, 'polymake').parent() # optional - polymake + sage: Associahedra(QQ, 4, 'polymake').parent() # optional - jupymake sage: Associahedra(QQ, 4, 'field').parent() diff --git a/src/sage/combinat/root_system/fundamental_group.py b/src/sage/combinat/root_system/fundamental_group.py index 80d5915eaa1..87b6e1094e0 100644 --- a/src/sage/combinat/root_system/fundamental_group.py +++ b/src/sage/combinat/root_system/fundamental_group.py @@ -249,7 +249,7 @@ def _repr_(self): """ return self.parent()._prefix + "[" + repr(self.value()) + "]" - def inverse(self): + def __invert__(self): r""" Return the inverse element of ``self``. @@ -257,7 +257,7 @@ def inverse(self): sage: from sage.combinat.root_system.fundamental_group import FundamentalGroupOfExtendedAffineWeylGroup sage: F = FundamentalGroupOfExtendedAffineWeylGroup(['A',3,1]) - sage: F(1).inverse() + sage: F(1).inverse() # indirect doctest pi[3] sage: F = FundamentalGroupOfExtendedAffineWeylGroup(['E',6,1], prefix="f") sage: F(1).inverse() @@ -266,8 +266,6 @@ def inverse(self): par = self.parent() return self.__class__(par, par.dual_node(self.value())) - __invert__ = inverse - def _richcmp_(self, x, op): r""" Compare ``self`` with `x`. diff --git a/src/sage/combinat/root_system/reflection_group_real.py b/src/sage/combinat/root_system/reflection_group_real.py index 310e9f5305b..9e5c2e2c1b0 100644 --- a/src/sage/combinat/root_system/reflection_group_real.py +++ b/src/sage/combinat/root_system/reflection_group_real.py @@ -706,6 +706,88 @@ def simple_root_index(self, i): [0, 1, 2] """ return self._index_set_inverse[i] + + def bruhat_cone(self, x, y, side='upper', backend='cdd'): + r""" + Return the (upper or lower) Bruhat cone associated to the interval ``[x,y]``. + + To a cover relation `v \prec w` in strong Bruhat order you can assign a positive + root `\beta` given by the unique reflection `s_\beta` such that `s_\beta v = w`. + + The upper Bruhat cone of the interval `[x,y]` is the non-empty, polyhedral cone generated + by the roots corresponding to `x \prec a` for all atoms `a` in the interval. + The lower Bruhat cone of the interval `[x,y]` is the non-empty, polyhedral cone generated + by the roots corresponding to `c \prec y` for all coatoms `c` in the interval. + + INPUT: + + - ``x`` - an element in the group `W` + - ``y`` - an element in the group `W` + - ``side`` (default: ``'upper'``) -- must be one of the following: + + * ``'upper'`` - return the upper Bruhat cone of the interval [``x``, ``y``] + * ``'lower'`` - return the lower Bruhat cone of the interval [``x``, ``y``] + + - ``backend`` -- string (default: ``'cdd'``); the backend to use to create the polyhedron + + EXAMPLES:: + + sage: W = ReflectionGroup(['A',2]) # optional - gap3 + sage: x = W.from_reduced_word([1]) # optional - gap3 + sage: y = W.w0 # optional - gap3 + sage: W.bruhat_cone(x, y) # optional - gap3 + A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 2 rays + + sage: W = ReflectionGroup(['E',6]) # optional - gap3 + sage: x = W.one() # optional - gap3 + sage: y = W.w0 # optional - gap3 + sage: W.bruhat_cone(x, y, side='lower') # optional - gap3 + A 6-dimensional polyhedron in QQ^6 defined as the convex hull of 1 vertex and 6 rays + + TESTS:: + + sage: W = ReflectionGroup(['A',2]) # optional - gap3 + sage: x = W.one() # optional - gap3 + sage: y = W.w0 # optional - gap3 + sage: W.bruhat_cone(x, y, side='nonsense') # optional - gap3 + Traceback (most recent call last): + ... + ValueError: side must be either 'upper' or 'lower' + + REFERENCES: + + - [Dy1994]_ + - [JS2021]_ + """ + if side == 'upper': + roots = [self.reflection_to_positive_root(x * r * x.inverse()) + for z, r in x.bruhat_upper_covers_reflections() + if z.bruhat_le(y)] + elif side == 'lower': + roots = [self.reflection_to_positive_root(y * r * y.inverse()) + for z, r in y.bruhat_lower_covers_reflections() + if x.bruhat_le(z)] + else: + raise ValueError("side must be either 'upper' or 'lower'") + + from sage.geometry.polyhedron.constructor import Polyhedron + if self.is_crystallographic(): + return Polyhedron(vertices=[[0] * self.rank()], + rays=roots, + ambient_dim=self.rank(), + backend=backend) + if backend == 'cdd': + from warnings import warn + warn("Using floating point numbers for roots of unity. This might cause numerical errors!") + from sage.rings.real_double import RDF as base_ring + else: + from sage.rings.qqbar import AA as base_ring + + return Polyhedron(vertices=[[0] * self.rank()], + rays=roots, + ambient_dim=self.rank(), + base_ring=base_ring, + backend=backend) class Element(RealReflectionGroupElement, ComplexReflectionGroup.Element): diff --git a/src/sage/combinat/schubert_polynomial.py b/src/sage/combinat/schubert_polynomial.py index b939754716c..794dc7ee2c6 100644 --- a/src/sage/combinat/schubert_polynomial.py +++ b/src/sage/combinat/schubert_polynomial.py @@ -1,5 +1,63 @@ r""" Schubert Polynomials + + +See :wikipedia:`Schubert_polynomial` and +`SymmetricFunctions.com `_. +Schubert polynomials are representatives of cohomology classes in flag varieties. +In `n` variables, they are indexed by permutations `w \in S_n`. They also form +a basis for the coinvariant ring of the `S_n` action on +`\ZZ[x_1, x_2, \ldots, x_n]`. + +EXAMPLES:: + + sage: X = SchubertPolynomialRing(ZZ) + sage: w = [1,2,5,4,3]; # a list representing an element of `S_5` + sage: X(w) + X[1, 2, 5, 4, 3] + +This can be expanded in terms of polynomial variables:: + + sage: X(w).expand() + x0^2*x1 + x0*x1^2 + x0^2*x2 + 2*x0*x1*x2 + x1^2*x2 + + x0*x2^2 + x1*x2^2 + x0^2*x3 + x0*x1*x3 + x1^2*x3 + + x0*x2*x3 + x1*x2*x3 + x2^2*x3 + +We can also convert back from polynomial variables. For example, +the longest permutation is a single term. In `S_5`, this is the +element (in one line notation) `w_0 = 54321`:: + + sage: w0 = [5,4,3,2,1] + sage: R. = PolynomialRing(ZZ) + sage: Sw0 = X(x0^4*x1^3*x2^2*x3); Sw0 + X[5, 4, 3, 2, 1] + +The polynomials also have the property that if the indexing permutation `w` is +multiplied by a simple transposition `s_i = (i, i+1)` such that the length of +`w` is more than the length of `ws_i`, then the Schubert +polynomial of the permutation `ws_i` is computed by applying the divided +difference operator :meth:`~SchubertPolynomial_class.divided_difference` to +the polynomial indexed by `w`. For example, applying the divided difference +operator `\partial_2` to the Schubert polynomial `\mathfrak{S}_{w_0}`:: + + sage: Sw0.divided_difference(2) + X[5, 3, 4, 2, 1] + +We can also check the properties listed in :wikipedia:`Schubert_polynomial`:: + + sage: X([1,2,3,4,5]) # the identity in one-line notation + X[1] + sage: X([1,3,2,4,5]).expand() # the transposition swapping 2 and 3 + x0 + x1 + sage: X([2,4,5,3,1]).expand() + x0^2*x1^2*x2*x3 + x0^2*x1*x2^2*x3 + x0*x1^2*x2^2*x3 + + sage: w = [4,5,1,2,3] + sage: s = SymmetricFunctions(QQ).schur() + sage: s[3,3].expand(2) + x0^3*x1^3 + sage: X(w).expand() + x0^3*x1^3 """ # **************************************************************************** # Copyright (C) 2007 Mike Hansen , diff --git a/src/sage/combinat/sf/character.py b/src/sage/combinat/sf/character.py index e55f356f5d1..e5752c307eb 100644 --- a/src/sage/combinat/sf/character.py +++ b/src/sage/combinat/sf/character.py @@ -104,7 +104,7 @@ def _other_to_self(self, sexpr): """ if sexpr == 0: return self(0) - if sexpr.support() == [[]]: + if list(sexpr.support()) == [[]]: return self._from_dict({self.one_basis(): sexpr.coefficient([])}, remove_zeros=False) out = self.zero() diff --git a/src/sage/combinat/sf/dual.py b/src/sage/combinat/sf/dual.py index 4ec49461f59..445521e4193 100644 --- a/src/sage/combinat/sf/dual.py +++ b/src/sage/combinat/sf/dual.py @@ -148,15 +148,14 @@ def __init__(self, dual_basis, scalar, scalar_name="", basis_name=None, prefix=N prefix = 'd_'+dual_basis.prefix() classical.SymmetricFunctionAlgebra_classical.__init__(self, self._sym, - basis_name = basis_name, - prefix = prefix) + basis_name=basis_name, + prefix=prefix) # temporary until Hom(GradedHopfAlgebrasWithBasis work better) category = sage.categories.all.ModulesWithBasis(self.base_ring()) - self .register_coercion(SetMorphism(Hom(self._dual_basis, self, category), self._dual_to_self)) + self.register_coercion(SetMorphism(Hom(self._dual_basis, self, category), self._dual_to_self)) self._dual_basis.register_coercion(SetMorphism(Hom(self, self._dual_basis, category), self._self_to_dual)) - def _dual_to_self(self, x): """ Coerce an element of the dual of ``self`` canonically into ``self``. @@ -193,7 +192,7 @@ def _dual_to_self(self, x): sage: h(m([2,1]) + 3*m[1,1,1]) d_m[1, 1, 1] - d_m[2, 1] """ - return self._element_class(self, dual = x) + return self._element_class(self, dual=x) def _self_to_dual(self, x): """ diff --git a/src/sage/combinat/sf/elementary.py b/src/sage/combinat/sf/elementary.py index 5081f4760fe..446acbf23a3 100644 --- a/src/sage/combinat/sf/elementary.py +++ b/src/sage/combinat/sf/elementary.py @@ -67,7 +67,7 @@ def _dual_basis_default(self): sage: e._dual_basis_default() is e.dual_basis() True """ - return self.dual_basis(scalar = None, prefix="f", basis_name = "forgotten") + return self.dual_basis(scalar=None, prefix="f", basis_name="forgotten") def coproduct_on_generators(self, i): r""" diff --git a/src/sage/combinat/sf/hall_littlewood.py b/src/sage/combinat/sf/hall_littlewood.py index 930d3c2b73e..507112d9311 100644 --- a/src/sage/combinat/sf/hall_littlewood.py +++ b/src/sage/combinat/sf/hall_littlewood.py @@ -73,7 +73,7 @@ def __repr__(self): """ return self._name + " over %s" % self._sym.base_ring() - def __init__(self, Sym, t = 't'): + def __init__(self, Sym, t='t'): """ Initialize ``self``. @@ -365,8 +365,8 @@ def __init__(self, hall_littlewood): s = self.__class__.__name__[15:].capitalize() sfa.SymmetricFunctionAlgebra_generic.__init__( self, hall_littlewood._sym, - basis_name = "Hall-Littlewood " + s + hall_littlewood._name_suffix, - prefix = "HL"+s) + basis_name="Hall-Littlewood " + s + hall_littlewood._name_suffix, + prefix="HL" +s) self.t = hall_littlewood.t self._sym = hall_littlewood._sym self._hall_littlewood = hall_littlewood @@ -407,7 +407,8 @@ def _s_to_self(self, x): sage: P(s[2,1]) 6*HLP[1, 1, 1] + HLP[2, 1] """ - return self._from_cache(x, self._s_cache, self._s_to_self_cache, t = self.t) + return self._from_cache(x, self._s_cache, self._s_to_self_cache, + t=self.t) def _self_to_s(self, x): r""" @@ -435,7 +436,8 @@ def _self_to_s(self, x): sage: s(P[2,1]) -6*s[1, 1, 1] + s[2, 1] """ - return self._s._from_cache(x, self._s_cache, self._self_to_s_cache, t = self.t) + return self._s._from_cache(x, self._s_cache, self._self_to_s_cache, + t=self.t) def transition_matrix(self, basis, n): r""" @@ -545,7 +547,7 @@ class Element(sfa.SymmetricFunctionAlgebra_generic.Element): Methods for elements of a Hall-Littlewood basis that are common to all bases. """ - def expand(self, n, alphabet = 'x'): + def expand(self, n, alphabet='x'): r""" Expands the symmetric function as a symmetric polynomial in ``n`` variables. @@ -577,7 +579,7 @@ def expand(self, n, alphabet = 'x'): x^2 """ s = self.parent().realization_of().schur() - return s(self).expand(n, alphabet = alphabet) + return s(self).expand(n, alphabet=alphabet) def scalar(self, x, zee=None): r""" @@ -618,7 +620,7 @@ def scalar(self, x, zee=None): s_x = s(x) return s_self.scalar(s_x, zee) - def scalar_hl(self, x, t = None): + def scalar_hl(self, x, t=None): r""" Returns the Hall-Littlewood (with parameter ``t``) scalar product of ``self`` and ``x``. @@ -661,8 +663,9 @@ def scalar_hl(self, x, t = None): if t is None: t = parent.t p = parent.realization_of().power() - f = lambda part1, part2: part1.centralizer_size(t = t) - return parent._apply_multi_module_morphism(p(self),p(x),f,orthogonal=True) + f = lambda part1, part2: part1.centralizer_size(t=t) + return parent._apply_multi_module_morphism(p(self), p(x), f, + orthogonal=True) ########### @@ -794,12 +797,11 @@ def _s_cache(self, n): sage: l(HLP._s_to_self_cache[2]) [([1, 1], [([1, 1], 1)]), ([2], [([1, 1], t), ([2], 1)])] """ - self._invert_morphism(n, QQt, self._self_to_s_cache, \ - self._s_to_self_cache, to_self_function = self._s_to_self_base, \ + self._invert_morphism(n, QQt, self._self_to_s_cache, + self._s_to_self_cache, to_self_function=self._s_to_self_base, upper_triangular=True, ones_on_diagonal=True) - ########### # Q basis # ########### @@ -844,9 +846,10 @@ def __init__(self, hall_littlewood): # temporary until Hom(GradedHopfAlgebrasWithBasis work better) category = sage.categories.all.ModulesWithBasis(self.base_ring()) - phi = self.module_morphism(diagonal = self._P._q_to_p_normalization, codomain = self._P, category = category) + phi = self.module_morphism(diagonal=self._P._q_to_p_normalization, + codomain=self._P, category=category) self._P.register_coercion(phi) - self .register_coercion(~phi) + self.register_coercion(~phi) def _p_to_q_normalization(self, m): r""" @@ -996,10 +999,12 @@ def _s_cache(self, n): sage: l(HLQp._self_to_s_cache[2]) [([1, 1], [([1, 1], 1), ([2], t)]), ([2], [([2], 1)])] """ - self._invert_morphism(n, QQt, self._self_to_s_cache, \ - self._s_to_self_cache, to_other_function = self._to_s, \ + self._invert_morphism(n, QQt, self._self_to_s_cache, + self._s_to_self_cache, + to_other_function=self._to_s, lower_triangular=True, ones_on_diagonal=True) + # Unpickling backward compatibility sage.misc.persist.register_unpickle_override('sage.combinat.sf.hall_littlewood', 'HallLittlewoodElement_p', HallLittlewood_p.Element) sage.misc.persist.register_unpickle_override('sage.combinat.sf.hall_littlewood', 'HallLittlewoodElement_q', HallLittlewood_q.Element) diff --git a/src/sage/combinat/sf/jack.py b/src/sage/combinat/sf/jack.py index 5231e113686..c46c78afefe 100644 --- a/src/sage/combinat/sf/jack.py +++ b/src/sage/combinat/sf/jack.py @@ -503,8 +503,8 @@ def __init__(self, jack): s = self.__class__.__name__[16:].capitalize() sfa.SymmetricFunctionAlgebra_generic.__init__( self, jack._sym, - basis_name = "Jack " + s + jack._name_suffix, - prefix = "Jack"+s) + basis_name="Jack " + s + jack._name_suffix, + prefix="Jack" + s) self.t = jack.t self._sym = jack._sym self._jack = jack @@ -550,7 +550,8 @@ def _m_to_self(self, x): sage: JP(m[2,1]) -3/2*JackP[1, 1, 1] + JackP[2, 1] """ - return self._from_cache(x, self._m_cache, self._m_to_self_cache, t = self.t) + return self._from_cache(x, self._m_cache, self._m_to_self_cache, + t=self.t) def _self_to_m(self, x): r""" @@ -578,7 +579,8 @@ def _self_to_m(self, x): sage: m(JP[2,1]) 3/2*m[1, 1, 1] + m[2, 1] """ - return self._m._from_cache(x, self._m_cache, self._self_to_m_cache, t = self.t) + return self._m._from_cache(x, self._m_cache, self._self_to_m_cache, + t=self.t) def c1(self, part): r""" @@ -897,15 +899,14 @@ def _m_cache(self, n): """ if n in self._self_to_m_cache: return - else: - self._self_to_m_cache[n] = {} + self._self_to_m_cache[n] = {} t = QQt.gen() monomial = sage.combinat.sf.sf.SymmetricFunctions(QQt).monomial() JP = sage.combinat.sf.sf.SymmetricFunctions(QQt).jack().P() - JP._gram_schmidt(n, monomial, lambda p: part_scalar_jack(p,p,t), \ - self._self_to_m_cache[n], upper_triangular=True) - JP._invert_morphism(n, QQt, self._self_to_m_cache, \ - self._m_to_self_cache, to_other_function = self._to_m) + JP._gram_schmidt(n, monomial, lambda p: part_scalar_jack(p, p, t), + self._self_to_m_cache[n], upper_triangular=True) + JP._invert_morphism(n, QQt, self._self_to_m_cache, + self._m_to_self_cache, to_other_function=self._to_m) def _to_m(self, part): r""" @@ -1071,7 +1072,8 @@ def __init__(self, jack): self._P = self._jack.P() # temporary until Hom(GradedHopfAlgebrasWithBasis) works better category = sage.categories.all.ModulesWithBasis(self.base_ring()) - phi = self.module_morphism(diagonal = self.c1, codomain = self._P, category = category) + phi = self.module_morphism(diagonal=self.c1, + codomain=self._P, category=category) # should use module_morphism(on_coeffs = ...) once it exists self._P.register_coercion(self._P._normalize_morphism(category) * phi) self .register_coercion(self ._normalize_morphism(category) *~phi) @@ -1107,15 +1109,19 @@ def __init__(self, jack): self._P = self._jack.P() # temporary until Hom(GradedHopfAlgebrasWithBasis) works better category = sage.categories.all.ModulesWithBasis(self.base_ring()) - phi = self._P.module_morphism(diagonal = self._P.scalar_jack_basis, codomain = self, category = category) - self .register_coercion(self ._normalize_morphism(category) * phi) + phi = self._P.module_morphism(diagonal=self._P.scalar_jack_basis, + codomain=self, category=category) + self.register_coercion(self._normalize_morphism(category) * phi) self._P.register_coercion(self._P._normalize_morphism(category) * ~phi) class Element(JackPolynomials_generic.Element): pass + qp_to_h_cache = {} h_to_qp_cache = {} + + class JackPolynomials_qp(JackPolynomials_generic): def __init__(self, jack): r""" @@ -1247,9 +1253,10 @@ def _self_to_h( self, x ): sage: h(JQp[2,1]) h[2, 1] - 3/5*h[3] """ - return self._h._from_cache(x, self._h_cache, self._self_to_h_cache, t = self.t) + return self._h._from_cache(x, self._h_cache, self._self_to_h_cache, + t=self.t) - def _h_to_self( self, x ): + def _h_to_self(self, x): r""" Isomorphism from the homogeneous basis into ``self`` @@ -1275,9 +1282,10 @@ def _h_to_self( self, x ): sage: JQp(h[2,1]) JackQp[2, 1] + 3/5*JackQp[3] """ - return self._from_cache(x, self._h_cache, self._h_to_self_cache, t = self.t) + return self._from_cache(x, self._h_cache, self._h_to_self_cache, + t=self.t) - def coproduct_by_coercion( self, elt ): + def coproduct_by_coercion(self, elt): r""" Returns the coproduct of the element ``elt`` by coercion to the Schur basis. diff --git a/src/sage/combinat/sf/k_dual.py b/src/sage/combinat/sf/k_dual.py index 7b557b25104..425af1bc96f 100644 --- a/src/sage/combinat/sf/k_dual.py +++ b/src/sage/combinat/sf/k_dual.py @@ -131,7 +131,7 @@ def __init__(self, Sym, k, t='t'): self._quotient_basis = Sym.m() else: self._quotient_basis = Sym.hall_littlewood(t=self.t).P() - Parent.__init__(self, category = GradedHopfAlgebras(R).Quotients().WithRealizations()) + Parent.__init__(self, category=GradedHopfAlgebras(R).Quotients().WithRealizations()) self.indices = ConstantFunction(Partitions_all_bounded(k)) def ambient(self): @@ -282,11 +282,11 @@ def _G_to_km_on_basis_single_level(self, w, m): if m < w.length(): return 0 ans = self.zero() - for la in Partitions(m, max_part = self.k): - ans += g.homogeneous_basis_noncommutative_variables_zero_Hecke((la)).coefficient(w)*mon(la) + for la in Partitions(m, max_part=self.k): + ans += g.homogeneous_basis_noncommutative_variables_zero_Hecke((la)).coefficient(w) * mon(la) return ans - def _AffineGrothendieck(self, w,m): + def _AffineGrothendieck(self, w, m): r""" Returns the affine Grothendieck polynomial indexed by the affine permutation ``w``. Because this belongs to the completion of the algebra, and hence is an diff --git a/src/sage/combinat/sf/llt.py b/src/sage/combinat/sf/llt.py index 3c854f342ed..b6b19659a3e 100644 --- a/src/sage/combinat/sf/llt.py +++ b/src/sage/combinat/sf/llt.py @@ -432,8 +432,8 @@ def __init__(self, llt, prefix): s = self.__class__.__name__[4:] sfa.SymmetricFunctionAlgebra_generic.__init__( self, llt._sym, - basis_name = "level %s LLT "%llt.level() + s + llt._name_suffix, - prefix = prefix) + basis_name="level %s LLT " % llt.level() + s + llt._name_suffix, + prefix=prefix) self.t = llt.t self._sym = llt._sym @@ -474,7 +474,8 @@ def _m_to_self(self, x): sage: HSp3(m[2,1]) -2*HSp3[1, 1, 1] + (2*t^2+2*t+1)*HSp3[2, 1] + (-2*t^2-t)*HSp3[3] """ - return self._from_cache(x, self._m_cache, self._m_to_self_cache, t = self.t) + return self._from_cache(x, self._m_cache, self._m_to_self_cache, + t=self.t) def _self_to_m(self, x): r""" @@ -502,8 +503,8 @@ def _self_to_m(self, x): sage: m(HSp3[2,1]) (t+2)*m[1, 1, 1] + (t+1)*m[2, 1] + t*m[3] """ - return self._m._from_cache(x, self._m_cache, self._self_to_m_cache, t = self.t) - + return self._m._from_cache(x, self._m_cache, self._self_to_m_cache, + t=self.t) def level(self): r""" @@ -598,8 +599,9 @@ def _m_cache(self, n): [([1, 1], [([1, 1], 1/t), ([2], -1/t)]), ([2], [([1, 1], -1/t), ([2], (t + 1)/t)])] """ - self._invert_morphism(n, QQt, self._self_to_m_cache, \ - self._m_to_self_cache, to_other_function = self._to_m) + self._invert_morphism(n, QQt, self._self_to_m_cache, + self._m_to_self_cache, + to_other_function=self._to_m) class Element(sfa.SymmetricFunctionAlgebra_generic.Element): pass diff --git a/src/sage/combinat/sf/macdonald.py b/src/sage/combinat/sf/macdonald.py index 3bc4bdbfd5f..d30339bc82d 100644 --- a/src/sage/combinat/sf/macdonald.py +++ b/src/sage/combinat/sf/macdonald.py @@ -746,8 +746,8 @@ def __init__(self, macdonald): s = self.__class__.__name__[21:].capitalize() sfa.SymmetricFunctionAlgebra_generic.__init__( self, macdonald._sym, - basis_name = "Macdonald " + s + macdonald._name_suffix, - prefix = "Mcd"+s) + basis_name="Macdonald " + s + macdonald._name_suffix, + prefix="Mcd" + s) self.q = macdonald.q self.t = macdonald.t self._macdonald = macdonald @@ -758,7 +758,7 @@ def __init__(self, macdonald): if hasattr(self, "_s_cache"): # temporary until Hom(GradedHopfAlgebrasWithBasis work better) category = ModulesWithBasis(self.base_ring()) - self .register_coercion(SetMorphism(Hom(self._s, self, category), self._s_to_self)) + self.register_coercion(SetMorphism(Hom(self._s, self, category), self._s_to_self)) self._s.register_coercion(SetMorphism(Hom(self, self._s, category), self._self_to_s)) def _s_to_self(self, x): @@ -787,7 +787,8 @@ def _s_to_self(self, x): sage: J(s[2,1]) ((-1/28*q+1/14)/(q-1/4))*McdJ[1, 1, 1] - (1/4/(q-1/4))*McdJ[2, 1] """ - return self._from_cache(x, self._s_cache, self._s_to_self_cache, q = self.q, t = self.t) + return self._from_cache(x, self._s_cache, self._s_to_self_cache, + q=self.q, t=self.t) def _self_to_s(self, x): r""" @@ -815,7 +816,8 @@ def _self_to_s(self, x): sage: s(J[2,1]) (3*q-6)*s[1, 1, 1] + (-4*q+1)*s[2, 1] """ - return self._s._from_cache(x, self._s_cache, self._self_to_s_cache, q = self.q, t = self.t) + return self._s._from_cache(x, self._s_cache, self._self_to_s_cache, + q=self.q, t=self.t) def c1(self, part): r""" @@ -1006,11 +1008,12 @@ def __init__(self, macdonald): self._J = macdonald.J() # temporary until Hom(GradedHopfAlgebrasWithBasis work better) category = ModulesWithBasis(self.base_ring()) - phi = self._J.module_morphism(diagonal = self.c2, codomain = self, category = category) - self.register_coercion( phi) + phi = self._J.module_morphism(diagonal=self.c2, + codomain=self, category=category) + self.register_coercion(phi) self._J.register_coercion(~phi) - def scalar_qt_basis(self, part1, part2 = None): + def scalar_qt_basis(self, part1, part2=None): r""" Returns the scalar product of `P(part1)` and `P(part2)` This scalar product formula is given in equation (4.11) p.323 @@ -1077,8 +1080,9 @@ def __init__(self, macdonald): # temporary until Hom(GradedHopfAlgebrasWithBasis) works better category = ModulesWithBasis(self.base_ring()) - phi = self._P.module_morphism(diagonal = self._P.scalar_qt_basis, codomain = self, category = category) - self .register_coercion( phi) + phi = self._P.module_morphism(diagonal=self._P.scalar_qt_basis, + codomain=self, category=category) + self.register_coercion(phi) self._P.register_coercion(~phi) class Element(MacdonaldPolynomials_generic.Element): @@ -1134,15 +1138,17 @@ def _s_cache(self, n): [([1, 1], [([1, 1], t^3 - t^2 - t + 1)]), ([2], [([1, 1], -q*t + t^2 + q - t), ([2], q*t^2 - q*t - t + 1)])] """ - self._invert_morphism(n, QQqt, self._self_to_s_cache, \ - self._s_to_self_cache, to_other_function = self._to_s, \ + self._invert_morphism(n, QQqt, self._self_to_s_cache, + self._s_to_self_cache, + to_other_function=self._to_s, upper_triangular=False) def _to_s(self, part): r""" Returns a function which gives the coefficient of a partition in the Schur expansion of self(part). - these computations are completed with coefficients in fraction + + These computations are completed with coefficients in fraction field of polynomials in `q` and `t` INPUT: @@ -1811,8 +1817,9 @@ def _s_cache(self, n): sage: l( S._self_to_s_cache[2] ) [([1, 1], [([1, 1], (-q*t^2 + q*t + t - 1)/(-q^3 + q^2 + q - 1)), ([2], (q*t - t^2 - q + t)/(-q^3 + q^2 + q - 1))]), ([2], [([1, 1], (q*t - t^2 - q + t)/(-q^3 + q^2 + q - 1)), ([2], (-q*t^2 + q*t + t - 1)/(-q^3 + q^2 + q - 1))])] """ - self._invert_morphism(n, QQqt, self._self_to_s_cache, \ - self._s_to_self_cache, to_other_function = self._to_s) + self._invert_morphism(n, QQqt, self._self_to_s_cache, + self._s_to_self_cache, + to_other_function=self._to_s) class Element(MacdonaldPolynomials_generic.Element): @@ -2020,6 +2027,7 @@ def qt_kostka(lam, mu): return _qt_kostka_cache[(lam,mu)] + # Backward compatibility for unpickling from sage.misc.persist import register_unpickle_override register_unpickle_override('sage.combinat.sf.macdonald', 'MacdonaldPolynomial_h', MacdonaldPolynomials_h.Element) diff --git a/src/sage/combinat/sf/orthotriang.py b/src/sage/combinat/sf/orthotriang.py index a0790e4855d..6905cbfd3d9 100644 --- a/src/sage/combinat/sf/orthotriang.py +++ b/src/sage/combinat/sf/orthotriang.py @@ -181,10 +181,13 @@ def _base_cache(self, n): else: self._self_to_base_cache[n] = {} - self._gram_schmidt(n, self._sf_base, self._scalar, self._self_to_base_cache,\ - leading_coeff=self._leading_coeff, upper_triangular=True) - self._invert_morphism(n, self.base_ring(), self._self_to_base_cache, \ - self._base_to_self_cache, to_other_function = self._to_base) + self._gram_schmidt(n, self._sf_base, self._scalar, + self._self_to_base_cache, + leading_coeff=self._leading_coeff, + upper_triangular=True) + self._invert_morphism(n, self.base_ring(), self._self_to_base_cache, + self._base_to_self_cache, + to_other_function=self._to_base) def _to_base(self, part): r""" diff --git a/src/sage/combinat/sf/sf.py b/src/sage/combinat/sf/sf.py index b4418a40606..f18f6fb75a2 100644 --- a/src/sage/combinat/sf/sf.py +++ b/src/sage/combinat/sf/sf.py @@ -1441,15 +1441,16 @@ def __init_extra__(self): for (basis1_name, basis2_name) in conversion_functions: basis1 = getattr(self, basis1_name)() basis2 = getattr(self, basis2_name)() - on_basis = SymmetricaConversionOnBasis(t = conversion_functions[basis1_name,basis2_name], domain = basis1, codomain = basis2) + on_basis = SymmetricaConversionOnBasis(t=conversion_functions[basis1_name,basis2_name], domain=basis1, codomain=basis2) from sage.rings.rational_field import RationalField if basis2_name != "powersum" or self._base.has_coerce_map_from(RationalField()): - iso(basis1._module_morphism(on_basis, codomain = basis2)) + iso(basis1._module_morphism(on_basis, codomain=basis2)) else: # Don't register conversions to powersums as coercions, # unless the base ring is a `\QQ`-algebra # (otherwise the coercion graph loses commutativity). - iso(basis1._module_morphism(on_basis, codomain = basis2), only_conversion = True) + iso(basis1._module_morphism(on_basis, codomain=basis2), + only_conversion=True) # Todo: fill in with other conversion functions on the classical bases @@ -1612,4 +1613,4 @@ def __call__(self, partition): # TODO: use self._codomain.sum_of_monomials, when the later # will have an optional optimization for the case when there # is no repetition in the support - return self._codomain._from_dict(dict(self._t(self.fake_sym.monomial(partition))), coerce = True) + return self._codomain._from_dict(dict(self._t(self.fake_sym.monomial(partition))), coerce=True) diff --git a/src/sage/combinat/sf/sfa.py b/src/sage/combinat/sf/sfa.py index d1fd77e8c69..7eac828fa4b 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -1072,11 +1072,11 @@ def lehrer_solomon(self, lam): sage: s = Sym.s() sage: pi_2 = (s.gessel_reutenauer(2)).omega_involution() sage: pi_1 = (s.gessel_reutenauer(1)).omega_involution() - sage: s.lehrer_solomon([2,1]) == pi_2 * pi_1 # since h_1, e_1 are pletistic identities + sage: s.lehrer_solomon([2,1]) == pi_2 * pi_1 # since h_1, e_1 are plethystic identities True Note that this also gives the `S_n`-equivariant structure of the - Orlik-Solmon algebra of the braid arrangement (also known as the + Orlik-Solomon algebra of the braid arrangement (also known as the type-`A` reflection arrangement). The representation corresponding to `\mathbf{LS}_\lambda` exhibits @@ -1449,6 +1449,27 @@ def coeff_of_m_mu_in_result(mu): distinct=True) return self(r) + def formal_series_ring(self): + r""" + Return the completion of all formal linear combinations of + ``self`` with finite linear combinations in each homogeneous + degree (computed lazily). + + EXAMPLES:: + + sage: s = SymmetricFunctions(ZZ).s() + sage: L = s.formal_series_ring() + sage: L + Lazy completion of Symmetric Functions over Integer Ring in the Schur basis + + TESTS:: + + sage: type(L) + + """ + from sage.rings.lazy_series_ring import LazySymmetricFunctions + return LazySymmetricFunctions(self) + class FilteredSymmetricFunctionsBases(Category_realization_of_parent): r""" The category of filtered bases of the ring of symmetric functions. @@ -2580,7 +2601,7 @@ def _inner_plethysm_pk_g(self, k, g, cache): for d in degrees: for mu in Partitions_n(d): mu_k = mu.power(k) - if mu_k in g: + if mu_k in g.support(): res += g.coefficient(mu_k)*mu_k.centralizer_size()/mu.centralizer_size()*p(mu) cache[(k,g)] = res @@ -2690,8 +2711,7 @@ def _dual_basis_default(self): sage: Sym.f()._dual_basis_default() Symmetric Functions over Rational Field in the elementary basis """ - return self.dual_basis(scalar=zee, scalar_name = "Hall scalar product") - + return self.dual_basis(scalar=zee, scalar_name="Hall scalar product") def dual_basis(self, scalar=None, scalar_name="", basis_name=None, prefix=None): r""" @@ -2750,8 +2770,8 @@ def dual_basis(self, scalar=None, scalar_name="", basis_name=None, prefix=None): scalar = zee scalar_name = "Hall scalar product" return dual.SymmetricFunctionAlgebra_dual(self, scalar, scalar_name, - basis_name = basis_name, - prefix = prefix) + basis_name=basis_name, + prefix=prefix) def basis_name(self): r""" @@ -4579,7 +4599,7 @@ def scalar(self, x, zee=None): p_x = p(x) return sum(zee(mu)*p_x.coefficient(mu)*p_self.coefficient(mu) for mu in p_self.support()) - def scalar_qt(self, x, q = None, t = None): + def scalar_qt(self, x, q=None, t=None): r""" Return the `q,t`-deformed standard Hall-Littlewood scalar product of ``self`` and ``x``. @@ -4633,10 +4653,10 @@ def scalar_qt(self, x, q = None, t = None): q = parent.q else: q = QQ['q','t'].gens()[0] - f = lambda part1, part2: part1.centralizer_size(t = t, q = q) + f = lambda part1, part2: part1.centralizer_size(t=t, q=q) return p._apply_multi_module_morphism(p(self), p(x), f, orthogonal=True) - def scalar_t(self, x, t = None): + def scalar_t(self, x, t=None): r""" Return the `t`-deformed standard Hall-Littlewood scalar product of ``self`` and ``x``. @@ -5155,7 +5175,7 @@ def bernstein_creation_operator(self, n): break return parent(res) - def _expand(self, condition, n, alphabet = 'x'): + def _expand(self, condition, n, alphabet='x'): r""" Expand the symmetric function as a symmetric polynomial in ``n`` variables. @@ -5338,7 +5358,7 @@ def restrict_degree(self, d, exact=True): res = dict(x for x in self._monomial_coefficients.items() if sum(x[0]) <= d) return self.parent()._from_dict(res) - def restrict_partition_lengths(self, l, exact = True): + def restrict_partition_lengths(self, l, exact=True): r""" Return the terms of ``self`` labelled by partitions of length ``l``. @@ -5390,10 +5410,11 @@ def restrict_parts(self, n): sage: z.restrict_parts(1) s[1] + s[1, 1, 1] """ - res = dict(x for x in self._monomial_coefficients.items() if _lmax(x[0]) <= n) + res = dict(x for x in self._monomial_coefficients.items() + if _lmax(x[0]) <= n) return self.parent()._from_dict(res) - def expand(self, n, alphabet = 'x'): + def expand(self, n, alphabet='x'): r""" Expand the symmetric function ``self`` as a symmetric polynomial in ``n`` variables. @@ -5493,7 +5514,7 @@ def skew_by(self, x): if p1.contains(p2)) return parent(s.element_class(s, ret)) - def hl_creation_operator(self, nu, t = None): + def hl_creation_operator(self, nu, t=None): r""" This is the vertex operator that generalizes Jing's operator. diff --git a/src/sage/combinat/sf/witt.py b/src/sage/combinat/sf/witt.py index 169fea1c370..2f942af97f3 100644 --- a/src/sage/combinat/sf/witt.py +++ b/src/sage/combinat/sf/witt.py @@ -1022,14 +1022,14 @@ def __init_extra__(self): # the elements of the Witt basis with respect to the powersum basis self._p_inverse_transition_matrices = {} - self .register_coercion(self._p._module_morphism(self._p_to_w_on_basis, codomain = self)) + self.register_coercion(self._p._module_morphism(self._p_to_w_on_basis, codomain=self)) from sage.rings.rational_field import RationalField if self.base_ring().has_coerce_map_from(RationalField): - self._p.register_coercion(self._module_morphism(self._w_to_p_on_basis, codomain = self._p)) + self._p.register_coercion(self._module_morphism(self._w_to_p_on_basis, codomain=self._p)) self._friendly = self._p else: # self._w_to_p_on_basis is a partial map at best - self._p.register_conversion(self._module_morphism(self._w_to_p_on_basis, codomain = self._p)) + self._p.register_conversion(self._module_morphism(self._w_to_p_on_basis, codomain=self._p)) if (not self._coerce_e) and (not self._coerce_h): # ensure that self has coercion at least to one other basis, # or else coercion-based computations will fail @@ -1054,8 +1054,8 @@ def __init_extra__(self): # cache for transition matrices which contain the coordinates of # the elements of the Witt basis with respect to the homogeneous basis self._h_inverse_transition_matrices = {} - self .register_coercion(self._h._module_morphism(self._h_to_w_on_basis, codomain = self)) - self._h.register_coercion(self._module_morphism(self._w_to_h_on_basis, codomain = self._h)) + self.register_coercion(self._h._module_morphism(self._h_to_w_on_basis, codomain=self)) + self._h.register_coercion(self._module_morphism(self._w_to_h_on_basis, codomain=self._h)) if self._friendly is None: self._friendly = self._h @@ -1076,8 +1076,8 @@ def __init_extra__(self): # cache for transition matrices which contain the coordinates of # the elements of the Witt basis with respect to the elementary basis self._e_inverse_transition_matrices = {} - self .register_coercion(self._e._module_morphism(self._e_to_w_on_basis, codomain = self)) - self._e.register_coercion(self._module_morphism(self._w_to_e_on_basis, codomain = self._e)) + self.register_coercion(self._e._module_morphism(self._e_to_w_on_basis, codomain=self)) + self._e.register_coercion(self._module_morphism(self._w_to_e_on_basis, codomain=self._e)) if self._friendly is None: self._friendly = self._e @@ -1337,7 +1337,7 @@ def verschiebung(self, n): w_coords_of_self = self.monomial_coefficients().items() from sage.combinat.partition import Partition dct = {Partition([i // n for i in lam]): coeff - for (lam, coeff) in w_coords_of_self - if all( i % n == 0 for i in lam )} + for lam, coeff in w_coords_of_self + if all(i % n == 0 for i in lam)} result_in_w_basis = parent._from_dict(dct) - return parent(result_in_w_basis) + return result_in_w_basis diff --git a/src/sage/combinat/sidon_sets.py b/src/sage/combinat/sidon_sets.py index 3f5cfcb3173..ec5038eb2c4 100644 --- a/src/sage/combinat/sidon_sets.py +++ b/src/sage/combinat/sidon_sets.py @@ -46,12 +46,12 @@ def sidon_sets(N, g = 1): sage: S.cardinality() 8 sage: S.category() - Category of finite sets + Category of finite enumerated sets sage: sid = S.an_element() sage: sid {2} sage: sid.category() - Category of finite sets + Category of finite enumerated sets TESTS:: diff --git a/src/sage/combinat/similarity_class_type.py b/src/sage/combinat/similarity_class_type.py index dad6ea70bb5..88515bb3cfe 100644 --- a/src/sage/combinat/similarity_class_type.py +++ b/src/sage/combinat/similarity_class_type.py @@ -105,11 +105,11 @@ class type, it is also possible to compute the number of classes of that type sage: simultaneous_similarity_classes(3, 2) q^10 + q^8 + 2*q^7 + 2*q^6 + 2*q^5 + q^4 -Similarity class types can be used to calculate the coefficients of generating +Similarity class types can be used to compute the coefficients of generating functions coming from the cycle index type techniques of Kung and Stong (see Morrison [Morrison06]_). -They can also be used to caclulate the number of invariant subspaces for a matrix +They can also be used to compute the number of invariant subspaces for a matrix over a finite field of any given dimension. For this we use the elegant recursive formula of Ramaré [R17]_ (see also [PR22]_). @@ -353,9 +353,10 @@ def centralizer_group_cardinality(la, q=None): q = ZZ['q'].gen() return q**centralizer_algebra_dim(la)*prod([fq(m, q=q) for m in la.to_exp()]) + def invariant_subspace_generating_function(la, q=None, t=None): """ - Return the invariant subpace generating function of a nilpotent matrix with + Return the invariant subspace generating function of a nilpotent matrix with Jordan block sizes given by ``la``. INPUT: diff --git a/src/sage/combinat/superpartition.py b/src/sage/combinat/superpartition.py index ced73e8dadd..6ba79867954 100644 --- a/src/sage/combinat/superpartition.py +++ b/src/sage/combinat/superpartition.py @@ -255,7 +255,7 @@ def _repr_(self) -> str: def _repr_pair(self) -> str: r""" - Represention of a super partition as a pair. + Representation of a super partition as a pair. A super partition is represented by a list consisting of the antisymmetric and symmetric parts. @@ -273,7 +273,7 @@ def _repr_pair(self) -> str: def _repr_list(self) -> str: r""" - Represention of a super partition as a list. + Representation of a super partition as a list. A super partition is represented by a list consisting of the negative values for the antisymmetric part listed first followed @@ -678,19 +678,19 @@ def add_horizontal_border_strip_star(self, h) -> list: EXAMPLES:: sage: SuperPartition([[4,1],[3]]).add_horizontal_border_strip_star(3) - [[4, 1; 3, 3], - [4, 1; 4, 2], - [3, 1; 5, 2], - [4, 1; 5, 1], + [[3, 1; 7], + [4, 1; 6], + [3, 0; 6, 2], [3, 1; 6, 1], - [4, 0; 4, 3], - [3, 0; 5, 3], [4, 0; 5, 2], - [3, 0; 6, 2], - [4, 1; 6], - [3, 1; 7]] + [4, 1; 5, 1], + [3, 0; 5, 3], + [3, 1; 5, 2], + [4, 0; 4, 3], + [4, 1; 4, 2], + [4, 1; 3, 3]] sage: SuperPartition([[2,1],[3]]).add_horizontal_border_strip_star(2) - [[2, 1; 3, 2], [2, 1; 4, 1], [2, 0; 3, 3], [2, 0; 4, 2], [2, 1; 5]] + [[2, 1; 5], [2, 0; 4, 2], [2, 1; 4, 1], [2, 0; 3, 3], [2, 1; 3, 2]] """ sp1, circ_list = self.to_circled_diagram() nsp = [list(la) + [0] for la in sp1.add_horizontal_border_strip(h)] @@ -726,29 +726,29 @@ def add_horizontal_border_strip_star_bar(self, h) -> list: EXAMPLES:: sage: SuperPartition([[4,1],[5,4]]).add_horizontal_border_strip_star_bar(3) - [[4, 3; 5, 4, 1], - [4, 1; 5, 4, 3], - [4, 2; 5, 5, 1], - [4, 1; 5, 5, 2], + [[4, 1; 8, 4], + [4, 1; 7, 5], + [4, 2; 7, 4], + [4, 1; 7, 4, 1], + [4, 2; 6, 5], + [4, 1; 6, 5, 1], + [4, 3; 6, 4], [4, 2; 6, 4, 1], [4, 1; 6, 4, 2], - [4, 1; 6, 5, 1], - [4, 1; 7, 4, 1], [4, 3; 5, 5], - [4, 3; 6, 4], - [4, 2; 6, 5], - [4, 2; 7, 4], - [4, 1; 7, 5], - [4, 1; 8, 4]] + [4, 2; 5, 5, 1], + [4, 1; 5, 5, 2], + [4, 3; 5, 4, 1], + [4, 1; 5, 4, 3]] sage: SuperPartition([[3,1],[5]]).add_horizontal_border_strip_star_bar(2) - [[3, 2; 5, 1], - [3, 1; 5, 2], - [4, 1; 5, 1], + [[3, 1; 7], + [4, 1; 6], + [3, 2; 6], [3, 1; 6, 1], [4, 2; 5], - [3, 2; 6], - [4, 1; 6], - [3, 1; 7]] + [4, 1; 5, 1], + [3, 2; 5, 1], + [3, 1; 5, 2]] """ sp1, circ_list = self.to_circled_diagram() nsp = [list(la) + [0] for la in sp1.add_horizontal_border_strip(h)] diff --git a/src/sage/combinat/tableau.py b/src/sage/combinat/tableau.py index c45fb0f2e87..f5652f86733 100644 --- a/src/sage/combinat/tableau.py +++ b/src/sage/combinat/tableau.py @@ -3239,26 +3239,26 @@ def promotion_operator(self, i): sage: t = Tableau([[1,2],[3]]) sage: t.promotion_operator(1) - [[[1, 2], [3], [4]], [[1, 2], [3, 4]], [[1, 2, 4], [3]]] + [[[1, 2, 4], [3]], [[1, 2], [3, 4]], [[1, 2], [3], [4]]] sage: t.promotion_operator(2) - [[[1, 1], [2, 3], [4]], - [[1, 1, 2], [3], [4]], + [[[1, 1, 2, 4], [3]], [[1, 1, 4], [2, 3]], - [[1, 1, 2, 4], [3]]] + [[1, 1, 2], [3], [4]], + [[1, 1], [2, 3], [4]]] sage: Tableau([[1]]).promotion_operator(2) - [[[1, 1], [2]], [[1, 1, 2]]] + [[[1, 1, 2]], [[1, 1], [2]]] sage: Tableau([[1,1],[2]]).promotion_operator(3) - [[[1, 1, 1], [2, 2], [3]], - [[1, 1, 1, 2], [2], [3]], + [[[1, 1, 1, 2, 3], [2]], [[1, 1, 1, 3], [2, 2]], - [[1, 1, 1, 2, 3], [2]]] + [[1, 1, 1, 2], [2], [3]], + [[1, 1, 1], [2, 2], [3]]] The example from [LLM2003]_ p. 12:: sage: Tableau([[1,1],[2,2]]).promotion_operator(3) - [[[1, 1, 1], [2, 2], [3, 3]], + [[[1, 1, 1, 3, 3], [2, 2]], [[1, 1, 1, 3], [2, 2], [3]], - [[1, 1, 1, 3, 3], [2, 2]]] + [[1, 1, 1], [2, 2], [3, 3]]] TESTS:: diff --git a/src/sage/combinat/tutorial.py b/src/sage/combinat/tutorial.py index 557995ba872..e9df13c746c 100644 --- a/src/sage/combinat/tutorial.py +++ b/src/sage/combinat/tutorial.py @@ -1842,7 +1842,7 @@ .. [CMS2012] Alexandre Casamayou, Nathann Cohen, Guillaume Connan, Thierry Dumont, Laurent Fousse, François Maltey, Matthias Meulien, Marc Mezzarobba, Clément Pernet, Nicolas M. Thiéry, Paul Zimmermann *Calcul Mathématique avec Sage* - http://sagebook.gforge.inria.fr/ + https://www.sagemath.org/sagebook/french.html .. [1] Or at least that should be the case; there are still many corners to diff --git a/src/sage/crypto/classical_cipher.py b/src/sage/crypto/classical_cipher.py index 0fe8c6b9434..72ea18a3117 100644 --- a/src/sage/crypto/classical_cipher.py +++ b/src/sage/crypto/classical_cipher.py @@ -1,19 +1,18 @@ """ Classical Ciphers """ - -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2007 David Kohel # # Distributed under the terms of the GNU General Public License (GPL) # -# http://www.gnu.org/licenses/ -#***************************************************************************** - +# https://www.gnu.org/licenses/ +# **************************************************************************** from .cipher import SymmetricKeyCipher from sage.monoids.string_monoid_element import StringMonoidElement from sage.modules.free_module import FreeModule + class AffineCipher(SymmetricKeyCipher): r""" Affine cipher class. This is the class that does the actual work of @@ -574,7 +573,3 @@ def inverse(self): E = self.parent() K = E.inverse_key(self.key()) return E(K) - - - - diff --git a/src/sage/crypto/mq/mpolynomialsystemgenerator.py b/src/sage/crypto/mq/mpolynomialsystemgenerator.py index 9dd861ed469..3c0bb6b349c 100644 --- a/src/sage/crypto/mq/mpolynomialsystemgenerator.py +++ b/src/sage/crypto/mq/mpolynomialsystemgenerator.py @@ -2,11 +2,12 @@ Abstract base class for generators of polynomial systems AUTHOR: - Martin Albrecht -""" +Martin Albrecht +""" from sage.structure.sage_object import SageObject + class MPolynomialSystemGenerator(SageObject): """ Abstract base class for generators of polynomial systems. @@ -26,8 +27,7 @@ def __getattr__(self, attr): if attr == "R": self.R = self.ring() return self.R - else: - raise AttributeError("'%s' object has no attribute '%s'"%(self.__class__,attr)) + raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__,attr)) def varformatstr(self, name): """ @@ -196,4 +196,3 @@ def random_element(self): NotImplementedError """ raise NotImplementedError - diff --git a/src/sage/data_structures/stream.py b/src/sage/data_structures/stream.py index e606f25579b..e349708c1ed 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -4,7 +4,7 @@ This module provides lazy implementations of basic operators on streams. The classes implemented in this module can be used to build up more complex streams for different kinds of series (Laurent, -Dirichlet, etc). +Dirichlet, etc.). EXAMPLES: @@ -85,6 +85,8 @@ # **************************************************************************** # Copyright (C) 2019 Kwankyu Lee +# 2022 Martin Rubey +# 2022 Travis Scrimshaw # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -96,6 +98,7 @@ from sage.rings.integer_ring import ZZ from sage.rings.infinity import infinity from sage.arith.misc import divisors +from sage.combinat.integer_vector_weighted import iterator_fast as wt_int_vec_iter class Stream(): """ @@ -505,7 +508,7 @@ class Stream_exact(Stream): """ def __init__(self, initial_coefficients, is_sparse, constant=None, degree=None, order=None): """ - Initialize a series that is known to be eventually geometric. + Initialize a stream with eventually constant coefficients. TESTS:: @@ -837,6 +840,7 @@ def __init__(self, is_sparse, approximate_order): sage: TestSuite(C).run(skip="_test_pickling") """ self._target = None + assert approximate_order is not None, "calling Stream_uninitialized with None as approximate order" super().__init__(is_sparse, approximate_order) def get_coefficient(self, n): @@ -1023,7 +1027,7 @@ def __eq__(self, other): INPUT: - - ``other`` -- a stream of coefficients + - ``other`` -- a :class:`Stream` of coefficients EXAMPLES:: @@ -1086,7 +1090,7 @@ def __eq__(self, other): INPUT: - - ``other`` -- a stream of coefficients + - ``other`` -- a :class:`Stream` of coefficients EXAMPLES:: @@ -1211,8 +1215,8 @@ class Stream_add(Stream_binaryCommutative): INPUT: - - ``left`` -- stream of coefficients on the left side of the operator - - ``right`` -- stream of coefficients on the right side of the operator + - ``left`` -- :class:`Stream` of coefficients on the left side of the operator + - ``right`` -- :class:`Stream` of coefficients on the right side of the operator EXAMPLES:: @@ -1271,8 +1275,8 @@ class Stream_sub(Stream_binary): INPUT: - - ``left`` -- stream of coefficients on the left side of the operator - - ``right`` -- stream of coefficients on the right side of the operator + - ``left`` -- :class:`Stream` of coefficients on the left side of the operator + - ``right`` -- :class:`Stream` of coefficients on the right side of the operator EXAMPLES:: @@ -1336,8 +1340,8 @@ class Stream_cauchy_mul(Stream_binary): INPUT: - - ``left`` -- stream of coefficients on the left side of the operator - - ``right`` -- stream of coefficients on the right side of the operator + - ``left`` -- :class:`Stream` of coefficients on the left side of the operator + - ``right`` -- :class:`Stream` of coefficients on the right side of the operator EXAMPLES:: @@ -1422,8 +1426,8 @@ class Stream_dirichlet_convolve(Stream_binary): INPUT: - - ``left`` -- stream of coefficients on the left side of the operator - - ``right`` -- stream of coefficients on the right side of the operator + - ``left`` -- :class:`Stream` of coefficients on the left side of the operator + - ``right`` -- :class:`Stream` of coefficients on the right side of the operator The coefficient of `n^{-s}` in the convolution of `l` and `r` equals `\sum_{k | n} l_k r_{n/k}`. @@ -1598,7 +1602,7 @@ def __init__(self, f, g): sage: g = Stream_function(lambda n: n^2, ZZ, True, 1) sage: h = Stream_cauchy_compose(f, g) """ - assert g._approximate_order > 0 + #assert g._approximate_order > 0 self._fv = f._approximate_order self._gv = g._approximate_order if self._fv < 0: @@ -1643,13 +1647,138 @@ def get_coefficient(self, n): return ret + sum(self._left[i] * self._pos_powers[i][n] for i in range(1, n // self._gv+1)) +class Stream_plethysm(Stream_binary): + r""" + Return the plethysm of ``f`` composed by ``g``. + + This is the plethysm `f \circ g = f(g)` when `g` is an element of + the ring of symmetric functions. + + INPUT: + + - ``f`` -- a :class:`Stream` + - ``g`` -- a :class:`Stream` with positive order + - ``p`` -- the powersum symmetric functions + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_function, Stream_plethysm + sage: s = SymmetricFunctions(QQ).s() + sage: p = SymmetricFunctions(QQ).p() + sage: f = Stream_function(lambda n: s[n], s, True, 1) + sage: g = Stream_function(lambda n: s[[1]*n], s, True, 1) + sage: h = Stream_plethysm(f, g, p) + sage: [s(h[i]) for i in range(5)] + [0, + s[1], + s[1, 1] + s[2], + 2*s[1, 1, 1] + s[2, 1] + s[3], + 3*s[1, 1, 1, 1] + 2*s[2, 1, 1] + s[2, 2] + s[3, 1] + s[4]] + sage: u = Stream_plethysm(g, f, p) + sage: [s(u[i]) for i in range(5)] + [0, + s[1], + s[1, 1] + s[2], + s[1, 1, 1] + s[2, 1] + 2*s[3], + s[1, 1, 1, 1] + s[2, 1, 1] + 3*s[3, 1] + 2*s[4]] + """ + def __init__(self, f, g, p): + r""" + Initialize ``self``. + + TESTS:: + + sage: from sage.data_structures.stream import Stream_function, Stream_plethysm + sage: s = SymmetricFunctions(QQ).s() + sage: p = SymmetricFunctions(QQ).p() + sage: f = Stream_function(lambda n: s[n], s, True, 1) + sage: g = Stream_function(lambda n: s[n-1,1], s, True, 2) + sage: h = Stream_plethysm(f, g, p) + """ + #assert g._approximate_order > 0 + self._fv = f._approximate_order + self._gv = g._approximate_order + self._p = p + val = self._fv * self._gv + super().__init__(f, g, f._is_sparse, val) + + def get_coefficient(self, n): + r""" + Return the ``n``-th coefficient of ``self``. + + INPUT: + + - ``n`` -- integer; the degree for the coefficient + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_function, Stream_plethysm + sage: s = SymmetricFunctions(QQ).s() + sage: p = SymmetricFunctions(QQ).p() + sage: f = Stream_function(lambda n: s[n], s, True, 1) + sage: g = Stream_function(lambda n: s[[1]*n], s, True, 1) + sage: h = Stream_plethysm(f, g, p) + sage: s(h.get_coefficient(5)) + 4*s[1, 1, 1, 1, 1] + 4*s[2, 1, 1, 1] + 2*s[2, 2, 1] + 2*s[3, 1, 1] + s[3, 2] + s[4, 1] + s[5] + sage: [s(h.get_coefficient(i)) for i in range(6)] + [0, + s[1], + s[1, 1] + s[2], + 2*s[1, 1, 1] + s[2, 1] + s[3], + 3*s[1, 1, 1, 1] + 2*s[2, 1, 1] + s[2, 2] + s[3, 1] + s[4], + 4*s[1, 1, 1, 1, 1] + 4*s[2, 1, 1, 1] + 2*s[2, 2, 1] + 2*s[3, 1, 1] + s[3, 2] + s[4, 1] + s[5]] + """ + if not n: # special case of 0 + return self._left[0] + + # We assume n > 0 + p = self._p + ret = p.zero() + for k in range(n+1): + temp = p(self._left[k]) + for la, c in temp: + inner = self._compute_product(n, la, c) + if inner is not None: + ret += inner + return ret + + def _compute_product(self, n, la, c): + """ + Compute the product ``c * p[la](self._right)`` in degree ``n``. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_plethysm, Stream_exact, Stream_function + sage: s = SymmetricFunctions(QQ).s() + sage: p = SymmetricFunctions(QQ).p() + sage: f = Stream_function(lambda n: s[n], s, True, 1) + sage: g = Stream_exact([s[2], s[3]], False, 0, 4, 2) + sage: h = Stream_plethysm(f, g, p) + sage: ret = h._compute_product(7, [2, 1], 1); ret + 1/12*p[2, 2, 1, 1, 1] + 1/4*p[2, 2, 2, 1] + 1/6*p[3, 2, 2] + + 1/12*p[4, 1, 1, 1] + 1/4*p[4, 2, 1] + 1/6*p[4, 3] + sage: ret == p[2,1](s[2] + s[3]).homogeneous_component(7) + True + """ + p = self._p + ret = p.zero() + for mu in wt_int_vec_iter(n, la): + temp = c + for i, j in zip(la, mu): + gs = self._right[j] + if not gs: + temp = p.zero() + break + temp *= p[i](gs) + ret += temp + return ret + ##################################################################### # Unary operations class Stream_scalar(Stream_inexact): """ - Base class for operators multiplying a coeffeicient stream - by a scalar. + Base class for operators multiplying a coefficient stream by a scalar. """ def __init__(self, series, scalar): """ @@ -2194,3 +2323,4 @@ def is_nonzero(self): True """ return self._series.is_nonzero() + diff --git a/src/sage/databases/cubic_hecke_db.py b/src/sage/databases/cubic_hecke_db.py new file mode 100644 index 00000000000..fd0068f8793 --- /dev/null +++ b/src/sage/databases/cubic_hecke_db.py @@ -0,0 +1,1517 @@ +# -*- coding: utf-8 -*- +r""" +Cubic Hecke Database + +This module contains the class :class:`CubicHeckeDataBase` which serves as an +interface to `Ivan Marin's data files +`__ +with respect to the cubic Hecke algebras. The data is available via a Python +wrapper as a pip installable package `database_cubic_hecke +`__. +For installation hints please see the documentation there. +All data needed for the cubic Hecke algebras on less than four strands is +included in this module for demonstration purpose (see for example +:func:`read_basis`, :func:`read_irr` , ... generated with the help of +:func:`create_demo_data`). + +In addition to Ivan Marin's data the package contains a function +:func:`read_markov` to obtain the coefficients of Markov traces on the +cubic Hecke algebras. This data has been precomputed with the help of +``create_markov_trace_data.py`` in the `database_cubic_hecke repository +`__. +Again, for less than four strands, this data is includes here for +demonstration purposes. + +Furthermore, this module contains the class :class:`CubicHeckeFileCache` +that enables +:class:`~sage.algebras.hecke_algebras.cubic_hecke_algebras.CubicHeckeAlgebra` +to keep intermediate results of calculations in the file system. + +The enum :class:`MarkovTraceModuleBasis` serves as basis for the submodule +of linear forms on the cubic Hecke algebra on at most four strands +satisfying the Markov trace condition for its cubic Hecke subalgebras. + +AUTHORS: + +- Sebastian Oehms (May 2020): initial version +- Sebastian Oehms (March 2022): PyPi version and Markov trace functionality +""" + +############################################################################## +# 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.misc.persist import _base_dumps, load +from sage.misc.temporary_file import atomic_write +from sage.misc.verbose import verbose +from sage.matrix.constructor import matrix +from sage.rings.integer_ring import ZZ +from sage.algebras.hecke_algebras.cubic_hecke_base_ring import CubicHeckeExtensionRing + + +# ------------------------------------------------------------------------------ +# functions to convert matrices and ring elements to and from flat python +# dictionaries in order to save matrices avoiding compatibility problems with +# older or newer sage versions and to save disc space +# ------------------------------------------------------------------------------ +def simplify(mat): + r""" + Convert a matrix to a dictionary consisting of flat Python objects. + + INPUT: + + - ``mat`` -- matrix to be converted into a ``dict`` + + OUTPUT: + + A ``dict`` from which ``mat`` can be reconstructed via + element construction. The values of the dictionary may be + dictionaries of tuples of integers or strings. + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import simplify + sage: import sage.algebras.hecke_algebras.cubic_hecke_base_ring as chbr + sage: ER. = chbr.CubicHeckeExtensionRing() + sage: mat = matrix(ER, [[2*a, -3], [c, 4*b*~c]]); mat + [ 2*a -3] + [ c 4*b*c^-1] + sage: simplify(mat) + {(0, 0): {(1, 0, 0): {0: 2}}, + (0, 1): {(0, 0, 0): {0: -3}}, + (1, 0): {(0, 0, 1): {0: 1}}, + (1, 1): {(0, 1, -1): {0: 4}}} + sage: mat == matrix(ER, _) + True + sage: F = ER.fraction_field() + sage: matf = mat.change_ring(F) + sage: simplify(matf) + {(0, 0): '2*a', (0, 1): '-3', (1, 0): 'c', (1, 1): '4*b/c'} + sage: matf == matrix(F, _) + True + """ + B = mat.base_ring() + d = mat.dict() + if isinstance(B, CubicHeckeExtensionRing): + # Laurent polynomial cannot be reconstructed from string + res = {k: {tuple(j): u.dict() for j, u in v.dict().items()} for k, v in d.items()} + else: + res = {k: str(v) for k, v in d.items()} + return res + + +class CubicHeckeDataSection(Enum): + r""" + Enum for the different sections of the database. + + The following choices are possible: + + - ``basis`` -- list of basis elements + - ``reg_left_reprs`` -- data for the left regular representation + - ``reg_right_reprs`` -- data for the right regular representation + - ``irr_reprs`` -- data for the split irreducible representations + - ``markov_tr_cfs`` -- data for the coefficients of the formal Markov traces + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import CubicHeckeDataBase + sage: cha_db = CubicHeckeDataBase() + sage: cha_db.section + + """ + basis = 'basis' + regular_left = 'regular_left' + regular_right = 'regular_right' + split_irred = 'split_irred' + markov_tr_cfs = 'markov_tr_cfs' + + +# ------------------------------------------------------------------------------- +# Class to supply data for the basis and matrix representation for the cubic +# Hecke algebra +# ------------------------------------------------------------------------------- +class CubicHeckeDataBase(SageObject): + r""" + Database interface for + :class:`~sage.algebras.hecke_algebras.cubic_hecke_algebras.CubicHeckeAlgebra` + + The original data are obtained from `Ivan Marin's web page + `__ + + The data needed to work with the cubic Hecke algebras on less than 4 strands + is completely contained in this module. Data needed for the larger algebras + can be installed as an optional Sage package which comes as a ``pip`` installable + `Python wrapper `__ of + Ivan Marin's data. For more information see the `corresponding repository + `__. + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import CubicHeckeDataBase + sage: cha_db = CubicHeckeDataBase() + sage: cha_db._feature + Feature('database_cubic_hecke') + """ + section = CubicHeckeDataSection + + def __init__(self): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import CubicHeckeDataBase + sage: cha_db = CubicHeckeDataBase() + sage: cha_db._data_library + {} + """ + from sage.features.databases import DatabaseCubicHecke + self._feature = DatabaseCubicHecke() + self._data_library = {} + self._demo = None + + def version(self): + r""" + Return the current version of the database. + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import CubicHeckeDataBase + sage: cha_db = CubicHeckeDataBase() + sage: cha_db.version() > '2022.1.1' # optional - database_cubic_hecke + True + """ + self._feature.require() + from database_cubic_hecke import version + return version() + + def demo_version(self): + r""" + Return whether the cubic Hecke database is 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(): + self._demo = False + else: + self._demo = True + return self._demo + + # -------------------------------------------------------------------------- + # read from an sobj-file obtained from Ivan Marin's database + # -------------------------------------------------------------------------- + def read(self, section, variables=None, nstrands=4): + r""" + Access various static data libraries. + + INPUT: + + ``section`` -- instance of enum :class:`CubicHeckeDataSection` + to select the data to be read in + + OUTPUT: + + A dictionary containing the data corresponding to the section. + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import CubicHeckeDataBase + sage: cha_db = CubicHeckeDataBase() + sage: basis = cha_db.read(cha_db.section.basis, nstrands=3) + sage: len(basis) + 24 + """ + if not isinstance(section, CubicHeckeDataSection): + raise TypeError('section must be an instance of enum %s' % CubicHeckeDataBase.section) + + data_lib = self._data_library + + nstrands = int(nstrands) + if (section, nstrands) in data_lib.keys(): + return data_lib[(section, nstrands)] + + verbose('loading data library %s for %s strands ...' % (section.value, nstrands)) + + from sage.algebras.hecke_algebras.cubic_hecke_matrix_rep import GenSign + + if self.demo_version(): + if nstrands >= 4: + self._feature.require() + from .cubic_hecke_db import read_basis, read_irr, read_regl, read_regr, read_markov + else: + from database_cubic_hecke import read_basis, read_irr, read_reg + from database_cubic_hecke.markov_trace_coeffs import read_markov + + def read_regl(variables, num_strands): + return read_reg(variables, num_strands=num_strands) + + def read_regr(variables, num_strands): + return read_reg(variables, right=True, num_strands=num_strands) + + if section == CubicHeckeDataSection.basis: + data_lib[(section, nstrands)] = read_basis(nstrands) + elif section == CubicHeckeDataSection.markov_tr_cfs: + keys = [k for k in MarkovTraceModuleBasis if k.strands() <= nstrands] + res = {k: read_markov(k.name, variables, num_strands=nstrands) for k in keys} + data_lib[(section, nstrands)] = res + elif section == CubicHeckeDataSection.split_irred: + dim_list, repr_list, repr_list_inv = read_irr(variables, nstrands) + data_lib[(section, nstrands)] = {GenSign.pos: repr_list, GenSign.neg: repr_list_inv} + else: + if section == CubicHeckeDataSection.regular_right: + dim_list, repr_list, repr_list_inv = read_regr(variables, nstrands) + else: + dim_list, repr_list, repr_list_inv = read_regl(variables, nstrands) + data_lib[(section, nstrands)] = {GenSign.pos: repr_list, GenSign.neg: repr_list_inv} + + verbose('... finished!') + return data_lib[(section, nstrands)] + + # -------------------------------------------------------------------------- + # matrix_reprs_from_file_cache_ + # -------------------------------------------------------------------------- + def read_matrix_representation(self, representation_type, gen_ind, nstrands, ring_of_definition): + r""" + Return the matrix representations from the database. + + INPUT: + + - ``representation_type`` -- an element of + :class:`~sage.algebras.hecke_algebras.cubic_hecke_matrix_rep.RepresentationType` + specifying the type of the representation + + OUTPUT: + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import CubicHeckeDataBase + sage: CHA3 = algebras.CubicHecke(2) + sage: GER = CHA3.extension_ring(generic=True) + sage: cha_db = CHA3._database + sage: rt = CHA3.repr_type + sage: m1 =cha_db.read_matrix_representation(rt.SplitIrredMarin, 1, 3, GER) + sage: len(m1) + 7 + sage: GBR = CHA3.base_ring(generic=True) + sage: m1rl = cha_db.read_matrix_representation(rt.RegularLeft, 1, 3, GBR) + sage: m1rl[0].dimensions() + (24, 24) + """ + from sage.algebras.hecke_algebras.cubic_hecke_matrix_rep import RepresentationType, GenSign + if not isinstance(representation_type, RepresentationType): + raise TypeError('representation_type must be an instance of enum %s' % RepresentationType) + + td = ring_of_definition.gens_dict_recursive() + if 'e3' in td.keys(): + td['j'] = td['e3'] + td.pop('e3') + v = tuple(td.values()) + + num_rep = representation_type.number_of_representations(nstrands) + rep_list = self.read(representation_type.data_section(), variables=v, nstrands=nstrands) + if gen_ind > 0: + rep_list = [rep_list[GenSign.pos][i] for i in range(num_rep)] + matrix_list = [matrix(ring_of_definition, rep[gen_ind-1], sparse=True) for rep in rep_list] + else: + # data of inverse of generators is stored under negative strand-index + rep_list = [rep_list[GenSign.neg][i] for i in range(num_rep)] + matrix_list = [matrix(ring_of_definition, rep[-gen_ind-1], sparse=True) for rep in rep_list] + for m in matrix_list: + m.set_immutable() + return matrix_list + + +class MarkovTraceModuleBasis(Enum): + r""" + Enum for the basis elements for the Markov trace module. + + The choice of the basis elements doesn't have a systematically background + apart from generating the submodule of maximal rank in the module of linear + forms on the cubic Hecke algebra for which the Markov trace condition with + respect to its cubic Hecke subalgebras hold. The number of crossings in + the corresponding links is chosen as minimal as possible. + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import MarkovTraceModuleBasis + sage: MarkovTraceModuleBasis.K92.description() + 'knot 9_34' + """ + def __repr__(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import MarkovTraceModuleBasis + sage: MarkovTraceModuleBasis.U2 # indirect doctest + U2 + """ + return self.name + + def __gt__(self, other): + r""" + Implement comparison of different items in order to have ``sorted`` work. + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import MarkovTraceModuleBasis + sage: sorted(MarkovTraceModuleBasis) + [U1, U2, U3, K4, U4, K4U, K6, K7, K91, K92] + """ + if self.__class__ is other.__class__: + tups = (self.strands(), len(self.braid_tietze()), self.name) + tupo = (other.strands(), len(other.braid_tietze()), other.name) + return tups > tupo + return NotImplemented + + def strands(self): + r""" + Return the number of strands of the minimal braid representative + of the link corresponding to ``self``. + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import MarkovTraceModuleBasis + sage: MarkovTraceModuleBasis.K7.strands() + 4 + """ + return self.value[1] + + def braid_tietze(self, strands_embed=None): + r""" + Return the Tietze representation of the braid corresponding to this basis + element. + + INPUT: + + - ``strands_embed`` -- (optional) the number of strands of the braid + if strands should be added + + OUTPUT: + + A tuple representing the braid in Tietze form. + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import MarkovTraceModuleBasis + sage: MarkovTraceModuleBasis.U2.braid_tietze() + () + sage: MarkovTraceModuleBasis.U2.braid_tietze(strands_embed=4) + (2, 3) + """ + if not strands_embed: + strands_embed = self.strands() + + if strands_embed > self.strands(): + last_gen = strands_embed-1 + return self.braid_tietze(strands_embed=last_gen) + (last_gen,) + + return self.value[2] + + def writhe(self): + r""" + Return the writhe of the link corresponding to this basis element. + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import MarkovTraceModuleBasis + sage: MarkovTraceModuleBasis.K4.writhe() + 0 + sage: MarkovTraceModuleBasis.K6.writhe() + 1 + """ + from sage.functions.generalized import sign + return sum(sign(t) for t in self.braid_tietze()) + + def description(self): + r""" + Return a description of the link corresponding to this basis element. + + In the case of knots it refers to the naming according to + `KnotInfo `__. + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import MarkovTraceModuleBasis + sage: MarkovTraceModuleBasis.U3.description() + 'three unlinks' + """ + return self.value[0] + + def link(self): + r""" + Return the :class:`Link` that represents this basis element. + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import MarkovTraceModuleBasis + sage: MarkovTraceModuleBasis.U1.link() + Link with 1 component represented by 0 crossings + sage: MarkovTraceModuleBasis.K4.link() + Link with 1 component represented by 4 crossings + """ + from sage.knots.link import Link + pd_code = self.value[3] + if pd_code is not None: + # since :class:`Link` does not construct disjoint union of unlinks + # from the braid representation, we need a pd_code here + return Link(pd_code) + else: + from sage.groups.braid import BraidGroup + B = BraidGroup(self.strands()) + return Link(B(self.braid_tietze())) + + def regular_homfly_polynomial(self): + r""" + Return the regular variant of the HOMFLY-PT polynomial of the link that + represents this basis element. + + This is the HOMFLY-PT polynomial renormalized by the writhe factor + such that it is an invariant of regular isotopy. + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import MarkovTraceModuleBasis + sage: MarkovTraceModuleBasis.U1.regular_homfly_polynomial() + 1 + sage: u2 = MarkovTraceModuleBasis.U2.regular_homfly_polynomial(); u2 + -L*M^-1 - L^-1*M^-1 + sage: u2**2 == MarkovTraceModuleBasis.U3.regular_homfly_polynomial() + True + sage: u2**3 == MarkovTraceModuleBasis.U4.regular_homfly_polynomial() + True + """ + H = self.link().homfly_polynomial() + L, M = H.parent().gens() + return H * L**self.writhe() + + def regular_kauffman_polynomial(self): + r""" + Return the regular variant of the Kauffman polynomial of the link that + represents this basis element. + + This is the Kauffman polynomial renormalized by the writhe factor + such that it is an invariant of regular isotopy. + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import MarkovTraceModuleBasis + sage: MarkovTraceModuleBasis.U1.regular_homfly_polynomial() + 1 + sage: u2 = MarkovTraceModuleBasis.U2.regular_kauffman_polynomial(); u2 + a*z^-1 - 1 + a^-1*z^-1 + sage: u2**2 == MarkovTraceModuleBasis.U3.regular_kauffman_polynomial() + True + sage: u2**3 == MarkovTraceModuleBasis.U4.regular_kauffman_polynomial() + True + """ + from sage.knots.knotinfo import KnotInfo + K = KnotInfo.L2a1_1.kauffman_polynomial().parent() + a, z = K.gens() + d = kauffman[self.name] + if d: + return K(d)*a**self.writhe() + U2rkp = MarkovTraceModuleBasis.U2.regular_kauffman_polynomial() + if self.name == 'K4U': + K4rkp = MarkovTraceModuleBasis.K4.regular_kauffman_polynomial() + return K4rkp * U2rkp + exp = self.strands() - 1 + return U2rkp**exp + + def links_gould_polynomial(self): + r""" + Return the Links-Gould polynomial of the link that represents this + basis element. + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import MarkovTraceModuleBasis + sage: MarkovTraceModuleBasis.U1.links_gould_polynomial() + 1 + sage: MarkovTraceModuleBasis.U2.links_gould_polynomial() + 0 + sage: MarkovTraceModuleBasis.K4.links_gould_polynomial() + 2*t0*t1 - 3*t0 - 3*t1 + t0*t1^-1 + 7 + t0^-1*t1 + - 3*t1^-1 - 3*t0^-1 + 2*t0^-1*t1^-1 + """ + from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing + R = LaurentPolynomialRing(ZZ, 't0, t1') + return R(links_gould[self.name]) + + U1 = ['one unlink', 1, (), []] + U2 = ['two unlinks', 2, (), [[3, 1, 4, 2], [4, 1, 3, 2]]] + U3 = ['three unlinks', 3, (), [[3, 7, 4, 8], [4, 7, 5, 8], + [5, 1, 6, 2], [6, 1, 3, 2]]] + U4 = ['four unlinks', 4, (), [[3, 9, 4, 10], [4, 9, 5, 10], [5, 11, 6, 12], + [6, 11, 7, 12], [7, 1, 8, 2], [8, 1, 3, 2]]] + K4U = ['knot 4_1 plus one unlink', 4, (1, -2, 1, -2), + [[3, 8, 4, 9], [9, 7, 10, 6], [7, 4, 8, 5], [5, 11, 6, 10], + [11, 1, 12, 2], [12, 1, 3, 2]]] + K4 = ['knot 4_1', 3, (1, -2, 1, -2), None] + K6 = ['knot 6_1', 4, (1, 1, 2, -1, -3, 2, -3), None] + K7 = ['knot 7_4', 4, (1, 1, 2, -1, 2, 2, 3, -2, 3), None] + K91 = ['knot 9_29', 4, (1, -2, -2, 3, -2, 1, -2, 3, -2), None] + K92 = ['knot 9_34', 4, (-1, 2, -1, 2, -3, 2, -1, 2, -3), None] + + +kauffman = { + 'U1': 1, + 'U2': {(1, -1): 1, (0, 0): -1, (-1, -1): 1}, + 'U3': None, + 'U4': None, + 'K4U': None, + 'K4': {(2, 2): 1, (1, 3): 1, (2, 0): -1, (1, 1): -1, (0, 2): 2, (-1, 3): 1, + (0, 0): -1, (-1, 1): -1, (-2, 2): 1, (-2, 0): -1}, + 'K6': {(2, 2): 1, (1, 3): 1, (0, 4): 1, (-1, 5): 1, (2, 0): -1, (-1, 3): -2, + (-2, 4): 2, (-3, 5): 1, (-1, 1): 2, (-2, 2): -4, (-3, 3): -3, (-4, 4): 1, + (-2, 0): 1, (-3, 1): 2, (-4, 2): -3, (-4, 0): 1}, + 'K7': {(-2, 2): 1, (-3, 3): 2, (-4, 4): 3, (-5, 5): 2, (-6, 6): 1, (-4, 2): -4, + (-5, 3): -2, (-7, 5): 3, (-8, 6): 1, (-4, 0): 2, (-6, 2): -3, (-7, 3): -8, + (-8, 4): -3, (-9, 5): 1, (-7, 1): 4, (-8, 2): 2, (-9, 3): -4, (-8, 0): -1, + (-9, 1): 4}, + 'K91': {(7, 3): 1, (6, 4): 3, (5, 5): 6, (4, 6): 8, (3, 7): 6, (2, 8): 2, + (5, 3): -5, (4, 4): -13, (3, 5): -8, (2, 6): 6, (1, 7): 9, (0, 8): 2, + (5, 1): 2, (4, 2): 8, (3, 3): -1, (2, 4): -24, (1, 5): -24, (0, 6): -1, + (-1, 7): 3, (4, 0): -2, (3, 1): 2, (2, 2): 17, (1, 3): 14, (0, 4): -11, + (-1, 5): -10, (-2, 6): 1, (2, 0): -5, (1, 1): -1, (0, 2): 12, (-1, 3): 9, + (-2, 4): -3, (0, 0): -3, (-1, 1): -1, (-2, 2): 3, (-2, 0): -1}, + 'K92': {(5, 5): 1, (4, 6): 4, (3, 7): 6, (2, 8): 3, (5, 3): -1, (4, 4): -7, + (3, 5): -11, (2, 6): 5, (1, 7): 14, (0, 8): 3, (4, 2): 3, (3, 3): 5, + (2, 4): -19, (1, 5): -26, (0, 6): 9, (-1, 7): 8, (2, 2): 10, (1, 3): 12, + (0, 4): -23, (-1, 5): -10, (-2, 6): 8, (2, 0): -1, (1, 1): -1, + (0, 2): 11, (-1, 3): 4, (-2, 4): -10, (-3, 5): 4, (0, 0): -1, + (-1, 1): -1, (-2, 2): 4, (-3, 3): -2, (-4, 4): 1, (-2, 0): -1}} + + +links_gould = { + 'U1': 1, + 'U2': 0, + 'U3': 0, + 'U4': 0, + 'K4U': 0, + 'K4': {(1, 1): 2, (1, 0): -3, (0, 1): -3, (1, -1): 1, (0, 0): 7, (-1, 1): 1, + (0, -1): -3, (-1, 0): -3, (-1, -1): 2}, + 'K6': {(2, 2): 2, (2, 1): -3, (1, 2): -3, (2, 0): 1, (1, 1): 10, (0, 2): 1, + (1, 0): -10, (0, 1): -10, (1, -1): 3, (0, 0): 17, (-1, 1): 3, (0, -1): -7, + (-1, 0): -7, (-1, -1): 4}, + 'K7': {(4, 3): -1, (3, 4): -1, (4, 2): 1, (3, 3): 6, (2, 4): 1, (3, 2): -11, + (2, 3): -11, (3, 1): 6, (2, 2): 28, (1, 3): 6, (2, 1): -27, (1, 2): -27, + (2, 0): 9, (1, 1): 38, (0, 2): 9, (1, 0): -17, (0, 1): -17, (0, 0): 9}, + 'K91': {(2, 2): 6, (2, 1): -20, (1, 2): -20, (2, 0): 29, (1, 1): 76, (0, 2): 29, + (2, -1): -25, (1, 0): -123, (0, 1): -123, (-1, 2): -25, (2, -2): 14, + (1, -1): 116, (0, 0): 217, (-1, 1): 116, (-2, 2): 14, (2, -3): -5, + (1, -2): -71, (0, -1): -216, (-1, 0): -216, (-2, 1): -71, (-3, 2): -5, + (2, -4): 1, (1, -3): 27, (0, -2): 136, (-1, -1): 214, (-2, 0): 136, + (-3, 1): 27, (-4, 2): 1, (1, -4): -5, (0, -3): -50, (-1, -2): -122, + (-2, -1): -122, (-3, 0): -50, (-4, 1): -5, (0, -4): 8, (-1, -3): 37, + (-2, -2): 52, (-3, -1): 37, (-4, 0): 8, (-1, -4): -4, (-2, -3): -9, + (-3, -2): -9, (-4, -1): -4}, + 'K92': {(3, 1): 6, (2, 2): 12, (1, 3): 6, (3, 0): -15, (2, 1): -63, (1, 2): -63, + (0, 3): -15, (3, -1): 14, (2, 0): 112, (1, 1): 216, (0, 2): 112, + (-1, 3): 14, (3, -2): -6, (2, -1): -92, (1, 0): -334, (0, 1): -334, + (-1, 2): -92, (-2, 3): -6, (3, -3): 1, (2, -2): 37, (1, -1): 262, + (0, 0): 503, (-1, 1): 262, (-2, 2): 37, (-3, 3): 1, (2, -3): -6, + (1, -2): -104, (0, -1): -400, (-1, 0): -400, (-2, 1): -104, (-3, 2): -6, + (1, -3): 17, (0, -2): 162, (-1, -1): 330, (-2, 0): 162, (-3, 1): 17, + (0, -3): -27, (-1, -2): -136, (-2, -1): -136, (-3, 0): -27, (-1, -3): 22, + (-2, -2): 54, (-3, -1): 22, (-2, -3): -7, (-3, -2): -7}} + + +class CubicHeckeFileCache(SageObject): + """ + A class to cache calculations of + :class:`~sage.algebras.hecke_algebras.cubic_hecke_algebras.CubicHeckeAlgebra` + in the local file system. + """ + + class section(Enum): + r""" + Enum for the different sections of file cache. The following choices are + possible: + + - ``matrix_representations`` -- file cache for representation matrices + of basis elements + - ``braid_images`` -- file cache for images of braids + - ``basis_extensions`` -- file cache for a dynamical growing basis used + in the case of cubic Hecke algebras on more than 4 strands + - ``markov_trace`` -- file cache for intermediate results of long + calculations in order to recover the results already obtained by + preboius attemps of calculation until the corresponding intermediate + step + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import CubicHeckeFileCache + sage: CHA2 = algebras.CubicHecke(2) + sage: cha_fc = CubicHeckeFileCache(CHA2) + sage: cha_fc.section + + """ + def filename(self, nstrands=None): + r""" + Return the file name under which the data of this file cache section + is stored as an sobj-file. + + INPUT: + + - ``nstrands`` -- (optional) :class:`Integer`; number of strands of + the underlying braid group if the data file depends on it + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import CubicHeckeFileCache + sage: CHA2 = algebras.CubicHecke(2) + sage: cha_fc = CubicHeckeFileCache(CHA2) + sage: cha_fc.section.matrix_representations.filename(2) + 'matrix_representations_2.sobj' + sage: cha_fc.section.braid_images.filename(2) + 'braid_images_2.sobj' + """ + if nstrands is None: + return '%s.sobj' % self.value + else: + return '%s_%s.sobj' % (self.value, nstrands) + + matrix_representations = 'matrix_representations' + braid_images = 'braid_images' + basis_extensions = 'basis_extensions' + markov_trace = 'markov_trace' + + def __init__(self, num_strands): + r""" + Initialize ``self``. + + INPUT: + + - ``num_strands`` -- integer giving the number of strands of the + corresponding cubic Hecke algebra + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import CubicHeckeFileCache + sage: cha_fc = CubicHeckeFileCache(2) + sage: cha_fc._file_cache_path.endswith('cubic_hecke') + True + """ + self._nstrands = num_strands + + from sage.env import DOT_SAGE + self._file_cache_path = os.path.join(DOT_SAGE, 'cubic_hecke') + self._data_library = {} + os.makedirs(self._file_cache_path, exist_ok=True) + + def _warn_incompatibility(self, fname): + """ + Warn the user that he has an incomaptible file cache under `Sage_DOT` + and move it away to another file (marked with timestamp). + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import CubicHeckeFileCache + sage: cha2_fc = CubicHeckeFileCache(2) + sage: path = cha2_fc._file_cache_path + sage: fname = os.path.join(path, 'test') + sage: os.system('touch %s' % fname) + 0 + sage: new_fname = cha2_fc._warn_incompatibility(fname) + doctest:...: UserWarning: incompatible file cache ...test has been saved to ...test_... + sage: os.remove(new_fname) + """ + from warnings import warn + from datetime import date + today = date.today() + new_fname = '%s_%s' % (fname, today) + os.rename(fname, new_fname) + warn('incompatible file cache %s has been saved to %s' % (fname, new_fname)) + return new_fname + + def reset_library(self, section=None): + r""" + Reset the file cache corresponding to the specified ``section``. + + INPUT: + + - ``section`` -- an element of :class:`CubicHeckeFileCache.section` + to select the section of the file cache or ``None`` (default) + meaning all sections + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import CubicHeckeFileCache + sage: cha2_fc = CubicHeckeFileCache(2) + sage: cha2_fc.reset_library(cha2_fc.section.braid_images) + sage: cha2_fc.read(cha2_fc.section.braid_images) + {} + sage: cha2_fc.reset_library(cha2_fc.section.matrix_representations) + sage: data_mat = cha2_fc.read(cha2_fc.section.matrix_representations) + sage: len(data_mat.keys()) + 4 + """ + if section is None: + for sec in self.section: + self.reset_library(section=sec) + return + + if not isinstance(section, CubicHeckeFileCache.section): + raise TypeError('section must be an instance of enum %s' % CubicHeckeFileCache.section) + + from sage.algebras.hecke_algebras.cubic_hecke_matrix_rep import RepresentationType + data_lib = self._data_library + empty_dict = {} + if section == self.section.matrix_representations: + for rep_type in RepresentationType: + new_dict = {} + empty_dict.update({rep_type.name: new_dict}) + elif section == self.section.basis_extensions: + empty_dict = [] + data_lib.update({section: empty_dict}) + + def is_empty(self, section=None): + r""" + Return ``True`` if the cache of the given ``section`` is empty. + + INPUT: + + - ``section`` -- an element of :class:`CubicHeckeFileCache.section` + to select the section of the file cache or ``None`` (default) + meaning all sections + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import CubicHeckeFileCache + sage: cha2_fc = CubicHeckeFileCache(2) + sage: cha2_fc.reset_library() + sage: cha2_fc.is_empty() + True + """ + if section is None: + return all(self.is_empty(section=sec) for sec in self.section) + + if not isinstance(section, CubicHeckeFileCache.section): + raise TypeError('section must be an instance of enum %s' % CubicHeckeFileCache.section) + + self.read(section) + data_lib = self._data_library[section] + from sage.algebras.hecke_algebras.cubic_hecke_matrix_rep import RepresentationType + if section == self.section.matrix_representations: + for rep_type in RepresentationType: + if len(data_lib[rep_type.name]) > 0: + return False + return True + + if section == self.section.basis_extensions and self._nstrands > 4: + # the new generators and their inverses are not counted + # since they are added during initialization + return len(data_lib) <= 2*(self._nstrands - 4) + return not data_lib + + # -------------------------------------------------------------------------- + # save data file system + # -------------------------------------------------------------------------- + def write(self, section=None): + r""" + Write data from memory to the file system. + + INPUT: + + - ``section`` -- an element of :class:`CubicHeckeFileCache.section` + specifying the section where the corresponding cached data belong to; + if omitted, the data of all sections is written to the file system + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import CubicHeckeFileCache + sage: cha2_fc = CubicHeckeFileCache(2) + sage: cha2_fc.reset_library(cha2_fc.section.braid_images) + sage: cha2_fc.write(cha2_fc.section.braid_images) + """ + data_lib = self._data_library + lib_path = self._file_cache_path + + if section is None: + for sec in self.section: + if sec in data_lib.keys(): + self.write(section=sec) + return + + if not isinstance(section, CubicHeckeFileCache.section): + raise TypeError('section must be an instance of enum %s' % CubicHeckeFileCache.section) + + if section not in data_lib.keys(): + raise ValueError("No data for file %s in memory" % section) + + verbose('saving file cache %s ...' % section) + fname = os.path.join(lib_path, section.filename(self._nstrands)) + with atomic_write(fname, binary=True) as f: + f.write(_base_dumps(data_lib[section])) + f.close() + + # -------------------------------------------------------------------------- + # read from file system + # -------------------------------------------------------------------------- + def read(self, section): + r""" + Read data into memory from the file system. + + INPUT: + + - ``section`` -- an element of :class:`CubicHeckeFileCache.section` + specifying the section where the corresponding cached data belong to + + OUTPUT: + + Dictionary containing the data library corresponding to the section + of file cache + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import CubicHeckeFileCache + sage: cha2_fc = CubicHeckeFileCache(2) + sage: cha2_fc.reset_library(cha2_fc.section.braid_images) + sage: cha2_fc.read(cha2_fc.section.braid_images) + {} + """ + if not isinstance(section, CubicHeckeFileCache.section): + raise TypeError('section must be an instance of enum %s' % CubicHeckeFileCache.section) + + data_lib = self._data_library + lib_path = self._file_cache_path + + if section in data_lib.keys(): + return data_lib[section] + + verbose('loading file cache %s ...' % section) + fname = os.path.join(lib_path, section.filename(self._nstrands)) + try: + data_lib[section] = load(fname) + verbose('... finished!') + except IOError: + self.reset_library(section) + verbose('... not found!') + except (ImportError, ModuleNotFoundError): + self._warn_incompatibility(fname) + self.reset_library(section) + + return data_lib[section] + + # -------------------------------------------------------------------------- + # read matrix representation from file cache + # -------------------------------------------------------------------------- + def read_matrix_representation(self, representation_type, monomial_tietze, ring_of_definition): + r""" + Return the matrix representations of the given monomial (in Tietze form) + if it has been stored in the file cache before. + INPUT: + + - ``representation_type`` -- an element of + :class:`~sage.algebras.hecke_algebras.cubic_hecke_matrix_rep.RepresentationType` + specifying the type of the representation + - ``monomial_tietze`` -- tuple representing the braid in Tietze form + - ``ring_of_definition`` -- instance of + :class:`~sage.algebras.hecke_algebras.cubic_hecke_base_ring.CubicHeckeRingOfDefinition` + respectively + :class:`~sage.algebras.hecke_algebras.cubic_hecke_base_ring.CubicHeckeExtensionRing` + depending on whether ``representation_type`` is split or not + + OUTPUT: + + Dictionary containing all matrix representations of ``self`` of the + given ``representation_type`` which have been stored in the file cache. + Otherwise ``None`` is returned. + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2) + sage: R = CHA2.base_ring(generic=True) + sage: cha_fc = CHA2._filecache + sage: g, = CHA2.gens(); gt = g.Tietze() + sage: rt = CHA2.repr_type + sage: g.matrix(representation_type=rt.RegularLeft) + [ 0 -v 1] + [ 1 u 0] + [ 0 w 0] + sage: [_] == cha_fc.read_matrix_representation(rt.RegularLeft, gt, R) + True + sage: cha_fc.reset_library(cha_fc.section.matrix_representations) + sage: cha_fc.write(cha_fc.section.matrix_representations) + sage: cha_fc.read_matrix_representation(rt.RegularLeft, gt, R) == None + True + """ + from sage.algebras.hecke_algebras.cubic_hecke_matrix_rep import RepresentationType + if not isinstance(representation_type, RepresentationType): + raise TypeError('representation_type must be an instance of enum %s' % RepresentationType) + + matrix_representations = self.read(self.section.matrix_representations)[representation_type.name] + if monomial_tietze in matrix_representations.keys(): + matrix_list_dict = matrix_representations[monomial_tietze] + matrix_list = [matrix(ring_of_definition, mat_dict, sparse=True) for mat_dict in matrix_list_dict] + for m in matrix_list: + m.set_immutable() + return matrix_list + return None + + # -------------------------------------------------------------------------- + # matrix_representation to file cache + # -------------------------------------------------------------------------- + def write_matrix_representation(self, representation_type, monomial_tietze, matrix_list): + r""" + Write the matrix representation of a monomial to the file cache. + + INPUT: + + - ``representation_type`` -- an element of + :class:`~sage.algebras.hecke_algebras.cubic_hecke_matrix_rep.RepresentationType` + specifying the type of the representation + - ``monomial_tietze`` -- tuple representing the braid in Tietze form + - ``matrix_list`` -- list of matrices corresponding to the irreducible + representations + + EXAMPLES:: + + sage: CHA2 = algebras.CubicHecke(2) + sage: R = CHA2.base_ring(generic=True) + sage: cha_fc = CHA2._filecache + sage: g, = CHA2.gens(); gi = ~g; git = gi.Tietze() + sage: rt = CHA2.repr_type + sage: m = gi.matrix(representation_type=rt.RegularRight) + sage: cha_fc.read_matrix_representation(rt.RegularRight, git, R) + [ + [ 0 1 (-u)/w] + [ 0 0 1/w] + [ 1 0 v/w] + ] + sage: CHA2.reset_filecache(cha_fc.section.matrix_representations) + sage: cha_fc.read_matrix_representation(rt.RegularLeft, git, R) == None + True + sage: cha_fc.write_matrix_representation(rt.RegularRight, git, [m]) + sage: [m] == cha_fc.read_matrix_representation(rt.RegularRight, git, R) + True + """ + from sage.algebras.hecke_algebras.cubic_hecke_matrix_rep import RepresentationType + if not isinstance(representation_type, RepresentationType): + raise TypeError('representation_type must be an instance of enum %s' % RepresentationType) + + sec = self.section.matrix_representations + all_matrix_representations = self.read(sec) + if representation_type.name not in all_matrix_representations.keys(): + # old file-cache is not compatible with current dictionary keys. + fname = os.path.join(self._file_cache_path, sec.filename(self._nstrands)) + self._warn_incompatibility(fname) + all_matrix_representations = self.read(sec) + + matrix_representations = all_matrix_representations[representation_type.name] + + if monomial_tietze in matrix_representations.keys(): + # entry already registered + return + + matrix_representation_dict = [simplify(mat) for mat in list(matrix_list)] + matrix_representations[monomial_tietze] = matrix_representation_dict + + self.write(sec) + return + + # -------------------------------------------------------------------------- + # read braid images from file cache + # -------------------------------------------------------------------------- + def read_braid_image(self, braid_tietze, ring_of_definition): + r""" + Return the list of pre calculated braid images from file cache. + + INPUT: + + - ``braid_tietze`` -- tuple representing the braid in Tietze form + - ``ring_of_definition`` -- a + :class:`~sage.algebras.hecke_algebras.cubic_hecke_base_ring.CubicHeckeRingOfDefinition` + + OUTPUT: + + A dictionary containing the pre calculated braid image of the given + braid. + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import CubicHeckeFileCache + sage: CHA2 = algebras.CubicHecke(2) + sage: ring_of_definition = CHA2.base_ring(generic=True) + sage: cha_fc = CubicHeckeFileCache(2) + sage: B2 = BraidGroup(2) + sage: b, = B2.gens(); b2 = b**2 + sage: cha_fc.is_empty(CubicHeckeFileCache.section.braid_images) + True + sage: b2_img = CHA2(b2); b2_img + w*c^-1 + u*c + (-v) + sage: cha_fc.write_braid_image(b2.Tietze(), b2_img.to_vector()) + sage: cha_fc.read_braid_image(b2.Tietze(), ring_of_definition) + (-v, u, w) + """ + braid_images = self.read(self.section.braid_images) + if braid_tietze in braid_images.keys(): + braid_image = braid_images[braid_tietze] + result_list = [ring_of_definition(cf) for cf in list(braid_image)] + from sage.modules.free_module_element import vector + return vector(ring_of_definition, result_list) + return None + + # -------------------------------------------------------------------------- + # braid image to_file cache + # -------------------------------------------------------------------------- + def write_braid_image(self, braid_tietze, braid_image_vect): + r""" + Write the braid image of the given braid to the file cache. + + INPUT: + + - ``braid_tietze`` -- tuple representing the braid in Tietze form + - ``braid_image_vect`` -- image of the given braid as a vector with + respect to the basis of the cubic Hecke algebra + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import CubicHeckeFileCache + sage: CHA2 = algebras.CubicHecke(2) + sage: ring_of_definition = CHA2.base_ring(generic=True) + sage: cha_fc = CubicHeckeFileCache(2) + sage: B2 = BraidGroup(2) + sage: b, = B2.gens(); b3 = b**3 + sage: b3_img = CHA2(b3); b3_img + u*w*c^-1 + (u^2-v)*c + (-u*v+w) + sage: cha_fc.write_braid_image(b3.Tietze(), b3_img.to_vector()) + sage: cha_fc.read_braid_image(b3.Tietze(), ring_of_definition) + (-u*v + w, u^2 - v, u*w) + sage: cha_fc.reset_library(CubicHeckeFileCache.section.braid_images) + sage: cha_fc.write(CubicHeckeFileCache.section.braid_images) + sage: cha_fc.is_empty(CubicHeckeFileCache.section.braid_images) + True + """ + braid_images = self.read(self.section.braid_images) + + if braid_tietze in braid_images.keys(): + # entry already registered + return + + braid_image_dict = [str(cf) for cf in list(braid_image_vect)] + braid_images[braid_tietze] = braid_image_dict + + self.write(self.section.braid_images) + return + + # -------------------------------------------------------------------------- + # basis to file cache + # -------------------------------------------------------------------------- + def update_basis_extensions(self, new_basis_extensions): + r""" + Update the file cache for basis extensions for cubic Hecke algebras on + more than 4 strands according to the given ``new_basis_extensions``. + + INPUT: + + - ``new_basis_extensions`` -- list of additional (to the static basis) + basis elements which should replace the former such list in the file + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import CubicHeckeFileCache + sage: CHA2 = algebras.CubicHecke(2) + sage: cha_fc = CubicHeckeFileCache(2) + sage: cha_fc.is_empty(CubicHeckeFileCache.section.basis_extensions) + True + sage: cha_fc.update_basis_extensions([(1,), (-1,)]) + sage: cha_fc.read(CubicHeckeFileCache.section.basis_extensions) + [(1,), (-1,)] + sage: cha_fc.reset_library(CubicHeckeFileCache.section.basis_extensions) + sage: cha_fc.write(CubicHeckeFileCache.section.basis_extensions) + sage: cha_fc.is_empty(CubicHeckeFileCache.section.basis_extensions) + True + """ + self._data_library.update({self.section.basis_extensions: new_basis_extensions}) + self.write(self.section.basis_extensions) + return + + +# ----------------------------------------------------------------------------- +# Demo data section +# ----------------------------------------------------------------------------- + +func_name = 'read_%s' + +var_decl = "\n %s = variables" +var_doc_input = "\n - ``variables`` -- tuple containing the indeterminates of the representation" +var_doc_decl = "\n sage: L.<%s> = LaurentPolynomialRing(ZZ)" + +template = """def %s(%snum_strands=3): + %s%s + data = {} + data[2] = %s + data[3] = %s + return data[num_strands] + +""" + + +doc = r"""{} + Return precomputed data of Ivan Marin. + + This code was generated by :func:`create_demo_data`, please do not edit. + + INPUT: +%s + - ``num_strands`` -- integer; number of strands of the cubic Hecke algebra + + EXAMPLES:: + + {}age: from sage.databases.cubic_hecke_db import %s%s + {}age: %s(%s2) + {} +""".format('r"""', 's', 's', '"""') # s in the middle to hide these lines from _test_enough_doctests + + +def create_demo_data(filename='demo_data.py'): + r""" + Generate code for the functions inserted below to access the + small cases of less than 4 strands as demonstration cases. + + The code is written to a file with the given name from where + it can be copied into this source file (in case a change is needed). + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import create_demo_data + sage: create_demo_data() # not tested + """ + # --------------------------------------------------------------- + # preparations + # --------------------------------------------------------------- + def create_repr_func(name, variables, data2, data3): + fname = func_name % name + if variables: + v = str(variables) + vars2 = v + ', ' + decl = var_decl % v + doc_dec = var_doc_decl % v[1: len(v)-1] + doc_str = doc % (var_doc_input, fname, doc_dec, fname, vars2) + res = template % (fname, 'variables, ', doc_str, decl, data2, data3) + else: + doc_str = doc % ('', fname, '', fname, '') + res = template % (fname, '', doc_str, '', data2, data3) + return res + + from textwrap import fill + + def fl(s): + return fill(str(s), subsequent_indent=' ') + + from sympy import var + from database_cubic_hecke import read_basis, read_irr, read_reg + + vari = var('a, b, c, j') + varr = var('u, v, w') + + # --------------------------------------------------------------- + # read data + # --------------------------------------------------------------- + bas2 = fl(read_basis(num_strands=2)) + bas3 = fl(read_basis(num_strands=3)) + + irr2 = fl(read_irr(variables=vari, num_strands=2)) + irr3 = fl(read_irr(variables=vari, num_strands=3)) + + regl2 = fl(read_reg(variables=varr, num_strands=2)) + regl3 = fl(read_reg(variables=varr, num_strands=3)) + + regr2 = fl(read_reg(variables=varr, num_strands=2, right=True)) + regr3 = fl(read_reg(variables=varr, num_strands=3, right=True)) + + # --------------------------------------------------------------- + # create functions and write them to file + # --------------------------------------------------------------- + bas = create_repr_func('basis', '', bas2, bas3) + irr = create_repr_func('irr', vari, irr2, irr3) + regl = create_repr_func('regl', varr, regl2, regl3) + regr = create_repr_func('regr', varr, regr2, regr3) + + with open(filename, 'w') as f: + f.write(bas) + f.write(irr) + f.write(regl) + f.write(regr) + + +def read_basis(num_strands=3): + r""" + Return precomputed data of Ivan Marin. + + This code was generated by :func:`create_demo_data`, please do not edit. + + INPUT: + + - ``num_strands`` -- integer; number of strands of the cubic Hecke algebra + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import read_basis + sage: read_basis(2) + [[], [1], [-1]] + """ + data = {} + data[2] = [[], [1], [-1]] + data[3] = [[], [1], [-1], [2], [-2], [1, 2], [1, -2], [-1, 2], [-1, -2], [1, 2, + 1], [1, 2, -1], [-1, 2, 1], [-1, 2, -1], [1, -2, 1], [-1, -2, + 1], [2, 1], [-2, 1], [2, -1], [-2, -1], [1, -2, -1], [-1, -2, + -1], [2, -1, 2], [1, 2, -1, 2], [-1, 2, -1, 2]] + return data[num_strands] + +def read_irr(variables, num_strands=3): + r""" + Return precomputed data of Ivan Marin. + + This code was generated by :func:`create_demo_data`, please do not edit. + + INPUT: + + - ``variables`` -- tuple containing the indeterminates of the representation + - ``num_strands`` -- integer; number of strands of the cubic Hecke algebra + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import read_irr + sage: L. = LaurentPolynomialRing(ZZ) + sage: read_irr((a, b, c, j), 2) + ([1, 1, 1], + [[{(0, 0): a}], [{(0, 0): c}], [{(0, 0): b}]], + [[{(0, 0): a^-1}], [{(0, 0): c^-1}], [{(0, 0): b^-1}]]) + """ + (a, b, c, j) = variables + data = {} + data[2] = ([1, 1, 1], [[{(0, 0): a}], [{(0, 0): c}], [{(0, 0): b}]], [[{(0, 0): + 1/a}], [{(0, 0): 1/c}], [{(0, 0): 1/b}]]) + data[3] = ([1, 1, 1, 2, 2, 2, 3], [[{(0, 0): a}, {(0, 0): a}], [{(0, 0): c}, + {(0, 0): c}], [{(0, 0): b}, {(0, 0): b}], [{(0, 0): b, (1, 0): + b*c, (1, 1): c}, {(0, 0): c, (0, 1): -1, (1, 1): b}], [{(0, + 0): a, (1, 0): a*b, (1, 1): b}, {(0, 0): b, (0, 1): -1, (1, + 1): a}], [{(0, 0): a, (1, 0): a*c, (1, 1): c}, {(0, 0): c, (0, + 1): -1, (1, 1): a}], [{(0, 0): c, (1, 0): a*c + b**2, (1, 1): + b, (2, 0): b, (2, 1): 1, (2, 2): a}, {(0, 0): a, (0, 1): -1, + (0, 2): b, (1, 1): b, (1, 2): -a*c - b**2, (2, 2): c}]], + [[{(0, 0): 1/a}, {(0, 0): 1/a}], [{(0, 0): 1/c}, {(0, 0): + 1/c}], [{(0, 0): 1/b}, {(0, 0): 1/b}], [{(0, 0): 1/b, (1, 0): + -1, (1, 1): 1/c}, {(0, 0): 1/c, (0, 1): 1/(b*c), (1, 1): + 1/b}], [{(0, 0): 1/a, (1, 0): -1, (1, 1): 1/b}, {(0, 0): 1/b, + (0, 1): 1/(a*b), (1, 1): 1/a}], [{(0, 0): 1/a, (1, 0): -1, (1, + 1): 1/c}, {(0, 0): 1/c, (0, 1): 1/(a*c), (1, 1): 1/a}], [{(0, + 0): 1/c, (1, 0): -a/b - b/c, (1, 1): 1/b, (2, 0): 1/b, (2, 1): + -1/(a*b), (2, 2): 1/a}, {(0, 0): 1/a, (0, 1): 1/(a*b), (0, 2): + 1/b, (1, 1): 1/b, (1, 2): a/b + b/c, (2, 2): 1/c}]]) + return data[num_strands] + +def read_regl(variables, num_strands=3): + r""" + Return precomputed data of Ivan Marin. + + This code was generated by :func:`create_demo_data`, please do not edit. + + INPUT: + + - ``variables`` -- tuple containing the indeterminates of the representation + - ``num_strands`` -- integer; number of strands of the cubic Hecke algebra + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import read_regl + sage: L. = LaurentPolynomialRing(ZZ) + sage: read_regl((u, v, w), 2) + ([3], + [[{(0, 1): -v, (0, 2): 1, (1, 0): 1, (1, 1): u, (2, 1): w}]], + [[{(0, 1): 1, (0, 2): -u*w^-1, (1, 2): w^-1, (2, 0): 1, (2, 2): v*w^-1}]]) + """ + (u, v, w) = variables + data = {} + data[2] = ([3], [[{(0, 1): -v, (0, 2): 1, (1, 0): 1, (1, 1): u, (2, 1): w}]], + [[{(0, 1): 1, (0, 2): -u/w, (1, 2): 1/w, (2, 0): 1, (2, 2): + v/w}]]) + data[3] = ([24], [[{(0, 1): -v, (0, 2): 1, (1, 0): 1, (1, 1): u, (2, 1): w, (3, + 5): -v, (3, 7): 1, (4, 6): -v, (4, 8): 1, (5, 3): 1, (5, 5): + u, (6, 4): 1, (6, 6): u, (7, 5): w, (8, 6): w, (9, 9): u, (9, + 15): 1, (10, 10): u, (10, 17): 1, (11, 9): w, (12, 10): w, + (13, 13): u, (13, 16): 1, (14, 13): w, (15, 9): -v, (15, 11): + 1, (16, 13): -v, (16, 14): 1, (17, 10): -v, (17, 12): 1, (18, + 19): -v, (18, 20): 1, (19, 18): 1, (19, 19): u, (20, 19): w, + (21, 22): -v, (21, 23): 1, (22, 21): 1, (22, 22): u, (23, 22): + w}, {(0, 3): -v, (0, 4): 1, (1, 15): -v, (1, 16): 1, (1, 22): + -v, (1, 23): u*v/w, (2, 17): -v, (2, 18): 1, (2, 23): v*(u*v - + w)/w, (3, 0): 1, (3, 3): u, (4, 3): w, (5, 9): -v, (5, 10): 1, + (5, 12): -u/w, (5, 22): u, (5, 23): -u**2/w, (6, 11): -v, (6, + 22): w, (6, 23): -u, (7, 12): -u*v/w, (7, 13): -v, (7, 19): 1, + (7, 21): -v, (7, 23): -u**2*v/w, (8, 12): -v, (8, 14): -v, (8, + 20): 1, (8, 23): -u*v, (9, 5): 1, (9, 9): u, (9, 23): u/w, + (10, 9): w, (10, 11): -u, (11, 6): 1, (11, 11): u, (11, 13): + u, (12, 13): w, (12, 23): -v, (13, 23): 1, (14, 8): 1, (14, + 14): u, (14, 23): v, (15, 1): 1, (15, 12): u/w, (15, 15): u, + (16, 11): v, (16, 15): w, (16, 23): -u, (17, 2): 1, (17, 12): + u*v/w, (17, 17): u, (18, 12): v, (18, 17): w, (19, 21): w, + (19, 23): v, (20, 14): w, (21, 7): 1, (21, 21): u, (21, 23): + u*v/w, (22, 11): 1, (23, 12): 1, (23, 23): u}]], [[{(0, 1): 1, + (0, 2): -u/w, (1, 2): 1/w, (2, 0): 1, (2, 2): v/w, (3, 5): 1, + (3, 7): -u/w, (4, 6): 1, (4, 8): -u/w, (5, 7): 1/w, (6, 8): + 1/w, (7, 3): 1, (7, 7): v/w, (8, 4): 1, (8, 8): v/w, (9, 11): + 1/w, (10, 12): 1/w, (11, 11): v/w, (11, 15): 1, (12, 12): v/w, + (12, 17): 1, (13, 14): 1/w, (14, 14): v/w, (14, 16): 1, (15, + 9): 1, (15, 11): -u/w, (16, 13): 1, (16, 14): -u/w, (17, 10): + 1, (17, 12): -u/w, (18, 19): 1, (18, 20): -u/w, (19, 20): 1/w, + (20, 18): 1, (20, 20): v/w, (21, 22): 1, (21, 23): -u/w, (22, + 23): 1/w, (23, 21): 1, (23, 23): v/w}, {(0, 3): 1, (0, 4): + -u/w, (1, 15): 1, (1, 16): -u/w, (1, 22): u*v/w, (1, 23): + -u/w, (2, 17): 1, (2, 18): -u/w, (3, 4): 1/w, (4, 0): 1, (4, + 4): v/w, (5, 9): 1, (5, 10): -u/w, (5, 13): -u/w, (5, 22): + -u**2/w, (6, 11): 1, (6, 12): -u/w, (6, 13): -u*v/w, (6, 22): + -u, (7, 19): -u/w, (7, 21): 1, (8, 13): -v, (8, 14): 1, (8, + 20): -u/w, (9, 10): 1/w, (9, 22): u/w, (10, 5): 1, (10, 6): + -u/w, (10, 10): v/w, (10, 13): -u**2/w, (10, 23): u/w, (11, + 22): 1, (12, 13): -u, (12, 23): 1, (13, 12): 1/w, (13, 13): + v/w, (14, 20): 1/w, (15, 13): u/w, (15, 16): 1/w, (15, 22): + -v/w, (16, 1): 1, (16, 6): v/w, (16, 13): u*v/w, (16, 16): + v/w, (17, 13): u*v/w, (17, 18): 1/w, (17, 23): -v/w, (18, 2): + 1, (18, 13): v, (18, 18): v/w, (18, 23): -v**2/w, (19, 7): 1, + (19, 12): v/w, (19, 19): v/w, (19, 23): u*v/w, (20, 8): 1, + (20, 20): v/w, (20, 23): v, (21, 13): -v/w, (21, 19): 1/w, + (22, 6): 1/w, (22, 13): u/w, (22, 22): v/w, (23, 13): 1}]]) + return data[num_strands] + +def read_regr(variables, num_strands=3): + r""" + Return precomputed data of Ivan Marin. + + This code was generated by :func:`create_demo_data`, please do not edit. + + INPUT: + + - ``variables`` -- tuple containing the indeterminates of the representation + - ``num_strands`` -- integer; number of strands of the cubic Hecke algebra + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import read_regr + sage: L. = LaurentPolynomialRing(ZZ) + sage: read_regr((u, v, w), 2) + ([3], + [[{(0, 1): -v, (0, 2): 1, (1, 0): 1, (1, 1): u, (2, 1): w}]], + [[{(0, 1): 1, (0, 2): -u*w^-1, (1, 2): w^-1, (2, 0): 1, (2, 2): v*w^-1}]]) + """ + (u, v, w) = variables + data = {} + data[2] = ([3], [[{(0, 1): -v, (0, 2): 1, (1, 0): 1, (1, 1): u, (2, 1): w}]], + [[{(0, 1): 1, (0, 2): -u/w, (1, 2): 1/w, (2, 0): 1, (2, 2): + v/w}]]) + data[3] = ([24], [[{(0, 1): -v, (0, 2): 1, (1, 0): 1, (1, 1): u, (2, 1): w, (3, + 15): -v, (3, 17): 1, (4, 16): -v, (4, 18): 1, (4, 22): v**2, + (4, 23): -v, (5, 9): -v, (5, 10): 1, (6, 13): -v, (6, 19): 1, + (6, 21): -v, (6, 22): -u*v, (7, 11): -v, (7, 12): 1, (8, 14): + -v, (8, 20): 1, (8, 22): -v*w, (9, 5): 1, (9, 9): u, (9, 23): + u/w, (10, 9): w, (10, 21): -u, (10, 22): -u**2, (11, 7): 1, + (11, 11): u, (11, 21): u, (11, 23): u*v/w, (12, 11): w, (12, + 22): -u*w, (13, 6): 1, (13, 13): u, (13, 22): v, (14, 8): 1, + (14, 14): u, (14, 23): v, (15, 3): 1, (15, 15): u, (15, 22): + u, (15, 23): -u**2/w, (16, 4): 1, (16, 16): u, (16, 21): v, + (17, 15): w, (17, 22): u*v, (17, 23): -u, (18, 16): w, (19, + 13): w, (20, 14): w, (21, 22): -v, (21, 23): 1, (22, 21): 1, + (22, 22): u, (23, 22): w}, {(0, 3): -v, (0, 4): 1, (1, 5): -v, + (1, 6): 1, (2, 7): -v, (2, 8): 1, (3, 0): 1, (3, 3): u, (4, + 3): w, (5, 1): 1, (5, 5): u, (6, 5): w, (7, 2): 1, (7, 7): u, + (8, 7): w, (9, 9): u, (9, 15): 1, (10, 13): u, (10, 16): 1, + (10, 22): -v, (11, 9): w, (12, 13): w, (12, 23): -v, (13, 23): + 1, (14, 21): w, (14, 23): v, (15, 9): -v, (15, 11): 1, (16, + 22): w, (16, 23): -u, (17, 13): -v, (17, 14): 1, (17, 21): -v, + (18, 19): -v, (18, 20): 1, (19, 18): 1, (19, 19): u, (20, 19): + w, (21, 17): 1, (21, 21): u, (22, 10): 1, (22, 22): u, (23, + 12): 1, (23, 23): u}]], [[{(0, 1): 1, (0, 2): -u/w, (1, 2): + 1/w, (2, 0): 1, (2, 2): v/w, (3, 15): 1, (3, 17): -u/w, (3, + 23): u*(u*v - w)/w**2, (4, 16): 1, (4, 18): -u/w, (4, 22): -v, + (4, 23): u*v/w, (5, 9): 1, (5, 10): -u/w, (5, 21): -u/w, (5, + 22): -u**2/w, (5, 23): -u*v/w**2, (6, 13): 1, (6, 19): -u/w, + (6, 23): -v/w, (7, 11): 1, (7, 12): -u/w, (7, 21): -u*v/w, (7, + 22): -u, (7, 23): -u*v**2/w**2, (8, 14): 1, (8, 20): -u/w, (8, + 21): -v, (8, 23): -v**2/w, (9, 10): 1/w, (9, 22): u/w, (10, + 5): 1, (10, 10): v/w, (10, 22): u*v/w, (11, 12): 1/w, (11, + 23): u/w, (12, 7): 1, (12, 12): v/w, (12, 23): u*v/w, (13, + 19): 1/w, (14, 20): 1/w, (15, 17): 1/w, (15, 21): u/w, (16, + 18): 1/w, (17, 3): 1, (17, 17): v/w, (17, 21): u*v/w, (18, 4): + 1, (18, 18): v/w, (18, 21): v, (19, 6): 1, (19, 19): v/w, (19, + 22): v, (20, 8): 1, (20, 20): v/w, (20, 23): v, (21, 22): 1, + (21, 23): -u/w, (22, 23): 1/w, (23, 21): 1, (23, 23): v/w}, + {(0, 3): 1, (0, 4): -u/w, (1, 5): 1, (1, 6): -u/w, (2, 7): 1, + (2, 8): -u/w, (3, 4): 1/w, (4, 0): 1, (4, 4): v/w, (5, 6): + 1/w, (6, 1): 1, (6, 6): v/w, (7, 8): 1/w, (8, 2): 1, (8, 8): + v/w, (9, 11): 1/w, (10, 13): -u**2/w, (10, 16): -u/w, (10, + 22): 1, (11, 11): v/w, (11, 15): 1, (12, 13): -u, (12, 23): 1, + (13, 12): 1/w, (13, 13): v/w, (14, 12): v/w, (14, 14): v/w, + (14, 17): 1, (15, 9): 1, (15, 11): -u/w, (16, 10): 1, (16, + 12): -u/w, (16, 16): v/w, (17, 13): u*v/w, (17, 14): -u/w, + (17, 21): 1, (18, 19): 1, (18, 20): -u/w, (19, 20): 1/w, (20, + 18): 1, (20, 20): v/w, (21, 13): -v/w, (21, 14): 1/w, (22, + 13): u/w, (22, 16): 1/w, (23, 13): 1}]]) + return data[num_strands] + + +# generated data function for cubic Hecke algebra +def read_markov(bas_ele, variables, num_strands=4): + r""" + Return precomputed Markov trace coefficients. + + This code was generated by ``create_markov_trace_data.py`` (from + the ``database_cubic_hecke`` repository), please do not edit. + + INPUT: + + - ``bas_ele`` -- an element of :class:`MarkovTraceModuleBasis` + - ``variables`` -- tuple consisting of the variables used in + the coefficients + - ``num_strands`` -- integer (default: 4); the number of strands + + + OUTPUT: + + A list of the coefficients. The i'th member corresponds to the i'th + basis element. + + EXAMPLES:: + + sage: from sage.databases.cubic_hecke_db import read_markov + sage: from sympy import var + sage: u, v, w, s = var('u, v, w, s') + sage: variables = (u, v, w, s) + sage: read_markov('U2', variables, num_strands=3) + [0, s, 1/s, s, 1/s, 0, 0, 0, 0, -s*v, s, s, -s*u/w, -v/s, 1/s, + 0, 0, 0, 0, 1/s, -u/(s*w), -v/s, 0, 0] + """ + u, v, w, s = variables + data = {} + data[2] = {'U1': [0, s, 1/s], 'U2': [1, 0, 0]} + data[3] = {'U1': [0, 0, 0, 0, 0, s**2, 1, 1, 1/s**2, u*s**2 + w, 0, 0, (s**2 + + v)/w, (u*s**2 + w)/s**2, 0, s**2, 1, 1, 1/s**2, 0, (s**2 + + v)/(w*s**2), (u*s**2 + w)/s**2, s**2, 0], 'U2': [0, s, 1/s, s, + 1/s, 0, 0, 0, 0, -v*s, s, s, (-u*s)/w, (-v)/s, 1/s, 0, 0, 0, + 0, 1/s, (-u)/(w*s), (-v)/s, 0, 0], 'U3': [1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'K4': [0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1]} + + return data[num_strands][bas_ele] + diff --git a/src/sage/databases/knotinfo_db.py b/src/sage/databases/knotinfo_db.py index 21be7479ef4..bd066b73809 100644 --- a/src/sage/databases/knotinfo_db.py +++ b/src/sage/databases/knotinfo_db.py @@ -2,7 +2,7 @@ r""" KnotInfo Database -This module contains the class :class:`KnotInfoDataBase` and auxilary classes +This module contains the class :class:`KnotInfoDataBase` and auxiliary classes for it which serves as an interface to the lists of named knots and links provided at the web-pages `KnotInfo `__ and `LinkInfo `__. diff --git a/src/sage/databases/sql_db.py b/src/sage/databases/sql_db.py index 0487d770f65..5e29cad2685 100644 --- a/src/sage/databases/sql_db.py +++ b/src/sage/databases/sql_db.py @@ -1254,8 +1254,7 @@ def get_skeleton(self, check=False): d = construct_skeleton(self) if d == self.__skeleton__: return d - else: - raise RuntimeError("Skeleton structure is out of whack!") + raise RuntimeError("skeleton structure is out of whack") return self.__skeleton__ def query(self, *args, **kwds): diff --git a/src/sage/docs/instancedoc.pyx b/src/sage/docs/instancedoc.py similarity index 95% rename from src/sage/docs/instancedoc.pyx rename to src/sage/docs/instancedoc.py index 681fbf767bd..e24368e4943 100644 --- a/src/sage/docs/instancedoc.pyx +++ b/src/sage/docs/instancedoc.py @@ -116,5 +116,7 @@ # https://www.gnu.org/licenses/ #***************************************************************************** -from sage.misc.lazy_import import lazy_import -lazy_import('sage.misc.instancedoc', 'instancedoc', deprecation=33763) +from sage.misc.superseded import deprecation +deprecation(33763, 'This module is deprecated. Use "sage.misc.instancedoc" instead.') + +from sage.misc.instancedoc import instancedoc diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 064414befbc..1752caa851a 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -43,6 +43,8 @@ ld_warning_regex = re.compile(r'^.*dylib.*was built for newer macOS version.*than being linked.*') # :trac:`30845` -- suppress warning on conda about ld ld_pie_warning_regex = re.compile(r'ld: warning: -pie being ignored. It is only used when linking a main executable') +# :trac:`34533` -- suppress warning on OS X 12.6 about chained fixups +chained_fixup_warning_regex = re.compile(r'ld: warning: -undefined dynamic_lookup may not work with chained fixups') sympow_cache_warning_regex = re.compile(r'\*\*WARNING\*\* /var/cache/sympow/datafiles/le64 yields insufficient permissions') find_sage_prompt = re.compile(r"^(\s*)sage: ", re.M) find_sage_continuation = re.compile(r"^(\s*)\.\.\.\.:", re.M) @@ -117,6 +119,9 @@ def fake_RIFtol(*args): (lambda g, w: "Long-step" in g, lambda g, w: (glpk_simplex_warning_regex.sub('', g), w)), + (lambda g, w: "chained fixups" in g, + lambda g, w: (chained_fixup_warning_regex.sub('', g), w)), + (lambda g, w: "insufficient permissions" in g, lambda g, w: (sympow_cache_warning_regex.sub('', g), w)), diff --git a/src/sage/doctest/sources.py b/src/sage/doctest/sources.py index 3136139e10a..6321e73a5e8 100644 --- a/src/sage/doctest/sources.py +++ b/src/sage/doctest/sources.py @@ -764,8 +764,8 @@ def _test_enough_doctests(self, check_extras=True, verbose=True): INPUT: - ``check_extras`` -- bool (default ``True``), whether to check if - doctests are created that do not correspond to either a ``sage: `` - or a ``>>> `` prompt + doctests are created that do not correspond to either a ``sage:`` + or a ``>>>`` prompt - ``verbose`` -- bool (default ``True``), whether to print offending line numbers when there are missing or extra tests diff --git a/src/sage/dynamics/arithmetic_dynamics/affine_ds.py b/src/sage/dynamics/arithmetic_dynamics/affine_ds.py index 3bab02718c3..ccf1de6115d 100644 --- a/src/sage/dynamics/arithmetic_dynamics/affine_ds.py +++ b/src/sage/dynamics/arithmetic_dynamics/affine_ds.py @@ -661,11 +661,11 @@ def nth_iterate(self, P, n): """ n = int(n) if n == 0: - return(P) + return P Q = P for i in range(n): Q = self(Q) - return(Q) + return Q def orbit(self, P, n): r""" @@ -728,7 +728,7 @@ def orbit(self, P, n): for i in range(bounds[0]+1, bounds[1]+1): Q = self(Q) orb.append(Q) - return(orb) + return orb def multiplier(self, P, n, check=True): r""" @@ -1014,7 +1014,7 @@ def orbit_structure(self, P): Q = self(Q) index += 1 I = orbit.index(Q) - return([I, index - I - 1]) + return [I, index - I - 1] def cyclegraph(self): r""" diff --git a/src/sage/dynamics/arithmetic_dynamics/berkovich_ds.py b/src/sage/dynamics/arithmetic_dynamics/berkovich_ds.py index 0301d68b8c9..92f10c05a9a 100644 --- a/src/sage/dynamics/arithmetic_dynamics/berkovich_ds.py +++ b/src/sage/dynamics/arithmetic_dynamics/berkovich_ds.py @@ -329,7 +329,7 @@ def __neq__(self, other): sage: f != g True """ - return not(self == other) + return not (self == other) def domain(self): """ @@ -863,7 +863,7 @@ def __call__(self, x, type_3_pole_check=True): g = DynamicalSystem_Berkovich(f) return g(self.domain()(QQ(0), QQ(1))).involution_map() # if the reduction is not constant, the image is the Gauss point - if not(num.is_constant() and dem.is_constant()): + if not (num.is_constant() and dem.is_constant()): return self.domain()(QQ(0), QQ(1)) if self.domain().is_padic_base(): reduced_value = field(num * dem.inverse_of_unit()).lift_to_precision(field.precision_cap()) diff --git a/src/sage/dynamics/arithmetic_dynamics/endPN_automorphism_group.py b/src/sage/dynamics/arithmetic_dynamics/endPN_automorphism_group.py index 91af87e49dd..8424b2fc505 100644 --- a/src/sage/dynamics/arithmetic_dynamics/endPN_automorphism_group.py +++ b/src/sage/dynamics/arithmetic_dynamics/endPN_automorphism_group.py @@ -275,8 +275,9 @@ def automorphism_group_QQ_fixedpoints(rational_function, return_functions=False, elements.append(matrix(F, 2, [a,b, 1, d])) if iso_type: - return(elements, which_group(elements)) - return(elements) + return elements, which_group(elements) + return elements + def height_bound(polynomial): r""" @@ -611,7 +612,8 @@ def remove_redundant_automorphisms(automorphisms, order_elts, moduli, integral_a del automorphisms[i][j] del order_elts[i][j] - return(automorphisms) + return automorphisms + def automorphism_group_QQ_CRT(rational_function, prime_lower_bound=4, return_functions=True, iso_type=False): r""" @@ -755,7 +757,7 @@ def automorphism_group_QQ_CRT(rational_function, prime_lower_bound=4, return_fun (gcd(orderaut + [24]) == 24 and \ (len(elements) == 12 or len(elements) == 8)): if iso_type: - return(elements, which_group(elements)) + return elements, which_group(elements) return elements else: N = gcd(orderaut + [12]) # all orders of elements divide N @@ -784,7 +786,7 @@ def automorphism_group_QQ_CRT(rational_function, prime_lower_bound=4, return_fun if (len(elements) == gcd(orderaut + [24])): #found enough automorphisms if iso_type: - return(elements, which_group(elements)) + return elements, which_group(elements) return elements elif numelts <= (len(temp)): badorders.append(order) @@ -819,8 +821,9 @@ def automorphism_group_QQ_CRT(rational_function, prime_lower_bound=4, return_fun p = primes.next(p) if iso_type: - return(elements, which_group(elements)) - return(elements) + return elements, which_group(elements) + return elements + def automorphism_group_FF(rational_function, absolute=False, iso_type=False, return_functions=False): r""" diff --git a/src/sage/dynamics/arithmetic_dynamics/endPN_minimal_model.py b/src/sage/dynamics/arithmetic_dynamics/endPN_minimal_model.py index 9c6d5a2dd52..65c6671ce59 100644 --- a/src/sage/dynamics/arithmetic_dynamics/endPN_minimal_model.py +++ b/src/sage/dynamics/arithmetic_dynamics/endPN_minimal_model.py @@ -1083,8 +1083,8 @@ def coshdelta(z): gn = g.nth_iterate_map(n) pts_poly = y*gn[0] - x*gn[1] d = ZZ(pts_poly.degree()) - max_mult = max([ex for p,ex in pts_poly.factor()]) - assert(n<=4), "n > 4, failed to find usable poly" + max_mult = max([ex for _, ex in pts_poly.factor()]) + assert (n <= 4), "n > 4, failed to find usable poly" R = get_bound_dynamical(pts_poly, g, m=n, dynatomic=dynatomic, prec=prec, emb=emb) # search starts in fundamental domain diff --git a/src/sage/dynamics/arithmetic_dynamics/product_projective_ds.py b/src/sage/dynamics/arithmetic_dynamics/product_projective_ds.py index 867d571db0d..caf82a3b00d 100644 --- a/src/sage/dynamics/arithmetic_dynamics/product_projective_ds.py +++ b/src/sage/dynamics/arithmetic_dynamics/product_projective_ds.py @@ -111,7 +111,7 @@ def _call_with_args(self, P, check=True): A = self.domain() Q = list(P) newP = [f(Q) for f in self.defining_polynomials()] - return(A.point(newP, check)) + return A.point(newP, check) def nth_iterate(self, P, n, normalize=False): r""" @@ -154,7 +154,7 @@ def nth_iterate(self, P, n, normalize=False): if n < 0: raise TypeError("must be a forward orbit") if n == 0: - return(self) + return self else: Q = self(P) if normalize: @@ -163,7 +163,7 @@ def nth_iterate(self, P, n, normalize=False): Q = self(Q) if normalize: Q.normalize_coordinates() - return(Q) + return Q def orbit(self, P, N, **kwds): r""" @@ -219,7 +219,7 @@ def orbit(self, P, N, **kwds): if N[0] < 0 or N[1] < 0: raise TypeError("orbit bounds must be non-negative") if N[0] > N[1]: - return([]) + return [] Q = copy(P) check = kwds.pop("check", True) @@ -237,7 +237,7 @@ def orbit(self, P, N, **kwds): if normalize: Q.normalize_coordinates() orb.append(Q) - return(orb) + return orb def nth_iterate_map(self, n): r""" diff --git a/src/sage/dynamics/arithmetic_dynamics/projective_ds.py b/src/sage/dynamics/arithmetic_dynamics/projective_ds.py index c814b81987a..cdfc11a9e52 100644 --- a/src/sage/dynamics/arithmetic_dynamics/projective_ds.py +++ b/src/sage/dynamics/arithmetic_dynamics/projective_ds.py @@ -2104,36 +2104,31 @@ def height_difference_bound(self, prec=None): EXAMPLES:: - sage: P. = ProjectiveSpace(QQ,1) - sage: f = DynamicalSystem_projective([x^2+y^2, x*y]) + sage: P. = ProjectiveSpace(QQ, 1) + sage: f = DynamicalSystem_projective([x^2 + y^2, x*y]) sage: f.height_difference_bound() 1.38629436111989 - This function does not automatically normalize. :: - - sage: P. = ProjectiveSpace(ZZ,2) - sage: f = DynamicalSystem_projective([4*x^2+100*y^2, 210*x*y, 10000*z^2]) - sage: f.height_difference_bound() - 12.1007121298723 - sage: f.normalize_coordinates() + sage: P. = ProjectiveSpace(ZZ, 2) + sage: f = DynamicalSystem_projective([4*x^2 + 100*y^2, 210*x*y, 10000*z^2]) sage: f.height_difference_bound() - 11.4075649493124 + 10.3089526606443 - A number field example:: + A number field example:: sage: R. = QQ[] sage: K. = NumberField(x^3 - 2) - sage: P. = ProjectiveSpace(K,2) - sage: f = DynamicalSystem_projective([1/(c+1)*x^2+c*y^2, 210*x*y, 10000*z^2]) + sage: P. = ProjectiveSpace(K, 2) + sage: f = DynamicalSystem_projective([1/(c+1)*x^2 + c*y^2, 210*x*y, 10000*z^2]) sage: f.height_difference_bound() - 12.1007121298723 + 11.3683039374269 :: - sage: P. = ProjectiveSpace(QQbar,2) + sage: P. = ProjectiveSpace(QQbar, 2) sage: f = DynamicalSystem_projective([x^2, QQbar(sqrt(-1))*y^2, QQbar(sqrt(3))*z^2]) sage: f.height_difference_bound() - 3.43967790223022 + 2.89037175789616 :: @@ -2167,7 +2162,7 @@ def height_difference_bound(self, prec=None): maxh = 0 for k in range(N + 1): CoeffPolys = (CR.gen(k) ** D).lift(I) - h = max([c.global_height(prec) for g in CoeffPolys for c in (g).coefficients()]) + h = max([g.global_height(prec) for g in CoeffPolys]) maxh = max(maxh, h) L = R((N + 1) * binomial(N + D - d, D - d)).log() + maxh C = max(U, L) #height difference dh(P) - L <= h(f(P)) <= dh(P) +U @@ -4847,7 +4842,7 @@ def sigma_invariants(self, n, formal=False, embedding=None, type='point', Computes the values of the elementary symmetric polynomials evaluated on the ``n`` multiplier spectra of this dynamical system. - The sigma invariants are the symetric polynomials evaluated on the + The sigma invariants are the symmetric polynomials evaluated on the characteristic polynomial of the multipliers. See [Hutz2019]_ for the full definition. Spepcifically, this function returns either the following polynomial or its coefficients (with signs @@ -5726,7 +5721,7 @@ def reduced_form(self, **kwds): pp_d = pts_poly.degree() pts_poly_CF = pts_poly_CF.subs({pts_poly_CF.parent().gen(1):1}).univariate_polynomial() max_mult = max([pp_d - pts_poly_CF.degree()] + [ex for p,ex in pts_poly_CF.roots()]) - assert(n<=4), "n > 4, failed to find usable poly" + assert (n<=4), "n > 4, failed to find usable poly" G,m = pts_poly.reduced_form(prec=prec, emb=emb, smallest_coeffs=False) sm_f = self.conjugate(m) @@ -5913,7 +5908,7 @@ def postcritical_set(self, check=True): post_critical_list = [] for point in critical_points: next_point = f(point) - while not(next_point in post_critical_list): + while next_point not in post_critical_list: post_critical_list.append(next_point) next_point = f(next_point) return post_critical_list diff --git a/src/sage/ext_data/nbconvert/postprocess.py b/src/sage/ext_data/nbconvert/postprocess.py index f36497fb73f..524c5b213a6 100755 --- a/src/sage/ext_data/nbconvert/postprocess.py +++ b/src/sage/ext_data/nbconvert/postprocess.py @@ -11,7 +11,6 @@ - Thierry Monteil (2018): initial version. """ - import sys import re @@ -27,7 +26,7 @@ # processing new_file = '' -for i,line in enumerate(lines): +for i, line in enumerate(lines): if line.startswith(' # ') and not wrong_title_fixed: new_file += re.sub('^ # ', '', line) new_file += '=' * (len(line) - 4) + '\n' @@ -44,4 +43,3 @@ # write new file with open(file_name, 'w') as f: f.write(new_file) - diff --git a/src/sage/features/databases.py b/src/sage/features/databases.py index 210229dfe82..d1e77a8ff32 100644 --- a/src/sage/features/databases.py +++ b/src/sage/features/databases.py @@ -127,6 +127,27 @@ def __init__(self): """ PythonModule.__init__(self, 'database_knotinfo', spkg='database_knotinfo') +class DatabaseCubicHecke(PythonModule): + r""" + A :class:`~sage.features.Feature` which describes the presence of the databases at the + web-page `Cubic Hecke algebra on 4 strands `__ + of Ivan Marin. + + EXAMPLES:: + + sage: from sage.features.databases import DatabaseCubicHecke + sage: DatabaseCubicHecke().is_present() # optional - database_cubic_hecke + FeatureTestResult('database_cubic_hecke', True) + """ + def __init__(self): + r""" + TESTS:: + + sage: from sage.features.databases import DatabaseCubicHecke + sage: isinstance(DatabaseCubicHecke(), DatabaseCubicHecke) + True + """ + PythonModule.__init__(self, 'database_cubic_hecke', spkg='database_cubic_hecke') class DatabaseReflexivePolytopes(StaticFile): r""" @@ -158,5 +179,6 @@ def all_features(): DatabaseCremona(), DatabaseCremona('cremona_mini'), DatabaseJones(), DatabaseKnotInfo(), + DatabaseCubicHecke(), DatabaseReflexivePolytopes(), DatabaseReflexivePolytopes('polytopes_db_4d', 'Hodge4d')] diff --git a/src/sage/features/latex.py b/src/sage/features/latex.py index b8915502582..02bcb57a0ca 100644 --- a/src/sage/features/latex.py +++ b/src/sage/features/latex.py @@ -14,7 +14,11 @@ from . import StaticFile, Executable, FeatureTestResult, FeatureNotPresentError -class latex(Executable): +latex_url = 'https://www.latex-project.org/' +latex_spkg = 'texlive' + + +class LaTeX(Executable): r""" A :class:`~sage.features.Feature` describing the presence of ``latex`` @@ -24,7 +28,7 @@ class latex(Executable): sage: latex().is_present() # optional - latex FeatureTestResult('latex', True) """ - def __init__(self): + def __init__(self, name): r""" TESTS:: @@ -32,8 +36,7 @@ def __init__(self): sage: isinstance(latex(), latex) True """ - Executable.__init__(self, "latex", executable="latex", - url="https://www.latex-project.org/") + super().__init__(name, executable=name, spkg=latex_spkg, url=latex_url) def is_functional(self): r""" @@ -62,7 +65,7 @@ def is_functional(self): # running latex from subprocess import run - cmd = ['latex', '-interaction=nonstopmode', filename_tex] + cmd = [self.name, '-interaction=nonstopmode', filename_tex] cmd = ' '.join(cmd) result = run(cmd, shell=True, cwd=base, capture_output=True, text=True) @@ -71,10 +74,32 @@ def is_functional(self): return FeatureTestResult(self, True) else: return FeatureTestResult(self, False, reason="Running latex on " - "a sample file returned non-zero " - "exit status {}".format(result.returncode)) + "a sample file returned non-zero " + "exit status {}".format(result.returncode)) + + +class latex(LaTeX): + r""" + A :class:`~sage.features.Feature` describing the presence of ``latex`` + + EXAMPLES:: + + sage: from sage.features.latex import latex + sage: latex().is_present() # optional - latex + FeatureTestResult('latex', True) + """ + def __init__(self): + r""" + TESTS:: + + sage: from sage.features.latex import latex + sage: isinstance(latex(), latex) + True + """ + super().__init__("latex") -class pdflatex(Executable): + +class pdflatex(LaTeX): r""" A :class:`~sage.features.Feature` describing the presence of ``pdflatex`` @@ -92,10 +117,10 @@ def __init__(self): sage: isinstance(pdflatex(), pdflatex) True """ - Executable.__init__(self, "pdflatex", executable="pdflatex", - url="https://www.latex-project.org/") + super().__init__("pdflatex") + -class xelatex(Executable): +class xelatex(LaTeX): r""" A :class:`~sage.features.Feature` describing the presence of ``xelatex`` @@ -113,10 +138,10 @@ def __init__(self): sage: isinstance(xelatex(), xelatex) True """ - Executable.__init__(self, "xelatex", executable="xelatex", - url="https://www.latex-project.org/") + super().__init__("xelatex") -class lualatex(Executable): + +class lualatex(LaTeX): r""" A :class:`~sage.features.Feature` describing the presence of ``lualatex`` @@ -134,8 +159,7 @@ def __init__(self): sage: isinstance(lualatex(), lualatex) True """ - Executable.__init__(self, "lualatex", executable="lualatex", - url="https://www.latex-project.org/") + super().__init__("lualatex") class TeXFile(StaticFile): @@ -145,12 +169,19 @@ class TeXFile(StaticFile): EXAMPLES:: sage: from sage.features.latex import TeXFile - sage: TeXFile('x', 'x.tex').is_present() # optional: pdflatex + sage: TeXFile('x', 'x.tex').is_present() # optional - latex FeatureTestResult('x', True) - sage: TeXFile('nonexisting', 'xxxxxx-nonexisting-file.tex').is_present() # optional - pdflatex - FeatureTestResult('nonexisting', False) """ def __init__(self, name, filename, **kwds): + r""" + Initialize. + + TESTS:: + + sage: from sage.features.latex import TeXFile + sage: TeXFile('nonexisting', 'xxxxxx-nonexisting-file.tex').is_present() # optional - latex + FeatureTestResult('nonexisting', False) + """ StaticFile.__init__(self, name, filename, search_path=[], **kwds) def absolute_filename(self) -> str: @@ -161,7 +192,7 @@ def absolute_filename(self) -> str: sage: from sage.features.latex import TeXFile sage: feature = TeXFile('latex_class_article', 'article.cls') - sage: feature.absolute_filename() # optional - pdflatex + sage: feature.absolute_filename() # optional - latex '.../latex/base/article.cls' """ from subprocess import run, CalledProcessError, PIPE @@ -171,8 +202,22 @@ def absolute_filename(self) -> str: universal_newlines=True, check=True) return proc.stdout.strip() except CalledProcessError: - raise FeatureNotPresentError(self, - reason="{filename!r} not found by kpsewhich".format(filename=self.filename)) + reason = "{filename!r} not found by kpsewhich".format(filename=self.filename) + raise FeatureNotPresentError(self, reason) + + def _is_present(self): + r""" + Test for the presence of the TeX file. + + EXAMPLES:: + + sage: from sage.features.latex import LaTeXPackage, latex + sage: f = LaTeXPackage("tkz-graph") + sage: g = latex() + sage: not f.is_present() or bool(g.is_present()) # indirect doctest + True + """ + return latex().is_present() and super()._is_present() class LaTeXPackage(TeXFile): @@ -183,7 +228,7 @@ class LaTeXPackage(TeXFile): EXAMPLES:: sage: from sage.features.latex import LaTeXPackage - sage: LaTeXPackage('graphics').is_present() # optional - pdflatex + sage: LaTeXPackage('graphics').is_present() # optional - latex FeatureTestResult('latex_package_graphics', True) """ @staticmethod diff --git a/src/sage/features/msolve.py b/src/sage/features/msolve.py index fa6679c2014..a7c7d5441b7 100644 --- a/src/sage/features/msolve.py +++ b/src/sage/features/msolve.py @@ -2,9 +2,11 @@ r""" Feature for testing the presence of msolve -`msolve `_ is a multivariate polynomial system solver -developed mainly by Jérémy Berthomieu (Sorbonne University), Christian Eder -(TU Kaiserslautern), and Mohab Safey El Din (Sorbonne University). +`msolve `_ is a multivariate polynomial system solver. + +.. SEEALSO:: + + - :mod:`sage.rings.polynomial.msolve` """ import subprocess diff --git a/src/sage/finance/markov_multifractal.py b/src/sage/finance/markov_multifractal.py index 99b5968f051..6206f56b304 100644 --- a/src/sage/finance/markov_multifractal.py +++ b/src/sage/finance/markov_multifractal.py @@ -276,4 +276,3 @@ def simulations(self, n, k=1): ## OUTPUT: ## m0, sigma, gamma_kbar, b ## """ - diff --git a/src/sage/functions/bessel.py b/src/sage/functions/bessel.py index 718bd20cd52..de1401dde68 100644 --- a/src/sage/functions/bessel.py +++ b/src/sage/functions/bessel.py @@ -216,7 +216,9 @@ from sage.functions.trig import sin, cos from sage.libs.mpmath import utils as mpmath_utils from sage.misc.latex import latex -from sage.rings.all import Integer, ZZ, QQ +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ from sage.structure.element import get_coercion_model from sage.symbolic.constants import pi from sage.symbolic.ring import SR @@ -1661,11 +1663,11 @@ def __init__(self): sage: spherical_bessel_J(3, x)._sympy_() jn(3, x) """ + conversions = dict(mathematica='SphericalBesselJ', + maxima='spherical_bessel_j', + sympy='jn') BuiltinFunction.__init__(self, 'spherical_bessel_J', nargs=2, - conversions=dict(mathematica= - 'SphericalBesselJ', - maxima='spherical_bessel_j', - sympy='jn')) + conversions=conversions) def _evalf_(self, n, z, parent, algorithm=None): r""" @@ -1759,11 +1761,11 @@ def __init__(self): sage: spherical_bessel_Y(3, x)._sympy_() yn(3, x) """ + conversions = dict(mathematica='SphericalBesselY', + maxima='spherical_bessel_y', + sympy='yn') BuiltinFunction.__init__(self, 'spherical_bessel_Y', nargs=2, - conversions=dict(mathematica= - 'SphericalBesselY', - maxima='spherical_bessel_y', - sympy='yn')) + conversions=conversions) def _evalf_(self, n, z, parent, algorithm=None): r""" @@ -1857,10 +1859,10 @@ def __init__(self): sage: spherical_hankel1 spherical_hankel1 """ + conversions = dict(mathematica='SphericalHankelH1', + maxima='spherical_hankel1') BuiltinFunction.__init__(self, 'spherical_hankel1', nargs=2, - conversions=dict(mathematica= - 'SphericalHankelH1', - maxima='spherical_hankel1')) + conversions=conversions) def _evalf_(self, n, z, parent, algorithm=None): r""" @@ -1959,9 +1961,8 @@ def __init__(self): spherical_hankel2 """ BuiltinFunction.__init__(self, 'spherical_hankel2', nargs=2, - conversions=dict(mathematica= - 'SphericalHankelH2', - maxima='spherical_hankel2')) + conversions=dict(mathematica='SphericalHankelH2', + maxima='spherical_hankel2')) def _evalf_(self, n, z, parent, algorithm=None): r""" diff --git a/src/sage/functions/gamma.py b/src/sage/functions/gamma.py index 6e64a1683d8..e93e518b0b9 100644 --- a/src/sage/functions/gamma.py +++ b/src/sage/functions/gamma.py @@ -4,7 +4,8 @@ from sage.symbolic.function import GinacFunction, BuiltinFunction from sage.symbolic.expression import register_symbol, symbol_table from sage.structure.all import parent as s_parent -from sage.rings.all import Rational, ComplexField +from sage.rings.complex_mpfr import ComplexField +from sage.rings.rational import Rational from sage.functions.exp_integral import Ei from sage.libs.mpmath import utils as mpmath_utils from .log import exp diff --git a/src/sage/functions/generalized.py b/src/sage/functions/generalized.py index 8c3f3fcf9b9..a24268c9b6b 100644 --- a/src/sage/functions/generalized.py +++ b/src/sage/functions/generalized.py @@ -52,7 +52,8 @@ ############################################################################## from sage.symbolic.function import (BuiltinFunction, GinacFunction) -from sage.rings.all import ComplexIntervalField, ZZ +from sage.rings.complex_interval_field import ComplexIntervalField +from sage.rings.integer_ring import ZZ class FunctionDiracDelta(BuiltinFunction): diff --git a/src/sage/functions/jacobi.py b/src/sage/functions/jacobi.py index 19a1981136d..af67d857f27 100644 --- a/src/sage/functions/jacobi.py +++ b/src/sage/functions/jacobi.py @@ -196,8 +196,7 @@ def __init__(self, kind): "'ds', 'dc', 'sn', 'sd', 'sc', 'cn', 'cd', 'cs'.") self.kind = kind BuiltinFunction.__init__(self, - name='jacobi_{}'.format(kind), - nargs=2, evalf_params_first=False, + name=f'jacobi_{kind}', nargs=2, evalf_params_first=False, conversions=dict(maple=('Jacobi{}'.format(kind.upper())), mathematica=('Jacobi{}'.format(kind.upper())), fricas=('jacobi{}'.format(kind.capitalize())), @@ -550,17 +549,10 @@ def __init__(self, kind): "'ds', 'dc', 'sn', 'sd', 'sc', 'cn', 'cd', 'cs'.") self.kind = kind BuiltinFunction.__init__(self, - name='inverse_jacobi_{}'.format(kind), - nargs=2, evalf_params_first=False, - conversions=dict(maple= - ('InverseJacobi{}' - .format(kind.upper())), - mathematica= - ('InverseJacobi{}' - .format(kind.upper())), - maxima= - ('inverse_jacobi_{}' - .format(kind)))) + name=f'inverse_jacobi_{kind}', nargs=2, evalf_params_first=False, + conversions=dict(maple=('InverseJacobi{}'.format(kind.upper())), + mathematica=f'InverseJacobi{kind.upper()}', + maxima=(f'inverse_jacobi_{kind}'))) def _eval_(self, x, m): r""" @@ -1073,10 +1065,9 @@ def __init__(self): jacobi_am """ BuiltinFunction.__init__(self, name='jacobi_am', nargs=2, - conversions=dict(maple='JacobiAM', - mathematica= - 'JacobiAmplitude'), - evalf_params_first=False) + conversions=dict(maple='JacobiAM', + mathematica='JacobiAmplitude'), + evalf_params_first=False) def _eval_(self, x, m): r""" diff --git a/src/sage/functions/log.py b/src/sage/functions/log.py index d322305b223..46cc279a287 100644 --- a/src/sage/functions/log.py +++ b/src/sage/functions/log.py @@ -1243,6 +1243,7 @@ def _print_latex_(self, z, m): harmonic_number = Function_harmonic_number_generalized() + class _Function_swap_harmonic(BuiltinFunction): r""" Harmonic number function with swapped arguments. For internal use only. @@ -1262,14 +1263,18 @@ class _Function_swap_harmonic(BuiltinFunction): """ def __init__(self): BuiltinFunction.__init__(self, "_swap_harmonic", nargs=2) + def _eval_(self, a, b, **kwds): - return harmonic_number(b,a,**kwds) + return harmonic_number(b, a, **kwds) + _swap_harmonic = _Function_swap_harmonic() + register_symbol(_swap_harmonic, {'maxima': 'gen_harmonic_number'}) register_symbol(_swap_harmonic, {'maple': 'harmonic'}) + class Function_harmonic_number(BuiltinFunction): r""" Harmonic number function, defined by: diff --git a/src/sage/functions/orthogonal_polys.py b/src/sage/functions/orthogonal_polys.py index 28f23f35ddf..77b7e06b909 100644 --- a/src/sage/functions/orthogonal_polys.py +++ b/src/sage/functions/orthogonal_polys.py @@ -2565,7 +2565,7 @@ def _eval_special_values_(self, n, a, x): if a == 0: return laguerre(n, x) if x == 0: - from sage.arith.all import binomial + from sage.arith.misc import binomial return binomial(n+a, n) def _pol_gen_laguerre(self, n, a, x): @@ -3028,5 +3028,5 @@ def eval_recursive(self, k, x, a, b, n, *args, **kwds): Hm2 = C * hahn.eval_recursive(k-2, x, a, b, n) return (Hm1 - Hm2) / A -hahn = Func_hahn() +hahn = Func_hahn() diff --git a/src/sage/functions/other.py b/src/sage/functions/other.py index 04237301e62..1ce7569919f 100644 --- a/src/sage/functions/other.py +++ b/src/sage/functions/other.py @@ -36,7 +36,7 @@ from sage.functions.trig import arctan2 -from sage.arith.all import binomial as arith_binomial +from sage.arith.misc import binomial as arith_binomial from sage.misc.functional import sqrt @@ -220,7 +220,7 @@ def _eval_floor_ceil(self, x, method, bits=0, **kwds): # The strategy is to first reduce the absolute diameter of the # interval until its size is at most 10^(-6). Then we check for # (B) by simplifying the expression. - from sage.rings.all import RealIntervalField + from sage.rings.real_mpfi import RealIntervalField # Might it be needed to simplify x? This only applies for # elements of SR (or its subrings) diff --git a/src/sage/functions/prime_pi.pyx b/src/sage/functions/prime_pi.pyx index dc73f9593aa..0a576734777 100644 --- a/src/sage/functions/prime_pi.pyx +++ b/src/sage/functions/prime_pi.pyx @@ -192,7 +192,7 @@ cdef class PrimePi(BuiltinFunction): return plot_step_function([(xmin,0),(xmax,0)], **kwds) y = self(xmin) v = [(xmin, y)] - from sage.rings.all import prime_range + from sage.rings.fast_arith import prime_range for p in prime_range(xmin+1, xmax+1, py_ints=True): y += 1 v.append((p,y)) diff --git a/src/sage/functions/special.py b/src/sage/functions/special.py index 349553d734c..02596e49620 100644 --- a/src/sage/functions/special.py +++ b/src/sage/functions/special.py @@ -506,6 +506,21 @@ def __init__(self): sage: fricas(elliptic_e(x, y)).D(x).sage()/elliptic_e(x, y).diff(x) # optional - fricas cos(x)/sqrt(-sin(x)^2 + 1) + Numerically:: + + sage: f = lambda x, y: elliptic_e(arcsin(x), y).subs(x=x, y=y) # optional - fricas + sage: g = lambda x, y: fricas.ellipticE(x, y).sage() # optional - fricas + sage: d = lambda x, y: f(x, y) - g(x, y) # optional - fricas + sage: [d(N(-pi/2+x), y) for x in range(1, 3) for y in range(-2,2)] # optional - fricas tol 1e-8 + [0.000000000000000, + 0.000000000000000, + 0.000000000000000, + 0.000000000000000, + 5.55111512312578e-17, + 0.000000000000000, + 0.000000000000000, + 0.000000000000000] + """ BuiltinFunction.__init__(self, 'elliptic_e', nargs=2, # Maple conversion left out since it uses @@ -841,11 +856,39 @@ def __init__(self): elliptic_f sage: elliptic_f(x, 2)._sympy_() elliptic_f(x, 2) + + Check that :trac:`34186` is fixed:: + + sage: _ = var("x y") + sage: fricas(elliptic_f(x, y)) # optional - fricas + ellipticF(sin(x),y) + + However, the conversion is only correct in the interval + `[-\pi/2, \pi/2]`:: + + sage: fricas(elliptic_f(x, y)).D(x).sage()/elliptic_f(x, y).diff(x) # optional - fricas + cos(x)/sqrt(-sin(x)^2 + 1) + + Numerically:: + + sage: f = lambda x, y: elliptic_f(arcsin(x), y).subs(x=x, y=y) # optional - fricas + sage: g = lambda x, y: fricas.ellipticF(x, y).sage() # optional - fricas + sage: d = lambda x, y: f(x, y) - g(x, y) # optional - fricas + sage: [d(N(-pi/2+x), y) for x in range(1, 3) for y in range(-2,2)] # optional - fricas tol 1e-8 + [0.000000000000000, + 0.000000000000000, + 0.000000000000000, + 0.000000000000000, + 5.55111512312578e-17, + 0.000000000000000, + 0.000000000000000, + 0.000000000000000] + """ BuiltinFunction.__init__(self, 'elliptic_f', nargs=2, conversions=dict(mathematica='EllipticF', maxima='elliptic_f', - # fricas='ellipticF', buggy + fricas='((x,y)+->ellipticF(sin(x), y))', sympy='elliptic_f')) def _eval_(self, z, m): diff --git a/src/sage/game_theory/normal_form_game.py b/src/sage/game_theory/normal_form_game.py index f0d58a98aec..373c586daca 100644 --- a/src/sage/game_theory/normal_form_game.py +++ b/src/sage/game_theory/normal_form_game.py @@ -1805,7 +1805,7 @@ def _solve_gambit_LP(self, maximization=True): """ if Game is None: raise NotImplementedError("gambit is not installed") - g = self._gambit_(maximization = maximization) + g = self._gambit_(maximization=maximization) output = ExternalLPSolver().solve(g) nasheq = Parser(output).format_gambit(g) return sorted(nasheq) @@ -2205,11 +2205,11 @@ def _is_NE(self, a, b, p1_support, p2_support, M1, M2): False """ # Check that supports are obeyed - if not(all(a[i] > 0 for i in p1_support) and - all(b[j] > 0 for j in p2_support) and - all(a[i] == 0 for i in range(len(a)) + if not (all(a[i] > 0 for i in p1_support) and + all(b[j] > 0 for j in p2_support) and + all(a[i] == 0 for i in range(len(a)) if i not in p1_support) and - all(b[j] == 0 for j in range(len(b)) + all(b[j] == 0 for j in range(len(b)) if j not in p2_support)): return False diff --git a/src/sage/game_theory/parser.py b/src/sage/game_theory/parser.py index 87b0676fecc..80d90d9a5cf 100644 --- a/src/sage/game_theory/parser.py +++ b/src/sage/game_theory/parser.py @@ -1,7 +1,6 @@ """ Parser For gambit And lrs Nash Equilibria """ - # **************************************************************************** # Copyright (C) 2014 James Campbell james.campbell@tanti.org.uk # 2015 Vincent Knight @@ -13,6 +12,7 @@ # https://www.gnu.org/licenses/ # **************************************************************************** + class Parser(): r""" A class for parsing the outputs of different algorithms called in other @@ -298,4 +298,3 @@ def format_gambit(self, gambit_game): nice_stuff.append(profile) return nice_stuff - diff --git a/src/sage/games/hexad.py b/src/sage/games/hexad.py index dff95e8e6b8..710513207a7 100644 --- a/src/sage/games/hexad.py +++ b/src/sage/games/hexad.py @@ -711,4 +711,3 @@ def blackjack_move(self, L0): return str(x) + ' --> ' + str(y) + ". The total went from " + str(total) + " to " + str(total - x + y) + "." print("This is a hexad. \n There is no winning move, so make a random legal move.") return L0 - diff --git a/src/sage/games/sudoku.py b/src/sage/games/sudoku.py index 3e2de73ef98..4731b9bc517 100644 --- a/src/sage/games/sudoku.py +++ b/src/sage/games/sudoku.py @@ -131,7 +131,7 @@ class Sudoku(SageObject): |4 9 1|8 5 6|7 2 3| +-----+-----+-----+ """ - def __init__(self, puzzle, verify_input = True): + def __init__(self, puzzle, verify_input=True): r""" Initialize a Sudoku puzzle, determine its size, sanity-check the inputs. @@ -177,7 +177,7 @@ def __init__(self, puzzle, verify_input = True): self.puzzle = tuple(puzzle) elif is_Matrix(puzzle): puzzle_size = puzzle.ncols() - if verify_input and not(puzzle.is_square()): + if verify_input and not puzzle.is_square(): raise ValueError('Sudoku puzzle must be a square matrix') self.puzzle = tuple([int(x) for x in puzzle.list()]) elif isinstance(puzzle, str): @@ -312,7 +312,7 @@ def _matrix_(self, R=None): ValueError: Sudoku puzzles only convert to matrices over Integer Ring, not Rational Field """ from sage.rings.integer_ring import ZZ, IntegerRing_class - if R and not(isinstance(R, IntegerRing_class)): + if R and not isinstance(R, IntegerRing_class): raise ValueError('Sudoku puzzles only convert to matrices over %s, not %s' % (ZZ, R)) return self.to_matrix() @@ -880,11 +880,11 @@ def make_row(row, col, entry): # These rows will represent the original hints, plus a single entry in every other location, # consistent with the requirements imposed on a solution to a Sudoku puzzle for cover in DLXCPP(ones): - if not(count_only): - solution = [0]*nfour + if not count_only: + solution = [0] * nfour for r in cover: row, col, entry = rowinfo[r] - solution[row*nsquare+col] = entry+1 + solution[row * nsquare + col] = entry + 1 yield solution else: yield None diff --git a/src/sage/geometry/cone.py b/src/sage/geometry/cone.py index d9d57b3b12a..e3d84cac696 100644 --- a/src/sage/geometry/cone.py +++ b/src/sage/geometry/cone.py @@ -2602,7 +2602,7 @@ def ConeFace(atoms, facets): self._face_lattice = lattice_from_incidences( atom_to_facets, facet_to_atoms, ConeFace, - key = id(self)) + key=id(self)) else: # Get face lattice as a sublattice of the ambient one allowed_indices = frozenset(self._ambient_ray_indices) @@ -2642,7 +2642,7 @@ def ConeFace(atoms, facets): L.add_edge(face_to_index[face], next_index) D = {i:f for i,f in enumerate(faces)} L.relabel(D) - self._face_lattice = FinitePoset(L, faces, key = id(self)) + self._face_lattice = FinitePoset(L, faces, key=id(self)) return self._face_lattice # Internally we use this name for a uniform behaviour of cones and fans. @@ -3533,21 +3533,15 @@ def an_affine_basis(self): sage: quadrant = Cone([(1,0), (0,1)]) sage: quadrant.an_affine_basis() - Traceback (most recent call last): - ... - NotImplementedError: this function is not implemented for unbounded polyhedra + [(0, 0), (1, 0), (0, 1)] sage: ray = Cone([(1, 1)]) sage: ray.an_affine_basis() - Traceback (most recent call last): - ... - NotImplementedError: this function is not implemented for unbounded polyhedra + [(0, 0), (1, 1)] sage: line = Cone([(1,0), (-1,0)]) sage: line.an_affine_basis() - Traceback (most recent call last): - ... - NotImplementedError: this function is not implemented for unbounded polyhedra + [(1, 0), (0, 0)] """ - return self.polyhedron().an_affine_basis() + return [vector(v) for v in self.polyhedron().an_affine_basis()] @cached_method def strict_quotient(self): diff --git a/src/sage/geometry/cone_catalog.py b/src/sage/geometry/cone_catalog.py index 43770fddccb..a3d1ffa670f 100644 --- a/src/sage/geometry/cone_catalog.py +++ b/src/sage/geometry/cone_catalog.py @@ -177,7 +177,7 @@ def nonnegative_orthant(ambient_dim=None, lattice=None): OUTPUT: - A :class:`.ConvexRationalPolyhedralCone` living in ``lattice`` + A :class:`~sage.geometry.cone.ConvexRationalPolyhedralCone` living in ``lattice`` and having ``ambient_dim`` standard basis vectors as its generators. Each generating ray has the integer ring as its base ring. @@ -287,7 +287,7 @@ def rearrangement(p, ambient_dim=None, lattice=None): OUTPUT: - A :class:`.ConvexRationalPolyhedralCone` representing the + A :class:`~sage.geometry.cone.ConvexRationalPolyhedralCone` representing the rearrangement cone of order ``p`` living in ``lattice``, with ambient dimension ``ambient_dim``. Each generating ray has the integer ring as its base ring. @@ -506,7 +506,7 @@ def schur(ambient_dim=None, lattice=None): OUTPUT: - A :class:`.ConvexRationalPolyhedralCone` representing the Schur + A :class:`~sage.geometry.cone.ConvexRationalPolyhedralCone` representing the Schur cone living in ``lattice``, with ambient dimension ``ambient_dim``. Each generating ray has the integer ring as its base ring. @@ -635,7 +635,7 @@ def trivial(ambient_dim=None, lattice=None): OUTPUT: - A :class:`.ConvexRationalPolyhedralCone` representing the + A :class:`~sage.geometry.cone.ConvexRationalPolyhedralCone` representing the trivial cone with no nonzero generators living in ``lattice``, with ambient dimension ``ambient_dim``. diff --git a/src/sage/geometry/convex_set.py b/src/sage/geometry/convex_set.py index 604df756af0..65249cd2a4d 100644 --- a/src/sage/geometry/convex_set.py +++ b/src/sage/geometry/convex_set.py @@ -622,6 +622,24 @@ def relative_interior(self): return self raise NotImplementedError + @cached_method + def representative_point(self): + """ + Return a "generic" point of ``self``. + + OUTPUT: + + A point in the relative interior of ``self`` as a coordinate vector. + + EXAMPLES:: + + sage: C = Cone([[1, 2, 0], [2, 1, 0]]) + sage: C.representative_point() + (1, 1, 0) + """ + affine_basis = self.an_affine_basis() + return sum(affine_basis) / len(affine_basis) + def _test_convex_set(self, tester=None, **options): """ Run some tests on the methods of :class:`ConvexSet_base`. @@ -746,7 +764,6 @@ def some_elements(self): raise NotImplementedError return list(self._some_elements_()) - @abstract_method(optional=True) def _some_elements_(self): r""" Generate some points of ``self``. @@ -757,11 +774,12 @@ def _some_elements_(self): sage: from sage.geometry.convex_set import ConvexSet_base sage: C = ConvexSet_base() - sage: C._some_elements_(C) + sage: list(C._some_elements_()) Traceback (most recent call last): ... TypeError: 'NotImplementedType' object is not callable """ + yield self.representative_point() @abstract_method(optional=True) def cartesian_product(self, other): @@ -855,10 +873,14 @@ def _test_contains(self, tester=None, **options): 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)) + try: + from sage.rings.qqbar import AA + except ImportError: + pass + else: + ext_space = self.ambient_vector_space(AA) + ext_space_point = ext_space(space_point) + tester.assertEqual(contains_space_point, self.contains(ext_space_point)) try: from sage.symbolic.ring import SR symbolic_space = self.ambient_vector_space(SR) diff --git a/src/sage/geometry/fan.py b/src/sage/geometry/fan.py index af3935741b8..26d814fb437 100644 --- a/src/sage/geometry/fan.py +++ b/src/sage/geometry/fan.py @@ -646,7 +646,8 @@ def result(): rays = new_rays else: rays = tuple(sorted(ray_set)) - cones = (tuple(sorted(rays.index(ray) for ray in cone.rays())) + ray_to_index = {ray: i for i, ray in enumerate(rays)} + cones = (tuple(sorted(ray_to_index[ray] for ray in cone.rays())) for cone in cones) return result() # Construct the fan from rays and "tuple cones" @@ -1925,7 +1926,8 @@ def _subdivide_stellar(self, new_rays, verbose): new_fan_rays = list(self.rays()) new_fan_rays.extend(ray for ray in new_rays if ray not in self.rays().set()) - cones = tuple(tuple(sorted(new_fan_rays.index(ray) for ray in cone)) + ray_to_index = {ray: i for i, ray in enumerate(new_fan_rays)} + cones = tuple(tuple(sorted(ray_to_index[ray] for ray in cone)) for cone in cones) fan = Fan(cones, new_fan_rays, check=False, normalize=False) return fan @@ -3184,10 +3186,10 @@ def primitive_collections(self): pass def is_not_facet(I): - return all(not(I <= f) for f in facets) + return all(not (I <= f) for f in facets) def is_in_SR(I): - return all(not(I >= sr) for sr in SR) + return all(not (I >= sr) for sr in SR) # Generators of SR are index sets I = {i1, ..., ik} # called "primitive collections" such that diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py index 6168e033670..45c8efb11ed 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py @@ -1474,7 +1474,7 @@ def intersection(self, other): UHP = self.model() # Both geodesic need to be UHP geodesics for this to work - if(other.model() != UHP): + if other.model() != UHP: other = other.to_model(UHP) # Get endpoints and ideal endpoints i_start_1, i_end_1 = sorted(self.ideal_endpoints(), key=str) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py index f6365aa90c5..9e3e8d3fc88 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py @@ -324,7 +324,7 @@ def matrix(self): """ return self._matrix - def inverse(self): + def __invert__(self): r""" Return the inverse of the isometry ``self``. @@ -332,13 +332,11 @@ def inverse(self): sage: UHP = HyperbolicPlane().UHP() sage: A = UHP.get_isometry(matrix(2,[4,1,3,2])) - sage: B = A.inverse() + sage: B = A.inverse() # indirect doctest sage: A*B == UHP.get_isometry(identity_matrix(2)) True """ - return self.__class__(self.domain(), self.matrix().inverse()) - - __invert__ = inverse + return self.__class__(self.domain(), self.matrix().__invert__()) def is_identity(self): """ diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py index 0d3446d0efa..6ae2e260aac 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py @@ -299,8 +299,8 @@ def _richcmp_(self, other, op): sage: p1 == p2 True """ - if not(isinstance(other, HyperbolicPoint) - or self.parent() is other.parent()): + if not (isinstance(other, HyperbolicPoint) + or self.parent() is other.parent()): return op == op_NE # bool is required to convert symbolic (in)equalities return bool(richcmp(self._coordinates, other._coordinates, op)) diff --git a/src/sage/geometry/hyperplane_arrangement/library.py b/src/sage/geometry/hyperplane_arrangement/library.py index 0c5f67b546e..cfa2fbeb083 100644 --- a/src/sage/geometry/hyperplane_arrangement/library.py +++ b/src/sage/geometry/hyperplane_arrangement/library.py @@ -152,9 +152,10 @@ def bigraphical(self, G, A=None, K=QQ, names=None): H = make_parent(K, n, names) x = H.gens() hyperplanes = [] - for e in G.edges(sort=True): - i = G.vertices(sort=True).index(e[0]) - j = G.vertices(sort=True).index(e[1]) + vertex_to_int = {u: i for i, u in enumerate(G)} + for u, v in G.edge_iterator(labels=False, sort_vertices=False): + i = vertex_to_int[u] + j = vertex_to_int[v] hyperplanes.append( x[i] - x[j] - A[i][j]) hyperplanes.append(-x[i] + x[j] - A[j][i]) return H(*hyperplanes) @@ -264,9 +265,10 @@ def G_semiorder(self, G, K=QQ, names=None): H = make_parent(K, n, names) x = H.gens() hyperplanes = [] - for e in G.edges(sort=True): - i = G.vertices(sort=True).index(e[0]) - j = G.vertices(sort=True).index(e[1]) + vertex_to_int = {u: i for i, u in enumerate(G.vertices(sort=True))} + for u, v in G.edge_iterator(labels=False): + i = vertex_to_int[u] + j = vertex_to_int[v] hyperplanes.append(x[i] - x[j] - 1) hyperplanes.append(x[i] - x[j] + 1) return H(*hyperplanes) @@ -303,9 +305,10 @@ def G_Shi(self, G, K=QQ, names=None): H = make_parent(K, n, names) x = H.gens() hyperplanes = [] - for e in G.edges(sort=True): - i = G.vertices(sort=True).index(e[0]) - j = G.vertices(sort=True).index(e[1]) + vertex_to_int = {u: i for i, u in enumerate(G.vertices(sort=True))} + for u, v in G.edge_iterator(labels=False): + i = vertex_to_int[u] + j = vertex_to_int[v] hyperplanes.append(x[i] - x[j]) hyperplanes.append(x[i] - x[j] - 1) return H(*hyperplanes) @@ -351,9 +354,10 @@ def graphical(self, G, K=QQ, names=None): H = make_parent(K, n, names) x = H.gens() hyperplanes = [] - for e in G.edges(sort=True): - i = G.vertices(sort=True).index(e[0]) - j = G.vertices(sort=True).index(e[1]) + vertex_to_int = {u: i for i, u in enumerate(G.vertices(sort=True))} + for u, v in G.edge_iterator(labels=False): + i = vertex_to_int[u] + j = vertex_to_int[v] hyperplanes.append(x[i] - x[j]) A = H(*hyperplanes) charpoly = G.chromatic_polynomial() diff --git a/src/sage/geometry/hyperplane_arrangement/plot.py b/src/sage/geometry/hyperplane_arrangement/plot.py index 02028e13297..bf2455a0ceb 100644 --- a/src/sage/geometry/hyperplane_arrangement/plot.py +++ b/src/sage/geometry/hyperplane_arrangement/plot.py @@ -406,13 +406,13 @@ def plot_hyperplane(hyperplane, **kwds): ranges_set = True ranges = kwds.pop('ranges') else: - ranges_set = False # give default values below + ranges_set = False # give default values below # the extra keywords have now been handled # now create the plot - if hyperplane.dimension() == 0: # a point on a line + if hyperplane.dimension() == 0: # a point on a line x, = hyperplane.A() d = hyperplane.b() - p = point((d/x,0), size = pt_size, **kwds) + p = point((d/x,0), size=pt_size, **kwds) if has_hyp_label: if not has_offset: label_offset = 0.1 diff --git a/src/sage/geometry/lattice_polytope.py b/src/sage/geometry/lattice_polytope.py index c7c53a65045..ff85c1724d8 100644 --- a/src/sage/geometry/lattice_polytope.py +++ b/src/sage/geometry/lattice_polytope.py @@ -1566,7 +1566,8 @@ def ambient_point_indices(self): if self._ambient is self: return tuple(range(self.npoints())) points = self._ambient.points() - return tuple(points.index(p) for p in self.points()) + point_to_index = {p: i for i, p in enumerate(points)} + return tuple(point_to_index[p] for p in self.points()) @cached_method def ambient_ordered_point_indices(self): @@ -1600,7 +1601,8 @@ def ambient_ordered_point_indices(self): if self._ambient is self: return tuple(range(self.npoints())) points = self._ambient.points() - return tuple(points.index(p) for p in sorted(self.points())) + point_to_index = {p: i for i, p in enumerate(points)} + return tuple(point_to_index[p] for p in sorted(self.points())) def ambient_vertex_indices(self): r""" @@ -2020,7 +2022,7 @@ def LPFace(vertices, facets): ambient_facet_indices=facets) return lattice_from_incidences( - vertex_to_facets, facet_to_vertices, LPFace, key = id(self)) + vertex_to_facets, facet_to_vertices, LPFace, key=id(self)) else: # Get face lattice as a sublattice of the ambient one allowed_indices = frozenset(self._ambient_vertex_indices) @@ -2059,7 +2061,7 @@ def LPFace(vertices, facets): L.add_edge(face_to_index[face], next_index) D = {i:f for i,f in enumerate(faces)} L.relabel(D) - return FinitePoset(L, faces, key = id(self)) + return FinitePoset(L, faces, key=id(self)) def faces(self, dim=None, codim=None): r""" @@ -3614,8 +3616,9 @@ def plot3d(self, elif dim == 3: if facet_colors is None: facet_colors = [facet_color] * self.nfacets() + vertex_to_index = {v: i for i, v in enumerate(self.vertices())} for f, c in zip(self.facets(), facet_colors): - pplot += IndexFaceSet([[self.vertices().index(v) for v in f.vertices(f.traverse_boundary())]], + pplot += IndexFaceSet([[vertex_to_index[v] for v in f.vertices(f.traverse_boundary())]], vertices, opacity=facet_opacity, rgbcolor=c) if show_edges: if dim == 1: @@ -4081,7 +4084,8 @@ def traverse_boundary(self): if next == l[-2]: next = prev l.append(next) - return [self.vertices().index(v.vertex(0)) for v in l] + vertex_to_index = {v: i for i, v in enumerate(self.vertices())} + return [vertex_to_index[v.vertex(0)] for v in l] def vertex(self, i): r""" diff --git a/src/sage/geometry/polyhedral_complex.py b/src/sage/geometry/polyhedral_complex.py index 47b23f0f29a..913a788b5df 100644 --- a/src/sage/geometry/polyhedral_complex.py +++ b/src/sage/geometry/polyhedral_complex.py @@ -723,17 +723,81 @@ def plot(self, **kwds): """ Return a plot of the polyhedral complex, if it is of dim at most 3. + INPUT: + + - ``explosion_factor`` -- (default: 0) if positive, separate the cells of + the complex by extra space. In this case, the following keyword arguments + can be passed to :func:`exploded_plot`: + + - ``center`` -- (default: ``None``, denoting the origin) the center of explosion + - ``sticky_vertices`` -- (default: ``False``) boolean or dict. + Whether to draw line segments between shared vertices of the given polyhedra. + A dict gives options for :func:`sage.plot.line`. + - ``sticky_center`` -- (default: ``True``) boolean or dict. When ``center`` is + a vertex of some of the polyhedra, whether to draw line segments connecting the + ``center`` to the shifted copies of these vertices. + A dict gives options for :func:`sage.plot.line`. + + - ``color`` -- (default: ``None``) if ``"rainbow"``, assign a different color + to every maximal cell; otherwise, passed on to + :meth:`~sage.geometry.polyhedron.base.Polyhedron_base.plot`. + + - other keyword arguments are passed on to + :meth:`~sage.geometry.polyhedron.base.Polyhedron_base.plot`. + EXAMPLES:: sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]) sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)]) - sage: pc = PolyhedralComplex([p1, p2]) - sage: pc.plot() # optional - sage.plot - Graphics object consisting of 10 graphics primitives + sage: p3 = Polyhedron(vertices=[(0, 0), (0, 2), (-1, 1)]) + sage: pc1 = PolyhedralComplex([p1, p2, p3, -p1, -p2, -p3]) + sage: bb = dict(xmin=-2, xmax=2, ymin=-3, ymax=3, axes=False) + sage: g0 = pc1.plot(color='rainbow', **bb) # optional - sage.plot + sage: g1 = pc1.plot(explosion_factor=0.5, **bb) # optional - sage.plot + sage: g2 = pc1.plot(explosion_factor=1, color='rainbow', alpha=0.5, **bb) # optional - sage.plot + sage: graphics_array([g0, g1, g2]).show(axes=False) # not tested + + sage: pc2 = PolyhedralComplex([polytopes.hypercube(3)]) + sage: pc3 = pc2.subdivide(new_vertices=[(0, 0, 0)]) + sage: g3 = pc3.plot(explosion_factor=1, color='rainbow', # optional - sage.plot + ....: alpha=0.5, axes=False, online=True) + sage: pc4 = pc2.subdivide(make_simplicial=True) + sage: g4 = pc4.plot(explosion_factor=1, center=(1, -1, 1), fill='blue', # optional - sage.plot + ....: wireframe='white', point={'color':'red', 'size':10}, + ....: alpha=0.6, online=True) + sage: pc5 = PolyhedralComplex([ + ....: Polyhedron(rays=[[1,0,0], [0,1,0], [0,0,-1]]), + ....: Polyhedron(rays=[[1,0,0], [0,-1,0], [0,0,-1]]), + ....: Polyhedron(rays=[[1,0,0], [0,-1,0], [0,0,1]]), + ....: Polyhedron(rays=[[-1,0,0], [0,-1,0], [0,0,-1]]), + ....: Polyhedron(rays=[[-1,0,0], [0,-1,0], [0,0,1]]), + ....: Polyhedron(rays=[[-1,0,0], [0,1,0], [0,0,-1]]), + ....: Polyhedron(rays=[[-1,0,0], [0,1,0], [0,0,1]])]) + sage: g5 = pc5.plot(explosion_factor=0.3, color='rainbow', alpha=0.8, # optional - sage.plot + ....: point={'size': 20}, axes=False, online=True) + """ if self.dimension() > 3: raise ValueError("cannot plot in high dimension") - return sum(cell.plot(**kwds) for cell in self.maximal_cell_iterator()) + if kwds.get('explosion_factor', 0): + return exploded_plot(self.maximal_cell_iterator(), **kwds) + + from sage.plot.colors import rainbow + from sage.plot.graphics import Graphics + + color = kwds.get('color') + polyhedra = self.maximal_cell_iterator() + if color == 'rainbow': + polyhedra = list(polyhedra) + cell_colors_dict = dict(zip(polyhedra, + rainbow(len(polyhedra)))) + g = Graphics() + for cell in polyhedra: + options = copy(kwds) + if color == 'rainbow': + options['color'] = cell_colors_dict[cell] + g += cell.plot(**options) + return g def is_pure(self): """ @@ -2433,3 +2497,110 @@ def cells_list_to_cells_dict(cells_list): else: cells_dict[d] = set([cell]) return cells_dict + + +def exploded_plot(polyhedra, *, + center=None, explosion_factor=1, sticky_vertices=False, + sticky_center=True, point=None, **kwds): + r""" + Return a plot of several ``polyhedra`` in one figure with extra space between them. + + INPUT: + + - ``polyhedra`` -- an iterable of :class:`~sage.geometry.polyhedron.base.Polyhedron_base` objects + + - ``center`` -- (default: ``None``, denoting the origin) the center of explosion + + - ``explosion_factor`` -- (default: 1) a nonnegative number; translate polyhedra by this + factor of the distance from ``center`` to their center + + - ``sticky_vertices`` -- (default: ``False``) boolean or dict. Whether to draw line segments between shared + vertices of the given polyhedra. A dict gives options for :func:`sage.plot.line`. + + - ``sticky_center`` -- (default: ``True``) boolean or dict. When ``center`` is a vertex of some + of the polyhedra, whether to draw line segments connecting the ``center`` to the shifted copies + of these vertices. A dict gives options for :func:`sage.plot.line`. + + - ``color`` -- (default: ``None``) if ``"rainbow"``, assign a different color to every maximal cell and + every vertex; otherwise, passed on to :meth:`~sage.geometry.polyhedron.base.Polyhedron_base.plot`. + + - other keyword arguments are passed on to :meth:`~sage.geometry.polyhedron.base.Polyhedron_base.plot`. + + EXAMPLES:: + + sage: from sage.geometry.polyhedral_complex import exploded_plot + sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]) + sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)]) + sage: p3 = Polyhedron(vertices=[(0, 0), (1, 1), (2, 0)]) + sage: exploded_plot([p1, p2, p3]) # optional - sage.plot + Graphics object consisting of 20 graphics primitives + sage: exploded_plot([p1, p2, p3], center=(1, 1)) # optional - sage.plot + Graphics object consisting of 19 graphics primitives + sage: exploded_plot([p1, p2, p3], center=(1, 1), sticky_vertices=True) # optional - sage.plot + Graphics object consisting of 23 graphics primitives + """ + from sage.plot.colors import rainbow + from sage.plot.graphics import Graphics + from sage.plot.line import line + from sage.plot.point import point as plot_point + import itertools + + polyhedra = list(polyhedra) + g = Graphics() + if not polyhedra: + return g + dim = polyhedra[0].ambient_dimension() + if center is None: + from sage.rings.rational_field import QQ + center = vector(QQ, dim) + else: + center = vector(center) + translations = [explosion_factor * ((p.center() + + sum(r.vector() for r in p.rays())) + - center) + for p in polyhedra] + vertex_translations_dict = {} + for P, t in zip(polyhedra, translations): + for v in P.vertices(): + v = v.vector() + v.set_immutable() + vertex_translations_dict[v] = vertex_translations_dict.get(v, []) + vertex_translations_dict[v].append(v + t) + + color = kwds.get('color') + if color == 'rainbow': + cell_colors_dict = dict(zip(polyhedra, + rainbow(len(polyhedra)))) + for p, t in zip(polyhedra, translations): + options = copy(kwds) + if color == 'rainbow': + options['color'] = cell_colors_dict[p] + g += (p + t).plot(point=False, **options) + + if sticky_vertices or sticky_center: + if sticky_vertices is True: + sticky_vertices = dict(color='gray') + if sticky_center is True: + sticky_center = dict(color='gray') + for vertex, vertex_translations in vertex_translations_dict.items(): + if vertex == center: + if sticky_center: + for vt in vertex_translations: + g += line((center, vt), **sticky_center) + else: + if sticky_vertices: + for vt1, vt2 in itertools.combinations(vertex_translations, 2): + g += line((vt1, vt2), **sticky_vertices) + if point is None: + # default from sage.geometry.polyhedron.plot + point = dict(size=10) + if point is not False: + if color == 'rainbow': + vertex_colors_dict = dict(zip(vertex_translations_dict.keys(), + rainbow(len(vertex_translations_dict.keys())))) + for vertex, vertex_translations in vertex_translations_dict.items(): + options = copy(point) + if color == 'rainbow': + options['color'] = vertex_colors_dict[vertex] + g += plot_point(vertex_translations, **options) + return g diff --git a/src/sage/geometry/polyhedron/backend_normaliz.py b/src/sage/geometry/polyhedron/backend_normaliz.py index 86b89632a51..f38498aa597 100644 --- a/src/sage/geometry/polyhedron/backend_normaliz.py +++ b/src/sage/geometry/polyhedron/backend_normaliz.py @@ -30,8 +30,8 @@ lazy_import('PyNormaliz', ['NmzResult', 'NmzCompute', 'NmzCone', 'NmzConeCopy'], feature=sage.features.normaliz.PyNormaliz()) -from sage.rings.all import ZZ, QQ, QQbar -from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ from sage.arith.functions import LCM_list from sage.misc.functional import denominator from sage.matrix.constructor import vector @@ -1082,10 +1082,11 @@ def _number_field_triple(normaliz_field): sage: Pn._number_field_triple(QuadraticField(5)) # optional - sage.rings.number_field ['a^2 - 5', 'a', '[2.236067977499789 +/- 8.06e-16]'] """ - from sage.rings.real_arb import RealBallField R = normaliz_field if R is QQ: return None + from sage.rings.real_arb import RealBallField + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing emb = RealBallField(53)(R.gen(0)) gen = 'a' R_a = PolynomialRing(QQ, gen) @@ -2336,8 +2337,9 @@ class functions. ((t^4 + 3*t^3 + 8*t^2 + 3*t + 1)/(t + 1), (3*t^3 + 2*t^2 + 3*t)/(t + 1)) """ from sage.groups.conjugacy_classes import ConjugacyClassGAP - from sage.rings.all import CyclotomicField - from sage.matrix.all import MatrixSpace + from sage.rings.number_field.number_field import CyclotomicField + from sage.rings.qqbar import QQbar + from sage.matrix.matrix_space import MatrixSpace from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.matrix.special import identity_matrix # Setting the group @@ -2475,6 +2477,8 @@ def _Hstar_as_rat_fct(self, initial_Hstar): [ 1 1 -1 -1 1] [ 2 0 0 0 -2] """ + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + from sage.rings.qqbar import QQbar chi_vars = ','.join('chi_{}'.format(i) for i in range(len(initial_Hstar))) Chi_ring = PolynomialRing(QQbar, chi_vars) virtual_ring = PolynomialRing(Chi_ring, initial_Hstar.base_ring().gens()) diff --git a/src/sage/geometry/polyhedron/backend_polymake.py b/src/sage/geometry/polyhedron/backend_polymake.py index 97dfdc2c63e..36ccc33c39f 100644 --- a/src/sage/geometry/polyhedron/backend_polymake.py +++ b/src/sage/geometry/polyhedron/backend_polymake.py @@ -56,48 +56,48 @@ class Polyhedron_polymake(Polyhedron_base): EXAMPLES:: - sage: p = Polyhedron(vertices=[(0,0),(1,0),(0,1)], rays=[(1,1)], # optional - polymake + sage: p = Polyhedron(vertices=[(0,0),(1,0),(0,1)], rays=[(1,1)], # optional - jupymake ....: lines=[], backend='polymake') - sage: TestSuite(p).run() # optional - polymake + sage: TestSuite(p).run() # optional - jupymake A lower-dimensional affine cone; we test that there are no mysterious inequalities coming in from the homogenization:: - sage: P = Polyhedron(vertices=[(1, 1)], rays=[(0, 1)], # optional - polymake + sage: P = Polyhedron(vertices=[(1, 1)], rays=[(0, 1)], # optional - jupymake ....: backend='polymake') - sage: P.n_inequalities() # optional - polymake + sage: P.n_inequalities() # optional - jupymake 1 - sage: P.equations() # optional - polymake + sage: P.equations() # optional - jupymake (An equation (1, 0) x - 1 == 0,) The empty polyhedron:: - sage: Polyhedron(eqns=[[1, 0, 0]], backend='polymake') # optional - polymake + sage: Polyhedron(eqns=[[1, 0, 0]], backend='polymake') # optional - jupymake The empty polyhedron in QQ^2 It can also be obtained differently:: - sage: P=Polyhedron(ieqs=[[-2, 1, 1], [-3, -1, -1], [-4, 1, -2]], # optional - polymake + sage: P=Polyhedron(ieqs=[[-2, 1, 1], [-3, -1, -1], [-4, 1, -2]], # optional - jupymake ....: backend='polymake') - sage: P # optional - polymake + sage: P # optional - jupymake The empty polyhedron in QQ^2 - sage: P.Vrepresentation() # optional - polymake + sage: P.Vrepresentation() # optional - jupymake () - sage: P.Hrepresentation() # optional - polymake + sage: P.Hrepresentation() # optional - jupymake (An equation -1 == 0,) The full polyhedron:: - sage: Polyhedron(eqns=[[0, 0, 0]], backend='polymake') # optional - polymake + sage: Polyhedron(eqns=[[0, 0, 0]], backend='polymake') # optional - jupymake A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 2 lines - sage: Polyhedron(ieqs=[[0, 0, 0]], backend='polymake') # optional - polymake + sage: Polyhedron(ieqs=[[0, 0, 0]], backend='polymake') # optional - jupymake A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 2 lines Quadratic fields work:: sage: V = polytopes.dodecahedron().vertices_list() # optional - sage.rings.number_field - sage: Polyhedron(vertices=V, backend='polymake') # optional - polymake # optional - sage.rings.number_field + sage: Polyhedron(vertices=V, backend='polymake') # optional - jupymake # optional - sage.rings.number_field A 3-dimensional polyhedron in (Number Field in sqrt5 with defining polynomial x^2 - 5 with sqrt5 = 2.236067977499790?)^3 @@ -107,31 +107,31 @@ class Polyhedron_polymake(Polyhedron_base): Tests copied from various methods in :mod:`sage.geometry.polyhedron.base`:: - sage: p = Polyhedron(vertices = [[1,0,0], [0,1,0], [0,0,1]], # optional - polymake + sage: p = Polyhedron(vertices = [[1,0,0], [0,1,0], [0,0,1]], # optional - jupymake ....: backend='polymake') - sage: p.n_equations() # optional - polymake + sage: p.n_equations() # optional - jupymake 1 - sage: p.n_inequalities() # optional - polymake + sage: p.n_inequalities() # optional - jupymake 3 - sage: p = Polyhedron(vertices = [[t,t^2,t^3] for t in range(6)], # optional - polymake + sage: p = Polyhedron(vertices = [[t,t^2,t^3] for t in range(6)], # optional - jupymake ....: backend='polymake') - sage: p.n_facets() # optional - polymake + sage: p.n_facets() # optional - jupymake 8 - sage: p = Polyhedron(vertices = [[1,0],[0,1],[1,1]], rays=[[1,1]], # optional - polymake + sage: p = Polyhedron(vertices = [[1,0],[0,1],[1,1]], rays=[[1,1]], # optional - jupymake ....: backend='polymake') - sage: p.n_vertices() # optional - polymake + sage: p.n_vertices() # optional - jupymake 2 - sage: p = Polyhedron(vertices = [[1,0],[0,1]], rays=[[1,1]], # optional - polymake + sage: p = Polyhedron(vertices = [[1,0],[0,1]], rays=[[1,1]], # optional - jupymake ....: backend='polymake') - sage: p.n_rays() # optional - polymake + sage: p.n_rays() # optional - jupymake 1 - sage: p = Polyhedron(vertices = [[0,0]], rays=[[0,1],[0,-1]], # optional - polymake + sage: p = Polyhedron(vertices = [[0,0]], rays=[[0,1],[0,-1]], # optional - jupymake ....: backend='polymake') - sage: p.n_lines() # optional - polymake + sage: p.n_lines() # optional - jupymake 1 """ @@ -150,10 +150,10 @@ def _is_zero(self, x): EXAMPLES:: - sage: p = Polyhedron([(0,0)], backend='polymake') # optional - polymake - sage: p._is_zero(0) # optional - polymake + sage: p = Polyhedron([(0,0)], backend='polymake') # optional - jupymake + sage: p._is_zero(0) # optional - jupymake True - sage: p._is_zero(1/100000) # optional - polymake + sage: p._is_zero(1/100000) # optional - jupymake False """ return x == 0 @@ -172,10 +172,10 @@ def _is_nonneg(self, x): EXAMPLES:: - sage: p = Polyhedron([(0,0)], backend='polymake') # optional - polymake - sage: p._is_nonneg(1) # optional - polymake + sage: p = Polyhedron([(0,0)], backend='polymake') # optional - jupymake + sage: p._is_nonneg(1) # optional - jupymake True - sage: p._is_nonneg(-1/100000) # optional - polymake + sage: p._is_nonneg(-1/100000) # optional - jupymake False """ return x >= 0 @@ -194,10 +194,10 @@ def _is_positive(self, x): EXAMPLES:: - sage: p = Polyhedron([(0,0)], backend='polymake') # optional - polymake - sage: p._is_positive(1) # optional - polymake + sage: p = Polyhedron([(0,0)], backend='polymake') # optional - jupymake + sage: p._is_positive(1) # optional - jupymake True - sage: p._is_positive(0) # optional - polymake + sage: p._is_positive(0) # optional - jupymake False """ return x > 0 @@ -211,29 +211,29 @@ def __init__(self, parent, Vrep, Hrep, polymake_polytope=None, **kwds): TESTS: - sage: p = Polyhedron(backend='polymake') # optional - polymake - sage: TestSuite(p).run() # optional - polymake - sage: p = Polyhedron(vertices=[(1, 1)], rays=[(0, 1)], # optional - polymake + sage: p = Polyhedron(backend='polymake') # optional - jupymake + sage: TestSuite(p).run() # optional - jupymake + sage: p = Polyhedron(vertices=[(1, 1)], rays=[(0, 1)], # optional - jupymake ....: backend='polymake') - sage: TestSuite(p).run() # optional - polymake + sage: TestSuite(p).run() # optional - jupymake We skip the Lawrence test because it involves numerically unstable floating point arithmetic:: - sage: p = Polyhedron(vertices=[(-1,-1), (1,0), (1,1), (0,1)], # optional - polymake + sage: p = Polyhedron(vertices=[(-1,-1), (1,0), (1,1), (0,1)], # optional - jupymake ....: backend='polymake') - sage: TestSuite(p).run(skip='_test_lawrence') # optional - polymake + sage: TestSuite(p).run(skip='_test_lawrence') # optional - jupymake :: - sage: p = Polyhedron(rays=[[1,1]], backend='polymake') # optional - polymake - sage: TestSuite(p).run() # optional - polymake - sage: p = Polyhedron(rays=[[1]], backend='polymake') # optional - polymake - sage: TestSuite(p).run() # optional - polymake - sage: p = Polyhedron(rays=[[1,1,1]], lines=[[1,0,0]], backend='polymake') # optional - polymake - sage: TestSuite(p).run() # optional - polymake - sage: p = Polyhedron(vertices=[[]], backend='polymake') # optional - polymake - sage: TestSuite(p).run() # optional - polymake + sage: p = Polyhedron(rays=[[1,1]], backend='polymake') # optional - jupymake + sage: TestSuite(p).run() # optional - jupymake + sage: p = Polyhedron(rays=[[1]], backend='polymake') # optional - jupymake + sage: TestSuite(p).run() # optional - jupymake + sage: p = Polyhedron(rays=[[1,1,1]], lines=[[1,0,0]], backend='polymake') # optional - jupymake + sage: TestSuite(p).run() # optional - jupymake + sage: p = Polyhedron(vertices=[[]], backend='polymake') # optional - jupymake + sage: TestSuite(p).run() # optional - jupymake """ if polymake_polytope is not None: if Hrep is not None or Vrep is not None: @@ -249,9 +249,9 @@ def _init_from_polymake_polytope(self, polymake_polytope): TESTS:: - sage: p = Polyhedron(backend='polymake') # optional - polymake - sage: from sage.geometry.polyhedron.backend_polymake import Polyhedron_polymake # optional - polymake - sage: Polyhedron_polymake._init_from_Hrepresentation(p, [], []) # indirect doctest # optional - polymake + sage: p = Polyhedron(backend='polymake') # optional - jupymake + sage: from sage.geometry.polyhedron.backend_polymake import Polyhedron_polymake # optional - jupymake + sage: Polyhedron_polymake._init_from_Hrepresentation(p, [], []) # indirect doctest # optional - jupymake """ self._polymake_polytope = polymake_polytope self._init_Vrepresentation_from_polymake() @@ -280,9 +280,9 @@ def _init_from_Vrepresentation(self, vertices, rays, lines, minimize=True, verbo EXAMPLES:: - sage: p = Polyhedron(backend='polymake') # optional - polymake - sage: from sage.geometry.polyhedron.backend_polymake import Polyhedron_polymake # optional - polymake - sage: Polyhedron_polymake._init_from_Vrepresentation(p, [], [], []) # optional - polymake + sage: p = Polyhedron(backend='polymake') # optional - jupymake + sage: from sage.geometry.polyhedron.backend_polymake import Polyhedron_polymake # optional - jupymake + sage: Polyhedron_polymake._init_from_Vrepresentation(p, [], [], []) # optional - jupymake """ from sage.interfaces.polymake import polymake data = self._polymake_Vrepresentation_data(vertices, rays, lines) @@ -347,9 +347,9 @@ def _init_from_Hrepresentation(self, ieqs, eqns, minimize=True, verbose=False): EXAMPLES:: - sage: p = Polyhedron(backend='polymake') # optional - polymake - sage: from sage.geometry.polyhedron.backend_polymake import Polyhedron_polymake # optional - polymake - sage: Polyhedron_polymake._init_from_Hrepresentation(p, [], []) # optional - polymake + sage: p = Polyhedron(backend='polymake') # optional - jupymake + sage: from sage.geometry.polyhedron.backend_polymake import Polyhedron_polymake # optional - jupymake + sage: Polyhedron_polymake._init_from_Hrepresentation(p, [], []) # optional - jupymake """ from sage.interfaces.polymake import polymake data = self._polymake_Hrepresentation_data(ieqs, eqns) @@ -420,9 +420,9 @@ def _init_from_Vrepresentation_and_Hrepresentation(self, Vrep, Hrep): sage: parent = Polyhedra_polymake(ZZ, 1, 'polymake') sage: Vrep = [[[0], [1]], [], []] sage: Hrep = [[[0, 1], [1, -1]], []] - sage: p = Polyhedron_polymake(parent, Vrep, Hrep, # indirect doctest # optional - polymake + sage: p = Polyhedron_polymake(parent, Vrep, Hrep, # indirect doctest # optional - jupymake ....: Vrep_minimal=True, Hrep_minimal=True) - sage: p # optional - polymake + sage: p # optional - jupymake A 1-dimensional polyhedron in ZZ^1 defined as the convex hull of 2 vertices """ Vrep = [list(x) for x in Vrep] @@ -482,13 +482,13 @@ def _init_Vrepresentation_from_polymake(self): EXAMPLES:: - sage: p = Polyhedron(vertices=[(0,1/2),(2,0),(4,5/6)], # indirect doctest # optional - polymake + sage: p = Polyhedron(vertices=[(0,1/2),(2,0),(4,5/6)], # indirect doctest # optional - jupymake ....: backend='polymake') - sage: set(p.Hrepresentation()) # optional - polymake + sage: set(p.Hrepresentation()) # optional - jupymake {An inequality (1, 4) x - 2 >= 0, An inequality (1, -12) x + 6 >= 0, An inequality (-5, 12) x + 10 >= 0} - sage: set(p.Vrepresentation()) # optional - polymake + sage: set(p.Vrepresentation()) # optional - jupymake {A vertex at (0, 1/2), A vertex at (2, 0), A vertex at (4, 5/6)} """ @@ -517,13 +517,13 @@ def _init_Hrepresentation_from_polymake(self): EXAMPLES:: - sage: p = Polyhedron(vertices=[(0,1/2), (2,0), (4,5/6)], # indirect doctest # optional - polymake + sage: p = Polyhedron(vertices=[(0,1/2), (2,0), (4,5/6)], # indirect doctest # optional - jupymake ....: backend='polymake') - sage: set(p.Hrepresentation()) # optional - polymake + sage: set(p.Hrepresentation()) # optional - jupymake {An inequality (1, 4) x - 2 >= 0, An inequality (1, -12) x + 6 >= 0, An inequality (-5, 12) x + 10 >= 0} - sage: set(p.Vrepresentation()) # optional - polymake + sage: set(p.Vrepresentation()) # optional - jupymake {A vertex at (0, 1/2), A vertex at (2, 0), A vertex at (4, 5/6)} """ @@ -553,9 +553,9 @@ def _from_polymake_polytope(cls, parent, polymake_polytope): sage: from sage.geometry.polyhedron.backend_polymake import Polyhedron_polymake sage: from sage.geometry.polyhedron.parent import Polyhedra sage: P=Polyhedron(ieqs=[[1, 0, 2], [3, 0, -2], [3, 2, -2]]) - sage: PP = polymake(P) # optional - polymake - sage: parent = Polyhedra(QQ, 2, backend='polymake') # optional - polymake - sage: Q=Polyhedron_polymake._from_polymake_polytope(parent, PP) # optional - polymake + sage: PP = polymake(P) # optional - jupymake + sage: parent = Polyhedra(QQ, 2, backend='polymake') # optional - jupymake + sage: Q=Polyhedron_polymake._from_polymake_polytope(parent, PP) # optional - jupymake """ if parent is None: from .parent import Polyhedra @@ -584,9 +584,9 @@ def _polymake_(self, polymake): EXAMPLES:: - sage: P = Polyhedron(vertices=[[1, 0], [0, 1], [0, 0]], backend='polymake') # optional - polymake - sage: PP = polymake(P) # optional - polymake - sage: PP.N_VERTICES # optional - polymake + sage: P = Polyhedron(vertices=[[1, 0], [0, 1], [0, 0]], backend='polymake') # optional - jupymake + sage: PP = polymake(P) # optional - jupymake + sage: PP.N_VERTICES # optional - jupymake 3 """ if self._polymake_polytope.parent() is polymake: @@ -600,8 +600,8 @@ def __getstate__(self): TESTS:: - sage: P = polytopes.simplex(backend='polymake') # optional - polymake - sage: P.__getstate__() # optional - polymake + sage: P = polytopes.simplex(backend='polymake') # optional - jupymake + sage: P.__getstate__() # optional - jupymake (Polyhedra in QQ^4, {'_Hrepresentation': (An inequality (0, -1, -1, -1) x + 1 >= 0, An inequality (0, 1, 0, 0) x + 0 >= 0, @@ -644,31 +644,31 @@ def __setstate__(self, state): Test that the obtained cone is valid:: - sage: from sage.geometry.polyhedron.backend_polymake import Polyhedron_polymake # optional - polymake - sage: P = polytopes.permutahedron(4, backend='polymake') # optional - polymake - sage: P1 = loads(dumps(P)) # optional - polymake - sage: P2 = Polyhedron_polymake(P1.parent(), None, None, P1._polymake_polytope) # optional - polymake - sage: P._test_polymake_pickling(other=P2) # optional - polymake + sage: from sage.geometry.polyhedron.backend_polymake import Polyhedron_polymake # optional - jupymake + sage: P = polytopes.permutahedron(4, backend='polymake') # optional - jupymake + sage: P1 = loads(dumps(P)) # optional - jupymake + sage: P2 = Polyhedron_polymake(P1.parent(), None, None, P1._polymake_polytope) # optional - jupymake + sage: P._test_polymake_pickling(other=P2) # optional - jupymake - sage: P = Polyhedron(lines=[[1,0], [0,1]], backend='polymake') # optional - polymake - sage: P1 = loads(dumps(P)) # optional - polymake - sage: P2 = Polyhedron_polymake(P1.parent(), None, None, P1._polymake_polytope) # optional - polymake - sage: P._test_polymake_pickling(other=P2) # optional - polymake + sage: P = Polyhedron(lines=[[1,0], [0,1]], backend='polymake') # optional - jupymake + sage: P1 = loads(dumps(P)) # optional - jupymake + sage: P2 = Polyhedron_polymake(P1.parent(), None, None, P1._polymake_polytope) # optional - jupymake + sage: P._test_polymake_pickling(other=P2) # optional - jupymake - sage: P = Polyhedron(backend='polymake') # optional - polymake - sage: P1 = loads(dumps(P)) # optional - polymake - sage: P._test_polymake_pickling(other=P1) # optional - polymake + sage: P = Polyhedron(backend='polymake') # optional - jupymake + sage: P1 = loads(dumps(P)) # optional - jupymake + sage: P._test_polymake_pickling(other=P1) # optional - jupymake - sage: P = polytopes.permutahedron(4, backend='polymake') * Polyhedron(lines=[[1]], backend='polymake') # optional - polymake - sage: P1 = loads(dumps(P)) # optional - polymake - sage: P2 = Polyhedron_polymake(P1.parent(), None, None, P1._polymake_polytope) # optional - polymake - sage: P._test_polymake_pickling(other=P2) # optional - polymake + sage: P = polytopes.permutahedron(4, backend='polymake') * Polyhedron(lines=[[1]], backend='polymake') # optional - jupymake + sage: P1 = loads(dumps(P)) # optional - jupymake + sage: P2 = Polyhedron_polymake(P1.parent(), None, None, P1._polymake_polytope) # optional - jupymake + sage: P._test_polymake_pickling(other=P2) # optional - jupymake - sage: print("Possible output"); P = polytopes.dodecahedron(backend='polymake') # optional - polymake # optional - sage.rings.number_field + sage: print("Possible output"); P = polytopes.dodecahedron(backend='polymake') # optional - jupymake # optional - sage.rings.number_field Possible output... - sage: P1 = loads(dumps(P)) # optional - polymake # optional - sage.rings.number_field - sage: P2 = Polyhedron_polymake(P1.parent(), None, None, P1._polymake_polytope) # optional - polymake # optional - sage.rings.number_field - sage: P._test_polymake_pickling(other=P2) # optional - polymake # optional - sage.rings.number_field + sage: P1 = loads(dumps(P)) # optional - jupymake # optional - sage.rings.number_field + sage: P2 = Polyhedron_polymake(P1.parent(), None, None, P1._polymake_polytope) # optional - jupymake # optional - sage.rings.number_field + sage: P._test_polymake_pickling(other=P2) # optional - jupymake # optional - sage.rings.number_field """ if "_pickle_vertices" in state[1]: vertices = state[1].pop("_pickle_vertices") @@ -703,7 +703,7 @@ def _test_polymake_pickling(self, tester=None, other=None, **options): TESTS:: - sage: polytopes.cross_polytope(3, backend='polymake')._test_polymake_pickling() # optional - polymake + sage: polytopes.cross_polytope(3, backend='polymake')._test_polymake_pickling() # optional - jupymake """ if tester is None: tester = self._tester(**options) @@ -739,10 +739,10 @@ class Polyhedron_QQ_polymake(Polyhedron_polymake, Polyhedron_QQ): EXAMPLES:: - sage: p = Polyhedron(vertices=[(0,0),(1,0),(0,1)], # optional - polymake + sage: p = Polyhedron(vertices=[(0,0),(1,0),(0,1)], # optional - jupymake ....: rays=[(1,1)], lines=[], ....: backend='polymake', base_ring=QQ) - sage: TestSuite(p).run() # optional - polymake + sage: TestSuite(p).run() # optional - jupymake """ pass @@ -759,9 +759,9 @@ class Polyhedron_ZZ_polymake(Polyhedron_polymake, Polyhedron_ZZ): EXAMPLES:: - sage: p = Polyhedron(vertices=[(0,0),(1,0),(0,1)], # optional - polymake + sage: p = Polyhedron(vertices=[(0,0),(1,0),(0,1)], # optional - jupymake ....: rays=[(1,1)], lines=[], ....: backend='polymake', base_ring=ZZ) - sage: TestSuite(p).run() # optional - polymake + sage: TestSuite(p).run() # optional - jupymake """ pass diff --git a/src/sage/geometry/polyhedron/base.py b/src/sage/geometry/polyhedron/base.py index 576e073a825..dbf8b4370b0 100644 --- a/src/sage/geometry/polyhedron/base.py +++ b/src/sage/geometry/polyhedron/base.py @@ -1,24 +1,5 @@ r""" -Base class for polyhedra - -This is split into several modules, organized as follows: - -- :mod:`~sage.geometry.polyhedron.base0` -- basic initialization etc. - -- :mod:`~sage.geometry.polyhedron.base1` -- methods defined by the - :class:`~sage.geometry.convex_set.ConvexSet_base` API - -- :mod:`~sage.geometry.polyhedron.base2` -- lattice points - -- :mod:`~sage.geometry.polyhedron.base3` -- combinatorial methods - -- :mod:`~sage.geometry.polyhedron.base4` -- methods relying on graphs - -- :mod:`~sage.geometry.polyhedron.base5` -- constructions of new polyhedra - -- :mod:`~sage.geometry.polyhedron.base6` -- plotting and affine projection - -- :mod:`~sage.geometry.polyhedron.base7` -- triangulation and volume +Base class for polyhedra: Miscellaneous methods """ # **************************************************************************** @@ -123,8 +104,8 @@ class Polyhedron_base(Polyhedron_base7): - ``Hrep_minimal`` (optional) -- see below - ``pref_rep`` -- string (default: ``None``); - one of``Vrep`` or ``Hrep`` to pick this in case the backend - cannot initialize from complete double description + one of ``Vrep`` or ``Hrep`` to pick this in case the backend + cannot initialize from complete double description - ``mutable`` -- ignored @@ -311,11 +292,11 @@ def to_linear_program(self, solver=None, return_variable=False, base_ring=None): for ineqn in self.inequalities_list(): b = -ineqn.pop(0) - p.add_constraint(p.sum([x[i]*ineqn[i] for i in range(len(ineqn))]) >= b) + p.add_constraint(p.sum([x[i] * ineqn[i] for i in range(len(ineqn))]) >= b) for eqn in self.equations_list(): b = -eqn.pop(0) - p.add_constraint(p.sum([x[i]*eqn[i] for i in range(len(eqn))]) == b) + p.add_constraint(p.sum([x[i] * eqn[i] for i in range(len(eqn))]) == b) if return_variable: return p, x @@ -402,7 +383,7 @@ def center(self): if self.dim() == 0: return self.vertices()[0].vector() else: - vertex_sum = vector(self.base_ring(), [0]*self.ambient_dim()) + vertex_sum = vector(self.base_ring(), [0] * self.ambient_dim()) for v in self.vertex_generator(): vertex_sum += v.vector() vertex_sum.set_immutable() @@ -416,7 +397,8 @@ def radius_square(self): OUTPUT: - The square of the radius, which is in :meth:`base_ring`. + The square of the radius, which is in + :meth:`~sage.geometry.polyhedron.base0.Polyhedron_base0.base_ring`. EXAMPLES:: @@ -634,7 +616,7 @@ def hyperplane_arrangement(self): A :class:`hyperplane arrangement ` consisting of the hyperplanes defined by the - :meth:`Hrepresentation`. + :meth:`~sage.geometry.polyhedron.base0.Polyhedron_base0.Hrepresentation`. If the polytope is full-dimensional, this is the hyperplane arrangement spanned by the facets of the polyhedron. @@ -799,7 +781,7 @@ def is_minkowski_summand(self, Y): r""" Test whether ``Y`` is a Minkowski summand. - See :meth:`minkowski_sum`. + See :meth:`~sage.geometry.polyhedron.base5.Polyhedron_base5.minkowski_sum`. OUTPUT: @@ -841,6 +823,7 @@ def barycentric_subdivision(self, subdivision_frac=None): REFERENCE: See :wikipedia:`Barycentric_subdivision` + Section 6.6, Handbook of Convex Geometry, Volume A, edited by P.M. Gruber and J.M. Wills. 1993, North-Holland Publishing Co.. @@ -954,7 +937,8 @@ def permutations_to_matrices(self, conj_class_reps, acting_group=None, additiona The dictionary has entries for the generators of the ``acting_group`` and the representatives of conjugacy classes in ``conj_class_reps``. By - default, the ``acting_group`` is the ``restricted_automorphism_group`` + default, the ``acting_group`` is the + :meth:`~sage.geometry.polyhedron.base4.Polyhedron_base4.restricted_automorphism_group` of the polytope. Each element in ``additional_elts`` also becomes a key. INPUT: @@ -963,16 +947,16 @@ def permutations_to_matrices(self, conj_class_reps, acting_group=None, additiona conjugacy classes of the ``acting_group``. - ``acting_group`` -- a subgroup of polytope's - ``restricted_automorphism_group``. + :meth:`~sage.geometry.polyhedron.base4.Polyhedron_base4.restricted_automorphism_group`. - - ``additional_elts`` -- list (default=None). a subset of the - ``restricted_automorphism_group`` of the polytope expressed as - permutations. + - ``additional_elts`` -- list (default=None). A subset of the + :meth:`~sage.geometry.polyhedron.base4.Polyhedron_base4.restricted_automorphism_group` + of the polytope expressed as permutations. OUTPUT: - A dictionary between elements of ``the restricted_automorphism_group`` - or ``acting_group`` expressed as permutations (keys) and matrices (values). + A dictionary between elements of the ``acting_group`` expressed as permutations + (keys) and matrices (values). EXAMPLES: @@ -1116,35 +1100,35 @@ def _polymake_init_(self): EXAMPLES:: sage: P = polytopes.cube() - sage: PP = polymake(P) # optional - polymake - sage: PP.N_VERTICES # optional - polymake + sage: PP = polymake(P) # optional - jupymake + sage: PP.N_VERTICES # optional - jupymake 8 Lower-dimensional polyhedron:: sage: P = Polyhedron(vertices=[[1, 0], [0, 1]]) - sage: PP = polymake(P) # optional - polymake - sage: PP.COMBINATORIAL_DIM # optional - polymake + sage: PP = polymake(P) # optional - jupymake + sage: PP.COMBINATORIAL_DIM # optional - jupymake 1 - sage: PP.AFFINE_HULL # optional - polymake + sage: PP.AFFINE_HULL # optional - jupymake -1 1 1 Empty polyhedron:: sage: P = Polyhedron(ambient_dim=2, vertices=[]) - sage: PP = polymake(P) # optional - polymake - sage: PP.COMBINATORIAL_DIM # optional - polymake + sage: PP = polymake(P) # optional - jupymake + sage: PP.COMBINATORIAL_DIM # optional - jupymake -1 Pointed unbounded polyhedron:: sage: P = Polyhedron(vertices=[[1, 0], [0, 1]], rays=[[1, 0]]) - sage: PP = polymake(P) # optional - polymake - sage: PP.VERTICES # optional - polymake + sage: PP = polymake(P) # optional - jupymake + sage: PP.VERTICES # optional - jupymake 1 0 1 1 1 0 0 1 0 - sage: PP.FACETS # optional - polymake + sage: PP.FACETS # optional - jupymake 1 0 -1 -1 1 1 0 0 1 @@ -1152,14 +1136,14 @@ def _polymake_init_(self): Non-pointed polyhedron:: sage: P = Polyhedron(vertices=[[1, 0], [0, 1]], lines=[[1, 0]]) - sage: PP = polymake(P) # optional - polymake - sage: PP.VERTICES # optional - polymake + sage: PP = polymake(P) # optional - jupymake + sage: PP.VERTICES # optional - jupymake 1 0 1 1 0 0 - sage: PP.FACETS # optional - polymake + sage: PP.FACETS # optional - jupymake 1 0 -1 0 0 1 - sage: PP.LINEALITY_SPACE # optional - polymake + sage: PP.LINEALITY_SPACE # optional - jupymake 0 1 0 Algebraic polyhedron:: @@ -1169,20 +1153,20 @@ def _polymake_init_(self): in (Number Field in sqrt5 with defining polynomial x^2 - 5 with sqrt5 = 2.236067977499790?)^3 defined as the convex hull of 20 vertices - sage: print("There may be a recompilation warning"); PP = polymake(P); PP # optional - polymake # optional - sage.rings.number_field + sage: print("There may be a recompilation warning"); PP = polymake(P); PP # optional - jupymake # optional - sage.rings.number_field There may be a recompilation warning... Polytope>[...] - sage: sorted(PP.VERTICES[:], key=repr)[0] # optional - polymake # optional - sage.rings.number_field + sage: sorted(PP.VERTICES[:], key=repr)[0] # optional - jupymake # optional - sage.rings.number_field 1 -1+1r5 -4+2r5 0 Floating-point polyhedron:: sage: P = polytopes.dodecahedron(exact=False); P A 3-dimensional polyhedron in RDF^3 defined as the convex hull of 20 vertices - sage: print("There may be a recompilation warning"); PP = polymake(P); PP # optional - polymake + sage: print("There may be a recompilation warning"); PP = polymake(P); PP # optional - jupymake There may be a recompilation warning... Polytope[...] - sage: sorted(PP.VERTICES[:], key=repr)[0] # optional - polymake + sage: sorted(PP.VERTICES[:], key=repr)[0] # optional - jupymake 1 -0.472135955 0 -1.236067978 """ @@ -1191,15 +1175,17 @@ def _polymake_init_(self): polymake_class = "Polytope<{}>".format(polymake_field) if self.is_empty(): # Polymake 3.1 cannot enter an empty polyhedron using - # FACETS and AFFINE_HULL. Use corresponding input properties instead. + # FACETS and AFFINE_HULL. + # Use corresponding input properties instead. # https://forum.polymake.org/viewtopic.php?f=8&t=545 return polymake.new_object(polymake_class, INEQUALITIES=self.inequalities_list(), EQUATIONS=self.equations_list()) - else: - return polymake.new_object(polymake_class, - FACETS=self.inequalities_list(), - AFFINE_HULL=self.equations_list(), - VERTICES= [ [1] + v for v in self.vertices_list() ] \ - + [ [0] + r for r in self.rays_list() ], - LINEALITY_SPACE=[ [0] + l for l in self.lines_list() ]) + + verts_and_rays = [[1] + v for v in self.vertices_list()] + verts_and_rays += [[0] + r for r in self.rays_list()] + return polymake.new_object(polymake_class, + FACETS=self.inequalities_list(), + AFFINE_HULL=self.equations_list(), + VERTICES=verts_and_rays, + LINEALITY_SPACE=[[0] + l for l in self.lines_list()]) diff --git a/src/sage/geometry/polyhedron/base0.py b/src/sage/geometry/polyhedron/base0.py index 2e0f6a716cf..7ed7e777374 100644 --- a/src/sage/geometry/polyhedron/base0.py +++ b/src/sage/geometry/polyhedron/base0.py @@ -1,7 +1,5 @@ r""" -Base class for polyhedra, part 0 - -Initialization and access to Vrepresentation and Hrepresentation. +Base class for polyhedra: Initialization and access to Vrepresentation and Hrepresentation """ # **************************************************************************** @@ -315,8 +313,8 @@ def _sage_input_(self, sib, coerced): sage: P = Polyhedron(vertices = [[1, 0], [0, 1]], rays = [[1, 1]], backend='normaliz') # optional - pynormaliz sage: sage_input(P) # optional - pynormaliz Polyhedron(backend='normaliz', base_ring=QQ, rays=[(QQ(1), QQ(1))], vertices=[(QQ(0), QQ(1)), (QQ(1), QQ(0))]) - sage: P = Polyhedron(vertices = [[1, 0], [0, 1]], rays = [[1, 1]], backend='polymake') # optional - polymake - sage: sage_input(P) # optional - polymake + sage: P = Polyhedron(vertices = [[1, 0], [0, 1]], rays = [[1, 1]], backend='polymake') # optional - jupymake + sage: sage_input(P) # optional - jupymake Polyhedron(backend='polymake', base_ring=QQ, rays=[(QQ(1), QQ(1))], vertices=[(QQ(1), QQ(0)), (QQ(0), QQ(1))]) """ kwds = dict() @@ -909,7 +907,7 @@ def inequalities_list(self): It is recommended to use :meth:`inequalities` or :meth:`inequality_generator` instead to iterate over the - list of :class:`Inequality` objects. + list of :class:`~sage.geometry.polyhedron.representation.Inequality` objects. EXAMPLES:: @@ -991,7 +989,7 @@ def vertices_list(self): .. NOTE:: It is recommended to use :meth:`vertex_generator` instead to - iterate over the list of :class:`Vertex` objects. + iterate over the list of :class:`~sage.geometry.polyhedron.representation.Vertex` objects. .. WARNING:: @@ -1202,7 +1200,7 @@ def rays_list(self): It is recommended to use :meth:`rays` or :meth:`ray_generator` instead to iterate over the list of - :class:`Ray` objects. + :class:`~sage.geometry.polyhedron.representation.Ray` objects. OUTPUT: @@ -1257,7 +1255,7 @@ def lines_list(self): .. NOTE:: It is recommended to use :meth:`line_generator` instead to - iterate over the list of :class:`Line` objects. + iterate over the list of :class:`~sage.geometry.polyhedron.representation.Line` objects. EXAMPLES:: @@ -1368,11 +1366,9 @@ def cdd_Hrepresentation(self): except AttributeError: from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ - from sage.rings.real_double import RDF - if self.base_ring() is ZZ or self.base_ring() is QQ: cdd_type = 'rational' - elif self.base_ring() is RDF: + elif isinstance(self.base_ring(), sage.rings.abc.RealDoubleField): cdd_type = 'real' else: raise TypeError('the base ring must be ZZ, QQ, or RDF') @@ -1433,11 +1429,9 @@ def cdd_Vrepresentation(self): except AttributeError: from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ - from sage.rings.real_double import RDF - if self.base_ring() is ZZ or self.base_ring() is QQ: cdd_type = 'rational' - elif self.base_ring() is RDF: + elif isinstance(self.base_ring(), sage.rings.abc.RealDoubleField): cdd_type = 'real' else: raise TypeError('the base ring must be ZZ, QQ, or RDF') diff --git a/src/sage/geometry/polyhedron/base1.py b/src/sage/geometry/polyhedron/base1.py index e36867647f5..daac9aa6c05 100644 --- a/src/sage/geometry/polyhedron/base1.py +++ b/src/sage/geometry/polyhedron/base1.py @@ -1,5 +1,5 @@ r""" -Base class for polyhedra, part 1 +Base class for polyhedra: Implementation of the :class:`ConvexSet_base` API Define methods that exist for convex sets, but not constructions such as dilation or product. @@ -426,9 +426,9 @@ def ambient_dim(self): def an_affine_basis(self): """ - Return points in ``self`` that are a basis for the affine span of the polytope. + Return points in ``self`` that form a basis for the affine span of ``self``. - This implementation of the method :meth:`ConvexSet_base.an_affine_basis` + This implementation of the method :meth:`~sage.geometry.convex_set.ConvexSet_base.an_affine_basis` for polytopes guarantees the following: - All points are vertices. @@ -438,6 +438,9 @@ def an_affine_basis(self): one vertex that is in the difference. Thus this method is independent of the concrete realization of the polytope. + For unbounded polyhedra, the result may contain arbitrary points of ``self``, + not just vertices. + EXAMPLES:: sage: P = polytopes.cube() @@ -455,42 +458,50 @@ def an_affine_basis(self): A vertex at (4, 1, 3, 5, 2), A vertex at (4, 2, 5, 3, 1)] - The method is not implemented for unbounded polyhedra:: + Unbounded polyhedra:: - sage: p = Polyhedron(vertices=[(0,0)],rays=[(1,0),(0,1)]) + sage: p = Polyhedron(vertices=[(0, 0)], rays=[(1,0), (0,1)]) + sage: p.an_affine_basis() + [A vertex at (0, 0), (1, 0), (0, 1)] + sage: p = Polyhedron(vertices=[(2, 1)], rays=[(1,0), (0,1)]) sage: p.an_affine_basis() - Traceback (most recent call last): - ... - NotImplementedError: this function is not implemented for unbounded polyhedra + [A vertex at (2, 1), (3, 1), (2, 2)] + sage: p = Polyhedron(vertices=[(2, 1)], rays=[(1,0)], lines=[(0,1)]) + sage: p.an_affine_basis() + [(2, 1), A vertex at (2, 0), (3, 0)] """ - if not self.is_compact(): - raise NotImplementedError("this function is not implemented for unbounded polyhedra") - chain = self.a_maximal_chain()[1:] # we exclude the empty face chain_indices = [face.ambient_V_indices() for face in chain] basis_indices = [] # We use in the following that elements in ``chain_indices`` are sorted lists # of V-indices. - # Thus for each two faces we can easily find the first vertex that differs. + # Thus for each two faces we can easily find the first Vrep that differs. for dim, face in enumerate(chain_indices): if dim == 0: - # Append the vertex. - basis_indices.append(face[0]) + # Append the V-indices of the minimal face. + basis_indices.extend(face[:]) continue prev_face = chain_indices[dim-1] for i in range(len(prev_face)): if prev_face[i] != face[i]: - # We found a vertex that ``face`` has, but its facet does not. + # We found a Vrep that ``face`` has, but its facet does not. basis_indices.append(face[i]) break else: # no break # ``prev_face`` contains all the same vertices as ``face`` until now. - # But ``face`` is guaranteed to contain one more vertex (at least). + # But ``face`` is guaranteed to contain one more Vrep (at least). basis_indices.append(face[len(prev_face)]) - return [self.Vrepresentation()[i] for i in basis_indices] + Vreps = [self.Vrepresentation()[i] for i in basis_indices] + if all(vrep.is_vertex() for vrep in Vreps): + return Vreps + for vrep in Vreps: + if vrep.is_vertex(): + vertex = vrep + return [vrep if vrep.is_vertex() else vertex.vector() + vrep.vector() + for vrep in Vreps] @abstract_method def a_maximal_chain(self): diff --git a/src/sage/geometry/polyhedron/base2.py b/src/sage/geometry/polyhedron/base2.py index 44654a024b2..0d7606d96d0 100644 --- a/src/sage/geometry/polyhedron/base2.py +++ b/src/sage/geometry/polyhedron/base2.py @@ -1,7 +1,5 @@ r""" -Base class for polyhedra, part 2 - -Define methods related to lattice points. +Base class for polyhedra: Methods related to lattice points """ # **************************************************************************** @@ -345,7 +343,7 @@ def integral_points_count(self, **kwds): r""" Return the number of integral points in the polyhedron. - This generic version of this method simply calls ``integral_points``. + This generic version of this method simply calls :meth:`integral_points`. EXAMPLES:: @@ -529,7 +527,7 @@ def get_integral_point(self, index, **kwds): Return the ``index``-th integral point in this polyhedron. This is equivalent to ``sorted(self.integral_points())[index]``. - However, so long as self.integral_points_count() does not need to + However, so long as :meth:`integral_points_count` does not need to enumerate all integral points, neither does this method. Hence it can be significantly faster. If the polyhedron is not compact, a ``ValueError`` is raised. @@ -541,7 +539,7 @@ def get_integral_point(self, index, **kwds): is raised. - ``**kwds`` -- optional keyword parameters that are passed to - :meth:`self.integral_points_count`. + :meth:`integral_points_count`. ALGORITHM: @@ -627,14 +625,14 @@ def random_integral_point(self, **kwds): INPUT: - ``**kwds`` -- optional keyword parameters that are passed to - :meth:`self.get_integral_point`. + :meth:`get_integral_point`. OUTPUT: The integral point in the polyhedron chosen uniformly at random. If the polyhedron is not compact, a ``ValueError`` is raised. If the - polyhedron does not contain any integral points, an ``EmptySetError`` is - raised. + polyhedron does not contain any integral points, an + :class:`~sage.categories.sets_cat.EmptySetError` is raised. .. SEEALSO:: diff --git a/src/sage/geometry/polyhedron/base3.py b/src/sage/geometry/polyhedron/base3.py index 0b85c4f944c..32a335ce586 100644 --- a/src/sage/geometry/polyhedron/base3.py +++ b/src/sage/geometry/polyhedron/base3.py @@ -1,8 +1,7 @@ r""" -Base class for polyhedra, part 3 +Base class for polyhedra: Methods regarding the combinatorics of a polyhedron -Define methods related to the combinatorics of a polyhedron -excluding methods relying on :mod:`sage.graphs`. +Excluding methods relying on :mod:`sage.graphs`. """ # **************************************************************************** @@ -104,9 +103,9 @@ def slack_matrix(self): .. NOTE:: The columns correspond to inequalities/equations in the - order :meth:`Hrepresentation`, the rows correspond to + order :meth:`~sage.geometry.polyhedron.base0.Polyhedron_base0.Hrepresentation`, the rows correspond to vertices/rays/lines in the order - :meth:`Vrepresentation`. + :meth:`~sage.geometry.polyhedron.base0.Polyhedron_base0.Vrepresentation`. .. SEEALSO:: @@ -136,8 +135,8 @@ def slack_matrix(self): [1 0 1 0 0 1] [1 0 0 0 1 1] - sage: P = polytopes.dodecahedron().faces(2)[0].as_polyhedron() - sage: P.slack_matrix() + sage: P = polytopes.dodecahedron().faces(2)[0].as_polyhedron() # optional - sage.rings.number_field + sage: P.slack_matrix() # optional - sage.rings.number_field [1/2*sqrt5 - 1/2 0 0 1 1/2*sqrt5 - 1/2 0] [ 0 0 1/2*sqrt5 - 1/2 1/2*sqrt5 - 1/2 1 0] [ 0 1/2*sqrt5 - 1/2 1 0 1/2*sqrt5 - 1/2 0] @@ -181,9 +180,9 @@ def incidence_matrix(self): .. NOTE:: The columns correspond to inequalities/equations in the - order :meth:`Hrepresentation`, the rows correspond to + order :meth:`~sage.geometry.polyhedron.base0.Polyhedron_base0.Hrepresentation`, the rows correspond to vertices/rays/lines in the order - :meth:`Vrepresentation`. + :meth:`~sage.geometry.polyhedron.base0.Polyhedron_base0.Vrepresentation`. .. SEEALSO:: @@ -369,7 +368,7 @@ def _test_combinatorial_polyhedron(self, tester=None, **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) + tester.info(tester._prefix + " ", newline=False) def face_generator(self, face_dimension=None, algorithm=None, **kwds): r""" @@ -381,8 +380,10 @@ def face_generator(self, face_dimension=None, algorithm=None, **kwds): - ``face_dimension`` -- integer (default ``None``), yield only faces of this dimension if specified + - ``algorithm`` -- string (optional); specify whether to start with facets or vertices: + * ``'primal'`` -- start with the facets * ``'dual'`` -- start with the vertices * ``None`` -- choose automatically @@ -645,13 +646,13 @@ def faces(self, face_dimension): A tuple of :class:`~sage.geometry.polyhedron.face.PolyhedronFace`. See - :mod:`~sage.geometry.polyhedron.face` for details. The order + module :mod:`sage.geometry.polyhedron.face` for details. The order is random but fixed. .. SEEALSO:: :meth:`face_generator`, - :meth:`facet`. + :meth:`~sage.geometry.polyhedron.base0.Polyhedron_base0.facet`. EXAMPLES: @@ -796,6 +797,7 @@ def f_vector(self, num_threads=None, parallelization_depth=None, algorithm=None) - ``algorithm`` -- string (optional); specify whether the face generator starts with facets or vertices: + * ``'primal'`` -- start with the facets * ``'dual'`` -- start with the vertices * ``None`` -- choose automatically @@ -807,10 +809,10 @@ def f_vector(self, num_threads=None, parallelization_depth=None, algorithm=None) .. NOTE:: - The ``vertices`` as given by :meth:`Polyhedron_base.vertices` + The ``vertices`` as given by :meth:`~sage.geometry.polyhedron.base0.Polyhedron_base0.vertices` do not need to correspond to `0`-dimensional faces. If a polyhedron contains `k` lines they correspond to `k`-dimensional faces. - See example below + See example below. EXAMPLES:: @@ -893,6 +895,7 @@ def vertex_adjacency_matrix(self, algorithm=None): - ``algorithm`` -- string (optional); specify whether the face generator starts with facets or vertices: + * ``'primal'`` -- start with the facets * ``'dual'`` -- start with the vertices * ``None`` -- choose automatically @@ -1030,6 +1033,7 @@ def facet_adjacency_matrix(self, algorithm=None): - ``algorithm`` -- string (optional); specify whether the face generator starts with facets or vertices: + * ``'primal'`` -- start with the facets * ``'dual'`` -- start with the vertices * ``None`` -- choose automatically @@ -1147,7 +1151,7 @@ def simplicity(self): A polytope `P` is `k`-simple, if every `(d-1-k)`-face is contained in exactly `k+1` facets of `P` for `1 \leq k \leq d-1`. Equivalently it is `k`-simple if the polar/dual polytope is `k`-simplicial. - If `self` is a simplex, it returns its dimension. + If ``self`` is a simplex, it returns its dimension. EXAMPLES:: @@ -1170,7 +1174,7 @@ def simplicity(self): ... NotImplementedError: this function is implemented for polytopes only """ - if not(self.is_compact()): + if not self.is_compact(): raise NotImplementedError("this function is implemented for polytopes only") return self.combinatorial_polyhedron().simplicity() @@ -1198,7 +1202,7 @@ def simpliciality(self): Return the largest integer `k` such that the polytope is `k`-simplicial. A polytope is `k`-simplicial, if every `k`-face is a simplex. - If `self` is a simplex, returns its dimension. + If ``self`` is a simplex, returns its dimension. EXAMPLES:: @@ -1221,7 +1225,7 @@ def simpliciality(self): ... NotImplementedError: this function is implemented for polytopes only """ - if not(self.is_compact()): + if not self.is_compact(): raise NotImplementedError("this function is implemented for polytopes only") return self.combinatorial_polyhedron().simpliciality() @@ -1260,7 +1264,7 @@ def is_simplicial(self): ... NotImplementedError: this function is implemented for polytopes only """ - if not(self.is_compact()): + if not self.is_compact(): raise NotImplementedError("this function is implemented for polytopes only") return self.combinatorial_polyhedron().is_simplicial() @@ -1691,7 +1695,8 @@ def meet_of_Hrep(self, *Hrepresentatives): INPUT: - ``Hrepresentatives`` -- facets or indices of Hrepresentatives; - the indices are assumed to be the indices of the Hrepresentation + the indices are assumed to be the indices of the + :meth:`~sage.geometry.polyhedron.base0.Polyhedron_base0.Hrepresentation` OUTPUT: a :class:`~sage.geometry.polyhedron.face.PolyhedronFace` @@ -1709,7 +1714,8 @@ def meet_of_Hrep(self, *Hrepresentatives): sage: P.meet_of_Hrep(1,3,7).ambient_H_indices() (0, 1, 3, 7) - The indices are the indices of the Hrepresentation. + The indices are the indices of the + :meth:`~sage.geometry.polyhedron.base0.Polyhedron_base0.Hrepresentation`. ``0`` corresponds to an equation and is ignored:: sage: P.meet_of_Hrep(0) @@ -1742,7 +1748,8 @@ def meet_of_Hrep(self, *Hrepresentatives): TESTS: Equations are not considered by the combinatorial polyhedron. - We check that the index corresponds to the Hrepresentation index:: + We check that the index corresponds to the + :meth:`~sage.geometry.polyhedron.base0.Polyhedron_base0.Hrepresentation` index:: sage: P = polytopes.permutahedron(3, backend='field') sage: P.Hrepresentation() @@ -1863,6 +1870,7 @@ def _test_combinatorial_face_as_combinatorial_polyhedron(self, tester=None, **op D2._test_bitsets(tester, **options) try: import sage.graphs.graph + assert sage.graphs.graph # to muffle pyflakes except ImportError: pass else: diff --git a/src/sage/geometry/polyhedron/base4.py b/src/sage/geometry/polyhedron/base4.py index d0262a2efd2..7e8aeb32890 100644 --- a/src/sage/geometry/polyhedron/base4.py +++ b/src/sage/geometry/polyhedron/base4.py @@ -1,6 +1,6 @@ # sage.doctest: optional - sage.graphs r""" -Base class for polyhedra, part 4 +Base class for polyhedra: Graph-theoretic methods Define methods relying on :mod:`sage.graphs`. """ @@ -152,16 +152,17 @@ def vertex_graph(self, **kwds): - ``algorithm`` -- string (optional); specify whether the face generator starts with facets or vertices: + * ``'primal'`` -- start with the facets * ``'dual'`` -- start with the vertices * ``None`` -- choose automatically - ..NOTE:: + .. NOTE:: The graph of a polyhedron with lines has no vertices, as the polyhedron has no vertices (`0`-faces). - The method :meth:`Polyhedron_base:vertices` returns + The method :meth:`~sage.geometry.polyhedron.base0.Polyhedron_base0.vertices` returns the defining points in this case. EXAMPLES:: @@ -211,7 +212,7 @@ def vertex_digraph(self, f, increasing=True): - ``f`` -- a linear form. The linear form can be provided as: - a vector space morphism with one-dimensional codomain, (see - :meth:`sage.modules.vector_space_morphism.linear_transformation` + :func:`sage.modules.vector_space_morphism.linear_transformation` and :class:`sage.modules.vector_space_morphism.VectorSpaceMorphism`) - a vector ; in this case the linear form is obtained by duality @@ -778,7 +779,7 @@ def restricted_automorphism_group(self, output="abstract"): - For ``output="abstract"`` and ``output="permutation"``: a :class:`PermutationGroup`. - - For ``output="matrix"``: a :class:`MatrixGroup`. + - For ``output="matrix"``: a :func:`~sage.groups.matrix_gps.finitely_generated.MatrixGroup`. - For ``output="matrixlist"``: a list of matrices. @@ -1052,8 +1053,8 @@ def is_combinatorially_isomorphic(self, other, algorithm='bipartite_graph'): INPUT: - ``other`` -- a polyhedron object - - ``algorithm`` (default = ``bipartite_graph``) -- the algorithm to use. - The other possible value is ``face_lattice``. + - ``algorithm`` (default = ``'bipartite_graph'``) -- the algorithm to use. + The other possible value is ``'face_lattice'``. OUTPUT: diff --git a/src/sage/geometry/polyhedron/base5.py b/src/sage/geometry/polyhedron/base5.py index b94da62a4b8..f16bce682b9 100644 --- a/src/sage/geometry/polyhedron/base5.py +++ b/src/sage/geometry/polyhedron/base5.py @@ -1,8 +1,7 @@ r""" -Base class for polyhedra, part 5 +Base class for polyhedra: Methods for constructing new polyhedra -Define methods constructing new polyhedra -except for affine hull and affine hull projection. +Except for affine hull and affine hull projection. """ # **************************************************************************** @@ -679,14 +678,14 @@ def minkowski_sum(self, other): .. MATH:: X \oplus Y = - \cup_{y\in Y} (X+y) = - \cup_{x\in X, y\in Y} (x+y) + \bigcup_{y\in Y} (X+y) = + \bigcup_{x\in X, y\in Y} (x+y) See :meth:`minkowski_difference` for a partial inverse operation. INPUT: - - ``other`` -- a :class:`Polyhedron_base` + - ``other`` -- a :class:`~sage.geometry.polyhedron.base.Polyhedron_base` OUTPUT: @@ -737,7 +736,7 @@ def minkowski_difference(self, other): X \ominus Y = (X^c \oplus Y)^c = - \cap_{y\in Y} (X-y) + \bigcap_{y\in Y} (X-y) where superscript-"c" means the complement in the ambient vector space. The Minkowski difference of convex sets is @@ -754,7 +753,7 @@ def minkowski_difference(self, other): INPUT: - - ``other`` -- a :class:`Polyhedron_base` + - ``other`` -- a :class:`~sage.geometry.polyhedron.base.Polyhedron_base` OUTPUT: @@ -885,7 +884,7 @@ def product(self, other): INPUT: - - ``other`` -- a :class:`Polyhedron_base` + - ``other`` -- a :class:`~sage.geometry.polyhedron.base.Polyhedron_base` OUTPUT: @@ -1148,7 +1147,7 @@ def subdirect_sum(self, other): INPUT: - - ``other`` -- a :class:`Polyhedron_base` + - ``other`` -- a :class:`~sage.geometry.polyhedron.base.Polyhedron_base` EXAMPLES:: @@ -1213,7 +1212,7 @@ def direct_sum(self, other): INPUT: - - ``other`` -- a :class:`Polyhedron_base` + - ``other`` -- a :class:`~sage.geometry.polyhedron.base.Polyhedron_base` EXAMPLES:: @@ -1898,17 +1897,17 @@ def face_truncation(self, face, linear_coefficients=None, cut_frac=None): INPUT: - - ``face`` -- a PolyhedronFace + - ``face`` -- a :class:`~sage.geometry.polyhedron.face.PolyhedronFace` - ``linear_coefficients`` -- tuple of integer. Specifies the coefficient of the normal vector of the cutting hyperplane used to truncate the face. The default direction is determined using the normal fan of the polyhedron. - ``cut_frac`` -- number between 0 and 1. Determines where the - hyperplane cuts the polyhedron. A value close to 0 cuts very close - to the face, whereas a value close to 1 cuts very close to the next - vertex (according to the normal vector of the cutting hyperplane). - Default is `\frac{1}{3}`. + hyperplane cuts the polyhedron. A value close to 0 cuts very close + to the face, whereas a value close to 1 cuts very close to the next + vertex (according to the normal vector of the cutting hyperplane). + Default is `\frac{1}{3}`. OUTPUT: @@ -2270,7 +2269,7 @@ def wedge(self, face, width=1): The backend should be preserved as long as the value of width permits. The base_ring will change to the field of fractions of the current - base_ring, unless width forces a different ring. :: + base_ring, unless ``width`` forces a different ring. :: sage: P = polytopes.cyclic_polytope(3,7, base_ring=ZZ, backend='field') sage: W1 = P.wedge(P.faces(2)[0]); W1.base_ring(); W1.backend() diff --git a/src/sage/geometry/polyhedron/base6.py b/src/sage/geometry/polyhedron/base6.py index b569f40613d..7a1f4882440 100644 --- a/src/sage/geometry/polyhedron/base6.py +++ b/src/sage/geometry/polyhedron/base6.py @@ -1,7 +1,5 @@ r""" -Base class for polyhedra, part 6 - -Define methods related to plotting including affine hull projection. +Base class for polyhedra: Methods for plotting and affine hull projection """ # **************************************************************************** @@ -36,7 +34,6 @@ from sage.modules.vector_space_morphism import linear_transformation from sage.matrix.constructor import matrix from sage.modules.free_module_element import vector -from sage.rings.qqbar import AA from sage.geometry.convex_set import AffineHullProjectionData from .base5 import Polyhedron_base5 @@ -50,7 +47,10 @@ class Polyhedron_base6(Polyhedron_base5): sage: P = polytopes.cube() sage: Polyhedron_base6.plot(P) Graphics3d Object - sage: Polyhedron_base6.tikz(P) + sage: print(Polyhedron_base6.tikz(P, output_type='TikzPicture')) + \RequirePackage{luatex85} + \documentclass[tikz]{standalone} + \begin{document} \begin{tikzpicture}% [x={(1.000000cm, 0.000000cm)}, y={(-0.000000cm, 1.000000cm)}, @@ -127,6 +127,7 @@ class Polyhedron_base6(Polyhedron_base5): %% %% \end{tikzpicture} + \end{document} sage: Q = polytopes.hypercube(4) sage: Polyhedron_base6.show(Q) @@ -475,9 +476,11 @@ def show(self, **kwds): def tikz(self, view=[0, 0, 1], angle=0, scale=1, edge_color='blue!95!black', facet_color='blue!95!black', - opacity=0.8, vertex_color='green', axis=False): + opacity=0.8, vertex_color='green', axis=False, + output_type=None): r""" - Return a string ``tikz_pic`` consisting of a tikz picture of ``self`` + Return a tikz picture of ``self`` as a string or as a + :class:`~sage.misc.latex_standalone.TikzPicture` according to a projection ``view`` and an angle ``angle`` obtained via the threejs viewer. ``self`` must be bounded. @@ -496,15 +499,20 @@ def tikz(self, view=[0, 0, 1], angle=0, scale=1, - ``opacity`` - real number (default: 0.8) between 0 and 1 giving the opacity of the front facets. - ``axis`` - Boolean (default: False) draw the axes at the origin or not. + - ``output_type`` - string (default: ``None``), valid values + are ``None`` (deprecated), ``'LatexExpr'`` and ``'TikzPicture'``, + whether to return a LatexExpr object (which inherits from Python + str) or a ``TikzPicture`` object from module + :mod:`sage.misc.latex_standalone` OUTPUT: - - LatexExpr -- containing the TikZ picture. + - LatexExpr object or TikzPicture object .. NOTE:: This is a wrapper of a method of the projection object - `self.projection()`. See :meth:`~sage.geometry.polyhedron.plot.Projection.tikz` + ``self.projection()``. See :meth:`~sage.geometry.polyhedron.plot.Projection.tikz` for more detail. The inputs ``view`` and ``angle`` can be obtained by visualizing it @@ -537,18 +545,25 @@ def tikz(self, view=[0, 0, 1], angle=0, scale=1, EXAMPLES:: sage: co = polytopes.cuboctahedron() - sage: Img = co.tikz([0,0,1], 0) - sage: print('\n'.join(Img.splitlines()[:9])) + sage: Img = co.tikz([0,0,1], 0, output_type='TikzPicture') + sage: Img + \documentclass[tikz]{standalone} + \begin{document} \begin{tikzpicture}% - [x={(1.000000cm, 0.000000cm)}, - y={(0.000000cm, 1.000000cm)}, - z={(0.000000cm, 0.000000cm)}, - scale=1.000000, - back/.style={loosely dotted, thin}, - edge/.style={color=blue!95!black, thick}, - facet/.style={fill=blue!95!black,fill opacity=0.800000}, - vertex/.style={inner sep=1pt,circle,draw=green!25!black,fill=green!75!black,thick}] - sage: print('\n'.join(Img.splitlines()[12:21])) + [x={(1.000000cm, 0.000000cm)}, + y={(0.000000cm, 1.000000cm)}, + z={(0.000000cm, 0.000000cm)}, + scale=1.000000, + ... + Use print to see the full content. + ... + \node[vertex] at (1.00000, 0.00000, 1.00000) {}; + \node[vertex] at (1.00000, 1.00000, 0.00000) {}; + %% + %% + \end{tikzpicture} + \end{document} + sage: print('\n'.join(Img.content().splitlines()[12:21])) %% with the command: ._tikz_3d_in_3d and parameters: %% view = [0, 0, 1] %% angle = 0 @@ -558,15 +573,40 @@ def tikz(self, view=[0, 0, 1], angle=0, scale=1, %% opacity = 0.8 %% vertex_color = green %% axis = False - sage: print('\n'.join(Img.splitlines()[22:26])) + sage: print('\n'.join(Img.content().splitlines()[22:26])) %% Coordinate of the vertices: %% \coordinate (-1.00000, -1.00000, 0.00000) at (-1.00000, -1.00000, 0.00000); \coordinate (-1.00000, 0.00000, -1.00000) at (-1.00000, 0.00000, -1.00000); + + When output type is a :class:`sage.misc.latex_standalone.TikzPicture`:: + + sage: co = polytopes.cuboctahedron() + sage: t = co.tikz([674,108,-731], 112, output_type='TikzPicture') + sage: t + \documentclass[tikz]{standalone} + \begin{document} + \begin{tikzpicture}% + [x={(0.249656cm, -0.577639cm)}, + y={(0.777700cm, -0.358578cm)}, + z={(-0.576936cm, -0.733318cm)}, + scale=1.000000, + ... + Use print to see the full content. + ... + \node[vertex] at (1.00000, 0.00000, 1.00000) {}; + \node[vertex] at (1.00000, 1.00000, 0.00000) {}; + %% + %% + \end{tikzpicture} + \end{document} + sage: path_to_file = t.pdf() # not tested + """ return self.projection().tikz(view, angle, scale, edge_color, facet_color, - opacity, vertex_color, axis) + opacity, vertex_color, axis, + output_type=output_type) def _rich_repr_(self, display_manager, **kwds): r""" @@ -640,7 +680,7 @@ def gale_transform(self): .. SEEALSO:: - :func`~sage.geometry.polyhedron.library.gale_transform_to_polyhedron`. + :func:`~sage.geometry.polyhedron.library.gale_transform_to_polyhedron`. TESTS:: @@ -700,6 +740,7 @@ def _test_gale_transform(self, tester=None, **options): try: import sage.graphs.graph + assert sage.graphs.graph # to muffle pyflakes except ImportError: pass else: @@ -720,7 +761,7 @@ def projection(self, projection=None): .. SEEALSO:: - :meth:`~sage.geometry.polyhedron.base.Polyhedron_base.schlegel_projection` for a more interesting projection. + :meth:`~sage.geometry.polyhedron.base6.Polyhedron_base6.schlegel_projection` for a more interesting projection. EXAMPLES:: @@ -784,8 +825,8 @@ def schlegel_projection(self, facet=None, position=None): INPUT: - - ``facet`` -- a PolyhedronFace. The facet into which the Schlegel - diagram is created. The default is the first facet. + - ``facet`` -- a :class:`~sage.geometry.polyhedron.face.PolyhedronFace`. + The facet into which the Schlegel diagram is created. The default is the first facet. - ``position`` -- a positive number. Determines a relative distance from the barycenter of ``facet``. A value close to 0 will place the @@ -967,6 +1008,7 @@ def _affine_hull_projection(self, *, except TypeError: if not extend: raise ValueError('the base ring needs to be extended; try with "extend=True"') + from sage.rings.qqbar import AA M = matrix(AA, M) A = M.gram_schmidt(orthonormal=orthonormal)[0] if minimal: @@ -1176,9 +1218,9 @@ def affine_hull_projection(self, A vertex at (2, 0, 0), A vertex at (1, 3/2, 0), A vertex at (1, 1/2, 4/3)) - sage: A = S.affine_hull_projection(orthonormal=True, extend=True); A + sage: A = S.affine_hull_projection(orthonormal=True, extend=True); A # optional - sage.rings.number_field A 3-dimensional polyhedron in AA^3 defined as the convex hull of 4 vertices - sage: A.vertices() + sage: A.vertices() # optional - sage.rings.number_field (A vertex at (0.7071067811865475?, 0.4082482904638630?, 1.154700538379252?), A vertex at (0.7071067811865475?, 1.224744871391589?, 0.?e-18), A vertex at (1.414213562373095?, 0.?e-18, 0.?e-18), @@ -1187,11 +1229,11 @@ def affine_hull_projection(self, With the parameter ``minimal`` one can get a minimal base ring:: sage: s = polytopes.simplex(3) - sage: s_AA = s.affine_hull_projection(orthonormal=True, extend=True) - sage: s_AA.base_ring() + sage: s_AA = s.affine_hull_projection(orthonormal=True, extend=True) # optional - sage.rings.number_field + sage: s_AA.base_ring() # optional - sage.rings.number_field Algebraic Real Field - sage: s_full = s.affine_hull_projection(orthonormal=True, extend=True, minimal=True) - sage: s_full.base_ring() + sage: s_full = s.affine_hull_projection(orthonormal=True, extend=True, minimal=True) # optional - sage.rings.number_field + sage: s_full.base_ring() # optional - sage.rings.number_field Number Field in a with defining polynomial y^4 - 4*y^2 + 1 with a = 0.5176380902050415? More examples with the ``orthonormal`` parameter:: @@ -1445,21 +1487,25 @@ def _test_affine_hull_projection(self, tester=None, verbose=False, **options): # Avoid very long doctests. return - data_sets = [None]*4 - data_sets[0] = self.affine_hull_projection(return_all_data=True) + try: + from sage.rings.qqbar import AA + except ImportError: + AA = None + + data_sets = [] + data_sets.append(self.affine_hull_projection(return_all_data=True)) if self.is_compact(): - data_sets[1] = self.affine_hull_projection(return_all_data=True, - orthogonal=True, - extend=True) - data_sets[2] = self.affine_hull_projection(return_all_data=True, - orthonormal=True, - extend=True) - data_sets[3] = self.affine_hull_projection(return_all_data=True, - orthonormal=True, - extend=True, - minimal=True) - else: - data_sets = data_sets[:1] + data_sets.append(self.affine_hull_projection(return_all_data=True, + orthogonal=True, + extend=True)) + if AA is not None: + data_sets.append(self.affine_hull_projection(return_all_data=True, + orthonormal=True, + extend=True)) + data_sets.append(self.affine_hull_projection(return_all_data=True, + orthonormal=True, + extend=True, + minimal=True)) for i, data in enumerate(data_sets): if verbose: diff --git a/src/sage/geometry/polyhedron/base7.py b/src/sage/geometry/polyhedron/base7.py index 93138e3dc4f..db828e2eb0a 100644 --- a/src/sage/geometry/polyhedron/base7.py +++ b/src/sage/geometry/polyhedron/base7.py @@ -1,7 +1,5 @@ r""" -Base class for polyhedra, part 7 - -Define methods related to triangulation and volume. +Base class for polyhedra: Methods for triangulation and volume computation """ # **************************************************************************** @@ -37,7 +35,6 @@ from sage.modules.free_module_element import vector from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ -from sage.rings.qqbar import AA from .base6 import Polyhedron_base6 class Polyhedron_base7(Polyhedron_base6): @@ -218,9 +215,9 @@ def triangulate(self, engine='auto', connected=True, fine=False, regular=None, s OUTPUT: A triangulation of the convex hull of the vertices as a - :class:`~sage.geometry.triangulation.point_configuration.Triangulation`. The + :class:`~sage.geometry.triangulation.element.Triangulation`. The indices in the triangulation correspond to the - :meth:`Vrepresentation` objects. + :meth:`~sage.geometry.polyhedron.base0.Polyhedron_base0.Vrepresentation` objects. EXAMPLES:: @@ -517,7 +514,7 @@ def volume(self, measure='ambient', engine='auto', **kwds): When considering lower-dimensional polytopes, we can ask for the ambient (full-dimensional), the induced measure (of the affine hull) or, in the case of lattice polytopes, for the induced rational measure. - This is controlled by the parameter `measure`. Different engines + This is controlled by the parameter ``measure``. Different engines may have different ideas on the definition of volume of a lower-dimensional object:: @@ -714,6 +711,7 @@ def volume(self, measure='ambient', engine='auto', **kwds): if Adet.is_square(): sqrt_Adet = Adet.sqrt() else: + from sage.rings.qqbar import AA sqrt_Adet = AA(Adet).sqrt() scaled_volume = AA(scaled_volume) return scaled_volume / sqrt_Adet @@ -910,6 +908,7 @@ def integrate(self, function, measure='ambient', **kwds): A = affine_hull_data.projection_linear_map.matrix() Adet = (A.transpose() * A).det() try: + from sage.rings.qqbar import AA Adet = AA.coerce(Adet) except TypeError: pass diff --git a/src/sage/geometry/polyhedron/base_ZZ.py b/src/sage/geometry/polyhedron/base_ZZ.py index 07c7bbd6a4c..4255f0232df 100644 --- a/src/sage/geometry/polyhedron/base_ZZ.py +++ b/src/sage/geometry/polyhedron/base_ZZ.py @@ -17,7 +17,7 @@ from sage.misc.cachefunc import cached_method from sage.modules.free_module_element import vector from .base_QQ import Polyhedron_QQ -from sage.arith.all import gcd +from sage.arith.misc import gcd ######################################################################### diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx b/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx index f9c7d8e7dde..102f38c5eef 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx @@ -16,13 +16,12 @@ Terminology used in this module: - 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. -- bit representation -- represents incidences as bitset, where - each bit represents one incidence. There might - be trailing zeros, to fit alignment requirements. - In most instances, faces are represented by the - bit representation, where each bit corresponds to - a Vrep or facet. Thus a bit representation can either be - a Vrep or facet representation depending on context. +- bit representation -- represents incidences as bitset, where each bit + represents one incidence. There might be trailing zeros, to fit alignment + requirements. In most instances, faces are represented by the bit + representation, where each bit corresponds to a Vrep or facet. Thus a bit + representation can either be a Vrep or facet representation depending on + context. EXAMPLES: @@ -1552,6 +1551,7 @@ cdef class CombinatorialPolyhedron(SageObject): - ``algorithm`` -- string (optional); specify whether the face generator starts with facets or vertices: + * ``'primal'`` -- start with the facets * ``'dual'`` -- start with the vertices * ``None`` -- choose automatically @@ -1722,6 +1722,7 @@ cdef class CombinatorialPolyhedron(SageObject): - ``algorithm`` -- string (optional); specify whether the face generator starts with facets or vertices: + * ``'primal'`` -- start with the facets * ``'dual'`` -- start with the vertices * ``None`` -- choose automatically @@ -2678,6 +2679,7 @@ cdef class CombinatorialPolyhedron(SageObject): - ``algorithm`` -- string (optional); specify whether the face generator starts with facets or vertices: + * ``'primal'`` -- start with the facets * ``'dual'`` -- start with the vertices * ``None`` -- choose automatically diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pyx b/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pyx index 5b40ee42cf2..c1a0b996a8a 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pyx @@ -18,7 +18,7 @@ Obtain a face from a face iterator:: sage: face = next(it); face A 2-dimensional face of a 3-dimensional combinatorial polyhedron -Obtain a face from a face lattice index: +Obtain a face from a face lattice index:: sage: P = polytopes.simplex(2) sage: C = CombinatorialPolyhedron(P) @@ -859,7 +859,7 @@ cdef class CombinatorialFace(SageObject): 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 + 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`` diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pyx b/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pyx index 7e854219418..b8f9f0a27b0 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pyx @@ -12,16 +12,14 @@ A (slightly generalized) description of the algorithm can be found in [KS2019]_. Terminology in this module: -- Coatoms -- the faces from which all others are constructed in - the face iterator. This will be facets or Vrep. - In non-dual mode, faces are constructed as - intersections of the facets. In dual mode, the are - constructed theoretically as joins of vertices. - The coatoms are repsented as incidences with the - atoms they contain. -- Atoms -- facets or Vrep depending on application of algorithm. - Atoms are repsented as incidences of coatoms they - are contained in. +- Coatoms -- the faces from which all others are constructed in the + face iterator. This will be facets or Vrep. In non-dual mode, faces + are constructed as intersections of the facets. In dual mode, they + are constructed theoretically as joins of vertices. The coatoms are + repsented as incidences with the atoms they contain. + +- Atoms -- facets or Vrep depending on application of algorithm. Atoms are + represented as incidences of coatoms they are contained in. .. SEEALSO:: @@ -1119,7 +1117,7 @@ cdef class FaceIterator_base(SageObject): ... ValueError: only possible when not in dual mode - Cannot run ``only_subfaces`` after ``ignore_subfaces:: + Cannot run ``only_subfaces`` after ``ignore_subfaces``:: sage: it = C.face_generator() sage: _ = next(it) @@ -1466,84 +1464,93 @@ cdef class FaceIterator(FaceIterator_base): ALGORITHM: - For the special case that the all intervals of the lattice not containing zero are boolean - (e.g. when the polyhedron is simple) the algorithm is modified. See below. - - - A (slightly generalized) description of the algorithm can be found in [KS2019]_. - The algorithm to visit all proper faces exactly once is roughly - equivalent to:: + equivalent to the following. A (slightly generalized) description of the + algorithm can be found in [KS2019]_. + + Initialization:: faces = [set(facet) for facet in P.facets()] face_iterator(faces, []) + The function ``face_iterator`` is defined recursively. It visits all faces of + the polyhedron `P`, except those contained in any of ``visited_all``. + It assumes ``faces`` to be exactly those facets of `P` + that are not contained in any of the ``visited_all``. + It assumes ``visited_all`` to be some list of faces of + a polyhedron `P_2`, which contains `P` as one of its faces:: + def face_iterator(faces, visited_all): - # Visit all faces of a polyhedron `P`, except those contained in - # any of the visited all. + while facets: + one_face = faces.pop() + maybe_new_faces = [one_face.intersection(face) for face in faces] + ... - # Assumes ``faces`` to be exactly those facets of `P` - # that are not contained in any of the ``visited_all``. + At this point we claim that ``maybe_new_faces`` contains all facets of ``one_face``, + which we have not visited before. - # Assumes ``visited_all`` to be some list of faces of - # a polyhedron `P_2`, which contains `P` as one of its faces. + Proof: Let `F` be a facet of ``one_face``. We have a chain: + `P \supset{}` ``one_face`` `{}\supset F`. + By the diamond property, there exists a ``second_face`` with + `P \supset{}` ``second_face`` `{}\supset F`. - while facets: - one_face = faces.pop() - new_faces = [one_face.intersection(face) for face in faces] - - # ``maybe_new_faces`` contains all facets of ``one_face``, - # which we have not visited before. - # Proof: Let `F` be a facet of ``one_face``. - # We have a chain: - # `P` ⊃ ``one_face`` ⊃ `F`. - # By diamond property there exists ``second_face`` with: - # `P` ⊃ ``second_face`` ⊃ `F`. - - # Either ``second_face`` is not an element of ``faces``: - # Hence ``second_face`` is contained in one of ``visited_all``. - # In particular, `F` is contained in ``visited_all``. - # Or ``second_face`` is an element of ``faces``: - # Then, intersecting ``one_face`` with ``second_face`` gives - # ``F``. ∎ - - # If an element in ``maybe_new_faces`` is inclusion maximal - # and not contained in any of the ``visited_all``, - # it is a facet of ``one_face``. - # Any facet in ``maybe_new_faces`` of ``one_face`` - # is inclusion maximal. + Now either ``second_face`` is not an element of ``faces``: + Hence ``second_face`` is contained in one of ``visited_all``. + In particular, `F` is contained in ``visited_all``. + + Or ``second_face`` is an element of ``faces``: + Then, intersecting ``one_face`` with ``second_face`` gives ``F``. + + This concludes the proof. + + Moreover, if an element in ``maybe_new_faces`` is inclusion-maximal + and not contained in any of the ``visited_all``, it is a facet of ``one_face``. + Any facet in ``maybe_new_faces`` of ``one_face`` is inclusion-maximal. + + Hence, in the following loop, an element ``face1`` in ``maybe_new_faces`` + is a facet of ``one_face`` if and only if it is not contained in another facet:: + + ... maybe_new_faces2 = [] for i, face1 in enumerate(maybe_new_faces): - # ``face1`` is a facet of ``one_face``, - # iff it is not contained in another facet. if (all(not face1 < face2 for face2 in maybe_new_faces[:i]) and all(not face1 <= face2 for face2 in maybe_new_faces[i+1:])): maybe_new_faces2.append(face1) + ... - # ``maybe_new_faces2`` contains only facets of ``one_face`` - # and some faces contained in any of ``visited_all``. - # It also contains all the facets not contained in any of ``visited_all``. - # Let ``new_faces`` be the list of all facets of ``one_face`` - # not contained in any of ``visited_all``. + Now ``maybe_new_faces2`` contains only facets of ``one_face`` + and some faces contained in any of ``visited_all``. + It also contains all the facets not contained in any of ``visited_all``. + + We construct ``new_faces`` as the list of all facets of ``one_face`` + not contained in any of ``visited_all``:: + + ... new_faces = [] for face1 in maybe_new_faces2: if all(not face1 < face2 for face2 in visited_all): new_faces.append(face1) + ... + + By induction we can apply the algorithm, to visit all + faces of ``one_face`` not contained in ``visited_all``:: - # By induction we can apply the algorithm, to visit all - # faces of ``one_face`` not contained in ``visited_all``: + ... face_iterator(new_faces, visited_all) + ... + + Finally we visit ``one_face`` and add it to ``visited_all``:: - # Finally visit ``one_face`` and add it to ``visited_all``: + ... visit(one_face) visited_all.append(one_face) - # Note: At this point, we have visited exactly those faces, - # contained in any of the ``visited_all``. + Note: At this point, we have visited exactly those faces, + contained in any of the ``visited_all``. The function ends here. - For the special case that the all intervals of the lattice not containing zero are boolean - (e.g. when the polyhedron is simple), the algorithm can be modified. + ALGORITHM for the special case that all intervals of the lattice not + containing zero are boolean (e.g. when the polyhedron is simple): We do not assume any other properties of our lattice in this case. Note that intervals of length 2 not containing zero, have exactly 2 elements now. 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 9fd5e1a2f14..4e9ba7e0e99 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pyx @@ -8,28 +8,29 @@ the face lattice of a polyhedron. Terminology in this module: -- Vrep -- ``[vertices, rays, lines]`` 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. - In non-dual mode, faces are constructed as - intersections of the facets. In dual mode, the are - constructed theoretically as joins of vertices. - The coatoms are repsented as incidences with the - atoms they contain. -- Atoms -- facets or Vrep depending on application of algorithm. - Atoms are repsented as incidences of coatoms they - are contained in. - -- Vrepresentation -- represents a face by a list of Vrep it contains. -- Hrepresentation -- represents a face by a list of Hrep it is contained in. -- bit representation -- represents incidences as ``uint64_t``-array, where - each Bit represents one incidences. There might - be trailing zeros, to fit alignment-requirements. - In most instances, faces are represented by the - Bit-representation, where each bit corresponds to - an atom. +- Vrep -- ``[vertices, rays, lines]`` 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. In non-dual mode, faces are + constructed as intersections of the facets. In dual mode, the are constructed + theoretically as joins of vertices. The coatoms are repsented as incidences + with the atoms they contain. + +- Atoms -- facets or Vrep depending on application of algorithm. Atoms are + repsented as incidences of coatoms they are contained in. + +- Vrepresentation -- represents a face by a list of Vrep it contains. + +- Hrepresentation -- represents a face by a list of Hrep it is contained in. + +- bit representation -- represents incidences as ``uint64_t``-array, where each + bit represents one incidence. There might be trailing zeros, to fit alignment + requirements. In most instances, faces are represented by the bit + representation, where each bit corresponds to an atom. EXAMPLES:: diff --git a/src/sage/geometry/polyhedron/face.py b/src/sage/geometry/polyhedron/face.py index 52304d08d75..d9dd5a6c4d6 100644 --- a/src/sage/geometry/polyhedron/face.py +++ b/src/sage/geometry/polyhedron/face.py @@ -1056,7 +1056,7 @@ def combinatorial_face_to_polyhedral_face(polyhedron, combinatorial_face): 4 sage: polytopes.simplex(backend='normaliz').equations()[0].index() # optional - pynormaliz 4 - sage: polytopes.simplex(backend='polymake').equations()[0].index() # optional - polymake + sage: polytopes.simplex(backend='polymake').equations()[0].index() # optional - jupymake 4 """ V_indices = combinatorial_face.ambient_V_indices() diff --git a/src/sage/geometry/polyhedron/parent.py b/src/sage/geometry/polyhedron/parent.py index 881c614238a..ba1987b1fe2 100644 --- a/src/sage/geometry/polyhedron/parent.py +++ b/src/sage/geometry/polyhedron/parent.py @@ -65,7 +65,7 @@ def Polyhedra(ambient_space_or_base_ring=None, ambient_dim=None, backend=None, * EXAMPLES:: sage: from sage.geometry.polyhedron.parent import Polyhedra - sage: Polyhedra(AA, 3) + sage: Polyhedra(AA, 3) # optional - sage.rings.number_field Polyhedra in AA^3 sage: Polyhedra(ZZ, 3) Polyhedra in ZZ^3 @@ -105,11 +105,11 @@ def Polyhedra(ambient_space_or_base_ring=None, ambient_dim=None, backend=None, * Traceback (most recent call last): ... ValueError: no default backend for computations with Real Field with 53 bits of precision - sage: Polyhedra(QQ[I], 2) + sage: Polyhedra(QQ[I], 2) # optional - sage.rings.number_field Traceback (most recent call last): ... ValueError: invalid base ring: Number Field in I with defining polynomial x^2 + 1 with I = 1*I cannot be coerced to a real field - sage: Polyhedra(AA, 3, backend='polymake') # optional - polymake + sage: Polyhedra(AA, 3, backend='polymake') # optional - jupymake # optional - sage.rings.number_field Traceback (most recent call last): ... ValueError: the 'polymake' backend for polyhedron cannot be used with Algebraic Real Field @@ -174,8 +174,9 @@ def Polyhedra(ambient_space_or_base_ring=None, ambient_dim=None, backend=None, * elif backend == 'polymake': base_field = base_ring.fraction_field() try: - from sage.interfaces.polymake import polymake + from sage.interfaces.polymake import polymake, PolymakeElement polymake_base_field = polymake(base_field) + assert isinstance(polymake_base_field, PolymakeElement) # to muffle pyflakes except TypeError: raise ValueError(f"the 'polymake' backend for polyhedron cannot be used with {base_field}") return Polyhedra_polymake(base_field, ambient_dim, backend) @@ -264,13 +265,13 @@ def list(self): sage: P.cardinality() +Infinity - sage: P = Polyhedra(AA, 0) - sage: P.category() + sage: P = Polyhedra(AA, 0) # optional - sage.rings.number_field + sage: P.category() # optional - sage.rings.number_field Category of finite enumerated polyhedral sets over Algebraic Real Field - sage: P.list() + sage: P.list() # optional - sage.rings.number_field [The empty polyhedron in AA^0, A 0-dimensional polyhedron in AA^0 defined as the convex hull of 1 vertex] - sage: P.cardinality() + sage: P.cardinality() # optional - sage.rings.number_field 2 """ if self.ambient_dim(): @@ -495,6 +496,35 @@ def Hrepresentation_space(self): from sage.modules.free_module import FreeModule return FreeModule(self.base_ring(), self.ambient_dim()+1) + def _repr_base_ring(self): + """ + Return an abbreviated string representation of the base ring. + + EXAMPLES:: + + sage: from sage.geometry.polyhedron.parent import Polyhedra + sage: Polyhedra(QQ, 3)._repr_base_ring() + 'QQ' + sage: K. = NumberField(x^2 - 3, embedding=AA(3).sqrt()) # optional - sage.rings.number_field + sage: Polyhedra(K, 4)._repr_base_ring() # optional - sage.rings.number_field + '(Number Field in sqrt3 with defining polynomial x^2 - 3 with sqrt3 = 1.732050807568878?)' + """ + + if self.base_ring() is ZZ: + return 'ZZ' + if self.base_ring() is QQ: + return 'QQ' + if self.base_ring() is RDF: + return 'RDF' + try: + from sage.rings.qqbar import AA + except ImportError: + pass + else: + if self.base_ring() is AA: + return 'AA' + return '({0})'.format(self.base_ring()) + def _repr_ambient_module(self): """ Return an abbreviated string representation of the ambient @@ -509,21 +539,11 @@ def _repr_ambient_module(self): sage: from sage.geometry.polyhedron.parent import Polyhedra sage: Polyhedra(QQ, 3)._repr_ambient_module() 'QQ^3' - sage: K. = NumberField(x^2 - 3, embedding=AA(3).sqrt()) - sage: Polyhedra(K, 4)._repr_ambient_module() + sage: K. = NumberField(x^2 - 3, embedding=AA(3).sqrt()) # optional - sage.rings.number_field + sage: Polyhedra(K, 4)._repr_ambient_module() # optional - sage.rings.number_field '(Number Field in sqrt3 with defining polynomial x^2 - 3 with sqrt3 = 1.732050807568878?)^4' """ - from sage.rings.qqbar import AA - if self.base_ring() is ZZ: - s = 'ZZ' - elif self.base_ring() is QQ: - s = 'QQ' - elif self.base_ring() is RDF: - s = 'RDF' - elif self.base_ring() is AA: - s = 'AA' - else: - s = '({0})'.format(self.base_ring()) + s = self._repr_base_ring() s += '^' + repr(self.ambient_dim()) return s @@ -602,11 +622,11 @@ def _element_constructor_(self, *args, **kwds): When the parent of the object is not ``self``, the default is not to copy:: - sage: Q = P.base_extend(AA) - sage: q = Q._element_constructor_(p) - sage: q is p + sage: Q = P.base_extend(AA) # optional - sage.rings.number_field + sage: q = Q._element_constructor_(p) # optional - sage.rings.number_field + sage: q is p # optional - sage.rings.number_field False - sage: q = Q._element_constructor_(p, copy=False) + sage: q = Q._element_constructor_(p, copy=False) # optional - sage.rings.number_field Traceback (most recent call last): ... ValueError: you need to make a copy when changing the parent @@ -693,9 +713,10 @@ def _element_constructor_polyhedron(self, polyhedron, **kwds): sage: P(p) A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 4 vertices - sage: P = Polyhedra(AA, 3, backend='field') - sage: p = Polyhedron(vertices=[(0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1)]) - sage: P(p) + sage: P = Polyhedra(AA, 3, backend='field') # optional - sage.rings.number_field + sage: vertices = [(0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1)] + sage: p = Polyhedron(vertices=vertices) # optional - sage.rings.number_field + sage: P(p) # optional - sage.rings.number_field A 3-dimensional polyhedron in AA^3 defined as the convex hull of 4 vertices """ Vrep = None @@ -1234,9 +1255,9 @@ def does_backend_handle_base_ring(base_ring, backend): sage: from sage.geometry.polyhedron.parent import does_backend_handle_base_ring sage: does_backend_handle_base_ring(QQ, 'ppl') True - sage: does_backend_handle_base_ring(QQ[sqrt(5)], 'ppl') + sage: does_backend_handle_base_ring(QQ[sqrt(5)], 'ppl') # optional - sage.rings.number_field False - sage: does_backend_handle_base_ring(QQ[sqrt(5)], 'field') + sage: does_backend_handle_base_ring(QQ[sqrt(5)], 'field') # optional - sage.rings.number_field True """ try: diff --git a/src/sage/geometry/polyhedron/plot.py b/src/sage/geometry/polyhedron/plot.py index df157066c43..6e40f119c1e 100644 --- a/src/sage/geometry/polyhedron/plot.py +++ b/src/sage/geometry/polyhedron/plot.py @@ -1228,11 +1228,13 @@ def render_3d(self, point_opts=None, line_opts=None, polygon_opts=None): def tikz(self, view=[0, 0, 1], angle=0, scale=1, edge_color='blue!95!black', facet_color='blue!95!black', - opacity=0.8, vertex_color='green', axis=False): + opacity=0.8, vertex_color='green', axis=False, + output_type=None): r""" - Return a string ``tikz_pic`` consisting of a tikz picture of ``self`` + Return a tikz picture of ``self`` as a string or as a + :class:`~sage.misc.latex_standalone.TikzPicture` according to a projection ``view`` and an angle ``angle`` - obtained via Jmol through the current state property. + obtained via the threejs viewer. INPUT: @@ -1249,10 +1251,15 @@ def tikz(self, view=[0, 0, 1], angle=0, scale=1, - ``opacity`` - real number (default: 0.8) between 0 and 1 giving the opacity of the front facets. - ``axis`` - Boolean (default: False) draw the axes at the origin or not. + - ``output_type`` - string (default: ``None``), valid values + are ``None`` (deprecated), ``'LatexExpr'`` and ``'TikzPicture'``, + whether to return a LatexExpr object (which inherits from Python + str) or a ``TikzPicture`` object from module + :mod:`sage.misc.latex_standalone` OUTPUT: - - LatexExpr -- containing the TikZ picture. + - LatexExpr object or TikzPicture object .. NOTE:: @@ -1285,19 +1292,56 @@ def tikz(self, view=[0, 0, 1], angle=0, scale=1, EXAMPLES:: sage: P1 = polytopes.small_rhombicuboctahedron() - sage: Image1 = P1.projection().tikz([1,3,5], 175, scale=4) + sage: Image1 = P1.projection().tikz([1,3,5], 175, scale=4, output_type='TikzPicture') sage: type(Image1) - - sage: print('\n'.join(Image1.splitlines()[:4])) + + sage: Image1 + \documentclass[tikz]{standalone} + \begin{document} \begin{tikzpicture}% - [x={(-0.939161cm, 0.244762cm)}, - y={(0.097442cm, -0.482887cm)}, - z={(0.329367cm, 0.840780cm)}, - sage: with open('polytope-tikz1.tex', 'w') as f: # not tested - ....: _ = f.write(Image1) + [x={(-0.939161cm, 0.244762cm)}, + y={(0.097442cm, -0.482887cm)}, + z={(0.329367cm, 0.840780cm)}, + scale=4.000000, + ... + Use print to see the full content. + ... + \node[vertex] at (-2.41421, 1.00000, -1.00000) {}; + \node[vertex] at (-2.41421, -1.00000, 1.00000) {}; + %% + %% + \end{tikzpicture} + \end{document} + sage: _ = Image1.tex('polytope-tikz1.tex') # not tested + sage: _ = Image1.png('polytope-tikz1.png') # not tested + sage: _ = Image1.pdf('polytope-tikz1.pdf') # not tested + sage: _ = Image1.svg('polytope-tikz1.svg') # not tested + + A second example:: sage: P2 = Polyhedron(vertices=[[1, 1],[1, 2],[2, 1]]) - sage: Image2 = P2.projection().tikz(scale=3, edge_color='blue!95!black', facet_color='orange!95!black', opacity=0.4, vertex_color='yellow', axis=True) + sage: Image2 = P2.projection().tikz(scale=3, edge_color='blue!95!black', facet_color='orange!95!black', opacity=0.4, vertex_color='yellow', axis=True, output_type='TikzPicture') + sage: Image2 + \documentclass[tikz]{standalone} + \begin{document} + \begin{tikzpicture}% + [scale=3.000000, + back/.style={loosely dotted, thin}, + edge/.style={color=blue!95!black, thick}, + facet/.style={fill=orange!95!black,fill opacity=0.400000}, + ... + Use print to see the full content. + ... + \node[vertex] at (1.00000, 2.00000) {}; + \node[vertex] at (2.00000, 1.00000) {}; + %% + %% + \end{tikzpicture} + \end{document} + + The second example using a LatexExpr as output type:: + + sage: Image2 = P2.projection().tikz(scale=3, edge_color='blue!95!black', facet_color='orange!95!black', opacity=0.4, vertex_color='yellow', axis=True, output_type='LatexExpr') sage: type(Image2) sage: print('\n'.join(Image2.splitlines()[:4])) @@ -1308,22 +1352,40 @@ def tikz(self, view=[0, 0, 1], angle=0, scale=1, sage: with open('polytope-tikz2.tex', 'w') as f: # not tested ....: _ = f.write(Image2) + A third example:: + sage: P3 = Polyhedron(vertices=[[-1, -1, 2],[-1, 2, -1],[2, -1, -1]]) sage: P3 A 2-dimensional polyhedron in ZZ^3 defined as the convex hull of 3 vertices - sage: Image3 = P3.projection().tikz([0.5,-1,-0.1], 55, scale=3, edge_color='blue!95!black',facet_color='orange!95!black', opacity=0.7, vertex_color='yellow', axis=True) - sage: print('\n'.join(Image3.splitlines()[:4])) + sage: Image3 = P3.projection().tikz([0.5,-1,-0.1], 55, scale=3, edge_color='blue!95!black',facet_color='orange!95!black', opacity=0.7, vertex_color='yellow', axis=True, output_type='TikzPicture') + sage: Image3 + \documentclass[tikz]{standalone} + \begin{document} \begin{tikzpicture}% - [x={(0.658184cm, -0.242192cm)}, - y={(-0.096240cm, 0.912008cm)}, - z={(-0.746680cm, -0.331036cm)}, - sage: with open('polytope-tikz3.tex', 'w') as f: # not tested - ....: _ = f.write(Image3) + [x={(0.658184cm, -0.242192cm)}, + y={(-0.096240cm, 0.912008cm)}, + z={(-0.746680cm, -0.331036cm)}, + scale=3.000000, + ... + Use print to see the full content. + ... + \node[vertex] at (-1.00000, 2.00000, -1.00000) {}; + \node[vertex] at (2.00000, -1.00000, -1.00000) {}; + %% + %% + \end{tikzpicture} + \end{document} + sage: _ = Image3.tex('polytope-tikz3.tex') # not tested + sage: _ = Image3.png('polytope-tikz3.png') # not tested + sage: _ = Image3.pdf('polytope-tikz3.pdf') # not tested + sage: _ = Image3.svg('polytope-tikz3.svg') # not tested + + A fourth example:: sage: P = Polyhedron(vertices=[[1,1,0,0],[1,2,0,0],[2,1,0,0],[0,0,1,0],[0,0,0,1]]) sage: P A 4-dimensional polyhedron in ZZ^4 defined as the convex hull of 5 vertices - sage: P.projection().tikz() + sage: P.projection().tikz(output_type='TikzPicture') Traceback (most recent call last): ... NotImplementedError: The polytope has to live in 2 or 3 dimensions. @@ -1335,7 +1397,7 @@ def tikz(self, view=[0, 0, 1], angle=0, scale=1, sage: P=Polyhedron(vertices=[[1,1,0,0],[1,2,0,0],[2,1,0,0],[0,0,1,0],[0,0,0,1]]) sage: P A 4-dimensional polyhedron in ZZ^4 defined as the convex hull of 5 vertices - sage: P.projection().tikz() + sage: P.projection().tikz(output_type='TikzPicture') Traceback (most recent call last): ... NotImplementedError: The polytope has to live in 2 or 3 dimensions. @@ -1347,15 +1409,40 @@ def tikz(self, view=[0, 0, 1], angle=0, scale=1, elif self.polyhedron_dim < 2 or self.polyhedron_dim > 3: raise NotImplementedError("The polytope has to be 2 or 3-dimensional.") elif self.polyhedron_ambient_dim == 2: # self is a polygon in 2-space - return self._tikz_2d(scale, edge_color, facet_color, opacity, + tikz_string = self._tikz_2d(scale, edge_color, facet_color, opacity, vertex_color, axis) elif self.polyhedron_dim == 2: # self is a polygon in 3-space - return self._tikz_2d_in_3d(view, angle, scale, edge_color, + tikz_string = self._tikz_2d_in_3d(view, angle, scale, edge_color, facet_color, opacity, vertex_color, axis) else: # self is a 3-polytope in 3-space - return self._tikz_3d_in_3d(view, angle, scale, edge_color, + tikz_string = self._tikz_3d_in_3d(view, angle, scale, edge_color, facet_color, opacity, vertex_color, axis) + # set default value + if output_type is None: + from sage.misc.superseded import deprecation + msg = ("The default type of the returned object will soon be " + "changed from `sage.misc.latex.LatexExpr` to " + "`sage.misc.latex_standalone.TikzPicture`. Please " + "update your code to specify the desired output type as " + "`.tikz(output_type='LatexExpr')` to keep the old " + "behavior or `.tikz(output_type='TikzPicture')` to use " + "the future default behavior.") + deprecation(33002, msg) + output_type = 'LatexExpr' + + # return + if output_type == 'LatexExpr': + return tikz_string + elif output_type == 'TikzPicture': + from sage.misc.latex_standalone import TikzPicture + return TikzPicture(tikz_string, standalone_config=None, + usepackage=None, usetikzlibrary=None, macros=None, + use_sage_preamble=False) + else: + raise ValueError("output_type (='{}') must be 'LatexExpr' or" + " 'TikzPicture'".format(output_type)) + def _tikz_2d(self, scale, edge_color, facet_color, opacity, vertex_color, axis): r""" Return a string ``tikz_pic`` consisting of a tikz picture of @@ -1394,9 +1481,9 @@ def _tikz_2d(self, scale, edge_color, facet_color, opacity, vertex_color, axis): Scientific notation is not used in the output (:trac:`16519`):: - sage: P=Polyhedron([[2*10^-10,0],[0,1],[1,0]],base_ring=QQ) - sage: tikzstr=P.projection().tikz() - sage: 'e-10' in tikzstr + sage: P = Polyhedron([[2*10^-10,0],[0,1],[1,0]],base_ring=QQ) + sage: tikz = P.projection().tikz(output_type='TikzPicture') + sage: 'e-10' in tikz.content() False .. NOTE:: @@ -1522,9 +1609,11 @@ def _tikz_2d_in_3d(self, view, angle, scale, edge_color, facet_color, sage: with open('polytope-tikz3.tex', 'w') as f: # not tested ....: _ = f.write(Image) + :: + sage: p = Polyhedron(vertices=[[1,0,0],[0,1,0],[0,0,1]]) sage: proj = p.projection() - sage: Img = proj.tikz([1,1,1],130,axis=True) + sage: Img = proj.tikz([1,1,1],130,axis=True, output_type='LatexExpr') sage: print('\n'.join(Img.splitlines()[12:21])) %% with the command: ._tikz_2d_in_3d and parameters: %% view = [1, 1, 1] @@ -1670,8 +1759,10 @@ def _tikz_3d_in_3d(self, view, angle, scale, edge_color, sage: with open('polytope-tikz1.tex', 'w') as f: # not tested ....: _ = f.write(Image) + :: + sage: Associahedron = Polyhedron(vertices=[[1,0,1],[1,0,0],[1,1,0],[0,0,-1],[0,1,0],[-1,0,0],[0,1,1],[0,0,1],[0,-1,0]]).polar() - sage: ImageAsso = Associahedron.projection().tikz([-15,-755,-655], 116, scale=1) + sage: ImageAsso = Associahedron.projection().tikz([-15,-755,-655], 116, scale=1, output_type='LatexExpr') sage: print('\n'.join(ImageAsso.splitlines()[12:30])) %% with the command: ._tikz_3d_in_3d and parameters: %% view = [-15, -755, -655] @@ -1816,9 +1907,10 @@ def _tikz_3d_in_3d(self, view, angle, scale, edge_color, # Draw the facets in the front by going in cycles for every facet. tikz_pic += '%%\n%%\n%% Drawing the facets\n%%\n' + vertex_to_index = {v: i for i, v in enumerate(vertices)} for index_facet in front_facets: cyclic_vert = cyclic_sort_vertices_2d(list(facets[index_facet].incident())) - cyclic_indices = [vertices.index(v) for v in cyclic_vert] + cyclic_indices = [vertex_to_index[v] for v in cyclic_vert] tikz_pic += '\\fill[facet] ' for v in cyclic_indices: if v in dict_drawing: diff --git a/src/sage/geometry/polyhedron/ppl_lattice_polytope.py b/src/sage/geometry/polyhedron/ppl_lattice_polytope.py index 5698d71a045..d45f8a3991a 100644 --- a/src/sage/geometry/polyhedron/ppl_lattice_polytope.py +++ b/src/sage/geometry/polyhedron/ppl_lattice_polytope.py @@ -671,7 +671,8 @@ def pointsets_mod_automorphism(self, pointsets): points = tuple(sorted(points)) Aut = self.lattice_automorphism_group(points, point_labels=tuple(range(len(points)))) - indexsets = set(frozenset(points.index(p) for p in ps) + point_to_index = {p: i for i, p in enumerate(points)} + indexsets = set(frozenset(point_to_index[p] for p in ps) for ps in pointsets) orbits = [] while indexsets: diff --git a/src/sage/geometry/pseudolines.py b/src/sage/geometry/pseudolines.py index 642a5c4b8aa..37b9ee324ca 100644 --- a/src/sage/geometry/pseudolines.py +++ b/src/sage/geometry/pseudolines.py @@ -168,7 +168,7 @@ class PseudolineArrangement: - def __init__(self, seq, encoding = "auto"): + def __init__(self, seq, encoding="auto"): r""" Creates an arrangement of pseudolines. @@ -463,8 +463,7 @@ def show(self, **args): L += text(str(i), (0, l[0][1]+.3), horizontal_alignment="right") L += text(str(i), (x+2, l[-1][1]+.3), horizontal_alignment="left") - return L.show(axes = False, **args) - + return L.show(axes=False, **args) def __repr__(self): r""" @@ -510,4 +509,4 @@ def __ne__(self, other): sage: p1 != p2 False """ - return not(self == other) + return not (self == other) diff --git a/src/sage/geometry/relative_interior.py b/src/sage/geometry/relative_interior.py index 8f463170aad..262c927a08a 100644 --- a/src/sage/geometry/relative_interior.py +++ b/src/sage/geometry/relative_interior.py @@ -276,6 +276,22 @@ def _some_elements_(self): if p in self: yield p + def representative_point(self): + """ + Return a "generic" point of ``self``. + + OUTPUT: + + A point in ``self`` (thus, in the relative interior of ``self``) as a coordinate vector. + + EXAMPLES:: + + sage: C = Cone([[1, 2, 0], [2, 1, 0]]) + sage: C.relative_interior().representative_point() + (1, 1, 0) + """ + return self._polyhedron.representative_point() + def _repr_(self): r""" Return a description of ``self``. diff --git a/src/sage/geometry/triangulation/element.py b/src/sage/geometry/triangulation/element.py index cf58a76187b..29de26bbc13 100644 --- a/src/sage/geometry/triangulation/element.py +++ b/src/sage/geometry/triangulation/element.py @@ -193,23 +193,19 @@ def triangulation_render_3d(triangulation, **kwds): exterior_triangs = [l for l in all_triangs if l not in interior_triangs] plot_interior_triangs = \ - sum([ polygon3d([coord[t[0]], coord[t[1]], coord[t[2]]], - texture = triang_int, **kwds) - for t in interior_triangs ]) + sum([polygon3d([coord[t[0]], coord[t[1]], coord[t[2]]], + texture=triang_int, **kwds) + for t in interior_triangs]) plot_exterior_triangs = \ - sum([ polygon3d([coord[t[0]], coord[t[1]], coord[t[2]]], - texture = triang_ext, **kwds) - for t in exterior_triangs ]) + sum([polygon3d([coord[t[0]], coord[t[1]], coord[t[2]]], + texture=triang_ext, **kwds) + for t in exterior_triangs]) - return \ - plot_points + \ + return plot_points + \ plot_interior_lines + plot_exterior_lines + \ plot_interior_triangs + plot_exterior_triangs - - - ######################################################################## class Triangulation(Element): """ @@ -227,8 +223,9 @@ class Triangulation(Element): """ def __init__(self, triangulation, parent, check=True): """ - The constructor of a ``Triangulation`` object. Note that an - internal reference to the underlying ``PointConfiguration`` is + The constructor of a ``Triangulation`` object. + + Note that an internal reference to the underlying ``PointConfiguration`` is kept. INPUT: @@ -236,12 +233,11 @@ def __init__(self, triangulation, parent, check=True): - ``parent`` -- a :class:`~sage.geometry.triangulation.point_configuration.PointConfiguration` - - ``triangulation`` -- an iterable of integers or iterable of - iterables (e.g. a list of lists). In the first case, the - integers specify simplices via - :meth:`PointConfiguration.simplex_to_int`. In the second - case, the point indices of the maximal simplices of the - triangulation. + - ``triangulation`` -- an iterable of integers or an iterable of + iterables (e.g. a list of lists), specifying the maximal simplices + of the triangulation. In the first case, each integer specifies a simplex + by the correspondence :meth:`PointConfiguration.simplex_to_int`. In the second + case, a simplex is specified by listing the indices of the included points. - ``check`` -- boolean. Whether to perform checks that the triangulation is, indeed, a triangulation of the point @@ -370,7 +366,7 @@ def __getitem__(self, i): def __len__(self): """ - Returns the length of the triangulation. + Return the length of the triangulation. TESTS:: @@ -572,8 +568,7 @@ def fan(self, origin=None): @cached_method def simplicial_complex(self): r""" - Return a simplicial complex from a triangulation of the point - configuration. + Return ``self`` as an (abstract) simplicial complex. OUTPUT: @@ -598,7 +593,7 @@ def simplicial_complex(self): @cached_method def _boundary_simplex_dictionary(self): """ - Return facets and the simplices they bound + Return facets and the simplices they bound. TESTS:: @@ -675,6 +670,38 @@ def boundary(self): in self._boundary_simplex_dictionary().items() if len(bounded_simplices) == 1) + @cached_method + def boundary_simplicial_complex(self): + r""" + Return the boundary of ``self`` as an (abstract) simplicial complex. + + OUTPUT: + + A :class:`~sage.topology.simplicial_complex.SimplicialComplex`. + + EXAMPLES:: + + sage: p = polytopes.cuboctahedron() + sage: triangulation = p.triangulate(engine='internal') + sage: bd_sc = triangulation.boundary_simplicial_complex() + sage: bd_sc + Simplicial complex with 12 vertices and 20 facets + + The boundary of every convex set is a topological sphere, so it has + spherical homology:: + + sage: bd_sc.homology() + {0: 0, 1: 0, 2: Z} + + It is a subcomplex of ``self`` as a :meth:`simplicial_complex`:: + + sage: sc = triangulation.simplicial_complex() + sage: all(f in sc for f in bd_sc.maximal_faces()) + True + """ + from sage.topology.simplicial_complex import SimplicialComplex + return SimplicialComplex(self.boundary(), maximality_check=False) + @cached_method def interior_facets(self): """ @@ -711,6 +738,90 @@ def interior_facets(self): in self._boundary_simplex_dictionary().items() if len(bounded_simplices) == 2) + def polyhedral_complex(self, **kwds): + """ + Return ``self`` as a :class:`~sage.geometry.polyhedral_complex.PolyhedralComplex`. + + OUTPUT: + + A :class:`~sage.geometry.polyhedral_complex.PolyhedralComplex` whose maximal cells + are the simplices of the triangulation. + + EXAMPLES:: + + sage: P = polytopes.cube() + sage: pc = PointConfiguration(P.vertices()) + sage: T = pc.placing_triangulation(); T + (<0,1,2,7>, <0,1,5,7>, <0,2,3,7>, <0,3,4,7>, <0,4,5,7>, <1,5,6,7>) + sage: C = T.polyhedral_complex(); C + Polyhedral complex with 6 maximal cells + sage: [P.vertices_list() for P in C.maximal_cells_sorted()] + [[[-1, -1, -1], [-1, -1, 1], [-1, 1, 1], [1, -1, -1]], + [[-1, -1, -1], [-1, 1, -1], [-1, 1, 1], [1, 1, -1]], + [[-1, -1, -1], [-1, 1, 1], [1, -1, -1], [1, 1, -1]], + [[-1, -1, 1], [-1, 1, 1], [1, -1, -1], [1, -1, 1]], + [[-1, 1, 1], [1, -1, -1], [1, -1, 1], [1, 1, 1]], + [[-1, 1, 1], [1, -1, -1], [1, 1, -1], [1, 1, 1]]] + """ + from sage.geometry.polyhedral_complex import PolyhedralComplex + from sage.geometry.polyhedron.constructor import Polyhedron + ambient_dim = self.point_configuration().ambient_dim() + points = self.point_configuration().points() + return PolyhedralComplex([Polyhedron(vertices=[points[i] for i in simplex]) + for simplex in self], + ambient_dim=ambient_dim, + maximality_check=False, + face_to_face_check=False, + **kwds) + + def boundary_polyhedral_complex(self, **kwds): + r""" + Return the boundary of ``self`` as a :class:`~sage.geometry.polyhedral_complex.PolyhedralComplex`. + + OUTPUT: + + A :class:`~sage.geometry.polyhedral_complex.PolyhedralComplex` whose maximal cells + are the simplices of the boundary of ``self``. + + EXAMPLES:: + + sage: P = polytopes.cube() + sage: pc = PointConfiguration(P.vertices()) + sage: T = pc.placing_triangulation(); T + (<0,1,2,7>, <0,1,5,7>, <0,2,3,7>, <0,3,4,7>, <0,4,5,7>, <1,5,6,7>) + sage: bd_C = T.boundary_polyhedral_complex(); bd_C + Polyhedral complex with 12 maximal cells + sage: [P.vertices_list() for P in bd_C.maximal_cells_sorted()] + [[[-1, -1, -1], [-1, -1, 1], [-1, 1, 1]], + [[-1, -1, -1], [-1, -1, 1], [1, -1, -1]], + [[-1, -1, -1], [-1, 1, -1], [-1, 1, 1]], + [[-1, -1, -1], [-1, 1, -1], [1, 1, -1]], + [[-1, -1, -1], [1, -1, -1], [1, 1, -1]], + [[-1, -1, 1], [-1, 1, 1], [1, -1, 1]], + [[-1, -1, 1], [1, -1, -1], [1, -1, 1]], + [[-1, 1, -1], [-1, 1, 1], [1, 1, -1]], + [[-1, 1, 1], [1, -1, 1], [1, 1, 1]], + [[-1, 1, 1], [1, 1, -1], [1, 1, 1]], + [[1, -1, -1], [1, -1, 1], [1, 1, 1]], + [[1, -1, -1], [1, 1, -1], [1, 1, 1]]] + + It is a subcomplex of ``self`` as a :meth:`polyhedral_complex`:: + + sage: C = T.polyhedral_complex() + sage: bd_C.is_subcomplex(C) + True + """ + from sage.geometry.polyhedral_complex import PolyhedralComplex + from sage.geometry.polyhedron.constructor import Polyhedron + ambient_dim = self.point_configuration().ambient_dim() + points = self.point_configuration().points() + return PolyhedralComplex([Polyhedron(vertices=[points[i] for i in simplex]) + for simplex in self.boundary()], + ambient_dim=ambient_dim, + maximality_check=False, + face_to_face_check=False, + **kwds) + @cached_method def normal_cone(self): r""" @@ -798,8 +909,7 @@ def normal_cone(self): def adjacency_graph(self): """ - Returns a graph showing which simplices are adjacent in the - triangulation + Return a graph showing which simplices are adjacent in the triangulation. OUTPUT: diff --git a/src/sage/geometry/triangulation/point_configuration.py b/src/sage/geometry/triangulation/point_configuration.py index 0efde128726..8d05c3bcb19 100644 --- a/src/sage/geometry/triangulation/point_configuration.py +++ b/src/sage/geometry/triangulation/point_configuration.py @@ -1377,8 +1377,7 @@ def secondary_polytope(self): #change the next line to only take the regular triangulations, #since they are the vertices of the secondary polytope anyway. l = self.triangulations_list() - return Polyhedron(vertices = [x.gkz_phi() for x in l]) - + return Polyhedron(vertices=[x.gkz_phi() for x in l]) def circuits_support(self): r""" @@ -2019,7 +2018,7 @@ def facets_of_simplex(simplex): # construct the initial simplex if point_order_is_given: - simplices = [frozenset(self.contained_simplex(large=False, point_order = point_order))] + simplices = [frozenset(self.contained_simplex(large=False, point_order=point_order))] else: simplices = [frozenset(self.contained_simplex(large=True))] for s in simplices[0]: diff --git a/src/sage/geometry/voronoi_diagram.py b/src/sage/geometry/voronoi_diagram.py index e280a41143a..1e9629aa646 100644 --- a/src/sage/geometry/voronoi_diagram.py +++ b/src/sage/geometry/voronoi_diagram.py @@ -15,7 +15,6 @@ from sage.structure.sage_object import SageObject from sage.geometry.polyhedron.constructor import Polyhedron -from sage.rings.qqbar import AA from sage.rings.rational_field import QQ import sage.rings.abc from sage.geometry.triangulation.point_configuration import PointConfiguration @@ -103,7 +102,7 @@ def __init__(self, points): self._n = self._points.n_points() if not self._n or self._points.base_ring().is_subring(QQ): self._base_ring = QQ - elif isinstance(self._points.base_ring(), sage.rings.abc.RealDoubleField) or self._points.base_ring() == AA: + elif isinstance(self._points.base_ring(), (sage.rings.abc.RealDoubleField, sage.rings.abc.AlgebraicRealField)): self._base_ring = self._points.base_ring() elif isinstance(self._points.base_ring(), sage.rings.abc.RealField): from sage.rings.real_double import RDF diff --git a/src/sage/graphs/bipartite_graph.py b/src/sage/graphs/bipartite_graph.py index 00f6c7006b3..1040419c32e 100644 --- a/src/sage/graphs/bipartite_graph.py +++ b/src/sage/graphs/bipartite_graph.py @@ -434,7 +434,7 @@ def __init__(self, data=None, partition=None, check=True, *args, **kwds): if check: if (any(left.intersection(self.neighbor_iterator(a)) for a in left) or - any(right.intersection(self.neighbor_iterator(a)) for a in right)): + any(right.intersection(self.neighbor_iterator(a)) for a in right)): raise TypeError("input graph is not bipartite with " "respect to the given partition") else: @@ -453,9 +453,8 @@ def __init__(self, data=None, partition=None, check=True, *args, **kwds): elif is_Matrix(data): # sanity check for mutually exclusive keywords if kwds.get("multiedges", False) and kwds.get("weighted", False): - raise TypeError( - "weighted multi-edge bipartite graphs from reduced " - "adjacency matrix not supported") + raise TypeError("weighted multi-edge bipartite graphs from " + "reduced adjacency matrix not supported") Graph.__init__(self, *args, **kwds) ncols = data.ncols() nrows = data.nrows() @@ -509,13 +508,12 @@ def __init__(self, data=None, partition=None, check=True, *args, **kwds): elif data.node_type[v] == "Top": self.right.add(v) else: - raise TypeError( - "NetworkX node_type defies bipartite " - "assumption (is not 'Top' or 'Bottom')") + raise TypeError("NetworkX node_type defies bipartite " + "assumption (is not 'Top' or 'Bottom')") elif partition: if check: if (any(left.intersection(self.neighbor_iterator(a)) for a in left) or - any(right.intersection(self.neighbor_iterator(a)) for a in right)): + any(right.intersection(self.neighbor_iterator(a)) for a in right)): raise TypeError("input graph is not bipartite with " "respect to the given partition") else: @@ -672,7 +670,7 @@ def add_vertex(self, name=None, left=False, right=False): # do nothing if we already have this vertex (idempotent) if name is not None and name in self: if ((left and name in self.left) or - (right and name in self.right)): + (right and name in self.right)): return else: raise RuntimeError("cannot add duplicate vertex to other partition") @@ -772,10 +770,9 @@ def add_vertices(self, vertices, left=False, right=False): # check that we're not trying to add vertices to the wrong sets # or that a vertex is to be placed in both if ((new_left & self.right) or - (new_right & self.left) or - (new_right & new_left)): - raise RuntimeError( - "cannot add duplicate vertex to other partition") + (new_right & self.left) or + (new_right & new_left)): + raise RuntimeError("cannot add duplicate vertex to other partition") # add vertices Graph.add_vertices(self, vertices) @@ -2400,7 +2397,11 @@ class by some canonization function `c`. If `G` and `H` are graphs, """ if certificate: - C, cert = GenericGraph.canonical_label(self, partition=partition, certificate=certificate, edge_labels=edge_labels, algorithm=algorithm, return_graph=return_graph) + C, cert = GenericGraph.canonical_label(self, partition=partition, + certificate=certificate, + edge_labels=edge_labels, + algorithm=algorithm, + return_graph=return_graph) else: from sage.groups.perm_gps.partn_ref.refinement_graphs import search_tree diff --git a/src/sage/graphs/digraph_generators.py b/src/sage/graphs/digraph_generators.py index 390550dd9c2..cceb89df765 100644 --- a/src/sage/graphs/digraph_generators.py +++ b/src/sage/graphs/digraph_generators.py @@ -687,14 +687,14 @@ def nauty_directg(self, graphs, options="", debug=False): if out: print(out) - for l in out.split('\n'): + for line in out.split('\n'): # directg return graphs in the digraph6 format. # digraph6 is very similar with the dig6 format used in sage : # digraph6_string = '&' + dig6_string # digraph6 specifications: # http://users.cecs.anu.edu.au/~bdm/data/formats.txt - if l and l[0] == '&': - yield DiGraph(l[1:], format='dig6') + if line and line[0] == '&': + yield DiGraph(line[1:], format='dig6') def Complete(self, n, loops=False): r""" @@ -1095,12 +1095,19 @@ def Kautz(self, k, D, vertices='strings'): sage: K = digraphs.Kautz([1,'a','B'], 2) sage: K.edges(sort=True) - [('1B', 'B1', '1'), ('1B', 'Ba', 'a'), ('1a', 'a1', '1'), ('1a', 'aB', 'B'), ('B1', '1B', 'B'), ('B1', '1a', 'a'), ('Ba', 'a1', '1'), ('Ba', 'aB', 'B'), ('a1', '1B', 'B'), ('a1', '1a', 'a'), ('aB', 'B1', '1'), ('aB', 'Ba', 'a')] + [('1B', 'B1', '1'), ('1B', 'Ba', 'a'), ('1a', 'a1', '1'), + ('1a', 'aB', 'B'), ('B1', '1B', 'B'), ('B1', '1a', 'a'), + ('Ba', 'a1', '1'), ('Ba', 'aB', 'B'), ('a1', '1B', 'B'), + ('a1', '1a', 'a'), ('aB', 'B1', '1'), ('aB', 'Ba', 'a')] sage: K = digraphs.Kautz([1,'aA','BB'], 2) sage: K.edges(sort=True) - [('1,BB', 'BB,1', '1'), ('1,BB', 'BB,aA', 'aA'), ('1,aA', 'aA,1', '1'), ('1,aA', 'aA,BB', 'BB'), ('BB,1', '1,BB', 'BB'), ('BB,1', '1,aA', 'aA'), ('BB,aA', 'aA,1', '1'), ('BB,aA', 'aA,BB', 'BB'), ('aA,1', '1,BB', 'BB'), ('aA,1', '1,aA', 'aA'), ('aA,BB', 'BB,1', '1'), ('aA,BB', 'BB,aA', 'aA')] - + [('1,BB', 'BB,1', '1'), ('1,BB', 'BB,aA', 'aA'), + ('1,aA', 'aA,1', '1'), ('1,aA', 'aA,BB', 'BB'), + ('BB,1', '1,BB', 'BB'), ('BB,1', '1,aA', 'aA'), + ('BB,aA', 'aA,1', '1'), ('BB,aA', 'aA,BB', 'BB'), + ('aA,1', '1,BB', 'BB'), ('aA,1', '1,aA', 'aA'), + ('aA,BB', 'BB,1', '1'), ('aA,BB', 'BB,aA', 'aA')] TESTS: diff --git a/src/sage/graphs/domination.py b/src/sage/graphs/domination.py index 54490a70ca5..7997270e655 100644 --- a/src/sage/graphs/domination.py +++ b/src/sage/graphs/domination.py @@ -66,6 +66,7 @@ from copy import copy from sage.rings.integer import Integer + def is_dominating(G, dom, focus=None): r""" Check whether ``dom`` is a dominating set of ``G``. @@ -101,6 +102,7 @@ def is_dominating(G, dom, focus=None): return not to_dom + def is_redundant(G, dom, focus=None): r""" Check whether ``dom`` has redundant vertices. @@ -166,6 +168,7 @@ def is_redundant(G, dom, focus=None): # with_private != set(dom) return len(with_private) != len(dom) + def private_neighbors(G, vertex, dom): r""" Return the private neighbors of a vertex with respect to other vertices. @@ -361,6 +364,7 @@ def neighbors_iter(x): dom = [v for v in g if b[v]] return Integer(len(dom)) if value_only else dom + # ============================================================================== # Enumeration of minimal dominating set as described in [BDHPR2019]_ # ============================================================================== @@ -785,6 +789,7 @@ def tree_search(H, plng, dom, i): for dom in tree_search(G, peeling, set(), 0): yield {int_to_vertex[v] for v in dom} + # ============================================================================== # Greedy heuristic for dominating set # ============================================================================== diff --git a/src/sage/graphs/dot2tex_utils.py b/src/sage/graphs/dot2tex_utils.py index d67d283234d..75e27d0f158 100644 --- a/src/sage/graphs/dot2tex_utils.py +++ b/src/sage/graphs/dot2tex_utils.py @@ -1,17 +1,18 @@ r""" This file contains some utility functions for the interface with dot2tex """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2010 Nicolas M. Thiery # # Distributed under the terms of the GNU General Public License (GPL) -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** import re from sage.misc.latex import latex from sage.misc.cachefunc import cached_function + @cached_function def have_dot2tex(): """ @@ -28,7 +29,7 @@ def have_dot2tex(): try: import dot2tex # Test for this required feature from dot2tex 2.8.7 - return dot2tex.dot2tex("graph {}", format = "positions") == {} + return dot2tex.dot2tex("graph {}", format="positions") == {} except (Exception, SystemExit): return False @@ -60,11 +61,12 @@ def assert_have_dot2tex(): import dot2tex except ImportError: print(import_error_string) - raise # re-raise current exception + raise # re-raise current exception else: - if dot2tex.dot2tex("graph {}", format = "positions") != {}: + if dot2tex.dot2tex("graph {}", format="positions") != {}: raise RuntimeError(check_error_string) + def quoted_latex(x): """ Strips the latex representation of ``x`` to make it suitable for a @@ -75,7 +77,8 @@ def quoted_latex(x): sage: sage.graphs.dot2tex_utils.quoted_latex(matrix([[1,1],[0,1],[0,0]])) '\\left(\\begin{array}{rr}1 & 1 \\\\0 & 1 \\\\0 & 0\\end{array}\\right)' """ - return re.sub("\"|\r|(%[^\n]*)?\n","", latex(x)) + return re.sub("\"|\r|(%[^\n]*)?\n", "", latex(x)) + def quoted_str(x): @@ -94,4 +97,4 @@ def quoted_str(x): [0 1]\n\ [0 0] """ - return re.sub("\n",r"\\n\\"+"\n", re.sub("\"|\r|}|{","", str(x))) + return re.sub("\n", r"\\n\\"+"\n", re.sub("\"|\r|}|{", "", str(x))) diff --git a/src/sage/graphs/generators/distance_regular.pyx b/src/sage/graphs/generators/distance_regular.pyx index 29e101f9473..9cf706540f7 100644 --- a/src/sage/graphs/generators/distance_regular.pyx +++ b/src/sage/graphs/generators/distance_regular.pyx @@ -86,6 +86,7 @@ def cocliques_HoffmannSingleton(): G = Graph(edges, format="list_of_edges") return G + def locally_GQ42_distance_transitive_graph(): r""" Return the unique amply regular graph with `\mu = 6` which is locally @@ -141,10 +142,10 @@ def ConwaySmith_for_3S7(): F = CyclotomicField(3) w = F.gen() - V= VectorSpace(GF(4), 6) - z2 = GF(4)('z2') # GF(4) = {0, 1, z2, z2+1} + V = VectorSpace(GF(4), 6) + z2 = GF(4)('z2') # GF(4) = {0, 1, z2, z2+1} - W = V.span([(0,0,1,1,1,1), (0,1,0,1,z2,z2+1), (1,0,0,1,z2+1,z2)]) + W = V.span([(0, 0, 1, 1, 1, 1), (0, 1, 0, 1, z2, z2 + 1), (1, 0, 0, 1, z2 + 1, z2)]) # we only need the 45 vectors with 2 zero entries # we also embed everything into CC @@ -199,6 +200,7 @@ def ConwaySmith_for_3S7(): G.name("Conway-Smith graph for 3S7") return G + def graph_3O73(): r""" Return the graph related to the group `3 O(7,3)`. @@ -225,6 +227,7 @@ def graph_3O73(): G.name("Distance transitive graph with automorphism group 3.O_7(3)") return G + def FosterGraph3S6(): r""" Return the Foster graph for `3.Sym(6)`. @@ -245,7 +248,6 @@ def FosterGraph3S6(): A description and construction of this graph can be found in [BCN1989]_ p. 397. """ - a = libgap.eval(("(2,6)(3,5)(4,11)(7,17)(8,16)(9,14)(13,22)(15,25)" "(18,29)(19,28)(20,21)(24,30)(26,35)(27,33)(31,39)" "(34,38)(36,43)(37,40)(42,44)")) @@ -253,12 +255,13 @@ def FosterGraph3S6(): "(14,23,28,31,24)(16,22,29,36,27)(25,32,35,42,34)" "(30,37,39,44,38)(33,40,43,45,41)")) - group = libgap.Group(a,b) + group = libgap.Group(a, b) G = Graph(group.Orbit([1, 7], libgap.OnSets), format='list_of_edges') G.name("Foster graph for 3.Sym(6) graph") return G + def J2Graph(): r""" Return the distance-transitive graph with automorphism group `J_2`. @@ -279,6 +282,7 @@ def J2Graph(): G.name("J_2 graph") return G + def IvanovIvanovFaradjevGraph(): r""" Return the IvanovIvanovFaradjev graph. @@ -303,6 +307,7 @@ def IvanovIvanovFaradjevGraph(): graph.name("Ivanov-Ivanov-Faradjev Graph") return graph + def LargeWittGraph(): r""" Return the large Witt graph. @@ -337,6 +342,7 @@ def LargeWittGraph(): W.name("Large Witt graph") return W + def TruncatedWittGraph(): r""" Return the truncated Witt graph. @@ -360,11 +366,12 @@ def TruncatedWittGraph(): """ # get large witt graph and remove all vertices which start with a 1 G = LargeWittGraph() - G.delete_vertices(filter(lambda x : x[0] == 1, G.vertices(sort=False))) + G.delete_vertices(filter(lambda x: x[0] == 1, G.vertices(sort=False))) G.name("Truncated Witt graph") return G + def DoublyTruncatedWittGraph(): r""" Return the doubly truncated Witt graph. @@ -386,13 +393,13 @@ def DoublyTruncatedWittGraph(): A description and construction of this graph can be found in [BCN1989]_ p. 368. """ - G = TruncatedWittGraph() - G.delete_vertices(filter(lambda x : x[1] == 1, G.vertices(sort=False))) + G.delete_vertices(filter(lambda x: x[1] == 1, G.vertices(sort=False))) G.name("Doubly Truncated Witt graph") return G + def distance_3_doubly_truncated_Golay_code_graph(): r""" Return a distance-regular graph with intersection array @@ -416,17 +423,18 @@ def distance_3_doubly_truncated_Golay_code_graph(): Description and construction of this graph are taken from [BCN1989]_ p. 364. """ - G = codes.GolayCode(GF(2),extended=False).punctured([0,1]).cosetGraph() + G = codes.GolayCode(GF(2), extended=False).punctured([0, 1]).cosetGraph() v = G.vertices(sort=False)[0] it = G.breadth_first_search(v, distance=3, report_distance=True) - vertices = [w for (w,d) in it if d == 3] + vertices = [w for (w, d) in it if d == 3] - edges =[(a ,b) for a, b in itertools.combinations(vertices, 2) - if G.has_edge((a, b))] + edges = [(a, b) for a, b in itertools.combinations(vertices, 2) + if G.has_edge((a, b))] H = Graph(edges, format='list_of_edges') return H + def shortened_00_11_binary_Golay_code_graph(): r""" Return a distance-regular graph with intersection array @@ -454,7 +462,7 @@ def shortened_00_11_binary_Golay_code_graph(): C_basis = code.basis() # Now special shortening - v = C_basis[0] + C_basis[1] # v has 11 at the start + v = C_basis[0] + C_basis[1] # v has 11 at the start C_basis = C_basis[2:] C_basis.append(v) C_basis = list(map(lambda x: x[2:], C_basis)) @@ -465,6 +473,7 @@ def shortened_00_11_binary_Golay_code_graph(): G.name("Shortened 00 11 binary Golay code") return G + def shortened_000_111_extended_binary_Golay_code_graph(): r""" Return a distance-regular graph with intersection array @@ -492,7 +501,7 @@ def shortened_000_111_extended_binary_Golay_code_graph(): C_basis = code.basis() # now special shortening - v = C_basis[0] + C_basis[1] + C_basis[2] # v has 111 at the start + v = C_basis[0] + C_basis[1] + C_basis[2] # v has 111 at the start C_basis = C_basis[3:] C_basis.append(v) C_basis = list(map(lambda x: x[3:], C_basis)) @@ -503,6 +512,7 @@ def shortened_000_111_extended_binary_Golay_code_graph(): G.name("Shortened 000 111 extended binary Golay code") return G + def vanLintSchrijverGraph(): r""" Return the van Lint-Schrijver graph. @@ -533,6 +543,7 @@ def vanLintSchrijverGraph(): H.name("Linst-Schrijver graph") return H + def LeonardGraph(): r""" Return the Leonard graph. @@ -567,6 +578,7 @@ def LeonardGraph(): G = Graph(edges, format="list_of_edges") return G + def UstimenkoGraph(const int m, const int q): r""" Return the Ustimenko graph with parameters `(m, q)`. @@ -617,6 +629,7 @@ def UstimenkoGraph(const int m, const int q): G.name(f"Ustimenko graph ({m}, {q})") return G + def BilinearFormsGraph(const int d, const int e, const int q): r""" Return a bilinear forms graph with the given parameters. @@ -704,9 +717,10 @@ def BilinearFormsGraph(const int d, const int e, const int q): edges.append((intM1, intM3)) G = Graph(edges, format='list_of_edges') - G.name("Bilinear forms graphs over F_%d with parameters (%d, %d)"%(q, d, e)) + G.name("Bilinear forms graphs over F_%d with parameters (%d, %d)" % (q, d, e)) return G + def AlternatingFormsGraph(const int n, const int q): r""" Return the alternating forms graph with the given parameters. @@ -800,9 +814,10 @@ def AlternatingFormsGraph(const int n, const int q): edges.append((t1, t3)) G = Graph(edges, format='list_of_edges') - G.name("Alternating forms graph on (F_%d)^%d"%(q, n)) + G.name("Alternating forms graph on (F_%d)^%d" % (q, n)) return G + def HermitianFormsGraph(const int n, const int r): r""" Return the Hermitian forms graph with the given parameters. @@ -905,6 +920,7 @@ def HermitianFormsGraph(const int n, const int r): G.name(f"Hermitian forms graph on (F_{q})^{n}") return G + def DoubleOddGraph(const int n): r""" Return the double odd graph on `2n+1` points. @@ -967,9 +983,10 @@ def DoubleOddGraph(const int n): edges.append((tuple(s1), tuple(s2))) G = Graph(edges, format='list_of_edges') - G.name("Bipartite double of Odd graph on a set of %d elements"%(2*n + 1)) + G.name("Bipartite double of Odd graph on a set of %d elements" % (2*n + 1)) return G + def HalfCube(const int n): r""" Return the halved cube in `n` dimensions. @@ -1035,9 +1052,10 @@ def HalfCube(const int n): G = Graph([range(2**(n - 1)), E], format='vertices_and_edges') G.set_pos(pos) - G.name("Half %d Cube"%n) + G.name("Half %d Cube" % n) return G + def GrassmannGraph(const int q, const int n, const int input_e): r""" Return the Grassmann graph with parameters `(q, n, e)`. @@ -1086,11 +1104,12 @@ def GrassmannGraph(const int q, const int n, const int input_e): PG = designs.ProjectiveGeometryDesign(n - 1, e - 1, q) # we want the intersection graph # the size of the intersection must be (q^{e-1} - 1) / (q-1) - size = (q**(e-1) - 1) // (q - 1) + size = (q**(e - 1) - 1) // (q - 1) G = PG.intersection_graph([size]) - G.name("Grassmann graph J_%d(%d, %d)"%(q, n, e)) + G.name("Grassmann graph J_%d(%d, %d)" % (q, n, e)) return G + def DoubleGrassmannGraph(const int q, const int e): r""" Return the bipartite double of the distance-`e` graph of the Grassmann graph `J_q(n,e)`. @@ -1145,7 +1164,7 @@ def DoubleGrassmannGraph(const int q, const int e): edges.append((Wbasis, Ubasis)) G = Graph(edges, format='list_of_edges') - G.name("Double Grassmann graph (%d, %d, %d)"%(n, e, q)) + G.name("Double Grassmann graph (%d, %d, %d)" % (n, e, q)) return G @@ -1216,11 +1235,12 @@ def is_from_GQ_spread(list arr): is not True: return False - return (s,t) + return (s, t) + def graph_from_GQ_spread(const int s, const int t): r""" - Return the point graph of the generalised quandrangle with + Return the point graph of the generalised quadrangle with order `(s, t)` after removing one of its spreads. These graphs are antipodal covers of complete graphs and, in particular, @@ -1269,6 +1289,7 @@ def graph_from_GQ_spread(const int s, const int t): G = Graph(edges, format="list_of_edges") return G + def GeneralisedDodecagonGraph(const int s, const int t): r""" Return the point-graph of a generalised dodecagon of order `(s,t)`. @@ -1363,16 +1384,17 @@ def GeneralisedDodecagonGraph(const int s, const int t): edges.append((p, l)) G = Graph(edges, format='list_of_edges') - G.name("Generalised dodecagon of order (1, %d)"%q) + G.name("Generalised dodecagon of order (1, %d)" % q) return G else: # orderType == 1 # dual H = GeneralisedDodecagonGraph(t, s) G = _line_graph_generalised_polygon(H) - G.name("Generalised dodecagon of order (%s, %d)"%(s, t)) + G.name("Generalised dodecagon of order (%s, %d)" % (s, t)) return G + def GeneralisedOctagonGraph(const int s, const int t): r""" Return the point-graph of a generalised octagon of order `(s,t)`. @@ -1427,7 +1449,7 @@ def GeneralisedOctagonGraph(const int s, const int t): elif t == 1: # (q, 1) q = s orderType = 1 - elif s**2 == t: # (q, q^2) + elif s**2 == t: # (q, q^2) q = s (p, k) = is_prime_power(q, get_data=True) @@ -1463,14 +1485,14 @@ def GeneralisedOctagonGraph(const int s, const int t): edges.append((p, l)) G = Graph(edges, format='list_of_edges') - G.name("Generalised octagon of order (1, %d)"%q) + G.name("Generalised octagon of order (1, %d)" % q) return G elif orderType == 1: # dual H = GeneralisedOctagonGraph(t, s) G = _line_graph_generalised_polygon(H) - G.name("Generalised octagon of order(%d, %d)"%(s, t)) + G.name("Generalised octagon of order(%d, %d)" % (s, t)) return G else: if q == 2: @@ -1570,14 +1592,14 @@ def GeneralisedHexagonGraph(const int s, const int t): edges.append((p, tuple(l))) G = Graph(edges, format='list_of_edges') - G.name("Generalised hexagon of order (1, %d)"%q) + G.name("Generalised hexagon of order (1, %d)" % q) return G elif orderType == 1: # dual graph H = GeneralisedHexagonGraph(t, s) G = _line_graph_generalised_polygon(H) - G.name("Generalised hexagon of order(%d, %d)"%(s, t)) + G.name("Generalised hexagon of order(%d, %d)" % (s, t)) return G elif orderType == 2: @@ -1587,7 +1609,7 @@ def GeneralisedHexagonGraph(const int s, const int t): group = libgap.AtlasGroup("U3(3).2", libgap.NrMovedPoints, 63) G = Graph(libgap.Orbit(group, [1, 19], libgap.OnSets), format='list_of_edges') - G.name("Generalised hexagon of order (%d, %d)"%(q, q)) + G.name("Generalised hexagon of order (%d, %d)" % (q, q)) return G elif q == 3: # we don't have permutation representation; so we build it @@ -1599,17 +1621,17 @@ def GeneralisedHexagonGraph(const int s, const int t): # now group is our permutation representation G = Graph(libgap.Orbit(group, [1, 52], libgap.OnSets), format='list_of_edges') - G.name("Generalised hexagon of order (%d, %d)"%(q, q)) + G.name("Generalised hexagon of order (%d, %d)" % (q, q)) return G elif q <= 5: n = 1365 if q == 4 else 3906 p = 43 if q == 4 else 185 - group = libgap.AtlasGroup("G2(%d)"%q, libgap.NrMovedPoints, n) + group = libgap.AtlasGroup("G2(%d)" % q, libgap.NrMovedPoints, n) G = Graph(libgap.Orbit(group, [1, p], libgap.OnSets), format='list_of_edges') - G.name("Generalised hexagon of order (%d, %d)"%(q, q)) + G.name("Generalised hexagon of order (%d, %d)" % (q, q)) return G else: @@ -1619,14 +1641,15 @@ def GeneralisedHexagonGraph(const int s, const int t): if q > 3: raise NotImplementedError("Graph would be too big") - movedPoints = 819 if q==2 else 26572 - group = libgap.AtlasGroup("3D4(%d)"%q, libgap.NrMovedPoints, movedPoints) + movedPoints = 819 if q == 2 else 26572 + group = libgap.AtlasGroup("3D4(%d)" % q, libgap.NrMovedPoints, movedPoints) G = Graph(libgap.Orbit(group, [1, 2], libgap.OnSets), format='list_of_edges') - G.name("Generalised hexagon of order (%d, %d)"%(q, q**3)) + G.name("Generalised hexagon of order (%d, %d)" % (q, q**3)) return G + def _extract_lines(G): r""" Return the set of lines from the point-graph of a generalised polygon. @@ -1666,14 +1689,13 @@ def _extract_lines(G): generalised polygons. See also [BCN1989]_ pp. 28, 29 for some theory about singular lines. """ - lines = [] edges = set(G.edges(labels=False, sort=False)) while edges: (x, y) = edges.pop() - #compute line + # compute line botX = set(G.neighbors(x, closed=True)) botY = set(G.neighbors(y, closed=True)) bot1 = botX.intersection(botY) @@ -1699,6 +1721,7 @@ def _extract_lines(G): return lines + def _line_graph_generalised_polygon(H): r""" Return the line-graph of the generalised polygon whose point-graph is `H`. @@ -1752,6 +1775,7 @@ def _line_graph_generalised_polygon(H): G = Graph(edges, format="list_of_edges") return G + def _intersection_array_from_graph(G): r""" Return the intersection array of the graph `G`. @@ -1816,6 +1840,7 @@ cdef enum ClassicalParametersGraph: LieE77, AffineE6 + def is_classical_parameters_graph(list array): r""" Return a tuple of parameters representing the array given. If such no tuple @@ -1889,7 +1914,7 @@ def is_classical_parameters_graph(list array): return -1 def check_parameters(int d, int b, int alpha, int beta, list arr): - bs = [(q_binomial(d, 1, b) - q_binomial(i, 1, b)) * \ + bs = [(q_binomial(d, 1, b) - q_binomial(i, 1, b)) * (beta - alpha * q_binomial(i, 1, b)) for i in range(d)] cs = [q_binomial(i, 1, b) * (1 + alpha*q_binomial(i-1, 1, b)) for i in range(1, d+1)] @@ -1908,7 +1933,7 @@ def is_classical_parameters_graph(list array): def a_(i): return b_(0) - b_(i) - c_(i) - if len(array) % 2 != 0 : + if len(array) % 2: return False d = len(array) // 2 @@ -1961,7 +1986,7 @@ def is_classical_parameters_graph(list array): gamma = ClassicalParametersGraph.NonExisting - if b == 1 : + if b == 1: if alpha == 1 and beta >= d: # since beta+d = n >= 2*d # Johnson Graph gamma = ClassicalParametersGraph.Johnson @@ -1971,18 +1996,18 @@ def is_classical_parameters_graph(list array): elif alpha == 2 and (beta == 2*d + 1 or beta == 2*d - 1): # Halved cube graph gamma = ClassicalParametersGraph.HalvedCube - else : + else: return False # no other (unbounbded) drg exists with b = 1 elif b < 0 and is_prime_power(-b): - if alpha + 1 == (1 + b*b) / (1 + b) and \ - beta + 1 == (1 - b**(d+1)) / (1 + b): + if (alpha + 1 == (1 + b*b) / (1 + b) and + beta + 1 == (1 - b**(d + 1)) / (1 + b)): # U(2d,r) gamma = ClassicalParametersGraph.UnitaryDualPolar1 elif alpha + 1 == b and beta + 1 == - (b**d): gamma = ClassicalParametersGraph.HermitianForms - elif d == 3 and alpha + 1 == 1 / (1+b) and \ - beta + 1 == q_binomial(3, 1, -b): + elif (d == 3 and alpha + 1 == 1 / (1+b) and + beta + 1 == q_binomial(3, 1, -b)): gamma = ClassicalParametersGraph.GeneralisedHexagon else: return False @@ -2001,7 +2026,7 @@ def is_classical_parameters_graph(list array): # Grassmann graph gamma = ClassicalParametersGraph.Grassmann - elif alpha == 0 and beta * beta in {1, b, b * b, b**3, b**4}: + elif alpha == 0 and beta * beta in {1, b, b * b, b**3, b**4}: # checked beta in {b^0, b^(0.5), b, b^(1.5), b^2} # dual polar graphs if beta == 1: @@ -2018,20 +2043,20 @@ def is_classical_parameters_graph(list array): elif beta == r: gamma = ClassicalParametersGraph.UnitaryDualPolar2 - elif k % 2 == 0 and alpha + 1 == q_binomial(3, 1, r) and \ - beta + 1 in {q_binomial(2*d + 2, 1, r), - q_binomial(2*d + 1, 1, r)}: + elif (k % 2 == 0 and alpha + 1 == q_binomial(3, 1, r) and + beta + 1 in {q_binomial(2*d + 2, 1, r), + q_binomial(2*d + 1, 1, r)}): gamma = ClassicalParametersGraph.Ustimenko elif alpha + 1 == b and integral_log(beta + 1, b) >= d: gamma = ClassicalParametersGraph.BilinearForms - elif k % 2 == 0 and alpha + 1 == b and \ - beta + 1 in {r**(2*d - 1),r**(2*d + 1)}: + elif (k % 2 == 0 and alpha + 1 == b and + beta + 1 in {r**(2*d - 1), r**(2*d + 1)}): gamma = ClassicalParametersGraph.AlternatingForms - elif d == 3 and k % 4 == 0 and alpha + 1 == q_binomial(5, 1, p**(k//4)) and \ - beta + 1 == q_binomial(10, 1, p**(k//4)): + elif (d == 3 and k % 4 == 0 and alpha + 1 == q_binomial(5, 1, p**(k//4)) and + beta + 1 == q_binomial(10, 1, p**(k//4))): gamma = ClassicalParametersGraph.LieE77 elif d == 3 and k % 4 == 0 and alpha + 1 == b and beta + 1 == (p**(k//4))**9: @@ -2041,6 +2066,7 @@ def is_classical_parameters_graph(list array): return False return (d, b, alpha, beta, gamma) + def graph_with_classical_parameters(int d, int b, alpha_in, beta_in, int gamma): r""" Return the graph with the classical parameters given. @@ -2134,14 +2160,14 @@ def graph_with_classical_parameters(int d, int b, alpha_in, beta_in, int gamma): return UnitaryDualPolarGraph(2 * d, -b) elif gamma == ClassicalParametersGraph.HermitianForms: - return HermitianFormsGraph(d,(-b)**2) + return HermitianFormsGraph(d, (-b)**2) elif gamma == ClassicalParametersGraph.GeneralisedHexagon: q = -b return GeneralisedHexagonGraph(q, q**3) elif gamma == ClassicalParametersGraph.Grassmann: - n = int(log((beta+1) * (b-1) + 1, b)) + d -1 + n = int(log((beta + 1) * (b - 1) + 1, b)) + d - 1 return GrassmannGraph(b, n, d) elif gamma == ClassicalParametersGraph.OrthogonalDualPolar1: @@ -2175,12 +2201,13 @@ def graph_with_classical_parameters(int d, int b, alpha_in, beta_in, int gamma): a = 0 if beta + 1 == q**(2*d - 1) else 1 return AlternatingFormsGraph(2*d + a, q) - elif gamma == ClassicalParametersGraph.LieE77 or \ - gamma == ClassicalParametersGraph.AffineE6: + elif (gamma == ClassicalParametersGraph.LieE77 or + gamma == ClassicalParametersGraph.AffineE6): raise NotImplementedError("Graph would be too big") raise ValueError("Incorrect family of graphs") + def is_pseudo_partition_graph(list arr): r""" Return `(m, a)` if the intersection array given satisfies: @@ -2235,7 +2262,7 @@ def is_pseudo_partition_graph(list arr): d = d // 2 - if d < 3 : + if d < 3: return False # c_2 = 2 (1+a) @@ -2265,6 +2292,7 @@ def is_pseudo_partition_graph(list arr): return False + def pseudo_partition_graph(int m, int a): r""" Return a pseudo partition graph with the given parameters. diff --git a/src/sage/graphs/generators/families.py b/src/sage/graphs/generators/families.py index f4a59dea38e..9837fe3a5fa 100644 --- a/src/sage/graphs/generators/families.py +++ b/src/sage/graphs/generators/families.py @@ -37,13 +37,13 @@ def JohnsonGraph(n, k): EXAMPLES: - The Johnson graph is a Hamiltonian graph. :: + The Johnson graph is a Hamiltonian graph:: sage: g = graphs.JohnsonGraph(7, 3) sage: g.is_hamiltonian() True - Every Johnson graph is vertex transitive. :: + Every Johnson graph is vertex transitive:: sage: g = graphs.JohnsonGraph(6, 4) sage: g.is_vertex_transitive() @@ -51,7 +51,7 @@ def JohnsonGraph(n, k): The complement of the Johnson graph `J(n,2)` is isomorphic to the Kneser Graph `K(n,2)`. In particular the complement of `J(5,2)` is isomorphic to - the Petersen graph. :: + the Petersen graph.:: sage: g = graphs.JohnsonGraph(5,2) sage: g.complement().is_isomorphic(graphs.PetersenGraph()) @@ -70,12 +70,12 @@ def JohnsonGraph(n, k): for j in elem_left: if j <= i: continue - g.add_edge(sub+Set([i]),sub+Set([j])) + g.add_edge(sub + Set([i]), sub + Set([j])) return g -def KneserGraph(n,k): +def KneserGraph(n, k): r""" Returns the Kneser Graph with parameters `n, k`. @@ -109,22 +109,22 @@ def KneserGraph(n,k): ValueError: Parameter k should be a strictly positive integer inferior to n """ - if not n>0: + if n <= 0: raise ValueError("Parameter n should be a strictly positive integer") - if not (k>0 and k<=n): + if k <= 0 or k > n: raise ValueError("Parameter k should be a strictly positive integer inferior to n") - g = Graph(name="Kneser graph with parameters {},{}".format(n,k)) + g = Graph(name="Kneser graph with parameters {},{}".format(n, k)) from sage.combinat.subset import Subsets - S = Subsets(n,k) + S = Subsets(n, k) if 2 * k > n: g.add_vertices(S) s0 = S.underlying_set() # {1,2,...,n} for s in S: for t in Subsets(s0.difference(s), k): - g.add_edge(s,t) + g.add_edge(s, t) return g @@ -212,18 +212,18 @@ def FurerGadget(k, prefix=None): V_b = list(zip(rep(prefix, k), V_b)) G.add_vertices(V_a) G.add_vertices(V_b) - powerset = list(chain.from_iterable(combinations(range(k), r) for r in range(0,k+1,2))) + powerset = list(chain.from_iterable(combinations(range(k), r) for r in range(0, k + 1, 2))) if prefix is not None: - G.add_edges(chain.from_iterable([((prefix,s),(prefix,(i,'a'))) for i in s] for s in powerset)) - G.add_edges(chain.from_iterable([((prefix,s),(prefix,(i,'b'))) for i in range(k) if i not in s] for s in powerset)) + G.add_edges(chain.from_iterable([((prefix, s), (prefix, (i, 'a'))) for i in s] for s in powerset)) + G.add_edges(chain.from_iterable([((prefix, s), (prefix, (i, 'b'))) for i in range(k) if i not in s] for s in powerset)) else: - G.add_edges(chain.from_iterable([(s,(i,'a')) for i in s] for s in powerset)) - G.add_edges(chain.from_iterable([(s,(i,'b')) for i in range(k) if i not in s] for s in powerset)) + G.add_edges(chain.from_iterable([(s, (i, 'a')) for i in s] for s in powerset)) + G.add_edges(chain.from_iterable([(s, (i, 'b')) for i in range(k) if i not in s] for s in powerset)) partition = [] for i in range(k): partition.append([V_a[i], V_b[i]]) if prefix is not None: - powerset = [(prefix,s) for s in powerset] + powerset = [(prefix, s) for s in powerset] partition.append(powerset) return G, partition @@ -317,9 +317,9 @@ def CaiFurerImmermanGraph(G, twisted=False): for v in G: Fk, p = FurerGadget(G.degree(v), v) total_partition += p - newG=newG.union(Fk) + newG = newG.union(Fk) edge_index[v] = 0 - for v,u in G.edge_iterator(labels=False): + for v, u in G.edge_iterator(labels=False): i = edge_index[v] edge_index[v] += 1 j = edge_index[u] @@ -394,22 +394,22 @@ def EgawaGraph(p, s): g = Graph(name="Egawa Graph with parameters " + str(p) + "," + str(s), multiedges=False) X = CompleteGraph(4) Y = Graph('O?Wse@UgqqT_LUebWkbT_') - g.add_vertices(product(*chain(repeat(Y, p), repeat(X,s)))) + g.add_vertices(product(*chain(repeat(Y, p), repeat(X, s)))) for v in g: for i in range(p): prefix = v[:i] suffix = v[i+1:] for el in Y.neighbor_iterator(v[i]): u = prefix + (el,) + suffix - g.add_edge(v,u) - for i in range(p, s+p): + g.add_edge(v, u) + for i in range(p, s + p): prefix = v[:i] suffix = v[i+1:] for el in X: if el == v[i]: continue u = prefix + (el,) + suffix - g.add_edge(v,u) + g.add_edge(v, u) return g @@ -491,9 +491,10 @@ def HammingGraph(n, q, X=None): if el == v[i]: continue u = prefix + (el,) + suffix - g.add_edge(v,u) + g.add_edge(v, u) return g + def BalancedTree(r, h): r""" Returns the perfectly balanced tree of height `h \geq 1`, @@ -680,7 +681,7 @@ def BarbellGraph(n1, n2): G = Graph(name="Barbell graph") G.add_clique(list(range(n1))) - G.add_path(list(range(n1 - 1 , n1 + n2 + 1))) + G.add_path(list(range(n1 - 1, n1 + n2 + 1))) G.add_clique(list(range(n1 + n2, n1 + n2 + n1))) G._circle_embedding(list(range(n1)), shift=1, angle=pi/4) @@ -755,7 +756,7 @@ def LollipopGraph(n1, n2): if n1 * n2 > 0: G.add_edge(n1 - 1, n1) if n1 == 1: - G.set_pos({0:(0, 0)}) + G.set_pos({0: (0, 0)}) else: G._circle_embedding(list(range(n1)), shift=1, angle=pi/4) G._line_embedding(list(range(n1, n1 + n2)), first=(2, 2), last=(n2 + 1, n2 + 1)) @@ -860,7 +861,6 @@ def AztecDiamondGraph(n): return H - def DipoleGraph(n): r""" Returns a dipole graph with n edges. @@ -900,7 +900,7 @@ def DipoleGraph(n): if n < 0: raise ValueError("invalid graph description, n should be >= 0") - return Graph([[0,1], [(0,1)]*n], name="Dipole graph", multiedges=True) + return Graph([[0, 1], [(0, 1)]*n], name="Dipole graph", multiedges=True) def BubbleSortGraph(n): @@ -976,26 +976,27 @@ def BubbleSortGraph(n): from sage.graphs.generators.basic import CompleteGraph return Graph(CompleteGraph(n), name="Bubble sort") from sage.combinat.permutation import Permutations - #create set from which to permute + # create set from which to permute label_set = [str(i) for i in range(1, n + 1)] d = {} - #iterate through all vertices + # iterate through all vertices for v in Permutations(label_set): - v = list(v) # So we can easily mutate it + v = list(v) # So we can easily mutate it tmp_dict = {} - #add all adjacencies + # add all adjacencies for i in range(n - 1): - #swap entries + # swap entries v[i], v[i + 1] = v[i + 1], v[i] - #add new vertex + # add new vertex new_vert = ''.join(v) tmp_dict[new_vert] = None - #swap back + # swap back v[i], v[i + 1] = v[i + 1], v[i] - #add adjacency dict + # add adjacency dict d[''.join(v)] = tmp_dict return Graph(d, name="Bubble sort") + def chang_graphs(): r""" Return the three Chang graphs. @@ -1040,7 +1041,8 @@ def chang_graphs(): loops=False, multiedges=False) g3 = Graph(r"[~~vVMWdKFpV`^UGIaIERQ`\DBxpA@g`CbGRI`AxICNaFM[?fM\?Ytj@CxrGGlYt", loops=False, multiedges=False) - return [g1,g2,g3] + return [g1, g2, g3] + def CirculantGraph(n, adjacency): r""" @@ -1139,14 +1141,15 @@ def CirculantGraph(n, adjacency): if not isinstance(adjacency, list): adjacency = [adjacency] - G = Graph(n, name="Circulant graph ("+str(adjacency)+")") + G = Graph(n, name="Circulant graph (" + str(adjacency) + ")") G._circle_embedding(list(range(n))) for v in G: - G.add_edges([(v,(v+j)%n) for j in adjacency]) + G.add_edges([(v, (v + j) % n) for j in adjacency]) return G + def CubeGraph(n, embedding=1): r""" Return the `n`-cube graph, also called the hypercube in `n` dimensions. @@ -1232,14 +1235,14 @@ def CubeGraph(n, embedding=1): l1.append(m + '1') dn[v0] = l0 dn[v1] = l1 - x,y = p[v] + x, y = p[v] pn[v0] = (x, y) pn[v1] = (x + ci, y + si) d, dn = dn, {} p, pn = pn, {} # construct the graph - G = Graph(d, format='dict_of_lists', pos=p, name="%d-Cube"%n) + G = Graph(d, format='dict_of_lists', pos=p, name="%d-Cube" % n) else: # construct recursively the adjacency dict @@ -1260,7 +1263,7 @@ def CubeGraph(n, embedding=1): d, dn = dn, {} # construct the graph - G = Graph(d, name="%d-Cube"%n, format='dict_of_lists') + G = Graph(d, name="%d-Cube" % n, format='dict_of_lists') if embedding == 2: # Orthogonal projection @@ -1276,7 +1279,8 @@ def CubeGraph(n, embedding=1): return G -def GoethalsSeidelGraph(k,r): + +def GoethalsSeidelGraph(k, r): r""" Returns the graph `\text{Goethals-Seidel}(k,r)`. @@ -1306,23 +1310,22 @@ def GoethalsSeidelGraph(k,r): Graph on 28 vertices sage: graphs.GoethalsSeidelGraph(3,3).is_strongly_regular(parameters=True) (28, 15, 6, 10) - """ from sage.combinat.designs.bibd import balanced_incomplete_block_design from sage.combinat.matrices.hadamard_matrix import hadamard_matrix from sage.matrix.constructor import Matrix from sage.matrix.constructor import block_matrix - v = (k-1)*r+1 - n = v*(r+1) + v = (k-1)*r + 1 + n = v*(r + 1) # N is the (v times b) incidence matrix of a bibd - N = balanced_incomplete_block_design(v,k).incidence_matrix() + N = balanced_incomplete_block_design(v, k).incidence_matrix() # L is a (r+1 times r) matrix, where r is the row sum of N - L = hadamard_matrix(r+1).submatrix(0,1) + L = hadamard_matrix(r + 1).submatrix(0, 1) L = [Matrix(C).transpose() for C in L.columns()] - zero = Matrix(r+1,1,[0]*(r+1)) + zero = Matrix(r + 1, 1, [0]*(r + 1)) # For every row of N, we replace the 0s with a column of zeros, and we # replace the ith 1 with the ith column of L. The result is P. @@ -1337,11 +1340,12 @@ def GoethalsSeidelGraph(k,r): # The final graph PP = P*P.transpose() for i in range(n): - PP[i,i] = 0 + PP[i, i] = 0 G = Graph(PP, format="seidel_adjacency_matrix") return G + def DorogovtsevGoltsevMendesGraph(n): """ Construct the n-th generation of the Dorogovtsev-Goltsev-Mendes @@ -1360,8 +1364,9 @@ def DorogovtsevGoltsevMendesGraph(n): (2002). """ import networkx - return Graph(networkx.dorogovtsev_goltsev_mendes_graph(n),\ - name="Dorogovtsev-Goltsev-Mendes Graph, %d-th generation"%n) + return Graph(networkx.dorogovtsev_goltsev_mendes_graph(n), + name="Dorogovtsev-Goltsev-Mendes Graph, %d-th generation" % n) + def FoldedCubeGraph(n): r""" @@ -1384,23 +1389,22 @@ def FoldedCubeGraph(n): sage: fc.is_isomorphic(clebsch) True """ - if n < 1: raise ValueError("The value of n must be at least 2") - g = CubeGraph(n-1) + g = CubeGraph(n - 1) g.name("Folded Cube Graph") # Complementing the binary word def complement(x): - x = x.replace('0','a') - x = x.replace('1','0') - x = x.replace('a','1') + x = x.replace('0', 'a') + x = x.replace('1', '0') + x = x.replace('a', '1') return x for x in g: if x[0] == '0': - g.add_edge(x,complement(x)) + g.add_edge(x, complement(x)) return g @@ -1509,11 +1513,12 @@ def FriendshipGraph(n): center = 2 * n G = Graph(N, name="Friendship graph") for i in range(0, N - 1, 2): - G.add_cycle([center, i, i+1]) - G.set_pos({center:(0, 0)}) + G.add_cycle([center, i, i + 1]) + G.set_pos({center: (0, 0)}) G._circle_embedding(list(range(N - 1)), radius=1) return G + def FuzzyBallGraph(partition, q): r""" Construct a Fuzzy Ball graph with the integer partition @@ -1554,14 +1559,14 @@ def FuzzyBallGraph(partition, q): {x^8 - 8*x^7 + 4079/150*x^6 - 68689/1350*x^5 + 610783/10800*x^4 - 120877/3240*x^3 + 1351/100*x^2 - 931/450*x} """ from sage.graphs.generators.basic import CompleteGraph - if len(partition)<1: + if len(partition) < 1: raise ValueError("partition must be a nonempty list of positive integers") - n=q+sum(partition) - g=CompleteGraph(n) - curr_vertex=0 - for e,p in enumerate(partition): - g.add_edges([(curr_vertex+i, 'a{0}'.format(e+1)) for i in range(p)]) - curr_vertex+=p + n = q + sum(partition) + g = CompleteGraph(n) + curr_vertex = 0 + for e, p in enumerate(partition): + g.add_edges([(curr_vertex + i, 'a{0}'.format(e + 1)) for i in range(p)]) + curr_vertex += p return g @@ -1613,7 +1618,7 @@ def fib(level, node, y): y -= s diff = F[level] T.add_edge(node, node - diff) - if level == 1: # only one child + if level == 1: # only one child pos[node - diff] = (node, y) return T.add_edge(node, node + diff) @@ -1673,9 +1678,9 @@ def GeneralizedPetersenGraph(n, k): - Anders Jonsson (2009-10-15) """ if n < 3: - raise ValueError("n must be larger than 2") + raise ValueError("n must be larger than 2") if k < 1 or k > (n - 1) // 2: - raise ValueError("k must be in 1<= k <=floor((n-1)/2)") + raise ValueError("k must be in 1<= k <=floor((n-1)/2)") G = Graph(2 * n, name="Generalized Petersen graph (n="+str(n)+",k="+str(k)+")") for i in range(n): G.add_edge(i, (i+1) % n) @@ -1685,6 +1690,7 @@ def GeneralizedPetersenGraph(n, k): G._circle_embedding(list(range(n, 2*n)), radius=.5, angle=pi/2) return G + def IGraph(n, j, k): r""" Return an I-graph with `2n` nodes. @@ -1769,6 +1775,7 @@ def IGraph(n, j, k): G._circle_embedding(list(range(n, 2 * n)), radius=.5, angle=pi/2) return G + def DoubleGeneralizedPetersenGraph(n, k): r""" Return a double generalized Petersen graph with `4n` nodes. @@ -1821,9 +1828,9 @@ def DoubleGeneralizedPetersenGraph(n, k): ValueError: k must be in 1 <= k <= floor((n - 1) / 2) """ if n < 3: - raise ValueError("n must be larger than 2") + raise ValueError("n must be larger than 2") if k < 1 or k > (n - 1) // 2: - raise ValueError("k must be in 1 <= k <= floor((n - 1) / 2)") + raise ValueError("k must be in 1 <= k <= floor((n - 1) / 2)") G = Graph(4 * n, name="Double generalized Petersen graph (n={}, k={})".format(n, k)) for i in range(n): @@ -1832,13 +1839,14 @@ def DoubleGeneralizedPetersenGraph(n, k): G.add_edge(i, i + n) G.add_edge(i + 2 * n, i + 3 * n) G.add_edge(i + n, (i + k) % n + 2 * n) - G.add_edge(i+ 2 * n, (i + k) % n + n) + G.add_edge(i + 2 * n, (i + k) % n + n) G._circle_embedding(list(range(n)), radius=3, angle=pi/2) G._circle_embedding(list(range(n, 2 * n)), radius=2, angle=pi/2) G._circle_embedding(list(range(2 * n, 3 * n)), radius=1.5, angle=pi/2) G._circle_embedding(list(range(3 * n, 4 * n)), radius=0.5, angle=pi/2) return G + def RoseWindowGraph(n, a, r): r""" Return a rose window graph with `2n` nodes. @@ -1927,6 +1935,7 @@ def RoseWindowGraph(n, a, r): G._circle_embedding(list(range(n, 2 * n)), radius=0.5, angle=pi/2) return G + def TabacjnGraph(n, a, b, r): r""" Return a Tabačjn graph with `2n` nodes. @@ -2036,7 +2045,7 @@ def TabacjnGraph(n, a, b, r): return G -def HararyGraph( k, n ): +def HararyGraph(k, n): r""" Returns the Harary graph on `n` vertices and connectivity `k`, where `2 \leq k < n`. @@ -2079,20 +2088,21 @@ def HararyGraph( k, n ): if k >= n: raise ValueError("Number of vertices n should be greater than k.") - if k%2 == 0: - G = CirculantGraph( n, list(range(1,k//2+1)) ) + if k % 2 == 0: + G = CirculantGraph(n, list(range(1, k//2 + 1))) else: - if n%2 == 0: - G = CirculantGraph( n, list(range(1,(k-1)//2+1)) ) + if n % 2 == 0: + G = CirculantGraph(n, list(range(1, (k - 1)//2 + 1))) for i in range(n): - G.add_edge( i, (i + n//2)%n ) + G.add_edge(i, (i + n//2) % n) else: - G = HararyGraph( k-1, n ) - for i in range((n-1)//2 + 1): - G.add_edge( i, (i + (n-1)//2)%n ) - G.name('Harary graph {0}, {1}'.format(k,n)) + G = HararyGraph(k - 1, n) + for i in range((n - 1)//2 + 1): + G.add_edge(i, (i + (n - 1)//2) % n) + G.name('Harary graph {0}, {1}'.format(k, n)) return G + def HyperStarGraph(n, k): r""" Return the hyper-star graph `HS(n, k)`. @@ -2163,7 +2173,8 @@ def HyperStarGraph(n, k): c[i] = one adj[u] = L - return Graph(adj, format='dict_of_lists', name="HS(%d,%d)"%(n,k)) + return Graph(adj, format='dict_of_lists', name="HS(%d,%d)" % (n, k)) + def LCFGraph(n, shift_list, repeats): r""" @@ -2240,6 +2251,7 @@ def LCFGraph(n, shift_list, repeats): G._circle_embedding(list(range(n)), radius=1, angle=pi/2) return G + def MycielskiGraph(k=1, relabel=True): r""" Returns the `k`-th Mycielski Graph. @@ -2296,7 +2308,7 @@ def MycielskiGraph(k=1, relabel=True): g = Graph() g.name("Mycielski Graph " + str(k)) - if k<0: + if k < 0: raise ValueError("parameter k must be a nonnegative integer") if k == 0: @@ -2307,10 +2319,10 @@ def MycielskiGraph(k=1, relabel=True): return g if k == 2: - g.add_edge(0,1) + g.add_edge(0, 1) return g - g0 = MycielskiGraph(k-1) + g0 = MycielskiGraph(k - 1) g = MycielskiStep(g0) g.name("Mycielski Graph " + str(k)) if relabel: @@ -2318,6 +2330,7 @@ def MycielskiGraph(k=1, relabel=True): return g + def MycielskiStep(g): r""" Perform one iteration of the Mycielski construction. @@ -2334,7 +2347,6 @@ def MycielskiStep(g): sage: h.is_isomorphic(graphs.GrotzschGraph()) True """ - # Make a copy of the input graph g gg = copy(g) @@ -2347,18 +2359,19 @@ def MycielskiStep(g): gg.add_vertices(wlist) # add the z vertex as (0,0) - gg.add_vertex((0,0)) + gg.add_vertex((0, 0)) # add the edges from z to w_i - gg.add_edges([((0, 0), (2, v)) for v in g] ) + gg.add_edges([((0, 0), (2, v)) for v in g]) # make the v_i w_j edges for v in g: - gg.add_edges([((1,v),(2,vv)) for vv in g.neighbors(v)]) + gg.add_edges([((1, v), (2, vv)) for vv in g.neighbors(v)]) return gg -def NKStarGraph(n,k): + +def NKStarGraph(n, k): r""" Returns the (n,k)-star graph. @@ -2393,34 +2406,35 @@ def NKStarGraph(n,k): - Michael Yurko (2009-09-01) """ from sage.combinat.permutation import Arrangements - #set from which to permute - set = [str(i) for i in range(1,n+1)] - #create dict + # set from which to permute + set = [str(i) for i in range(1, n + 1)] + # create dict d = {} - for v in Arrangements(set,k): - v = list(v) # So we can easily mutate it + for v in Arrangements(set, k): + v = list(v) # So we can easily mutate it tmp_dict = {} - #add edges of dimension i - for i in range(1,k): - #swap 0th and ith element + # add edges of dimension i + for i in range(1, k): + # swap 0th and ith element v[0], v[i] = v[i], v[0] - #convert to str and add to list + # convert to str and add to list vert = "".join(v) tmp_dict[vert] = None - #swap back + # swap back v[0], v[i] = v[i], v[0] - #add other edges + # add other edges tmp_bit = v[0] for i in set: - #check if external - if not (i in v): + # check if external + if i not in v: v[0] = i - #add edge + # add edge vert = "".join(v) tmp_dict[vert] = None v[0] = tmp_bit d["".join(v)] = tmp_dict - return Graph(d, name="(%d,%d)-star"%(n,k)) + return Graph(d, name="(%d,%d)-star" % (n, k)) + def NStarGraph(n): r""" @@ -2451,26 +2465,26 @@ def NStarGraph(n): - Michael Yurko (2009-09-01) """ from sage.combinat.permutation import Permutations - #set from which to permute - set = [str(i) for i in range(1,n+1)] - #create dictionary of lists - #vertices are adjacent if the first element - #is swapped with the ith element + # set from which to permute + set = [str(i) for i in range(1, n + 1)] + # create dictionary of lists + # vertices are adjacent if the first element is swapped with the ith element d = {} for v in Permutations(set): - v = list(v) # So we can easily mutate it + v = list(v) # So we can easily mutate it tmp_dict = {} - for i in range(1,n): + for i in range(1, n): if v[0] != v[i]: - #swap 0th and ith element + # swap 0th and ith element v[0], v[i] = v[i], v[0] - #convert to str and add to list + # convert to str and add to list vert = "".join(v) tmp_dict[vert] = None - #swap back + # swap back v[0], v[i] = v[i], v[0] d["".join(v)] = tmp_dict - return Graph(d, name = "%d-star"%n) + return Graph(d, name="%d-star" % n) + def OddGraph(n): r""" @@ -2503,13 +2517,13 @@ def OddGraph(n): ... ValueError: Parameter n should be an integer strictly greater than 1 """ - - if not n>1: + if n <= 1: raise ValueError("Parameter n should be an integer strictly greater than 1") - g = KneserGraph(2*n-1,n-1) + g = KneserGraph(2*n - 1, n - 1) g.name("Odd Graph with parameter %s" % n) return g + def PaleyGraph(q): r""" Paley graph with `q` vertices @@ -2549,10 +2563,11 @@ def PaleyGraph(q): raise ValueError("parameter q must be a prime power") if not mod(q, 4) == 1: raise ValueError("parameter q must be congruent to 1 mod 4") - g = Graph([FiniteField(q,'a'), lambda i,j: (i-j).is_square()], - loops=False, name="Paley graph with parameter {}".format(q)) + g = Graph([FiniteField(q, 'a'), lambda i, j: (i - j).is_square()], + loops=False, name="Paley graph with parameter {}".format(q)) return g + def PasechnikGraph(n): r""" Pasechnik strongly regular graph on `(4n-1)^2` vertices @@ -2638,6 +2653,7 @@ def SquaredSkewHadamardMatrixGraph(n): G.name("skewhad^2_{}".format(n)) return G + def SwitchedSquaredSkewHadamardMatrixGraph(n): r""" A strongly regular graph in Seidel switching class of @@ -2824,7 +2840,6 @@ def HanoiTowerGraph(pegs, disks, labels=True, positions=True): - Rob Beezer, (2009-12-26), with assistance from Su Doree """ - # sanitize input from sage.rings.integer import Integer pegs = Integer(pegs) @@ -2843,7 +2858,7 @@ def HanoiTowerGraph(pegs, disks, labels=True, positions=True): # the number of pegs, and low-order digits to the right # complete graph on number of pegs when just a single disk - edges = [[i,j] for i in range(pegs) for j in range(i+1,pegs)] + edges = [[i, j] for i in range(pegs) for j in range(i + 1, pegs)] nverts = 1 for d in range(2, disks+1): @@ -2857,7 +2872,7 @@ def HanoiTowerGraph(pegs, disks, labels=True, positions=True): for p in range(pegs): largedisk = p*nverts for anedge in prevedges: - edges.append([anedge[0]+largedisk, anedge[1]+largedisk]) + edges.append([anedge[0] + largedisk, anedge[1] + largedisk]) # Two new states may only differ in the large disk # being the only disk on two different pegs, thus @@ -2873,12 +2888,11 @@ def HanoiTowerGraph(pegs, disks, labels=True, positions=True): emptypegs.remove(apeg) reduced_state = reduced_state//pegs for freea, freeb in Subsets(emptypegs, 2): - edges.append([freea*nverts+state,freeb*nverts+state]) + edges.append([freea*nverts + state, freeb*nverts + state]) H = Graph({}, loops=False, multiedges=False) H.add_edges(edges) - # Making labels and/or computing positions can take a long time, # relative to just constructing the edges on integer vertices. # We try to minimize coercion overhead, but need Sage @@ -2923,7 +2937,7 @@ def HanoiTowerGraph(pegs, disks, labels=True, positions=True): locy_temp = parity*sine[p]*locx + cosine[p]*locy - radius*parity*sine[p] locx = locx_temp locy = locy_temp - pos[i] = (locx,locy) + pos[i] = (locx, locy) # set positions, then relabel (not vice versa) if positions: H.set_pos(pos) @@ -2932,6 +2946,7 @@ def HanoiTowerGraph(pegs, disks, labels=True, positions=True): return H + def line_graph_forbidden_subgraphs(): r""" Returns the 9 forbidden subgraphs of a line graph. @@ -3204,6 +3219,7 @@ def next_step(triangle_list): dg.relabel() return dg + def GeneralizedSierpinskiGraph(G, k, stretch=None): r""" Return the generalized Sierpinski graph of `G` of dimension `k`. @@ -3354,6 +3370,7 @@ def rec(H, kk): for u in H}) return H + def WheelGraph(n): """ Returns a Wheel graph with n nodes. @@ -3428,6 +3445,7 @@ def WheelGraph(n): G.name("Wheel graph") return G + def WindmillGraph(k, n): r""" Return the Windmill graph `Wd(k, n)`. @@ -3499,18 +3517,18 @@ def WindmillGraph(k, n): slide = 1/sin(sector/4) pos_dict = {} - for i in range(0,k): + for i in range(0, k): x = float(cos(i*pi/(k-2))) y = float(sin(i*pi/(k-2))) + slide - pos_dict[i] = (x,y) + pos_dict[i] = (x, y) G = Graph() pos = {0: [0, 0]} for i in range(n): - V = list( range(i*(k-1)+1, (i+1)*(k-1)+1) ) + V = list(range(i*(k - 1) + 1, (i + 1)*(k - 1) + 1)) G.add_clique([0]+V) - for j,v in enumerate(V): - x,y = pos_dict[j] + for j, v in enumerate(V): + x, y = pos_dict[j] xv = x*cos(i*sector) - y*sin(i*sector) yv = x*sin(i*sector) + y*cos(i*sector) pos[v] = [xv, yv] @@ -3569,6 +3587,7 @@ def trees(vertices): from sage.graphs.trees import TreeIterator return iter(TreeIterator(vertices)) + def nauty_gentreeg(options="", debug=False): r""" Return a generator which creates non-isomorphic trees from nauty's gentreeg @@ -3692,7 +3711,8 @@ def nauty_gentreeg(options="", debug=False): G = Graph(s[:-1], format='sparse6', loops=False, multiedges=False) yield G -def RingedTree(k, vertex_labels = True): + +def RingedTree(k, vertex_labels=True): r""" Return the ringed tree on k-levels. @@ -3735,43 +3755,44 @@ def RingedTree(k, vertex_labels = True): [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30] """ - if k<1: + if k < 1: raise ValueError('The number of levels must be >= 1.') # Creating the Balanced tree, which contains most edges already - g = BalancedTree(2,k-1) - g.name('Ringed Tree on '+str(k)+' levels') + g = BalancedTree(2, k - 1) + g.name('Ringed Tree on ' + str(k) + ' levels') # We consider edges layer by layer - for i in range(1,k): - vertices = list(range(2**(i)-1,2**(i+1)-1)) + for i in range(1, k): + vertices = list(range(2**(i) - 1, 2**(i + 1) - 1)) # Add the missing edges g.add_cycle(vertices) # And set the vertices' positions radius = i if i <= 1 else 1.5**i - shift = -2**(i-2)+.5 if i > 1 else 0 - g._circle_embedding(vertices, radius = radius, shift = shift) + shift = -2**(i - 2) + .5 if i > 1 else 0 + g._circle_embedding(vertices, radius=radius, shift=shift) # Specific position for the central vertex - g._pos[0] = (0,0.2) + g._pos[0] = (0, 0.2) # Relabel vertices as binary words if not vertex_labels: return g vertices = [''] - for i in range(k-1): - for j in range(2**(i)-1,2**(i+1)-1): + for i in range(k - 1): + for j in range(2**(i) - 1, 2**(i + 1) - 1): v = vertices[j] - vertices.append(v+'0') - vertices.append(v+'1') + vertices.append(v + '0') + vertices.append(v + '1') g.relabel(vertices) return g + def MathonPseudocyclicMergingGraph(M, t): r""" Mathon's merging of classes in a pseudo-cyclic 3-class association scheme @@ -3824,6 +3845,7 @@ def MathonPseudocyclicMergingGraph(M, t): A += sum(M[0].tensor_product(x) for x in M[1:]) return Graph(A) + def MathonPseudocyclicStronglyRegularGraph(t, G=None, L=None): r""" Return a strongly regular graph on `(4t+1)(4t-1)^2` vertices from @@ -3912,24 +3934,24 @@ def MathonPseudocyclicStronglyRegularGraph(t, G=None, L=None): from sage.matrix.constructor import matrix, block_matrix, \ ones_matrix, identity_matrix from sage.arith.all import two_squares - p = 4*t+1 + p = 4*t + 1 try: x = two_squares(p) except ValueError: raise ValueError(str(p)+" must be a sum of two squares!") if G is None: from sage.graphs.strongly_regular_db import strongly_regular_graph as SRG - G = SRG(p, 2*t, t-1) + G = SRG(p, 2*t, t - 1) G.relabel(range(p)) if L is None: from sage.matrix.constructor import circulant - L = circulant(list(range(2 * t + 1))+list(range(-2 * t, 0))) - q = 4*t -1 - K = GF(q,prefix='x') - K_pairs = set(frozenset([x,-x]) for x in K) + L = circulant(list(range(2 * t + 1)) + list(range(-2 * t, 0))) + q = 4*t - 1 + K = GF(q, prefix='x') + K_pairs = set(frozenset([x, -x]) for x in K) K_pairs.discard(frozenset([0])) a = [None]*(q-1) # order the non-0 elements of K as required - for i,(x,y) in enumerate(K_pairs): + for i, (x, y) in enumerate(K_pairs): a[i] = x a[-i-1] = y a.append(K(0)) # and append the 0 of K at the end @@ -3946,36 +3968,37 @@ def B(m): def f(i, j): if i == j: return 0 * I - elif (a[j]-a[i]).is_square(): + elif (a[j] - a[i]).is_square(): return I + F else: return J - F elif m < 2*t: def f(i, j): - return F * P[a.index(g**(2*m) * (a[i]+a[j]))] + return F * P[a.index(g**(2*m) * (a[i] + a[j]))] elif m == 2*t: def f(i, j): return E * P[i] - return block_matrix(q,q, [f(i, j) for i in range(q) for j in range(q)]) + return block_matrix(q, q, [f(i, j) for i in range(q) for j in range(q)]) def Acon(i, j): J = ones_matrix(q**2) - if i==j: - return B(0) - if L[i,j]>0: - if G.has_edge(i,j): - return B(L[i,j]) - return J-B(L[i,j]) - if G.has_edge(i,j): - return B(-L[i,j]).T - return J-B(-L[i,j]).T - - A = Graph(block_matrix(p, p, [Acon(i,j) for i in range(p) for j in range(p)])) - A.name("Mathon's PC SRG on "+str(p*q**2)+" vertices") + if i == j: + return B(0) + if L[i, j] > 0: + if G.has_edge(i, j): + return B(L[i, j]) + return J - B(L[i, j]) + if G.has_edge(i, j): + return B(-L[i, j]).T + return J - B(-L[i, j]).T + + A = Graph(block_matrix(p, p, [Acon(i, j) for i in range(p) for j in range(p)])) + A.name("Mathon's PC SRG on " + str(p*q**2) + " vertices") A.relabel() return A -def TuranGraph(n,r): + +def TuranGraph(n, r): r""" Returns the Turan graph with parameters `n, r`. @@ -4038,6 +4061,7 @@ def TuranGraph(n,r): return g + def MuzychukS6Graph(n, d, Phi='fixed', Sigma='fixed', verbose=False): r""" Return a strongly regular graph of S6 type from [Muz2007]_ on @@ -4128,8 +4152,8 @@ def MuzychukS6Graph(n, d, Phi='fixed', Sigma='fixed', verbose=False): ... ValueError: Sigma must be 'random' or 'fixed' """ - ### TO DO: optimise - ### add option to return phi, sigma? generate phi, sigma from seed? (int say?) + # TO DO: optimise + # add option to return phi, sigma? generate phi, sigma from seed? (int say?) from sage.combinat.designs.block_design import ProjectiveGeometryDesign from sage.misc.prandom import randrange @@ -4149,7 +4173,7 @@ def MuzychukS6Graph(n, d, Phi='fixed', Sigma='fixed', verbose=False): t = time() # build L, L_i and the design - m = int((n**d-1)/(n-1) + 1) #from m = p + 1, p = (n^d-1) / (n-1) + m = int((n**d - 1)/(n - 1) + 1) # from m = p + 1, p = (n^d-1) / (n-1) L = CompleteGraph(m) L.delete_edges([(2 * x, 2 * x + 1) for x in range(m // 2)]) L_i = [L.edges_incident(x, labels=False) for x in range(m)] @@ -4166,7 +4190,7 @@ def MuzychukS6Graph(n, d, Phi='fixed', Sigma='fixed', verbose=False): while ParClasses[0]: nextHyp = ParClasses[0].pop() for C in ParClasses[1:]: - listC = sum(C,[]) + listC = sum(C, []) for x in nextHyp: if x in listC: break @@ -4189,8 +4213,8 @@ def MuzychukS6Graph(n, d, Phi='fixed', Sigma='fixed', verbose=False): for C in ParClasses: EC = matrix(QQ, v) for line in C: - for i,j in combinations(line, 2): - EC[i,j] = EC[j,i] = 1/k + for i, j in combinations(line, 2): + EC[i, j] = EC[j, i] = 1/k EC -= ones_v E[tuple(C[0])] = EC if verbose: @@ -4206,21 +4230,19 @@ def MuzychukS6Graph(n, d, Phi='fixed', Sigma='fixed', verbose=False): rand = randrange(0, len(temp)) Phi[(x, line)] = temp.pop(rand) elif Phi == 'fixed': - Phi = {(x,line):val for x in range(m) for val,line in enumerate(L_i[x])} + Phi = {(x, line): val for x in range(m) for val, line in enumerate(L_i[x])} else: assert isinstance(Phi, dict), \ - "Phi must be a dictionary or 'random' or 'fixed'" - assert set(Phi.keys()) == \ - set([(x, line) for x in range(m) for line in L_i[x]]), \ - 'each Phi_i must have domain L_i' + "Phi must be a dictionary or 'random' or 'fixed'" + assert set(Phi.keys()) == set([(x, line) for x in range(m) for line in L_i[x]]), \ + 'each Phi_i must have domain L_i' for x in range(m): - assert m - 2 == len(set([val - for (key, val) in Phi.items() if key[0] == x])), \ - 'each phi_i must be injective' + assert m - 2 == len(set([val for (key, val) in Phi.items() if key[0] == x])), \ + 'each phi_i must be injective' for val in Phi.values(): - assert val in range(m-1), \ - 'codomain should be {0,..., (n^d - 1)/(n - 1) - 1}' - phi = {(x, line):ParClasses[Phi[(x, line)]] for x in range(m) for line in L_i[x]} + assert val in range(m - 1), \ + 'codomain should be {0,..., (n^d - 1)/(n - 1) - 1}' + phi = {(x, line): ParClasses[Phi[(x, line)]] for x in range(m) for line in L_i[x]} if verbose: print('finished phi at %f (+%f)' % (time() - t, time() - t1)) t1 = time() @@ -4253,7 +4275,7 @@ def MuzychukS6Graph(n, d, Phi='fixed', Sigma='fixed', verbose=False): t1 = time() # build V - edges = [] ###how many? *m^2*n^2 + edges = [] # how many? *m^2*n^2 for (i, j) in L.edges(sort=True, labels=False): for hyp in phi[(i, (i, j))]: for x in hyp: @@ -4275,8 +4297,8 @@ def MuzychukS6Graph(n, d, Phi='fixed', Sigma='fixed', verbose=False): F_i = [1 - D_i[x] - ones_v for x in range(m)] # as the sum of (1/v)*J_\Omega_i, D_i, F_i is identity A_i = [(v-k)*ones_v - k*F_i[x] for x in range(m)] - # we know A_i = k''*(1/v)*J_\Omega_i + r''*D_i + s''*F_i, - # and (k'', s'', r'') = (v - k, 0, -k) + # we know A_i = k''*(1/v)*J_\Omega_i + r''*D_i + s''*F_i, + # and (k'', s'', r'') = (v - k, 0, -k) if verbose: print('finished D, F and A at %f (+%f)' % (time() - t, time() - t1)) t1 = time() @@ -4291,6 +4313,7 @@ def MuzychukS6Graph(n, d, Phi='fixed', Sigma='fixed', verbose=False): print('finished at %f (+%f)' % ((time() - t), time() - t1)) return V + def CubeConnectedCycle(d): r""" Return the cube-connected cycle of dimension `d`. @@ -4352,7 +4375,7 @@ def CubeConnectedCycle(d): if d == 1: G.allow_loops(True) # only d = 1 requires loops - G.add_edges([((0,0),(0,1)), ((0,0),(0,0)), ((0,1),(0,1))]) + G.add_edges([((0, 0), (0, 1)), ((0, 0), (0, 0)), ((0, 1), (0, 1))]) return G if d == 2: @@ -4364,10 +4387,10 @@ def CubeConnectedCycle(d): ((2, 0), (3, 0)), ((3, 0), (3, 1)), ((3, 0), (3, 1))]) return G - for x in range(1<= 2 and len(edge) <= 3,\ - "%s is not a valid format for edge"%(edge) + "%s is not a valid format for edge" % (edge) u = edge[0] v = edge[1] - assert self.has_edge(*edge), "%s is not an edge"%(edge) + assert self.has_edge(*edge), "%s is not an edge" % (edge) if len(edge) == 2: if self.has_multiple_edges(): for label in self.edge_label(u, v): @@ -21489,10 +21498,10 @@ def graphviz_string(self, **options): if options['rankdir'] != "down": directions = {'up': 'BT', 'down': 'TB', 'left': 'RL', 'right': 'LR'} if options['rankdir'] not in directions: - raise ValueError("rankdir should be one of %s"%directions.keys()) - s += ' rankdir=%s\n'%(directions[options['rankdir']]) + raise ValueError("rankdir should be one of %s" % directions.keys()) + s += ' rankdir=%s\n' % (directions[options['rankdir']]) if (options['vertex_labels'] and - options['labels'] == "latex"): # not a perfect option name + options['labels'] == "latex"): # not a perfect option name # TODO: why do we set this only for latex labels? s += ' node [shape="plaintext"];\n' @@ -21501,11 +21510,11 @@ def graphviz_string(self, **options): if not options['vertex_labels']: node_options = "" elif options['labels'] == "latex": - node_options = " [label=\" \", texlbl=\"$%s$\"]"%quoted_latex(v) + node_options = " [label=\" \", texlbl=\"$%s$\"]" % quoted_latex(v) else: - node_options = " [label=\"%s\"]" %quoted_str(v) + node_options = " [label=\"%s\"]" % quoted_str(v) - s += ' %s %s;\n'%(key(v), node_options) + s += ' %s %s;\n' % (key(v), node_options) s += "\n" # subgraphs clusters for loop @@ -21519,7 +21528,7 @@ def graphviz_string(self, **options): s += '}\n\n' if default_color is not None: - s += 'edge [color="%s"];\n'%default_color + s += 'edge [color="%s"];\n' % default_color # edges for loop for u, v, label in self.edge_iterator(): @@ -21528,17 +21537,17 @@ def graphviz_string(self, **options): 'backward': False, 'dot': None, 'edge_string': default_edge_string, - 'color' : default_color, - 'label' : label, + 'color' : default_color, + 'label' : label, 'label_style': options['labels'] if options['edge_labels'] else None } for f in edge_option_functions: - edge_options.update(f((u, v,label))) + edge_options.update(f((u, v, label))) if not edge_options['edge_string'] in ['--', '->']: - raise ValueError("edge_string(='{}') in edge_options dict for the " - "edge ({}, {}) should be '--' " - "or '->'".format(edge_options['edge_string'], u, v)) + raise ValueError("edge_string(='{}') in edge_options dict for " + "the edge ({}, {}) should be '--' or '->'" + .format(edge_options['edge_string'], u, v)) dot_options = [] @@ -21549,9 +21558,9 @@ def graphviz_string(self, **options): label = edge_options['label'] if label is not None and edge_options['label_style'] is not None: if edge_options['label_style'] == 'latex': - dot_options.append('label=" ", texlbl="$%s$"'%quoted_latex(label)) + dot_options.append('label=" ", texlbl="$%s$"' % quoted_latex(label)) else: - dot_options.append('label="%s"'% label) + dot_options.append('label="%s"' % label) if edge_options['color'] != default_color: col = edge_options['color'] @@ -21574,13 +21583,14 @@ def graphviz_string(self, **options): dot_options.append('dir={}'.format(edge_options['dir'])) else: raise ValueError("dir(='{}') in edge_options dict for the" - " edge ({}, {}) should be 'forward', 'back', 'both'," - " or 'none'".format(edge_options['dir'], u, v)) + " edge ({}, {}) should be 'forward', 'back'," + " 'both', or 'none'" + .format(edge_options['dir'], u, v)) - s+= ' %s %s %s' % (key(u), edge_options['edge_string'], key(v)) + s += ' %s %s %s' % (key(u), edge_options['edge_string'], key(v)) if dot_options: s += " [" + ", ".join(dot_options)+"]" - s+= ";\n" + s += ";\n" s += "}" return s @@ -21620,7 +21630,7 @@ def graphviz_to_file_named(self, filename, **options): with open(filename, 'wt') as file: file.write(self.graphviz_string(**options)) - ### Spectrum + # Spectrum def spectrum(self, laplacian=False): r""" @@ -21806,23 +21816,47 @@ def eigenvectors(self, laplacian=False): sage: C = graphs.CycleGraph(8) sage: C.eigenvectors() - [(2, [ - (1, 1, 1, 1, 1, 1, 1, 1) - ], 1), (-2, [ - (1, -1, 1, -1, 1, -1, 1, -1) - ], 1), (0, [ - (1, 0, -1, 0, 1, 0, -1, 0), - (0, 1, 0, -1, 0, 1, 0, -1) - ], 2), (-1.4142135623..., [(1, 0, -1, 1.4142135623..., -1, 0, 1, -1.4142135623...), (0, 1, -1.4142135623..., 1, 0, -1, 1.4142135623..., -1)], 2), (1.4142135623..., [(1, 0, -1, -1.4142135623..., -1, 0, 1, 1.4142135623...), (0, 1, 1.4142135623..., 1, 0, -1, -1.4142135623..., -1)], 2)] + [(2, + [ + (1, 1, 1, 1, 1, 1, 1, 1) + ], + 1), + (-2, + [ + (1, -1, 1, -1, 1, -1, 1, -1) + ], + 1), + (0, + [ + (1, 0, -1, 0, 1, 0, -1, 0), + (0, 1, 0, -1, 0, 1, 0, -1) + ], + 2), + (-1.4142135623..., + [(1, 0, -1, 1.4142135623..., -1, 0, 1, -1.4142135623...), + (0, 1, -1.4142135623..., 1, 0, -1, 1.4142135623..., -1)], + 2), + (1.4142135623..., + [(1, 0, -1, -1.4142135623..., -1, 0, 1, 1.4142135623...), + (0, 1, 1.4142135623..., 1, 0, -1, -1.4142135623..., -1)], + 2)] A digraph may have complex eigenvalues. Previously, the complex parts of graph eigenvalues were being dropped. For a 3-cycle, we have:: sage: T = DiGraph({0:[1], 1:[2], 2:[0]}) sage: T.eigenvectors() - [(1, [ - (1, 1, 1) - ], 1), (-0.5000000000... - 0.8660254037...*I, [(1, -0.5000000000... - 0.8660254037...*I, -0.5000000000... + 0.8660254037...*I)], 1), (-0.5000000000... + 0.8660254037...*I, [(1, -0.5000000000... + 0.8660254037...*I, -0.5000000000... - 0.8660254037...*I)], 1)] + [(1, + [ + (1, 1, 1) + ], + 1), + (-0.5000000000... - 0.8660254037...*I, + [(1, -0.5000000000... - 0.8660254037...*I, -0.5000000000... + 0.8660254037...*I)], + 1), + (-0.5000000000... + 0.8660254037...*I, + [(1, -0.5000000000... + 0.8660254037...*I, -0.5000000000... - 0.8660254037...*I)], + 1)] """ if laplacian: M = self.kirchhoff_matrix(vertices=list(self)) @@ -21941,7 +21975,7 @@ def eigenspaces(self, laplacian=False): # which would be a change in default behavior return M.right_eigenspaces(format='galois', algebraic_multiplicity=False) - ### Automorphism and isomorphism + # Automorphism and isomorphism def relabel(self, perm=None, inplace=True, return_map=False, check_input=True, complete_partial_function=True, immutable=None): r""" @@ -22167,14 +22201,14 @@ def relabel(self, perm=None, inplace=True, return_map=False, check_input=True, c if not inplace: G = copy(self) perm2 = G.relabel(perm, - return_map= return_map, - check_input = check_input, - complete_partial_function = complete_partial_function) + return_map=return_map, + check_input=check_input, + complete_partial_function=complete_partial_function) if immutable is None: immutable = self.is_immutable() if immutable: - G = self.__class__(G, immutable = True) + G = self.__class__(G, immutable=True) if return_map: return G, perm2 @@ -22287,8 +22321,8 @@ def degree_to_cell(self, vertex, cell): (0, 2) """ if self._directed: - in_neighbors_in_cell = set([a for a,_,_ in self.incoming_edges(vertex)]) & set(cell) - out_neighbors_in_cell = set([a for _,a,_ in self.outgoing_edges(vertex)]) & set(cell) + in_neighbors_in_cell = set([a for a, _, _ in self.incoming_edges(vertex)]) & set(cell) + out_neighbors_in_cell = set([a for _, a, _ in self.outgoing_edges(vertex)]) & set(cell) return (len(in_neighbors_in_cell), len(out_neighbors_in_cell)) else: neighbors_in_cell = set(self.neighbors(vertex)) & set(cell) @@ -22348,9 +22382,9 @@ def is_equitable(self, partition, quotient_matrix=False): """ from sage.misc.flatten import flatten if sorted(flatten(partition, max_level=1)) != self.vertices(sort=True): - raise TypeError("Partition (%s) is not valid for this graph: vertices are incorrect."%partition) - if any(len(cell)==0 for cell in partition): - raise TypeError("Partition (%s) is not valid for this graph: there is a cell of length 0."%partition) + raise TypeError("Partition (%s) is not valid for this graph: vertices are incorrect." % partition) + if any(not cell for cell in partition): + raise TypeError("Partition (%s) is not valid for this graph: there is a cell of length 0." % partition) if quotient_matrix: from sage.matrix.constructor import Matrix from sage.rings.integer_ring import IntegerRing @@ -22436,9 +22470,9 @@ def coarsest_equitable_refinement(self, partition, sparse=True): """ from sage.misc.flatten import flatten if set(flatten(partition, max_level=1)) != set(self): - raise TypeError("partition (%s) is not valid for this graph: vertices are incorrect"%partition) + raise TypeError("partition (%s) is not valid for this graph: vertices are incorrect" % partition) if any(len(cell) == 0 for cell in partition): - raise TypeError("partition (%s) is not valid for this graph: there is a cell of length 0"%partition) + raise TypeError("partition (%s) is not valid for this graph: there is a cell of length 0" % partition) if self.has_multiple_edges(): raise TypeError("refinement function does not support multiple edges") G = copy(self) @@ -22694,8 +22728,8 @@ def automorphism_group(self, partition=None, verbosity=0, raise NotImplementedError("algorithm 'bliss' cannot be used for graph with multiedges") have_bliss = False - if (algorithm == 'bliss' or # explicit choice from the user; or - (algorithm is None and # by default + if (algorithm == 'bliss' or # explicit choice from the user; or + (algorithm is None and # by default have_bliss)): Bliss().require() @@ -22720,8 +22754,7 @@ def automorphism_group(self, partition=None, verbosity=0, return ret[0] return ret - if (algorithm is not None and - algorithm != "sage"): + if algorithm is not None and algorithm != "sage": raise ValueError("'algorithm' must be equal to 'bliss', 'sage', or None") from sage.groups.perm_gps.partn_ref.refinement_graphs import search_tree @@ -22735,21 +22768,24 @@ def automorphism_group(self, partition=None, verbosity=0, partition = [list(self)] if edge_labels or self.has_multiple_edges(): - G, partition, relabeling = graph_isom_equivalent_non_edge_labeled_graph(self, partition, return_relabeling=True, ignore_edge_labels=(not edge_labels)) + ret = graph_isom_equivalent_non_edge_labeled_graph(self, partition, + return_relabeling=True, + ignore_edge_labels=(not edge_labels)) + G, partition, relabeling = ret G_vertices = list(chain(*partition)) - G_to = {u: i for i,u in enumerate(G_vertices)} + G_to = {u: i for i, u in enumerate(G_vertices)} DoDG = DiGraph if self._directed else Graph H = DoDG(len(G_vertices), loops=G.allows_loops()) HB = H._backend - for u,v in G.edge_iterator(labels=False): + for u, v in G.edge_iterator(labels=False): HB.add_edge(G_to[u], G_to[v], None, G._directed) GC = HB.c_graph()[0] partition = [[G_to[vv] for vv in cell] for cell in partition] A = search_tree(GC, partition, lab=False, dict_rep=True, dig=dig, verbosity=verbosity, order=order) if order: - a,b,c = A + a, b, c = A else: - a,b = A + a, b = A b_new = {v: b[G_to[v]] for v in G_to} b = b_new # b is a translation of the labellings @@ -22778,11 +22814,11 @@ def automorphism_group(self, partition=None, verbosity=0, b = translation_d else: G_vertices = list(chain(*partition)) - G_to = {u: i for i,u in enumerate(G_vertices)} + G_to = {u: i for i, u in enumerate(G_vertices)} DoDG = DiGraph if self._directed else Graph H = DoDG(len(G_vertices), loops=self.allows_loops()) HB = H._backend - for u,v in self.edge_iterator(labels=False): + for u, v in self.edge_iterator(labels=False): HB.add_edge(G_to[u], G_to[v], None, self._directed) GC = HB.c_graph()[0] partition = [[G_to[vv] for vv in cell] for cell in partition] @@ -22790,15 +22826,15 @@ def automorphism_group(self, partition=None, verbosity=0, if return_group: A = search_tree(GC, partition, dict_rep=True, lab=False, dig=dig, verbosity=verbosity, order=order) if order: - a,b,c = A + a, b, c = A else: - a,b = A + a, b = A b_new = {v: b[G_to[v]] for v in G_to} b = b_new else: a = search_tree(GC, partition, dict_rep=False, lab=False, dig=dig, verbosity=verbosity, order=order) if order: - a,c = a + a, c = a output = [] if return_group: @@ -22822,17 +22858,15 @@ def automorphism_group(self, partition=None, verbosity=0, from sage.groups.perm_gps.partn_ref.refinement_graphs import get_orbits output.append([[G_from[v] for v in W] for W in get_orbits(a, self.num_verts())]) - # A Python switch statement! - return { 0: None, - 1: output[0], - 2: tuple(output), - 3: tuple(output), - 4: tuple(output) - }[len(output)] + if len(output) == 1: + return output[0] + elif len(output) > 1: + return tuple(output) + return None def is_vertex_transitive(self, partition=None, verbosity=0, - edge_labels=False, order=False, - return_group=True, orbits=False): + edge_labels=False, order=False, + return_group=True, orbits=False): """ Returns whether the automorphism group of self is transitive within the partition provided, by default the unit partition of the @@ -22962,17 +22996,18 @@ def is_hamiltonian(self, solver=None, constraint_generation=None, except EmptySetError: return False + def is_isomorphic(self, other, certificate=False, verbosity=0, edge_labels=False): r""" Tests for isomorphism between self and other. INPUT: - - ``certificate`` -- if True, then output is `(a, b)`, where `a` - is a boolean and `b` is either a map or ``None``. + - ``certificate`` -- if True, then output is `(a, b)`, where `a` + is a boolean and `b` is either a map or ``None`` - - ``edge_labels`` -- boolean (default: ``False``); if ``True`` allows - only permutations respecting edge labels. + - ``edge_labels`` -- boolean (default: ``False``); if ``True`` allows + only permutations respecting edge labels OUTPUT: @@ -23197,10 +23232,12 @@ def is_isomorphic(self, other, certificate=False, verbosity=0, edge_labels=False if not self.order() and not other.order(): return (True, None) if certificate else True - if (self.is_directed() != other.is_directed() or self.order() != other.order() or - self.size() != other.size() or self.degree_sequence() != other.degree_sequence()): + if (self.is_directed() != other.is_directed() or + self.order() != other.order() or + self.size() != other.size() or + self.degree_sequence() != other.degree_sequence()): if certificate: - return False,None + return False, None else: return False @@ -23212,36 +23249,53 @@ def is_isomorphic(self, other, certificate=False, verbosity=0, edge_labels=False if edge_labels and sorted(self.edge_labels(), key=str) != sorted(other.edge_labels(), key=str): return (False, None) if certificate else False else: - G, partition, relabeling, G_edge_labels = graph_isom_equivalent_non_edge_labeled_graph(self, return_relabeling=True, ignore_edge_labels=(not edge_labels), return_edge_labels=True) - self_vertices = sum(partition,[]) - G2, partition2, relabeling2, G2_edge_labels = graph_isom_equivalent_non_edge_labeled_graph(other, return_relabeling=True, ignore_edge_labels=(not edge_labels), return_edge_labels=True) + ret = graph_isom_equivalent_non_edge_labeled_graph(self, return_relabeling=True, + ignore_edge_labels=(not edge_labels), + return_edge_labels=True) + G, partition, relabeling, G_edge_labels = ret + self_vertices = sum(partition, []) + ret = graph_isom_equivalent_non_edge_labeled_graph(other, return_relabeling=True, + ignore_edge_labels=(not edge_labels), + return_edge_labels=True) + G2, partition2, relabeling2, G2_edge_labels = ret + if [len(_) for _ in partition] != [len(_) for _ in partition2]: return (False, None) if certificate else False - multilabel = (lambda e:e) if edge_labels else (lambda e:[[None, el[1]] for el in e]) + + if edge_labels: + def multilabel(e): + return e + else: + def multilabel(e): + return [[None, el[1]] for el in e] + if [multilabel(_) for _ in G_edge_labels] != [multilabel(_) for _ in G2_edge_labels]: return (False, None) if certificate else False - partition2 = sum(partition2,[]) + partition2 = sum(partition2, []) other_vertices = partition2 else: G = self partition = [self_vertices] G2 = other partition2 = other_vertices - G_to = {u: i for i,u in enumerate(self_vertices)} - from sage.graphs.graph import Graph - from sage.graphs.digraph import DiGraph - DoDG = DiGraph if self._directed else Graph + G_to = {u: i for i, u in enumerate(self_vertices)} + if self._directed: + from sage.graphs.digraph import DiGraph + DoDG = DiGraph + else: + from sage.graphs.graph import Graph + DoDG = Graph H = DoDG(len(self_vertices), loops=G.allows_loops()) HB = H._backend - for u,v in G.edge_iterator(labels=False): + for u, v in G.edge_iterator(labels=False): HB.add_edge(G_to[u], G_to[v], None, G._directed) G = HB.c_graph()[0] partition = [[G_to[vv] for vv in cell] for cell in partition] GC = G - G2_to = {u: i for i,u in enumerate(other_vertices)} + G2_to = {u: i for i, u in enumerate(other_vertices)} H2 = DoDG(len(other_vertices), loops=G2.allows_loops()) H2B = H2._backend - for u,v in G2.edge_iterator(labels=False): + for u, v in G2.edge_iterator(labels=False): H2B.add_edge(G2_to[u], G2_to[v], None, G2._directed) G2 = H2B.c_graph()[0] partition2 = [G2_to[vv] for vv in partition2] @@ -23445,7 +23499,6 @@ class by some canonization function `c`. If `G` and `H` are graphs, ....: assert gcan0 == gcan1, (edges, labels, part, pp) ....: assert gcan0 == gcan2, (edges, labels, part, pp) """ - # Check parameter combinations if algorithm not in [None, 'sage', 'bliss']: raise ValueError("'algorithm' must be equal to 'bliss', 'sage', or None") @@ -23489,21 +23542,21 @@ class by some canonization function `c`. If `G` and `H` are graphs, if edge_labels or self.has_multiple_edges(): G, partition, relabeling = graph_isom_equivalent_non_edge_labeled_graph(self, partition, return_relabeling=True) G_vertices = list(chain(*partition)) - G_to = {u: i for i,u in enumerate(G_vertices)} + G_to = {u: i for i, u in enumerate(G_vertices)} DoDG = DiGraph if self._directed else Graph H = DoDG(len(G_vertices), loops=G.allows_loops()) HB = H._backend - for u,v in G.edge_iterator(labels=False): + for u, v in G.edge_iterator(labels=False): HB.add_edge(G_to[u], G_to[v], None, G._directed) GC = HB.c_graph()[0] partition = [[G_to[vv] for vv in cell] for cell in partition] - a,b,c = search_tree(GC, partition, certificate=True, dig=dig) + a, b, c = search_tree(GC, partition, certificate=True, dig=dig) # c is a permutation to the canonical label of G, which depends only on isomorphism class of self. H = copy(self) c_new = {v: c[G_to[relabeling[v]]] for v in self} else: G_vertices = list(chain(*partition)) - G_to = {u: i for i,u in enumerate(G_vertices)} + G_to = {u: i for i, u in enumerate(G_vertices)} DoDG = DiGraph if self._directed else Graph H = DoDG(len(G_vertices), loops=self.allows_loops()) HB = H._backend @@ -23511,7 +23564,7 @@ class by some canonization function `c`. If `G` and `H` are graphs, HB.add_edge(G_to[u], G_to[v], None, self._directed) GC = HB.c_graph()[0] partition = [[G_to[vv] for vv in cell] for cell in partition] - a,b,c = search_tree(GC, partition, certificate=True, dig=dig) + a, b, c = search_tree(GC, partition, certificate=True, dig=dig) H = copy(self) c_new = {v: c[G_to[v]] for v in G_to} H.relabel(c_new) @@ -23520,8 +23573,8 @@ class by some canonization function `c`. If `G` and `H` are graphs, else: return H - def is_cayley(self, return_group = False, mapping = False, - generators = False, allow_disconnected = False): + def is_cayley(self, return_group=False, mapping=False, + generators=False, allow_disconnected=False): r""" Check whether the graph is a Cayley graph. @@ -23626,11 +23679,11 @@ def is_cayley(self, return_group = False, mapping = False, ....: generators = True) (False, False, False) """ - if self.order() == 0: + if not self: n = return_group + mapping + generators - if n == 0: + if not n: return False - return tuple([False] * (n+1)) + return tuple([False] * (n + 1)) compute_map = mapping or generators certificate = return_group or compute_map @@ -23639,7 +23692,7 @@ def is_cayley(self, return_group = False, mapping = False, if allow_disconnected and self.is_vertex_transitive(): C = self.connected_components_subgraphs() if certificate: - c, CG = C[0].is_cayley(return_group = True) + c, CG = C[0].is_cayley(return_group=True) if c: from sage.groups.perm_gps.permgroup import PermutationGroup I = [C[0].is_isomorphic(g, certificate=True)[1] for g in C] @@ -23649,24 +23702,24 @@ def is_cayley(self, return_group = False, mapping = False, for h in CG.gens()] + \ [[tuple([M[v] for M in I]) for v in C[0].vertices(sort=False)]] - G = PermutationGroup(gens, domain = self.vertices(sort=False)) + G = PermutationGroup(gens, domain=self.vertices(sort=False)) else: - c = C[0].is_cayley(return_group = False) - elif not self.allows_loops() and not self.allows_multiple_edges() and \ - self.density() > Rational(1)/Rational(2): + c = C[0].is_cayley(return_group=False) + elif (not self.allows_loops() and not self.allows_multiple_edges() and + self.density() > Rational(1)/Rational(2)): if certificate: - c, G = self.complement().is_cayley(return_group = True, - allow_disconnected = True) + c, G = self.complement().is_cayley(return_group=True, + allow_disconnected=True) else: - c = self.complement().is_cayley(return_group = False, - allow_disconnected = True) + c = self.complement().is_cayley(return_group=False, + allow_disconnected=True) else: A = self.automorphism_group() if certificate: - G = A.has_regular_subgroup(return_group = True) + G = A.has_regular_subgroup(return_group=True) c = G is not None else: - c = A.has_regular_subgroup(return_group = False) + c = A.has_regular_subgroup(return_group=False) if c and compute_map: v = next(self.vertex_iterator()) map = {(f**-1)(v): f for f in G} @@ -23909,7 +23962,7 @@ def katz_matrix(self, alpha, nonedgesonly=False, vertices=None): raise ValueError('the parameter alpha must be strictly positive') n = self.order() - if n == 0 : + if not n: raise ValueError('graph is empty') if vertices is None: vertices = self.vertices(sort=True) @@ -23927,7 +23980,7 @@ def katz_matrix(self, alpha, nonedgesonly=False, vertices=None): raise ValueError('the parameter alpha must be less than the reciprocal of the spectral radius of the graph') In = matrix.identity(n) - K = (In - alpha * A.transpose()).inverse() - In + K = (In - alpha * A.transpose()).inverse() - In if nonedgesonly: onesmat = matrix(QQ, n, n, lambda i, j: 1) Missing = onesmat - A - In @@ -23935,7 +23988,7 @@ def katz_matrix(self, alpha, nonedgesonly=False, vertices=None): else: return K - def katz_centrality(self, alpha , u=None): + def katz_centrality(self, alpha, u=None): r""" Return the Katz centrality of vertex `u`. @@ -24115,7 +24168,7 @@ def edge_polytope(self, backend=None): dim = self.num_verts() e = identity_matrix(dim).rows() dic = {v: e[i] for i, v in enumerate(self)} - vertices = ((dic[i] + dic[j]) for i,j in self.edge_iterator(sort_vertices=False, labels=False)) + vertices = ((dic[i] + dic[j]) for i, j in self.edge_iterator(sort_vertices=False, labels=False)) parent = Polyhedra(ZZ, dim, backend=backend) return parent([vertices, [], []], None) @@ -24246,13 +24299,13 @@ def symmetric_edge_polytope(self, backend=None): dim = self.num_verts() e = identity_matrix(dim).rows() dic = {v: e[i] for i, v in enumerate(self)} - vertices = chain(((dic[i] - dic[j]) for i,j in self.edge_iterator(sort_vertices=False, labels=False)), - ((dic[j] - dic[i]) for i,j in self.edge_iterator(sort_vertices=False, labels=False))) + vertices = chain(((dic[i] - dic[j]) for i, j in self.edge_iterator(sort_vertices=False, labels=False)), + ((dic[j] - dic[i]) for i, j in self.edge_iterator(sort_vertices=False, labels=False))) parent = Polyhedra(ZZ, dim, backend=backend) return parent([vertices, [], []], None) -def tachyon_vertex_plot(g, bgcolor=(1,1,1), +def tachyon_vertex_plot(g, bgcolor=(1, 1, 1), vertex_colors=None, vertex_size=0.06, pos3d=None, @@ -24283,12 +24336,12 @@ def tachyon_vertex_plot(g, bgcolor=(1,1,1), from math import sqrt from sage.plot.plot3d.tachyon import Tachyon - c = [0,0,0] + c = [0, 0, 0] r = [] verts = list(g) if vertex_colors is None: - vertex_colors = {(1,0,0): verts} + vertex_colors = {(1, 0, 0): verts} try: for v in verts: c[0] += pos3d[v][0] @@ -24321,14 +24374,17 @@ def tachyon_vertex_plot(g, bgcolor=(1,1,1), i = 0 for color in vertex_colors: i += 1 - TT.texture('node_color_%d'%i, ambient=0.1, diffuse=0.9, + TT.texture('node_color_%d' % i, ambient=0.1, diffuse=0.9, specular=0.03, opacity=1.0, color=color) for v in vertex_colors[color]: - TT.sphere((pos3d[v][0], pos3d[v][1], pos3d[v][2]), vertex_size, 'node_color_%d'%i) + TT.sphere((pos3d[v][0], pos3d[v][1], pos3d[v][2]), vertex_size, 'node_color_%d' % i) return TT, pos3d -def graph_isom_equivalent_non_edge_labeled_graph(g, partition=None, standard_label=None, return_relabeling=False, return_edge_labels=False, inplace=False, ignore_edge_labels=False): + +def graph_isom_equivalent_non_edge_labeled_graph(g, partition=None, standard_label=None, + return_relabeling=False, return_edge_labels=False, + inplace=False, ignore_edge_labels=False): r""" Helper function for canonical labeling of edge labeled (di)graphs. @@ -24546,13 +24602,12 @@ def graph_isom_equivalent_non_edge_labeled_graph(g, partition=None, standard_lab # We build the list of distinct edge labels edge_labels = [] - for _,_,label in G.edge_iterator(): + for _, _, label in G.edge_iterator(): if label != standard_label and label not in edge_labels: edge_labels.append(label) edge_labels = sorted(edge_labels, key=str) - # We now add to G, for each edge (u, v, l), a new vertex i in [n..n + m] and # arcs (u, i, None) and (i, v, None). We record for each distinct label l # the list of added vertices. @@ -24568,7 +24623,7 @@ def graph_isom_equivalent_non_edge_labeled_graph(g, partition=None, standard_lab edges = list(G._backend.iterator_edges(G, True)) i = G_order - for u,v,l in edges: + for u, v, l in edges: if l != standard_label: for el, part in edge_partition: if el == l: @@ -24618,7 +24673,7 @@ def graph_isom_equivalent_non_edge_labeled_graph(g, partition=None, standard_lab else: # Flatten edge_partition to [list of edges, list of edges, ...] - edge_partition = [part for _,part in edge_partition] + edge_partition = [part for _, part in edge_partition] new_partition = [part for part in chain(partition, edge_partition) if part] diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index 96f4f8c6e75..af93c60a7a2 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -1072,19 +1072,19 @@ def __init__(self, data=None, pos=None, loops=None, format=None, if format is None and isinstance(data, DiGraph): data = data.to_undirected() format = 'Graph' - if (format is None and - isinstance(data, list) and - len(data) >= 2 and - callable(data[1])): + if (format is None and + isinstance(data, list) and + len(data) >= 2 and + callable(data[1])): format = 'rule' - if (format is None and - isinstance(data, list) and - len(data) == 2 and - isinstance(data[0], list) and # a list of two lists, the second of - ((isinstance(data[1], list) and # which contains iterables (the edges) - (not data[1] or callable(getattr(data[1][0], "__iter__", None)))) or - (isinstance(data[1], EdgesView)))): + if (format is None and + isinstance(data, list) and + len(data) == 2 and + isinstance(data[0], list) and # a list of two lists, the second of + ((isinstance(data[1], list) and # which contains iterables (the edges) + (not data[1] or callable(getattr(data[1][0], "__iter__", None)))) or + isinstance(data[1], EdgesView))): format = "vertices_and_edges" if format is None and isinstance(data, dict): @@ -1100,14 +1100,14 @@ def __init__(self, data=None, pos=None, loops=None, format=None, # the input is a networkx (Multi)(Di)Graph format = 'NX' - if (format is None and - hasattr(data, 'vcount') and - hasattr(data, 'get_edgelist')): + if (format is None and + hasattr(data, 'vcount') and + hasattr(data, 'get_edgelist')): try: import igraph except ImportError: - raise ImportError("The data seems to be a igraph object, but "+ - "igraph is not installed in Sage. To install "+ + raise ImportError("The data seems to be a igraph object, but " + "igraph is not installed in Sage. To install " "it, run 'sage -i python_igraph'") if format is None and isinstance(data, igraph.Graph): format = 'igraph' @@ -1192,8 +1192,8 @@ def __init__(self, data=None, pos=None, loops=None, format=None, elif format == 'igraph': if data.is_directed(): - raise ValueError("An *undirected* igraph graph was expected. "+ - "To build an directed graph, call the DiGraph "+ + raise ValueError("An *undirected* igraph graph was expected. " + "To build an directed graph, call the DiGraph " "constructor.") self.add_vertices(range(data.vcount())) @@ -1201,21 +1201,21 @@ def __init__(self, data=None, pos=None, loops=None, format=None, if vertex_labels and 'name' in data.vertex_attributes(): vs = data.vs() - self.relabel({v:vs[v]['name'] for v in self}) + self.relabel({v: vs[v]['name'] for v in self}) elif format == 'rule': f = data[1] verts = data[0] if loops is None: - loops = any(f(v,v) for v in verts) + loops = any(f(v, v) for v in verts) if weighted is None: weighted = False self.allow_loops(loops, check=False) self.allow_multiple_edges(True if multiedges else False, check=False) self.add_vertices(verts) - self.add_edges(e for e in itertools.combinations(verts,2) if f(*e)) + self.add_edges(e for e in itertools.combinations(verts, 2) if f(*e)) if loops: - self.add_edges((v,v) for v in verts if f(v,v)) + self.add_edges((v, v) for v in verts if f(v, v)) elif format == "vertices_and_edges": self.allow_multiple_edges(bool(multiedges), check=False) @@ -1226,7 +1226,8 @@ def __init__(self, data=None, pos=None, loops=None, format=None, elif format == 'dict_of_dicts': from .graph_input import from_dict_of_dicts from_dict_of_dicts(self, data, loops=loops, multiedges=multiedges, weighted=weighted, - convert_empty_dict_labels_to_None = False if convert_empty_dict_labels_to_None is None else convert_empty_dict_labels_to_None) + convert_empty_dict_labels_to_None=(False if convert_empty_dict_labels_to_None is None + else convert_empty_dict_labels_to_None)) elif format == 'dict_of_lists': from .graph_input import from_dict_of_lists @@ -1260,12 +1261,12 @@ def __init__(self, data=None, pos=None, loops=None, format=None, if data_structure == "static_sparse": from sage.graphs.base.static_sparse_backend import StaticSparseBackend ib = StaticSparseBackend(self, - loops = self.allows_loops(), - multiedges = self.allows_multiple_edges()) + loops=self.allows_loops(), + multiedges=self.allows_multiple_edges()) self._backend = ib self._immutable = True - ### Formats + # Formats @doc_index("Basic methods") def graph6_string(self): @@ -1395,9 +1396,9 @@ def sparse6_string(self): V = sorted(self) except TypeError: V = self - v_to_int = {v:i for i,v in enumerate(V)} - edges = [sorted((v_to_int[u], v_to_int[v])) for u,v in self.edge_iterator(labels=False)] - edges.sort(key=lambda e: (e[1], e[0])) # reverse lexicographic order + v_to_int = {v: i for i, v in enumerate(V)} + edges = [sorted((v_to_int[u], v_to_int[v])) for u, v in self.edge_iterator(labels=False)] + edges.sort(key=lambda e: (e[1], e[0])) # reverse lexicographic order # encode bit vector k = int((ZZ(n) - 1).nbits()) @@ -1425,15 +1426,15 @@ def sparse6_string(self): # encode s as a 6-string, as in R(x), but padding with 1's # pad on the right to make a multiple of 6 - s = s + ( '1' * ((6 - len(s))%6) ) + s = s + ('1' * ((6 - len(s)) % 6)) # split into groups of 6, and convert numbers to decimal, adding 63 six_bits = '' for i in range(0, len(s), 6): - six_bits += chr( int( s[i:i+6], 2) + 63 ) + six_bits += chr(int(s[i:i+6], 2) + 63) return ':' + generic_graph_pyx.small_integer_to_graph6(n) + six_bits - ### Attributes + # Attributes @doc_index("Basic methods") def is_directed(self): @@ -1447,7 +1448,8 @@ def is_directed(self): """ return False - ### Properties + # Properties + @doc_index("Graph properties") def is_tree(self, certificate=False, output='vertex'): r""" @@ -1787,7 +1789,7 @@ def is_block_graph(self): if self.is_clique(): return True - B,C = self.blocks_and_cut_vertices() + B, C = self.blocks_and_cut_vertices() return all(self.is_clique(vertices=block) for block in B) @doc_index("Graph properties") @@ -1898,7 +1900,7 @@ def is_apex(self): True """ # Easy cases: null graph, subgraphs of K_5 and K_3,3 - if self.order() <= 5 or ( self.order() <= 6 and self.is_bipartite() ): + if self.order() <= 5 or (self.order() <= 6 and self.is_bipartite()): return True return len(self.apex_vertices(k=1)) > 0 @@ -2024,7 +2026,6 @@ def apex_vertices(self, k=None): it = self.vertex_iterator() return [next(it) for _ in range(k)] - if not self.is_connected(): # We search for its non planar connected components. If it has more # than one such component, the graph is not apex. It is apex if @@ -2032,7 +2033,7 @@ def apex_vertices(self, k=None): # planar, or if its unique non planar component is apex. P = [H for H in self.connected_components_subgraphs() if not H.is_planar()] - if not P: # The graph is planar + if not P: # The graph is planar it = self.vertex_iterator() return [next(it) for _ in range(k)] elif len(P) > 1: @@ -2053,7 +2054,6 @@ def apex_vertices(self, k=None): # We make a basic copy of the graph since we will modify it H = Graph(self.edges(labels=0, sort=False), immutable=False, loops=False, multiedges=False) - # General case: basic implementation # # Test for each vertex if its removal makes the graph planar. @@ -2071,7 +2071,7 @@ def apex_vertices(self, k=None): apex = set() for deg in sorted(V): for u in V[deg]: - if u in apex: # True if neighbor of an apex of degree 2 + if u in apex: # True if neighbor of an apex of degree 2 if deg == 2: # We ensure that its neighbors are known apex apex.update(H.neighbor_iterator(u)) @@ -2484,7 +2484,7 @@ def is_triangle_free(self, algorithm='dense_graph', certificate=False): return (self.adjacency_matrix()**3).trace() == 0 else: - raise ValueError("Algorithm '%s' not yet implemented. Please contribute." %(algorithm)) + raise ValueError("Algorithm '%s' not yet implemented. Please contribute." % (algorithm)) @doc_index("Graph properties") def is_split(self): @@ -2753,7 +2753,7 @@ def is_arc_transitive(self): e = next(self.edge_iterator(labels=False)) e = [A._domain_to_gap[e[0]], A._domain_to_gap[e[1]]] - return libgap(A).OrbitLength(e,libgap.OnTuples) == 2*self.size() + return libgap(A).OrbitLength(e, libgap.OnTuples) == 2*self.size() @doc_index("Graph properties") def is_half_transitive(self): @@ -2957,24 +2957,29 @@ def degree_constrained_subgraph(self, bounds, solver=None, verbose=0, p = MixedIntegerLinearProgram(maximization=False, solver=solver) b = p.new_variable(binary=True) - if isinstance(bounds,dict): - f_bounds = lambda x: bounds[x] + if isinstance(bounds, dict): + def f_bounds(x): + return bounds[x] else: f_bounds = bounds - if self.weighted(): from sage.rings.real_mpfr import RR - weight = lambda x: x if x in RR else 1 + + def weight(x): + return x if x in RR else 1 else: - weight = lambda x: 1 + def weight(x): + return 1 for v in self: - minimum,maximum = f_bounds(v) - p.add_constraint(p.sum(b[frozenset((x,y))]*weight(l) for x,y,l in self.edges_incident(v)), - min=minimum, max=maximum) + minimum, maximum = f_bounds(v) + p.add_constraint(p.sum(b[frozenset((x, y))]*weight(l) + for x, y, l in self.edges_incident(v)), + min=minimum, max=maximum) - p.set_objective(p.sum(b[frozenset((x,y))]*weight(l) for x,y,l in self.edge_iterator())) + p.set_objective(p.sum(b[frozenset((x, y))]*weight(l) + for x, y, l in self.edge_iterator())) try: p.solve(log=verbose) @@ -2986,7 +2991,7 @@ def degree_constrained_subgraph(self, bounds, solver=None, verbose=0, g.delete_edges(e for e in g.edge_iterator(labels=False) if not b[frozenset(e)]) return g - ### Orientations + # Orientations @doc_index("Connectivity, orientations, trees") def strong_orientation(self): @@ -3081,7 +3086,7 @@ def strong_orientation(self): if seen.get(e[1], False) is False: d.add_edge(e) next_.extend(ee for ee in self.edges_incident(e[1]) - if ((e[0],e[1]) != (ee[0],ee[1])) and ((e[0],e[1]) != (ee[1],ee[0]))) + if ((e[0], e[1]) != (ee[0], ee[1])) and ((e[0], e[1]) != (ee[1], ee[0]))) i += 1 seen[e[1]] = i @@ -3154,8 +3159,8 @@ def minimum_outdegree_orientation(self, use_edge_labels=False, solver=None, verb """ self._scream_if_not_simple() if self.is_directed(): - raise ValueError("Cannot compute an orientation of a DiGraph. "+\ - "Please convert it to a Graph if you really mean it.") + raise ValueError("Cannot compute an orientation of a DiGraph. " + "Please convert it to a Graph if you really mean it.") if use_edge_labels: from sage.rings.real_mpfr import RR @@ -3188,8 +3193,8 @@ def outgoing(u, e, variable): for u in self: p.add_constraint(p.sum(weight(e) * outgoing(u, e, orientation[frozenset(e)]) - for e in self.edge_iterator(vertices=[u], labels=False)) - - degree['max'], max=0) + for e in self.edge_iterator(vertices=[u], labels=False)) + - degree['max'], max=0) p.set_objective(degree['max']) @@ -3329,7 +3334,7 @@ def bounded_outdegree_orientation(self, bound, solver=None, verbose=False, return DiGraph() vertices = list(self) - vertices_id = {y: x for x,y in enumerate(vertices)} + vertices_id = {y: x for x, y in enumerate(vertices)} b = {} @@ -3338,7 +3343,7 @@ def bounded_outdegree_orientation(self, bound, solver=None, verbose=False, b = bound else: try: - b = dict(zip(vertices,map(bound, vertices))) + b = dict(zip(vertices, map(bound, vertices))) except TypeError: b = dict(zip(vertices, [bound]*n)) @@ -3349,17 +3354,17 @@ def bounded_outdegree_orientation(self, bound, solver=None, verbose=False, d.add_edges(('s', vertices_id[v], b[v]) for v in vertices) d.add_edges(((vertices_id[u], vertices_id[v]), 't', 1) - for u,v in self.edges(sort=False, labels=None) ) + for u, v in self.edges(sort=False, labels=None)) # each v is linked to its incident edges - for u,v in self.edge_iterator(labels=None): - u,v = vertices_id[u], vertices_id[v] - d.add_edge(u, (u,v), 1) - d.add_edge(v, (u,v), 1) + for u, v in self.edge_iterator(labels=None): + u, v = vertices_id[u], vertices_id[v] + d.add_edge(u, (u, v), 1) + d.add_edge(v, (u, v), 1) # Solving the maximum flow - value, flow = d.flow('s','t', value_only=False, integer=True, + value, flow = d.flow('s', 't', value_only=False, integer=True, use_edge_labels=True, solver=solver, verbose=verbose, integrality_tolerance=integrality_tolerance) @@ -3374,7 +3379,7 @@ def bounded_outdegree_orientation(self, bound, solver=None, verbose=False, for u in [x for x in range(n) if x in flow]: - for uu,vv in flow.neighbors_out(u): + for uu, vv in flow.neighbors_out(u): v = vv if vv != u else uu D.add_edge(vertices[u], vertices[v]) @@ -3494,8 +3499,8 @@ def orientations(self, data_structure=None, sparse=None): yield D return - E = [[(u,v,label), (v,u,label)] if u != v else [(u,v,label)] - for u,v,label in self.edge_iterator()] + E = [[(u, v, label), (v, u, label)] if u != v else [(u, v, label)] + for u, v, label in self.edge_iterator()] verts = self.vertices(sort=False) for edges in itertools.product(*E): D = DiGraph(data=[verts, edges], @@ -3509,7 +3514,7 @@ def orientations(self, data_structure=None, sparse=None): D._embedding = copy(self._embedding) yield D - ### Coloring + # Coloring @doc_index("Basic methods") def bipartite_color(self): @@ -3561,7 +3566,7 @@ def bipartite_sets(self): left = set() right = set() - for u,s in color.items(): + for u, s in color.items(): if s: left.add(u) else: @@ -4170,11 +4175,11 @@ def weight(x): W = {} L = {} - for u,v,l in self.edge_iterator(): + for u, v, l in self.edge_iterator(): if u is v: continue fuv = frozenset((u, v)) - if fuv not in L or ( use_edge_labels and W[fuv] < weight(l) ): + if fuv not in L or (use_edge_labels and W[fuv] < weight(l)): L[fuv] = l if use_edge_labels: W[fuv] = weight(l) @@ -4183,7 +4188,7 @@ def weight(x): import networkx g = networkx.Graph() if use_edge_labels: - for (u, v),w in W.items(): + for (u, v), w in W.items(): g.add_edge(u, v, weight=w) else: for u, v in L: @@ -4205,14 +4210,14 @@ def weight(x): p = MixedIntegerLinearProgram(maximization=True, solver=solver) b = p.new_variable(binary=True) if use_edge_labels: - p.set_objective(p.sum(w * b[fe] for fe,w in W.items())) + p.set_objective(p.sum(w * b[fe] for fe, w in W.items())) else: p.set_objective(p.sum(b[fe] for fe in L)) # for any vertex v, there is at most one edge incident to v in # the maximum matching for v in g: p.add_constraint(p.sum(b[frozenset(e)] for e in self.edge_iterator(vertices=[v], labels=False) - if e[0] != e[1]), max=1) + if e[0] != e[1]), max=1) p.solve(log=verbose) b = p.get_values(b, convert=bool, tolerance=integrality_tolerance) @@ -4304,7 +4309,7 @@ def is_factor_critical(self, matching=None, algorithm='Edmonds', solver=None, ve Bipartite graphs are not factor-critical:: - sage: G = graphs.RandomBipartite(randint(1, 10), randint(1, 10), .5) + sage: G = graphs.RandomBipartite(randint(1, 10), randint(1, 10), .5) sage: G.is_factor_critical() False @@ -4351,7 +4356,7 @@ def is_factor_critical(self, matching=None, algorithm='Edmonds', solver=None, ve # The graph must have an odd number of vertices, be 2-edge connected, so # without bridges, and not bipartite if (not self.order() % 2 or not self.is_connected() or - list(self.bridges()) or self.is_bipartite()): + list(self.bridges()) or self.is_bipartite()): return False if matching: @@ -4416,7 +4421,7 @@ def is_factor_critical(self, matching=None, algorithm='Edmonds', solver=None, ve even.add(a) odd.discard(a) Q.put(a) - else: # y has not been visited yet + else: # y has not been visited yet z = next(M.neighbor_iterator(y)) odd.add(y) even.add(z) @@ -4509,18 +4514,18 @@ def has_homomorphism_to(self, H, core=False, solver=None, verbose=0, # Each vertex has an image for ug in self: - p.add_constraint(p.sum(b[ug,uh] for uh in H) == 1) + p.add_constraint(p.sum(b[ug, uh] for uh in H) == 1) nonedges = H.complement().edges(sort=False, labels=False) - for ug,vg in self.edges(sort=False, labels=False): + for ug, vg in self.edges(sort=False, labels=False): # Two adjacent vertices cannot be mapped to the same element for uh in H: - p.add_constraint(b[ug,uh] + b[vg,uh] <= 1) + p.add_constraint(b[ug, uh] + b[vg, uh] <= 1) # Two adjacent vertices cannot be mapped to no adjacent vertices - for uh,vh in nonedges: - p.add_constraint(b[ug,uh] + b[vg,vh] <= 1) - p.add_constraint(b[ug,vh] + b[vg,uh] <= 1) + for uh, vh in nonedges: + p.add_constraint(b[ug, uh] + b[vg, vh] <= 1) + p.add_constraint(b[ug, vh] + b[vg, uh] <= 1) # Minimize the mapping's size if core: @@ -4529,7 +4534,7 @@ def has_homomorphism_to(self, H, core=False, solver=None, verbose=0, m = p.new_variable(nonnegative=True) for uh in H: for ug in self: - p.add_constraint(b[ug,uh] <= m[uh]) + p.add_constraint(b[ug, uh] <= m[uh]) p.set_objective(p.sum(m[vh] for vh in H)) @@ -4542,7 +4547,6 @@ def has_homomorphism_to(self, H, core=False, solver=None, verbose=0, mapping = dict(x[0] for x in b.items() if x[1]) return mapping - @doc_index("Clique-related methods") def fractional_clique_number(self, solver='PPL', verbose=0, check_components=True, check_bipartite=True): @@ -4672,7 +4676,7 @@ def maximum_average_degree(self, value_only=True, solver=None, verbose=0): d = p.new_variable(nonnegative=True) one = p.new_variable(nonnegative=True) - for u,v in g.edge_iterator(labels=False): + for u, v in g.edge_iterator(labels=False): fuv = frozenset((u, v)) p.add_constraint(one[fuv] - 2 * d[u], max=0) p.add_constraint(one[fuv] - 2 * d[v], max=0) @@ -4692,9 +4696,9 @@ def maximum_average_degree(self, value_only=True, solver=None, verbose=0): # setting the minimum to 1/(10 * size of the whole graph ) # should be safe :-) - m = 1/(10 *Integer(g.order())) + m = 1/(10 * Integer(g.order())) d_val = p.get_values(d) - g_mad = g.subgraph(v for v,l in d_val.items() if l > m) + g_mad = g.subgraph(v for v, l in d_val.items() if l > m) if value_only: return g_mad.average_degree() @@ -4787,21 +4791,21 @@ def independent_set_of_representatives(self, family, solver=None, verbose=0, # Associates to the vertices the classes to which they belong lists = {v: [] for v in self} - for i,f in enumerate(family): + for i, f in enumerate(family): for v in f: lists[v].append(i) # a classss has exactly one representative - p.add_constraint(p.sum(classss[v,i] for v in f), max=1, min=1) + p.add_constraint(p.sum(classss[v, i] for v in f), max=1, min=1) # A vertex represents at most one classss (vertex_taken is binary), and # vertex_taken[v]==1 if v is the representative of some classss for v in self: - p.add_constraint(p.sum(classss[v,i] for i in lists[v]) - vertex_taken[v], max=0) + p.add_constraint(p.sum(classss[v, i] for i in lists[v]) - vertex_taken[v], max=0) # Two adjacent vertices can not both be representatives of a set - for u,v in self.edge_iterator(labels=None): + for u, v in self.edge_iterator(labels=None): p.add_constraint(vertex_taken[u] + vertex_taken[v], max=1) p.set_objective(None) @@ -4814,9 +4818,9 @@ def independent_set_of_representatives(self, family, solver=None, verbose=0, classss = p.get_values(classss, convert=bool, tolerance=integrality_tolerance) repr = [] - for i,f in enumerate(family): + for i, f in enumerate(family): for v in f: - if classss[v,i]: + if classss[v, i]: repr.append(v) break @@ -4924,7 +4928,7 @@ def minor(self, H, solver=None, verbose=0, *, integrality_tolerance=1e-3): rs = p.new_variable(binary=True) for v in self: - p.add_constraint(p.sum(rs[h,v] for h in H), max=1) + p.add_constraint(p.sum(rs[h, v] for h in H), max=1) # We ensure that the set of representatives of a # vertex h contains a tree, and thus is connected @@ -4934,29 +4938,29 @@ def minor(self, H, solver=None, verbose=0, *, integrality_tolerance=1e-3): # there can be a edge for h between two vertices # only if those vertices represent h - for u,v in self.edge_iterator(labels=None): + for u, v in self.edge_iterator(labels=None): fuv = frozenset((u, v)) for h in H: - p.add_constraint(edges[h,fuv] - rs[h,u], max=0) - p.add_constraint(edges[h,fuv] - rs[h,v], max=0) + p.add_constraint(edges[h, fuv] - rs[h, u], max=0) + p.add_constraint(edges[h, fuv] - rs[h, v], max=0) # The number of edges of the tree in h is exactly the cardinal # of its representative set minus 1 for h in H: - p.add_constraint( p.sum(edges[h,frozenset(e)] for e in self.edge_iterator(labels=None)) - - p.sum(rs[h,v] for v in self), min=-1, max=-1) + p.add_constraint(p.sum(edges[h, frozenset(e)] for e in self.edge_iterator(labels=None)) + - p.sum(rs[h, v] for v in self), min=-1, max=-1) # a tree has no cycle epsilon = 1/(5*Integer(self.order())) r_edges = p.new_variable(nonnegative=True) for h in H: - for u,v in self.edge_iterator(labels=None): - p.add_constraint(r_edges[h,(u,v)] + r_edges[h,(v,u)] - edges[h,frozenset((u,v))], min=0) + for u, v in self.edge_iterator(labels=None): + p.add_constraint(r_edges[h, (u, v)] + r_edges[h, (v, u)] - edges[h, frozenset((u, v))], min=0) for v in self: - p.add_constraint(p.sum(r_edges[h,(u,v)] for u in self.neighbor_iterator(v)), max=1-epsilon) + p.add_constraint(p.sum(r_edges[h, (u, v)] for u in self.neighbor_iterator(v)), max=1-epsilon) # Once the representative sets are described, we must ensure # there are arcs corresponding to those of H between them @@ -4966,14 +4970,14 @@ def minor(self, H, solver=None, verbose=0, *, integrality_tolerance=1e-3): for v1, v2 in self.edge_iterator(labels=None): fv1v2 = frozenset((v1, v2)) - p.add_constraint(h_edges[(h1,h2),fv1v2] - rs[h2,v2], max=0) - p.add_constraint(h_edges[(h1,h2),fv1v2] - rs[h1,v1], max=0) + p.add_constraint(h_edges[(h1, h2), fv1v2] - rs[h2, v2], max=0) + p.add_constraint(h_edges[(h1, h2), fv1v2] - rs[h1, v1], max=0) - p.add_constraint(h_edges[(h2,h1),fv1v2] - rs[h1,v2], max=0) - p.add_constraint(h_edges[(h2,h1),fv1v2] - rs[h2,v1], max=0) + p.add_constraint(h_edges[(h2, h1), fv1v2] - rs[h1, v2], max=0) + p.add_constraint(h_edges[(h2, h1), fv1v2] - rs[h2, v1], max=0) - p.add_constraint(p.sum(h_edges[(h1,h2),frozenset(e)] + h_edges[(h2,h1),frozenset(e)] - for e in self.edge_iterator(labels=None)), min=1) + p.add_constraint(p.sum(h_edges[(h1, h2), frozenset(e)] + h_edges[(h2, h1), frozenset(e)] + for e in self.edge_iterator(labels=None)), min=1) p.set_objective(None) @@ -4986,11 +4990,11 @@ def minor(self, H, solver=None, verbose=0, *, integrality_tolerance=1e-3): rs_dict = {} for h in H: - rs_dict[h] = [v for v in self if rs[h,v]] + rs_dict[h] = [v for v in self if rs[h, v]] return rs_dict - ### Convexity + # Convexity @doc_index("Algorithmically hard stuff") def convexity_properties(self): @@ -5080,7 +5084,7 @@ def centrality_degree(self, v=None): else: return self.degree(v)/n_minus_one - ### Distances + # Distances @doc_index("Distances") def eccentricity(self, v=None, by_weight=False, algorithm=None, @@ -5278,7 +5282,7 @@ def eccentricity(self, v=None, by_weight=False, algorithm=None, if with_labels: return dict(zip(v, eccentricity(self, algorithm=algo, vertex_list=v))) else: - return eccentricity(self, algorithm=algo,vertex_list=v) + return eccentricity(self, algorithm=algo, vertex_list=v) if algorithm == 'DHV': if by_weight: @@ -5305,7 +5309,7 @@ def eccentricity(self, v=None, by_weight=False, algorithm=None, check_weight)[0] algorithm = 'From_Dictionary' - elif algorithm in ['Floyd-Warshall-Python', 'Floyd-Warshall-Cython', 'Johnson_Boost','DHV']: + elif algorithm in ['Floyd-Warshall-Python', 'Floyd-Warshall-Cython', 'Johnson_Boost', 'DHV']: raise ValueError("algorithm '" + algorithm + "' works only if all" + " eccentricities are needed") @@ -5866,7 +5870,7 @@ def distance_graph(self, dist): D.add_edges((u, u) for u in self) return D - ### Constructors + # Constructors @doc_index("Basic methods") def to_directed(self, data_structure=None, sparse=None): @@ -5938,18 +5942,18 @@ def to_directed(self, data_structure=None, sparse=None): else: data_structure = "static_sparse" from sage.graphs.digraph import DiGraph - D = DiGraph(name = self.name(), - pos = self.get_pos(), - multiedges = self.allows_multiple_edges(), - loops = self.allows_loops(), - data_structure = (data_structure if data_structure!="static_sparse" - else "sparse")) # we need a mutable copy + D = DiGraph(name=self.name(), + pos=self.get_pos(), + multiedges=self.allows_multiple_edges(), + loops=self.allows_loops(), + data_structure=(data_structure if data_structure != "static_sparse" + else "sparse")) # we need a mutable copy D.add_vertices(self.vertex_iterator()) D.set_vertices(self.get_vertices()) - for u,v,l in self.edge_iterator(): - D.add_edge(u,v,l) - D.add_edge(v,u,l) + for u, v, l in self.edge_iterator(): + D.add_edge(u, v, l) + D.add_edge(v, u, l) if hasattr(self, '_embedding'): D._embedding = copy(self._embedding) D._weighted = self._weighted @@ -6031,7 +6035,7 @@ def join(self, other, labels="pairs", immutable=None): else: G.add_edges(((0, u), (1, v)) for u in self for v in other) - G.name('%s join %s'%(self.name(), other.name())) + G.name('%s join %s' % (self.name(), other.name())) if immutable is None: immutable = self.is_immutable() and other.is_immutable() @@ -6190,11 +6194,11 @@ def twograph(self): T.append([x, y, z]) T = TwoGraph(T) - T.relabel({i: v for i,v in enumerate(self.vertices(sort=False))}) + T.relabel({i: v for i, v in enumerate(self)}) return T - ### Visualization + # Visualization @doc_index("Basic methods") def write_to_eps(self, filename, **options): @@ -6226,7 +6230,7 @@ def write_to_eps(self, filename, **options): if filename[-4:] != '.eps': filename += '.eps' f = open(filename, 'w') - f.write( print_graph_eps(self.vertices(sort=False), self.edge_iterator(), pos) ) + f.write(print_graph_eps(self, self.edge_iterator(), pos)) f.close() @doc_index("Algorithmically hard stuff") @@ -6333,11 +6337,11 @@ def topological_minor(self, H, vertices=False, paths=False, solver=None, verbose # Exactly one representative per vertex of H for h in H: - p.add_constraint(p.sum(v_repr[h,g] for g in G), min=1, max=1) + p.add_constraint(p.sum(v_repr[h, g] for g in G), min=1, max=1) # A vertex of G can only represent one vertex of H for g in G: - p.add_constraint(p.sum(v_repr[h,g] for h in H), max=1) + p.add_constraint(p.sum(v_repr[h, g] for h in H), max=1) ################### # Is representent # @@ -6349,7 +6353,7 @@ def topological_minor(self, H, vertices=False, paths=False, solver=None, verbose for g in G: for h in H: - p.add_constraint(v_repr[h,g] - is_repr[g], max=0) + p.add_constraint(v_repr[h, g] - is_repr[g], max=0) ################################### # paths between the representents # @@ -6368,21 +6372,21 @@ def topological_minor(self, H, vertices=False, paths=False, solver=None, verbose # These functions return the balance of flow corresponding to # commodity C at vertex v def flow_in(C, v): - return p.sum(flow[C,(v,u)] for u in G.neighbor_iterator(v)) + return p.sum(flow[C, (v, u)] for u in G.neighbor_iterator(v)) def flow_out(C, v): - return p.sum(flow[C,(u,v)] for u in G.neighbor_iterator(v)) + return p.sum(flow[C, (u, v)] for u in G.neighbor_iterator(v)) def flow_balance(C, v): - return flow_in(C,v) - flow_out(C,v) + return flow_in(C, v) - flow_out(C, v) - for h1,h2 in H.edge_iterator(labels=False): + for h1, h2 in H.edge_iterator(labels=False): for v in G: # The flow balance depends on whether the vertex v is a # representative of h1 or h2 in G, or a representative of none - p.add_constraint(flow_balance((h1,h2),v) == v_repr[h1,v] - v_repr[h2,v]) + p.add_constraint(flow_balance((h1, h2), v) == v_repr[h1, v] - v_repr[h2, v]) ############################# # Internal vertex of a path # @@ -6396,7 +6400,7 @@ def flow_balance(C, v): # When is a vertex internal for a commodity ? for C in H.edge_iterator(labels=False): for g in G: - p.add_constraint(flow_in(C,g) + flow_out(C,g) - is_internal[C,g], max=1) + p.add_constraint(flow_in(C, g) + flow_out(C, g) - is_internal[C, g], max=1) ############################ # Two paths do not cross ! # @@ -6406,8 +6410,8 @@ def flow_balance(C, v): # the vertex is a representent for g in G: - p.add_constraint(p.sum(is_internal[C,g] for C in H.edge_iterator(labels=False)) - + is_repr[g], max=1) + p.add_constraint(p.sum(is_internal[C, g] for C in H.edge_iterator(labels=False)) + + is_repr[g], max=1) # (The following inequalities are not necessary, but they seem to be of # help (the solvers find the answer quicker when they are added) @@ -6415,33 +6419,29 @@ def flow_balance(C, v): # The flow on one edge can go in only one direction. Besides, it can # belong to at most one commodity and has a maximum intensity of 1. - for g1,g2 in G.edge_iterator(labels=None): - - p.add_constraint( p.sum(flow[C,(g1,g2)] for C in H.edge_iterator(labels=False)) - + p.sum(flow[C,(g2,g1)] for C in H.edge_iterator(labels=False)), - max=1) - + for g1, g2 in G.edge_iterator(labels=None): + p.add_constraint(p.sum(flow[C, (g1, g2)] for C in H.edge_iterator(labels=False)) + + p.sum(flow[C, (g2, g1)] for C in H.edge_iterator(labels=False)), + max=1) # Now we can solve the problem itself ! try: p.solve(log=verbose) - except MIPSolverException: return False - minor = G.subgraph(immutable=False) is_repr = p.get_values(is_repr, convert=bool, tolerance=integrality_tolerance) v_repr = p.get_values(v_repr, convert=bool, tolerance=integrality_tolerance) flow = p.get_values(flow, convert=bool, tolerance=integrality_tolerance) - for u,v in minor.edge_iterator(labels=False): + for u, v in minor.edge_iterator(labels=False): used = False for C in H.edge_iterator(labels=False): - if flow[C,(u,v)] or flow[C,(v,u)]: + if flow[C, (u, v)] or flow[C, (v, u)]: used = True minor.set_edge_label(u, v, C) break @@ -6453,13 +6453,13 @@ def flow_balance(C, v): for g in minor: if is_repr[g]: for h in H: - if v_repr[h,v]: + if v_repr[h, v]: minor.set_vertex(g, h) break return minor - ### Cliques + # Cliques @doc_index("Clique-related methods") def cliques_maximal(self, algorithm="native"): @@ -6911,9 +6911,9 @@ def independent_set(self, algorithm="Cliquer", value_only=False, reduction_rules sphinx_plot(g.plot(partition=[g.independent_set()])) """ my_cover = self.vertex_cover(algorithm=algorithm, value_only=value_only, - reduction_rules=reduction_rules, - solver=solver, verbose=verbose, - integrality_tolerance=integrality_tolerance) + reduction_rules=reduction_rules, + solver=solver, verbose=verbose, + integrality_tolerance=integrality_tolerance) if value_only: return self.order() - my_cover else: @@ -7081,7 +7081,7 @@ def vertex_cover(self, algorithm="Cliquer", value_only=False, # We first take a copy of the graph without multiple edges, if any. g = Graph(data=self.edges(sort=False), format='list_of_edges', - multiedges=self.allows_multiple_edges()) + multiedges=self.allows_multiple_edges()) g.allow_multiple_edges(False) degree_at_most_two = {u for u in g if g.degree(u) <= 2} @@ -7115,7 +7115,7 @@ def vertex_cover(self, algorithm="Cliquer", value_only=False, degree_at_most_two.discard(v) elif du == 2: - v,w = g.neighbors(u) + v, w = g.neighbors(u) if g.has_edge(v, w): # RULE 3: If the neighbors v and w of a degree 2 vertex @@ -7142,7 +7142,7 @@ def vertex_cover(self, algorithm="Cliquer", value_only=False, g.delete_vertex(v) g.delete_vertex(w) for z in neigh: - g.add_edge(u,z) + g.add_edge(u, z) folded_vertices.append((u, v, w)) @@ -7152,11 +7152,9 @@ def vertex_cover(self, algorithm="Cliquer", value_only=False, degree_at_most_two.discard(v) degree_at_most_two.discard(w) - # RULE 5: # TODO: add extra reduction rules - ################## # Main Algorithm # ################## @@ -7187,7 +7185,7 @@ def vertex_cover(self, algorithm="Cliquer", value_only=False, p.set_objective(p.sum(b[v] for v in g)) # an edge contains at least one vertex of the minimum vertex cover - for u,v in g.edge_iterator(labels=None): + for u, v in g.edge_iterator(labels=None): p.add_constraint(b[u] + b[v], min=1) p.solve(log=verbose) @@ -7211,7 +7209,7 @@ def vertex_cover(self, algorithm="Cliquer", value_only=False, cover_g.update(ppset) # RULE 4: folded_vertices.reverse() - for u,v,w in folded_vertices: + for u, v, w in folded_vertices: if u in cover_g: cover_g.discard(u) cover_g.add(v) @@ -7366,9 +7364,9 @@ def traverse(start, pointer): # Perform ear decomposition on each connected component of input graph. for v in self: if v not in seen: - # Start the depth first search from first vertex + # Start the depth first search from first vertex DFS(v) - value = {u:i for i,u in enumerate(dfs_order)} + value = {u: i for i, u in enumerate(dfs_order)} # Traverse all the non Tree edges, according to DFS order for u in dfs_order: @@ -7557,9 +7555,9 @@ def clique_polynomial(self, t=None): number_of = [0]*(self.order() + 1) for x in IndependentSets(self, complement=True): number_of[len(x)] += 1 - return sum(coeff*t**i for i,coeff in enumerate(number_of) if coeff) + return sum(coeff*t**i for i, coeff in enumerate(number_of) if coeff) - ### Miscellaneous + # Miscellaneous @doc_index("Leftovers") def cores(self, k=None, with_labels=False): @@ -7724,11 +7722,11 @@ def cores(self, k=None, with_labels=False): return verts, [] bin_boundaries = [0] curr_degree = 0 - for i,v in enumerate(verts): + for i, v in enumerate(verts): if degrees[v] > curr_degree: bin_boundaries.extend([i] * (degrees[v] - curr_degree)) curr_degree = degrees[v] - vert_pos = {v: pos for pos,v in enumerate(verts)} + vert_pos = {v: pos for pos, v in enumerate(verts)} # Lists of neighbors. nbrs = {v: set(self.neighbors(v)) for v in self} # form vertex core building up from smallest @@ -7752,7 +7750,7 @@ def cores(self, k=None, with_labels=False): bin_start = bin_boundaries[core[u]] vert_pos[u] = bin_start vert_pos[verts[bin_start]] = pos - verts[bin_start],verts[pos] = verts[pos],verts[bin_start] + verts[bin_start], verts[pos] = verts[pos], verts[bin_start] bin_boundaries[core[u]] += 1 core[u] -= 1 @@ -8287,15 +8285,16 @@ def _gomory_hu_tree(self, vertices, algorithm=None): # Take any two vertices (u,v) it = iter(vertices) - u,v = next(it),next(it) + u, v = next(it), next(it) # Compute a uv min-edge-cut. # # The graph is split into U,V with u \in U and v\in V. - flow,edges,[U,V] = self.edge_cut(u, v, use_edge_labels=True, vertices=True, algorithm=algorithm) + flow, edges, [U, V] = self.edge_cut(u, v, use_edge_labels=True, + vertices=True, algorithm=algorithm) # One graph for each part of the previous one - gU,gV = self.subgraph(U, immutable=False), self.subgraph(V, immutable=False) + gU, gV = self.subgraph(U, immutable=False), self.subgraph(V, immutable=False) # A fake vertex fU (resp. fV) to represent U (resp. V) fU = frozenset(U) @@ -8308,12 +8307,12 @@ def _gomory_hu_tree(self, vertices, algorithm=None): # If the same edge is added several times their capacities add up. from sage.rings.real_mpfr import RR - for uu,vv,capacity in edges: + for uu, vv, capacity in edges: capacity = capacity if capacity in RR else 1 # Assume uu is in gU if uu in V: - uu,vv = vv,uu + uu, vv = vv, uu # Create the new edges if necessary if not gU.has_edge(uu, fV): @@ -8513,7 +8512,7 @@ def two_factor_petersen(self, solver=None, verbose=0, *, integrality_tolerance=1 # and have to be translated back to (u,v) classes_b = [] for c in classes: - classes_b.append([(u,v) for ((uu,u),(vv,v)) in c]) + classes_b.append([(u, v) for ((uu, u), (vv, v)) in c]) return classes_b @@ -9078,7 +9077,7 @@ def effective_resistance(self, i, j, *, base_ring=None): if base_ring is None: base_ring = ZZ - if i == j : + if i == j: return base_ring(0) self._scream_if_not_simple() @@ -9086,7 +9085,7 @@ def effective_resistance(self, i, j, *, base_ring=None): connected_i = self.connected_component_containing_vertex(i) if j in connected_i: component = self.subgraph(connected_i) - return component.effective_resistance(i,j) + return component.effective_resistance(i, j) else: from sage.rings.infinity import Infinity return Infinity @@ -9935,7 +9934,7 @@ def bipartite_double(self, extended=False): G.add_edges(((v, 0), (v, 1)) for v in self) prefix = "Extended " if extended else "" - G.name("%sBipartite Double of %s"%(prefix, self.name())) + G.name("%sBipartite Double of %s" % (prefix, self.name())) return G # Aliases to functions defined in other modules @@ -9976,6 +9975,7 @@ def bipartite_double(self, extended=False): from sage.graphs.graph_coloring import fractional_chromatic_index from sage.graphs.hyperbolicity import hyperbolicity + _additional_categories = { "is_long_hole_free" : "Graph properties", "is_long_antihole_free" : "Graph properties", @@ -10024,4 +10024,4 @@ def bipartite_double(self, extended=False): "hyperbolicity" : "Distances", } -__doc__ = __doc__.replace("{INDEX_OF_METHODS}",gen_thematic_rest_table_index(Graph,_additional_categories)) +__doc__ = __doc__.replace("{INDEX_OF_METHODS}", gen_thematic_rest_table_index(Graph, _additional_categories)) diff --git a/src/sage/graphs/graph_database.py b/src/sage/graphs/graph_database.py index 9db59191da7..f416d7c04ee 100644 --- a/src/sage/graphs/graph_database.py +++ b/src/sage/graphs/graph_database.py @@ -36,13 +36,13 @@ Available: http://artsci.drake.edu/grout/graphs/ """ -################################################################################ +# ############################################################################## # Copyright (C) 2007 Emily A. Kirkman # # # Distributed under the terms of the GNU General Public License (GPL) -# http://www.gnu.org/licenses/ -################################################################################ +# https://www.gnu.org/licenses/ +# ############################################################################## from . import graph import os @@ -51,7 +51,7 @@ from sage.databases.sql_db import SQLDatabase, SQLQuery from sage.env import GRAPHS_DATA_DIR from sage.graphs.graph import Graph -dblocation = os.path.join(GRAPHS_DATA_DIR,'graphs.db') +dblocation = os.path.join(GRAPHS_DATA_DIR, 'graphs.db') def degseq_to_data(degree_sequence): @@ -105,6 +105,7 @@ def data_to_degseq(data, graph6=None): else: return degseq + def graph6_to_plot(graph6): """ Return a ``Graphics`` object from a ``graph6`` string. @@ -126,6 +127,7 @@ def graph6_to_plot(graph6): g = Graph(str(graph6)) return g.plot(layout='circular', vertex_size=30, vertex_labels=False, graph_border=False) + def subgraphs_to_query(subgraphs, db): """ Return a GraphQuery object required for the induced_subgraphs parameter. @@ -175,6 +177,7 @@ def subgraphs_to_query(subgraphs, db): raise KeyError('unable to initiate query: illegal input format for induced_subgraphs') return q + # tables columns input data type sqlite data type # ----------------------------------------------------------------------------- aut_grp = ['aut_grp_size', # Integer INTEGER @@ -218,6 +221,7 @@ def subgraphs_to_query(subgraphs, db): valid_kwds = aut_grp + degrees + misc + spectrum + graph_data + def graph_db_info(tablename=None): """ Return a dictionary of allowed table and column names. @@ -254,6 +258,7 @@ def graph_db_info(tablename=None): info = info[tablename] return info + class GenericGraphQuery(SQLQuery): def __init__(self, query_string, database=None, param_tuple=None): @@ -311,7 +316,7 @@ def __init__(self, query_string, database=None, param_tuple=None): if database is None: database = GraphDatabase() if not isinstance(database, GraphDatabase): - raise TypeError('%s is not a valid GraphDatabase'%database) + raise TypeError('%s is not a valid GraphDatabase' % database) SQLQuery.__init__(self, database, query_string, param_tuple) @@ -431,12 +436,11 @@ class located in :mod:`sage.databases.sql_db` to make the query for key in kwds: # check validity if key not in valid_kwds: - raise KeyError('%s is not a valid key for this database.'%str(key)) + raise KeyError('%s is not a valid key for this database.' % str(key)) - # designate a query_dict - qdict = {'display_cols': None} # reserve display cols until end - # (database.py currently concatenates - # them including repeats) + # designate a query_dict and reserve display_cols until end + # (database.py currently concatenates them including repeats) + qdict = {'display_cols': None} # set table name if key in graph_data: @@ -453,7 +457,8 @@ class located in :mod:`sage.databases.sql_db` to make the query # set expression if not isinstance(kwds[key], list): if key == 'induced_subgraphs': - qdict['expression'] = [key, 'regexp', '.*%s.*'%(graph.Graph(kwds[key]).canonical_label()).graph6_string()] + s6 = (graph.Graph(kwds[key]).canonical_label()).graph6_string() + qdict['expression'] = [key, 'regexp', '.*%s.*' % s6] else: qdict['expression'] = [key, '=', kwds[key]] elif key == 'degree_sequence': @@ -465,9 +470,10 @@ class located in :mod:`sage.databases.sql_db` to make the query join_dict = {qdict['table_name']: ('graph_id', 'graph_id')} if key == 'induced_subgraphs' and isinstance(kwds[key], list): self.intersect(subgraphs_to_query(kwds[key], graph_db), - 'graph_data', join_dict, in_place=True) + 'graph_data', join_dict, in_place=True) else: - self.intersect(SQLQuery(graph_db, qdict), 'graph_data', join_dict,in_place=True) + self.intersect(SQLQuery(graph_db, qdict), 'graph_data', + join_dict, in_place=True) # include search params (keys) in join clause # again, we exclude graph_data because it is the base table @@ -482,7 +488,7 @@ class located in :mod:`sage.databases.sql_db` to make the query graph_data_disp = ['graph_data'] disp_tables = [aut_grp_disp, degrees_disp, misc_disp, spectrum_disp] - # graph_data intentionally left out because it is always called + # graph_data intentionally left out because it is always called # organize display if display_cols is not None: @@ -506,27 +512,28 @@ class located in :mod:`sage.databases.sql_db` to make the query # join clause for display tables join_str = 'FROM graph_data ' for tab in master_join: - join_str += 'INNER JOIN %s ON graph_data.graph_id=%s.graph_id '%(tab, tab) + join_str += 'INNER JOIN %s ON graph_data.graph_id=%s.graph_id ' % (tab, tab) # construct sql syntax substring for display cols disp_list = ['SELECT graph_data.graph6, '] for col in graph_data_disp[1:]: if col != 'graph6': - disp_list.append('graph_data.%s, '%col) + disp_list.append('graph_data.%s, ' % col) for col in aut_grp_disp[1:]: - disp_list.append('aut_grp.%s, '%col) + disp_list.append('aut_grp.%s, ' % col) for col in degrees_disp[1:]: - disp_list.append('degrees.%s, '%col) + disp_list.append('degrees.%s, ' % col) for col in misc_disp[1:]: - disp_list.append('misc.%s, '%col) + disp_list.append('misc.%s, ' % col) for col in spectrum_disp[1:]: - disp_list.append('spectrum.%s, '%col) + disp_list.append('spectrum.%s, ' % col) disp_list[-1] = disp_list[-1].rstrip(', ') + ' ' disp_str = ''.join(disp_list) # substitute disp_str and join_str back into self's query string - self.__query_string__ = re.sub('SELECT.*WHERE ', disp_str + join_str + \ - 'WHERE ', self.__query_string__) + self.__query_string__ = re.sub('SELECT.*WHERE ', + disp_str + join_str + 'WHERE ', + self.__query_string__) self.__query_string__ += ' ORDER BY graph_data.graph6' def query_iterator(self): @@ -717,6 +724,7 @@ def number_of(self): q = GenericGraphQuery(s, self.__database__, self.__param_tuple__) return len(q.query_results()) + class GraphDatabase(SQLDatabase): def __init__(self): @@ -942,15 +950,15 @@ def _gen_interact_func(self, display, **kwds): not be called directly. """ function_name = '__temporary_interact_function' - arg = ['%s=%s'%(word, kwds[word]) for word in kwds] - boxes = ["%s=input_grid(1,2,['=',%s])"%(word, kwds[word]) for word in kwds] - params = ['%s=%s[0]'%tuple(2 * [arg[i].split('=')[0]]) for i in range(len(arg))] + arg = ['%s=%s' % (word, kwds[word]) for word in kwds] + boxes = ["%s=input_grid(1,2,['=',%s])" % (word, kwds[word]) for word in kwds] + params = ['%s=%s[0]' % tuple(2 * [arg[i].split('=')[0]]) for i in range(len(arg))] s = 'def %s(%s):' % (function_name, ','.join(boxes)) t = """ print('

Query Results:

') GraphQuery(display_cols=%s,%s).show(with_picture=True) - """%tuple([display, ','.join(params)]) + """ % tuple([display, ','.join(params)]) s += '\t' + '\n\t'.join(t.split('\n')) + '\n' exec(s) return locals()[function_name] diff --git a/src/sage/graphs/graph_decompositions/vertex_separation.pyx b/src/sage/graphs/graph_decompositions/vertex_separation.pyx index f62c30cb51f..c0a7ca8dc47 100644 --- a/src/sage/graphs/graph_decompositions/vertex_separation.pyx +++ b/src/sage/graphs/graph_decompositions/vertex_separation.pyx @@ -475,7 +475,7 @@ def linear_ordering_to_path_decomposition(G, L): def pathwidth(self, k=None, certificate=False, algorithm="BAB", verbose=False, max_prefix_length=20, max_prefix_number=10**6): - """ + r""" Compute the pathwidth of ``self`` (and provides a decomposition) INPUT: diff --git a/src/sage/graphs/graph_generators.py b/src/sage/graphs/graph_generators.py index fc647b5499d..9a8c0877d3d 100644 --- a/src/sage/graphs/graph_generators.py +++ b/src/sage/graphs/graph_generators.py @@ -18,6 +18,7 @@ import subprocess + # This method appends a list of methods to the doc as a 3xN table. # Here's the point : @@ -31,24 +32,29 @@ def __append_to_doc(methods): global __doc__ __doc__ += ("\n.. csv-table::\n" - " :class: contentstable\n" - " :widths: 33, 33, 33\n" - " :delim: |\n\n") + " :class: contentstable\n" + " :widths: 33, 33, 33\n" + " :delim: |\n\n") h = (len(methods)+2)//3 # Reorders the list of methods for horizontal reading, the only one Sphinx understands reordered_methods = [0]*3*h for i, m in enumerate(methods): - reordered_methods[3*(i%h)+(i//h)] = m + reordered_methods[3*(i % h) + (i//h)] = m methods = reordered_methods # Adding the list to the __doc__ string - wrap_name = lambda x : ":meth:`"+str(x)+" `" if x else "" + def wrap_name(x): + if x: + return ":meth:`" + str(x) + " `" + return "" + while methods: a = methods.pop(0) b = methods.pop(0) c = methods.pop(0) - __doc__ += " "+wrap_name(a)+" | "+wrap_name(b)+" | "+wrap_name(c)+"\n" + __doc__ += " " + wrap_name(a) + " | " + wrap_name(b) + " | " + wrap_name(c) + "\n" + __doc__ += """ **Basic structures** @@ -451,15 +457,17 @@ def __append_to_doc(methods): --------------------- """ -########################################################################### - -# Copyright (C) 2006 Robert L. Miller -# and Emily A. Kirkman -# Copyright (C) 2009 Michael C. Yurko +# **************************************************************************** +# Copyright (C) 2006 Robert L. Miller +# Emily A. Kirkman +# 2009 Michael C. Yurko # -# Distributed under the terms of the GNU General Public License (GPL) -# http://www.gnu.org/licenses/ -########################################################################### +# 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/ +# **************************************************************************** # import from Python standard library @@ -721,8 +729,8 @@ class GraphGenerators(): # Graph Iterators ########################################################################### - def __call__(self, vertices=None, property=None, augment='edges', - size=None, degree_sequence=None, loops=False, sparse=True, copy = True): + def __call__(self, vertices=None, property=None, augment='edges', size=None, + degree_sequence=None, loops=False, sparse=True, copy=True): """ Accesses the generator of isomorphism class representatives. Iterates over distinct, exhaustive representatives. See the docstring @@ -769,8 +777,8 @@ def __call__(self, vertices=None, property=None, augment='edges', """ # Use nauty for the basic case, as it is much faster. if (vertices and property is None and size is None and - degree_sequence is None and not loops and augment == 'edges' and - sparse and copy): + degree_sequence is None and not loops and augment == 'edges' and + sparse and copy): for g in graphs.nauty_geng(vertices): yield g return @@ -785,7 +793,8 @@ def property(x): if degree_sequence is not None: if vertices is None: raise NotImplementedError - if len(degree_sequence) != vertices or sum(degree_sequence)%2 or sum(degree_sequence) > vertices*(vertices-1): + if (len(degree_sequence) != vertices or sum(degree_sequence) % 2 + or sum(degree_sequence) > vertices*(vertices - 1)): raise ValueError("Invalid degree sequence.") degree_sequence = sorted(degree_sequence) if augment == 'edges': @@ -828,9 +837,9 @@ def extra_property(x): vertices += 1 g = Graph(vertices, loops=loops, sparse=sparse) gens = [] - for i in range(vertices-1): + for i in range(vertices - 1): gen = list(range(i)) - gen.append(i+1) + gen.append(i + 1) gen.append(i) gen += list(range(i + 2, vertices)) gens.append(gen) @@ -840,7 +849,6 @@ def extra_property(x): else: raise NotImplementedError - def nauty_geng(self, options="", debug=False): r""" Return a generator which creates graphs from nauty's geng program. @@ -1040,7 +1048,7 @@ def nauty_genbg(self, options="", debug=False): confuse the creation of a Sage graph. Option ``-q`` which suppress auxiliary output (except from ``-v``) should never be used as we are unable to recover the partition of the vertices of the bipartite graph - without the auxilary output. Hence the partition of the vertices of + without the auxiliary output. Hence the partition of the vertices of returned bipartite graphs might not respect the requirement. The res/mod option can be useful when using the output in a routine run @@ -1193,7 +1201,6 @@ def cospectral_graphs(self, vertices, matrix_function=lambda g: g.adjacency_matr A list of lists of graphs. Each sublist will be a list of cospectral graphs (lists of cardinality 1 being omitted). - .. SEEALSO:: :meth:`Graph.is_strongly_regular` -- tests whether a graph is @@ -1256,21 +1263,21 @@ def cospectral_graphs(self, vertices, matrix_function=lambda g: g.adjacency_matr """ from sage.graphs.graph_generators import graphs as graph_gen if graphs is None: - graph_list=graph_gen(vertices, property=lambda _: True) + graph_list = graph_gen(vertices, property=lambda _: True) elif callable(graphs): - graph_list=iter(g for g in graph_gen(vertices, property=lambda _: True) if graphs(g)) + graph_list = iter(g for g in graph_gen(vertices, property=lambda _: True) if graphs(g)) else: - graph_list=iter(graphs) + graph_list = iter(graphs) from collections import defaultdict - charpolys=defaultdict(list) + charpolys = defaultdict(list) for g in graph_list: - cp=matrix_function(g).charpoly() + cp = matrix_function(g).charpoly() charpolys[cp].append(g) - cospectral_graphs=[] - for cp,g_list in charpolys.items(): - if len(g_list)>1: + cospectral_graphs = [] + for cp, g_list in charpolys.items(): + if len(g_list) > 1: cospectral_graphs.append(g_list) return cospectral_graphs @@ -1330,15 +1337,14 @@ def _read_planar_code(self, code_input): 3: [1, 2, 4], 4: [1, 3, 2]} """ - #start of code to read planar code - + # start of code to read planar code header = code_input.read(15) assert header == '>>planar_code<<', 'Not a valid planar code header' - #read graph per graph + # read graph per graph while True: c = code_input.read(1) - if len(c)==0: + if not c: return # Each graph is stored in the following way : @@ -1556,7 +1562,7 @@ def fusenes(self, hexagon_count, benzenoids=False): # there is only one unique fusene with 1 hexagon (and benzene doesn't generate it) if hexagon_count == 1: - g = {1:[6, 2], 2:[1, 3], 3:[2, 4], 4:[3, 5], 5:[4, 6], 6:[5, 1]} + g = {1: [6, 2], 2: [1, 3], 3: [2, 4], 4: [3, 5], 5: [4, 6], 6: [5, 1]} G = graph.Graph(g) G.set_embedding(g) yield(G) @@ -1922,7 +1928,7 @@ def planar_graphs(self, order, minimum_degree=None, if exact_connectivity and minimum_connectivity is None: raise ValueError("Minimum connectivity must be specified to use the exact_connectivity option.") - if minimum_connectivity is not None and not (1 <= minimum_connectivity <= 3): + if minimum_connectivity is not None and not (1 <= minimum_connectivity <= 3): raise ValueError("Minimum connectivity should be a number between 1 and 3.") # minimum degree should be None or a number between 1 and 5 @@ -1947,8 +1953,8 @@ def planar_graphs(self, order, minimum_degree=None, minimum_degree > 0): raise ValueError("Minimum connectivity can be at most the minimum degree.") - #exact connectivity is not implemented for minimum connectivity 3 - if exact_connectivity and minimum_connectivity==3: + # exact connectivity is not implemented for minimum connectivity 3 + if exact_connectivity and minimum_connectivity == 3: raise NotImplementedError("Generation of planar graphs with connectivity exactly 3 is not implemented.") if only_bipartite and minimum_degree > 3: @@ -1982,7 +1988,7 @@ def planar_graphs(self, order, minimum_degree=None, if order == 0: return - minimum_order = {0:1, 1:2, 2:3, 3:4, 4:6, 5:12}[minimum_degree] + minimum_order = {0: 1, 1: 2, 2: 3, 3: 4, 4: 6, 5: 12}[minimum_degree] if order < minimum_order: return @@ -2135,10 +2141,10 @@ def triangulations(self, order, minimum_degree=None, minimum_connectivity=None, if exact_connectivity and minimum_connectivity is None: raise ValueError("Minimum connectivity must be specified to use the exact_connectivity option.") - if minimum_connectivity is not None and not (3 <= minimum_connectivity <= 5): + if minimum_connectivity is not None and not (3 <= minimum_connectivity <= 5): raise ValueError("Minimum connectivity should be None or a number between 3 and 5.") - if minimum_degree is not None and not (3 <= minimum_degree <= 5): + if minimum_degree is not None and not (3 <= minimum_degree <= 5): raise ValueError("Minimum degree should be None or a number between 3 and 5.") # for Eulerian triangulations the minimum degree is set to 4 (unless it was already specifically set) @@ -2157,11 +2163,12 @@ def triangulations(self, order, minimum_degree=None, minimum_connectivity=None, elif minimum_degree < minimum_connectivity: raise ValueError("Minimum connectivity can be at most the minimum degree.") - #exact connectivity is not implemented for minimum connectivity equal to minimum degree - if exact_connectivity and minimum_connectivity==minimum_degree: + # exact connectivity is not implemented for minimum connectivity equal + # to minimum degree + if exact_connectivity and minimum_connectivity == minimum_degree: raise NotImplementedError("Generation of triangulations with minimum connectivity equal to minimum degree is not implemented.") - minimum_order = {3:4, 4:6, 5:12}[minimum_degree] + minimum_order = {3: 4, 4: 6, 5: 12}[minimum_degree] if order < minimum_order: return @@ -2280,8 +2287,9 @@ def quadrangulations(self, order, minimum_degree=None, minimum_connectivity=None raise ValueError("Minimum degree should be None, 2 or 3.") if (no_nonfacial_quadrangles and - minimum_connectivity == 2): - raise NotImplementedError("Generation of no non-facial quadrangles and minimum connectivity 2 is not implemented") + minimum_connectivity == 2): + raise NotImplementedError("Generation of no non-facial quadrangles " + "and minimum connectivity 2 is not implemented") # check combination of values of minimum degree and minimum connectivity if minimum_connectivity is None: @@ -2295,7 +2303,7 @@ def quadrangulations(self, order, minimum_degree=None, minimum_connectivity=None elif minimum_degree < minimum_connectivity: raise ValueError("Minimum connectivity can be at most the minimum degree.") - minimum_order = {2:4, 3:8}[minimum_degree] + minimum_order = {2: 4, 3: 8}[minimum_degree] if order < minimum_order: return @@ -2316,221 +2324,221 @@ def quadrangulations(self, order, minimum_degree=None, minimum_connectivity=None # Basic Graphs ########################################################################### from .generators import basic - BullGraph = staticmethod(basic.BullGraph) - ButterflyGraph = staticmethod(basic.ButterflyGraph) - CircularLadderGraph = staticmethod(basic.CircularLadderGraph) - ClawGraph = staticmethod(basic.ClawGraph) - CycleGraph = staticmethod(basic.CycleGraph) - CompleteGraph = staticmethod(basic.CompleteGraph) - CompleteBipartiteGraph = staticmethod(basic.CompleteBipartiteGraph) - CompleteMultipartiteGraph= staticmethod(basic.CompleteMultipartiteGraph) - DiamondGraph = staticmethod(basic.DiamondGraph) - GemGraph = staticmethod(basic.GemGraph) - DartGraph = staticmethod(basic.DartGraph) - ForkGraph = staticmethod(basic.ForkGraph) - EmptyGraph = staticmethod(basic.EmptyGraph) - Grid2dGraph = staticmethod(basic.Grid2dGraph) - GridGraph = staticmethod(basic.GridGraph) - HouseGraph = staticmethod(basic.HouseGraph) - HouseXGraph = staticmethod(basic.HouseXGraph) - LadderGraph = staticmethod(basic.LadderGraph) - PathGraph = staticmethod(basic.PathGraph) - StarGraph = staticmethod(basic.StarGraph) + BullGraph = staticmethod(basic.BullGraph) + ButterflyGraph = staticmethod(basic.ButterflyGraph) + CircularLadderGraph = staticmethod(basic.CircularLadderGraph) + ClawGraph = staticmethod(basic.ClawGraph) + CycleGraph = staticmethod(basic.CycleGraph) + CompleteGraph = staticmethod(basic.CompleteGraph) + CompleteBipartiteGraph = staticmethod(basic.CompleteBipartiteGraph) + CompleteMultipartiteGraph = staticmethod(basic.CompleteMultipartiteGraph) + DiamondGraph = staticmethod(basic.DiamondGraph) + GemGraph = staticmethod(basic.GemGraph) + DartGraph = staticmethod(basic.DartGraph) + ForkGraph = staticmethod(basic.ForkGraph) + EmptyGraph = staticmethod(basic.EmptyGraph) + Grid2dGraph = staticmethod(basic.Grid2dGraph) + GridGraph = staticmethod(basic.GridGraph) + HouseGraph = staticmethod(basic.HouseGraph) + HouseXGraph = staticmethod(basic.HouseXGraph) + LadderGraph = staticmethod(basic.LadderGraph) + PathGraph = staticmethod(basic.PathGraph) + StarGraph = staticmethod(basic.StarGraph) Toroidal6RegularGrid2dGraph = staticmethod(basic.Toroidal6RegularGrid2dGraph) - ToroidalGrid2dGraph = staticmethod(basic.ToroidalGrid2dGraph) + ToroidalGrid2dGraph = staticmethod(basic.ToroidalGrid2dGraph) ########################################################################### # Small Graphs ########################################################################### from .generators import smallgraphs, distance_regular - Balaban10Cage = staticmethod(smallgraphs.Balaban10Cage) - Balaban11Cage = staticmethod(smallgraphs.Balaban11Cage) - BidiakisCube = staticmethod(smallgraphs.BidiakisCube) - BiggsSmithGraph = staticmethod(smallgraphs.BiggsSmithGraph) - BlanusaFirstSnarkGraph = staticmethod(smallgraphs.BlanusaFirstSnarkGraph) - BlanusaSecondSnarkGraph = staticmethod(smallgraphs.BlanusaSecondSnarkGraph) - BrinkmannGraph = staticmethod(smallgraphs.BrinkmannGraph) - BrouwerHaemersGraph = staticmethod(smallgraphs.BrouwerHaemersGraph) - BuckyBall = staticmethod(smallgraphs.BuckyBall) - CameronGraph = staticmethod(smallgraphs.CameronGraph) - Cell600 = staticmethod(smallgraphs.Cell600) - Cell120 = staticmethod(smallgraphs.Cell120) - ChvatalGraph = staticmethod(smallgraphs.ChvatalGraph) - ClebschGraph = staticmethod(smallgraphs.ClebschGraph) + Balaban10Cage = staticmethod(smallgraphs.Balaban10Cage) + Balaban11Cage = staticmethod(smallgraphs.Balaban11Cage) + BidiakisCube = staticmethod(smallgraphs.BidiakisCube) + BiggsSmithGraph = staticmethod(smallgraphs.BiggsSmithGraph) + BlanusaFirstSnarkGraph = staticmethod(smallgraphs.BlanusaFirstSnarkGraph) + BlanusaSecondSnarkGraph = staticmethod(smallgraphs.BlanusaSecondSnarkGraph) + BrinkmannGraph = staticmethod(smallgraphs.BrinkmannGraph) + BrouwerHaemersGraph = staticmethod(smallgraphs.BrouwerHaemersGraph) + BuckyBall = staticmethod(smallgraphs.BuckyBall) + CameronGraph = staticmethod(smallgraphs.CameronGraph) + Cell600 = staticmethod(smallgraphs.Cell600) + Cell120 = staticmethod(smallgraphs.Cell120) + ChvatalGraph = staticmethod(smallgraphs.ChvatalGraph) + ClebschGraph = staticmethod(smallgraphs.ClebschGraph) cocliques_HoffmannSingleton = staticmethod(distance_regular.cocliques_HoffmannSingleton) - ConwaySmith_for_3S7 = staticmethod(distance_regular.ConwaySmith_for_3S7) - CoxeterGraph = staticmethod(smallgraphs.CoxeterGraph) - DejterGraph = staticmethod(smallgraphs.DejterGraph) - DesarguesGraph = staticmethod(smallgraphs.DesarguesGraph) + ConwaySmith_for_3S7 = staticmethod(distance_regular.ConwaySmith_for_3S7) + CoxeterGraph = staticmethod(smallgraphs.CoxeterGraph) + DejterGraph = staticmethod(smallgraphs.DejterGraph) + DesarguesGraph = staticmethod(smallgraphs.DesarguesGraph) distance_3_doubly_truncated_Golay_code_graph = staticmethod(distance_regular.distance_3_doubly_truncated_Golay_code_graph) - DoubleStarSnark = staticmethod(smallgraphs.DoubleStarSnark) + DoubleStarSnark = staticmethod(smallgraphs.DoubleStarSnark) DoublyTruncatedWittGraph = staticmethod(distance_regular.DoublyTruncatedWittGraph) - DurerGraph = staticmethod(smallgraphs.DurerGraph) - DyckGraph = staticmethod(smallgraphs.DyckGraph) - EllinghamHorton54Graph = staticmethod(smallgraphs.EllinghamHorton54Graph) - EllinghamHorton78Graph = staticmethod(smallgraphs.EllinghamHorton78Graph) - ErreraGraph = staticmethod(smallgraphs.ErreraGraph) - F26AGraph = staticmethod(smallgraphs.F26AGraph) - FlowerSnark = staticmethod(smallgraphs.FlowerSnark) - FolkmanGraph = staticmethod(smallgraphs.FolkmanGraph) - FosterGraph = staticmethod(smallgraphs.FosterGraph) - FosterGraph3S6 = staticmethod(distance_regular.FosterGraph3S6) - FranklinGraph = staticmethod(smallgraphs.FranklinGraph) - FruchtGraph = staticmethod(smallgraphs.FruchtGraph) - GoldnerHararyGraph = staticmethod(smallgraphs.GoldnerHararyGraph) - GolombGraph = staticmethod(smallgraphs.GolombGraph) - GossetGraph = staticmethod(smallgraphs.GossetGraph) - graph_3O73 = staticmethod(distance_regular.graph_3O73) - GrayGraph = staticmethod(smallgraphs.GrayGraph) - GritsenkoGraph = staticmethod(smallgraphs.GritsenkoGraph) - GrotzschGraph = staticmethod(smallgraphs.GrotzschGraph) - HallJankoGraph = staticmethod(smallgraphs.HallJankoGraph) - WellsGraph = staticmethod(smallgraphs.WellsGraph) - HarborthGraph = staticmethod(smallgraphs.HarborthGraph) - HarriesGraph = staticmethod(smallgraphs.HarriesGraph) - HarriesWongGraph = staticmethod(smallgraphs.HarriesWongGraph) - HeawoodGraph = staticmethod(smallgraphs.HeawoodGraph) - HerschelGraph = staticmethod(smallgraphs.HerschelGraph) - HigmanSimsGraph = staticmethod(smallgraphs.HigmanSimsGraph) - HoffmanGraph = staticmethod(smallgraphs.HoffmanGraph) - HoffmanSingletonGraph = staticmethod(smallgraphs.HoffmanSingletonGraph) - HoltGraph = staticmethod(smallgraphs.HoltGraph) - HortonGraph = staticmethod(smallgraphs.HortonGraph) - IoninKharaghani765Graph = staticmethod(smallgraphs.IoninKharaghani765Graph) + DurerGraph = staticmethod(smallgraphs.DurerGraph) + DyckGraph = staticmethod(smallgraphs.DyckGraph) + EllinghamHorton54Graph = staticmethod(smallgraphs.EllinghamHorton54Graph) + EllinghamHorton78Graph = staticmethod(smallgraphs.EllinghamHorton78Graph) + ErreraGraph = staticmethod(smallgraphs.ErreraGraph) + F26AGraph = staticmethod(smallgraphs.F26AGraph) + FlowerSnark = staticmethod(smallgraphs.FlowerSnark) + FolkmanGraph = staticmethod(smallgraphs.FolkmanGraph) + FosterGraph = staticmethod(smallgraphs.FosterGraph) + FosterGraph3S6 = staticmethod(distance_regular.FosterGraph3S6) + FranklinGraph = staticmethod(smallgraphs.FranklinGraph) + FruchtGraph = staticmethod(smallgraphs.FruchtGraph) + GoldnerHararyGraph = staticmethod(smallgraphs.GoldnerHararyGraph) + GolombGraph = staticmethod(smallgraphs.GolombGraph) + GossetGraph = staticmethod(smallgraphs.GossetGraph) + graph_3O73 = staticmethod(distance_regular.graph_3O73) + GrayGraph = staticmethod(smallgraphs.GrayGraph) + GritsenkoGraph = staticmethod(smallgraphs.GritsenkoGraph) + GrotzschGraph = staticmethod(smallgraphs.GrotzschGraph) + HallJankoGraph = staticmethod(smallgraphs.HallJankoGraph) + WellsGraph = staticmethod(smallgraphs.WellsGraph) + HarborthGraph = staticmethod(smallgraphs.HarborthGraph) + HarriesGraph = staticmethod(smallgraphs.HarriesGraph) + HarriesWongGraph = staticmethod(smallgraphs.HarriesWongGraph) + HeawoodGraph = staticmethod(smallgraphs.HeawoodGraph) + HerschelGraph = staticmethod(smallgraphs.HerschelGraph) + HigmanSimsGraph = staticmethod(smallgraphs.HigmanSimsGraph) + HoffmanGraph = staticmethod(smallgraphs.HoffmanGraph) + HoffmanSingletonGraph = staticmethod(smallgraphs.HoffmanSingletonGraph) + HoltGraph = staticmethod(smallgraphs.HoltGraph) + HortonGraph = staticmethod(smallgraphs.HortonGraph) + IoninKharaghani765Graph = staticmethod(smallgraphs.IoninKharaghani765Graph) IvanovIvanovFaradjevGraph = staticmethod(distance_regular.IvanovIvanovFaradjevGraph) - J2Graph = staticmethod(distance_regular.J2Graph) - JankoKharaghaniGraph = staticmethod(smallgraphs.JankoKharaghaniGraph) - JankoKharaghaniTonchevGraph = staticmethod(smallgraphs.JankoKharaghaniTonchevGraph) - KittellGraph = staticmethod(smallgraphs.KittellGraph) - KrackhardtKiteGraph = staticmethod(smallgraphs.KrackhardtKiteGraph) - Klein3RegularGraph = staticmethod(smallgraphs.Klein3RegularGraph) - Klein7RegularGraph = staticmethod(smallgraphs.Klein7RegularGraph) - LargeWittGraph = staticmethod(distance_regular.LargeWittGraph) - LeonardGraph = staticmethod(distance_regular.LeonardGraph) - LjubljanaGraph = staticmethod(smallgraphs.LjubljanaGraph) - vanLintSchrijverGraph = staticmethod(distance_regular.vanLintSchrijverGraph) - LivingstoneGraph = staticmethod(smallgraphs.LivingstoneGraph) + J2Graph = staticmethod(distance_regular.J2Graph) + JankoKharaghaniGraph = staticmethod(smallgraphs.JankoKharaghaniGraph) + JankoKharaghaniTonchevGraph = staticmethod(smallgraphs.JankoKharaghaniTonchevGraph) + KittellGraph = staticmethod(smallgraphs.KittellGraph) + KrackhardtKiteGraph = staticmethod(smallgraphs.KrackhardtKiteGraph) + Klein3RegularGraph = staticmethod(smallgraphs.Klein3RegularGraph) + Klein7RegularGraph = staticmethod(smallgraphs.Klein7RegularGraph) + LargeWittGraph = staticmethod(distance_regular.LargeWittGraph) + LeonardGraph = staticmethod(distance_regular.LeonardGraph) + LjubljanaGraph = staticmethod(smallgraphs.LjubljanaGraph) + vanLintSchrijverGraph = staticmethod(distance_regular.vanLintSchrijverGraph) + LivingstoneGraph = staticmethod(smallgraphs.LivingstoneGraph) locally_GQ42_distance_transitive_graph = staticmethod(distance_regular.locally_GQ42_distance_transitive_graph) - LocalMcLaughlinGraph = staticmethod(smallgraphs.LocalMcLaughlinGraph) - M22Graph = staticmethod(smallgraphs.M22Graph) - MarkstroemGraph = staticmethod(smallgraphs.MarkstroemGraph) + LocalMcLaughlinGraph = staticmethod(smallgraphs.LocalMcLaughlinGraph) + M22Graph = staticmethod(smallgraphs.M22Graph) + MarkstroemGraph = staticmethod(smallgraphs.MarkstroemGraph) MathonStronglyRegularGraph = staticmethod(smallgraphs.MathonStronglyRegularGraph) - McGeeGraph = staticmethod(smallgraphs.McGeeGraph) - McLaughlinGraph = staticmethod(smallgraphs.McLaughlinGraph) - MeredithGraph = staticmethod(smallgraphs.MeredithGraph) - MoebiusKantorGraph = staticmethod(smallgraphs.MoebiusKantorGraph) - MoserSpindle = staticmethod(smallgraphs.MoserSpindle) - NauruGraph = staticmethod(smallgraphs.NauruGraph) - PappusGraph = staticmethod(smallgraphs.PappusGraph) - PoussinGraph = staticmethod(smallgraphs.PoussinGraph) - PerkelGraph = staticmethod(smallgraphs.PerkelGraph) - PetersenGraph = staticmethod(smallgraphs.PetersenGraph) - RobertsonGraph = staticmethod(smallgraphs.RobertsonGraph) - SchlaefliGraph = staticmethod(smallgraphs.SchlaefliGraph) + McGeeGraph = staticmethod(smallgraphs.McGeeGraph) + McLaughlinGraph = staticmethod(smallgraphs.McLaughlinGraph) + MeredithGraph = staticmethod(smallgraphs.MeredithGraph) + MoebiusKantorGraph = staticmethod(smallgraphs.MoebiusKantorGraph) + MoserSpindle = staticmethod(smallgraphs.MoserSpindle) + NauruGraph = staticmethod(smallgraphs.NauruGraph) + PappusGraph = staticmethod(smallgraphs.PappusGraph) + PoussinGraph = staticmethod(smallgraphs.PoussinGraph) + PerkelGraph = staticmethod(smallgraphs.PerkelGraph) + PetersenGraph = staticmethod(smallgraphs.PetersenGraph) + RobertsonGraph = staticmethod(smallgraphs.RobertsonGraph) + SchlaefliGraph = staticmethod(smallgraphs.SchlaefliGraph) shortened_00_11_binary_Golay_code_graph = staticmethod(distance_regular.shortened_00_11_binary_Golay_code_graph) shortened_000_111_extended_binary_Golay_code_graph = staticmethod(distance_regular.shortened_000_111_extended_binary_Golay_code_graph) - ShrikhandeGraph = staticmethod(smallgraphs.ShrikhandeGraph) - SimsGewirtzGraph = staticmethod(smallgraphs.SimsGewirtzGraph) - SousselierGraph = staticmethod(smallgraphs.SousselierGraph) - SylvesterGraph = staticmethod(smallgraphs.SylvesterGraph) - SzekeresSnarkGraph = staticmethod(smallgraphs.SzekeresSnarkGraph) - ThomsenGraph = staticmethod(smallgraphs.ThomsenGraph) - TietzeGraph = staticmethod(smallgraphs.TietzeGraph) - Tutte12Cage = staticmethod(smallgraphs.Tutte12Cage) + ShrikhandeGraph = staticmethod(smallgraphs.ShrikhandeGraph) + SimsGewirtzGraph = staticmethod(smallgraphs.SimsGewirtzGraph) + SousselierGraph = staticmethod(smallgraphs.SousselierGraph) + SylvesterGraph = staticmethod(smallgraphs.SylvesterGraph) + SzekeresSnarkGraph = staticmethod(smallgraphs.SzekeresSnarkGraph) + ThomsenGraph = staticmethod(smallgraphs.ThomsenGraph) + TietzeGraph = staticmethod(smallgraphs.TietzeGraph) + Tutte12Cage = staticmethod(smallgraphs.Tutte12Cage) TruncatedIcosidodecahedralGraph = staticmethod(smallgraphs.TruncatedIcosidodecahedralGraph) TruncatedTetrahedralGraph = staticmethod(smallgraphs.TruncatedTetrahedralGraph) - TruncatedWittGraph = staticmethod(distance_regular.TruncatedWittGraph) - TutteCoxeterGraph = staticmethod(smallgraphs.TutteCoxeterGraph) - TutteGraph = staticmethod(smallgraphs.TutteGraph) - U42Graph216 = staticmethod(smallgraphs.U42Graph216) - U42Graph540 = staticmethod(smallgraphs.U42Graph540) - WagnerGraph = staticmethod(smallgraphs.WagnerGraph) - WatkinsSnarkGraph = staticmethod(smallgraphs.WatkinsSnarkGraph) - WienerArayaGraph = staticmethod(smallgraphs.WienerArayaGraph) - SuzukiGraph = staticmethod(smallgraphs.SuzukiGraph) + TruncatedWittGraph = staticmethod(distance_regular.TruncatedWittGraph) + TutteCoxeterGraph = staticmethod(smallgraphs.TutteCoxeterGraph) + TutteGraph = staticmethod(smallgraphs.TutteGraph) + U42Graph216 = staticmethod(smallgraphs.U42Graph216) + U42Graph540 = staticmethod(smallgraphs.U42Graph540) + WagnerGraph = staticmethod(smallgraphs.WagnerGraph) + WatkinsSnarkGraph = staticmethod(smallgraphs.WatkinsSnarkGraph) + WienerArayaGraph = staticmethod(smallgraphs.WienerArayaGraph) + SuzukiGraph = staticmethod(smallgraphs.SuzukiGraph) ########################################################################### # Platonic Solids ########################################################################### from .generators import platonic_solids - DodecahedralGraph = staticmethod(platonic_solids.DodecahedralGraph) - HexahedralGraph = staticmethod(platonic_solids.HexahedralGraph) - IcosahedralGraph = staticmethod(platonic_solids.IcosahedralGraph) - OctahedralGraph = staticmethod(platonic_solids.OctahedralGraph) - TetrahedralGraph = staticmethod(platonic_solids.TetrahedralGraph) + DodecahedralGraph = staticmethod(platonic_solids.DodecahedralGraph) + HexahedralGraph = staticmethod(platonic_solids.HexahedralGraph) + IcosahedralGraph = staticmethod(platonic_solids.IcosahedralGraph) + OctahedralGraph = staticmethod(platonic_solids.OctahedralGraph) + TetrahedralGraph = staticmethod(platonic_solids.TetrahedralGraph) ########################################################################### # Families ########################################################################### from .generators import families from . import strongly_regular_db - AlternatingFormsGraph = staticmethod(distance_regular.AlternatingFormsGraph) - AztecDiamondGraph = staticmethod(families.AztecDiamondGraph) - BalancedTree = staticmethod(families.BalancedTree) - BarbellGraph = staticmethod(families.BarbellGraph) - BilinearFormsGraph = staticmethod(distance_regular.BilinearFormsGraph) - BubbleSortGraph = staticmethod(families.BubbleSortGraph) - CaiFurerImmermanGraph = staticmethod(families.CaiFurerImmermanGraph) - chang_graphs = staticmethod(families.chang_graphs) - CirculantGraph = staticmethod(families.CirculantGraph) - CubeGraph = staticmethod(families.CubeGraph) - CubeConnectedCycle = staticmethod(families.CubeConnectedCycle) - DipoleGraph = staticmethod(families.DipoleGraph) + AlternatingFormsGraph = staticmethod(distance_regular.AlternatingFormsGraph) + AztecDiamondGraph = staticmethod(families.AztecDiamondGraph) + BalancedTree = staticmethod(families.BalancedTree) + BarbellGraph = staticmethod(families.BarbellGraph) + BilinearFormsGraph = staticmethod(distance_regular.BilinearFormsGraph) + BubbleSortGraph = staticmethod(families.BubbleSortGraph) + CaiFurerImmermanGraph = staticmethod(families.CaiFurerImmermanGraph) + chang_graphs = staticmethod(families.chang_graphs) + CirculantGraph = staticmethod(families.CirculantGraph) + CubeGraph = staticmethod(families.CubeGraph) + CubeConnectedCycle = staticmethod(families.CubeConnectedCycle) + DipoleGraph = staticmethod(families.DipoleGraph) distance_regular_graph = staticmethod(distance_regular.distance_regular_graph) DorogovtsevGoltsevMendesGraph = staticmethod(families.DorogovtsevGoltsevMendesGraph) DoubleGeneralizedPetersenGraph = staticmethod(families.DoubleGeneralizedPetersenGraph) - DoubleGrassmannGraph = staticmethod(distance_regular.DoubleGrassmannGraph) - DoubleOddGraph = staticmethod(distance_regular.DoubleOddGraph) - EgawaGraph = staticmethod(families.EgawaGraph) - FibonacciTree = staticmethod(families.FibonacciTree) - FoldedCubeGraph = staticmethod(families.FoldedCubeGraph) - FriendshipGraph = staticmethod(families.FriendshipGraph) - FurerGadget = staticmethod(families.FurerGadget) - FuzzyBallGraph = staticmethod(families.FuzzyBallGraph) + DoubleGrassmannGraph = staticmethod(distance_regular.DoubleGrassmannGraph) + DoubleOddGraph = staticmethod(distance_regular.DoubleOddGraph) + EgawaGraph = staticmethod(families.EgawaGraph) + FibonacciTree = staticmethod(families.FibonacciTree) + FoldedCubeGraph = staticmethod(families.FoldedCubeGraph) + FriendshipGraph = staticmethod(families.FriendshipGraph) + FurerGadget = staticmethod(families.FurerGadget) + FuzzyBallGraph = staticmethod(families.FuzzyBallGraph) GeneralisedDodecagonGraph = staticmethod(distance_regular.GeneralisedDodecagonGraph) GeneralisedHexagonGraph = staticmethod(distance_regular.GeneralisedHexagonGraph) GeneralisedOctagonGraph = staticmethod(distance_regular.GeneralisedOctagonGraph) GeneralizedPetersenGraph = staticmethod(families.GeneralizedPetersenGraph) GeneralizedSierpinskiGraph = staticmethod(families.GeneralizedSierpinskiGraph) - GoethalsSeidelGraph = staticmethod(families.GoethalsSeidelGraph) - GrassmannGraph = staticmethod(distance_regular.GrassmannGraph) - HalfCube = staticmethod(distance_regular.HalfCube) - HammingGraph = staticmethod(families.HammingGraph) - HanoiTowerGraph = staticmethod(families.HanoiTowerGraph) - HararyGraph = staticmethod(families.HararyGraph) - HermitianFormsGraph = staticmethod(distance_regular.HermitianFormsGraph) - HyperStarGraph = staticmethod(families.HyperStarGraph) - IGraph = staticmethod(families.IGraph) - JohnsonGraph = staticmethod(families.JohnsonGraph) - KneserGraph = staticmethod(families.KneserGraph) - LCFGraph = staticmethod(families.LCFGraph) + GoethalsSeidelGraph = staticmethod(families.GoethalsSeidelGraph) + GrassmannGraph = staticmethod(distance_regular.GrassmannGraph) + HalfCube = staticmethod(distance_regular.HalfCube) + HammingGraph = staticmethod(families.HammingGraph) + HanoiTowerGraph = staticmethod(families.HanoiTowerGraph) + HararyGraph = staticmethod(families.HararyGraph) + HermitianFormsGraph = staticmethod(distance_regular.HermitianFormsGraph) + HyperStarGraph = staticmethod(families.HyperStarGraph) + IGraph = staticmethod(families.IGraph) + JohnsonGraph = staticmethod(families.JohnsonGraph) + KneserGraph = staticmethod(families.KneserGraph) + LCFGraph = staticmethod(families.LCFGraph) line_graph_forbidden_subgraphs = staticmethod(families.line_graph_forbidden_subgraphs) - LollipopGraph = staticmethod(families.LollipopGraph) + LollipopGraph = staticmethod(families.LollipopGraph) MathonPseudocyclicMergingGraph = staticmethod(families.MathonPseudocyclicMergingGraph) MathonPseudocyclicStronglyRegularGraph = staticmethod(families.MathonPseudocyclicStronglyRegularGraph) - MuzychukS6Graph = staticmethod(families.MuzychukS6Graph) - MycielskiGraph = staticmethod(families.MycielskiGraph) - MycielskiStep = staticmethod(families.MycielskiStep) - NKStarGraph = staticmethod(families.NKStarGraph) - NStarGraph = staticmethod(families.NStarGraph) - OddGraph = staticmethod(families.OddGraph) - PaleyGraph = staticmethod(families.PaleyGraph) - PasechnikGraph = staticmethod(families.PasechnikGraph) - petersen_family = staticmethod(families.petersen_family) - RingedTree = staticmethod(families.RingedTree) - RoseWindowGraph = staticmethod(families.RoseWindowGraph) - SierpinskiGasketGraph = staticmethod(families.SierpinskiGasketGraph) + MuzychukS6Graph = staticmethod(families.MuzychukS6Graph) + MycielskiGraph = staticmethod(families.MycielskiGraph) + MycielskiStep = staticmethod(families.MycielskiStep) + NKStarGraph = staticmethod(families.NKStarGraph) + NStarGraph = staticmethod(families.NStarGraph) + OddGraph = staticmethod(families.OddGraph) + PaleyGraph = staticmethod(families.PaleyGraph) + PasechnikGraph = staticmethod(families.PasechnikGraph) + petersen_family = staticmethod(families.petersen_family) + RingedTree = staticmethod(families.RingedTree) + RoseWindowGraph = staticmethod(families.RoseWindowGraph) + SierpinskiGasketGraph = staticmethod(families.SierpinskiGasketGraph) SquaredSkewHadamardMatrixGraph = staticmethod(families.SquaredSkewHadamardMatrixGraph) SwitchedSquaredSkewHadamardMatrixGraph = staticmethod(families.SwitchedSquaredSkewHadamardMatrixGraph) strongly_regular_graph = staticmethod(strongly_regular_db.strongly_regular_graph) - TabacjnGraph = staticmethod(families.TabacjnGraph) - TadpoleGraph = staticmethod(families.TadpoleGraph) - trees = staticmethod(families.trees) - nauty_gentreeg = staticmethod(families.nauty_gentreeg) - TuranGraph = staticmethod(families.TuranGraph) - UstimenkoGraph = staticmethod(distance_regular.UstimenkoGraph) - WheelGraph = staticmethod(families.WheelGraph) - WindmillGraph = staticmethod(families.WindmillGraph) + TabacjnGraph = staticmethod(families.TabacjnGraph) + TadpoleGraph = staticmethod(families.TadpoleGraph) + trees = staticmethod(families.trees) + nauty_gentreeg = staticmethod(families.nauty_gentreeg) + TuranGraph = staticmethod(families.TuranGraph) + UstimenkoGraph = staticmethod(distance_regular.UstimenkoGraph) + WheelGraph = staticmethod(families.WheelGraph) + WindmillGraph = staticmethod(families.WindmillGraph) ########################################################################### # Graphs from classical geometries over `F_q` @@ -2544,8 +2552,7 @@ def quadrangulations(self, order, minimum_degree=None, minimum_connectivity=None OrthogonalPolarGraph = staticmethod(classical_geometries.OrthogonalPolarGraph) SymplecticDualPolarGraph = staticmethod(classical_geometries.SymplecticDualPolarGraph) SymplecticPolarGraph = staticmethod(classical_geometries.SymplecticPolarGraph) - TaylorTwographDescendantSRG = \ - staticmethod(classical_geometries.TaylorTwographDescendantSRG) + TaylorTwographDescendantSRG = staticmethod(classical_geometries.TaylorTwographDescendantSRG) TaylorTwographSRG = staticmethod(classical_geometries.TaylorTwographSRG) T2starGeneralizedQuadrangleGraph = staticmethod(classical_geometries.T2starGeneralizedQuadrangleGraph) Nowhere0WordsTwoWeightCodeGraph = staticmethod(classical_geometries.Nowhere0WordsTwoWeightCodeGraph) @@ -2559,46 +2566,46 @@ def quadrangulations(self, order, minimum_degree=None, minimum_connectivity=None ########################################################################### from .generators import chessboard ChessboardGraphGenerator = staticmethod(chessboard.ChessboardGraphGenerator) - BishopGraph = staticmethod(chessboard.BishopGraph) - KingGraph = staticmethod(chessboard.KingGraph) - KnightGraph = staticmethod(chessboard.KnightGraph) - QueenGraph = staticmethod(chessboard.QueenGraph) - RookGraph = staticmethod(chessboard.RookGraph) + BishopGraph = staticmethod(chessboard.BishopGraph) + KingGraph = staticmethod(chessboard.KingGraph) + KnightGraph = staticmethod(chessboard.KnightGraph) + QueenGraph = staticmethod(chessboard.QueenGraph) + RookGraph = staticmethod(chessboard.RookGraph) ########################################################################### # Intersection graphs ########################################################################### from .generators import intersection - IntervalGraph = staticmethod(intersection.IntervalGraph) - IntersectionGraph = staticmethod(intersection.IntersectionGraph) - PermutationGraph = staticmethod(intersection.PermutationGraph) - OrthogonalArrayBlockGraph = staticmethod(intersection.OrthogonalArrayBlockGraph) - ToleranceGraph = staticmethod(intersection.ToleranceGraph) + IntervalGraph = staticmethod(intersection.IntervalGraph) + IntersectionGraph = staticmethod(intersection.IntersectionGraph) + PermutationGraph = staticmethod(intersection.PermutationGraph) + OrthogonalArrayBlockGraph = staticmethod(intersection.OrthogonalArrayBlockGraph) + ToleranceGraph = staticmethod(intersection.ToleranceGraph) ########################################################################### # Random Graphs ########################################################################### from .generators import random - RandomBarabasiAlbert = staticmethod(random.RandomBarabasiAlbert) - RandomBipartite = staticmethod(random.RandomBipartite) - RandomRegularBipartite = staticmethod(random.RandomRegularBipartite) - RandomBicubicPlanar = staticmethod(random.RandomBicubicPlanar) - RandomBlockGraph = staticmethod(random.RandomBlockGraph) + RandomBarabasiAlbert = staticmethod(random.RandomBarabasiAlbert) + RandomBipartite = staticmethod(random.RandomBipartite) + RandomRegularBipartite = staticmethod(random.RandomRegularBipartite) + RandomBicubicPlanar = staticmethod(random.RandomBicubicPlanar) + RandomBlockGraph = staticmethod(random.RandomBlockGraph) RandomBoundedToleranceGraph = staticmethod(random.RandomBoundedToleranceGraph) - RandomChordalGraph = staticmethod(random.RandomChordalGraph) - RandomGNM = staticmethod(random.RandomGNM) - RandomGNP = staticmethod(random.RandomGNP) - RandomHolmeKim = staticmethod(random.RandomHolmeKim) - RandomIntervalGraph = staticmethod(random.RandomIntervalGraph) - RandomLobster = staticmethod(random.RandomLobster) + RandomChordalGraph = staticmethod(random.RandomChordalGraph) + RandomGNM = staticmethod(random.RandomGNM) + RandomGNP = staticmethod(random.RandomGNP) + RandomHolmeKim = staticmethod(random.RandomHolmeKim) + RandomIntervalGraph = staticmethod(random.RandomIntervalGraph) + RandomLobster = staticmethod(random.RandomLobster) RandomNewmanWattsStrogatz = staticmethod(random.RandomNewmanWattsStrogatz) - RandomRegular = staticmethod(random.RandomRegular) - RandomShell = staticmethod(random.RandomShell) - RandomToleranceGraph = staticmethod(random.RandomToleranceGraph) - RandomTreePowerlaw = staticmethod(random.RandomTreePowerlaw) - RandomTree = staticmethod(random.RandomTree) - RandomTriangulation = staticmethod(random.RandomTriangulation) - RandomUnitDiskGraph = staticmethod(random.RandomUnitDiskGraph) + RandomRegular = staticmethod(random.RandomRegular) + RandomShell = staticmethod(random.RandomShell) + RandomToleranceGraph = staticmethod(random.RandomToleranceGraph) + RandomTreePowerlaw = staticmethod(random.RandomTreePowerlaw) + RandomTree = staticmethod(random.RandomTree) + RandomTriangulation = staticmethod(random.RandomTriangulation) + RandomUnitDiskGraph = staticmethod(random.RandomUnitDiskGraph) ########################################################################### # Maps @@ -2613,11 +2620,12 @@ def quadrangulations(self, order, minimum_degree=None, minimum_connectivity=None # Degree Sequence ########################################################################### from .generators import degree_sequence - DegreeSequence = staticmethod(degree_sequence.DegreeSequence) - DegreeSequenceBipartite = staticmethod(degree_sequence.DegreeSequenceBipartite) + DegreeSequence = staticmethod(degree_sequence.DegreeSequence) + DegreeSequenceBipartite = staticmethod(degree_sequence.DegreeSequenceBipartite) DegreeSequenceConfigurationModel = staticmethod(degree_sequence.DegreeSequenceConfigurationModel) - DegreeSequenceTree = staticmethod(degree_sequence.DegreeSequenceTree) - DegreeSequenceExpected = staticmethod(degree_sequence.DegreeSequenceExpected) + DegreeSequenceTree = staticmethod(degree_sequence.DegreeSequenceTree) + DegreeSequenceExpected = staticmethod(degree_sequence.DegreeSequenceExpected) + def canaug_traverse_vert(g, aut_gens, max_verts, property, dig=False, loops=False, sparse=True): """ @@ -2701,9 +2709,9 @@ def canaug_traverse_vert(g, aut_gens, max_verts, property, dig=False, loops=Fals for i in range(len(children)): k = 0 for j in range(possibilities): - if (1 << j)&i: + if (1 << j) & i: if dig and j >= n: - k += (1 << (gen[j-n]+n)) + k += (1 << (gen[j - n] + n)) else: k += (1 << gen[j]) while children[k] != -1: @@ -2712,7 +2720,7 @@ def canaug_traverse_vert(g, aut_gens, max_verts, property, dig=False, loops=Fals i = children[i] if i != k: # union i & k - smaller, larger = sorted([i,k]) + smaller, larger = sorted([i, k]) children[larger] = smaller num_roots -= 1 @@ -2733,18 +2741,18 @@ def canaug_traverse_vert(g, aut_gens, max_verts, property, dig=False, loops=Fals if dig: index = 0 while 2 * index < possibilities: - if (1 << index)&i: - edges.append((index,n)) + if (1 << index) & i: + edges.append((index, n)) index += 1 while index < possibilities: - if (1 << index)&i: - edges.append((n,index-n)) + if (1 << index) & i: + edges.append((n, index - n)) index += 1 else: index = 0 while (1 << index) <= i: - if (1 << index)&i: - edges.append((index,n)) + if (1 << index) & i: + edges.append((index, n)) index += 1 z.add_edges(edges) z_s = [] @@ -2752,7 +2760,7 @@ def canaug_traverse_vert(g, aut_gens, max_verts, property, dig=False, loops=Fals z_s.append(z) if loops: z = z.copy(sparse=sparse) - z.add_edge((n,n)) + z.add_edge((n, n)) if property(z): z_s.append(z) for z in z_s: @@ -2773,6 +2781,7 @@ def canaug_traverse_vert(g, aut_gens, max_verts, property, dig=False, loops=Fals yield a break + def check_aut(aut_gens, cut_vert, n): """ Helper function for exhaustive generation. @@ -2867,18 +2876,18 @@ def canaug_traverse_edge(g, aut_gens, property, dig=False, loops=False, sparse=T yield g n = g.order() if dig: - max_size = n*(n-1) + max_size = n * (n - 1) else: - max_size = (n*(n-1))>>1 # >> 1 is just / 2 (this is n choose 2) + max_size = (n * (n - 1)) >> 1 # >> 1 is just / 2 (this is n choose 2) if loops: max_size += n if g.size() < max_size: # build a list representing C(g) - the edge to be added # is one of max_size choices if dig: - children = [[(j,i) for i in range(n)] for j in range(n)] + children = [[(j, i) for i in range(n)] for j in range(n)] else: - children = [[(j,i) for i in range(j)] for j in range(n)] + children = [[(j, i) for i in range(j)] for j in range(n)] # union-find C(g) under Aut(g) orbits = list(range(n)) for gen in aut_gens: @@ -2938,12 +2947,12 @@ def canaug_traverse_edge(g, aut_gens, property, dig=False, loops=False, sparse=T j_range = list(range(i)) for j in j_range: if children[i][j] == (i, j): - roots.append((i,j)) + roots.append((i, j)) if loops: seen = [] for i in range(n): if orbits[i] not in seen: - roots.append((i,i)) + roots.append((i, i)) seen.append(orbits[i]) for i, j in roots: if g.has_edge(i, j): @@ -2978,6 +2987,7 @@ def canaug_traverse_edge(g, aut_gens, property, dig=False, loops=False, sparse=T yield a break + def check_aut_edge(aut_gens, cut_edge, i, j, n, dig=False): """ Helper function for exhaustive generation. @@ -3017,5 +3027,6 @@ def check_aut_edge(aut_gens, cut_edge, i, j, n, dig=False): if not dig and new_perm[cut_edge[0]] == j and new_perm[cut_edge[1]] == i: yield new_perm + # Easy access to the graph generators from the command line: graphs = GraphGenerators() diff --git a/src/sage/graphs/graph_latex.py b/src/sage/graphs/graph_latex.py index d3692c609e1..ed9a573ee08 100644 --- a/src/sage/graphs/graph_latex.py +++ b/src/sage/graphs/graph_latex.py @@ -385,15 +385,15 @@ GraphLatex class and functions ------------------------------ """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2009 Robert Beezer # Copyright (C) 2009 Fidel Barrera Cruz # # 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.structure.sage_object import SageObject from sage.misc.cachefunc import cached_function @@ -514,7 +514,7 @@ class GraphLatex(SageObject): 'units': 'cm', 'scale': 1.0, 'graphic_size': (5, 5), - 'margins': (0,0,0,0), + 'margins': (0, 0, 0, 0), 'vertex_color': 'black', 'vertex_colors': {}, 'vertex_fill_color': 'white', @@ -1100,8 +1100,8 @@ def set_option(self, option_name, option_value=None): # formats = ('tkz_graph', 'dot2tex') styles = ('Custom', 'Shade', 'Art', 'Normal', 'Dijkstra', 'Welsh', 'Classic', 'Simple') - unit_names = ('in','mm','cm','pt', 'em', 'ex') - shape_names = ('circle', 'sphere','rectangle', 'diamond') + unit_names = ('in', 'mm', 'cm', 'pt', 'em', 'ex') + shape_names = ('circle', 'sphere', 'rectangle', 'diamond') label_places = ('above', 'below', 'right', 'left') compass_points = ('NO', 'SO', 'EA', 'WE') number_types = (int, Integer, float, RealLiteral) @@ -1109,11 +1109,11 @@ def set_option(self, option_name, option_value=None): # Options with structurally similar tests # boolean_options = ('vertex_labels', 'vertex_labels_math', 'edge_fills', - 'edge_labels', 'edge_labels_math', 'edge_label_sloped') + 'edge_labels', 'edge_labels_math', 'edge_label_sloped') color_options = ('vertex_color', 'vertex_fill_color', 'vertex_label_color', - 'edge_color', 'edge_fill_color', 'edge_label_color') + 'edge_color', 'edge_fill_color', 'edge_label_color') color_dicts = ('vertex_colors', 'vertex_fill_colors', 'vertex_label_colors', - 'edge_colors', 'edge_fill_colors', 'edge_label_colors') + 'edge_colors', 'edge_fill_colors', 'edge_label_colors') boolean_dicts = ('edge_label_slopes',) positive_scalars = ('scale', 'vertex_size', 'edge_thickness') positive_scalar_dicts = ('vertex_sizes', 'edge_thicknesses') @@ -1128,7 +1128,7 @@ def set_option(self, option_name, option_value=None): elif name == 'units' and value not in unit_names: raise ValueError('%s option must be one of: in, mm, cm, pt, em, ex, not %s' % (name, value)) elif name == 'graphic_size' and not(isinstance(value, tuple) and (len(value) == 2)): - raise ValueError( '%s option must be an ordered pair, not %s' % (name, value)) + raise ValueError('%s option must be an ordered pair, not %s' % (name, value)) elif name == 'margins' and not((isinstance(value, tuple)) and (len(value) == 4)): raise ValueError('%s option must be 4-tuple, not %s' % (name, value)) elif name in color_options: @@ -1142,12 +1142,19 @@ def set_option(self, option_name, option_value=None): raise ValueError('%s option must be the shape of a vertex, not %s' % (name, value)) elif name in positive_scalars and not (type(value) in number_types and (value >= 0.0)): raise ValueError('%s option must be a positive number, not %s' % (name, value)) - elif name == 'vertex_label_placement' and not(value == 'center') and not(isinstance(value, tuple) and len(value) == 2 and type(value[0]) in number_types and value[0] >= 0 and type(value[1]) in number_types and value[1] >= 0): + elif (name == 'vertex_label_placement' and value != 'center' and + not (isinstance(value, tuple) and len(value) == 2 and + type(value[0]) in number_types and value[0] >= 0 and + type(value[1]) in number_types and value[1] >= 0)): raise ValueError('%s option must be None, or a pair of positive numbers, not %s' % (name, value)) - elif name == 'edge_label_placement' and not(((type(value) in number_types) and (0 <= value) and (value <= 1)) or (value in label_places)): + elif (name == 'edge_label_placement' and + not ((type(value) in number_types and 0 <= value <= 1) + or value in label_places)): raise ValueError('%s option must be a number between 0.0 and 1.0 or a place (like "above"), not %s' % (name, value)) - elif name == 'loop_placement' and not(isinstance(value, tuple) and (len(value) == 2) and (value[0] >= 0) and (value[1] in compass_points)): - raise ValueError( '%s option must be a pair that is a positive number followed by a compass point abbreviation, not %s' % (name, value)) + elif (name == 'loop_placement' and + not (isinstance(value, tuple) and len(value) == 2 and + value[0] >= 0 and value[1] in compass_points)): + raise ValueError('%s option must be a pair that is a positive number followed by a compass point abbreviation, not %s' % (name, value)) # # Checks/test on dictionaries of values (ie per-vertex or per-edge defaults) # @@ -1186,7 +1193,10 @@ def set_option(self, option_name, option_value=None): raise TypeError('%s option must be a dictionary, not %s' % (name, value)) else: for key, p in value.items(): - if not(p == 'center') and not(isinstance(p, tuple) and len(p) == 2 and type(p[0]) in number_types and p[0] >= 0 and type(p[1]) in number_types and p[1] >= 0): + if (p != 'center' and + not (isinstance(p, tuple) and len(p) == 2 and + type(p[0]) in number_types and p[0] >= 0 and + type(p[1]) in number_types and p[1] >= 0)): raise ValueError('%s option for %s needs to be None or a pair of positive numbers, not %s' % (name, key, p)) elif name == 'edge_label_placements': if not isinstance(value, dict): @@ -1205,8 +1215,8 @@ def set_option(self, option_name, option_value=None): # These have been verified as tuples before going into this next check elif name in positive_tuples: for x in value: - if not type(x) in [int, Integer, float, RealLiteral] or not x >= 0.0: - raise ValueError( '%s option of %s cannot contain %s' % (name, value, x)) + if type(x) not in [int, Integer, float, RealLiteral] or not x >= 0.0: + raise ValueError('%s option of %s cannot contain %s' % (name, value, x)) # # Verified. Set it. self._options[option_name] = option_value @@ -1417,7 +1427,7 @@ def dot2tex_picture(self): if 'edge_colors' in options: edge_colors = options['edge_colors'] new_edge_colors = {} - for edge,col in edge_colors.items(): + for edge, col in edge_colors.items(): if col in new_edge_colors: new_edge_colors[col].append(edge) else: @@ -1708,7 +1718,7 @@ def translate(p): c = dvc if u in vertex_colors: c = cc.to_rgb(vertex_colors[u]) - v_color[ u ] = c + v_color[u] = c # c = dvfc if u in vertex_fill_colors: @@ -1890,19 +1900,19 @@ def translate(p): vertex_fill_color_names = {} vertex_label_color_names = {} for u in vertex_list: - vertex_color_names[ u ] = 'c' + prefix + str(index_of_vertex[ u ]) - s += [r'\definecolor{', vertex_color_names[ u ], '}{rgb}', '{'] + vertex_color_names[u] = 'c' + prefix + str(index_of_vertex[u]) + s += [r'\definecolor{', vertex_color_names[u], '}{rgb}', '{'] s += [str(round(v_color[u][0], 4)), ','] s += [str(round(v_color[u][1], 4)), ','] s += [str(round(v_color[u][2], 4)), '}\n'] - vertex_fill_color_names[ u ] = 'cf' + prefix + str(index_of_vertex[ u ]) - s += [r'\definecolor{', vertex_fill_color_names[ u ], '}{rgb}', '{'] + vertex_fill_color_names[u] = 'cf' + prefix + str(index_of_vertex[u]) + s += [r'\definecolor{', vertex_fill_color_names[u], '}{rgb}', '{'] s += [str(round(vf_color[u][0], 4)), ','] s += [str(round(vf_color[u][1], 4)), ','] s += [str(round(vf_color[u][2], 4)), '}\n'] if vertex_labels: - vertex_label_color_names[u] = 'cl' + prefix + str(index_of_vertex[ u ]) - s += [r'\definecolor{', vertex_label_color_names[ u ], '}{rgb}{'] + vertex_label_color_names[u] = 'cl' + prefix + str(index_of_vertex[u]) + s += [r'\definecolor{', vertex_label_color_names[u], '}{rgb}{'] s += [str(round(vl_color[u][0], 4)), ','] s += [str(round(vl_color[u][1], 4)), ','] s += [str(round(vl_color[u][2], 4)), '}\n'] @@ -1954,7 +1964,7 @@ def translate(p): else: s += ['LabelOut=true,'] s += ['Ldist=', str(round(float(scale * vl_placement[u][0]), 4)), units, ','] - s += ['Lpos=',str(round(float(vl_placement[u][1]), 4)), ','] # degrees, no units + s += ['Lpos=', str(round(float(vl_placement[u][1]), 4)), ','] # degrees, no units else: s += ['NoLabel,'] # vertex label information is available to all pre-built styles @@ -2005,7 +2015,7 @@ def translate(p): s += ['pos=', str(round(float(el_placement[edge]), 4)), ','] # no units needed s += ['text=', edge_label_color_names[edge], ','] s += ['},'] - el = self._graph.edge_label(edge[0],edge[1]) + el = self._graph.edge_label(edge[0], edge[1]) if edge_labels_math and not (isinstance(el, str) and el[0] == '$' and el[-1] == '$'): lab = r'\hbox{$%s$}' % latex(el) else: diff --git a/src/sage/graphs/isgci.py b/src/sage/graphs/isgci.py index df3de1960d2..2ea1adce781 100644 --- a/src/sage/graphs/isgci.py +++ b/src/sage/graphs/isgci.py @@ -55,30 +55,13 @@ ------------------------- id : gc_32 name : chordal - type : base - + ... Problems : ----------- 3-Colourability : Linear Clique : Polynomial Clique cover : Polynomial - Cliquewidth : Unbounded - Cliquewidth expression : NP-complete - Colourability : Linear - Cutwidth : NP-complete - Domination : NP-complete - Feedback vertex set : Polynomial - Hamiltonian cycle : NP-complete - Hamiltonian path : NP-complete - Independent set : Linear - Maximum bisection : Unknown - Maximum cut : NP-complete - Minimum bisection : Unknown - Recognition : Linear - Treewidth : Polynomial - Weighted clique : Polynomial - Weighted feedback vertex set : Unknown - Weighted independent set : Linear + ... It is possible to obtain the complete list of the classes stored in ISGCI by calling the :meth:`~GraphClasses.show_all` method (beware -- long output):: @@ -637,30 +620,15 @@ def description(self): ------------------------- id : gc_32 name : chordal - type : base - + ... Problems : ----------- 3-Colourability : Linear Clique : Polynomial Clique cover : Polynomial - Cliquewidth : Unbounded - Cliquewidth expression : NP-complete - Colourability : Linear - Cutwidth : NP-complete - Domination : NP-complete - Feedback vertex set : Polynomial - Hamiltonian cycle : NP-complete - Hamiltonian path : NP-complete - Independent set : Linear - Maximum bisection : Unknown - Maximum cut : NP-complete - Minimum bisection : Unknown + ... Recognition : Linear - Treewidth : Polynomial - Weighted clique : Polynomial - Weighted feedback vertex set : Unknown - Weighted independent set : Linear + ... """ classes = GraphClasses().classes() cls = classes[self._gc_id] @@ -737,7 +705,7 @@ def classes(self): sage: type(t) <... 'dict'> sage: sorted(t["gc_151"].keys()) - ['id', 'name', 'problem', 'type'] + ['id', 'name',... 'problem',... 'type'] sage: t["gc_151"]['name'] 'cograph' sage: t["gc_151"]['problem']['Clique'] @@ -780,16 +748,14 @@ def smallgraphs(self): EXAMPLES:: sage: t = graph_classes.smallgraphs() - sage: t - {'2C_4': Graph on 8 vertices, - '2K_2': Graph on 4 vertices, - '2K_3': Graph on 6 vertices, - '2K_3 + e': Graph on 6 vertices, - '2K_4': Graph on 8 vertices, - '2P_3': Graph on 6 vertices, - ... + sage: t['2C_4'] + Graph on 8 vertices + sage: t['2K_3 + e'] + Graph on 6 vertices sage: t['fish'] Graph on 6 vertices + sage: t['bull'] + Graph on 5 vertices """ self._get_ISGCI() return self.smallgraphs() @@ -827,11 +793,11 @@ def _download_db(self): EXAMPLES:: - sage: graph_classes._download_db() # Not tested -- requires internet + sage: graph_classes._download_db() # optional - internet """ import tempfile u = urlopen('https://www.graphclasses.org/data.zip', - context=SSLContext()) + context=default_context()) with tempfile.NamedTemporaryFile(suffix=".zip") as f: f.write(u.read()) z = zipfile.ZipFile(f.name) @@ -858,7 +824,6 @@ def _parse_db(self, directory): sage: graph_classes._parse_db(GRAPHS_DATA_DIR) """ import xml.etree.cElementTree as ET - import os.path from sage.graphs.graph import Graph xml_file = os.path.join(GRAPHS_DATA_DIR, _XML_FILE) @@ -903,7 +868,7 @@ def update_db(self): EXAMPLES:: - sage: graph_classes.update_db() # Not tested -- requires internet + sage: graph_classes.update_db() # optional - internet """ self._download_db() @@ -935,8 +900,6 @@ def _get_ISGCI(self): sage: graph_classes._get_ISGCI() # long time (4s on sage.math, 2012) """ - - import os.path from sage.misc.misc import SAGE_DB try: @@ -1039,12 +1002,11 @@ def _XML_to_dict(root): EXAMPLES:: - sage: graph_classes.Perfect.description() # indirect doctest + sage: graph_classes.Perfect.description() # indirect doctest Class of graphs : Perfect ------------------------- id : gc_56 name : perfect - type : base ... """ ans = root.attrib.copy() diff --git a/src/sage/graphs/matchpoly.pyx b/src/sage/graphs/matchpoly.pyx index 36c6c8fb2c4..444d99ebee3 100644 --- a/src/sage/graphs/matchpoly.pyx +++ b/src/sage/graphs/matchpoly.pyx @@ -50,7 +50,7 @@ x = polygen(ZZ, 'x') def matching_polynomial(G, complement=True, name=None): - """ + r""" Computes the matching polynomial of the graph `G`. If `p(G, k)` denotes the number of `k`-matchings (matchings with `k` edges) diff --git a/src/sage/graphs/strongly_regular_db.pyx b/src/sage/graphs/strongly_regular_db.pyx index a98c5662b35..a4eff7ecff8 100644 --- a/src/sage/graphs/strongly_regular_db.pyx +++ b/src/sage/graphs/strongly_regular_db.pyx @@ -532,6 +532,7 @@ def is_goethals_seidel(int v, int k, int l, int mu): from sage.graphs.generators.families import GoethalsSeidelGraph return [GoethalsSeidelGraph, k_bibd, r_bibd] + @cached_function def is_NOodd(int v, int k, int l, int mu): r""" @@ -587,20 +588,21 @@ def is_NOodd(int v, int k, int l, int mu): return r += 1 s += 1 - if abs(r)>abs(s): + if abs(r) > abs(s): (r, s) = (s, r) # r=-eq^(n-1) s= eq^(n-1)(q-2) q = 2 - s//r p, t = is_prime_power(q, get_data=True) pp, kk = is_prime_power(abs(r), get_data=True) - if p == pp and t != 0: - n = kk//t + 1 - e = 1 if v == (q**n)*(q**n+1)//2 else -1 - if (v == (q**n)*(q**n+e)//2 and - k == (q**n-e)*(q**(n-1)+e) and - l == 2*(q**(2*n-2)-1)+e*q**(n-1)*(q-1) and - mu == 2*q**(n-1)*(q**(n-1)+e)): + if p == pp and t: + n = kk//t + 1 + e = 1 if v == (q**n)*(q**n + 1)//2 else -1 + if (v == (q**n)*(q**n + e)//2 and + k == (q**n - e)*(q**(n - 1) + e) and + l == 2*(q**(2*n - 2) - 1) + e*q**(n - 1)*(q - 1) and + mu == 2*q**(n - 1)*(q**(n - 1) + e)): from sage.graphs.generators.classical_geometries import NonisotropicOrthogonalPolarGraph - return (NonisotropicOrthogonalPolarGraph, 2*n+1, q, '+' if e==1 else '-') + return (NonisotropicOrthogonalPolarGraph, 2*n + 1, q, '+' if e == 1 else '-') + @cached_function def is_NOperp_F5(int v, int k, int l, int mu): @@ -644,18 +646,19 @@ def is_NOperp_F5(int v, int k, int l, int mu): r, s = eigenvalues(v, k, l, mu) # 2*e*5**(n-1), -e*5**(n-1); note exceptional case n=1 if r is None: return - if abs(r)0 else -1 + e = 1 if r > 0 else -1 p, n = is_prime_power(abs(r), get_data=True) - if (3 == p and n != 0): + if 3 == p and n: n += 1 - if (v == 3**(n-1)*(3**n-e)//2 and - k == 3**(n-1)*(3**(n-1)-e)//2 and - l == 3**(n-2)*(3**(n-1)+e)//2 and - mu == 3**(n-1)*(3**(n-2)-e)//2): + if (v == 3**(n - 1)*(3**n - e)//2 and + k == 3**(n - 1)*(3**(n - 1) - e)//2 and + l == 3**(n - 2)*(3**(n - 1) + e)//2 and + mu == 3**(n - 1)*(3**(n - 2) - e)//2): from sage.graphs.generators.classical_geometries import NonisotropicOrthogonalPolarGraph - return (NonisotropicOrthogonalPolarGraph, 2*n, 3, '+' if e==1 else '-') + return (NonisotropicOrthogonalPolarGraph, 2*n, 3, '+' if e == 1 else '-') + @cached_function def is_NU(int v, int k, int l, int mu): @@ -805,29 +810,30 @@ def is_NU(int v, int k, int l, int mu): return r += 1 s += 1 - if abs(r)>abs(s): + if abs(r) > abs(s): (r, s) = (s, r) p, t = is_prime_power(abs(r), get_data=True) - if p==2: # it can be that q=2, then we'd have r>s now + if p == 2: # it can be that q=2, then we'd have r>s now pp, kk = is_prime_power(abs(s), get_data=True) - if pp==2 and kk>0: + if pp == 2 and kk > 0: (r, s) = (s, r) p, t = is_prime_power(abs(r), get_data=True) - if r==1: + if r == 1: return - kr = k//(r-1) # eq^{n-1}+1 - e = 1 if kr>0 else -1 + kr = k//(r-1) # eq^{n-1}+1 + e = 1 if kr > 0 else -1 q = (kr-1)//r pp, kk = is_prime_power(q, get_data=True) - if p == pp and kk != 0: - n = t//kk + 2 - if (v == q**(n-1)*(q**n - e)//(q + 1) and - k == (q**(n-1) + e)*(q**(n-2) - e) and - l == q**(2*n-5)*(q+1) - e*q**(n-2)*(q-1) - 2 and - mu == q**(n-3)*(q + 1)*(q**(n-2) - e)): + if p == pp and kk: + n = t//kk + 2 + if (v == q**(n - 1)*(q**n - e)//(q + 1) and + k == (q**(n - 1) + e)*(q**(n - 2) - e) and + l == q**(2*n - 5)*(q + 1) - e*q**(n - 2)*(q - 1) - 2 and + mu == q**(n - 3)*(q + 1)*(q**(n - 2) - e)): from sage.graphs.generators.classical_geometries import NonisotropicUnitaryPolarGraph return (NonisotropicUnitaryPolarGraph, n, q) + @cached_function def is_haemers(int v, int k, int l, int mu): r""" @@ -862,13 +868,14 @@ def is_haemers(int v, int k, int l, int mu): cdef int q, n, p p, n = is_prime_power(mu, get_data=True) q = mu - if 2 == p and n != 0: - if (v == q**2*(q+2) and - k == q*(q+1)-1 and - l == q-2): + if 2 == p and n: + if (v == q**2*(q + 2) and + k == q*(q + 1) - 1 and + l == q - 2): from sage.graphs.generators.classical_geometries import HaemersGraph return (HaemersGraph, q) + @cached_function def is_cossidente_penttila(int v, int k, int l, int mu): r""" @@ -905,15 +912,16 @@ def is_cossidente_penttila(int v, int k, int l, int mu): sage: t = is_cossidente_penttila(5,5,5,5); t """ cdef int q, n, p - q = 2*l+3 + q = 2*l + 3 p, n = is_prime_power(q, get_data=True) - if 2 < p and n != 0: - if (v == (q**3+1)*(q+1)//2 and - k == (q**2+1)*(q-1)//2 and - mu == (q-1)**2//2): + if 2 < p and n: + if (v == (q**3 + 1)*(q + 1)//2 and + k == (q**2 + 1)*(q - 1)//2 and + mu == (q - 1)**2//2): from sage.graphs.generators.classical_geometries import CossidentePenttilaGraph return (CossidentePenttilaGraph, q) + @cached_function def is_complete_multipartite(int v, int k, int l, int mu): r""" @@ -952,9 +960,9 @@ def is_complete_multipartite(int v, int k, int l, int mu): sage: g.is_strongly_regular(parameters=True) (20, 16, 12, 16) """ - if v>k: - r = v//(v-k) # number of parts (of size v-k each) - if l==(v-k)*(r-2) and k==mu and v == r*(v-k): + if v > k: + r = v//(v - k) # number of parts (of size v-k each) + if l == (v - k)*(r - 2) and k == mu and v == r*(v - k): from sage.graphs.generators.basic import CompleteMultipartiteGraph def CompleteMultipartiteSRG(nparts, partsize): @@ -1005,10 +1013,10 @@ def is_polhill(int v, int k, int l, int mu): [. at ...>] """ if (v, k, l, mu) not in [(1024, 231, 38, 56), - (1024, 264, 56, 72), - (1024, 297, 76, 90), - (1024, 330, 98, 110), - (1024, 462, 206, 210)]: + (1024, 264, 56, 72), + (1024, 297, 76, 90), + (1024, 330, 98, 110), + (1024, 462, 206, 210)]: return from itertools import product @@ -1019,7 +1027,7 @@ def is_polhill(int v, int k, int l, int mu): def additive_cayley(vertices): g = Graph() g.add_vertices(vertices[0].parent()) - edges = [(x,x+vv) + edges = [(x, x + vv) for vv in set(vertices) for x in g] g.add_edges(edges) @@ -1027,21 +1035,19 @@ def is_polhill(int v, int k, int l, int mu): return g # D is a Partial Difference Set of (Z4)^2, see section 2. - G = cartesian_product([IntegerModRing(4),IntegerModRing(4)]) - D = [ - [(2,0),(0,1),(0,3),(1,1),(3,3)], - [(1,0),(3,0),(0,2),(1,3),(3,1)], - [(1,2),(3,2),(2,1),(2,3),(2,2)] - ] + G = cartesian_product([IntegerModRing(4), IntegerModRing(4)]) + D = [[(2, 0), (0, 1), (0, 3), (1, 1), (3, 3)], + [(1, 0), (3, 0), (0, 2), (1, 3), (3, 1)], + [(1, 2), (3, 2), (2, 1), (2, 3), (2, 2)]] D = [[G(e) for e in x] for x in D] # The K_i are hyperplanes partitionning the nonzero elements of # GF(2^s)^2. See section 6. s = 3 G1 = GF(2**s,'x') - Gp = cartesian_product([G1,G1]) - K = [Gp((x,1)) for x in G1]+[Gp((1,0))] - K = [[x for x in Gp if x[0]*uu+x[1]*vv == 0] for (uu,vv) in K] + Gp = cartesian_product([G1, G1]) + K = [Gp((x, 1)) for x in G1] + [Gp((1, 0))] + K = [[x for x in Gp if x[0]*uu + x[1]*vv == 0] for (uu, vv) in K] # We now define the P_{i,j}. see section 6. @@ -1066,15 +1072,15 @@ def is_polhill(int v, int k, int l, int mu): P[2,4] = list(xrange((-1) + 2**(s-2)+3 , 2**(s-1)+1)) + [2**(s-1)+2**(s-2)+1,1] P[3,4] = list(xrange((-1) + 2**(s-1)+3 , 2**(s-1)+2**(s-2)+1)) + [2**(s-2)+1,0] - R = {x:copy(P[x]) for x in P} + R = {x: copy(P[x]) for x in P} for x in P: P[x] = [K[i] for i in P[x]] - P[x] = set(sum(P[x],[])).difference([Gp((0,0))]) + P[x] = set(sum(P[x], [])).difference([Gp((0, 0))]) - P[1,4].add(Gp((0,0))) - P[2,4].add(Gp((0,0))) - P[3,4].add(Gp((0,0))) + P[1, 4].add(Gp((0, 0))) + P[2, 4].add(Gp((0, 0))) + P[3, 4].add(Gp((0, 0))) # We now define the R_{i,j}. see *end* of section 6. @@ -1085,39 +1091,40 @@ def is_polhill(int v, int k, int l, int mu): for x in R: R[x] = [K[i] for i in R[x]] - R[x] = set(sum(R[x],[])).difference([Gp((0,0))]) + R[x] = set(sum(R[x], [])).difference([Gp((0, 0))]) - R[1,3].add(Gp((0,0))) - R[2,4].add(Gp((0,0))) - R[3,4].add(Gp((0,0))) + R[1, 3].add(Gp((0, 0))) + R[2, 4].add(Gp((0, 0))) + R[3, 4].add(Gp((0, 0))) # Dabcd = Da, Db, Dc, Dd (cf. p273) # D1234 = D1, D2, D3, D4 (cf. p276) Dabcd = [] D1234 = [] - Gprod = cartesian_product([G,Gp]) - for DD,PQ in [(Dabcd,P), (D1234,R)]: - for i in range(1,5): - Dtmp = [product([G.zero()],PQ[0,i]), - product(D[0],PQ[1,i]), - product(D[1],PQ[2,i]), - product(D[2],PQ[3,i])] + Gprod = cartesian_product([G, Gp]) + for DD,PQ in [(Dabcd, P), (D1234, R)]: + for i in range(1, 5): + Dtmp = [product([G.zero()], PQ[0, i]), + product(D[0], PQ[1, i]), + product(D[1], PQ[2, i]), + product(D[2], PQ[3, i])] Dtmp = map(set, Dtmp) Dtmp = [Gprod(e) for e in sum(map(list, Dtmp), [])] DD.append(Dtmp) # Now that we have the data, we can return the graphs. if k == 231: - return [lambda :additive_cayley(Dabcd[0])] + return [lambda: additive_cayley(Dabcd[0])] if k == 264: - return [lambda :additive_cayley(D1234[2])] + return [lambda: additive_cayley(D1234[2])] if k == 297: - return [lambda :additive_cayley(D1234[0]+D1234[1]+D1234[2]).complement()] + return [lambda: additive_cayley(D1234[0] + D1234[1] + D1234[2]).complement()] if k == 330: - return [lambda :additive_cayley(Dabcd[0]+Dabcd[1]+Dabcd[2]).complement()] + return [lambda: additive_cayley(Dabcd[0] + Dabcd[1] + Dabcd[2]).complement()] if k == 462: - return [lambda :additive_cayley(Dabcd[0]+Dabcd[1])] + return [lambda: additive_cayley(Dabcd[0] + Dabcd[1])] + def is_RSHCD(int v, int k, int l, int mu): r""" @@ -1143,11 +1150,11 @@ def is_RSHCD(int v, int k, int l, int mu): Graph on 64 vertices sage: g.is_strongly_regular(parameters=True) (64, 27, 10, 12) - """ if SRG_from_RSHCD(v, k, l, mu, existence=True) is True: return [SRG_from_RSHCD, v, k, l, mu] + def SRG_from_RSHCD(v, k, l, mu, existence=False, check=True): r""" Return a `(v,k,l,mu)`-strongly regular graph from a RSHCD @@ -1195,29 +1202,30 @@ def SRG_from_RSHCD(v, k, l, mu, existence=False, check=True): Traceback (most recent call last): ... ValueError: I do not know how to build a (784, 0, 14, 38)-SRG from a RSHCD - """ from sage.combinat.matrices.hadamard_matrix import regular_symmetric_hadamard_matrix_with_constant_diagonal - sgn = lambda x: 1 if x>=0 else -1 + def sgn(x): + return 1 if x >= 0 else -1 n = v a = (n-4*mu)//2 e = 2*k - n + 1 + a t = abs(a//2) - if (e**2 == 1 and - k == (n-1-a+e)/2 and - l == (n-2*a)/4 - (1-e) and - mu== (n-2*a)/4 and - regular_symmetric_hadamard_matrix_with_constant_diagonal(n,sgn(a)*e,existence=True) is True): + if (e**2 == 1 and + k == (n-1-a+e)/2 and + l == (n-2*a)/4 - (1-e) and + mu== (n-2*a)/4 and + regular_symmetric_hadamard_matrix_with_constant_diagonal(n, sgn(a)*e, existence=True) is True): if existence: return True from sage.matrix.constructor import identity_matrix as I - from sage.matrix.constructor import ones_matrix as J + from sage.matrix.constructor import ones_matrix as J - H = regular_symmetric_hadamard_matrix_with_constant_diagonal(n,sgn(a)*e) + H = regular_symmetric_hadamard_matrix_with_constant_diagonal(n, sgn(a)*e) if list(H.column(0)[1:]).count(1) == k: H = -H - G = Graph((J(n)-I(n)-H+H[0,0]*I(n))/2,loops=False,multiedges=False,format="adjacency_matrix") + G = Graph((J(n) - I(n) - H + H[0, 0]*I(n)) / 2, + loops=False, multiedges=False, format="adjacency_matrix") if check: assert G.is_strongly_regular(parameters=True) == (v, k, l, mu) return G @@ -1226,6 +1234,7 @@ def SRG_from_RSHCD(v, k, l, mu, existence=False, check=True): return False raise ValueError("I do not know how to build a {}-SRG from a RSHCD".format((v, k, l, mu))) + @cached_function def is_unitary_polar(int v, int k, int l, int mu): r""" @@ -1273,7 +1282,7 @@ def is_unitary_polar(int v, int k, int l, int mu): q = k//mu if q*mu != k or q < 2: return - p,t = is_prime_power(q, get_data=True) + p, t = is_prime_power(q, get_data=True) if p**t != q or t % 2: return # at this point we know that we should have U(n,q) for some n and q=p^t, t even @@ -1281,25 +1290,26 @@ def is_unitary_polar(int v, int k, int l, int mu): q_pow_d_minus_one = r+1 else: q_pow_d_minus_one = -s-1 - ppp,ttt = is_prime_power(q_pow_d_minus_one, get_data=True) + ppp, ttt = is_prime_power(q_pow_d_minus_one, get_data=True) d = ttt//t + 1 if ppp != p or (d-1)*t != ttt: return t //= 2 # U(2d+1,q); write q^(1/2) as p^t - if (v == (q**d - 1)*((q**d)*p**t + 1)//(q - 1) and - k == q*(q**(d-1) - 1)*((q**d)//(p**t) + 1)//(q - 1) and - l == q*q*(q**(d-2)-1)*((q**(d-1))//(p**t) + 1)//(q - 1) + q - 1): + if (v == (q**d - 1)*((q**d)*p**t + 1)//(q - 1) and + k == q*(q**(d-1) - 1)*((q**d)//(p**t) + 1)//(q - 1) and + l == q*q*(q**(d-2)-1)*((q**(d-1))//(p**t) + 1)//(q - 1) + q - 1): from sage.graphs.generators.classical_geometries import UnitaryPolarGraph return (UnitaryPolarGraph, 2*d+1, p**t) # U(2d,q); - if (v == (q**d - 1)*((q**d)//(p**t) + 1)//(q - 1) and - k == q*(q**(d-1) - 1)*((q**(d-1))//(p**t) + 1)//(q - 1) and - l == q*q*(q**(d-2)-1)*((q**(d-2))//(p**t) + 1)//(q - 1) + q - 1): + if (v == (q**d - 1)*((q**d)//(p**t) + 1)//(q - 1) and + k == q*(q**(d-1) - 1)*((q**(d-1))//(p**t) + 1)//(q - 1) and + l == q*q*(q**(d-2)-1)*((q**(d-2))//(p**t) + 1)//(q - 1) + q - 1): from sage.graphs.generators.classical_geometries import UnitaryPolarGraph return (UnitaryPolarGraph, 2*d, p**t) + @cached_function def is_unitary_dual_polar(int v, int k, int l, int mu): r""" @@ -1339,19 +1349,20 @@ def is_unitary_dual_polar(int v, int k, int l, int mu): q = mu - 1 if q < 2: return - p,t = is_prime_power(q, get_data=True) + p, t = is_prime_power(q, get_data=True) if p**t != q or t % 2: return if (r < 0 and q != -r - 1) or (s < 0 and q != -s - 1): return t //= 2 # we have correct mu, negative eigenvalue, and q=p^(2t) - if (v == (q**2*p**t + 1)*(q*p**t + 1) and - k == q*p**t*(q + 1) and - l == k - 1 - q**2*p**t): + if (v == (q**2*p**t + 1)*(q*p**t + 1) and + k == q*p**t*(q + 1) and + l == k - 1 - q**2*p**t): from sage.graphs.generators.classical_geometries import UnitaryDualPolarGraph return (UnitaryDualPolarGraph, 5, p**t) + @cached_function def is_GQqmqp(int v, int k, int l, int mu): r""" @@ -1410,25 +1421,26 @@ def is_GQqmqp(int v, int k, int l, int mu): """ # do we have GQ(s,t)? we must have mu=t+1, s=l+1, # v=(s+1)(st+1), k=s(t+1) - S=l+1 - T=mu-1 + S = l + 1 + T = mu - 1 q = (S+T)//2 p, w = is_prime_power(q, get_data=True) - if (v == (S+1)*(S*T+1) and - k == S*(T+1) and - q == p**w and - (S+T)//2 == q): + if (v == (S+1)*(S*T+1) and + k == S*(T+1) and + q == p**w and + (S+T)//2 == q): if p % 2 == 0: from sage.graphs.generators.classical_geometries\ import T2starGeneralizedQuadrangleGraph as F else: from sage.graphs.generators.classical_geometries\ import AhrensSzekeresGeneralizedQuadrangleGraph as F - if (S,T) == (q-1, q+1): + if (S, T) == (q-1, q+1): return (F, q, False) - elif (S,T) == (q+1, q-1): + elif (S, T) == (q+1, q-1): return (F, q, True) + @cached_function def is_twograph_descendant_of_srg(int v, int k0, int l, int mu): r""" @@ -1475,23 +1487,23 @@ def is_twograph_descendant_of_srg(int v, int k0, int l, int mu): (279, 150, 85, 75) """ cdef int b, k, s - if k0 != 2*mu or v % 2 == 0: + if k0 != 2*mu or not v % 2: return b = v+1+4*mu D = sqrt(b**2-16*v*mu) - if int(D)==D: + if int(D) == D: for kf in [(-D+b)//4, (D+b)//4]: k = int(kf) - if k == kf and \ - strongly_regular_graph(v+1, k, l - 2*mu + k , k - mu, existence=True) is True: + if (k == kf and + strongly_regular_graph(v+1, k, l - 2*mu + k, k - mu, existence=True) is True): try: - g = strongly_regular_graph_lazy(v+1, k, l - 2*mu + k) # Sage might not know how to build g + g = strongly_regular_graph_lazy(v+1, k, l - 2*mu + k) # Sage might not know how to build g def la(*gr): from sage.combinat.designs.twographs import twograph_descendant gg = g[0](*gr) if (gg.name() is None) or (gg.name() == ''): - gg = Graph(gg, name=str((v+1, k, l - 2*mu + k , k - mu))+"-strongly regular graph") + gg = Graph(gg, name=str((v+1, k, l - 2*mu + k, k - mu))+"-strongly regular graph") return twograph_descendant(gg, next(gg.vertex_iterator()), name=True) return (la, *g[1:]) @@ -1538,7 +1550,7 @@ def is_taylor_twograph_srg(int v, int k, int l, int mu): r, s = eigenvalues(v, k, l, mu) if r is None: return - p,t = is_prime_power(v-1, get_data=True) + p, t = is_prime_power(v-1, get_data=True) if p**t+1 != v or t % 3 != 0 or p % 2 == 0: return q = p**(t//3) @@ -1547,6 +1559,7 @@ def is_taylor_twograph_srg(int v, int k, int l, int mu): return (TaylorTwographSRG, q) return + def is_switch_skewhad(int v, int k, int l, int mu): r""" Test whether some ``switch skewhad^2+*`` is `(v,k,\lambda,\mu)`-strongly regular. @@ -1584,14 +1597,15 @@ def is_switch_skewhad(int v, int k, int l, int mu): r, s = eigenvalues(v, k, l, mu) if r is None: return - if r.switch_OA_srg at ..., 12, 25) sage: is_switch_OA_srg(842, 406, 195, 196) (.switch_OA_srg at ..., 14, 29) - """ cdef int n_2_p_1 = v - cdef int n = floor(sqrt(n_2_p_1-1)) + cdef int n = floor(sqrt(n_2_p_1 - 1)) - if n*n != n_2_p_1-1: # is it a square? + if n*n != n_2_p_1 - 1: # is it a square? return None cdef int c = k//n - if (k % n or - l != c*c-1 or - k != 1+(c-1)*(c+1)+(n-c)*(n-c-1) or - not orthogonal_array(c+1,n,existence=True,resolvable=True) is True): + if (k % n or l != c*c-1 or k != 1+(c-1)*(c+1)+(n-c)*(n-c-1) or + orthogonal_array(c+1, n, existence=True, resolvable=True) is not True): return None def switch_OA_srg(c, n): OA = [tuple(x) for x in orthogonal_array(c+1, n, resolvable=True)] - g = Graph([OA, lambda x,y: any(xx==yy for xx,yy in zip(x,y))], + g = Graph([OA, lambda x, y: any(xx == yy for xx, yy in zip(x, y))], loops=False) g.add_vertex(0) g.seidel_switching(OA[:c*n]) @@ -1692,15 +1703,16 @@ def is_nowhere0_twoweight(int v, int k, int l, int mu): r, s = eigenvalues(v, k, l, mu) if r is None: return - if r 4 and is_prime_power(q) and 0==r%2 and \ - v == r*(q-1)**2 and \ - 4*k == q*(q-2)*(q-3) and \ - 8*mu == q*(q-3)*(q-4): + if (q > 4 and is_prime_power(q) and not r % 2 and + v == r*(q-1)**2 and + 4*k == q*(q-2)*(q-3) and + 8*mu == q*(q-3)*(q-4)): return (Nowhere0WordsTwoWeightCodeGraph, q) + cdef eigenvalues(int v, int k, int l, int mu): r""" Return the eigenvalues of a (v,k,l,mu)-strongly regular graph. @@ -1719,7 +1731,7 @@ cdef eigenvalues(int v, int k, int l, int mu): c = (mu-k) D = b**2-4*c if not is_square(D): - return [None,None] + return [None, None] return [(-b+sqrt(D))/2.0, (-b-sqrt(D))/2.0] @@ -1807,9 +1819,10 @@ def eigenmatrix(int v, int k, int l, int mu): from sage.rings.integer_ring import ZZ r, s = eigenvalues(v, k, l, mu) if r is not None: - return Matrix(ZZ, [[1,k,v-k-1],[1,r,-r-1],[1,s,-s-1]]) + return Matrix(ZZ, [[1, k, v-k-1], [1, r, -r-1], [1, s, -s-1]]) -cpdef latin_squares_graph_parameters(int v,int k, int l,int mu): + +cpdef latin_squares_graph_parameters(int v, int k, int l,int mu): r""" Check whether (v,k,l,mu)-strongly regular graph has parameters of an `L_g(n)` s.r.g. @@ -1839,10 +1852,11 @@ cpdef latin_squares_graph_parameters(int v,int k, int l,int mu): r, s = s, r g = -s n = r+g - if v==n**2 and k==g*(n-1) and l==(g-1)*(g-2)+n-2 and mu==g*(g-1): + if v == n**2 and k == g*(n-1) and l == (g-1)*(g-2)+n-2 and mu == g*(g-1): return g, n return + def _H_3_cayley_graph(L): r""" return the `L`-Cayley graph of the group `H_3` from Prop. 12 in [JK2003]_. @@ -1861,16 +1875,17 @@ def _H_3_cayley_graph(L): from sage.groups.free_group import FreeGroup from sage.groups.finitely_presented import FinitelyPresentedGroup G = FreeGroup('x,y,z') - x,y,z = G.gens() - rels = (x**5,y**5,z**4,x*y*x**(-1)*y**(-1),z*x*z**(-1)*x**(-2),z*y*z**(-1)*y**(-2)) - G = FinitelyPresentedGroup(G,rels) - x,y,z = G.gens() + x, y, z = G.gens() + rels = (x**5, y**5, z**4, x*y*x**(-1)*y**(-1), z*x*z**(-1)*x**(-2), z*y*z**(-1)*y**(-2)) + G = FinitelyPresentedGroup(G, rels) + x, y, z = G.gens() H = G.as_permutation_group() L = [[int(u) for u in x] for x in L] x, y, z = (H.gen(0), H.gen(1), H.gen(2)) L = [H(x**xx*y**yy*z**zz) for xx, yy, zz in L] return Graph(H.cayley_graph(generators=L, simple=True)) + def SRG_100_44_18_20(): r""" Return a `(100, 44, 18, 20)`-strongly regular graph. @@ -1885,11 +1900,13 @@ def SRG_100_44_18_20(): sage: G.is_strongly_regular(parameters=True) # long time (100, 44, 18, 20) """ - return _H_3_cayley_graph(["100","110","130","140","200","230","240","300", - "310","320","400","410","420","440","041","111","221","231","241", - "321","331","401","421","441","002","042","112","122","142","212", - "232","242","322","342","033","113","143","223","303","333","343", - "413","433","443"]) + L = ['100', '110', '130', '140', '200', '230', '240', '300', '310', '320', + '400', '410', '420', '440', '041', '111', '221', '231', '241', '321', + '331', '401', '421', '441', '002', '042', '112', '122', '142', '212', + '232', '242', '322', '342', '033', '113', '143', '223', '303', '333', + '343', '413', '433', '443'] + return _H_3_cayley_graph(L) + def SRG_100_45_20_20(): r""" @@ -1905,11 +1922,12 @@ def SRG_100_45_20_20(): sage: G.is_strongly_regular(parameters=True) # long time (100, 45, 20, 20) """ - return _H_3_cayley_graph(["120","140","200","210","201","401","411","321", - "002","012","022","042","303","403","013","413","240","031","102", - "323","300","231","132","133","310","141","142","233","340","241", - "202","333","410","341","222","433","430","441","242","302","312", - "322","332","442","143"]) + L = ['120', '140', '200', '210', '201', '401', '411', '321', '002', '012', + '022', '042', '303', '403', '013', '413', '240', '031', '102', '323', + '300', '231', '132', '133', '310', '141', '142', '233', '340', '241', + '202', '333', '410', '341', '222', '433', '430', '441', '242', '302', + '312', '322', '332', '442', '143'] + return _H_3_cayley_graph(L) def SRG_105_32_4_12(): @@ -1966,6 +1984,7 @@ def SRG_120_77_52_44(): g.name('PG(2,2)s in PG(2,4)') return g + def SRG_144_39_6_12(): r""" Return a `(144,39,6,12)`-strongly regular graph. @@ -1990,12 +2009,13 @@ def SRG_144_39_6_12(): if len(o) != 39: continue h = Graph() - h.add_edges(G.Orbit([1,o[0]],libgap.OnSets)) + h.add_edges(G.Orbit([1, o[0]], libgap.OnSets)) if h.is_strongly_regular(): h.relabel() h.name('PGL_3(3) on cosets of 13:3') return h + def SRG_176_49_12_14(): r""" Return a `(176,49,12,14)`-strongly regular graph. @@ -2017,7 +2037,7 @@ def SRG_176_49_12_14(): from sage.combinat.designs.database import HigmanSimsDesign d = HigmanSimsDesign() g = d.incidence_graph(labels=True) - ag=g.automorphism_group().conjugacy_classes_representatives() + ag = g.automorphism_group().conjugacy_classes_representatives() # Looking for an involution that maps a point of the design to one of the # blocks that contains it. It is called a polarity with only absolute @@ -2030,10 +2050,11 @@ def SRG_176_49_12_14(): if (aut.order() == 2 and all(i in aut(i) for i in d.ground_set())): g = Graph() - g.add_edges(((u,v) for u in d.ground_set() for v in aut(u)), loops=False) + g.add_edges(((u, v) for u in d.ground_set() for v in aut(u)), loops=False) g.name('Higman symmetric 2-design') return g + def SRG_176_105_68_54(): r""" Return a `(176, 105, 68, 54)`-strongly regular graph. @@ -2058,6 +2079,7 @@ def SRG_176_105_68_54(): g.name('Witt 3-(22,7,4)') return g + def SRG_210_99_48_45(): r""" Return a strongly regular graph with parameters `(210, 99, 48, 45)` @@ -2091,12 +2113,12 @@ def SRG_210_99_48_45(): (7, 3, 1, 4, 5, 6), (7, 2, 4, 3, 5, 6), (7, 3, 2, 4, 5, 1), (7, 2, 4, 3, 5, 1)])) s = libgap.SymmetricGroup(7) - O = s.Orbit(kd[0],libgap.OnSetsTuples) - sa = s.Action(O,libgap.OnSetsTuples) + O = s.Orbit(kd[0], libgap.OnSetsTuples) + sa = s.Action(O, libgap.OnSetsTuples) G = Graph() for g in kd[1:]: - G.add_edges(libgap.Orbit(sa,[libgap.Position(O,kd[0]),\ - libgap.Position(O,g)],libgap.OnSets)) + G.add_edges(libgap.Orbit(sa, [libgap.Position(O, kd[0]), + libgap.Position(O, g)], libgap.OnSets)) G.name('merging of S_7 on Circulant(6,[1,4])s') return G @@ -2126,10 +2148,11 @@ def SRG_243_110_37_60(): from sage.coding.golay_code import GolayCode M = GolayCode(GF(3), False).generator_matrix() V = list(M.right_kernel()) - g = Graph([list(xrange(len(V))), lambda x,y:(V[x]-V[y]).hamming_weight() == 9 ]) + g = Graph([list(xrange(len(V))), lambda x, y: (V[x] - V[y]).hamming_weight() == 9]) g.name('Ternary Golay code') return g + def SRG_253_140_87_65(): r""" Return a `(253, 140, 87, 65)`-strongly regular graph. @@ -2153,6 +2176,7 @@ def SRG_253_140_87_65(): g.name('Witt 4-(23,7,1)') return g + def SRG_196_91_42_42(): r""" Return a `(196,91,42,42)`-strongly regular graph. @@ -2210,6 +2234,7 @@ def SRG_220_84_38_28(): G.name('Tonchev: quasisymmetric 2-(45,9,8)') return G + def SRG_276_140_58_84(): r""" Return a `(276, 140, 58, 84)`-strongly regular graph. @@ -2237,11 +2262,12 @@ def SRG_276_140_58_84(): [20, 75, 98, 239, 267], [21, 33, 56, 113, 240], [23, 127, 152, 164, 172], [25, 101, 128, 183, 264], [27, 129, 154, 160, 201], [28, 126, 144, 161, 228], [29, 100, 133, 204, 266], [30, 108, 146, 200, 219]] g.add_vertex(-1) - g.seidel_switching(sum(C,[])) + g.seidel_switching(sum(C, [])) g.relabel() g.name('Haemers-Tonchev') return g + def SRG_280_135_70_60(): r""" Return a strongly regular graph with parameters `(280, 135, 70, 60)`. @@ -2260,14 +2286,15 @@ def SRG_280_135_70_60(): libgap.load_package("AtlasRep") # A representation of J2 acting on a 3.PGL(2,9) it contains. - J2 = libgap.AtlasGroup("J2", libgap.NrMovedPoints, 280) - edges = J2.Orbit([1,2], libgap.OnSets) - g = Graph() + J2 = libgap.AtlasGroup("J2", libgap.NrMovedPoints, 280) + edges = J2.Orbit([1, 2], libgap.OnSets) + g = Graph() g.add_edges(edges) g.relabel() g.name('J_2 on cosets of 3.PGL(2,9)') return g + def SRG_280_117_44_52(): r""" Return a strongly regular graph with parameters `(280, 117, 44, 52)`. @@ -2292,19 +2319,20 @@ def SRG_280_117_44_52(): from sage.graphs.hypergraph_generators import hypergraphs # V is the set of partitions {{a,b,c},{d,e,f},{g,h,i}} of {0,...,8} - H = hypergraphs.CompleteUniform(9,3) + H = hypergraphs.CompleteUniform(9, 3) g = H.intersection_graph() V = g.complement().cliques_maximal() V = [frozenset(u) for u in V] # G is the graph defined on V in which two vertices are adjacent when they # corresponding partitions cross-intersect on 7 nonempty sets - G = Graph([V, lambda x,y: + G = Graph([V, lambda x, y: sum(any(xxx in yy for xxx in xx) for xx in x for yy in y) != 7], loops=False) G.name('Mathon-Rosa') return G + def strongly_regular_from_two_weight_code(L): r""" Return a strongly regular graph from a two-weight code. @@ -2337,12 +2365,13 @@ def strongly_regular_from_two_weight_code(L): if is_Matrix(L): L = LinearCode(L) V = [tuple(l) for l in L] - w1, w2 = sorted(set(sum(map(bool,x)) for x in V).difference([0])) - G = Graph([V,lambda u,v: sum(uu!=vv for uu,vv in zip(u,v)) == w1]) + w1, w2 = sorted(set(sum(map(bool, x)) for x in V).difference([0])) + G = Graph([V, lambda u, v: sum(uu!=vv for uu, vv in zip(u, v)) == w1]) G.relabel() G.name('two-weight code: '+str(L)) return G + def SRG_416_100_36_20(): r""" Return a `(416,100,36,20)`-strongly regular graph. @@ -2362,13 +2391,14 @@ def SRG_416_100_36_20(): """ from sage.libs.gap.libgap import libgap libgap.load_package("AtlasRep") - g=libgap.AtlasGroup("G2(4)",libgap.NrMovedPoints,416) + g=libgap.AtlasGroup("G2(4)", libgap.NrMovedPoints, 416) h = Graph() - h.add_edges(g.Orbit([1,5],libgap.OnSets)) + h.add_edges(g.Orbit([1, 5],libgap.OnSets)) h.relabel() h.name('G_2(4) on cosets of HS') return h + def SRG_560_208_72_80(): r""" Return a `(560,208,72,80)`-strongly regular graph @@ -2385,17 +2415,18 @@ def SRG_560_208_72_80(): """ from sage.libs.gap.libgap import libgap libgap.load_package("AtlasRep") - g=libgap.AtlasGroup("Sz8",libgap.NrMovedPoints,560) + g=libgap.AtlasGroup("Sz8", libgap.NrMovedPoints, 560) h = Graph() - h.add_edges(g.Orbit([1,2],libgap.OnSets)) - h.add_edges(g.Orbit([1,4],libgap.OnSets)) - h.add_edges(g.Orbit([1,8],libgap.OnSets)) - h.add_edges(g.Orbit([1,27],libgap.OnSets)) + h.add_edges(g.Orbit([1, 2],libgap.OnSets)) + h.add_edges(g.Orbit([1, 4],libgap.OnSets)) + h.add_edges(g.Orbit([1, 8],libgap.OnSets)) + h.add_edges(g.Orbit([1, 27],libgap.OnSets)) h.relabel() h.name('Sz(8)-graph') return h + def strongly_regular_from_two_intersection_set(M): r""" Return a strongly regular graph from a 2-intersection set. @@ -2447,14 +2478,15 @@ def strongly_regular_from_two_intersection_set(M): # For every v point of M for v in M: # u is adjacent with all vertices on a uv line. - g.add_edges([[u,tuple([u[i]+qq*v[i] for i in range(k)])] \ - for qq in K if not qq==K.zero()]) + g.add_edges([[u, tuple([u[i] + qq*v[i] for i in range(k)])] + for qq in K if not qq==K.zero()]) g.relabel() e = QQ((1,k)) qq = g.num_verts()**e g.name('two-intersection set in PG('+str(k)+','+str(qq)+')') return g + def SRG_120_63_30_36(): r""" Return a `(120,63,30,36)`-strongly regular graph @@ -2470,7 +2502,8 @@ def SRG_120_63_30_36(): (120, 63, 30, 36) """ from sage.graphs.generators.families import JohnsonGraph - return JohnsonGraph(10,3).distance_graph([2]) + return JohnsonGraph(10, 3).distance_graph([2]) + def SRG_126_25_8_4(): r""" @@ -2487,7 +2520,8 @@ def SRG_126_25_8_4(): (126, 25, 8, 4) """ from sage.graphs.generators.families import JohnsonGraph - return JohnsonGraph(9,4).distance_graph([1,4]) + return JohnsonGraph(9, 4).distance_graph([1, 4]) + def SRG_175_72_20_36(): r""" @@ -2509,6 +2543,7 @@ def SRG_175_72_20_36(): from sage.graphs.generators.smallgraphs import HoffmanSingletonGraph return HoffmanSingletonGraph().line_graph().distance_graph([2]) + def SRG_176_90_38_54(): r""" Return a `(176,90,38,54)`-strongly regular graph @@ -2533,11 +2568,12 @@ def SRG_176_90_38_54(): g.relabel(range(175)) # c=filter(lambda x: len(x)==5, g.cliques_maximal()) # r=flatten(Hypergraph(c).packing()[:18]) # takes 3s, so we put the answer here - r = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23, - 24,25,28,29,32,38,39,41,42,43,47,49,50,51,52,53,55,57,61,63,65, - 67,69,72,75,77,79,81,84,87,88,89,92,95,96,97,99,101,102,104, - 105,107,112,114,117,118,123,125,129,132,139,140,141,144,146, - 147,153,154,162,165,166,167,170,172,173,174] + r = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 28, 29, 32, 38, 39, 41, 42, 43, 47, 49, 50, 51, + 52, 53, 55, 57, 61, 63, 65, 67, 69, 72, 75, 77, 79, 81, 84, 87, 88, 89, + 92, 95, 96, 97, 99, 101, 102, 104, 105, 107, 112, 114, 117, 118, 123, + 125, 129, 132, 139, 140, 141, 144, 146, 147, 153, 154, 162, 165, 166, + 167, 170, 172, 173, 174] g.add_vertex() g.seidel_switching(r) g.name('a Seidel switching of ' + g.name()) @@ -2592,13 +2628,12 @@ def SRG_126_50_13_24(): from sage.graphs.strongly_regular_db import SRG_175_72_20_36 from sage.graphs.generators.smallgraphs import HoffmanSingletonGraph hs = HoffmanSingletonGraph() - s = set(hs.vertices(sort=False)).difference(hs.neighbors(0)+[0]) - g = SRG_175_72_20_36().subgraph(hs.edge_boundary(s,s)) + s = set(hs.vertices(sort=False)).difference(hs.neighbors(0) + [0]) + g = SRG_175_72_20_36().subgraph(hs.edge_boundary(s, s)) g.name('Goethals graph') return g - def SRG_1288_792_476_504(): r""" Return a `(1288, 792, 476, 504)`-strongly regular graph. @@ -2626,11 +2661,12 @@ def SRG_1288_792_476_504(): for c in C] C = [s for s in C if len(s) == 12] G = Graph([[frozenset(c) for c in C], - lambda x,y: len(x.symmetric_difference(y)) == 12]) + lambda x, y: len(x.symmetric_difference(y)) == 12]) G.relabel() G.name('binary Golay code') return G + cdef bint seems_feasible(int v, int k, int l, int mu): r""" Check if the set of parameters seems feasible. @@ -2649,25 +2685,25 @@ cdef bint seems_feasible(int v, int k, int l, int mu): """ cdef uint_fast32_t tmp[2] - if (v<0 or k<=0 or l<0 or mu<0 or - k>=v-1 or l>=k or mu>k or - v-2*k+mu-2 < 0 or # lambda of complement graph >=0 - v-2*k+l < 0 or # μ of complement graph >= 0 - mu*(v-k-1) != k*(k-l-1)): + if (v < 0 or k <= 0 or l < 0 or mu < 0 or + k >= v - 1 or l >= k or mu > k or + v - 2*k + mu - 2 < 0 or # lambda of complement graph >=0 + v - 2*k + l < 0 or # μ of complement graph >= 0 + mu*(v - k - 1) != k*(k - l - 1)): return False - if mu == k: # complete multipartite graph - r = v//(v-k) # number of parts (of size v-k each) + if mu == k: # complete multipartite graph + r = v//(v-k) # number of parts (of size v-k each) return (l == (v-k)*(r-2) and v == r*(v-k)) - if mu == 0: # the complement of a complete multipartite graph - r = v//(k+1) # number of parts (of size k+1 each) + if mu == 0: # the complement of a complete multipartite graph + r = v//(k+1) # number of parts (of size k+1 each) return (l == k-1 and v == r*(k+1)) # Conference graphs. Only possible if 'v' is a sum of two squares (3.A of # [BL1984]_) if (v-1)*(mu-l)-2*k == 0: - return two_squares_c(v,tmp) + return two_squares_c(v, tmp) rr, ss = eigenvalues(v, k, l, mu) if rr is None: @@ -2682,45 +2718,43 @@ cdef bint seems_feasible(int v, int k, int l, int mu): # Theorem 21.3 of [WilsonACourse] or # 3.B of [BL1984]_ # (Krein conditions) - if ((r+1)*(k+r+2*r*s) > (k+r)*(s+1)**2 or - (s+1)*(k+s+2*r*s) > (k+s)*(r+1)**2): + if (r+1)*(k+r+2*r*s) > (k+r)*(s+1)**2 or (s+1)*(k+s+2*r*s) > (k+s)*(r+1)**2: return False # multiplicity of eigenvalues 'r,s' (f=lambda_r, g=lambda_s) # # They are integers (checked by the 'integrality condition'). f = -k*(s+1)*(k-s)//(mu*(r-s)) - g = k*(r+1)*(k-r)//(mu*(r-s)) - if 1+f+g != v: # the only other eigenvalue, k, has multiplicity 1 + g = k*(r+1)*(k-r)//(mu*(r-s)) + if 1 + f + g != v: # the only other eigenvalue, k, has multiplicity 1 return False # 3.C of [BL1984]_ # (Absolute bound) - if (2*v > f*(f+3) or - 2*v > g*(g+3)): + if 2*v > f*(f+3) or 2*v > g*(g+3): return False # 3.D of [BL1984]_ # (Claw bound) - if (mu != s**2 and - mu != s*(s+1) and - 2*(r+1) > s*(s+1)*(mu+1)): + if (mu != s**2 and + mu != s*(s+1) and + 2*(r+1) > s*(s+1)*(mu+1)): return False # 3.E of [BL1984]_ # (the Case μ=1) if mu == 1: - if ( k % (l+1) or - (v*k) % ((l+1)*(l+2))): + if k % (l+1) or (v*k) % ((l+1)*(l+2)): return False # 3.F of [BL1984]_ # (the Case μ=2) - if mu == 2 and 2*k < l*(l+3) and k%(l+1): + if mu == 2 and 2*k < l*(l + 3) and k % (l + 1): return False return True + def strongly_regular_graph(int v, int k, int l, int mu=-1, bint existence=False, bint check=True): r""" Return a `(v,k,\lambda,\mu)`-strongly regular graph. @@ -2847,7 +2881,7 @@ def strongly_regular_graph(int v, int k, int l, int mu=-1, bint existence=False, Multipartite Graph with set sizes [3, 3]: Graph on 6 vertices """ if mu == -1: - mu = k*(k-l-1)//(v-k-1) + mu = k*(k - l - 1)//(v - k - 1) g = strongly_regular_graph_lazy(v, k, l, mu=mu, existence=existence) if existence is True: return g @@ -2857,6 +2891,7 @@ def strongly_regular_graph(int v, int k, int l, int mu=-1, bint existence=False, raise RuntimeError(f"Sage built an incorrect {params}-SRG.") return G + def strongly_regular_graph_lazy(int v, int k, int l, int mu=-1, bint existence=False): r""" return a promise to build an `(v,k,l,mu)`-srg @@ -2893,10 +2928,10 @@ def strongly_regular_graph_lazy(int v, int k, int l, int mu=-1, bint existence=F """ load_brouwer_database() if mu == -1: - mu = k*(k-l-1)//(v-k-1) + mu = k*(k - l - 1)//(v - k - 1) params = (v, k, l, mu) - params_complement = (v,v-k-1,v-2*k+mu-2,v-2*k+l) + params_complement = (v, v - k - 1, v - 2*k + mu - 2, v - 2*k + l) if not seems_feasible(v, k, l, mu): if existence: @@ -2913,7 +2948,7 @@ def strongly_regular_graph_lazy(int v, int k, int l, int mu=-1, bint existence=F val = _small_srg_database[params_complement] return True if existence else (lambda *t: val[0](*t).complement(), *val[1:]) - test_functions = [is_complete_multipartite, # must be 1st, to prevent 0-divisions + test_functions = [is_complete_multipartite, # must be 1st, to prevent 0-divisions is_paley, is_johnson, is_orthogonal_array_block_graph, is_steiner, is_affine_polar, @@ -2940,7 +2975,7 @@ def strongly_regular_graph_lazy(int v, int k, int l, int mu=-1, bint existence=F if existence: return True ans = f(*params) - return (ans[0],*ans[1:]) + return (ans[0], *ans[1:]) if f(*params_complement): if existence: return True @@ -2952,7 +2987,7 @@ def strongly_regular_graph_lazy(int v, int k, int l, int mu=-1, bint existence=F # We try to return the most appropriate error message. global _brouwer_database - brouwer_data = _brouwer_database.get(params,None) + brouwer_data = _brouwer_database.get(params, None) if brouwer_data is not None: comments = brouwer_data['comments'] @@ -2986,6 +3021,7 @@ def strongly_regular_graph_lazy(int v, int k, int l, int mu=-1, bint existence=F f"Sage cannot figure out if a {params}-strongly " f"regular graph exists.") + def apparently_feasible_parameters(int n): r""" Return a list of a priori feasible parameters `(v,k,\lambda,\mu)`, with `0<\mu 0 and mu < k and seems_feasible(v, k, l, mu): feasible.add((v, k, l, mu)) return feasible + def _build_small_srg_database(): r""" Build the database of small strongly regular graphs. @@ -3106,7 +3142,6 @@ def _build_small_srg_database(): sage: graphs.strongly_regular_graph(1024, 825, 668, 650)# not tested (too long) complement(two-intersection set in PG(10,2)): Graph on 1024 vertices """ - from sage.graphs.generators.smallgraphs import McLaughlinGraph from sage.graphs.generators.smallgraphs import CameronGraph from sage.graphs.generators.smallgraphs import GritsenkoGraph @@ -3125,8 +3160,9 @@ def _build_small_srg_database(): global _small_srg_database _small_srg_database = { - ( 36, 14, 4, 6): [Graph,('c~rLDEOcKTPO`U`HOIj@MWFLQFAaRIT`HIWqPsQQJ'+ - 'DXGLqYM@gRLAWLdkEW@RQYQIErcgesClhKefC_ygSGkZ`OyHETdK[?lWStCapVgKK')], + ( 36, 14, 4, 6): [Graph, ('c~rLDEOcKTPO`U`HOIj@MWFLQFAaRIT`HIWqPsQQJ' + 'DXGLqYM@gRLAWLdkEW@RQYQIErcgesClhKefC_ygS' + 'GkZ`OyHETdK[?lWStCapVgKK')], ( 50, 7, 0, 1): [HoffmanSingletonGraph], ( 56, 10, 0, 2): [SimsGewirtzGraph], ( 65, 32, 15, 16): [GritsenkoGraph], @@ -3172,30 +3208,30 @@ def _build_small_srg_database(): # Turns the known two-weight codes into SRG constructors # - cdef int n,q,k,w1,w2,K,N,l,m,K_O,l_O,m_O + cdef int n, q, k, w1, w2, K, N, l, m, K_O, l_O, m_O import sage.coding.two_weight_db from sage.matrix.constructor import matrix from sage.rings.integer_ring import ZZ - cinv = matrix(ZZ, [[1,0,0],[0,0,1],[0,1,0]]) + cinv = matrix(ZZ, [[1, 0, 0], [0, 0, 1], [0, 1, 0]]) for code in sage.coding.two_weight_db.data: - n,q,k,w1,w2 = code['n'], code['K'].cardinality(), code['k'], code['w1'], code['w2'] + n, q, k, w1, w2 = code['n'], code['K'].cardinality(), code['k'], code['w1'], code['w2'] N = q**k - K_O = n*(q-1) - l_O = K_O**2+3*K_O-q*(w1+w2)-K_O*q*(w1+w2)+w1*w2*q**2 + K_O = n*(q - 1) + l_O = K_O**2 + 3*K_O - q*(w1 + w2) - K_O*q*(w1 + w2) + w1*w2*q**2 m_O = (w1*w2*q**2)//N - em = eigenmatrix(N,K_O,l_O,m_O) # 1st eigenmatrix - assert((not em is None) and (em.det() != 0)) - emi = N*em.inverse() # 2nd eigenmatrix + em = eigenmatrix(N, K_O, l_O, m_O) # 1st eigenmatrix + assert((em is not None) and (em.det() != 0)) + emi = N*em.inverse() # 2nd eigenmatrix # 1st and 2nd eigenmatrices equal up to renumbering graphs? - selfdual = em==cinv*emi*cinv - _small_srg_database[N,K_O,l_O,m_O] = \ + selfdual = em == cinv*emi*cinv + _small_srg_database[N, K_O, l_O, m_O] = \ [lambda x: strongly_regular_from_two_intersection_set(x.transpose()), code['M']] - if not selfdual: # we can build two graphs (not complements to each other!) - K, s, r = emi[0,1], emi[1,1], emi[2,1] # by Thm 5.7 in [CK1986]_. - l = K+r*s+r+s - m = K+r*s - _small_srg_database[N,K,l,m] = [strongly_regular_from_two_weight_code, code['M']] + if not selfdual: # we can build two graphs (not complements to each other!) + K, s, r = emi[0, 1], emi[1, 1], emi[2, 1] # by Thm 5.7 in [CK1986]_. + l = K + r*s + r + s + m = K + r*s + _small_srg_database[N, K, l, m] = [strongly_regular_from_two_weight_code, code['M']] cdef load_brouwer_database(): @@ -3257,8 +3293,8 @@ def _check_database(): _brouwer_database, saved_database = {}, _brouwer_database cdef int missed = 0 - for params,dic in sorted(saved_database.items()): - sage_answer = strongly_regular_graph(*params,existence=True) + for params, dic in sorted(saved_database.items()): + sage_answer = strongly_regular_graph(*params, existence=True) if dic['status'] == 'open': if sage_answer is True: print("Sage can build a {}, Brouwer's database cannot".format(params)) @@ -3267,13 +3303,13 @@ def _check_database(): if sage_answer is not True: print(("Sage cannot build a ({:<4} {:<4} {:<4} {:<4}) that exists. " + "Comment from Brouwer's database: ").format(*params) - + dic['comments']) + + dic['comments']) missed += 1 assert sage_answer is not False elif dic['status'] == 'impossible': assert sage_answer is not True else: - assert False # must not happen + assert False # must not happen status = [x['status'] for x in saved_database.values()] print("\nIn Andries Brouwer's database:") diff --git a/src/sage/graphs/weakly_chordal.pyx b/src/sage/graphs/weakly_chordal.pyx index 983155f3cdc..45c692d8955 100644 --- a/src/sage/graphs/weakly_chordal.pyx +++ b/src/sage/graphs/weakly_chordal.pyx @@ -225,7 +225,7 @@ def is_long_hole_free(g, certificate=False): bitset_add(dense_graph, u * n + v) bitset_add(dense_graph, v * n + u) - # Allocate some data strutures + # Allocate some data structures cdef MemoryAllocator mem = MemoryAllocator() cdef int* path = mem.allocarray(n, sizeof(int)) cdef int path_top @@ -456,7 +456,7 @@ def is_long_antihole_free(g, certificate=False): bitset_add(dense_graph, u * n + v) bitset_add(dense_graph, v * n + u) - # Allocate some data strutures + # Allocate some data structures cdef MemoryAllocator mem = MemoryAllocator() cdef int* path = mem.allocarray(n, sizeof(int)) cdef int path_top diff --git a/src/sage/groups/abelian_gps/abelian_group.py b/src/sage/groups/abelian_gps/abelian_group.py index cc94e2b568e..8a6afa76bbb 100644 --- a/src/sage/groups/abelian_gps/abelian_group.py +++ b/src/sage/groups/abelian_gps/abelian_group.py @@ -1170,7 +1170,7 @@ def random_element(self): result = self.one() for g in self.gens(): order = g.order() - if order not in ZZ: + if order is infinity: order = 42 # infinite order; randomly chosen maximum result *= g**(randint(0,order)) return result @@ -1683,56 +1683,26 @@ def __init__(self, ambient, gens, names="f", category=None): sage: G.subgroup([prod(g^k for g,k in zip(G.gens(),[1,-2,3,-4,5]))]) Multiplicative Abelian subgroup isomorphic to Z generated by {f0*f1^-2*f2^3*f3^-4*f4} """ - from sage.interfaces.gap import gap + from sage.libs.gap.libgap import libgap if not isinstance(ambient, AbelianGroup_class): - raise TypeError("ambient (=%s) must be an abelian group."%ambient) + raise TypeError("ambient (=%s) must be an abelian group" % ambient) if not isinstance(gens, tuple): - raise TypeError("gens (=%s) must be a tuple"%gens) + raise TypeError("gens (=%s) must be a tuple" % gens) self._ambient_group = ambient - Hgens = tuple(x for x in gens if x != ambient.one()) ## in case someone puts 1 in the list of generators - self._gens = Hgens - ambient_invs = ambient.gens_orders() - invsf = [x for x in ambient_invs if x > 0] ## fixes the problem with - invs0 = [x for x in ambient_invs if x == 0] ## the infinite parts - Ggens = list(ambient.variable_names()) - if invs0!=[]: - Gfgens = [x for x in ambient.variable_names() if - ambient_invs[Ggens.index(x)] != 0] - Gf = AbelianGroup(invsf, names=Gfgens) - s1 = "G:= %s; gens := GeneratorsOfGroup(G)"%Gf._gap_init_() - gap.eval(s1) - Hgens0 = [ - x for x in Hgens if - any(e!=0 and (g.order() not in ZZ) for e,g in zip(x.exponents(),ambient.gens())) - ] - Hgensf = [x for x in Hgens if x not in Hgens0] - # the "infinite" generators of H - for i in range(len(Gfgens)): - cmd = ("%s := gens["+str(i+1)+"]")%Gfgens[i] - gap.eval(cmd) - else: # invs0==[]: - Hgensf = Hgens - Hgens0 = [] # added for consistency - G = ambient - s1 = "G:= %s; gens := GeneratorsOfGroup(G)"%G._gap_init_() - gap.eval(s1) - for i in range(len(Ggens)): - cmd = '%s := gens[%s]'%(Ggens[i], i+1) - gap.eval(cmd) - s2 = "gensH:=%s"%list(Hgensf) #### remove from this the ones <--> 0 invar - gap.eval(s2) - s3 = 'H:=Subgroup(G,gensH)' - gap.eval(s3) - # a GAP command which returns the "invariants" of the - # subgroup as an AbelianPcpGroup, RelativeOrdersOfPcp(Pcp(G)), - # works if G is the subgroup declared as a AbelianPcpGroup - self._abinvs = eval(gap.eval("AbelianInvariants(H)")) - invs = self._abinvs - if Hgens0 != []: - for x in Hgens0: - invs.append(0) + H_gens = tuple(x for x in gens if x != ambient.one()) # clean entry + self._gens = H_gens + + H = libgap(ambient).Subgroup(H_gens) + + invs = H.TorsionSubgroup().AbelianInvariants().sage() + rank = len([1 for g in H.GeneratorsOfGroup() + if g.Order().sage() is infinity]) + invs += [0] * rank + + self._abinvs = invs invs = tuple(ZZ(i) for i in invs) + if category is None: category = Groups().Commutative().Subobjects() AbelianGroup_class.__init__(self, invs, names, category=category) @@ -1778,7 +1748,7 @@ def __contains__(self, x): return False if x.parent() is self: return True - elif x in self.ambient_group(): + if x in self.ambient_group(): amb_inv = self.ambient_group().gens_orders() inv_basis = diagonal_matrix(ZZ, amb_inv) gens_basis = matrix( @@ -1870,7 +1840,7 @@ def _repr_(self): sage: A._repr_() 'Multiplicative Abelian subgroup isomorphic to Z generated by {a}' """ - eldv = self.gens_orders() + eldv = self._abinvs if self.is_trivial(): return "Trivial Abelian subgroup" s = 'Multiplicative Abelian subgroup isomorphic to ' diff --git a/src/sage/groups/abelian_gps/abelian_group_morphism.py b/src/sage/groups/abelian_gps/abelian_group_morphism.py index 400283827b1..718a0d50f57 100644 --- a/src/sage/groups/abelian_gps/abelian_group_morphism.py +++ b/src/sage/groups/abelian_gps/abelian_group_morphism.py @@ -81,38 +81,38 @@ class AbelianGroupMorphism(Morphism): #Traceback (most recent call last): # File "", line 1, in ? # File ".abeliangp_hom.sage.py", line 737, in __init__ -# raise TypeError, "Sorry, the orders of the corresponding elements in %s, %s must be equal."%(genss,imgss) -#TypeError: Sorry, the orders of the corresponding elements in [a*b, a*c], [x, y] must be equal. +# raise TypeError("the orders of the corresponding elements in %s, %s must be equal" % (genss,imgss)) +#TypeError: the orders of the corresponding elements in [a*b, a*c], [x, y] must be equal # # sage: phi = AbelianGroupMorphism_im_gens(G,H,[a*b,(a*c)^2],[x*y,y]) #------------------------------------------------------------ #Traceback (most recent call last): # File "", line 1, in ? # File ".abeliangp_hom.sage.py", line 730, in __init__ -# raise TypeError, "Sorry, the list %s must generate G."%genss -#TypeError: Sorry, the list [a*b, c^2] must generate G. +# raise TypeError("the list %s must generate G" % genss) +#TypeError: the list [a*b, c^2] must generate G def __init__(self, G, H, genss, imgss): from sage.categories.homset import Hom Morphism.__init__(self, Hom(G, H)) if len(genss) != len(imgss): - raise TypeError("Sorry, the lengths of %s, %s must be equal." % (genss, imgss)) + raise TypeError("the lengths of %s, %s must be equal" % (genss, imgss)) self._domain = G self._codomain = H - if not(G.is_abelian()): - raise TypeError("Sorry, the groups must be abelian groups.") - if not(H.is_abelian()): - raise TypeError("Sorry, the groups must be abelian groups.") + if not G.is_abelian(): + raise TypeError("the groups must be abelian groups") + if not H.is_abelian(): + raise TypeError("the groups must be abelian groups") G_domain = G.subgroup(genss) if G_domain.order() != G.order(): - raise TypeError("Sorry, the list %s must generate G." % genss) + raise TypeError("the list %s must generate G" % genss) # self.domain_invs = G.gens_orders() # self.codomaininvs = H.gens_orders() self.domaingens = genss self.codomaingens = imgss for i in range(len(self.domaingens)): if (self.domaingens[i]).order() != (self.codomaingens[i]).order(): - raise TypeError("Sorry, the orders of the corresponding elements in %s, %s must be equal." % (genss, imgss)) + raise TypeError("the orders of the corresponding elements in %s, %s must be equal" % (genss, imgss)) def _libgap_(self): """ diff --git a/src/sage/groups/abelian_gps/dual_abelian_group.py b/src/sage/groups/abelian_gps/dual_abelian_group.py index df2b51e0d57..a1650f4eab0 100644 --- a/src/sage/groups/abelian_gps/dual_abelian_group.py +++ b/src/sage/groups/abelian_gps/dual_abelian_group.py @@ -63,11 +63,10 @@ # http://www.gnu.org/licenses/ ########################################################################### -from sage.rings.infinity import infinity from sage.structure.category_object import normalize_names from sage.structure.unique_representation import UniqueRepresentation from sage.groups.abelian_gps.dual_abelian_group_element import ( - DualAbelianGroupElement, is_DualAbelianGroupElement ) + DualAbelianGroupElement, is_DualAbelianGroupElement) from sage.misc.mrange import mrange from sage.misc.cachefunc import cached_method from sage.groups.group import AbelianGroup as AbelianGroupBase @@ -126,7 +125,7 @@ def __init__(self, G, names, base_ring): self._group = G names = normalize_names(G.ngens(), names) self._assign_names(names) - AbelianGroupBase.__init__(self) # TODO: category=CommutativeGroups() + AbelianGroupBase.__init__(self) # TODO: category=CommutativeGroups() def group(self): """ @@ -165,7 +164,7 @@ def __str__(self): sage: print(Fd) DualAbelianGroup( AbelianGroup ( 3, (5, 64, 729) ) ) """ - s = "DualAbelianGroup( AbelianGroup ( %s, %s ) )"%(self.ngens(), self.gens_orders()) + s = "DualAbelianGroup( AbelianGroup ( %s, %s ) )" % (self.ngens(), self.gens_orders()) return s def _repr_(self): @@ -188,9 +187,9 @@ def _repr_(self): eldv = G.gens_orders() gp = "" for x in eldv: - if x!=0: - gp = gp + "Z/%sZ x "%x - if x==0: + if x != 0: + gp = gp + "Z/%sZ x " % x + if x == 0: gp = gp + "Z x " gp = gp[:-2].strip() s = 'Dual of Abelian Group isomorphic to ' + gp + ' over ' + str(self.base_ring()) @@ -235,7 +234,7 @@ def random_element(self): result = self.one() for g in self.gens(): order = g.order() - result *= g**(randint(0,order)) + result *= g**(randint(0, order)) return result def gen(self, i=0): @@ -255,8 +254,8 @@ def gen(self, i=0): """ n = self.group().ngens() if i < 0 or i >= n: - raise IndexError("Argument i (= %s) must be between 0 and %s."%(i, n-1)) - x = [0]*n + raise IndexError("Argument i (= %s) must be between 0 and %s." % (i, n - 1)) + x = [0] * n if self.gens_orders()[i] != 1: x[i] = 1 return self.element_class(self, x) @@ -324,7 +323,7 @@ def invariants(self): # TODO: deprecate return self.group().gens_orders() - def __contains__(self,X): + def __contains__(self, X): """ Implements "in". @@ -380,8 +379,8 @@ def list(self): sage: Gd.list() (1, B, B^2, A, A*B, A*B^2) """ - if not(self.is_finite()): - raise NotImplementedError("Group must be finite") + if not self.is_finite(): + raise NotImplementedError("the group must be finite") invs = self.gens_orders() return tuple(self(t) for t in mrange(invs)) diff --git a/src/sage/groups/abelian_gps/element_base.py b/src/sage/groups/abelian_gps/element_base.py index d5b93a3f918..c1eec7ecb1f 100644 --- a/src/sage/groups/abelian_gps/element_base.py +++ b/src/sage/groups/abelian_gps/element_base.py @@ -15,7 +15,7 @@ # Copyright (C) 2012 Volker Braun # # Distributed under the terms of the GNU General Public License (GPL) -# http://www.gnu.org/licenses/ +# https://www.gnu.org/licenses/ ########################################################################### from sage.structure.element import MultiplicativeGroupElement @@ -298,14 +298,14 @@ def __pow__(self, n): for e,order in zip(self._exponents, G.gens_orders()) ] return G.element_class(G, exponents) - def inverse(self): + def __invert__(self): """ - Returns the inverse element. + Return the inverse element. EXAMPLES:: sage: G. = AbelianGroup([0,5]) - sage: a.inverse() + sage: a.inverse() # indirect doctest a^-1 sage: a.__invert__() a^-1 @@ -319,12 +319,10 @@ def inverse(self): (-1, 4) """ G = self.parent() - exponents = [ (-e)%order if order!=0 else -e - for e,order in zip(self._exponents, G.gens_orders()) ] + exponents = [(-e) % order if order != 0 else -e + for e, order in zip(self._exponents, G.gens_orders())] return G.element_class(G, exponents) - __invert__ = inverse - def is_trivial(self): """ Test whether ``self`` is the trivial group element ``1``. diff --git a/src/sage/groups/abelian_gps/values.py b/src/sage/groups/abelian_gps/values.py index 27e91cf30ed..e476176cb7d 100644 --- a/src/sage/groups/abelian_gps/values.py +++ b/src/sage/groups/abelian_gps/values.py @@ -329,14 +329,14 @@ def __pow__(self, n): pow_self._value = pow(self.value(), m) return pow_self - def inverse(self): + def __invert__(self): """ Return the inverse element. EXAMPLES:: sage: G. = AbelianGroupWithValues([2,-1], [0,4]) - sage: a.inverse() + sage: a.inverse() # indirect doctest a^-1 sage: a.inverse().value() 1/2 @@ -349,13 +349,10 @@ def inverse(self): sage: (a*b).inverse().value() -1/2 """ - m = AbelianGroupElement.inverse(self) + m = AbelianGroupElement.__invert__(self) m._value = ~self.value() return m - __invert__ = inverse - - class AbelianGroupWithValues_class(AbelianGroup_class): """ diff --git a/src/sage/groups/additive_abelian/additive_abelian_group.py b/src/sage/groups/additive_abelian/additive_abelian_group.py index 0a326d42c60..3efc1d7621c 100644 --- a/src/sage/groups/additive_abelian/additive_abelian_group.py +++ b/src/sage/groups/additive_abelian/additive_abelian_group.py @@ -11,7 +11,8 @@ from sage.modules.fg_pid.fgp_element import FGP_Element from sage.rings.integer_ring import ZZ -def AdditiveAbelianGroup(invs, remember_generators = True): + +def AdditiveAbelianGroup(invs, remember_generators=True): r""" Construct a finitely-generated additive abelian group. diff --git a/src/sage/groups/additive_abelian/additive_abelian_wrapper.py b/src/sage/groups/additive_abelian/additive_abelian_wrapper.py index 87bbfcb938e..7c0aec05ff8 100644 --- a/src/sage/groups/additive_abelian/additive_abelian_wrapper.py +++ b/src/sage/groups/additive_abelian/additive_abelian_wrapper.py @@ -465,7 +465,7 @@ def _element_constructor_(self, x, check=False): (6, 2) """ if parent(x) is self.universe(): - return self.element_class(self, self.discrete_log(x), element = x) + return self.element_class(self, self.discrete_log(x), element=x) return addgp.AdditiveAbelianGroup_fixed_gens._element_constructor_(self, x, check) diff --git a/src/sage/groups/affine_gps/group_element.py b/src/sage/groups/affine_gps/group_element.py index bc0bf5c702e..cb5fd945239 100644 --- a/src/sage/groups/affine_gps/group_element.py +++ b/src/sage/groups/affine_gps/group_element.py @@ -455,7 +455,7 @@ def _act_on_(self, x, self_on_left): if self_on_left: return self(x) - def inverse(self): + def __invert__(self): """ Return the inverse group element. @@ -473,19 +473,17 @@ def inverse(self): sage: ~g [1 1] [1] x |-> [0 1] x + [0] - sage: g * g.inverse() + sage: g * g.inverse() # indirect doctest [1 0] [0] x |-> [0 1] x + [0] sage: g * g.inverse() == g.inverse() * g == G(1) True """ parent = self.parent() - A = parent.matrix_space()(self._A.inverse()) - b = -A*self.b() + A = parent.matrix_space()(~self._A) + b = -A * self.b() return parent.element_class(parent, A, b, check=False) - __invert__ = inverse - def _richcmp_(self, other, op): """ Compare ``self`` with ``other``. diff --git a/src/sage/groups/cubic_braid.py b/src/sage/groups/cubic_braid.py index 295d43aa1d2..90f8e07ba2c 100644 --- a/src/sage/groups/cubic_braid.py +++ b/src/sage/groups/cubic_braid.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- r""" Cubic Braid Groups @@ -7,7 +6,7 @@ .. MATH:: - s_i^3 = 1 + s_i^3 = 1. In general these groups have firstly been investigated by Coxeter, H.S.M. in: "Factor groups of the braid groups, Proceedings of the Fourth Canadian @@ -22,13 +21,13 @@ `G_{32}`. Coxeter realized these groups as subgroups of unitary groups with respect -to a certain hermitian form over the complex numbers (in fact over `\QQ` +to a certain Hermitian form over the complex numbers (in fact over `\QQ` adjoined with a primitive 12-th root of unity). In "Einige endliche Faktorgruppen der Zopfgruppen" (Math. Z., 163 (1978), 291-302) J. Assion considered two series `S(m)` and `U(m)` of finite factors of these groups. The additional relations on the braid group -generators `\{ s_1, \cdot , s_{m-1}\}` are +generators `\{ s_1, \cdots, s_{m-1}\}` are .. MATH:: @@ -46,15 +45,14 @@ as symplectic groups over `GF(3)` (resp. subgroups therein) and `U(m)` as general unitary groups over `GF(4)` (resp. subgroups therein). -This class implements all the groups considered by Coxeter and Assion as -finitely presented groups together with the classical realizations given -by the authors. It also contains the conversion maps between the two ways -of realization. In addition the user can construct other realizations and -maps to matrix groups with help of the Burau representation. In case gap3 -and CHEVIE are installed the reflection groups (via the gap3 interface) -are available, too. The methods for all this functionality are -:meth:`as_classical_group`, :meth:`as_matrix_group`, :meth:`as_permutation_group` -and :meth:`as_reflection_group`. +All the groups considered by Coxeter and Assion are considered as finitely +presented groups together with the classical realizations. It also allows +for the conversion maps between the two realizations. In addition, we can +construct other realizations and maps to matrix groups with help of the +Burau representation. In case ``gap3`` and ``CHEVIE`` are installed, the +reflection groups (via the ``gap3`` interface) are available, too. This can +be done using the methods :meth:`as_classical_group`, :meth:`as_matrix_group`, +:meth:`as_permutation_group`, and :meth:`as_reflection_group`. TESTS:: @@ -116,11 +114,11 @@ def _reduce_tietze(tietze_list): """ def eliminate_item(tietze_list): """ - this sub method searches for an item in the Tietze expression such + This sub method searches for an item in the Tietze expression such that it together with the first entry gives a pair which can be replaced by the second braid relation and the generators degree - reduction. If no such pair exists, it returns None. Else-wise the - reduced tietze list is returned. + reduction. If no such pair exists, it returns ``None``. Otherwise + the reduced tietze list is returned. """ l = len(tietze_list) if l < 2: @@ -170,20 +168,19 @@ def eliminate_item(tietze_list): # ---------------------------------------------------------------------------------- def AssionGroupS(n=None, names='s'): r""" - Construct cubic braid groups as instance of :class:`CubicBraidGroup` which have been + Construct cubic braid groups :class:`CubicBraidGroup` which have been investigated by J.Assion using the notation S(m). This function is a short hand cut for setting the construction arguments ``cbg_type=CubicBraidGroup.type.AssionS`` and default ``names='s'``. - For more information type ``CubicBraidGroup?`` - INPUT: - - ``n`` -- integer or None (default). The number of strands. This argument is passed - to the corresponding argument of the classcall of :class:`CubicBraidGroup`. + - ``n`` -- integer (optional); the number of strands + - ``names`` -- (default: ``'s'``) string or list/tuple/iterable of strings + + .. SEEALSO:: - - ``names`` -- string or list/tuple/iterable of strings (default:'s'). This argument is - passed to the corresponding argument of the classcall of :class:`CubicBraidGroup`. + :class:`CubicBraidGroup` EXAMPLES:: @@ -194,7 +191,7 @@ def AssionGroupS(n=None, names='s'): sage: S3 == S3x True """ - return CubicBraidGroup(n = n, names = names, cbg_type=CubicBraidGroup.type.AssionS) + return CubicBraidGroup(n=n, names=names, cbg_type=CubicBraidGroup.type.AssionS) def AssionGroupU(n=None, names='u'): @@ -204,15 +201,14 @@ def AssionGroupU(n=None, names='u'): for setting the construction arguments ``cbg_type=CubicBraidGroup.type.AssionU`` and default ``names='u'``. - For more information type ``CubicBraidGroup?`` - INPUT: - - ``n`` -- integer or None (default). The number of strands. This argument is passed - to the corresponding argument of the classcall of :class:`CubicBraidGroup`. + - ``n`` -- integer (optional); the number of strands + - ``names`` -- (default: ``'s'``) string or list/tuple/iterable of strings + + .. SEEALSO:: - - ``names`` -- string or list/tuple/iterable of strings (default:'u'). This argument is - passed to the corresponding argument of the classcall of :class:`CubicBraidGroup`. + :class:`CubicBraidGroup` EXAMPLES:: @@ -222,10 +218,8 @@ def AssionGroupU(n=None, names='u'): Assion group on 3 strands of type U sage: U3 == U3x True - """ - return CubicBraidGroup(n = n, names = names, cbg_type=CubicBraidGroup.type.AssionU) - + return CubicBraidGroup(n=n, names=names, cbg_type=CubicBraidGroup.type.AssionU) ############################################################################## @@ -233,13 +227,12 @@ def AssionGroupU(n=None, names='u'): # Class CubicBraidElement (for elements) # ############################################################################## + class CubicBraidElement(FinitelyPresentedGroupElement): r""" - This class models elements of cubic factor groups of the braid group. - It is the element class of the CubicBraidGroup. + Elements of cubic factor groups of the braid group. - For more information see the documentation of the parent - :class:`CubicBraidGroup`. + For more information see :class:`CubicBraidGroup`. EXAMPLES:: @@ -250,11 +243,9 @@ class CubicBraidElement(FinitelyPresentedGroupElement): sage: ele1 == ele2 True """ - def __init__(self, parent, x, check=True): """ - The Python constructor. - It is overloaded to achieve a reduction of the Tietze expression + Initialize ``self``. EXAMPLES:: @@ -277,10 +268,11 @@ def _richcmp_(self, other, op): """ Rich comparison of ``self`` with ``other``. - Overwrite comparison since the inherited one from :class:`FinitelyPresentedGroupElement` - (via Gap) does not terminate in the case of more than 5 strands (not - only infinite cases). On less than 5 strands comparison is not assumed - to be random free (see the :trac:`33498` and section 47.3-2 of the + Overwrite comparison since the inherited one from + :class:`FinitelyPresentedGroupElement` (via Gap) does not terminate + in the case of more than 5 strands (not only infinite cases). + On less than 5 strands comparison is not assumed to be deterministic + (see the :trac:`33498` and section 47.3-2 of the `Gap Reference manual `__). Therefore, the comparison is done via the Burau representation. @@ -313,7 +305,6 @@ def _richcmp_(self, other, op): omat = other._matrix_() return smat._richcmp_(omat, op) - def __hash__(self): r""" Return a hash value for ``self``. @@ -329,10 +320,11 @@ def __hash__(self): @cached_method def _matrix_(self): r""" - Return self as a matrix group element according to its parent's default matrix group. - So far this method returns the same results as :meth:`burau_matrix` in the default - case. But its behavior with respect to performance is different: The first invocation - for a group will be slower but all successive ones will be faster! + Return ``self`` as a matrix according to its parent's default matrix + group. + + So far, this method returns the same results as :meth:`burau_matrix` + in the default case. EXAMPLES:: @@ -345,15 +337,9 @@ def _matrix_(self): mat_grp = self.parent().as_matrix_group() return mat_grp(self).matrix() - def braid(self): r""" - Return the canonical braid preimage of ``self`` as Object of the - class :class:`Braid`. - - OUTPUT: - - The preimage of ``self`` as instance of :class:`Braid`. + Return the canonical braid preimage of ``self`` as a :class:`Braid`. EXAMPLES:: @@ -363,40 +349,39 @@ class :class:`Braid`. sage: c1.braid().parent() Braid group on 3 strands """ - braid_group = self.parent().braid_group() return braid_group(self) - @cached_method - def burau_matrix(self, root_bur = None, domain = None, characteristic = None, var='t', reduced=False): + def burau_matrix(self, root_bur=None, domain=None, characteristic=None, + var='t', reduced=False): r""" Return the Burau matrix of the cubic braid coset. This method uses the same method belonging to :class:`Braid`, but reduces the indeterminate to a primitive sixth (resp. twelfth in case - reduced='unitary') root of unity. + ``reduced='unitary'``) root of unity. INPUT (all arguments are optional keywords): - ``root_bur`` -- six (resp. twelfth) root of unity in some field - (default root of unity over `\QQ`). - - ``domain`` -- base_ring for the Burau matrix (default is Cyclotomic - Field of order 3 and degree 2, resp. the domain of `root_bur` if given). - - ``characteristic`` - integer giving the characteristic of the - domain (default is 0 or the characteristic of `domain` if given). - - ``var`` -- string used for the indeterminate name in case root_bur - must be constructed in a splitting field. - - ``reduced`` -- boolean (default: ``False``) or string; for more + (default: root of unity over `\QQ`) + - ``domain`` -- (default: cyclotomic field of order 3 and degree 2, resp. + the domain of `root_bur` if given) base ring for the Burau matrix + - ``characteristic`` -- integer giving the characteristic of the + domain (default: 0 or the characteristic of ``domain`` if given) + - ``var`` -- string used for the indeterminate name in case ``root_bur`` + must be constructed in a splitting field + - ``reduced`` -- boolean or string (default: ``False``); for more information see the documentation of :meth:`burau_matrix` of - :class:`Braid`. + :class:`Braid` OUTPUT: The Burau matrix of the cubic braid coset with entries in the - domain given by the options. In case the option `reduced='unitary'` + domain given by the options. In case the option ``reduced='unitary'`` is given a triple consisting of the Burau matrix, its adjoined and - the hermitian form is returned. + the Hermitian form is returned. EXAMPLES:: @@ -465,7 +450,7 @@ def burau_matrix(self, root_bur = None, domain = None, characteristic = None, va from sage.misc.functional import cyclotomic_polynomial min_pol_root_bur = cyclotomic_polynomial(6, var=var) unitary = False - if type(reduced) == str: + if isinstance(reduced, str): if reduced == 'unitary': unitary = True min_pol_root_bur = cyclotomic_polynomial(12, var=var) @@ -487,7 +472,7 @@ def burau_matrix(self, root_bur = None, domain = None, characteristic = None, va def find_root(domain): min_pol = min_pol_root_bur.change_ring(domain) root_list = min_pol.roots() - if not(root_list): + if not root_list: domain = min_pol.splitting_field(min_pol_root_bur.variable_name()) min_pol = min_pol_root_bur.change_ring(domain) root_list = min_pol.roots() @@ -501,13 +486,13 @@ def find_root(domain): if domain is None: - if (characteristic is None): + if characteristic is None: # -------------------------------------------------------------------- # setting the default characteristic in order to achieve the according # representations being well defined # -------------------------------------------------------------------- cbg_type = self.parent()._cbg_type - if cbg_type == CubicBraidGroup.type.AssionS: + if cbg_type == CubicBraidGroup.type.AssionS: characteristic = 3 # making Assion type S relations vanish elif cbg_type == CubicBraidGroup.type.AssionU: characteristic = 2 # making Assion type U relations vanish @@ -572,9 +557,8 @@ def conv2domain (laur_pol): ############################################################################## class CubicBraidGroup(FinitelyPresentedGroup): r""" - This class implements factor groups of the Artin braid group mapping - their generators to elements of order 3 (see the module header for more - information on these groups). + Factor groups of the Artin braid group mapping their generators to elements + of order 3. These groups are implemented as a particular case of finitely presented groups similar to the :class:`BraidGroup_class`. @@ -583,35 +567,37 @@ class CubicBraidGroup(FinitelyPresentedGroup): the name of the generators in a similar way as it works for the :class:`BraidGroup_class`. - INPUT (to the constructor): + INPUT: - ``names`` -- see the corresponding documentation of :class:`BraidGroup_class`. + - ``cbg_type`` -- (default: ``CubicBraidGroup.type.Coxeter``; + see explanation below) enum type :class:`CubicBraidGroup.type` - - ``cbg_type`` -- (optional keyword, default = CubicBraidGroup.type.Coxeter, - see explanation below) of enum type :class:`CubicBraidGroup.type`. - - Setting the keyword ``cbg_type`` to one on the values ``CubicBraidGroup.type.AssionS`` - or ``CubicBraidGroup.type.AssionU`` the additional relations due to Assion are added: + Setting the keyword ``cbg_type`` to one on the values + ``CubicBraidGroup.type.AssionS`` or ``CubicBraidGroup.type.AssionU``, + the additional relations due to Assion are added: .. MATH:: \begin{array}{lll} \mbox{S:} & s_3 s_1 t_2 s_1 t_2^{-1} t_3 t_2 s_1 t_2^{-1} t_3^{-1} = 1 - & \mbox{ for } m >= 5 \mbox{ in case } S(m)\\ + & \mbox{ for } m >= 5 \mbox{ in case } S(m), \\ \mbox{U:} & t_1 t_3 = 1 - & \mbox{ for } m >= 5 \mbox{ in case } U(m) + & \mbox{ for } m >= 5 \mbox{ in case } U(m), \end{array} - where `t_i = (s_i s_{i+1})^3`. If ``cbg_type == CubicBraidGroup.type.Coxeter`` (default) - only the cubic relation on the generators is active (Coxeter's case of investigation). - Note that for `n = 2, 3, 4` the groups do not differ between the three possible - values of cbg_type (as finitely presented groups). But anyway, the instances for - ``CubicBraidGroup.type.Coxeter, CubicBraidGroup.type.AssionS`` and ``CubicBraidGroup.type.AssionU`` - are different, since they have different classical realizations implemented. + where `t_i = (s_i s_{i+1})^3`. If ``cbg_type == CubicBraidGroup.type.Coxeter`` + (default) only the cubic relation on the generators is active (Coxeter's + case of investigation). Note that for `n = 2, 3, 4`, the groups do not + differ between the three possible values of ``cbg_type`` (as finitely + presented groups). However, the ``CubicBraidGroup.type.Coxeter``, + ``CubicBraidGroup.type.AssionS`` and ``CubicBraidGroup.type.AssionU`` + are different, so they have different classical realizations implemented. - The creation of instances of this class can also be done more easily by help - of :func:`CubicBraidGroup`, :func:`AssionGroupS` and :func:`AssionGroupU` - (similar to :func:`BraidGroup` with respect to :class:`BraidGroup_class`). + .. SEEALSO:: + + Instances can also be constructed more easily by using + :func:`CubicBraidGroup`, :func:`AssionGroupS` and :func:`AssionGroupU`. EXAMPLES:: @@ -620,7 +606,7 @@ class CubicBraidGroup(FinitelyPresentedGroup): sage: U3.gens() (c0, c1) - alternative possibilities defining U3:: + Alternative possibilities defining ``U3``:: sage: U3 = AssionGroupU(3); U3 Assion group on 3 strands of type U @@ -631,7 +617,7 @@ class CubicBraidGroup(FinitelyPresentedGroup): sage: U3.gens() (u1, u2) - alternates naming the generators:: + Alternates naming the generators:: sage: U3 = AssionGroupU(3, 'a, b'); U3 Assion group on 3 strands of type U @@ -645,7 +631,8 @@ class CubicBraidGroup(FinitelyPresentedGroup): #I Forcing finiteness test True sage: U3.as_classical_group() - Subgroup generated by [(1,7,6)(3,19,14)(4,15,10)(5,11,18)(12,16,20), (1,12,13)(2,15,19)(4,9,14)(5,18,8)(6,21,16)] of (The projective general unitary group of degree 3 over Finite Field of size 2) + Subgroup generated by [(1,7,6)(3,19,14)(4,15,10)(5,11,18)(12,16,20), (1,12,13)(2,15,19)(4,9,14)(5,18,8)(6,21,16)] + of (The projective general unitary group of degree 3 over Finite Field of size 2) sage: C3.as_classical_group() Subgroup with 2 generators ( [ E(3)^2 0] [ 1 -E(12)^7] @@ -665,13 +652,14 @@ class CubicBraidGroup(FinitelyPresentedGroup): ############################################################################## # Enum for the type of the group ############################################################################## + class type(Enum): r""" Enum class to select the type of the group: - - ``Coxeter`` -- 'C' the full cubic braid group. - - ``AssionS`` -- 'S' finite factor group of type S considered by Assion. - - ``AssionU`` -- 'U' finite factor group of type U considered by Assion. + - ``Coxeter`` -- ``'C'`` the full cubic braid group. + - ``AssionS`` -- ``'S'`` finite factor group of type S considered by Assion + - ``AssionU`` -- ``'U'`` finite factor group of type U considered by Assion EXAMPLES:: @@ -695,19 +683,6 @@ def __classcall_private__(cls, n=None, names='c', cbg_type=None): r""" Normalize input to ensure a unique representation. - INPUT: - - - ``n`` -- integer or ``None`` (default). The number of - strands. If not specified the ``names`` are counted and the - group is assumed to have one more strand than generators. - - - ``names`` -- string or list/tuple/iterable of strings (default: - ``'c'``). The generator names or name prefix. - - - ``cbg_type`` -- (optional keyword, default = CubicBraidGroup.type.Coxeter) - of enum type :class:`CubicBraidGroup.type` is passed to the corresponding - keyword argument of the constructor of :class:`CubicBraidGroup`. - EXAMPLES:: sage: C3 = CubicBraidGroup(3); C3.generators() @@ -739,16 +714,9 @@ def __classcall_private__(cls, n=None, names='c', cbg_type=None): return super().__classcall__(cls, names, cbg_type=cbg_type) def __init__(self, names, cbg_type=None): - """ + r""" Python constructor. - INPUT: - - - ``names`` -- see the corresponding documentation of :class:`BraidGroup_class`. - - - ``cbg_type`` -- (optional keyword, default = CubicBraidGroup.type.Coxeter) of enum type - :class:`CubicBraidGroup.type` to select the type of the group. - TESTS:: sage: C3 = CubicBraidGroup(3) # indirect doctest @@ -775,18 +743,18 @@ def __init__(self, names, cbg_type=None): if cbg_type is None: cbg_type = CubicBraidGroup.type.Coxeter if not isinstance(cbg_type, CubicBraidGroup.type): - raise TypeError("the cbg_type must be an instance of %s" %(CubicBraidGroup.type)) + raise TypeError("the cbg_type must be an instance of %s" % CubicBraidGroup.type) - free_group = FreeGroup(names) - self._cbg_type = cbg_type - self._nstrands = n+1 - self._ident = self._cbg_type.value + self._nstrands.str() + free_group = FreeGroup(names) + self._cbg_type = cbg_type + self._nstrands = n + 1 + self._ident = self._cbg_type.value + self._nstrands.str() self._braid_group = BraidGroup(names) # internal naming of elements for convenience - b = [free_group([i]) for i in range(1 , n+1)] - t = [free_group([i, i+1])**3 for i in range(1 , n)] - ti = [free_group([-i, -i-1])**3 for i in range(1 , n)] + b = [free_group([i]) for i in range(1, n+1)] + t = [free_group([i, i+1]) ** 3 for i in range(1, n)] + ti = [free_group([-i, -i-1]) ** 3 for i in range(1, n)] # first the braid relation rels = list(self._braid_group.relations()) @@ -798,7 +766,7 @@ def __init__(self, names, cbg_type=None): # than Assion's relation Satz 2.2 for cbg_type=CubicBraidGroup.type.AssionS # and Satz 2.4 for cbg_type=CubicBraidGroup.type.AssionU if n > 3: - for i in range(n-3): + for i in range(n - 3): if cbg_type == CubicBraidGroup.type.AssionU: rels.append((t[i]*t[i+2])**3) elif cbg_type == CubicBraidGroup.type.AssionS: @@ -916,16 +884,17 @@ def codegrees(self): # ------------------------------------------------------------------------------- # Methods for test_suite # ------------------------------------------------------------------------------- + def _internal_test_attached_group(self, attached_group, tester): r""" - It tests conversion maps from ``self`` to the given attached Group + Test conversion maps from ``self`` to the given attached Group, which must have been defined using the :meth:`as_classical_group`, :meth:`as_matrix_group`, :meth:`as_permutation_group` or :meth:`as_reflection_group`. INPUT: - - ``attached_group`` -- attached group to be tested as specified above. + - ``attached_group`` -- attached group to be tested as specified above EXAMPLES:: @@ -941,14 +910,15 @@ def _internal_test_attached_group(self, attached_group, tester): tester.assertEqual(att_grp_elem_back, elem) return - def _test_classical_group(self, **options): r""" - Method called by TestSuite. + Check the classical group properties. The following is checked: - - construction of classical group was faithful. - - coercion maps to and from classical group exist and are inverse to each other. + + - Construction of classical group was faithful. + - Coercion maps to and from classical group exist and are + inverse to each other. EXAMPLES:: @@ -961,14 +931,15 @@ def _test_classical_group(self, **options): self._internal_test_attached_group(classic_grp, tester) return - def _test_permutation_group(self, **options): r""" - Method called by TestSuite. + Check the permutation group properties. The following is checked: - - construction of permutation group was faithful. - - coercion maps to and from permutation group exist and are inverse to each other. + + - Construction of permutation group was faithful. + - Coercion maps to and from permutation group exist and are + inverse to each other. EXAMPLES:: @@ -981,14 +952,15 @@ def _test_permutation_group(self, **options): self._internal_test_attached_group(permgrp, tester) return - def _test_matrix_group(self, **options): r""" - Method called by TestSuite. + Check the matrix group properties. The following is checked: - - construction of matrix group was faithful in several cases. - - coercion maps to and from matrix group exist. + + - Construction of matrix group was faithful. + - Coercion maps to and from matrix group exist and are + inverse to each other. EXAMPLES:: @@ -1022,11 +994,13 @@ def _test_matrix_group(self, **options): def _test_reflection_group(self, **options): r""" - Method called by TestSuite. + Check the reflection group properties. The following is checked: - - construction of reflection group was faithful. - - coercion maps to and from reflection group exist and are inverse to each other. + + - Construction of reflection group was faithful. + - Coercion maps to and from reflection group exist and are + inverse to each other. EXAMPLES:: @@ -1081,6 +1055,7 @@ def _create_classical_realization(self, just_embedded=False): # ------------------------------------------------------------------------------- # Set up data of the classical Assion group (generic part) # ------------------------------------------------------------------------------- + def set_classical_realization(self, base_group, proj_group, centralizing_matrix, transvec_matrices): r""" Internal method to create classical group for Assion groups. @@ -1158,6 +1133,7 @@ def set_classical_realization(self, base_group, proj_group, centralizing_matrix, # ------------------------------------------------------------------------------- # Case for symplectic groups # ------------------------------------------------------------------------------- + def create_sympl_realization(self, m): r""" Internal method to create classical group for symplectic @@ -1170,7 +1146,6 @@ def create_sympl_realization(self, m): The function calculates the centralizing matrix and the transvections as given by Assion and then uses set_classical_realization to complete the construction. """ - # ----------------------------------------------------------- # getting the invariant bilinear form of the group # and setting constants. @@ -1229,6 +1204,7 @@ def transvec2mat(v, bas=bas, bform=bform, fact=1): # ------------------------------------------------------------------------------- # Case for unitary groups # ------------------------------------------------------------------------------- + def create_unitary_realization(self, m): """ Internal method to create classical group for @@ -1241,7 +1217,6 @@ def create_unitary_realization(self, m): The function calculates the centralizing_matrix and the transvections as given by Assion and then uses set_classical_realization to complete the construction. """ - # --------------------------------------------------------------------- # getting the invariant bilinear form of the group # and setting constants @@ -1299,7 +1274,6 @@ def transvec2mat(v, bas=bas, bform=bform, fact=a): t = [x + fact *(x * bform * v.conjugate()) * v for x in bas] return matrix(F, t) - # ------------------------------------------------------------------------------ # setting the centralizing matrix for the case of projective group realization. # ------------------------------------------------------------------------------ @@ -1324,7 +1298,7 @@ def transvec2mat(v, bas=bas, bform=bform, fact=a): # ------------------------------------------------------------------------------- # Setting the Classical group # ------------------------------------------------------------------------------- - if self._cbg_type == CubicBraidGroup.type.AssionS: + if self._cbg_type == CubicBraidGroup.type.AssionS: dim_sympl_group = n-1 # S(n-1) = Sp(n-1, 3) if n % 2 == 0: dim_sympl_group = n # S(n-1) = subgroup of PSp(n, 3) @@ -1362,28 +1336,30 @@ def transvec2mat(v, bas=bas, bform=bform, fact=a): def _element_constructor_(self, x, **kwds): r""" - Extensions to the _element constructor of :class:`FinitelyPresentedGroup`: + Return an element of ``self``. + + Extensions to the element constructor of :class:`FinitelyPresentedGroup`: new functionalities are: - constructing element from an element of the attached classical group - (embedded and not embedded) + (embedded and not embedded) - constructing element from an element of the attached permutation group - constructing element from an element of the attached reflection group INPUT: - ``x`` -- can be one of the following: - - an instance of the element class of ``self`` (but possible to a different parent). - - an instance of the element class of the braid group. - - a tuple representing a braid in Tietze form. - - an instance of an element class of a parent P such that there is a map from ``self`` to P - having :meth:`lift`, for example an element of an alternative realization of ``self``, such - as the classical realization. - - any other object which works for the element constructor of :class:`FinitelyPresentedGroup`. - - OUTPUT: - instance of the element class of ``self`` + * an instance of the element class of ``self`` (but possible + to a different parent) + * an instance of the element class of the braid group + * a tuple representing a braid in Tietze form + * an instance of an element class of a parent ``P`` such that there + is a map from ``self`` to ``P`` having :meth:`lift`; for example, + an element of an alternative realization of ``self``, such as + the classical realization + * any other object which works for the element constructor + of :class:`FinitelyPresentedGroup` EXAMPLES:: @@ -1414,7 +1390,7 @@ def strands(self): r""" Return the number of strands of the braid group whose image is ``self``. - OUTPUT: Integer. + OUTPUT: :class:`Integer` EXAMPLES:: @@ -1430,12 +1406,12 @@ def strands(self): # ---------------------------------------------------------------------------------- def braid_group(self): r""" - Return an Instance of :class:`BraidGroup` with identical generators, such that + Return a :class:`BraidGroup` with identical generators, such that there exists an epimorphism to ``self``. OUTPUT: - Instance of :class:`BraidGroup` having conversion maps to and from ``self`` + A :class:`BraidGroup` having conversion maps to and from ``self`` (which is just a section in the latter case). EXAMPLES:: @@ -1476,29 +1452,24 @@ def braid_group(self): @cached_method def as_matrix_group(self, root_bur=None, domain=None, characteristic=None, var='t', reduced=False): r""" - Creates an epimorphic image of ``self`` as a matrix group by use of the burau representation. + Creates an epimorphic image of ``self`` as a matrix group by use of + the burau representation. - INPUT (all arguments are optional by keyword): + INPUT: - - ``root_bur`` -- six (resp. twelfth) root of unity in some field - (default root of unity over `\QQ`). - - ``domain`` -- base_ring for the Burau matrix (default is Cyclotomic - Field of order 3 and degree 2, resp. the domain of `root_bur` if given). - - ``characteristic`` - integer giving the characteristic of the - domain (default is 0 or the characteristic of `domain` if given) - If none of the keywords `root_bur`, `domain` and `characteristic` is - given the default characteristic is 3 (resp. 2) if ``self`` is of ``cbg_type - CubicBraidGroup.type.AssionS`` (resp. ``CubicBraidGroup.type.AssionU``). - - ``var`` -- string used for the indeterminate name in case `root_bur` - must be constructed in a splitting field. + - ``root_bur`` -- (default: root of unity over `\QQ`) six (resp. twelfth) + root of unity in some field + - ``domain`` -- (default: cyclotomic field of order 3 and degree 2, resp. + the domain of ``root_bur`` if given) base ring for the Burau matrix + - ``characteristic`` -- integer (optional); the characteristic of the + domain; if none of the keywords ``root_bur``, ``domain`` and + ``characteristic`` are given, the default characteristic is 3 + (resp. 2) if ``self`` is of ``cbg_type`` + ``CubicBraidGroup.type.AssionS`` (resp. ``CubicBraidGroup.type.AssionU``) + - ``var`` -- string used for the indeterminate name in case ``root_bur`` + must be constructed in a splitting field - ``reduced`` -- boolean (default: ``False``); for more information - see the documentation of :meth:`burau_matrix` of :class:`Braid`. - - OUTPUT: - - An instance of the class :class:`FinitelyGeneratedMatrixGroup_gap` according to the - input arguments together with a group homomorphism registered as a conversion - from ``self`` to it. + see the documentation of :meth:`Braid.burau_matrix` EXAMPLES:: @@ -1606,20 +1577,15 @@ def as_matrix_group(self, root_bur=None, domain=None, characteristic=None, var=' @cached_method def as_permutation_group(self, use_classical=True): r""" - This method returns a permutation group isomorphic to ``self`` together - with group isomorphism from ``self`` as a conversion. + Return a permutation group isomorphic to ``self`` that has a + group isomorphism from ``self`` as a conversion. - INPUT (all arguments are optional by keyword): - - - ``use_classical`` -- (boolean, default True) by default the permutation - group is calculated via the attached classical matrix group, since this - results in a smaller degree. If set to False the permutation group will - be calculated using ``self`` (as finitely presented group). - - OUTPUT: + INPUT: - An instance of class :class:`PermutationGroup_generic` together with a group homomorphism - from ``self`` registered as a conversion. + - ``use_classical`` -- boolean (default: ``True``); the permutation + group is calculated via the attached classical matrix group as this + results in a smaller degree; if ``False``, the permutation group will + be calculated using ``self`` (as finitely presented group) EXAMPLES:: @@ -1658,34 +1624,37 @@ def as_permutation_group(self, use_classical=True): # ---------------------------------------------------------------------------------- def as_classical_group(self, embedded=False): r""" - Creates an isomorphic image of ``self`` as a classical group according + Create an isomorphic image of ``self`` as a classical group according to the construction given by Coxeter resp. Assion. - INPUT (optional keyword): + INPUT: - - ``embedded`` -- boolean (default = False). This boolean does effect the - cases of Assion groups when they are realized as projective groups, only. + - ``embedded`` -- boolean (default: ``False``); this boolean effects the + cases of Assion groups when they are realized as projective groups only. More precisely: if ``self`` is of ``cbg_type CubicBraidGroup.type.AssionS`` - (for example) and the number of strands ``n`` is even, than its classical group - is a subgroup of ``PSp(n,3)`` (being centralized by the element - ``self.centralizing_element(projective=True))``. By default this group will be - given. Setting ``embedded = True`` the classical realization is given as - subgroup of its classical enlargement with one more strand (in this - case as subgroup of ``Sp(n,3))``. + (for example) and the number of strands ``n`` is even, than its classical + group is a subgroup of ``PSp(n,3)`` (being centralized by the element + ``self.centralizing_element(projective=True))``. By default this group + will be given. Setting ``embedded = True`` the classical realization + is given as subgroup of its classical enlargement with one more strand + (in this case as subgroup of ``Sp(n,3))``. OUTPUT: - Depending on the type of ``self`` and the number of strands an instance of ``Sp(n-1,3)``, - ``GU(n-1,2)``, subgroup of ``PSp(n,3), PGU(n,2)`` or a subgroup of ``GU(n-1, UCF)`` - (``cbg_type == CubicBraidGroup.type.Coxeter``) with respect to a certain hermitian form - attached to the Burau representation (used by Coxeter and Squier). Here ``UCF`` stands - for the universal cyclotomic field. + Depending on the type of ``self`` and the number of strands an + instance of ``Sp(n-1,3)``, ``GU(n-1,2)``, subgroup of ``PSp(n,3)``, + ``PGU(n,2)``, or a subgroup of ``GU(n-1, UCF)`` + (``cbg_type == CubicBraidGroup.type.Coxeter``) with respect to a + certain Hermitian form attached to the Burau representation + (used by Coxeter and Squier). Here ``UCF`` stands for the universal + cyclotomic field. EXAMPLES:: sage: U3 = AssionGroupU(3) sage: U3Cl = U3.as_classical_group(); U3Cl - Subgroup generated by [(1,7,6)(3,19,14)(4,15,10)(5,11,18)(12,16,20), (1,12,13)(2,15,19)(4,9,14)(5,18,8)(6,21,16)] of (The projective general unitary group of degree 3 over Finite Field of size 2) + Subgroup generated by [(1,7,6)(3,19,14)(4,15,10)(5,11,18)(12,16,20), (1,12,13)(2,15,19)(4,9,14)(5,18,8)(6,21,16)] + of (The projective general unitary group of degree 3 over Finite Field of size 2) sage: U3Clemb = U3.as_classical_group(embedded=True); U3Clemb Subgroup with 2 generators ( [0 0 a] [a + 1 a a] @@ -1745,8 +1714,8 @@ def as_classical_group(self, embedded=False): return self._classical_embedding elif self._classical_group is not None: return self._classical_group - else: - raise ValueError("no classical embedding defined") + + raise ValueError("no classical embedding defined") # ---------------------------------------------------------------------------------- @@ -1754,18 +1723,17 @@ def as_classical_group(self, embedded=False): # ---------------------------------------------------------------------------------- def as_reflection_group(self): r""" - Creates an isomorphic image of ``self`` as irreducible complex reflection group. - This is possible only for the finite cubic braid groups of ``cbg_type - CubicBraidGroup.type.Coxeter``. + Return an isomorphic image of ``self`` as irreducible complex + reflection group. - This method uses the sage implementation of reflection group via the gap3 CHEVIE - package. To use this method you must have gap3 together with CHEVIE installed! - - OUTPUT: + This is possible only for the finite cubic braid groups of ``cbg_type`` + ``CubicBraidGroup.type.Coxeter``. - An instance of the class :class:`IrreducibleComplexReflectionGroup` together with - a group isomorphism from ``self`` registered as a conversion. + .. NOTE:: + This method uses the sage implementation of reflection group via + the ``gap3`` ``CHEVIE`` package. These must be installed in order + to use this method. EXAMPLES:: @@ -1793,10 +1761,11 @@ def as_reflection_group(self): [ E(3)^2 -E(4)] [-E(12)^7 0] - The reflection groups can also be viewed as subgroups of unitary groups - over the universal cyclotomic field. Note that the unitary group corresponding - to the reflection group is isomorphic but different from the classical group due - to different hermitian forms for the unitary groups they live in:: + The reflection groups can also be viewed as subgroups of unitary groups + over the universal cyclotomic field. Note that the unitary group + corresponding to the reflection group is isomorphic but different from + the classical group due to different hermitian forms for the unitary + groups they live in:: sage: C4 = CubicBraidGroup(4) # optional - gap3 sage: R4 = C4.as_reflection_group() # optional - gap3 @@ -1807,7 +1776,6 @@ def as_reflection_group(self): sage: _ == C4.classical_invariant_form() # optional - gap3 False """ - # ------------------------------------------------------------------------------- # the reflection groups are called according to the Shephard-Todd classification: # 2 strands -> G(2,1,1) @@ -1819,7 +1787,7 @@ def as_reflection_group(self): if not is_chevie_available(): raise ImportError("the GAP3 package 'CHEVIE' is needed to obtain the corresponding reflection groups") - if self._cbg_type != CubicBraidGroup.type.Coxeter or self.strands() > 5 or self.strands() < 2: + if self._cbg_type != CubicBraidGroup.type.Coxeter or self.strands() > 5 or self.strands() < 2: raise ValueError("no reflection group defined") # ------------------------------------------------------------------------------- @@ -1852,15 +1820,17 @@ def classical_invariant_form(self): OUTPUT: - A square matrix of dimension according to the space the classical realization is - operating on. In the case of the full cubic braid groups and of the Assion groups - of ``cbg_type CubicBraidGroup.type.AssionU`` the matrix is hermitian. In the case of - the Assion groups of ``cbg_type CubicBraidGroup.type.AssionS`` it is alternating. - Note that the invariant form of the full cubic braid group on more than 5 strands - is degenerated (causing the group to be infinite). + A square matrix of dimension according to the space the classical + realization is operating on. In the case of the full cubic braid groups + and of the Assion groups of ``cbg_type CubicBraidGroup.type.AssionU`` + the matrix is Hermitian. In the case of the Assion groups of + ``cbg_type CubicBraidGroup.type.AssionS`` it is alternating. + Note that the invariant form of the full cubic braid group on more + than 5 strands is degenerated (causing the group to be infinite). - In the case of Assion groups having projective classical groups the invariant form - corresponds to the ambient group of its classical embedding. + In the case of Assion groups having projective classical groups, + the invariant form corresponds to the ambient group of its + classical embedding. EXAMPLES:: @@ -1901,7 +1871,7 @@ def classical_invariant_form(self): self._create_classical_realization() if self._classical_invariant_form is None: - raise ValueError("no classical invariant form defined!") + raise ValueError("no classical invariant form defined") return self._classical_invariant_form @@ -1914,30 +1884,32 @@ def centralizing_element(self, embedded=False): Return the centralizing element defined by the work of Assion (Hilfssatz 1.1.3 and 1.2.3). - INPUT (optional): + INPUT: - - ``embedded`` -- boolean (default = False). This boolean just effects + - ``embedded`` -- boolean (default; ``False``); this boolean only effects the cases of Assion groups when they are realized as projective groups. More precisely: if ``self`` is of ``cbg_type CubicBraidGroup.type.AssionS`` - (for example) and the number of strands ``n`` is even, than its classical - group is a subgroup of ``PSp(n,3)`` being centralized by the element return - for option ``embedded=False``. Otherwise the image of this element inside - the embedded classical group will be returned (see option embedded of - :meth:`classical_group`)! + (for example) and the number of strands ``n`` is even, than its + classical group is a subgroup of ``PSp(n,3)`` being centralized + by the element return for option ``embedded=False``. Otherwise the + image of this element inside the embedded classical group will be + returned (see option embedded of :meth:`classical_group`). OUTPUT: - Depending on the optional keyword a permutation as an element of ``PSp(n,3)`` - (type S) or ``PGU(n,2)`` (type U) for ``n = 0 mod 2`` (type S) reps. ``n = 0 mod 3`` - (type U) is returned. Else-wise, the centralizing element is a matrix - belonging to ``Sp(n,3)`` reps. ``GU(n,2)``. + Depending on the optional keyword a permutation as an element + of ``PSp(n,3)`` (type S) or ``PGU(n,2)`` (type U) for ``n = 0 mod 2`` + (type S) resp. ``n = 0 mod 3`` (type U) is returned. Otherwise, the + centralizing element is a matrix belonging to ``Sp(n,3)`` + resp. ``GU(n,2)``. EXAMPLES:: sage: U3 = AssionGroupU(3); U3 Assion group on 3 strands of type U sage: U3Cl = U3.as_classical_group(); U3Cl - Subgroup generated by [(1,7,6)(3,19,14)(4,15,10)(5,11,18)(12,16,20), (1,12,13)(2,15,19)(4,9,14)(5,18,8)(6,21,16)] of (The projective general unitary group of degree 3 over Finite Field of size 2) + Subgroup generated by [(1,7,6)(3,19,14)(4,15,10)(5,11,18)(12,16,20), (1,12,13)(2,15,19)(4,9,14)(5,18,8)(6,21,16)] + of (The projective general unitary group of degree 3 over Finite Field of size 2) sage: c = U3.centralizing_element(); c (1,16)(2,9)(3,10)(4,19)(6,12)(7,20)(13,21)(14,15) sage: c in U3Cl @@ -1946,7 +1918,7 @@ def centralizing_element(self, embedded=False): sage: P.centralizer(c) == U3Cl True - embedded Version:: + Embedded version:: sage: cm = U3.centralizing_element(embedded=True); cm [a + 1 a + 1 1] @@ -1959,7 +1931,6 @@ def centralizing_element(self, embedded=False): sage: [cm * U4Cl(g) == U4Cl(g) * cm for g in U4.gens()] [True, True, False] """ - # ------------------------------------------------------------------------------- # create the centralizing elements if not already done # ------------------------------------------------------------------------------- @@ -2010,7 +1981,7 @@ def order(self): def is_finite(self): r""" - Method from :class:`GroupMixinLibGAP` overwritten because of performance reason. + Return if ``self`` is a finite group or not. EXAMPLES:: @@ -2019,30 +1990,28 @@ def is_finite(self): sage: AssionGroupS(6).is_finite() True """ - from sage.rings.infinity import infinity - return not self.order() is infinity + return not (self._cbg_type == CubicBraidGroup.type.Coxeter and self.strands() > 5) - # ---------------------------------------------------------------------------------- + # ------------------------------------------------------------------ # creating a CubicBraidGroup as subgroup of self on less strands - # ---------------------------------------------------------------------------------- - def cubic_braid_subgroup(self, nstrands = None): + # ------------------------------------------------------------------ + def cubic_braid_subgroup(self, nstrands=None): r""" - Creates a cubic braid group as subgroup of ``self`` on the first ``nstrands`` strands. + Return a cubic braid group as subgroup of ``self`` on the first + ``nstrands`` strands. INPUT: - - ``nstrands`` -- integer > 0 and < ``self.strands()`` giving the number of strands - for the subgroup. The default is one strand less than ``self`` has. + - ``nstrands`` -- (default: ``self.strands() - 1``) integer at least 1 + and at most ``self.strands()`` giving the number of strands of + the subgroup - OUTPUT: + .. WARNING:: - An instance of this class realizing the subgroup. - - .. NOTE:: - - Since ``self`` is inherited from :class:`UniqueRepresentation` the obtained instance - is identical to other instances created with the same arguments (see example - below). The ambient group corresponds to the last call of this method. + Since ``self`` is inherited from :class:`UniqueRepresentation`, the + obtained instance is identical to other instances created with the + same arguments (see example below). The ambient group corresponds + to the last call of this method. EXAMPLES:: @@ -2068,8 +2037,10 @@ def cubic_braid_subgroup(self, nstrands = None): if nstrands >= n or nstrands <= 0: raise ValueError("nstrands must be positive and less than %s" %(self.strands())) - gens = self.gens() - gens_red = tuple([gens[i] for i in range(nstrands -1)]) - subgrp = CubicBraidGroup(names=gens_red, cbg_type=self._cbg_type) + + names = self.variable_names() + names_red = names[:nstrands-1] + subgrp = CubicBraidGroup(names=names_red, cbg_type=self._cbg_type) subgrp._ambient = self return subgrp + diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index 2a61bbf91dc..d953022d3da 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -1486,7 +1486,7 @@ def epimorphisms(self, H): res.append(fhom) return res - def alexander_matrix(self, im_gens = None): + def alexander_matrix(self, im_gens=None): """ Return the Alexander matrix of the group. diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index 2846028b5e7..0c51deafc64 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -149,13 +149,14 @@ def _lexi_gen(zeroes=False): count = Integer(0) while True: mwrap, ind = count.quo_rem(26) - if mwrap == 0 and not(zeroes): + if mwrap == 0 and not zeroes: name = '' else: name = str(mwrap) name = chr(ord('a') + ind) + name yield name - count = count + 1 + count += 1 + class FreeGroupElement(ElementLibGAP): """ diff --git a/src/sage/groups/generic.py b/src/sage/groups/generic.py index de015974697..15aee2f0a20 100644 --- a/src/sage/groups/generic.py +++ b/src/sage/groups/generic.py @@ -383,7 +383,7 @@ def bsgs(a, b, bounds, operation='*', identity=None, inverse=None, op=None): - ``operation`` - string: '*', '+', 'other' - ``identity`` - the identity element of the group - ``inverse()`` - function of 1 argument ``x`` returning inverse of ``x`` - - ``op()`` - function of 2 arguments ``x``, ``y`` returning ``x*y`` in group + - ``op()`` - function of 2 arguments ``x``, ``y`` returning ``x*y`` in the group OUTPUT: @@ -463,7 +463,8 @@ def bsgs(a, b, bounds, operation='*', identity=None, inverse=None, op=None): ran = 1 + ub - lb # the length of the interval - c = op(inverse(b), multiple(a, lb, operation=operation)) + mult = lambda x, y: multiple(x, y, operation=operation, identity=identity, inverse=inverse, op=op) + c = op(inverse(b), mult(a, lb)) if ran < 30: # use simple search for small ranges d = c @@ -497,7 +498,7 @@ def bsgs(a, b, bounds, operation='*', identity=None, inverse=None, op=None): raise ValueError("log of %s to the base %s does not exist in %s" % (b, a, bounds)) -def discrete_log_rho(a, base, ord=None, operation='*', hash_function=hash): +def discrete_log_rho(a, base, ord=None, operation='*', identity=None, inverse=None, op=None, hash_function=hash): """ Pollard Rho algorithm for computing discrete logarithm in cyclic group of prime order. @@ -512,6 +513,9 @@ def discrete_log_rho(a, base, ord=None, operation='*', hash_function=hash): to compute it - ``operation`` -- a string (default: ``'*'``) denoting whether we are in an additive group or a multiplicative one + - ``identity`` - the group's identity + - ``inverse()`` - function of 1 argument ``x`` returning inverse of ``x`` + - ``op()`` - function of 2 arguments ``x``, ``y`` returning ``x*y`` in the group - ``hash_function`` -- having an efficient hash function is critical for this algorithm (see examples) @@ -585,7 +589,8 @@ def discrete_log_rho(a, base, ord=None, operation='*', hash_function=hash): # should be reasonable choices partition_size = 20 memory_size = 4 - + mult = op + power = lambda x, y: multiple(x, y, operation=operation, identity=identity, inverse=inverse, op=op) if operation in addition_names: mult = add power = mul @@ -596,11 +601,10 @@ def discrete_log_rho(a, base, ord=None, operation='*', hash_function=hash): power = pow if ord is None: ord = base.multiplicative_order() - else: + elif ord is None or inverse is None or identity is None or op is None: raise ValueError ord = Integer(ord) - if not ord.is_prime(): raise ValueError("for Pollard rho algorithm the order of the group must be prime") @@ -610,7 +614,7 @@ def discrete_log_rho(a, base, ord=None, operation='*', hash_function=hash): isqrtord = ord.isqrt() if isqrtord < partition_size: # setup to costly, use bsgs - return bsgs(base, a, bounds=(0, ord), operation=operation) + return bsgs(base, a, bounds=(0, ord), identity=identity, inverse=inverse, op=op, operation=operation) reset_bound = 8 * isqrtord # we take some margin @@ -663,7 +667,7 @@ def discrete_log_rho(a, base, ord=None, operation='*', hash_function=hash): raise ValueError("Pollard rho algorithm failed to find a logarithm") -def discrete_log(a, base, ord=None, bounds=None, operation='*', identity=None, inverse=None, op=None): +def discrete_log(a, base, ord=None, bounds=None, operation='*', identity=None, inverse=None, op=None, algorithm='bsgs'): r""" Totally generic discrete log function. @@ -676,7 +680,8 @@ def discrete_log(a, base, ord=None, bounds=None, operation='*', identity=None, i - ``operation`` - string: '*', '+', 'other' - ``identity`` - the group's identity - ``inverse()`` - function of 1 argument ``x`` returning inverse of ``x`` - - ``op()`` - function of 2 arguments ``x``, ``y`` returning ``x*y`` in group + - ``op()`` - function of 2 arguments ``x``, ``y`` returning ``x*y`` in the group + - ``algorithm`` - string denoting what algorithm to use for prime-order logarithms: 'bsgs', 'rho', 'lambda' ``a`` and ``base`` must be elements of some group with identity given by identity, inverse of ``x`` by ``inverse(x)``, and group @@ -698,15 +703,15 @@ def discrete_log(a, base, ord=None, bounds=None, operation='*', identity=None, i than using this function. E.g., if ``x`` is an integer modulo `n`, use its log method instead! - ALGORITHM: Pohlig-Hellman and Baby step giant step. + ALGORITHM: Pohlig-Hellman, Baby step giant step, Pollard's lambda/kangaroo, and Pollard's rho. EXAMPLES:: sage: b = Mod(2,37); a = b^20 sage: discrete_log(a, b) 20 - sage: b = Mod(2,997); a = b^20 - sage: discrete_log(a, b) + sage: b = Mod(3,2017); a = b^20 + sage: discrete_log(a, b, bounds=(10, 100)) 20 sage: K = GF(3^6,'b') @@ -784,63 +789,164 @@ def discrete_log(a, base, ord=None, bounds=None, operation='*', identity=None, i sage: discrete_log(u,g) 123456789 + The above examples also work when the 'rho' and 'lambda' algorithms are used:: + + sage: b = Mod(2,37); a = b^20 + sage: discrete_log(a, b, algorithm='rho') + 20 + sage: b = Mod(3,2017); a = b^20 + sage: discrete_log(a, b, algorithm='lambda', bounds=(10, 100)) + 20 + + sage: K = GF(3^6,'b') + sage: b = K.gen() + sage: a = b^210 + sage: discrete_log(a, b, K.order()-1, algorithm='rho') + 210 + + sage: b = Mod(1,37); x = Mod(2,37) + sage: discrete_log(x, b, algorithm='lambda') + Traceback (most recent call last): + ... + ValueError: no discrete log of 2 found to base 1 + sage: b = Mod(1,997); x = Mod(2,997) + sage: discrete_log(x, b, algorithm='rho') + Traceback (most recent call last): + ... + ValueError: no discrete log of 2 found to base 1 + + sage: F=GF(37^2,'a') + sage: E=EllipticCurve(F,[1,1]) + sage: F.=GF(37^2,'a') + sage: E=EllipticCurve(F,[1,1]) + sage: P=E(25*a + 16 , 15*a + 7 ) + sage: P.order() + 672 + sage: Q=39*P; Q + (36*a + 32 : 5*a + 12 : 1) + sage: discrete_log(Q,P,P.order(),operation='+',algorithm='lambda') + 39 + + sage: F. = GF(2^63) + sage: g = F.gen() + sage: u = g**123456789 + sage: discrete_log(u,g,algorithm='rho') + 123456789 + + TESTS: + + Random testing:: + + sage: G = Zmod(randrange(1, 1000)) + sage: base = G.random_element() + sage: order = choice([base.additive_order(), G.order()]) + sage: assert order.divides(G.cardinality()) + sage: sol = randrange(base.additive_order()) + sage: elem = sol * base + sage: args = (elem, base, order) + sage: kwargs = {'operation': '+'} + sage: kwargs['algorithm'] = choice(['bsgs', 'rho', 'lambda']) + sage: if randrange(2): + ....: lo = randrange(-order, sol+1) + ....: hi = randrange(sol+1, 2*order) + ....: assert lo <= sol <= hi + ....: kwargs['bounds'] = (lo, hi) + sage: try: + ....: res = discrete_log(*args, **kwargs) + ....: except ValueError: + ....: # lambda can fail randomly + ....: assert kwargs['algorithm'] == 'lambda' + ....: else: + ....: assert res == sol + AUTHORS: - William Stein and David Joyner (2005-01-05) - John Cremona (2008-02-29) rewrite using ``dict()`` and make generic + - Julien Grijalva (2022-08-09) rewrite to make more generic, more algorithm options, and more effective use of bounds """ + from operator import mul, add, pow + power = mul if operation in addition_names else pow + mult = add if operation in addition_names else mul + if op: + mult = op + power = lambda x, y: multiple(x, y, operation=operation, identity=identity, inverse=inverse, op=op) + if bounds: + lb, ub = map(integer_ring.ZZ, bounds) + if (op is None or identity is None or inverse is None or ord is None) and operation not in addition_names+multiplication_names: + raise ValueError("ord, op, identity, and inverse must all be specified for this operation") if ord is None: if operation in multiplication_names: try: ord = base.multiplicative_order() except Exception: ord = base.order() - elif operation in addition_names: + else: try: ord = base.additive_order() except Exception: ord = base.order() - else: - try: - ord = base.order() - except Exception: - raise ValueError("ord must be specified") + else: + ord = integer_ring.ZZ(ord) try: from sage.rings.infinity import Infinity if ord == +Infinity: - return bsgs(base, a, bounds, operation=operation) - if ord == 1 and a != base: + return bsgs(base, a, bounds, identity=identity, inverse=inverse, op=op, operation=operation) + if base == power(base, 0) and a != base: raise ValueError f = ord.factor() l = [0] * len(f) + mods = [] + running_mod = 1 + offset = 0 + if bounds: + a = mult(a, power(base, -lb)) + offset = lb + bound = ub - lb + i = -1 # this corrects a bug in which the loop is never entered and i never gets assigned a value for i, (pi, ri) in enumerate(f): + gamma = power(base, ord // pi) + # pohlig-hellman doesn't work with an incorrect order, and the user might have provided a bad parameter + while gamma == power(gamma, 0) and ri > 0: # identity might be None + ord //= pi + ri -= 1 + gamma = power(base, ord // pi) + if not bounds: + bound = ord - 1 + running_bound = min(bound, pi**ri - 1) + j = -1 for j in range(ri): - if operation in multiplication_names: - c = bsgs(base**(ord // pi), - (a / base**l[i])**(ord // pi**(j + 1)), - (0, pi), - operation=operation) - l[i] += c * (pi**j) - elif operation in addition_names: - c = bsgs(base * (ord // pi), - (a - base * l[i]) * (ord // pi**(j + 1)), - (0, pi), - operation=operation) - l[i] += c * (pi**j) + temp_bound = min(running_bound, pi - 1) + h = power(mult(a, power(base, -l[i])), ord // pi**(j + 1)) + if algorithm == 'bsgs': + c = bsgs(gamma, h, (0, temp_bound), inverse=inverse, identity=identity, op=op, operation=operation) + elif algorithm == 'rho': + c = discrete_log_rho(h, gamma, ord=pi, inverse=inverse, identity=identity, op=op, operation=operation) + elif algorithm == 'lambda': + c = discrete_log_lambda(h, gamma, (0, temp_bound), inverse=inverse, identity=identity, op=op, operation=operation) + l[i] += c * (pi**j) + running_bound //= pi + running_mod *= pi + if running_mod > bound: + break + mods.append(pi ** (j+1)) + if running_mod > bound: + break # we have log%running_mod. if we know that log= lb: if mut: H.set_immutable() - if ub > c - d and H in mem: + if ub >= c - d and H in mem: return c - d r, e = M[hash_function(H) % k] H = mult(H, e) @@ -1033,7 +1140,7 @@ def linear_relation(P, Q, operation='+', identity=None, inverse=None, op=None): m1 * h) except ValueError: pass # to next h - raise ValueError("No solution found in linear_relation!") + raise ValueError("no solution found in linear_relation") ################################################################ # diff --git a/src/sage/groups/group_exp.py b/src/sage/groups/group_exp.py index 3fa83c6ff86..4f3d27a8def 100644 --- a/src/sage/groups/group_exp.py +++ b/src/sage/groups/group_exp.py @@ -211,20 +211,18 @@ def __init__(self, parent, x): raise ValueError("%s is not an element of %s" % (x, parent._G)) ElementWrapper.__init__(self, parent, x) - def inverse(self): + def __invert__(self): r""" Invert the element ``self``. EXAMPLES:: sage: EZ = GroupExp()(ZZ) - sage: EZ(-3).inverse() + sage: EZ(-3).inverse() # indirect doctest 3 """ return GroupExpElement(self.parent(), -self.value) - __invert__ = inverse - def __mul__(self, x): r""" Multiply ``self`` by `x`. diff --git a/src/sage/groups/group_semidirect_product.py b/src/sage/groups/group_semidirect_product.py index 120f6f96bbc..3ac629b9411 100644 --- a/src/sage/groups/group_semidirect_product.py +++ b/src/sage/groups/group_semidirect_product.py @@ -61,9 +61,9 @@ def wrapper(prefix, s): return gstr return gstr + " * " + hstr - def inverse(self): + def __invert__(self): r""" - The inverse of ``self``. + Return the inverse of ``self``. EXAMPLES:: @@ -89,8 +89,6 @@ def inverse(self): hi = ~h return self.__class__(par, (par._twist(hi, ~g), hi)) - __invert__ = inverse - def to_opposite(self): r""" Send an element to its image in the opposite semidirect product. diff --git a/src/sage/groups/matrix_gps/matrix_group.py b/src/sage/groups/matrix_gps/matrix_group.py index b3e3d491620..8cea6a6ef66 100644 --- a/src/sage/groups/matrix_gps/matrix_group.py +++ b/src/sage/groups/matrix_gps/matrix_group.py @@ -541,7 +541,7 @@ def __richcmp__(self, other, op): except (AttributeError, NotImplementedError): return richcmp(id(self), id(other), op) - assert(n_self == n_other) + assert n_self == n_other for g, h in zip(self_gens, other_gens): lx = g.matrix() rx = h.matrix() diff --git a/src/sage/groups/matrix_gps/orthogonal.py b/src/sage/groups/matrix_gps/orthogonal.py index f6721afaacd..8ff96051a8b 100644 --- a/src/sage/groups/matrix_gps/orthogonal.py +++ b/src/sage/groups/matrix_gps/orthogonal.py @@ -135,16 +135,15 @@ def normalize_args_e(degree, ring, e): return ZZ(e) - - ############################################################################### # Orthogonal Group: common Code for both GO and SO ############################################################################### def _OG(n, R, special, e=0, var='a', invariant_form=None): r""" - This function is commonly used by the functions GO and SO to avoid uneccessarily - duplicated code. For documentation and examples see the individual functions. + This function is commonly used by the functions GO and SO to avoid + unnecessarily duplicated code. For documentation and examples see + the individual functions. TESTS: diff --git a/src/sage/groups/old.pyx b/src/sage/groups/old.pyx index 8d6740a454a..c17082f59cb 100644 --- a/src/sage/groups/old.pyx +++ b/src/sage/groups/old.pyx @@ -31,7 +31,7 @@ cdef class Group(sage.structure.parent.Parent): """ Generic group class """ - def __init__(self, category = None): + def __init__(self, category=None): """ TESTS:: diff --git a/src/sage/groups/perm_gps/cubegroup.py b/src/sage/groups/perm_gps/cubegroup.py index 3c704b0dc9b..921c6201c43 100644 --- a/src/sage/groups/perm_gps/cubegroup.py +++ b/src/sage/groups/perm_gps/cubegroup.py @@ -944,7 +944,7 @@ def repr2d(self, mv): line13 = " +--------------+\n" return line1+line2+line3+line4+line5+line6+line7+line8+line9+line10+line11+line12+line13 - def plot_cube(self, mv, title=True, colors = [lpurple, yellow, red, green, orange, blue]): + def plot_cube(self, mv, title=True, colors=[lpurple, yellow, red, green, orange, blue]): r""" Input the move mv, as a string in the Singmaster notation, and output the 2D plot of the cube in that state. diff --git a/src/sage/groups/perm_gps/partn_ref/data_structures.pyx b/src/sage/groups/perm_gps/partn_ref/data_structures.pyx index 90995722d64..74065fe1fec 100644 --- a/src/sage/groups/perm_gps/partn_ref/data_structures.pyx +++ b/src/sage/groups/perm_gps/partn_ref/data_structures.pyx @@ -828,11 +828,12 @@ cdef SC_print_level(StabilizerChain *SC, int level): print('| labels {}'.format([SC.labels [level][i] for i from 0 <= i < n])) print('|') print('| generators {}'.format([[SC.generators [level][n*i + j] for j from 0 <= j < n] for i from 0 <= i < SC.num_gens[level]])) - print('\ inverses {}'.format([[SC.gen_inverses[level][n*i + j] for j from 0 <= j < n] for i from 0 <= i < SC.num_gens[level]])) + print(r'\ inverses {}'.format([[SC.gen_inverses[level][n*i + j] for j from 0 <= j < n] for i from 0 <= i < SC.num_gens[level]])) else: print('/ level {}'.format(level)) print('|') - print('\ base_size {}'.format(SC.base_size)) + print(r'\ base_size {}'.format(SC.base_size)) + cdef StabilizerChain *SC_new_base(StabilizerChain *SC, int *base, int base_len): """ diff --git a/src/sage/groups/perm_gps/partn_ref/refinement_binary.pyx b/src/sage/groups/perm_gps/partn_ref/refinement_binary.pyx index 5f54d4f8ac2..f13fccf891a 100644 --- a/src/sage/groups/perm_gps/partn_ref/refinement_binary.pyx +++ b/src/sage/groups/perm_gps/partn_ref/refinement_binary.pyx @@ -607,7 +607,7 @@ cdef int ith_word_nonlinear(BinaryCodeStruct self, int i, bitset_s *word): return 0 cdef int refine_by_bip_degree(PartitionStack *col_ps, void *S, int *cells_to_refine_by, int ctrb_len): - """ + r""" Refines the input partition by checking degrees of vertices to the given cells in the associated bipartite graph (vertices split into columns and words). @@ -731,11 +731,14 @@ cdef int refine_by_bip_degree(PartitionStack *col_ps, void *S, int *cells_to_ref return invariant cdef int compare_linear_codes(int *gamma_1, int *gamma_2, void *S1, void *S2, int degree): - """ + r""" Compare gamma_1(S1) and gamma_2(S2). - Return return -1 if gamma_1(S1) < gamma_2(S2), 0 if gamma_1(S1) == gamma_2(S2), - 1 if gamma_1(S1) > gamma_2(S2). (Just like the python \code{cmp}) function. + This returns: + + - -1 if gamma_1(S1) < gamma_2(S2), + - 0 if gamma_1(S1) == gamma_2(S2), + - 1 if gamma_1(S1) > gamma_2(S2). Abstractly, what this function does is relabel the basis of B by gamma_1 and gamma_2, run a row reduction on each, and verify that the matrices are the @@ -745,9 +748,9 @@ cdef int compare_linear_codes(int *gamma_1, int *gamma_2, void *S1, void *S2, in code has a 1 in the entry in which they differ is reported as larger. INPUT: - gamma_1, gamma_2 -- list permutations (inverse) - S1, S2 -- binary code struct objects + - gamma_1, gamma_2 -- list permutations (inverse) + - S1, S2 -- binary code struct objects """ cdef int i, piv_loc_1, piv_loc_2, cur_col, cur_row=0 cdef bint is_pivot_1, is_pivot_2 @@ -804,16 +807,19 @@ cdef int compare_linear_codes(int *gamma_1, int *gamma_2, void *S1, void *S2, in return 0 cdef int compare_nonlinear_codes(int *gamma_1, int *gamma_2, void *S1, void *S2, int degree): - """ + r""" Compare gamma_1(S1) and gamma_2(S2). - Return return -1 if gamma_1(S1) < gamma_2(S2), 0 if gamma_1(S1) == gamma_2(S2), - 1 if gamma_1(S1) > gamma_2(S2). (Just like the python \code{cmp}) function. + This returns: + + - -1 if gamma_1(S1) < gamma_2(S2), + - 0 if gamma_1(S1) == gamma_2(S2), + - 1 if gamma_1(S1) > gamma_2(S2). INPUT: - gamma_1, gamma_2 -- list permutations (inverse) - S1, S2 -- a binary code struct object + - gamma_1, gamma_2 -- list permutations (inverse) + - S1, S2 -- a binary code struct object """ cdef int side=0, i, start, end, n_one_1, n_one_2, cur_col cdef int where_0, where_1 diff --git a/src/sage/groups/perm_gps/partn_ref/refinement_graphs.pyx b/src/sage/groups/perm_gps/partn_ref/refinement_graphs.pyx index 34d453cba77..4ab08bd6ba7 100644 --- a/src/sage/groups/perm_gps/partn_ref/refinement_graphs.pyx +++ b/src/sage/groups/perm_gps/partn_ref/refinement_graphs.pyx @@ -1257,8 +1257,9 @@ cdef void free_dg_edge_gen(iterator *dg_edge_gen): sig_free(dg_edge_gen) -def generate_dense_graphs_edge_addition(int n, bint loops, G = None, depth = None, bint construct = False, - bint indicate_mem_err = True): +def generate_dense_graphs_edge_addition(int n, bint loops, G=None, depth=None, + bint construct=False, + bint indicate_mem_err=True): r""" EXAMPLES:: @@ -1534,7 +1535,10 @@ cdef void free_cgd_2(void *data): cdef canonical_generator_data *cgd = data deallocate_cgd(cgd) -def generate_dense_graphs_vert_addition(int n, base_G = None, bint construct = False, bint indicate_mem_err = True): + +def generate_dense_graphs_vert_addition(int n, base_G=None, + bint construct=False, + bint indicate_mem_err=True): r""" EXAMPLES:: diff --git a/src/sage/groups/perm_gps/partn_ref/refinement_sets.pyx b/src/sage/groups/perm_gps/partn_ref/refinement_sets.pyx index 1ae52e88426..6e80294db3c 100644 --- a/src/sage/groups/perm_gps/partn_ref/refinement_sets.pyx +++ b/src/sage/groups/perm_gps/partn_ref/refinement_sets.pyx @@ -685,7 +685,9 @@ cdef iterator *setup_set_gen(iterator *subset_gen, int degree, int max_size): bitset_clear(&empty_set.bits) return subset_iterator -def sets_modulo_perm_group(list generators, int max_size, bint indicate_mem_err = 1): + +def sets_modulo_perm_group(list generators, int max_size, + bint indicate_mem_err=1): r""" Given generators of a permutation group, list subsets up to permutations in the group. diff --git a/src/sage/groups/perm_gps/partn_ref2/refinement_generic.pyx b/src/sage/groups/perm_gps/partn_ref2/refinement_generic.pyx index 2fcb0363a8b..f2ccca042ac 100644 --- a/src/sage/groups/perm_gps/partn_ref2/refinement_generic.pyx +++ b/src/sage/groups/perm_gps/partn_ref2/refinement_generic.pyx @@ -639,7 +639,7 @@ cdef class PartitionRefinement_generic: self._backtrack(True) self._finish_latex() - cdef void _backtrack(self, bint first_step = False): + cdef void _backtrack(self, bint first_step=False): r""" Backtracking with pruning. @@ -913,7 +913,7 @@ cdef class PartitionRefinement_generic: "\\begin{tikzpicture}\n" + "\\tikzset{level distance=3cm, edge from parent/.style=" + "{draw, edge from parent path={(\\tikzparentnode.south) -- (\\tikzchildnode.north)}}}\n" + - "\Tree") + "\\Tree") self._latex_debug_string += "[." self._latex_act_node() diff --git a/src/sage/groups/perm_gps/permgroup.py b/src/sage/groups/perm_gps/permgroup.py index 49089346995..0a447179cf8 100644 --- a/src/sage/groups/perm_gps/permgroup.py +++ b/src/sage/groups/perm_gps/permgroup.py @@ -532,6 +532,20 @@ def __init__(self, gens=None, gap_group=None, canonicalize=True, _libgap = None + def __copy__(self): + r""" + Return a "copy" of ``self`` by returning ``self``. + + EXAMPLES:: + + sage: G = PermutationGroup(((1,2), (4,5))) + sage: copy(G) is G + True + """ + return self + + __deepcopy__ = __copy__ + def construction(self): """ Return the construction of ``self``. @@ -2781,7 +2795,7 @@ def semidirect_product(self, N, mapping, check=True): from sage.categories.finite_permutation_groups import FinitePermutationGroups if N not in FinitePermutationGroups(): raise TypeError("{0} is not a permutation group".format(N)) - if not PermutationGroup(gens = mapping[0]) == self: + if not PermutationGroup(gens=mapping[0]) == self: msg = 'the generator list must generate the calling group, {0} does not generate {1}' raise ValueError(msg.format(mapping[0], self._repr_())) if len(mapping[0]) != len(mapping[1]): @@ -3159,7 +3173,7 @@ def commutator(self, other=None): return PermutationGroup(gap_group=gap_group) @hap_decorator - def cohomology(self, n, p = 0): + def cohomology(self, n, p=0): r""" Computes the group cohomology `H^n(G, F)`, where `F = \ZZ` if `p=0` and `F = \ZZ / p \ZZ` if `p > 0` is a prime. @@ -3208,7 +3222,7 @@ def cohomology(self, n, p = 0): return AbelianGroup(len(L), L) @hap_decorator - def cohomology_part(self, n, p = 0): + def cohomology_part(self, n, p=0): r""" Compute the p-part of the group cohomology `H^n(G, F)`, where `F = \ZZ` if `p=0` and `F = \ZZ / p \ZZ` if @@ -3244,7 +3258,7 @@ def cohomology_part(self, n, p = 0): return AbelianGroup(len(L), L) @hap_decorator - def homology(self, n, p = 0): + def homology(self, n, p=0): r""" Computes the group homology `H_n(G, F)`, where `F = \ZZ` if `p=0` and `F = \ZZ / p \ZZ` if @@ -3292,7 +3306,7 @@ def homology(self, n, p = 0): return AbelianGroup(len(L), L) @hap_decorator - def homology_part(self, n, p = 0): + def homology_part(self, n, p=0): r""" Computes the `p`-part of the group homology `H_n(G, F)`, where `F = \ZZ` if `p=0` and @@ -3616,7 +3630,7 @@ def _regular_subgroup_gap(self): return C @cached_method - def has_regular_subgroup(self, return_group = False): + def has_regular_subgroup(self, return_group=False): r""" Return whether the group contains a regular subgroup. @@ -3656,12 +3670,10 @@ def has_regular_subgroup(self, return_group = False): b = (C is not None) if b and return_group: G = self.subgroup(gap_group=C.Representative()) - if return_group: - return G - else: - return b - def blocks_all(self, representatives = True): + return G if return_group else b + + def blocks_all(self, representatives=True): r""" Return the list of block systems of imprimitivity. @@ -4190,7 +4202,7 @@ def is_normal(self, other): sage: H.is_normal(G) False """ - if not(self.is_subgroup(other)): + if not self.is_subgroup(other): raise TypeError("%s must be a subgroup of %s" % (self, other)) return bool(other._libgap_().IsNormal(self)) diff --git a/src/sage/groups/perm_gps/permgroup_element.pyx b/src/sage/groups/perm_gps/permgroup_element.pyx index 037884f55da..840273a3eea 100644 --- a/src/sage/groups/perm_gps/permgroup_element.pyx +++ b/src/sage/groups/perm_gps/permgroup_element.pyx @@ -1175,7 +1175,7 @@ cdef class PermutationGroupElement(MultiplicativeGroupElement): return result cpdef _act_on_(self, x, bint self_on_left): - """ + r""" Return the result of the action of ``self`` on ``x``. For example, if ``x=f(z)`` is a polynomial, then this function returns @@ -1619,7 +1619,7 @@ cdef class PermutationGroupElement(MultiplicativeGroupElement): return ~self def sign(self): - """ + r""" Returns the sign of self, which is `(-1)^{s}`, where `s` is the number of swaps. @@ -1849,20 +1849,20 @@ cdef class PermutationGroupElement(MultiplicativeGroupElement): [2, 1, 1] """ cycle_type = [len(c) for c in self.cycle_tuples(singletons)] - cycle_type.sort(reverse = True) + cycle_type.sort(reverse=True) if as_list: return cycle_type else: from sage.combinat.partition import _Partitions return _Partitions(cycle_type) - def has_descent(self, i, side = "right", positive = False): - """ + def has_descent(self, i, side="right", positive=False): + r""" INPUT: - - ``i``: an element of the index set - - ``side``: "left" or "right" (default: "right") - - ``positive``: a boolean (default: False) + - ``i`` -- an element of the index set + - ``side`` -- "left" or "right" (default: "right") + - ``positive`` -- a boolean (default: False) Returns whether ``self`` has a left (resp. right) descent at position ``i``. If ``positive`` is True, then test for a non diff --git a/src/sage/groups/perm_gps/permgroup_named.py b/src/sage/groups/perm_gps/permgroup_named.py index 43e47f7574e..d5078f03b40 100644 --- a/src/sage/groups/perm_gps/permgroup_named.py +++ b/src/sage/groups/perm_gps/permgroup_named.py @@ -1745,7 +1745,7 @@ def __init__(self, n): """ n = Integer(n) self._n = n - if not(n in [9, 10, 11, 12, 21, 22, 23, 24]): + if n not in [9, 10, 11, 12, 21, 22, 23, 24]: raise ValueError("argument must belong to {9, 10, 11, 12, 21, 22, 23, 24}") id = 'MathieuGroup(%s)' % n PermutationGroup_generic.__init__(self, gap_group=id) @@ -2702,7 +2702,7 @@ def __init__(self, n, q, name='a'): if q in FiniteFields(): name = q.gen() q = q.cardinality() - if not(q in NonNegativeIntegers()): + if q not in NonNegativeIntegers(): raise ValueError('q must be a prime power or a finite field') if n == 1: id = 'Group([()])' diff --git a/src/sage/groups/raag.py b/src/sage/groups/raag.py index 79c8ec88604..59b78c6d757 100644 --- a/src/sage/groups/raag.py +++ b/src/sage/groups/raag.py @@ -39,7 +39,7 @@ from sage.combinat.free_module import CombinatorialFreeModule from sage.categories.fields import Fields from sage.categories.algebras_with_basis import AlgebrasWithBasis -from sage.algebras.clifford_algebra import CliffordAlgebraElement +from sage.algebras.clifford_algebra_element import CohomologyRAAGElement from sage.typeset.ascii_art import ascii_art from sage.typeset.unicode_art import unicode_art @@ -853,54 +853,8 @@ def degree_on_basis(self, I): """ return len(I) - class Element(CliffordAlgebraElement): + class Element(CohomologyRAAGElement): """ An element in the cohomology ring of a right-angled Artin group. """ - def _mul_(self, other): - """ - Return ``self`` multiplied by ``other``. - - EXAMPLES:: - - sage: C4 = graphs.CycleGraph(4) - sage: A = groups.misc.RightAngledArtin(C4) - sage: H = A.cohomology() - sage: b = sum(H.basis()) - sage: b * b - 2*e0*e2 + 2*e1*e3 + 2*e0 + 2*e1 + 2*e2 + 2*e3 + 1 - """ - zero = self.parent().base_ring().zero() - I = self.parent()._indices - d = {} - - for ml,cl in self: - for mr,cr in other: - # Create the next term - t = list(mr) - for i in reversed(ml): - pos = 0 - for j in t: - if i == j: - pos = None - break - if i < j: - break - pos += 1 - cr = -cr - if pos is None: - t = None - break - t.insert(pos, i) - - if t is None: # The next term is 0, move along - continue - - t = tuple(t) - if t not in I: # not an independent set, so this term is also 0 - continue - d[t] = d.get(t, zero) + cl * cr - if d[t] == zero: - del d[t] - return self.__class__(self.parent(), d) diff --git a/src/sage/homology/algebraic_topological_model.py b/src/sage/homology/algebraic_topological_model.py index b14de9bdfa3..f89a1529dd9 100644 --- a/src/sage/homology/algebraic_topological_model.py +++ b/src/sage/homology/algebraic_topological_model.py @@ -12,7 +12,6 @@ - John H. Palmieri (2015-09) """ - ######################################################################## # Copyright (C) 2015 John H. Palmieri # @@ -20,7 +19,7 @@ # 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/ ######################################################################## # TODO: cythonize this. @@ -590,4 +589,3 @@ def conditionally_sparse(m): iota = ChainComplexMorphism(iota_data, M, C) phi = ChainContraction(phi_data, pi, iota) return phi, M - diff --git a/src/sage/homology/chain_complex.py b/src/sage/homology/chain_complex.py index 99fa87c9751..d88389e5c6d 100644 --- a/src/sage/homology/chain_complex.py +++ b/src/sage/homology/chain_complex.py @@ -1154,7 +1154,7 @@ def _homology_chomp(self, deg, base_ring, verbose, generators): # one has to complete the answer of chomp result = H for idx in self.nonzero_degrees(): - if not(idx in H): + if idx not in H: result[idx] = HomologyGroup(0, base_ring) return result if deg in H: @@ -2238,4 +2238,3 @@ def scalar(a): from sage.misc.persist import register_unpickle_override register_unpickle_override('sage.homology.chain_complex', 'ChainComplex', ChainComplex_class) - diff --git a/src/sage/homology/chain_homotopy.py b/src/sage/homology/chain_homotopy.py index feac17c26b7..16a1c385edc 100644 --- a/src/sage/homology/chain_homotopy.py +++ b/src/sage/homology/chain_homotopy.py @@ -39,13 +39,14 @@ # 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.categories.morphism import Morphism from sage.categories.homset import Hom from sage.homology.chain_complex_morphism import ChainComplexMorphism + # In a perfect world, this would inherit from something like # "TwoMorphism" rather than "Morphism"... class ChainHomotopy(Morphism): @@ -581,4 +582,3 @@ def dual(self): deg = self.domain().degree_of_differential() matrices = {i-deg: matrix_dict[i].transpose() for i in matrix_dict} return ChainContraction(matrices, self.iota().dual(), self.pi().dual()) - diff --git a/src/sage/homology/examples.py b/src/sage/homology/examples.py index 704b1219189..c469f9d6c54 100644 --- a/src/sage/homology/examples.py +++ b/src/sage/homology/examples.py @@ -21,7 +21,7 @@ 'SurfaceOfGenus', 'MooreSpace', 'ComplexProjectivePlane', - 'PseudoQuaternionicProjectivePlane', + 'QuaternionicProjectivePlane', 'PoincareHomologyThreeSphere', 'RealProjectiveSpace', 'K3Surface', diff --git a/src/sage/homology/free_resolution.py b/src/sage/homology/free_resolution.py new file mode 100644 index 00000000000..7d3ea29057e --- /dev/null +++ b/src/sage/homology/free_resolution.py @@ -0,0 +1,894 @@ +r""" +Free resolutions + +Let `R` be a commutative ring. A finite free resolution of an `R`-module `M` +is a chain complex of free `R`-modules + +.. MATH:: + + R^{n_1} \xleftarrow{d_1} R^{n_1} \xleftarrow{d_2} + \cdots \xleftarrow{d_k} R^{n_k} \xleftarrow{d_{k+1}} 0 + +terminating with a zero module at the end that is exact (all homology groups +are zero) such that the image of `d_1` is `M`. + +EXAMPLES:: + + sage: from sage.homology.free_resolution import FreeResolution + sage: S. = PolynomialRing(QQ) + sage: m = matrix(S, 1, [z^2 - y*w, y*z - x*w, y^2 - x*z]).transpose() + sage: r = FreeResolution(m, name='S') + sage: r + S^1 <-- S^3 <-- S^2 <-- 0 + + sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) + sage: r = I.free_resolution() + sage: r + S^1 <-- S^3 <-- S^2 <-- 0 + +:: + + sage: S. = PolynomialRing(QQ) + sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) + sage: r = I.graded_free_resolution() + sage: r + S(0) <-- S(-2)⊕S(-2)⊕S(-2) <-- S(-3)⊕S(-3) <-- 0 + +An example of a minimal free resolution from [CLO2005]_:: + + sage: R. = QQ[] + sage: I = R.ideal([y*z - x*w, y^3 - x^2*z, x*z^2 - y^2*w, z^3 - y*w^2]) + sage: r = I.free_resolution() + sage: r + S^1 <-- S^4 <-- S^4 <-- S^1 <-- 0 + sage: len(r) + 3 + sage: r.matrix(2) + [-z^2 -x*z y*w -y^2] + [ y 0 -x 0] + [ -w y z x] + [ 0 w 0 z] + +AUTHORS: + +- Kwankyu Lee (2022-05-13): initial version +- Travis Scrimshaw (2022-08-23): refactored for free module inputs +""" + +# **************************************************************************** +# Copyright (C) 2022 Kwankyu Lee +# (C) 2022 Travis Scrimshaw +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from sage.libs.singular.singular import si2sa_resolution +from sage.libs.singular.function import singular_function +from sage.misc.lazy_attribute import lazy_attribute +from sage.misc.abstract_method import abstract_method +from sage.misc.classcall_metaclass import ClasscallMetaclass +from sage.structure.sage_object import SageObject +from sage.structure.element import Matrix +from sage.categories.principal_ideal_domains import PrincipalIdealDomains +from sage.categories.integral_domains import IntegralDomains +from sage.modules.free_module_element import vector +from sage.modules.free_module import FreeModule +from sage.modules.free_module import Module_free_ambient, FreeModule_generic +from sage.rings.ideal import Ideal_generic + +from copy import copy + + +class FreeResolution(SageObject, metaclass=ClasscallMetaclass): + r""" + A free resolution. + + Let `R` be a commutative ring. A *free resolution* of an `R`-module `M` + is a (possibly infinite) chain complex of free `R`-modules + + .. MATH:: + + R^{n_1} \xleftarrow{d_1} R^{n_1} \xleftarrow{d_2} + \cdots \xleftarrow{d_k} R^{n_k} \xleftarrow{d_{k+1}} \cdots + + that is exact (all homology groups are zero) such that the image + of `d_1` is `M`. + """ + @staticmethod + def __classcall_private__(cls, module, *args, graded=False, degrees=None, shifts=None, **kwds): + """ + Dispatch to the correct constructor. + + TESTS:: + + sage: from sage.homology.free_resolution import FreeResolution + sage: S. = PolynomialRing(QQ) + sage: m = matrix(S, 1, [z^2 - y*w, y*z - x*w, y^2 - x*z]).transpose() + sage: r = FreeResolution(m, name='S') + sage: type(r) + + + sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) + sage: r = FreeResolution(I) + sage: type(r) + + + sage: R. = QQ[] + sage: M = R^3 + sage: v = M([x^2, 2*x^2, 3*x^2]) + sage: w = M([0, x, 2*x]) + sage: S = M.submodule([v, w]) + sage: r = FreeResolution(S) + sage: type(r) + + + sage: I = R.ideal([x^4 + 3*x^2 + 2]) + sage: r = FreeResolution(I) + sage: type(r) + + + sage: R. = QQ[] + sage: I = R.ideal([x^2, y^3]) + sage: Q = R.quo(I) + sage: Q.is_integral_domain() + False + sage: xb, yb = Q.gens() + sage: FreeResolution(Q.ideal([xb])) # has torsion + Traceback (most recent call last): + ... + NotImplementedError: the ring must be a polynomial ring using Singular + """ + if degrees is not None or shifts is not None: + graded = True + + if isinstance(module, Matrix): + is_free_module = False + S = module.base_ring() + if S in PrincipalIdealDomains(): + module = module.echelon_form() + if module.nrows() > module.rank(): + module = module.submatrix(nrows=module.rank()) + module.set_immutable() + is_free_module = True + if not module.is_immutable(): + # We need to make an immutable copy of the matrix + module = copy(module) + module.set_immutable() + if is_free_module: + if graded: + from sage.homology.graded_resolution import GradedFiniteFreeResolution_free_module + return GradedFiniteFreeResolution_free_module(module, + *args, + degrees=degrees, + shifts=shifts, + **kwds) + return FiniteFreeResolution_free_module(module, *args, **kwds) + + from sage.rings.polynomial.multi_polynomial_libsingular import MPolynomialRing_libsingular + if not isinstance(S, MPolynomialRing_libsingular): + raise NotImplementedError("the matrix must be over a PID or a " + " polynomial ring that is using Singular") + + if graded: + # We are computing a graded resolution + from sage.homology.graded_resolution import GradedFiniteFreeResolution_singular + return GradedFiniteFreeResolution_singular(module, *args, degrees=degrees, + shifts=shifts, **kwds) + + return FiniteFreeResolution_singular(module, **kwds) + + if graded: + return module.graded_free_resolution(*args, **kwds) + return module.free_resolution(*args, **kwds) + + def __init__(self, module, name='S', **kwds): + """ + Initialize ``self``. + + INPUT: + + - ``base_ring`` -- a ring + - ``name`` -- (default: ``'S'``) the name of the ring for printing + + TESTS:: + + sage: from sage.homology.free_resolution import FreeResolution + sage: S. = PolynomialRing(QQ) + sage: m1 = matrix(S, 1, [z^2 - y*w, y*z - x*w, y^2 - x*z]) + sage: r = FreeResolution(m1, name='S') + sage: TestSuite(r).run(skip=['_test_pickling']) + """ + if isinstance(module, Ideal_generic): + S = module.ring() + else: # module or matrix + S = module.base_ring() + + self._base_ring = S + self._name = name + self._module = module + + def _repr_(self): + r""" + Return a string representation of ``self``. + + TESTS:: + + sage: from sage.homology.free_resolution import FreeResolution + sage: S. = PolynomialRing(QQ) + sage: m1 = matrix(S, 1, [z^2 - y*w, y*z - x*w, y^2 - x*z]) + sage: r = FreeResolution(m1, name='S') + sage: print(FreeResolution._repr_(r)) + Free resolution of the row space of the matrix: + [z^2 - y*w y*z - x*w y^2 - x*z] + """ + if isinstance(self._module, Matrix): + return f"Free resolution of the row space of the matrix:\n{self._module}" + return f"Free resolution of {self._module}" + + def _repr_module(self, i): + r""" + Return the string form of the `i`-th free module. + + INPUT: + + - ``i`` -- a positive integer + + EXAMPLES:: + + sage: from sage.homology.free_resolution import FreeResolution + sage: S. = PolynomialRing(QQ) + sage: m = matrix(S, 1, [y*w - z^2, -x*w + y*z, x*z - y^2]) + sage: r = FreeResolution(m.transpose(), name='S') + sage: r._repr_module(2) + 'S^2' + sage: r # indirect doctest + S^1 <-- S^3 <-- S^2 <-- 0 + """ + if i == 0: + r = self._maps[0].nrows() + s = f'{self._name}^{r}' + return s + elif i > self._length: + s = '0' + else: + r = self._maps[i - 1].ncols() + if r > 0: + s = f'{self._name}^{r}' + else: + s = '0' + return s + + @abstract_method + def differential(self, i): + r""" + Return the ``i``-th differential map. + + INPUT: + + - ``i`` -- a positive integer + + TESTS:: + + sage: from sage.homology.free_resolution import FreeResolution + sage: S. = PolynomialRing(QQ) + sage: m1 = matrix(S, 1, [z^2 - y*w, y*z - x*w, y^2 - x*z]) + sage: r = FreeResolution(m1, name='S') + sage: FreeResolution.differiental(r, 1) + Traceback (most recent call last): + ... + AttributeError: type object 'FreeResolution' has no attribute 'differiental' + """ + + def target(self): + r""" + Return the codomain of the `0`-th differential map. + + The codomain of the `0`-th differential map is the cokernel of + the first differential map. + + EXAMPLES:: + + sage: S. = PolynomialRing(QQ) + sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) + sage: r = I.graded_free_resolution() + sage: r + S(0) <-- S(-2)⊕S(-2)⊕S(-2) <-- S(-3)⊕S(-3) <-- 0 + sage: r.target() + Quotient module by Submodule of Ambient free module of rank 1 over the integral domain + Multivariate Polynomial Ring in x, y, z, w over Rational Field + Generated by the rows of the matrix: + [-z^2 + y*w] + [ y*z - x*w] + [-y^2 + x*z] + """ + return self.differential(0).codomain() + + +class FiniteFreeResolution(FreeResolution): + r""" + Finite free resolutions. + + The matrix at index `i` in the list defines the differential map from + `(i + 1)`-th free module to the `i`-th free module over the base ring by + multiplication on the left. The number of matrices in the list is the + length of the resolution. The number of rows and columns of the matrices + define the ranks of the free modules in the resolution. + + Note that the first matrix in the list defines the differential map at + homological index `1`. + + A subclass must provide a ``_maps`` attribute that contains a list of the + maps defining the resolution. + + A subclass can define ``_initial_differential`` attribute that + contains the `0`-th differential map whose codomain is the target + of the free resolution. + + EXAMPLES:: + + sage: from sage.homology.free_resolution import FreeResolution + sage: S. = PolynomialRing(QQ) + sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) + sage: r = FreeResolution(I) + sage: r.differential(0) + Coercion map: + From: Ambient free module of rank 1 over the integral domain + Multivariate Polynomial Ring in x, y, z, w over Rational Field + To: Quotient module by Submodule of Ambient free module of rank 1 + over the integral domain Multivariate Polynomial Ring in x, y, z, w over Rational Field + Generated by the rows of the matrix: + [-z^2 + y*w] + [ y*z - x*w] + [-y^2 + x*z] + """ + @lazy_attribute + def _length(self): + """ + The length of ``self``. + + TESTS:: + + sage: from sage.homology.free_resolution import FreeResolution + sage: S. = PolynomialRing(QQ) + sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) + sage: r = FreeResolution(I) + sage: r._length + 2 + """ + return len(self._maps) + + def _repr_(self): + """ + Return the string form of this resolution. + + INPUT: + + - ``i`` -- a positive integer + + EXAMPLES:: + + sage: S. = PolynomialRing(QQ) + sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) + sage: r = I.graded_free_resolution() + sage: r + S(0) <-- S(-2)⊕S(-2)⊕S(-2) <-- S(-3)⊕S(-3) <-- 0 + """ + s = self._repr_module(0) + for i in range(1, self._length + 1): + s += ' <-- ' + self._repr_module(i) + s += ' <-- 0' + return s + + def __len__(self): + r""" + Return the length of this resolution. + + The length of a free resolution is the index of the last nonzero free module. + + EXAMPLES:: + + sage: S. = PolynomialRing(QQ) + sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) + sage: r = I.graded_free_resolution() + sage: r + S(0) <-- S(-2)⊕S(-2)⊕S(-2) <-- S(-3)⊕S(-3) <-- 0 + sage: len(r) + 2 + """ + return len(self._maps) + + def __getitem__(self, i): + r""" + Return the ``i``-th free module of this resolution. + + INPUT: + + - ``i`` -- a positive integer + + EXAMPLES:: + + sage: S. = PolynomialRing(QQ) + sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) + sage: r = I.graded_free_resolution() + sage: r + S(0) <-- S(-2)⊕S(-2)⊕S(-2) <-- S(-3)⊕S(-3) <-- 0 + sage: r.target() + Quotient module by Submodule of Ambient free module of rank 1 over the integral domain + Multivariate Polynomial Ring in x, y, z, w over Rational Field + Generated by the rows of the matrix: + [-z^2 + y*w] + [ y*z - x*w] + [-y^2 + x*z] + """ + if i < 0: + raise IndexError('invalid index') + elif i > self._length: + F = FreeModule(self._base_ring, 0) + elif i == self._length: + F = FreeModule(self._base_ring, self._maps[i - 1].ncols()) + else: + F = FreeModule(self._base_ring, self._maps[i].nrows()) + return F + + def differential(self, i): + r""" + Return the ``i``-th differential map. + + INPUT: + + - ``i`` -- a positive integer + + EXAMPLES:: + + sage: S. = PolynomialRing(QQ) + sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) + sage: r = I.graded_free_resolution() + sage: r + S(0) <-- S(-2)⊕S(-2)⊕S(-2) <-- S(-3)⊕S(-3) <-- 0 + sage: r.differential(3) + Free module morphism defined by the matrix + [] + Domain: Ambient free module of rank 0 over the integral domain + Multivariate Polynomial Ring in x, y, z, w over Rational Field + Codomain: Ambient free module of rank 2 over the integral domain + Multivariate Polynomial Ring in x, y, z, w over Rational Field + sage: r.differential(2) + Free module morphism defined as left-multiplication by the matrix + [-y x] + [ z -y] + [-w z] + Domain: Ambient free module of rank 2 over the integral domain + Multivariate Polynomial Ring in x, y, z, w over Rational Field + Codomain: Ambient free module of rank 3 over the integral domain + Multivariate Polynomial Ring in x, y, z, w over Rational Field + sage: r.differential(1) + Free module morphism defined as left-multiplication by the matrix + [z^2 - y*w y*z - x*w y^2 - x*z] + Domain: Ambient free module of rank 3 over the integral domain + Multivariate Polynomial Ring in x, y, z, w over Rational Field + Codomain: Ambient free module of rank 1 over the integral domain + Multivariate Polynomial Ring in x, y, z, w over Rational Field + sage: r.differential(0) + Coercion map: + From: Ambient free module of rank 1 over the integral domain + Multivariate Polynomial Ring in x, y, z, w over Rational Field + To: Quotient module by Submodule of Ambient free module of rank 1 over the integral domain + Multivariate Polynomial Ring in x, y, z, w over Rational Field + Generated by the rows of the matrix: + [-z^2 + y*w] + [ y*z - x*w] + [-y^2 + x*z] + """ + if i < 0: + raise IndexError('invalid index') + elif i == 0: + try: + return self._initial_differential + except AttributeError: + raise ValueError('0th differential map undefined') + elif i == self._length + 1: + s = FreeModule(self._base_ring, 0) + t = FreeModule(self._base_ring, self._maps[i - 2].ncols()) + m = s.hom(0, t) + elif i > self._length + 1: + s = FreeModule(self._base_ring, 0) + t = FreeModule(self._base_ring, 0) + m = s.hom(0, t) + else: + s = FreeModule(self._base_ring, self._maps[i - 1].ncols()) + t = FreeModule(self._base_ring, self._maps[i - 1].nrows()) + m = s.hom(self._maps[i - 1], t, side='right') + return m + + def matrix(self, i): + r""" + Return the matrix representing the ``i``-th differential map. + + INPUT: + + - ``i`` -- a positive integer + + EXAMPLES:: + + sage: S. = PolynomialRing(QQ) + sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) + sage: r = I.graded_free_resolution() + sage: r + S(0) <-- S(-2)⊕S(-2)⊕S(-2) <-- S(-3)⊕S(-3) <-- 0 + sage: r.matrix(3) + [] + sage: r.matrix(2) + [-y x] + [ z -y] + [-w z] + sage: r.matrix(1) + [z^2 - y*w y*z - x*w y^2 - x*z] + """ + if i <= 0: + raise IndexError('invalid index') + elif i <= self._length: + return self._maps[i - 1] + else: + return self.differential(i).matrix() + + def chain_complex(self): + r""" + Return this resolution as a chain complex. + + A chain complex in Sage has its own useful methods. + + EXAMPLES:: + + sage: S. = PolynomialRing(QQ) + sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) + sage: r = I.graded_free_resolution() + sage: unicode_art(r.chain_complex()) + ⎛-y x⎞ + ⎜ z -y⎟ + (z^2 - y*w y*z - x*w y^2 - x*z) ⎝-w z⎠ + 0 <── C_0 <────────────────────────────── C_1 <────── C_2 <── 0 + """ + from sage.homology.chain_complex import ChainComplex + mats = {} + for i in range(self._length, 0, -1): + mats[i] = self.matrix(i) + return ChainComplex(mats, degree_of_differential=-1) + + @lazy_attribute + def _initial_differential(self): + r""" + Define the `0`-th differential map of this resolution. + + EXAMPLES:: + + sage: from sage.homology.free_resolution import FreeResolution + sage: S. = PolynomialRing(QQ) + sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) + sage: r = FreeResolution(I) + sage: r._initial_differential + Coercion map: + From: Ambient free module of rank 1 over the integral domain + Multivariate Polynomial Ring in x, y, z, w over Rational Field + To: Quotient module by Submodule of Ambient free module of rank 1 + over the integral domain Multivariate Polynomial Ring in x, y, z, w over Rational Field + Generated by the rows of the matrix: + [-z^2 + y*w] + [ y*z - x*w] + [-y^2 + x*z] + """ + module = self._module + if isinstance(module, Ideal_generic): + S = module.ring() + M = FreeModule(S, 1) + N = M.submodule([vector([g]) for g in module.gens()]) + elif isinstance(module, Module_free_ambient): + S = module.base_ring() + M = module.ambient_module() + N = module + elif isinstance(module, Matrix): + S = module.base_ring() + N = module.row_space() + M = N.ambient_module() + Q = M.quotient(N) + return Q.coerce_map_from(M) + + def _m(self): + r""" + Return the matrix whose column space is ``self._module``. + + If ``self._module`` is an ideal, then just the ideal is returned. + + TESTS:: + + sage: from sage.homology.free_resolution import FreeResolution + sage: S. = PolynomialRing(QQ) + sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) + sage: r = FreeResolution(I) + sage: r._m() + Ideal (-z^2 + y*w, y*z - x*w, -y^2 + x*z) of + Multivariate Polynomial Ring in x, y, z, w over Rational Field + + sage: m = matrix(S, 1, [z^2 - y*w, y*z - x*w, y^2 - x*z]).transpose() + sage: r = FreeResolution(m, name='S') + sage: r._m() + [z^2 - y*w y*z - x*w y^2 - x*z] + + sage: M = m.image() + sage: r = FreeResolution(M, name='S') + sage: r._m() + [z^2 - y*w y*z - x*w y^2 - x*z] + """ + if isinstance(self._module, Ideal_generic): + return self._module + if isinstance(self._module, Module_free_ambient): + return self._module.matrix().transpose() + if isinstance(self._module, Matrix): + return self._module.transpose() + raise ValueError("unable to create a matrix/ideal to build the resolution") + + +class FiniteFreeResolution_free_module(FiniteFreeResolution): + r""" + Free resolutions of a free module. + + INPUT: + + - ``module`` -- a free module or ideal over a PID + - ``name`` -- the name of the base ring + + EXAMPLES:: + + sage: R. = QQ[] + sage: M = R^3 + sage: v = M([x^2, 2*x^2, 3*x^2]) + sage: w = M([0, x, 2*x]) + sage: S = M.submodule([v, w]) + sage: S + Free module of degree 3 and rank 2 over + Univariate Polynomial Ring in x over Rational Field + Echelon basis matrix: + [ x^2 2*x^2 3*x^2] + [ 0 x 2*x] + sage: res = S.free_resolution() + sage: res + S^3 <-- S^2 <-- 0 + sage: ascii_art(res.chain_complex()) + [ x^2 0] + [2*x^2 x] + [3*x^2 2*x] + 0 <-- C_0 <-------------- C_1 <-- 0 + + sage: R. = PolynomialRing(QQ) + sage: I = R.ideal([x^4 + 3*x^2 + 2]) + sage: res = I.free_resolution() + sage: res + S^1 <-- S^1 <-- 0 + """ + @lazy_attribute + def _maps(self): + r""" + Return the maps that define ``self``. + + EXAMPLES:: + + sage: R. = QQ[] + sage: M = R^3 + sage: v = M([x^2, 2*x^2, 3*x^2]) + sage: w = M([0, x, 2*x]) + sage: S = M.submodule([v, w]) + sage: res = S.free_resolution() + sage: res + S^3 <-- S^2 <-- 0 + sage: ascii_art(res.chain_complex()) + [ x^2 0] + [2*x^2 x] + [3*x^2 2*x] + 0 <-- C_0 <-------------- C_1 <-- 0 + sage: res._maps + [ + [ x^2 0] + [2*x^2 x] + [3*x^2 2*x] + ] + + sage: R. = PolynomialRing(QQ) + sage: I = R.ideal([x^4 + 3*x^2 + 2]) + sage: res = I.free_resolution() + sage: res._maps + [[x^4 + 3*x^2 + 2]] + + sage: from sage.homology.free_resolution import FreeResolution + sage: M = matrix([[x^2, 2], + ....: [3*x^2, 5], + ....: [5*x^2, 4]]) + sage: res = FreeResolution(M.transpose()) + sage: res + S^3 <-- S^2 <-- 0 + sage: res._m() + [ 1 0] + [ 5/2 -x^2] + [ 2 -6*x^2] + sage: res._maps + [ + [ 1 0] + [ 5/2 -x^2] + [ 2 -6*x^2] + ] + + An overdetermined system over a PID:: + + sage: res = FreeResolution(M) + sage: res + S^2 <-- S^2 <-- 0 + sage: res._m() + [x^2 0] + [ 2 -1] + sage: res._maps + [ + [x^2 0] + [ 2 -1] + ] + """ + if isinstance(self._module, Ideal_generic): + from sage.matrix.constructor import matrix + return [matrix([[self._module.gen()]])] + return [self._m()] + + +class FiniteFreeResolution_singular(FiniteFreeResolution): + r""" + Minimal free resolutions of ideals or submodules of free modules + of multivariate polynomial rings implemented in Singular. + + INPUT: + + - ``module`` -- a submodule of a free module `M` of rank `n` over `S` or + an ideal of a multi-variate polynomial ring + + - ``name`` -- string (optional); name of the base ring + + - ``algorithm`` -- (default: ``'heuristic'``) Singular algorithm + to compute a resolution of ``ideal`` + + OUTPUT: a minimal free resolution of the ideal + + If ``module`` is an ideal of `S`, it is considered as a submodule of a + free module of rank `1` over `S`. + + The available algorithms and the corresponding Singular commands + are shown below: + + ============= ============================ + algorithm Singular commands + ============= ============================ + ``minimal`` ``mres(ideal)`` + ``shreyer`` ``minres(sres(std(ideal)))`` + ``standard`` ``minres(nres(std(ideal)))`` + ``heuristic`` ``minres(res(std(ideal)))`` + ============= ============================ + + EXAMPLES:: + + sage: from sage.homology.free_resolution import FreeResolution + sage: S. = PolynomialRing(QQ) + sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) + sage: r = FreeResolution(I) + sage: r + S^1 <-- S^3 <-- S^2 <-- 0 + sage: len(r) + 2 + + :: + + sage: FreeResolution(I, algorithm='minimal') + S^1 <-- S^3 <-- S^2 <-- 0 + sage: FreeResolution(I, algorithm='shreyer') + S^1 <-- S^3 <-- S^2 <-- 0 + sage: FreeResolution(I, algorithm='standard') + S^1 <-- S^3 <-- S^2 <-- 0 + sage: FreeResolution(I, algorithm='heuristic') + S^1 <-- S^3 <-- S^2 <-- 0 + + We can also construct a resolution by passing in a matrix defining + the initial differential:: + + sage: m = matrix(S, 1, [z^2 - y*w, y*z - x*w, y^2 - x*z]).transpose() + sage: r = FreeResolution(m, name='S') + sage: r + S^1 <-- S^3 <-- S^2 <-- 0 + sage: r.matrix(1) + [z^2 - y*w y*z - x*w y^2 - x*z] + + An additional construction is using a submodule of a free module:: + + sage: M = m.image() + sage: r = FreeResolution(M, name='S') + sage: r + S^1 <-- S^3 <-- S^2 <-- 0 + + A nonhomogeneous ideal:: + + sage: I = S.ideal([z^2 - y*w, y*z - x*w, y^2 - x]) + sage: R = FreeResolution(I) + sage: R + S^1 <-- S^3 <-- S^3 <-- S^1 <-- 0 + sage: R.matrix(2) + [ y*z - x*w y^2 - x 0] + [-z^2 + y*w 0 y^2 - x] + [ 0 -z^2 + y*w -y*z + x*w] + sage: R.matrix(3) + [ y^2 - x] + [-y*z + x*w] + [ z^2 - y*w] + """ + def __init__(self, module, name='S', algorithm='heuristic', **kwds): + r""" + Initialize ``self``. + + TESTS:: + + sage: from sage.homology.free_resolution import FreeResolution + sage: S. = PolynomialRing(QQ) + sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) + sage: r = FreeResolution(I) + sage: TestSuite(r).run(skip=['_test_pickling']) + + sage: m = matrix(S, 1, [z^2 - y*w, y*z - x*w, y^2 - x*z]).transpose() + sage: r = FreeResolution(m, name='S') + sage: TestSuite(r).run(skip=['_test_pickling']) + + sage: M = m.image() + sage: r = FreeResolution(M, name='S') + sage: TestSuite(r).run(skip=['_test_pickling']) + """ + self._algorithm = algorithm + super().__init__(module, name=name, **kwds) + + @lazy_attribute + def _maps(self): + r""" + Return the maps that define ``self``. + + TESTS:: + + sage: from sage.homology.free_resolution import FreeResolution + sage: S. = PolynomialRing(QQ) + sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) + sage: r = FreeResolution(I) + sage: r._maps + [ + [-y x] + [ z -y] + [z^2 - y*w y*z - x*w y^2 - x*z], [-w z] + ] + """ + # This ensures the first component of the Singular resolution to be a + # module, like the later components. This is important when the + # components are converted to Sage modules. + module = singular_function("module") + mod = module(self._m()) + + if self._algorithm == 'minimal': + mres = singular_function('mres') # syzygy method + r = mres(mod, 0) + elif self._algorithm == 'shreyer': + std = singular_function('std') + sres = singular_function('sres') # Shreyer method + minres = singular_function('minres') + r = minres(sres(std(mod), 0)) + elif self._algorithm == 'standard': + nres = singular_function('nres') # standard basis method + minres = singular_function('minres') + r = minres(nres(mod, 0)) + elif self._algorithm == 'heuristic': + std = singular_function('std') + res = singular_function('res') # heuristic method + minres = singular_function('minres') + r = minres(res(std(mod), 0)) + + return si2sa_resolution(r) + diff --git a/src/sage/homology/graded_resolution.py b/src/sage/homology/graded_resolution.py new file mode 100644 index 00000000000..97a3cb9624b --- /dev/null +++ b/src/sage/homology/graded_resolution.py @@ -0,0 +1,575 @@ +r""" +Graded free resolutions + +Let `R` be a commutative ring. A graded free resolution of a graded +`R`-module `M` is a :mod:`free resolution ` +such that all maps are homogeneous module homomorphisms. + +EXAMPLES:: + + sage: S. = PolynomialRing(QQ) + sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) + sage: r = I.graded_free_resolution(algorithm='minimal') + sage: r + S(0) <-- S(-2)⊕S(-2)⊕S(-2) <-- S(-3)⊕S(-3) <-- 0 + sage: I.graded_free_resolution(algorithm='shreyer') + S(0) <-- S(-2)⊕S(-2)⊕S(-2) <-- S(-3)⊕S(-3) <-- 0 + sage: I.graded_free_resolution(algorithm='standard') + S(0) <-- S(-2)⊕S(-2)⊕S(-2) <-- S(-3)⊕S(-3) <-- 0 + sage: I.graded_free_resolution(algorithm='heuristic') + S(0) <-- S(-2)⊕S(-2)⊕S(-2) <-- S(-3)⊕S(-3) <-- 0 + +:: + + sage: d = r.differential(2) + sage: d + Free module morphism defined as left-multiplication by the matrix + [ y x] + [-z -y] + [ w z] + Domain: Ambient free module of rank 2 over the integral domain Multivariate Polynomial Ring + in x, y, z, w over Rational Field + Codomain: Ambient free module of rank 3 over the integral domain Multivariate Polynomial Ring + in x, y, z, w over Rational Field + sage: d.image() + Submodule of Ambient free module of rank 3 over the integral domain Multivariate Polynomial Ring + in x, y, z, w over Rational Field + Generated by the rows of the matrix: + [ y -z w] + [ x -y z] + sage: m = d.image() + sage: m.graded_free_resolution(shifts=(2,2,2)) + S(-2)⊕S(-2)⊕S(-2) <-- S(-3)⊕S(-3) <-- 0 + +An example of multigraded resolution from Example 9.1 of [MilStu2005]_:: + + sage: R. = QQ[] + sage: S. = QQ[] + sage: phi = S.hom([s, s*t, s*t^2, s*t^3]) + sage: I = phi.kernel(); I + Ideal (c^2 - b*d, b*c - a*d, b^2 - a*c) of Multivariate Polynomial Ring in a, b, c, d over Rational Field + sage: P3 = ProjectiveSpace(S) + sage: C = P3.subscheme(I) # twisted cubic curve + sage: r = I.graded_free_resolution(degrees=[(1,0), (1,1), (1,2), (1,3)]) + sage: r + S((0, 0)) <-- S((-2, -4))⊕S((-2, -3))⊕S((-2, -2)) <-- S((-3, -5))⊕S((-3, -4)) <-- 0 + sage: r.K_polynomial(names='s,t') + s^3*t^5 + s^3*t^4 - s^2*t^4 - s^2*t^3 - s^2*t^2 + 1 + +AUTHORS: + +- Kwankyu Lee (2022-05): initial version +- Travis Scrimshaw (2022-08-23): refactored for free module inputs +""" + +# **************************************************************************** +# Copyright (C) 2022 Kwankyu Lee +# (C) 2022 Travis Scrimshaw +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from sage.libs.singular.singular import si2sa_resolution_graded +from sage.libs.singular.function import singular_function +from sage.misc.lazy_attribute import lazy_attribute +from sage.structure.element import Matrix +from sage.modules.free_module_element import vector +from sage.modules.free_module import Module_free_ambient +from sage.rings.integer_ring import ZZ +from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing +from sage.rings.ideal import Ideal_generic + +from sage.homology.free_resolution import (FiniteFreeResolution, + FiniteFreeResolution_free_module, + FiniteFreeResolution_singular) + +class GradedFiniteFreeResolution(FiniteFreeResolution): + r""" + Graded finite free resolutions. + + INPUT: + + - ``module`` -- a homogeneous submodule of a free module `M` of rank `n` + over `S` or a homogeneous ideal of a multivariate polynomial ring `S` + - ``degrees`` -- (default: a list with all entries `1`) a list of integers + or integer vectors giving degrees of variables of `S` + - ``shifts`` -- a list of integers or integer vectors giving shifts of + degrees of `n` summands of the free module `M`; this is a list of zero + degrees of length `n` by default + - ``name`` -- a string; name of the base ring + + .. WARNING:: + + This does not check that the module is homogeneous. + """ + def __init__(self, module, degrees=None, shifts=None, name='S', **kwds): + r""" + Initialize ``self``. + + TESTS:: + + sage: S. = PolynomialRing(QQ) + sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) + sage: r = I.graded_free_resolution() + sage: TestSuite(r).run(skip=['_test_pickling']) + + An overdetermined system over a PID:: + + sage: from sage.homology.free_resolution import FreeResolution + sage: M = matrix([[x^2, 2*x^2], + ....: [3*x^2, 5*x^2], + ....: [5*x^2, 4*x^2]]) + sage: res = FreeResolution(M, graded=True) + sage: res + S(0)⊕S(0) <-- S(-2)⊕S(-2) <-- 0 + sage: res._res_shifts + [[2, 2]] + """ + super().__init__(module, name=name, **kwds) + + nvars = self._base_ring.ngens() + + if degrees is None: + degrees = nvars * (1,) # standard grading + + if len(degrees) != nvars: + raise ValueError('the length of degrees does not match the number of generators') + + if degrees[0] in ZZ: + zero_deg = 0 + multigrade = False + else: # degrees are integer vectors + degrees = tuple([vector(v) for v in degrees]) + zero_deg = degrees[0].parent().zero() + multigrade = True + + if shifts is None: + if isinstance(self._module, Ideal_generic): + rank = 1 + elif isinstance(self._module, Module_free_ambient): + rank = self._m().nrows() + elif isinstance(self._module, Matrix): + rank = self._module.ncols() + + shifts = rank * [zero_deg] + + self._shifts = shifts + self._degrees = tuple(degrees) + self._multigrade = multigrade + self._zero_deg = zero_deg + + def _repr_module(self, i): + """ + EXAMPLES:: + + sage: S. = PolynomialRing(QQ) + sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) + sage: r = I.graded_free_resolution() + sage: r._repr_module(0) + 'S(0)' + sage: r._repr_module(1) + 'S(-2)⊕S(-2)⊕S(-2)' + sage: r._repr_module(2) + 'S(-3)⊕S(-3)' + sage: r._repr_module(3) + '0' + + sage: r = I.graded_free_resolution(shifts=[-1]) + sage: r._repr_module(0) + 'S(1)' + """ + self._maps # to set _res_shifts + if i > len(self): + return '0' + + if i == 0: + shifts = self._shifts + else: + shifts = self._res_shifts[i - 1] + + if not shifts: + return '0' + + return '\u2295'.join(f'{self._name}' + '({})'.format(-sh) + for sh in shifts) + + def shifts(self, i): + r""" + Return the shifts of ``self``. + + EXAMPLES:: + + sage: S. = PolynomialRing(QQ) + sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) + sage: r = I.graded_free_resolution() + sage: r.shifts(0) + [0] + sage: r.shifts(1) + [2, 2, 2] + sage: r.shifts(2) + [3, 3] + sage: r.shifts(3) + [] + """ + if i < 0: + raise IndexError('invalid index') + elif i == 0: + shifts = self._shifts + elif i > len(self): + shifts = [] + else: + self._maps # to set _res_shifts + shifts = self._res_shifts[i - 1] + + return shifts + + def betti(self, i, a=None): + r""" + Return the `i`-th Betti number in degree `a`. + + INPUT: + + - ``i`` -- nonnegative integer + + - ``a`` -- a degree; if ``None``, return Betti numbers in all degrees + + EXAMPLES:: + + sage: S. = PolynomialRing(QQ) + sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) + sage: r = I.graded_free_resolution() + sage: r.betti(0) + {0: 1} + sage: r.betti(1) + {2: 3} + sage: r.betti(2) + {3: 2} + sage: r.betti(1, 0) + 0 + sage: r.betti(1, 1) + 0 + sage: r.betti(1, 2) + 3 + """ + shifts = self.shifts(i) + + if a is None: + degrees = shifts + else: + degrees = [a] + + betti = {} + for s in degrees: + betti[s] = len([d for d in shifts if d == s]) + + if a is None: + return betti + else: + return betti[a] if a in betti else 0 + + def K_polynomial(self, names=None): + r""" + Return the K-polynomial of this resolution. + + INPUT: + + - ``names`` -- (optional) a string of names of the variables + of the K-polynomial + + EXAMPLES:: + + sage: S. = PolynomialRing(QQ) + sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) + sage: r = I.graded_free_resolution() + sage: r.K_polynomial() + 2*t^3 - 3*t^2 + 1 + """ + if self._multigrade: + n = self._degrees[0].degree() + else: + n = 1 + + if names is not None: + L = LaurentPolynomialRing(ZZ, names=names) + else: + L = LaurentPolynomialRing(ZZ, 't', n) + + kpoly = 1 + sign = -1 + self._maps # to set _res_shifts + for j in range(len(self)): + for v in self._res_shifts[j]: + if self._multigrade: + kpoly += sign * L.monomial(*list(v)) + else: + kpoly += sign * L.monomial(v) + sign = -sign + + return kpoly + + +class GradedFiniteFreeResolution_free_module(GradedFiniteFreeResolution, FiniteFreeResolution_free_module): + r""" + Graded free resolution of free modules. + + .. WARNING:: + + This does not check that the module is homogeneous. + + EXAMPLES:: + + sage: from sage.homology.free_resolution import FreeResolution + sage: R. = QQ[] + sage: M = matrix([[x^3, 3*x^3, 5*x^3], + ....: [0, x, 2*x]]) + sage: res = FreeResolution(M, graded=True) + sage: res + S(0)⊕S(0)⊕S(0) <-- S(-3)⊕S(-1) <-- 0 + """ + def __init__(self, module, degrees=None, *args, **kwds): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: from sage.homology.free_resolution import FreeResolution + sage: R. = QQ[] + sage: M = matrix([[x^3, 3*x^3, 5*x^3], + ....: [0, x, 2*x]]) + sage: res = FreeResolution(M, graded=True) + sage: TestSuite(res).run(skip="_test_pickling") + """ + super().__init__(module, degrees=degrees, *args, **kwds) + + if len(self._degrees) > 1 and any(d != 1 for d in self._degrees): + raise NotImplementedError("only the natural grading supported " + "when more than one generator") + + @lazy_attribute + def _maps(self): + r""" + The maps that define ``self``. + + This also sets the attribute ``_res_shifts``. + + TESTS:: + + sage: from sage.homology.free_resolution import FreeResolution + sage: R. = QQ[] + sage: M = matrix([[x^3, 3*x^3, 5*x^3], + ....: [0, x, 2*x]]) + sage: res = FreeResolution(M, graded=True) + sage: res + S(0)⊕S(0)⊕S(0) <-- S(-3)⊕S(-1) <-- 0 + sage: res._maps + [ + [ x^3 0] + [3*x^3 x] + [5*x^3 2*x] + ] + sage: res._res_shifts + [[3, 1]] + + sage: I = R.ideal([x^4]) + sage: res = I.graded_free_resolution(shifts=[1], degrees=[2]) + sage: res + S(-1) <-- S(-9) <-- 0 + sage: res._maps + [[x^4]] + sage: res._res_shifts + [[9]] + """ + def compute_degree(base, i): + """ + Compute the degree by ``base * deg + shift``, + where ``*`` is entry-wise multiplication, ``deg`` and + ``shift`` are the ``i``-th index. + """ + deg = self._degrees[0] + shift = self._shifts[i] + if self._multigrade: + return vector([val * d + s for val, d, s in zip(base, deg, shift)]) + return base * deg + shift + + if isinstance(self._module, Ideal_generic): + from sage.matrix.constructor import matrix + val = self._module.gen(0) + self._res_shifts = [[compute_degree(val.degree(), 0)]] + return [matrix([[val]])] + + M = self._m() + + def find_deg(i): + for j in range(M.nrows()): + ret = M[j,i].degree() + if ret != -1: + return ret + raise NotImplementedError("a generator maps to 0") + + self._res_shifts = [[compute_degree(find_deg(i), i) + for i in range(M.ncols())]] + return [M] + + +class GradedFiniteFreeResolution_singular(GradedFiniteFreeResolution, FiniteFreeResolution_singular): + r""" + Graded free resolutions of submodules and ideals of multivariate + polynomial rings implemented using Singular. + + INPUT: + + - ``module`` -- a homogeneous submodule of a free module `M` of rank `n` + over `S` or a homogeneous ideal of a multivariate polynomial ring `S` + + - ``degrees`` -- (default: a list with all entries `1`) a list of integers + or integer vectors giving degrees of variables of `S` + + - ``shifts`` -- a list of integers or integer vectors giving shifts of + degrees of `n` summands of the free module `M`; this is a list of zero + degrees of length `n` by default + + - ``name`` -- a string; name of the base ring + + - ``algorithm`` -- Singular algorithm to compute a resolution of ``ideal`` + + If ``module`` is an ideal of `S`, it is considered as a submodule of a + free module of rank `1` over `S`. + + The degrees given to the variables of `S` are integers or integer vectors of + the same length. In the latter case, `S` is said to be multigraded, and the + resolution is a multigraded free resolution. The standard grading where all + variables have degree `1` is used if the degrees are not specified. + + A summand of the graded free module `M` is a shifted (or twisted) module of + rank one over `S`, denoted `S(-d)` with shift `d`. + + The computation of the resolution is done by using ``libSingular``. + Different Singular algorithms can be chosen for best performance. + + OUTPUT: a graded minimal free resolution of ``ideal`` + + The available algorithms and the corresponding Singular commands are shown + below: + + ============= ============================ + algorithm Singular commands + ============= ============================ + ``minimal`` ``mres(ideal)`` + ``shreyer`` ``minres(sres(std(ideal)))`` + ``standard`` ``minres(nres(std(ideal)))`` + ``heuristic`` ``minres(res(std(ideal)))`` + ============= ============================ + + .. WARNING:: + + This does not check that the module is homogeneous. + + EXAMPLES:: + + sage: S. = PolynomialRing(QQ) + sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) + sage: r = I.graded_free_resolution() + sage: r + S(0) <-- S(-2)⊕S(-2)⊕S(-2) <-- S(-3)⊕S(-3) <-- 0 + sage: len(r) + 2 + + sage: I = S.ideal([z^2 - y*w, y*z - x*w, y - x]) + sage: I.is_homogeneous() + True + sage: r = I.graded_free_resolution() + sage: r + S(0) <-- S(-1)⊕S(-2)⊕S(-2) <-- S(-3)⊕S(-3)⊕S(-4) <-- S(-5) <-- 0 + """ + def __init__(self, module, degrees=None, shifts=None, name='S', algorithm='heuristic', **kwds): + """ + Initialize. + + TESTS:: + + sage: S. = PolynomialRing(QQ) + sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) + sage: r = I.graded_free_resolution() + sage: TestSuite(r).run(skip=['_test_pickling']) + """ + super().__init__(module, degrees=degrees, shifts=shifts, name=name, **kwds) + self._algorithm = algorithm + + @lazy_attribute + def _maps(self): + """ + The maps that define ``self``. + + This also sets the attribute ``_res_shifts``. + + TESTS:: + + sage: S. = PolynomialRing(QQ) + sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) + sage: r = I.graded_free_resolution() + sage: r._maps + [ + [-y x] + [ z -y] + [z^2 - y*w y*z - x*w y^2 - x*z], [-w z] + ] + sage: r._res_shifts + [[2, 2, 2], [3, 3]] + """ + #cdef int i, j, k, ncols, nrows + #cdef list res_shifts, prev_shifts, new_shifts + + # This ensures the first component of the Singular resolution to be a + # module, like the later components. This is important when the + # components are converted to Sage modules. + module = singular_function("module") + mod = module(self._m()) + + if self._algorithm == 'minimal': + mres = singular_function('mres') # syzygy method + r = mres(mod, 0) + elif self._algorithm == 'shreyer': + std = singular_function('std') + sres = singular_function('sres') # Shreyer method + minres = singular_function('minres') + r = minres(sres(std(mod), 0)) + elif self._algorithm == 'standard': + nres = singular_function('nres') # standard basis method + minres = singular_function('minres') + r = minres(nres(mod, 0)) + elif self._algorithm == 'heuristic': + std = singular_function('std') + res = singular_function('res') # heuristic method + minres = singular_function('minres') + r = minres(res(std(mod), 0)) + + res_mats, res_degs = si2sa_resolution_graded(r, self._degrees) + + # compute shifts of free modules in the resolution + res_shifts = [] + prev_shifts = list(self._shifts) + for k in range(len(res_degs)): + new_shifts = [] + degs = res_degs[k] + ncols = len(degs) + for j in range(ncols): + col = degs[j] + nrows = len(col) + # should be enough to compute the new shifts + # from any one entry of the column vector + for i in range(nrows): + d = col[i] + if d is not None: + e = prev_shifts[i] + new_shifts.append(d + e) + break + res_shifts.append(new_shifts) + prev_shifts = new_shifts + + self._res_shifts = res_shifts + return res_mats + diff --git a/src/sage/homology/homology_group.py b/src/sage/homology/homology_group.py index adfac7717a0..0b27087a362 100644 --- a/src/sage/homology/homology_group.py +++ b/src/sage/homology/homology_group.py @@ -5,7 +5,6 @@ group that prints itself in a way that is suitable for homology groups. """ - ######################################################################## # Copyright (C) 2013 John H. Palmieri # Volker Braun @@ -14,7 +13,7 @@ # 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.modules.free_module import VectorSpace @@ -182,6 +181,4 @@ def HomologyGroup(n, base_ring, invfac=None): invfac = [0] * (n - len(invfac)) + invfac elif len(invfac) > n: raise ValueError("invfac (={}) must have length n (={})".format(invfac, n)) - M = HomologyGroup_class(n, invfac) - return M - + return HomologyGroup_class(n, invfac) diff --git a/src/sage/homology/homology_vector_space_with_basis.py b/src/sage/homology/homology_vector_space_with_basis.py index 8861ee20ad9..63a80657917 100644 --- a/src/sage/homology/homology_vector_space_with_basis.py +++ b/src/sage/homology/homology_vector_space_with_basis.py @@ -14,7 +14,6 @@ - John H. Palmieri, Travis Scrimshaw (2015-09) """ - ######################################################################## # Copyright (C) 2015 John H. Palmieri # Travis Scrimshaw @@ -23,7 +22,7 @@ # 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.cachefunc import cached_method @@ -871,4 +870,3 @@ def sum_indices(k, i_k_plus_one, S_k_plus_one): return [[S_k]] return [[i_k] + l for i_k in range(S_k, i_k_plus_one) for l in sum_indices(k-1, i_k, S_k)] - diff --git a/src/sage/homology/koszul_complex.py b/src/sage/homology/koszul_complex.py index c83605db2f5..858d5a71283 100644 --- a/src/sage/homology/koszul_complex.py +++ b/src/sage/homology/koszul_complex.py @@ -1,7 +1,6 @@ """ Koszul Complexes """ - ######################################################################## # Copyright (C) 2014 Travis Scrimshaw # @@ -9,7 +8,7 @@ # as published by the Free Software Foundation; either version 2 of # the License, or (at your option) any later version. # -# http://www.gnu.org/licenses/ +# https://www.gnu.org/licenses/ ######################################################################## from sage.structure.unique_representation import UniqueRepresentation @@ -22,6 +21,7 @@ import itertools + class KoszulComplex(ChainComplex_class, UniqueRepresentation): r""" A Koszul complex. @@ -166,4 +166,3 @@ def _repr_(self): if not self._elements: return "Trivial Koszul complex over {}".format(self.base_ring()) return "Koszul complex defined by {} over {}".format(self._elements, self.base_ring()) - diff --git a/src/sage/homology/matrix_utils.py b/src/sage/homology/matrix_utils.py index 1f2b583fc54..b1edc656e58 100644 --- a/src/sage/homology/matrix_utils.py +++ b/src/sage/homology/matrix_utils.py @@ -5,7 +5,6 @@ with the differentials thought of as matrices. This module contains some utility functions for this purpose. """ - ######################################################################## # Copyright (C) 2013 John H. Palmieri # @@ -13,7 +12,7 @@ # 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/ ######################################################################## # TODO: this module is a clear candidate for cythonizing. Need to @@ -203,4 +202,3 @@ def dhsw_snf(mat, verbose=False): if len(ed) < rows: return ed + [0]*(rows - len(ed)) return ed[:rows] - diff --git a/src/sage/homology/simplicial_complex_homset.py b/src/sage/homology/simplicial_complex_homset.py index 6a9d78f69f1..f5b54a59dc2 100644 --- a/src/sage/homology/simplicial_complex_homset.py +++ b/src/sage/homology/simplicial_complex_homset.py @@ -10,4 +10,3 @@ sage.topology.simplicial_complex_homset.is_SimplicialComplexHomset) SimplicialComplexHomset = deprecated_function_alias(31925, sage.topology.simplicial_complex_homset.SimplicialComplexHomset) - diff --git a/src/sage/homology/tests.py b/src/sage/homology/tests.py index e49d0cc8373..89c86857d44 100644 --- a/src/sage/homology/tests.py +++ b/src/sage/homology/tests.py @@ -61,7 +61,8 @@ def random_chain_complex(level=1): mat = random_matrix(ZZ, nrows, ncols, sparse=sparseness) dim = randint(-bound, bound) deg = 2 * randint(0, 1) - 1 # -1 or 1 - return ChainComplex({dim: mat}, degree = deg) + return ChainComplex({dim: mat}, degree=deg) + @random_testing def test_random_chain_complex(level=1, trials=1, verbose=False): diff --git a/src/sage/interacts/library.py b/src/sage/interacts/library.py index 8166014739d..06d680109a0 100644 --- a/src/sage/interacts/library.py +++ b/src/sage/interacts/library.py @@ -226,7 +226,7 @@ def taylor_polynomial(title, f, order: int): html(r'$f(x)\;=\;%s$' % latex(f)) html(r'$\hat{f}(x;%s)\;=\;%s+\mathcal{O}(x^{%s})$' % (x0, latex(ft), order + 1)) - show(dot + p + pt, ymin = -.5, ymax = 1) + show(dot + p + pt, ymin=-.5, ymax=1) @library_interact( @@ -1176,14 +1176,15 @@ def trapezoid_integration( for i in range(n): xi = interval[0] + i*h yi = f(xi) - trapezoids += line([[xi, 0], [xi, yi], [xi + h, f(xi + h)],[xi + h, 0],[xi, 0]], rgbcolor = (1,0,0)) + trapezoids += line([[xi, 0], [xi, yi], [xi + h, f(xi + h)],[xi + h, 0],[xi, 0]], rgbcolor=(1, 0, 0)) xs.append(xi) ys.append(yi) xs.append(xi + h) ys.append(f(xi + h)) html(r'Function $f(x)=%s$'%latex(f(x))) - show(plot(f, interval[0], interval[1]) + trapezoids, xmin = interval[0], xmax = interval[1]) + show(plot(f, interval[0], interval[1]) + trapezoids, + xmin=interval[0], xmax=interval[1]) numeric_value = integral_numerical(f, interval[0], interval[1])[0] approx = h *(ys[0]/2 + sum([ys[i] for i in range(1,n)]) + ys[n]/2) @@ -1326,7 +1327,8 @@ def parabola(a, b, c): html(r'Function $f(x)=%s$'%latex(f(x))) - show(plot(f(x),x,interval[0],interval[1]) + parabolas + lines, xmin = interval[0], xmax = interval[1]) + show(plot(f(x), x, interval[0], interval[1]) + parabolas + lines, + xmin=interval[0], xmax=interval[1]) numeric_value = integral_numerical(f,interval[0],interval[1])[0] approx = dx/3 *(ys[0] + sum([4*ys[i] for i in range(1,n,2)]) + sum([2*ys[i] for i in range(2,n,2)]) + ys[n]) @@ -1467,9 +1469,9 @@ def riemann_sum( color_rect='green' else: color_rect='red' - rects = rects +polygon2d(body, rgbcolor = color_rect,alpha=0.1)\ - + point((xs[i],ys[i]), rgbcolor = (1,0,0))\ - + line(body,rgbcolor='black',zorder=-1) + rects = rects + polygon2d(body, rgbcolor=color_rect, alpha=0.1)\ + + point((xs[i], ys[i]), rgbcolor=(1, 0, 0))\ + + line(body, rgbcolor='black', zorder=-1) html('Adjust your data and click Update button. Click repeatedly for another random values.') show(plot(func(x),(x,a,b),zorder=5) + rects) @@ -1870,7 +1872,7 @@ def polar_prime_spiral(interval, show_factors, highlight_primes, show_curves, n, list = [] list2 = [] if not show_factors: - for i in srange(start, end, include_endpoint = True): + for i in srange(start, end, include_endpoint=True): if Integer(i).is_pseudoprime(): list.append(f(i-start+1)) # primes list else: @@ -1878,7 +1880,7 @@ def polar_prime_spiral(interval, show_factors, highlight_primes, show_curves, n, P = points(list) R = points(list2, alpha=.1) # faded composites else: - for i in srange(start, end, include_endpoint = True): + for i in srange(start, end, include_endpoint=True): # 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: @@ -1889,7 +1891,7 @@ def polar_prime_spiral(interval, show_factors, highlight_primes, show_curves, n, 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) + R = points(list2, hue=.1, pointsize=p_size) if n > 0: html('$n = %s$' % factor(n)) diff --git a/src/sage/interfaces/axiom.py b/src/sage/interfaces/axiom.py index c6ef330ae63..0bc5334b071 100644 --- a/src/sage/interfaces/axiom.py +++ b/src/sage/interfaces/axiom.py @@ -214,16 +214,16 @@ def __init__(self, name='axiom', command='axiom -nox -noclef', self.__eval_using_file_cutoff = eval_using_file_cutoff self._COMMANDS_CACHE = '%s/%s_commandlist_cache.sobj' % (DOT_SAGE, name) Expect.__init__(self, - name = name, - prompt = r'\([0-9]+\) -> ', - command = command, - script_subdirectory = script_subdirectory, + name=name, + prompt=r'\([0-9]+\) -> ', + command=command, + script_subdirectory=script_subdirectory, server=server, server_tmpdir=server_tmpdir, - restart_on_ctrlc = False, - verbose_start = False, - init_code = init_code, - logfile = logfile, + restart_on_ctrlc=False, + verbose_start=False, + init_code=init_code, + logfile=logfile, eval_using_file_cutoff=eval_using_file_cutoff) self._prompt_wait = self._prompt @@ -993,4 +993,3 @@ def axiom_console(): if not get_display_manager().is_in_terminal(): raise RuntimeError('Can use the console only in the terminal. Try %%axiom magics instead.') os.system('axiom -nox') - diff --git a/src/sage/interfaces/ecm.py b/src/sage/interfaces/ecm.py index 171040c77a3..99287dec718 100644 --- a/src/sage/interfaces/ecm.py +++ b/src/sage/interfaces/ecm.py @@ -10,7 +10,7 @@ Sage includes GMP-ECM, which is a highly optimized implementation of Lenstra's elliptic curve factorization method. See -http://ecm.gforge.inria.fr for more about GMP-ECM. +https://gitlab.inria.fr/zimmerma/ecm for more about GMP-ECM. AUTHORS: @@ -46,11 +46,11 @@ # Distributed under the terms of the GNU General Public License (GPL) # 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/ +# https://www.gnu.org/licenses/ ############################################################################### import re -import subprocess +from subprocess import Popen, PIPE, call from sage.structure.sage_object import SageObject from sage.rings.integer_ring import ZZ @@ -63,7 +63,7 @@ def __init__(self, B1=10, B2=None, **kwds): Create an interface to the GMP-ECM elliptic curve method factorization program. - See http://ecm.gforge.inria.fr + See https://gitlab.inria.fr/zimmerma/ecm INPUT: @@ -216,8 +216,6 @@ def _run_ecm(self, cmd, n): sage: ecm._run_ecm(['cat'], 1234) '1234' """ - from subprocess import Popen, PIPE - # Under normal usage this program only returns ASCII; anything # else mixed is garbage and an error # So just accept latin-1 without encoding errors, and let the @@ -261,7 +259,7 @@ def interact(self): """ print("Enter numbers to run ECM on them.") print("Press control-D to exit.") - subprocess.call(self._cmd) + call(self._cmd) # Recommended settings from # http://www.mersennewiki.org/index.php/Elliptic_Curve_Method @@ -661,7 +659,7 @@ def factor(self, n, factor_digits=None, B1=2000, proof=False, **kwds): # Step 3: Call find_factor until a factorization is found n_factorization = [n] while len(n_factorization) == 1: - n_factorization = self.find_factor(n,B1=B1) + n_factorization = self.find_factor(n, B1=B1) factors.extend(n_factorization) return sorted(probable_prime_factors) diff --git a/src/sage/interfaces/four_ti_2.py b/src/sage/interfaces/four_ti_2.py index 92de8d2d478..79a07dcdad4 100644 --- a/src/sage/interfaces/four_ti_2.py +++ b/src/sage/interfaces/four_ti_2.py @@ -1,7 +1,7 @@ r""" Interface to 4ti2 -http://www.4ti2.de +https://4ti2.github.io/ You must have the 4ti2 Sage package installed on your computer for this interface to work. @@ -43,8 +43,9 @@ class FourTi2(): r""" - This object defines an interface to the program 4ti2. Each command - 4ti2 has is exposed as one method. + An interface to the program 4ti2. + + Each 4ti2 command is exposed as a method of this class. """ def __init__(self, directory=None): r""" @@ -155,10 +156,11 @@ def write_single_row(self, row, filename): def write_array(self, array, nrows, ncols, filename): r""" - Write the matrix ``array`` of integers (can be represented as - a list of lists) to the file ``filename`` in directory - ``directory()`` in 4ti2 format. The matrix must have ``nrows`` - rows and ``ncols`` columns. + Write the integer matrix ``array`` to the file ``filename`` + in directory ``directory()`` in 4ti2 format. + + The matrix must have ``nrows`` rows and ``ncols`` columns. + It can be provided as a list of lists. INPUT: @@ -216,9 +218,10 @@ def read_matrix(self, filename): def _process_input(self, kwds): r""" - kwds is a dict, and the values are written to files with - extension given by the keys, except for the keys ``self`` - and ``project``. + Process the input in the dictionary ``kwds``. + + The values are written to files with extensions given + by the keys, except for the keys ``self`` and ``project``. This interesting method is intended to be called as the first thing going on in a method implementing some action of 4ti2, @@ -227,13 +230,13 @@ def _process_input(self, kwds): just by giving the parameters of the method names that are the extension of the corresponding files. - Nothing is written if the value is None. Otherwise the value + Nothing is written if the value is ``None``. Otherwise the value is written as a matrix to the file given by the value of the key ``'project'`` with extension given by the key. INPUT: - - kwds -- A dict controlling what data is written to what files. + - ``kwds`` -- A dict controlling what data is written to what files. OUTPUT: @@ -280,17 +283,17 @@ def call(self, command, project, verbose=True, *, options=()): INPUT: - - command -- The 4ti2 program to run. - - project -- The file name of the project to run on. - - verbose -- Display the output of 4ti2 if ``True``. - - options -- A list of strings to pass to the program. + - ``command`` -- The 4ti2 program to run. + - ``project`` -- The file name of the project to run on. + - ``verbose`` -- Display the output of 4ti2 if ``True``. + - ``options`` -- A list of strings to pass to the program. EXAMPLES:: sage: from sage.interfaces.four_ti_2 import four_ti_2 sage: four_ti_2.write_matrix([[6,10,15]], "test_file") - sage: four_ti_2.call("groebner", "test_file", False) # optional - 4ti2 - sage: four_ti_2.read_matrix("test_file.gro") # optional - 4ti2 + sage: four_ti_2.call("groebner", "test_file", False) # optional - 4ti2 + sage: four_ti_2.read_matrix("test_file.gro") # optional - 4ti2 [-5 0 2] [-5 3 0] """ @@ -306,8 +309,9 @@ def call(self, command, project, verbose=True, *, options=()): def zsolve(self, mat=None, rel=None, rhs=None, sign=None, lat=None, project=None): r""" - Run the 4ti2 program ``zsolve`` on the parameters. See - ``http://www.4ti2.de/`` for details. + Run the 4ti2 program ``zsolve`` on the parameters. + + See `4ti2 website `_ for details. EXAMPLES:: @@ -316,7 +320,7 @@ def zsolve(self, mat=None, rel=None, rhs=None, sign=None, lat=None, project=None sage: rel = ['<', '<'] sage: rhs = [2, 3] sage: sign = [1,0,1] - sage: four_ti_2.zsolve(A, rel, rhs, sign) # optional - 4ti2 + sage: four_ti_2.zsolve(A, rel, rhs, sign) # optional - 4ti2 [ [ 1 -1 0] [ 0 -1 0] @@ -324,7 +328,7 @@ def zsolve(self, mat=None, rel=None, rhs=None, sign=None, lat=None, project=None [1 1 0] [ 1 -2 1] [0 1 0], [ 0 -2 1], [] ] - sage: four_ti_2.zsolve(lat=[[1,2,3],[1,1,1]]) # optional - 4ti2 + sage: four_ti_2.zsolve(lat=[[1,2,3],[1,1,1]]) # optional - 4ti2 [ [1 2 3] [0 0 0], [], [1 1 1] @@ -338,14 +342,15 @@ def zsolve(self, mat=None, rel=None, rhs=None, sign=None, lat=None, project=None def qsolve(self, mat=None, rel=None, sign=None, project=None): r""" - Run the 4ti2 program ``qsolve`` on the parameters. See - ``http://www.4ti2.de/`` for details. + Run the 4ti2 program ``qsolve`` on the parameters. + + See `4ti2 website `_ for details. EXAMPLES:: sage: from sage.interfaces.four_ti_2 import four_ti_2 sage: A = [[1,1,1],[1,2,3]] - sage: four_ti_2.qsolve(A) # optional - 4ti2 + sage: four_ti_2.qsolve(A) # optional - 4ti2 [[], [ 1 -2 1]] """ project = self._process_input(locals()) @@ -355,13 +360,14 @@ def qsolve(self, mat=None, rel=None, sign=None, project=None): def rays(self, mat=None, project=None): r""" - Run the 4ti2 program ``rays`` on the parameters. See - ``http://www.4ti2.de/`` for details. + Run the 4ti2 program ``rays`` on the parameters. + + See `4ti2 website `_ for details. EXAMPLES:: sage: from sage.interfaces.four_ti_2 import four_ti_2 - sage: four_ti_2.rays(four_ti_2._magic3x3()) # optional - 4ti2 + sage: four_ti_2.rays(four_ti_2._magic3x3()) # optional - 4ti2 [0 2 1 2 1 0 1 0 2] [1 0 2 2 1 0 0 2 1] [1 2 0 0 1 2 2 0 1] @@ -373,19 +379,20 @@ def rays(self, mat=None, project=None): def hilbert(self, mat=None, lat=None, project=None): r""" - Run the 4ti2 program ``hilbert`` on the parameters. See - ``http://www.4ti2.de/`` for details. + Run the 4ti2 program ``hilbert`` on the parameters. + + See `4ti2 website `_ for details. EXAMPLES:: sage: from sage.interfaces.four_ti_2 import four_ti_2 - sage: four_ti_2.hilbert(four_ti_2._magic3x3()) # optional - 4ti2 + sage: four_ti_2.hilbert(four_ti_2._magic3x3()) # optional - 4ti2 [2 0 1 0 1 2 1 2 0] [1 0 2 2 1 0 0 2 1] [0 2 1 2 1 0 1 0 2] [1 2 0 0 1 2 2 0 1] [1 1 1 1 1 1 1 1 1] - sage: four_ti_2.hilbert(lat=[[1,2,3],[1,1,1]]) # optional - 4ti2 + sage: four_ti_2.hilbert(lat=[[1,2,3],[1,1,1]]) # optional - 4ti2 [2 1 0] [0 1 2] [1 1 1] @@ -396,13 +403,14 @@ def hilbert(self, mat=None, lat=None, project=None): def graver(self, mat=None, lat=None, project=None): r""" - Run the 4ti2 program ``graver`` on the parameters. See - ``http://www.4ti2.de/`` for details. + Run the 4ti2 program ``graver`` on the parameters. + + See `4ti2 website `_ for details. EXAMPLES:: sage: from sage.interfaces.four_ti_2 import four_ti_2 - sage: four_ti_2.graver([1,2,3]) # optional - 4ti2 + sage: four_ti_2.graver([1,2,3]) # optional - 4ti2 [ 2 -1 0] [ 3 0 -1] [ 1 1 -1] @@ -420,13 +428,14 @@ def graver(self, mat=None, lat=None, project=None): def ppi(self, n): r""" - Run the 4ti2 program ``ppi`` on the parameters. See - ``http://www.4ti2.de/`` for details. + Run the 4ti2 program ``ppi`` on the parameters. + + See `4ti2 website `_ for details. EXAMPLES:: sage: from sage.interfaces.four_ti_2 import four_ti_2 - sage: four_ti_2.ppi(3) # optional - 4ti2 + sage: four_ti_2.ppi(3) # optional - 4ti2 [-2 1 0] [ 0 -3 2] [-1 -1 1] @@ -439,13 +448,14 @@ def ppi(self, n): def circuits(self, mat=None, project=None): r""" - Run the 4ti2 program ``circuits`` on the parameters. See - ``http://www.4ti2.de/`` for details. + Run the 4ti2 program ``circuits`` on the parameters. + + See `4ti2 website `_ for details. EXAMPLES:: sage: from sage.interfaces.four_ti_2 import four_ti_2 - sage: four_ti_2.circuits([1,2,3]) # optional - 4ti2 + sage: four_ti_2.circuits([1,2,3]) # optional - 4ti2 [ 0 3 -2] [ 2 -1 0] [ 3 0 -1] @@ -456,13 +466,14 @@ def circuits(self, mat=None, project=None): def minimize(self, mat=None, lat=None): r""" - Run the 4ti2 program ``minimize`` on the parameters. See - ``http://www.4ti2.de/`` for details. + Run the 4ti2 program ``minimize`` on the parameters. + + See `4ti2 website `_ for details. EXAMPLES:: sage: from sage.interfaces.four_ti_2 import four_ti_2 - sage: four_ti_2.minimize() # optional - 4ti2 + sage: four_ti_2.minimize() # optional - 4ti2 Traceback (most recent call last): ... NotImplementedError: 4ti2 command 'minimize' not implemented in Sage. @@ -472,18 +483,20 @@ def minimize(self, mat=None, lat=None): def groebner(self, mat=None, lat=None, project=None): r""" - Run the 4ti2 program ``groebner`` on the parameters. This - computes a Toric Groebner basis of a matrix. See - ``http://www.4ti2.de/`` for details. + Run the 4ti2 program ``groebner`` on the parameters. + + This computes a toric Groebner basis of a matrix. + + See `4ti2 website `_ for details. EXAMPLES:: sage: from sage.interfaces.four_ti_2 import four_ti_2 sage: A = [6,10,15] - sage: four_ti_2.groebner(A) # optional - 4ti2 + sage: four_ti_2.groebner(A) # optional - 4ti2 [-5 0 2] [-5 3 0] - sage: four_ti_2.groebner(lat=[[1,2,3],[1,1,1]]) # optional - 4ti2 + sage: four_ti_2.groebner(lat=[[1,2,3],[1,1,1]]) # optional - 4ti2 [-1 0 1] [ 2 1 0] """ @@ -498,7 +511,7 @@ def _magic3x3(self): EXAMPLES:: sage: from sage.interfaces.four_ti_2 import four_ti_2 - sage: four_ti_2._magic3x3() # optional - 4ti2 + sage: four_ti_2._magic3x3() # optional - 4ti2 [ 1 1 1 -1 -1 -1 0 0 0] [ 1 1 1 0 0 0 -1 -1 -1] [ 0 1 1 -1 0 0 -1 0 0] diff --git a/src/sage/interfaces/fricas.py b/src/sage/interfaces/fricas.py index dee071c736b..22381799212 100644 --- a/src/sage/interfaces/fricas.py +++ b/src/sage/interfaces/fricas.py @@ -576,7 +576,7 @@ def _register_symbols(): 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.functions.special import elliptic_e + from sage.functions.special import elliptic_e, elliptic_f from sage.misc.functional import symbolic_sum, symbolic_prod from sage.rings.infinity import infinity register_symbol(pi, {'fricas': 'pi'}, 0) # %pi::INFORM is %pi, but (pi) also exists @@ -598,6 +598,7 @@ def _register_symbols(): register_symbol(gamma, {'fricas': 'Gamma'}, 1) register_symbol(gamma, {'fricas': 'Gamma'}, 2) register_symbol(lambda x, y: elliptic_e(asin(x), y), {'fricas': 'ellipticE'}, 2) + register_symbol(lambda x, y: elliptic_f(asin(x), y), {'fricas': 'ellipticF'}, 2) register_symbol(lambda x, y: x + y, {'fricas': '+'}, 2) register_symbol(lambda x, y: x - y, {'fricas': '-'}, 2) register_symbol(lambda x, y: x * y, {'fricas': '*'}, 2) @@ -1092,7 +1093,7 @@ def __getitem__(self, n): raise else: n += l - if not(0 <= n < l): + if not (0 <= n < l): raise IndexError("index out of range") # use "elt" instead of "." here because then the error # message is clearer diff --git a/src/sage/interfaces/frobby.py b/src/sage/interfaces/frobby.py index 7680704d291..9f514422528 100644 --- a/src/sage/interfaces/frobby.py +++ b/src/sage/interfaces/frobby.py @@ -78,12 +78,12 @@ def __call__(self, action, input=None, options=[], verbose=False): print("Frobby command: ", repr(command)) print("Frobby input:\n", input) - process = Popen(command, stdin = PIPE, stdout = PIPE, stderr = PIPE) + process = Popen(command, stdin=PIPE, stdout=PIPE, stderr=PIPE) if input: frinput = str_to_bytes(input) else: frinput = None - output, err = process.communicate(input = frinput) + output, err = process.communicate(input=frinput) output = bytes_to_str(output) err = bytes_to_str(err) diff --git a/src/sage/interfaces/gap.py b/src/sage/interfaces/gap.py index c34fe530c34..ba175d4e340 100644 --- a/src/sage/interfaces/gap.py +++ b/src/sage/interfaces/gap.py @@ -1814,4 +1814,3 @@ def gap_console(): cmd, _ = gap_command(use_workspace_cache=False) cmd += ' ' + os.path.join(SAGE_EXTCODE,'gap','console.g') os.system(cmd) - diff --git a/src/sage/interfaces/gnuplot.py b/src/sage/interfaces/gnuplot.py index d93f6b33fb4..8ab14c75e1c 100644 --- a/src/sage/interfaces/gnuplot.py +++ b/src/sage/interfaces/gnuplot.py @@ -196,7 +196,3 @@ def gnuplot_console(): if not get_display_manager().is_in_terminal(): raise RuntimeError('Can use the console only in the terminal. Try %%gnuplot magics instead.') os.system('gnuplot') - - - - diff --git a/src/sage/interfaces/jmoldata.py b/src/sage/interfaces/jmoldata.py index b94f168c6cf..6660a14e1cc 100644 --- a/src/sage/interfaces/jmoldata.py +++ b/src/sage/interfaces/jmoldata.py @@ -75,7 +75,7 @@ def export_image(self, targetfile, datafile, #name (path) of data file Jmol can read or script file telling it what to read or load datafile_cmd='script', #"script" or "load" - image_type ='PNG', #PNG, JPG, GIF + image_type='PNG', #PNG, JPG, GIF figsize=5, **kwds): r""" diff --git a/src/sage/interfaces/latte.py b/src/sage/interfaces/latte.py index ecb59e9ac77..4c6d8e9f061 100644 --- a/src/sage/interfaces/latte.py +++ b/src/sage/interfaces/latte.py @@ -409,7 +409,7 @@ def integrate(arg, polynomial=None, algorithm='triangulate', raw_output=False, v ans = bytes_to_str(ans) ans = ans.splitlines() ans = ans[-5].split() - assert(ans[0]=='Answer:') + assert ans[0] == 'Answer:' ans = ans[1] tempd.cleanup() diff --git a/src/sage/interfaces/lisp.py b/src/sage/interfaces/lisp.py index 668b35640df..0f9d09605bf 100644 --- a/src/sage/interfaces/lisp.py +++ b/src/sage/interfaces/lisp.py @@ -74,28 +74,28 @@ def __init__(self, Expect.__init__(self, # The capitalized version of this is used for printing. - name = 'Lisp', + name='Lisp', # This is regexp of the input prompt. If you can change # it to be very obfuscated that would be better. Even # better is to use sequence numbers. - prompt = '> ', + prompt='> ', # This is the command that starts up your program - command = "ecl", + command="ecl", server=server, server_tmpdir=server_tmpdir, - script_subdirectory = script_subdirectory, + script_subdirectory=script_subdirectory, # If this is true, then whenever the user presses Control-C to # interrupt a calculation, the whole interface is restarted. - restart_on_ctrlc = False, + restart_on_ctrlc=False, # If true, print out a message when starting # up the command when you first send a command # to this interface. - verbose_start = False, + verbose_start=False, logfile=logfile, diff --git a/src/sage/interfaces/macaulay2.py b/src/sage/interfaces/macaulay2.py index 4c7a8ae9851..14d217cff9e 100644 --- a/src/sage/interfaces/macaulay2.py +++ b/src/sage/interfaces/macaulay2.py @@ -216,14 +216,14 @@ def __init__(self, maxread=None, script_subdirectory=None, ) command = "%s --no-debug --no-readline --silent -e '%s'" % (command, init_str) Expect.__init__(self, - name = 'macaulay2', - prompt = PROMPT, - command = command, - server = server, - server_tmpdir = server_tmpdir, - script_subdirectory = script_subdirectory, - verbose_start = False, - logfile = logfile, + name='macaulay2', + prompt=PROMPT, + command=command, + server=server, + server_tmpdir=server_tmpdir, + script_subdirectory=script_subdirectory, + verbose_start=False, + logfile=logfile, eval_using_file_cutoff=500) # Macaulay2 provides no "clear" function. However, Macaulay2 does provide @@ -1866,7 +1866,6 @@ def macaulay2_console(): os.system('M2') - def reduce_load_macaulay2(): """ Used for reconstructing a copy of the Macaulay2 interpreter from a pickle. @@ -1878,4 +1877,3 @@ def reduce_load_macaulay2(): Macaulay2 """ return macaulay2 - diff --git a/src/sage/interfaces/matlab.py b/src/sage/interfaces/matlab.py index 0f930905656..d3701a553b8 100644 --- a/src/sage/interfaces/matlab.py +++ b/src/sage/interfaces/matlab.py @@ -170,15 +170,15 @@ class Matlab(Expect): def __init__(self, maxread=None, script_subdirectory=None, logfile=None, server=None,server_tmpdir=None): Expect.__init__(self, - name = 'matlab', - prompt = '>> ', - command = "matlab -nodisplay", - server = server, - server_tmpdir = server_tmpdir, - script_subdirectory = script_subdirectory, - restart_on_ctrlc = False, - verbose_start = False, - logfile = logfile, + name='matlab', + prompt='>> ', + command="matlab -nodisplay", + server=server, + server_tmpdir=server_tmpdir, + script_subdirectory=script_subdirectory, + restart_on_ctrlc=False, + verbose_start=False, + logfile=logfile, eval_using_file_cutoff=100) def __reduce__(self): diff --git a/src/sage/interfaces/maxima.py b/src/sage/interfaces/maxima.py index a05b4995cac..8950adfdea1 100644 --- a/src/sage/interfaces/maxima.py +++ b/src/sage/interfaces/maxima.py @@ -566,14 +566,14 @@ def __init__(self, script_subdirectory=None, logfile=None, server=None, MaximaAbstract.__init__(self,"maxima") Expect.__init__(self, - name = 'maxima', - prompt = r'\(\%i[0-9]+\) ', - command = '{0} -p {1}'.format(MAXIMA, shlex.quote(STARTUP)), - script_subdirectory = script_subdirectory, - restart_on_ctrlc = False, - verbose_start = False, - init_code = init_code, - logfile = logfile, + name='maxima', + prompt=r'\(\%i[0-9]+\) ', + command='{0} -p {1}'.format(MAXIMA, shlex.quote(STARTUP)), + script_subdirectory=script_subdirectory, + restart_on_ctrlc=False, + verbose_start=False, + init_code=init_code, + logfile=logfile, eval_using_file_cutoff=eval_using_file_cutoff) # Must match what is in the file sage-maxima.lisp self._display_prompt = '' @@ -1253,8 +1253,8 @@ def __init__(self, parent, name, defn, args, latex): # An instance -maxima = Maxima(init_code = ['display2d : false', - 'domain : complex', 'keepfloat : true'], +maxima = Maxima(init_code=['display2d : false', + 'domain : complex', 'keepfloat : true'], script_subdirectory=None) diff --git a/src/sage/interfaces/maxima_lib.py b/src/sage/interfaces/maxima_lib.py index ecfa7af38b4..e772ede1101 100644 --- a/src/sage/interfaces/maxima_lib.py +++ b/src/sage/interfaces/maxima_lib.py @@ -1691,29 +1691,29 @@ def max_to_sr(expr): op_max=caar(expr) if op_max in special_max_to_sage: return special_max_to_sage[op_max](expr) - if not(op_max in max_op_dict): + if op_max not in max_op_dict: op_max_str = maxprint(op_max).python()[1:-1] if op_max_str in max_to_pynac_table: # nargs ? op = max_to_pynac_table[op_max_str] else: # This could be unsafe if the conversion to SR # changes the structure of expr - sage_expr=SR(maxima(expr)) - op=sage_expr.operator() + sage_expr = SR(maxima(expr)) + op = sage_expr.operator() if op in sage_op_dict: raise RuntimeError("Encountered operator mismatch in maxima-to-sr translation") max_op_dict[op_max]=op sage_op_dict[op]=op_max else: - op=max_op_dict[op_max] - max_args=cdr(expr) - args=[max_to_sr(a) for a in max_args] + op = max_op_dict[op_max] + max_args = cdr(expr) + args = [max_to_sr(a) for a in max_args] return op(*args) elif expr.symbolp(): - if not(expr in max_sym_dict): - sage_symbol=SR(maxima(expr)) - sage_sym_dict[sage_symbol]=expr - max_sym_dict[expr]=sage_symbol + if expr not in max_sym_dict: + sage_symbol = SR(maxima(expr)) + sage_sym_dict[sage_symbol] = expr + max_sym_dict[expr] = sage_symbol return max_sym_dict[expr] else: e = expr.python() diff --git a/src/sage/interfaces/mupad.py b/src/sage/interfaces/mupad.py index 077ef21e70d..e4e8531e6c2 100644 --- a/src/sage/interfaces/mupad.py +++ b/src/sage/interfaces/mupad.py @@ -118,19 +118,16 @@ def __init__(self, maxread=None, script_subdirectory=None, server=None, server_t True """ Expect.__init__(self, - name = 'MuPAD', - prompt = PROMPT, + name='MuPAD', + prompt=PROMPT, # the -U SAGE=TRUE allows for MuPAD programs to test whether they are run from Sage - command = "mupkern -P e -U SAGE=TRUE", - script_subdirectory = script_subdirectory, - server = server, - server_tmpdir = server_tmpdir, - restart_on_ctrlc = False, - verbose_start = False, - logfile = None) - - - + command="mupkern -P e -U SAGE=TRUE", + script_subdirectory=script_subdirectory, + server=server, + server_tmpdir=server_tmpdir, + restart_on_ctrlc=False, + verbose_start=False, + logfile=None) def _function_class(self): """ @@ -693,4 +690,3 @@ def __doctest_cleanup(): """ import sage.interfaces.quit sage.interfaces.quit.expect_quitall() - diff --git a/src/sage/interfaces/mwrank.py b/src/sage/interfaces/mwrank.py index 636b7d6d961..701b91b5afe 100644 --- a/src/sage/interfaces/mwrank.py +++ b/src/sage/interfaces/mwrank.py @@ -185,13 +185,13 @@ def __init__(self, options="", server=None,server_tmpdir=None): sage: TestSuite(Mwrank_class).run() """ Expect.__init__(self, - name = 'mwrank', - prompt = 'Enter curve: ', - command = "mwrank %s" % options, - server = server, - server_tmpdir = server_tmpdir, - restart_on_ctrlc = True, - verbose_start = False) + name='mwrank', + prompt='Enter curve: ', + command="mwrank %s" % options, + server=server, + server_tmpdir=server_tmpdir, + restart_on_ctrlc=True, + verbose_start=False) def __getattr__(self, attrname): """ @@ -362,4 +362,3 @@ def mwrank_console(): if not get_display_manager().is_in_terminal(): raise RuntimeError('Can use the console only in the terminal. Try %%mwrank magics instead.') os.system('mwrank') - diff --git a/src/sage/interfaces/octave.py b/src/sage/interfaces/octave.py index 8f12f515ce4..ae1b87c55cb 100644 --- a/src/sage/interfaces/octave.py +++ b/src/sage/interfaces/octave.py @@ -146,6 +146,7 @@ import pexpect from sage.misc.verbose import verbose from sage.misc.instancedoc import instancedoc +from sage.misc.temporary_file import tmp_filename from sage.cpython.string import bytes_to_str @@ -156,13 +157,13 @@ class Octave(Expect): EXAMPLES:: sage: octave.eval("a = [ 1, 1, 2; 3, 5, 8; 13, 21, 33 ]") # optional - octave - 'a =\n\n 1 1 2\n 3 5 8\n 13 21 33\n\n' + 'a =\n\n 1 1 2\n 3 5 8\n 13 21 33\n' sage: octave.eval("b = [ 1; 3; 13]") # optional - octave - 'b =\n\n 1\n 3\n 13\n\n' - sage: octave.eval("c=a \\ b") # solves linear equation: a*c = b # optional - octave; random output - 'c =\n\n 1\n 7.21645e-16\n -7.21645e-16\n\n' + 'b =\n\n 1\n 3\n 13\n' + sage: octave.eval(r"c=a \ b") # solves linear equation: a*c = b # optional - octave; random output + 'c =\n\n 1\n 7.21645e-16\n -7.21645e-16\n' sage: octave.eval("c") # optional - octave; random output - 'c =\n\n 1\n 7.21645e-16\n -7.21645e-16\n\n' + 'c =\n\n 1\n 7.21645e-16\n -7.21645e-16\n' TESTS: @@ -186,19 +187,21 @@ def __init__(self, maxread=None, script_subdirectory=None, logfile=None, command = os.getenv('SAGE_OCTAVE_COMMAND') or 'octave-cli' if server is None: server = os.getenv('SAGE_OCTAVE_SERVER') or None + # Use a temporary workspace file. + workspace_file = tmp_filename() Expect.__init__(self, - name = 'octave', + name='octave', # We want the prompt sequence to be unique to avoid confusion with syntax error messages containing >>> - prompt = r'octave\:\d+> ', + prompt=r'octave\:\d+> ', # We don't want any pagination of output - command = command + " --no-line-editing --silent --eval 'PS2(PS1());more off' --persist", - maxread = maxread, - server = server, - server_tmpdir = server_tmpdir, - script_subdirectory = script_subdirectory, - restart_on_ctrlc = False, - verbose_start = False, - logfile = logfile, + command=command + f" --no-line-editing --silent --eval 'PS2(PS1());more off; octave_core_file_name (\"{workspace_file}\")' --persist", + maxread=maxread, + server=server, + server_tmpdir=server_tmpdir, + script_subdirectory=script_subdirectory, + restart_on_ctrlc=False, + verbose_start=False, + logfile=logfile, eval_using_file_cutoff=100) self._seed = seed @@ -510,9 +513,9 @@ def solve_linear_system(self, A, b): sb = self.sage2octave_matrix_string(b) self.eval("a = " + sA ) self.eval("b = " + sb ) - soln = octave.eval("c = a \\ b") + soln = octave.eval(r"c = a \ b") soln = soln.replace("\n\n ","[") - soln = soln.replace("\n\n","]") + soln = soln.rstrip() + "]" soln = soln.replace("\n",",") sol = soln[3:] return eval(sol) diff --git a/src/sage/interfaces/polymake.py b/src/sage/interfaces/polymake.py index e335f33ae0a..c459a7268e6 100644 --- a/src/sage/interfaces/polymake.py +++ b/src/sage/interfaces/polymake.py @@ -65,7 +65,7 @@ class PolymakeError(RuntimeError): TESTS:: - sage: polymake.eval('print foo;') # optional polymake + sage: polymake.eval('print foo;') # optional - jupymake Traceback (most recent call last): ... PolymakeError: Unquoted string "foo" may clash with future reserved word... @@ -114,34 +114,11 @@ class PolymakeAbstract(ExtraTabCompletion, Interface): EXAMPLES:: - sage: from sage.interfaces.polymake import PolymakeAbstract, polymake_expect, polymake_jupymake + sage: from sage.interfaces.polymake import PolymakeAbstract, polymake_jupymake We test the verbosity management with very early doctests because messages will not be repeated. - Testing the deprecated pexpect-based interface:: - - sage: type(polymake_expect) - <...sage.interfaces.polymake.PolymakeExpect... - sage: isinstance(polymake_expect, PolymakeAbstract) - True - sage: p = polymake_expect.rand_sphere(4, 20, seed=5) # optional - polymake_expect - doctest...: DeprecationWarning: the pexpect-based interface to - polymake is deprecated. - Install package jupymake so that Sage can use the more robust - jupymake-based interface to polymake - See https://trac.sagemath.org/27745 for details. - sage: p # optional - polymake_expect - Random spherical polytope of dimension 4; seed=5... - sage: set_verbose(3) - sage: p.H_VECTOR # optional - polymake_expect - used package ppl - The Parma Polyhedra Library ... - 1 16 40 16 1 - sage: set_verbose(0) - sage: p.F_VECTOR # optional - polymake_expect - 20 94 148 74 - Testing the JuPyMake interface:: sage: isinstance(polymake_jupymake, PolymakeAbstract) @@ -178,17 +155,8 @@ def version(self): EXAMPLES:: - sage: polymake.version() # optional - polymake # random + sage: polymake.version() # optional - jupymake # random '4...' - - TESTS:: - - sage: from sage.interfaces.polymake import Polymake - sage: Polymake(command='foobar').version() - Traceback (most recent call last): - ... - RuntimeError: runtime error with deprecated pexpect-based interface to polymake; please install jupymake - """ return self.get('$Polymake::Version') @@ -210,10 +178,10 @@ def _object_class(self): TESTS:: - sage: C = polymake('cube(3)') # indirect doctest # optional - polymake - sage: C # optional - polymake + sage: C = polymake('cube(3)') # indirect doctest # optional - jupymake + sage: C # optional - jupymake cube of dimension 3 - sage: type(C) # optional - polymake + sage: type(C) # optional - jupymake """ @@ -228,10 +196,10 @@ def _function_element_class(self): We use ellipses in the tests, to make it more robust against future changes in polymake:: - sage: p = polymake.rand_sphere(4, 20, seed=5) # optional - polymake - sage: p.get_schedule # optional - polymake # indirect doctest + sage: p = polymake.rand_sphere(4, 20, seed=5) # optional - jupymake + sage: p.get_schedule # optional - jupymake # indirect doctest Member function 'get_schedule' of Polymake::polytope::Polytope__Rational object - sage: p.get_schedule('"F_VECTOR"') # optional - polymake # random + sage: p.get_schedule('"F_VECTOR"') # optional - jupymake # random CONE_DIM : RAYS | INPUT_RAYS precondition : BOUNDED ( POINTED : ) POINTED : @@ -261,7 +229,7 @@ def function_call(self, function, args=None, kwds=None): """ EXAMPLES:: - sage: polymake.rand_sphere(4, 30, seed=15) # optional - polymake # indirect doctest + sage: polymake.rand_sphere(4, 30, seed=15) # optional - jupymake # indirect doctest Random spherical polytope of dimension 4; seed=15... """ @@ -278,15 +246,15 @@ def _function_call_string(self, function, args, kwds): EXAMPLES:: - sage: polymake._function_call_string('cube', ['2','7','3'], ['group=>1']) # optional - polymake + sage: polymake._function_call_string('cube', ['2','7','3'], ['group=>1']) # optional - jupymake 'cube(2,7,3, group=>1);' - sage: c = polymake('cube(2,7,3, group=>1)') # optional - polymake - sage: c.VERTICES # optional - polymake + sage: c = polymake('cube(2,7,3, group=>1)') # optional - jupymake + sage: c.VERTICES # optional - jupymake 1 3 3 1 7 3 1 3 7 1 7 7 - sage: c.GROUP # optional - polymake + sage: c.GROUP # optional - jupymake full combinatorial group """ @@ -305,10 +273,10 @@ def _coerce_impl(self, x, use_special=True): Test that dictionaries are converted to hashes:: - sage: h = polymake({'"a"': 1, '"b"': 2}) # optional - polymake - sage: h # optional - polymake + sage: h = polymake({'"a"': 1, '"b"': 2}) # optional - jupymake + sage: h # optional - jupymake HASH(0x...) - sage: h['"a"'] # optional - polymake + sage: h['"a"'] # optional - jupymake 1 """ if isinstance(x, dict): @@ -416,13 +384,13 @@ def _start(self): TESTS:: - sage: polymake._start() # optional - polymake + sage: polymake._start() # optional - jupymake Since 'normal_fan' is not defined in the polymake application 'polytope', we now get :: - sage: 'normal_fan' in dir(polymake) # optional - polymake + sage: 'normal_fan' in dir(polymake) # optional - jupymake False """ @@ -457,14 +425,14 @@ def _read_in_file_command(self, filename): Force use of file:: - sage: L = polymake([42] * 400) # optional - polymake - sage: len(L) # optional - polymake + sage: L = polymake([42] * 400) # optional - jupymake + sage: len(L) # optional - jupymake 400 Just below standard file cutoff of 1024:: - sage: L = polymake([42] * 84) # optional - polymake - sage: len(L) # optional - polymake + sage: L = polymake([42] * 84) # optional - jupymake + sage: len(L) # optional - jupymake 84 """ return 'eval read_file "{}";\n'.format(filename) @@ -496,13 +464,13 @@ def clear(self, var): TESTS:: - sage: c = polymake.cube(15) # optional - polymake - sage: polymake._available_vars = [] # optional - polymake - sage: old = c._name # optional - polymake - sage: del c # optional - polymake # indirect doctest - sage: len(polymake._available_vars) # optional - polymake + sage: c = polymake.cube(15) # optional - jupymake + sage: polymake._available_vars = [] # optional - jupymake + sage: old = c._name # optional - jupymake + sage: del c # optional - jupymake # indirect doctest + sage: len(polymake._available_vars) # optional - jupymake 1 - sage: polymake._next_var_name() in old # optional - polymake + sage: polymake._next_var_name() in old # optional - jupymake True """ @@ -535,13 +503,13 @@ def _create(self, value, name=None): EXAMPLES:: - sage: polymake._create("('foo', 'bar')", name="my_array") # optional - polymake + sage: polymake._create("('foo', 'bar')", name="my_array") # optional - jupymake '@my_array' - sage: print(polymake.eval('print join(", ", @my_array);')) # optional - polymake + sage: print(polymake.eval('print join(", ", @my_array);')) # optional - jupymake foo, bar - sage: polymake._create('"foobar"', name="my_string") # optional - polymake + sage: polymake._create('"foobar"', name="my_string") # optional - jupymake '$my_string[0]' - sage: print(polymake.eval('print $my_string[0];')) # optional - polymake + sage: print(polymake.eval('print $my_string[0];')) # optional - jupymake foobar """ @@ -583,18 +551,18 @@ def set(self, var, value): EXAMPLES:: - sage: c = polymake('cube(3)') # optional - polymake # indirect doctest - sage: d = polymake.cube(3) # optional - polymake + sage: c = polymake('cube(3)') # optional - jupymake # indirect doctest + sage: d = polymake.cube(3) # optional - jupymake Equality is, for "big" objects such as polytopes, comparison by identity:: - sage: c == d # optional - polymake + sage: c == d # optional - jupymake False However, the list of vertices is equal:: - sage: c.VERTICES == d.VERTICES # optional - polymake + sage: c.VERTICES == d.VERTICES # optional - jupymake True TESTS: @@ -603,13 +571,13 @@ def set(self, var, value): It should, however, **never** be needed to do the following *explicitly*:: - sage: polymake.set('myvar', 'cube(3)') # optional - polymake - sage: polymake.get('$myvar[0]') # optional - polymake + sage: polymake.set('myvar', 'cube(3)') # optional - jupymake + sage: polymake.get('$myvar[0]') # optional - jupymake 'Polymake::polytope::Polytope__Rational=ARRAY(...)' The following tests against :trac:`22658`:: - sage: P = polymake.new_object("Polytope", FACETS=[[12, -2, -3, -5, -8, -13, -21, -34, -55], # optional - polymake + sage: P = polymake.new_object("Polytope", FACETS=[[12, -2, -3, -5, -8, -13, -21, -34, -55], # optional - jupymake ....: [0, 1, 0, 0, 0, 0, 0, 0, 0], ....: [0, 0, 0, 0, 0, 0, 0, 0, 1], ....: [0, 0, 0, 0, 0, 0, 0, 1, 0], @@ -618,7 +586,7 @@ def set(self, var, value): ....: [0, 0, 0, 0, 1, 0, 0, 0, 0], ....: [0, 0, 0, 1, 0, 0, 0, 0, 0], ....: [0, 0, 1, 0, 0, 0, 0, 0, 0]]) - sage: P.VERTICES # optional - polymake + sage: P.VERTICES # optional - jupymake 1 6 0 0 0 0 0 0 0 1 0 4 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 @@ -628,7 +596,7 @@ def set(self, var, value): 1 0 0 0 0 0 4/7 0 0 1 0 0 0 0 12/13 0 0 0 1 0 0 0 3/2 0 0 0 0 - sage: P.F_VECTOR # optional - polymake + sage: P.F_VECTOR # optional - jupymake 9 36 84 126 126 84 36 9 """ @@ -643,13 +611,13 @@ def get(self, cmd): EXAMPLES:: - sage: polymake.get('cube(3)') # optional - polymake + sage: polymake.get('cube(3)') # optional - jupymake 'Polymake::polytope::Polytope__Rational=ARRAY(...)' Note that the above string representation is what polymake provides. In our interface, we use what polymake calls a "description":: - sage: polymake('cube(3)') # optional - polymake + sage: polymake('cube(3)') # optional - jupymake cube of dimension 3 @@ -667,7 +635,7 @@ def help(self, topic, pager=True): EXAMPLES:: - sage: print(polymake.help('Polytope', pager=False)) # optional - polymake # random + sage: print(polymake.help('Polytope', pager=False)) # optional - jupymake # random objects/Polytope: Not necessarily bounded or unbounded polyhedron. Nonetheless, the name "Polytope" is used for two reasons: @@ -682,7 +650,7 @@ def help(self, topic, pager=True): and the available help topics are displayed resp. printed, without user interaction:: - sage: polymake.help('TRIANGULATION') # optional - polymake # random + sage: polymake.help('TRIANGULATION') # optional - jupymake # random doctest:warning ... UserWarning: Polymake expects user interaction. We abort and return the options that Polymake provides. @@ -696,7 +664,7 @@ def help(self, topic, pager=True): If an unknown help topic is requested, a :class:`PolymakeError` results:: - sage: polymake.help('Triangulation') # optional - polymake + sage: polymake.help('Triangulation') # optional - jupymake Traceback (most recent call last): ... PolymakeError: unknown help topic 'Triangulation' @@ -723,25 +691,25 @@ def _tab_completion(self): TESTS:: - sage: polymake.application('fan') # optional - polymake - sage: 'normal_fan' in dir(polymake) # optional - polymake # indirect doctest + sage: polymake.application('fan') # optional - jupymake + sage: 'normal_fan' in dir(polymake) # optional - jupymake # indirect doctest True - sage: polymake.application('polytope') # optional - polymake + sage: polymake.application('polytope') # optional - jupymake Since ``'normal_fan'`` is not defined in the polymake application ``'polytope'``, we now get:: - sage: 'normal_fan' in dir(polymake) # optional - polymake + sage: 'normal_fan' in dir(polymake) # optional - jupymake False Global functions from ``'core'`` are available:: - sage: 'show_credits' in dir(polymake) # optional - polymake + sage: 'show_credits' in dir(polymake) # optional - jupymake True Global functions from ``'common'`` are available:: - sage: 'lex_ordered' in dir(polymake) # optional - polymake + sage: 'lex_ordered' in dir(polymake) # optional - jupymake True """ if not self.is_running(): @@ -778,20 +746,20 @@ def application(self, app): terms of inequalities. Polymake knows to compute the f- and h-vector and finds that the polytope is very ample:: - sage: q = polymake.new_object("Polytope", INEQUALITIES=[[5,-4,0,1],[-3,0,-4,1],[-2,1,0,0],[-4,4,4,-1],[0,0,1,0],[8,0,0,-1],[1,0,-1,0],[3,-1,0,0]]) # optional - polymake - sage: q.H_VECTOR # optional - polymake + sage: q = polymake.new_object("Polytope", INEQUALITIES=[[5,-4,0,1],[-3,0,-4,1],[-2,1,0,0],[-4,4,4,-1],[0,0,1,0],[8,0,0,-1],[1,0,-1,0],[3,-1,0,0]]) # optional - jupymake + sage: q.H_VECTOR # optional - jupymake 1 5 5 1 - sage: q.F_VECTOR # optional - polymake + sage: q.F_VECTOR # optional - jupymake 8 14 8 - sage: q.VERY_AMPLE # optional - polymake + sage: q.VERY_AMPLE # optional - jupymake true In the application 'fan', polymake can now compute the normal fan of `q` and its (primitive) rays:: - sage: polymake.application('fan') # optional - polymake - sage: g = q.normal_fan() # optional - polymake - sage: g.RAYS # optional - polymake + sage: polymake.application('fan') # optional - jupymake + sage: g = q.normal_fan() # optional - jupymake + sage: g.RAYS # optional - jupymake -1 0 1/4 0 -1 1/4 1 0 0 @@ -800,7 +768,7 @@ def application(self, app): 0 0 -1 0 -1 0 -1 0 0 - sage: g.RAYS.primitive() # optional - polymake + sage: g.RAYS.primitive() # optional - jupymake -4 0 1 0 -4 1 1 0 0 @@ -819,20 +787,20 @@ def application(self, app): but only in 'tropical', the following shows the effect of changing the application. :: - sage: polymake.application('polytope') # optional - polymake - sage: 'trop_witness' in dir(polymake) # optional - polymake + sage: polymake.application('polytope') # optional - jupymake + sage: 'trop_witness' in dir(polymake) # optional - jupymake False - sage: polymake.application('tropical') # optional - polymake - sage: 'trop_witness' in dir(polymake) # optional - polymake + sage: polymake.application('tropical') # optional - jupymake + sage: 'trop_witness' in dir(polymake) # optional - jupymake True - sage: polymake.application('polytope') # optional - polymake - sage: 'trop_witness' in dir(polymake) # optional - polymake + sage: polymake.application('polytope') # optional - jupymake + sage: 'trop_witness' in dir(polymake) # optional - jupymake False For completeness, we show what happens when asking for an application that doesn't exist:: - sage: polymake.application('killerapp') # optional - polymake + sage: polymake.application('killerapp') # optional - jupymake Traceback (most recent call last): ... ValueError: Unknown polymake application 'killerapp' @@ -840,7 +808,7 @@ def application(self, app): Of course, a different error results when we send an explicit command in polymake to change to an unknown application:: - sage: polymake.eval('application "killerapp";') # optional - polymake + sage: polymake.eval('application "killerapp";') # optional - jupymake Traceback (most recent call last): ... PolymakeError: Unknown application killerapp @@ -862,17 +830,17 @@ def new_object(self, name, *args, **kwds): EXAMPLES:: - sage: q = polymake.new_object("Polytope", INEQUALITIES=[[4,-4,0,1],[-4,0,-4,1],[-2,1,0,0],[-4,4,4,-1],[0,0,1,0],[8,0,0,-1]]) # optional - polymake - sage: q.N_VERTICES # optional - polymake + sage: q = polymake.new_object("Polytope", INEQUALITIES=[[4,-4,0,1],[-4,0,-4,1],[-2,1,0,0],[-4,4,4,-1],[0,0,1,0],[8,0,0,-1]]) # optional - jupymake + sage: q.N_VERTICES # optional - jupymake 4 - sage: q.BOUNDED # optional - polymake + sage: q.BOUNDED # optional - jupymake true - sage: q.VERTICES # optional - polymake + sage: q.VERTICES # optional - jupymake 1 2 0 4 1 3 0 8 1 2 1 8 1 3 1 8 - sage: q.full_typename() # optional - polymake + sage: q.full_typename() # optional - jupymake 'Polytope' """ @@ -898,15 +866,15 @@ class PolymakeElement(ExtraTabCompletion, InterfaceElement): We support all "big" polymake types, Perl arrays of length different from one, and Perl scalars:: - sage: p = polymake.rand_sphere(4, 20, seed=5) # optional - polymake - sage: p.typename() # optional - polymake + sage: p = polymake.rand_sphere(4, 20, seed=5) # optional - jupymake + sage: p.typename() # optional - jupymake 'Polytope' - sage: p # optional - polymake + sage: p # optional - jupymake Random spherical polytope of dimension 4; seed=5... Now, one can work with that element in Python syntax, for example:: - sage: p.VERTICES[2][2] # optional - polymake + sage: p.VERTICES[2][2] # optional - jupymake 1450479926727001/2251799813685248 """ @@ -920,21 +888,21 @@ def _repr_(self): of the object that is not longer than single line, it is used for printing:: - sage: p = polymake.rand_sphere(3, 12, seed=15) # optional - polymake - sage: p # optional - polymake + sage: p = polymake.rand_sphere(3, 12, seed=15) # optional - jupymake + sage: p # optional - jupymake Random spherical polytope of dimension 3; seed=15... - sage: c = polymake.cube(4) # optional - polymake - sage: c # optional - polymake + sage: c = polymake.cube(4) # optional - jupymake + sage: c # optional - jupymake cube of dimension 4 We use the print representation of scalars to display scalars:: - sage: p.N_VERTICES # optional - polymake + sage: p.N_VERTICES # optional - jupymake 12 The items of a Perl arrays are shown separated by commas:: - sage: p.get_member('list_properties') # optional - polymake # random + sage: p.get_member('list_properties') # optional - jupymake # random POINTS, CONE_AMBIENT_DIM, BOUNDED, FEASIBLE, N_POINTS, POINTED, CONE_DIM, FULL_DIM, LINEALITY_DIM, LINEALITY_SPACE, COMBINATORIAL_DIM, AFFINE_HULL, VERTICES, N_VERTICES @@ -942,8 +910,8 @@ def _repr_(self): We chose to print rule chains explicitly, so that the user doesn't need to know how to list the rules using polymake commands:: - sage: r = p.get_schedule('"H_VECTOR"') # optional - polymake - sage: r # optional - polymake # random + sage: r = p.get_schedule('"H_VECTOR"') # optional - jupymake + sage: r # optional - jupymake # random precondition : N_RAYS | N_INPUT_RAYS ( ppl.convex_hull.primal: FACETS, LINEAR_SPAN : RAYS | INPUT_RAYS ) sensitivity check for FacetPerm ppl.convex_hull.primal: FACETS, LINEAR_SPAN : RAYS | INPUT_RAYS @@ -954,16 +922,16 @@ def _repr_(self): F_VECTOR : N_FACETS, N_RAYS, COMBINATORIAL_DIM precondition : SIMPLICIAL ( H_VECTOR : F_VECTOR ) H_VECTOR : F_VECTOR - sage: r.typeof() # optional - polymake + sage: r.typeof() # optional - jupymake ('Polymake::Core::Scheduler::RuleChain', 'ARRAY') Similarly, polymake matrices and vectors are explicitly listed:: - sage: c.VERTICES.typename() # optional - polymake + sage: c.VERTICES.typename() # optional - jupymake 'Matrix' - sage: c.VERTICES[0].typename() # optional - polymake + sage: c.VERTICES[0].typename() # optional - jupymake 'Vector' - sage: c.VERTICES # optional - polymake # random + sage: c.VERTICES # optional - jupymake # random 1 -1 -1 -1 -1 1 1 -1 -1 -1 1 -1 1 -1 -1 @@ -980,15 +948,15 @@ def _repr_(self): 1 1 -1 1 1 1 -1 1 1 1 1 1 1 1 1 - sage: c.VERTICES[0] # optional - polymake + sage: c.VERTICES[0] # optional - jupymake 1 -1 -1 -1 -1 For other types, we simply use the print representation offered by polymake:: - sage: p.TWO_FACE_SIZES.typename() # optional - polymake + sage: p.TWO_FACE_SIZES.typename() # optional - jupymake 'Map' - sage: p.TWO_FACE_SIZES # optional - polymake + sage: p.TWO_FACE_SIZES # optional - jupymake {(3 20)} """ @@ -1031,25 +999,25 @@ def _richcmp_(self, other, op): The default for comparing equality for polytopes is *identity*:: - sage: p1 = polymake.rand_sphere(3, 12, seed=15) # optional - polymake - sage: p2 = polymake.rand_sphere(3, 12, seed=15) # optional - polymake - sage: p1 == p2 # optional - polymake + sage: p1 = polymake.rand_sphere(3, 12, seed=15) # optional - jupymake + sage: p2 = polymake.rand_sphere(3, 12, seed=15) # optional - jupymake + sage: p1 == p2 # optional - jupymake False However, other data types are compared by equality, not identity:: - sage: p1.VERTICES == p2.VERTICES # optional - polymake + sage: p1.VERTICES == p2.VERTICES # optional - jupymake True A computation applied to a polytope can change the available properties, and thus we have :: - sage: p1.get_member('list_properties') == p2.get_member('list_properties') # optional - polymake + sage: p1.get_member('list_properties') == p2.get_member('list_properties') # optional - jupymake True - sage: p1.F_VECTOR # optional - polymake + sage: p1.F_VECTOR # optional - jupymake 12 30 20 - sage: p1.get_member('list_properties') == p2.get_member('list_properties') # optional - polymake + sage: p1.get_member('list_properties') == p2.get_member('list_properties') # optional - jupymake False """ @@ -1069,9 +1037,9 @@ def __bool__(self): EXAMPLES:: sage: from sage.interfaces.polymake import polymake - sage: bool(polymake(0)) # optional polymake + sage: bool(polymake(0)) # optional - jupymake False - sage: bool(polymake(1)) # optional polymake + sage: bool(polymake(1)) # optional - jupymake True """ @@ -1080,8 +1048,6 @@ def __bool__(self): cmd = '{} {} {};'.format(self._name, P._equality_symbol(), t) return P.get(cmd) == t - - def known_properties(self): """ List the names of properties that have been computed so far on this element. @@ -1095,23 +1061,23 @@ def known_properties(self): EXAMPLES:: - sage: c = polymake.cube(4) # optional - polymake - sage: c.known_properties() # optional - polymake + sage: c = polymake.cube(4) # optional - jupymake + sage: c.known_properties() # optional - jupymake ['AFFINE_HULL', 'BOUNDED', 'CONE_AMBIENT_DIM', 'CONE_DIM', ... 'VERTICES_IN_FACETS'] - sage: c.list_properties() # optional - polymake + sage: c.list_properties() # optional - jupymake CONE_AMBIENT_DIM, CONE_DIM, FACETS, AFFINE_HULL, VERTICES_IN_FACETS, BOUNDED... A computation can change the list of known properties:: - sage: c.F_VECTOR # optional - polymake + sage: c.F_VECTOR # optional - jupymake 16 32 24 8 - sage: c.known_properties() # optional - polymake + sage: c.known_properties() # optional - jupymake ['AFFINE_HULL', 'BOUNDED', 'COMBINATORIAL_DIM', @@ -1136,8 +1102,8 @@ def _member_list(self): TESTS:: - sage: c = polymake.cube(4) # optional - polymake - sage: c._member_list() # optional - polymake + sage: c = polymake.cube(4) # optional - jupymake + sage: c._member_list() # optional - jupymake ['AFFINE_HULL', ... 'WEAKLY_CENTERED', @@ -1164,10 +1130,10 @@ def typename(self): EXAMPLES:: - sage: c = polymake.cube(4) # optional - polymake - sage: c.typename() # optional - polymake + sage: c = polymake.cube(4) # optional - jupymake + sage: c.typename() # optional - jupymake 'Polytope' - sage: c.VERTICES.typename() # optional - polymake + sage: c.VERTICES.typename() # optional - jupymake 'Matrix' """ @@ -1183,10 +1149,10 @@ def full_typename(self): EXAMPLES:: - sage: c = polymake.cube(4) # optional - polymake - sage: c.full_typename() # optional - polymake + sage: c = polymake.cube(4) # optional - jupymake + sage: c.full_typename() # optional - jupymake 'Polytope' - sage: c.VERTICES.full_typename() # optional - polymake + sage: c.VERTICES.full_typename() # optional - jupymake 'Matrix' """ @@ -1202,10 +1168,10 @@ def qualified_typename(self): EXAMPLES:: - sage: c = polymake.cube(4) # optional - polymake - sage: c.qualified_typename() # optional - polymake + sage: c = polymake.cube(4) # optional - jupymake + sage: c.qualified_typename() # optional - jupymake 'polytope::Polytope' - sage: c.VERTICES.qualified_typename() # optional - polymake + sage: c.VERTICES.qualified_typename() # optional - jupymake 'common::Matrix' """ @@ -1230,8 +1196,8 @@ def _tab_completion(self): EXAMPLES:: - sage: c = polymake.cube(4) # optional - polymake - sage: c._tab_completion() # optional - polymake + sage: c = polymake.cube(4) # optional - jupymake + sage: c._tab_completion() # optional - jupymake ['AFFINE_HULL', ... 'zero_vector', @@ -1262,34 +1228,34 @@ def __getattr__(self, attrname): A property:: - sage: c = polymake.cube(3) # optional - polymake - sage: c.H_VECTOR # optional - polymake + sage: c = polymake.cube(3) # optional - jupymake + sage: c.H_VECTOR # optional - jupymake 1 5 5 1 - sage: c.N_VERTICES # optional - polymake + sage: c.N_VERTICES # optional - jupymake 8 - sage: d = polymake.cross(3) # optional - polymake - sage: d.N_VERTICES # optional - polymake + sage: d = polymake.cross(3) # optional - jupymake + sage: d.N_VERTICES # optional - jupymake 6 A function:: - sage: c.minkowski_sum_fukuda # optional - polymake + sage: c.minkowski_sum_fukuda # optional - jupymake minkowski_sum_fukuda (bound to Polymake::polytope::Polytope__Rational object) - sage: s = c.minkowski_sum_fukuda(d) # optional - polymake - sage: s.N_VERTICES # optional - polymake + sage: s = c.minkowski_sum_fukuda(d) # optional - jupymake + sage: s.N_VERTICES # optional - jupymake 24 - sage: s # optional - polymake + sage: s # optional - jupymake Polytope[SAGE...] A member function:: - sage: c = polymake.cube(2) # optional - polymake - sage: V = polymake.new_object('Vector', [1,0,0]) # optional - polymake - sage: V # optional - polymake + sage: c = polymake.cube(2) # optional - jupymake + sage: V = polymake.new_object('Vector', [1,0,0]) # optional - jupymake + sage: V # optional - jupymake 1 0 0 - sage: c.contains # optional - polymake + sage: c.contains # optional - jupymake Member function 'contains' of Polymake::polytope::Polytope__Rational object - sage: c.contains(V) # optional - polymake + sage: c.contains(V) # optional - jupymake true """ @@ -1318,21 +1284,21 @@ def get_member_function(self, attrname): EXAMPLES:: - sage: c = polymake.cube(2) # optional - polymake - sage: c.contains # optional - polymake + sage: c = polymake.cube(2) # optional - jupymake + sage: c.contains # optional - jupymake Member function 'contains' of Polymake::polytope::Polytope__Rational object - sage: V = polymake.new_object('Vector', [1,0,0]) # optional - polymake - sage: V # optional - polymake + sage: V = polymake.new_object('Vector', [1,0,0]) # optional - jupymake + sage: V # optional - jupymake 1 0 0 - sage: c.contains(V) # optional - polymake + sage: c.contains(V) # optional - jupymake true Whether a member function of the given name actually exists for that object will only be clear when calling it:: - sage: c.get_member_function("foo") # optional - polymake + sage: c.get_member_function("foo") # optional - jupymake Member function 'foo' of Polymake::polytope::Polytope__Rational object - sage: c.get_member_function("foo")() # optional - polymake + sage: c.get_member_function("foo")() # optional - jupymake Traceback (most recent call last): ... TypeError: Can't locate object method "foo" via package "Polymake::polytope::Polytope__Rational" @@ -1352,31 +1318,31 @@ def get_member(self, attrname): EXAMPLES:: - sage: p = polymake.rand_sphere(4, 20, seed=5) # optional - polymake + sage: p = polymake.rand_sphere(4, 20, seed=5) # optional - jupymake Normally, a property would be accessed as follows:: - sage: p.F_VECTOR # optional - polymake + sage: p.F_VECTOR # optional - jupymake 20 94 148 74 However, explicit access is possible as well:: - sage: p.get_member('F_VECTOR') # optional - polymake + sage: p.get_member('F_VECTOR') # optional - jupymake 20 94 148 74 In some cases, the explicit access works better:: - sage: p.type # optional - polymake + sage: p.type # optional - jupymake Member function 'type' of Polymake::polytope::Polytope__Rational object - sage: p.get_member('type') # optional - polymake + sage: p.get_member('type') # optional - jupymake Polytope[SAGE...] - sage: p.get_member('type').get_member('name') # optional - polymake + sage: p.get_member('type').get_member('name') # optional - jupymake Polytope Note that in the last example calling the erroneously constructed member function ``type`` still works:: - sage: p.type() # optional - polymake + sage: p.type() # optional - jupymake Polytope[SAGE...] """ @@ -1391,19 +1357,19 @@ def __getitem__(self, key): EXAMPLES:: - sage: p = polymake.rand_sphere(3, 12, seed=15) # optional - polymake - sage: p.VERTICES[3] # optional - polymake + sage: p = polymake.rand_sphere(3, 12, seed=15) # optional - jupymake + sage: p.VERTICES[3] # optional - jupymake 1 7977905618560809/18014398509481984 -1671539598851959/144115188075855872 8075083879632623/9007199254740992 - sage: p.list_properties()[2] # optional - polymake + sage: p.list_properties()[2] # optional - jupymake BOUNDED Slicing:: - sage: p.F_VECTOR[:] # optional - polymake + sage: p.F_VECTOR[:] # optional - jupymake [12, 30, 20] - sage: p.F_VECTOR[0:1] # optional - polymake + sage: p.F_VECTOR[0:1] # optional - jupymake [12] - sage: p.F_VECTOR[0:3:2] # optional - polymake + sage: p.F_VECTOR[0:3:2] # optional - jupymake [12, 20] """ P = self._check_valid() @@ -1434,8 +1400,8 @@ def __iter__(self): EXAMPLES:: - sage: p = polymake.rand_sphere(3, 12, seed=15) # optional - polymake - sage: [ x for x in p.VERTICES[3] ] # optional - polymake + sage: p = polymake.rand_sphere(3, 12, seed=15) # optional - jupymake + sage: [ x for x in p.VERTICES[3] ] # optional - jupymake [1, 7977905618560809/18014398509481984, -1671539598851959/144115188075855872, 8075083879632623/9007199254740992] """ for i in range(len(self)): @@ -1445,10 +1411,10 @@ def __len__(self): """ EXAMPLES:: - sage: p = polymake.rand_sphere(3, 12, seed=15) # optional - polymake - sage: len(p.FACETS) # optional - polymake + sage: p = polymake.rand_sphere(3, 12, seed=15) # optional - jupymake + sage: len(p.FACETS) # optional - jupymake 20 - sage: len(p.list_properties()) >= 12 # optional - polymake + sage: len(p.list_properties()) >= 12 # optional - jupymake True """ @@ -1475,19 +1441,19 @@ def typeof(self): EXAMPLES:: - sage: p = polymake.rand_sphere(3, 13, seed=12) # optional - polymake - sage: p.typeof() # optional - polymake + sage: p = polymake.rand_sphere(3, 13, seed=12) # optional - jupymake + sage: p.typeof() # optional - jupymake ('Polymake::polytope::Polytope__Rational', 'ARRAY') - sage: p.VERTICES.typeof() # optional - polymake + sage: p.VERTICES.typeof() # optional - jupymake ('Polymake::common::Matrix_A_Rational_I_NonSymmetric_Z', 'ARRAY') - sage: p.get_schedule('"F_VECTOR"').typeof() # optional - polymake + sage: p.get_schedule('"F_VECTOR"').typeof() # optional - jupymake ('Polymake::Core::Scheduler::RuleChain', 'ARRAY') On "small" objects, it just returns empty strings:: - sage: p.N_VERTICES.typeof() # optional - polymake + sage: p.N_VERTICES.typeof() # optional - jupymake ('', '') - sage: p.list_properties().typeof() # optional - polymake + sage: p.list_properties().typeof() # optional - jupymake ('', '') """ P = self._check_valid() @@ -1503,50 +1469,50 @@ def _sage_(self): EXAMPLES:: - sage: a = polymake(1/2); a # optional - polymake + sage: a = polymake(1/2); a # optional - jupymake 1/2 - sage: a.sage() # optional - polymake + sage: a.sage() # optional - jupymake 1/2 - sage: _.parent() # optional - polymake + sage: _.parent() # optional - jupymake Rational Field Quadratic extensions:: sage: K. = QuadraticField(5) - sage: polymake(K(0)).sage() # optional - polymake + sage: polymake(K(0)).sage() # optional - jupymake 0 - sage: _.parent() # optional - polymake + sage: _.parent() # optional - jupymake Rational Field - sage: polymake(sqrt5).sage() # optional - polymake + sage: polymake(sqrt5).sage() # optional - jupymake a - sage: polymake(-sqrt5).sage() # optional - polymake + sage: polymake(-sqrt5).sage() # optional - jupymake -a - sage: polymake(1/3-1/2*sqrt5).sage() # optional - polymake + sage: polymake(1/3-1/2*sqrt5).sage() # optional - jupymake -1/2*a + 1/3 - sage: polymake(-1+sqrt5).sage() # optional - polymake + sage: polymake(-1+sqrt5).sage() # optional - jupymake a - 1 Vectors:: - sage: PP = polymake.cube(3) # optional - polymake - sage: PP.F_VECTOR.sage() # optional - polymake + sage: PP = polymake.cube(3) # optional - jupymake + sage: PP.F_VECTOR.sage() # optional - jupymake (8, 12, 6) - sage: _.parent() # optional - polymake + sage: _.parent() # optional - jupymake Ambient free module of rank 3 over the principal ideal domain Integer Ring Matrices:: - sage: polymake.unit_matrix(2).sage() # optional - polymake + sage: polymake.unit_matrix(2).sage() # optional - jupymake [1 0] [0 1] - sage: _.parent() # optional - polymake + sage: _.parent() # optional - jupymake Full MatrixSpace of 2 by 2 dense matrices over Integer Ring Polytopes:: - sage: polymake.cube(3).sage() # optional - polymake + sage: polymake.cube(3).sage() # optional - jupymake A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 8 vertices - sage: polymake.icosahedron().sage() # optional - polymake + sage: polymake.icosahedron().sage() # optional - jupymake A 3-dimensional polyhedron in (Number Field in a with defining polynomial x^2 - 5 with a = 2.236067977499790?)^3 defined as the convex hull of 12 vertices @@ -1630,8 +1596,8 @@ def _sage_doc_(self): """ EXAMPLES:: - sage: c = polymake.cube(3) # optional - polymake - sage: print(c._sage_doc_()) # optional - polymake # random + sage: c = polymake.cube(3) # optional - jupymake + sage: print(c._sage_doc_()) # optional - jupymake # random objects/Polytope: Not necessarily bounded or unbounded polyhedron. Nonetheless, the name "Polytope" is used for two reasons: @@ -1643,7 +1609,7 @@ def _sage_doc_(self): objects/Polytope/specializations/Polytope: A rational polyhedron realized in Q^d - sage: print(c.FACETS._sage_doc_()) # optional - polymake # random + sage: print(c.FACETS._sage_doc_()) # optional - jupymake # random property_types/Algebraic Types/SparseMatrix: A SparseMatrix is a two-dimensional associative array with row and column indices as keys; elements equal to the default value (ElementType(), which is 0 for most numerical types) are not stored, but implicitly encoded by the gaps in the key set. Each row and column is organized as an AVL-tree. @@ -1703,13 +1669,13 @@ class PolymakeFunctionElement(InterfaceFunctionElement): EXAMPLES:: - sage: c = polymake.cube(2) # optional - polymake - sage: V = polymake.new_object('Vector', [1,0,0]) # optional - polymake - sage: V # optional - polymake + sage: c = polymake.cube(2) # optional - jupymake + sage: V = polymake.new_object('Vector', [1,0,0]) # optional - jupymake + sage: V # optional - jupymake 1 0 0 - sage: c.contains # optional - polymake + sage: c.contains # optional - jupymake Member function 'contains' of Polymake::polytope::Polytope__Rational object - sage: c.contains(V) # optional - polymake + sage: c.contains(V) # optional - jupymake true """ @@ -1725,10 +1691,10 @@ def __init__(self, obj, name, memberfunction=False): EXAMPLES:: - sage: p = polymake.rand_sphere(3, 13, seed=12) # optional - polymake - sage: p.minkowski_sum_fukuda # optional - polymake + sage: p = polymake.rand_sphere(3, 13, seed=12) # optional - jupymake + sage: p.minkowski_sum_fukuda # optional - jupymake minkowski_sum_fukuda (bound to Polymake::polytope::Polytope__Rational object) - sage: p.get_schedule # optional - polymake + sage: p.get_schedule # optional - jupymake Member function 'get_schedule' of Polymake::polytope::Polytope__Rational object """ @@ -1740,10 +1706,10 @@ def _repr_(self): """ EXAMPLES:: - sage: p = polymake.rand_sphere(3, 13, seed=12) # optional - polymake - sage: p.minkowski_sum_fukuda # optional - polymake + sage: p = polymake.rand_sphere(3, 13, seed=12) # optional - jupymake + sage: p.minkowski_sum_fukuda # optional - jupymake minkowski_sum_fukuda (bound to Polymake::polytope::Polytope__Rational object) - sage: p.contains # optional - polymake + sage: p.contains # optional - jupymake Member function 'contains' of Polymake::polytope::Polytope__Rational object """ @@ -1758,11 +1724,11 @@ def __call__(self, *args, **kwds): We consider both member functions of an element and global functions bound to an element:: - sage: p = polymake.rand_sphere(3, 13, seed=12) # optional - polymake - sage: p.get_schedule('"VERTICES"') # optional - polymake # random + sage: p = polymake.rand_sphere(3, 13, seed=12) # optional - jupymake + sage: p.get_schedule('"VERTICES"') # optional - jupymake # random sensitivity check for VertexPerm cdd.convex_hull.canon: POINTED, RAYS, LINEALITY_SPACE : INPUT_RAYS - sage: p.minkowski_sum_fukuda(p).F_VECTOR # optional - polymake # not tested + sage: p.minkowski_sum_fukuda(p).F_VECTOR # optional - jupymake # not tested 13 33 22 """ @@ -1781,8 +1747,8 @@ def _sage_doc_(self): EXAMPLES:: - sage: p = polymake.rand_sphere(3, 13, seed=12) # optional - polymake - sage: print(p.get_schedule._sage_doc_()) # optional - polymake # random + sage: p = polymake.rand_sphere(3, 13, seed=12) # optional - jupymake + sage: print(p.get_schedule._sage_doc_()) # optional - jupymake # random objects/Core::Object/methods/get_schedule: get_schedule(request; ... ) -> Core::RuleChain @@ -1798,7 +1764,7 @@ def _sage_doc_(self): Several requests may be listed. Returns Core::RuleChain - sage: print(p.minkowski_sum_fukuda._sage_doc_()) # optional - polymake # random + sage: print(p.minkowski_sum_fukuda._sage_doc_()) # optional - jupymake # random functions/Producing a polytope from polytopes/minkowski_sum_fukuda: minkowski_sum_fukuda(summands) -> Polytope @@ -1827,655 +1793,6 @@ def _sage_doc_(self): return P.help(self._name.split("->")[-1], pager=False) -class PolymakeExpect(PolymakeAbstract, Expect): - r""" - Interface to the polymake interpreter using pexpect. - - In order to use this interface, you need to either install the - optional polymake package for Sage, or install polymake system-wide - on your computer; it is available from https://polymake.org. - - Type ``polymake.[tab]`` for a list of most functions - available from your polymake install. Type - ``polymake.Function?`` for polymake's help about a given ``Function``. - Type ``polymake(...)`` to create a new polymake - object, and ``polymake.eval(...)`` to run a string using - polymake and get the result back as a string. - - EXAMPLES:: - - sage: from sage.interfaces.polymake import polymake_expect as polymake - sage: type(polymake) - <...sage.interfaces.polymake.PolymakeExpect... - sage: p = polymake.rand_sphere(4, 20, seed=5) # optional - polymake_expect - sage: p # optional - polymake_expect - Random spherical polytope of dimension 4; seed=5... - sage: set_verbose(3) - sage: p.H_VECTOR; # optional - polymake_expect # random - used package ppl - The Parma Polyhedra Library ... - sage: p.H_VECTOR # optional - polymake_expect - 1 16 40 16 1 - sage: set_verbose(0) - sage: p.F_VECTOR # optional - polymake_expect - 20 94 148 74 - sage: print(p.F_VECTOR._sage_doc_()) # optional - polymake_expect # random - property_types/Algebraic Types/Vector: - A type for vectors with entries of type Element. - - You can perform algebraic operations such as addition or scalar multiplication. - - You can create a new Vector by entering its elements, e.g.: - $v = new Vector(1,2,3); - or - $v = new Vector([1,2,3]); - - .. automethod:: _eval_line - """ - - def __init__(self, script_subdirectory=None, - logfile=None, server=None, server_tmpdir=None, - seed=None, command=None): - """ - TESTS:: - - sage: from sage.interfaces.polymake import PolymakeExpect - sage: PolymakeExpect() - Polymake - sage: PolymakeExpect().is_running() - False - - """ - if command is None: - command = "env TERM=dumb {}".format(os.getenv('SAGE_POLYMAKE_COMMAND') or 'polymake') - PolymakeAbstract.__init__(self, seed=seed) - Expect.__init__(self, - name="polymake", - command=command, - prompt="polytope > ", - server=server, - server_tmpdir=server_tmpdir, - script_subdirectory=script_subdirectory, - restart_on_ctrlc=False, - logfile=logfile, - eval_using_file_cutoff=1024) # > 1024 causes hangs - - def _start(self, alt_message=None): - """ - Start the polymake interface in the application "polytope". - - .. NOTE:: - - There should be no need to call this explicitly. - - TESTS:: - - sage: from sage.interfaces.polymake import polymake_expect as polymake - sage: polymake.application('fan') # optional - polymake_expect - sage: 'normal_fan' in dir(polymake) # optional - polymake_expect - True - sage: polymake.quit() # optional - polymake_expect - sage: polymake._start() # optional - polymake_expect - doctest...: DeprecationWarning: the pexpect-based interface to - polymake is deprecated. - Install package jupymake so that Sage can use the more robust - jupymake-based interface to polymake - See https://trac.sagemath.org/27745 for details. - - Since 'normal_fan' is not defined in the polymake application 'polytope', - we now get:: - - sage: 'normal_fan' in dir(polymake) # optional - polymake_expect - False - - """ - from sage.misc.superseded import deprecation - if not self.is_running(): - try: - self._change_prompt("polytope > ") - Expect._start(self, alt_message=None) - except RuntimeError: - raise RuntimeError("runtime error with deprecated pexpect-based interface to polymake; please install jupymake") - deprecation(27745, "the pexpect-based interface to polymake is deprecated. Install package jupymake so that Sage can use the more robust jupymake-based interface to polymake") - PolymakeAbstract._start(self) - self.eval('use File::Slurp;') - - def _quit_string(self): - """ - TESTS:: - - sage: from sage.interfaces.polymake import polymake_expect as polymake - sage: polymake._quit_string() - 'exit;' - """ - return "exit;" - - def _keyboard_interrupt(self): - r""" - Interrupt a computation with . - - TESTS: - - For reasons that are not clear to the author, the following test - is very flaky. Therefore, this test is marked as "not tested". :: - - sage: from sage.interfaces.polymake import polymake_expect as polymake - sage: c = polymake.cube(15) # optional - polymake_expect - sage: alarm(1) # not tested - sage: try: # not tested # indirect doctest - ....: c.F_VECTOR - ....: except KeyboardInterrupt: - ....: pass - Interrupting Polymake... - doctest:warning - ... - RuntimeWarning: We ignore that Polymake issues warning during keyboard interrupt - doctest:warning - ... - RuntimeWarning: We ignore that Polymake raises error during keyboard interrupt - - Afterwards, the interface should still be running. :: - - sage: c.N_FACETS # optional - polymake_expect - 30 - - """ - if not self.is_running(): - raise KeyboardInterrupt - print("Interrupting %s..." % self) - while True: - try: - self._expect.send(chr(3)) - except pexpect.ExceptionPexpect as msg: - raise pexpect.ExceptionPexpect("THIS IS A BUG -- PLEASE REPORT. This should never happen.\n" + msg) - sleep(0.1) - i = self._expect.expect_list(self._prompt, timeout=1) - if i == 0: - break - elif i == 7: # EOF - warnings.warn("Polymake {} during keyboard interrupt".format(_available_polymake_answers[i]), RuntimeWarning) - self._crash_msg() - self.quit() - elif i == 8: # Timeout - self.quit() - raise RuntimeError("{} interface is not responding. We closed it".format(self)) - elif i != 3: # Anything but a "computation killed" - warnings.warn("We ignore that {} {} during keyboard interrupt".format(self, _available_polymake_answers[i]), RuntimeWarning) - raise KeyboardInterrupt("Ctrl-c pressed while running {}".format(self)) - - def _synchronize(self): - """ - TESTS:: - - sage: from sage.interfaces.polymake import polymake_expect as polymake - sage: Q = polymake.cube(4) # optional - polymake_expect - sage: polymake('"ok"') # optional - polymake_expect - ok - sage: polymake._expect.sendline() # optional - polymake_expect - 1 - - Now the interface is badly out of sync:: - - sage: polymake('"foobar"') # optional - polymake_expect - ) failed: - ...PolymakeError: Can't locate object method "description" via package "1" - (perhaps you forgot to load "1"?)...> - sage: Q.typeof() # optional - polymake_expect # random - ('foobar...', 'Polymake::polytope::Polytope__Rational') - sage: Q.typeof.clear_cache() # optional - polymake_expect - - After synchronisation, things work again as expected:: - - sage: polymake._synchronize() # optional - polymake_expect - doctest:warning - ... - UserWarning: Polymake seems out of sync: - The expected output did not appear before reaching the next prompt. - sage: polymake('"back to normal"') # optional - polymake_expect - back to normal - sage: Q.typeof() # optional - polymake_expect - ('Polymake::polytope::Polytope__Rational', 'ARRAY') - - """ - if not self.is_running(): - return - rnd = randrange(2147483647) - res = str(rnd+1) - cmd = 'print 1+{};' + os.linesep - self._sendstr(cmd.format(rnd)) - pat = self._expect.expect(self._prompt, timeout=0.5) - # 0: normal prompt - # 1: continuation prompt - # 2: user input expected when requestion "help" - # 3: what we are looking for when interrupting a computation - # 4: error - # 5: warning - # 6: anything but an error or warning, thus, an information - # 7: unexpected end of the stream - # 8: (expected) timeout - if pat == 8: # timeout - warnings.warn("{} unexpectedly {} during synchronisation.".format(self, _available_polymake_answers[pat]), RuntimeWarning) - self.interrupt() - # ... but we continue, as that probably means we currently are at the end of the buffer - elif pat == 7: # EOF - self._crash_msg() - self.quit() - elif pat == 0: - # We got the right prompt, but perhaps in a wrong position in the stream - # The result of the addition should appear *before* our prompt - if res not in bytes_to_str(self._expect.before): - try: - warnings.warn("{} seems out of sync: The expected output did not appear before reaching the next prompt.".format(self)) - while True: - i = self._expect.expect_list(self._prompt, timeout=0.1) - if i == 8: # This time, we do expect a timeout - return - elif i > 0: - raise RuntimeError("Polymake unexpectedly {}".format(_available_polymake_answers[i])) - except pexpect.TIMEOUT: - warnings.warn("A timeout has occurred when synchronising {}.".format(self), RuntimeWarning) - self._interrupt() - except pexpect.EOF: - self._crash_msg() - self.quit() - else: - return - else: - raise RuntimeError("Polymake unexpectedly {}".format(_available_polymake_answers[pat])) - - def _eval_line(self, line, allow_use_file=True, wait_for_prompt=True, restart_if_needed=True, **kwds): - r""" - Evaluate a command. - - INPUT: - - - ``line`` -- string; a command to be evaluated - - ``allow_use_file`` -- (default: ``True``) bool; whether or not - to use a file if the line is very long - - ``wait_for_prompt`` -- (default: ``True``) bool; whether or not - to wait before polymake returns a prompt. If it is a string, it is - considered as alternative prompt to be waited for - - ``restart_if_needed`` (default: ``True``) bool; whether or - not to restart polymake in case something goes wrong - - further optional arguments (e.g., timeout) that will be passed to - :meth:`pexpect.pty_spawn.spawn.expect`. Note that they are ignored - if the line is too long and thus is evaluated via a file. So, - if a timeout is defined, it should be accompanied by - ``allow_use_file=False``. - - Different reaction types of polymake, including warnings, comments, - errors, request for user interaction, and yielding a continuation prompt, - are taken into account. - - Usually, this method is indirectly called via - :meth:`~sage.interfaces.expect.Expect.eval`. - - EXAMPLES:: - - sage: from sage.interfaces.polymake import polymake_expect as polymake # optional - polymake_expect - sage: p = polymake.cube(3) # optional - polymake_expect # indirect doctest - - Here we see that remarks printed by polymake are displayed if - the verbosity is positive:: - - sage: set_verbose(1) - sage: p.N_LATTICE_POINTS # optional - polymake_expect # random - used package latte - LattE (Lattice point Enumeration) is a computer software dedicated to the - problems of counting lattice points and integration inside convex polytopes. - Copyright by Matthias Koeppe, Jesus A. De Loera and others. - http://www.math.ucdavis.edu/~latte/ - 27 - sage: set_verbose(0) - - If polymake raises an error, the polymake *interface* raises - a :class:`PolymakeError`:: - - sage: polymake.eval('FOOBAR(3);') # optional - polymake_expect - Traceback (most recent call last): - ... - PolymakeError: Undefined subroutine &Polymake::User::FOOBAR called... - - If a command is incomplete, then polymake returns a continuation - prompt. In that case, we raise an error:: - - sage: polymake.eval('print 3') # optional - polymake_expect - Traceback (most recent call last): - ... - SyntaxError: Incomplete polymake command 'print 3' - sage: polymake.eval('print 3;') # optional - polymake_expect - '3' - - However, if the command contains line breaks but eventually is complete, - no error is raised:: - - sage: print(polymake.eval('$tmp="abc";\nprint $tmp;')) # optional - polymake_expect - abc - - When requesting help, polymake sometimes expect the user to choose - from a list. In that situation, we abort with a warning, and show - the list from which the user can choose; we could demonstrate this using - the :meth:`help` method, but here we use an explicit code evaluation:: - - sage: print(polymake.eval('help "TRIANGULATION";')) # optional - polymake_expect # random - doctest:warning - ... - UserWarning: Polymake expects user interaction. We abort and return - the options that Polymake provides. - There are 5 help topics matching 'TRIANGULATION': - 1: objects/Cone/properties/Triangulation and volume/TRIANGULATION - 2: objects/Polytope/properties/Triangulation and volume/TRIANGULATION - 3: objects/Visualization/Visual::PointConfiguration/methods/TRIANGULATION - 4: objects/Visualization/Visual::Polytope/methods/TRIANGULATION - 5: objects/PointConfiguration/properties/Triangulation and volume/TRIANGULATION - - By default, we just wait until polymake returns a result. However, - it is possible to explicitly set a timeout. The following usually does - work in an interactive session and often in doc tests, too. However, - sometimes it hangs, and therefore we remove it from the tests, for now:: - - sage: c = polymake.cube(15) # optional - polymake_expect - sage: polymake.eval('print {}->F_VECTOR;'.format(c.name()), timeout=1) # not tested # optional - polymake_expect - Traceback (most recent call last): - ... - RuntimeError: Polymake fails to respond timely - - We verify that after the timeout, polymake is still able to give answers:: - - sage: c # optional - polymake_expect - cube of dimension 15 - sage: c.N_VERTICES # optional - polymake_expect - 32768 - - Note, however, that the recovery after a timeout is not perfect. - It may happen that in some situation the interface collapses and - thus polymake would automatically be restarted, thereby losing all - data that have been computed before. - - """ - line = line.strip() - if allow_use_file and wait_for_prompt and self._eval_using_file_cutoff and len(line) > self._eval_using_file_cutoff: - return self._eval_line_using_file(line) - try: - if not self.is_running(): - self._start() - E = self._expect - try: - if len(line) >= 4096: - raise RuntimeError("Sending more than 4096 characters with {} on a line may cause a hang and you're sending {} characters".format(self, len(line))) - E.sendline(line) - if not wait_for_prompt: - return '' - - except OSError as msg: - if restart_if_needed: - # The subprocess most likely crashed. - # If it's really still alive, we fall through - # and raise RuntimeError. - if sys.platform.startswith('sunos'): - # On (Open)Solaris, we might need to wait a - # while because the process might not die - # immediately. See Trac #14371. - for t in [0.5, 1.0, 2.0]: - if E.isalive(): - time.sleep(t) - else: - break - if not E.isalive(): - try: - self._synchronize() - except (TypeError, RuntimeError): - pass - return self._eval_line(line, allow_use_file=allow_use_file, wait_for_prompt=wait_for_prompt, restart_if_needed=False, **kwds) - raise RuntimeError("{}\nError evaluating {} in {}".format(msg, line, self)) - - p_warnings = [] - p_errors = [] - have_warning = False - have_error = False - have_log = False - if len(line) > 0: - first = True - while True: - try: - if isinstance(wait_for_prompt, str): - pat = E.expect(wait_for_prompt, **kwds) - else: - pat = E.expect_list(self._prompt, **kwds) - except pexpect.EOF as msg: - try: - if self.is_local(): - tmp_to_use = self._local_tmpfile() - else: - tmp_to_use = self._remote_tmpfile() - if self._read_in_file_command(tmp_to_use) in line: - raise pexpect.EOF(msg) - except NotImplementedError: - pass - if self._quit_string() in line: - # we expect to get an EOF if we're quitting. - return '' - elif restart_if_needed: # the subprocess might have crashed - try: - self._synchronize() - return self._eval_line(line, allow_use_file=allow_use_file, wait_for_prompt=wait_for_prompt, restart_if_needed=False, **kwds) - except (TypeError, RuntimeError): - pass - raise RuntimeError("{}\n{} crashed executing {}".format(msg, self, line)) - if self._terminal_echo: - out = E.before - else: - out = E.before.rstrip(b'\n\r') - if self._terminal_echo and first: - i = out.find(b"\n") - j = out.rfind(b"\r") - out = out[i + 1:j].replace(b'\r\n', b'\n') - else: - out = out.strip().replace(b'\r\n', b'\n') - first = False - if have_error: - p_errors.append(out) - have_error = False - out = b"" - elif have_warning: - p_warnings.append(out) - have_warning = False - out = b"" - elif have_log: - if get_verbose() > 0: - print(bytes_to_str(out)) - have_log = False - out = b"" - # 0: normal prompt - # 1: continuation prompt - # 2: user input expected when requestion "help" - # 3: what we are looking for when interrupting a computation - # 4: error - # 5: warning - # 6: anything but an error or warning, thus, an information - # 7: unexpected end of the stream - # 8: (expected) timeout - if pat == 0: - have_log = False - have_error = False - have_warning = False - if E.buffer: - if not E.buffer.strip(): - E.send(chr(3)) - sleep(0.1) - pat = E.expect_list(self._prompt) - if E.buffer or pat: - raise RuntimeError("Couldn't return to prompt after command '{}'".format(line)) - break - elif pat == 1: # unexpected continuation prompt - # Return to normal prompt - i = pat - E.send(chr(3)) - sleep(0.1) - i = E.expect_list(self._prompt) - assert i == 0, "Command '{}': Couldn't return to normal prompt after polymake {}. Instead, polymake {}".format(line, _available_polymake_answers[pat], _available_polymake_answers[i]) - raise SyntaxError("Incomplete polymake command '{}'".format(line)) - elif pat == 2: # request for user interaction - # Return to normal prompt - warnings.warn("{} expects user interaction. We abort and return the options that {} provides.".format(self, self)) - i = pat - while i: - self._expect.sendline(chr(3)) - sleep(0.1) - i = self._expect.expect(self._prompt, timeout=0.1) - # User interaction is expected to happen when requesting help - if line.startswith('help'): - out = str_to_bytes(os.linesep).join(out.split(str_to_bytes(os.linesep))[:-1]) - break - else: - RuntimeError("Polymake unexpectedly {}".format(_available_polymake_answers[pat])) - elif pat == 3: # killed by signal - i = pat - while pat != 0: - E.send(chr(3)) - sleep(0.1) - i = E.expect_list(self._prompt) - RuntimeError("Polymake unexpectedly {}".format(_available_polymake_answers[pat])) - elif pat == 4: # polymake error - have_error = True - elif pat == 5: # polymake warning - have_warning = True - elif pat == 6: # apparently polymake prints a comment - have_log = True - elif pat == 7: # we have reached the end of the buffer - warnings.warn("Polymake unexpectedly {}".format(_available_polymake_answers[pat]), RuntimeWarning) - E.buffer = E.before + E.after + E.buffer - break - else: # timeout or some other problem - # Polymake would still continue with the computation. Thus, we send an interrupt - E.send(chr(3)) - sleep(0.1) - while E.expect_list(self._prompt, timeout=0.1): - # ... and since a single Ctrl-c just interrupts *one* of polymake's - # rule chains, we repeat until polymake is running out of rules. - E.send(chr(3)) - sleep(0.1) - raise RuntimeError("Polymake {}".format(_available_polymake_answers[pat])) - else: - out = b'' - except KeyboardInterrupt: - self._keyboard_interrupt() - raise KeyboardInterrupt("Ctrl-c pressed while running {}".format(self)) - for w in p_warnings: - warnings.warn(bytes_to_str(w), RuntimeWarning) - for e in p_errors: - raise PolymakeError(bytes_to_str(e)) - return bytes_to_str(out) - - def application(self, app): - """ - Change to a given polymake application. - - INPUT: - - - ``app``, a string, one of "common", "fulton", "group", "matroid", "topaz", - "fan", "graph", "ideal", "polytope", "tropical" - - EXAMPLES: - - We expose a computation that uses both the 'polytope' and the 'fan' - application of polymake. Let us start by defining a polytope `q` in - terms of inequalities. Polymake knows to compute the f- and h-vector - and finds that the polytope is very ample:: - - sage: from sage.interfaces.polymake import polymake_expect as polymake - sage: q = polymake.new_object("Polytope", INEQUALITIES=[[5,-4,0,1],[-3,0,-4,1],[-2,1,0,0],[-4,4,4,-1],[0,0,1,0],[8,0,0,-1],[1,0,-1,0],[3,-1,0,0]]) # optional - polymake_expect - sage: q.H_VECTOR # optional - polymake_expect - 1 5 5 1 - sage: q.F_VECTOR # optional - polymake_expect - 8 14 8 - sage: q.VERY_AMPLE # optional - polymake_expect - true - - In the application 'fan', polymake can now compute the normal fan - of `q` and its (primitive) rays:: - - sage: polymake.application('fan') # optional - polymake_expect - sage: g = q.normal_fan() # optional - polymake_expect - sage: g.RAYS # optional - polymake_expect - -1 0 1/4 - 0 -1 1/4 - 1 0 0 - 1 1 -1/4 - 0 1 0 - 0 0 -1 - 0 -1 0 - -1 0 0 - sage: g.RAYS.primitive() # optional - polymake_expect - -4 0 1 - 0 -4 1 - 1 0 0 - 4 4 -1 - 0 1 0 - 0 0 -1 - 0 -1 0 - -1 0 0 - - Note that the list of functions available by tab completion depends - on the application. - - TESTS: - - Since 'trop_witness' is not defined in the polymake application 'polytope' - but only in 'tropical', the following shows the effect of changing - the application. :: - - sage: polymake.application('polytope') # optional - polymake_expect - sage: 'trop_witness' in dir(polymake) # optional - polymake_expect - False - sage: polymake.application('tropical') # optional - polymake_expect - sage: 'trop_witness' in dir(polymake) # optional - polymake_expect - True - sage: polymake.application('polytope') # optional - polymake_expect - sage: 'trop_witness' in dir(polymake) # optional - polymake_expect - False - - For completeness, we show what happens when asking for an application - that doesn't exist:: - - sage: polymake.application('killerapp') # optional - polymake_expect - Traceback (most recent call last): - ... - ValueError: Unknown polymake application 'killerapp' - - Of course, a different error results when we send an explicit - command in polymake to change to an unknown application:: - - sage: polymake.eval('application "killerapp";') # optional - polymake_expect - Traceback (most recent call last): - ... - PolymakeError: Unknown application killerapp - - """ - if not self.is_running(): - self._start() - if app not in ["common", "fulton", "group", "matroid", "topaz", "fan", "graph", "ideal", "polytope", "tropical"]: - raise ValueError("Unknown polymake application '{}'".format(app)) - self._application = app - patterns = ["{} > ".format(app), # 0: normal prompt - r"{} \([0-9]+\)> ".format(app), # 1: continuation prompt - "Please choose ", # 2: user input expected when requesting "help" - "killed by signal", # 3: what we are looking for when interrupting a computation - "polymake: +ERROR: +", # 4: error - "polymake: +WARNING: +", # 5: warning - "polymake: +", # 6: anything but an error or warning, thus, an information - pexpect.EOF, # 7: unexpected end of the stream - pexpect.TIMEOUT] # 8: timeout - self._change_prompt(self._expect.compile_pattern_list(patterns)) - self._sendstr('application "{}";{}'.format(app, os.linesep)) - pat = self._expect.expect_list(self._prompt) - if pat: - raise RuntimeError("When changing the application, polymake unexpectedly {}".format(_available_polymake_answers[pat])) - - -Polymake = PolymakeExpect - - class PolymakeJuPyMake(PolymakeAbstract): r""" Interface to the polymake interpreter using JuPyMake. @@ -2730,13 +2047,6 @@ def reduce_load_Polymake(): return polymake -polymake_expect = PolymakeExpect() - -polymake_jupymake = PolymakeJuPyMake() - -from sage.features import PythonModule -if PythonModule("JuPyMake").is_present(): - polymake = polymake_jupymake -else: - polymake = polymake_expect +Polymake = PolymakeJuPyMake +polymake = polymake_jupymake = PolymakeJuPyMake() diff --git a/src/sage/interfaces/povray.py b/src/sage/interfaces/povray.py index 880c5fecbcd..b892cb112cd 100644 --- a/src/sage/interfaces/povray.py +++ b/src/sage/interfaces/povray.py @@ -37,7 +37,7 @@ def __call__(self, pov_file, outfile='sage.ppm', block=True, **kwargs): outfile = os.path.abspath(os.path.expanduser(outfile)) - if not('W' in kwargs and 'H' in kwargs): + if not ('W' in kwargs and 'H' in kwargs): return "You must specify a width and height." cmd = "povray -D +FP +I%s +O%s " % (pov_file, outfile) diff --git a/src/sage/interfaces/primecount.py b/src/sage/interfaces/primecount.py index e037cb2794d..e647bf9b980 100644 --- a/src/sage/interfaces/primecount.py +++ b/src/sage/interfaces/primecount.py @@ -3,4 +3,3 @@ from sage.misc.lazy_import import lazy_import lazy_import("primecountpy.primecount", ['phi', 'nth_prime', 'prime_pi', 'prime_pi_128'], deprecation=(32894, "the module sage.interfaces.primecount is deprecated - use primecountpy.primecount instead")) - diff --git a/src/sage/interfaces/qepcad.py b/src/sage/interfaces/qepcad.py index 549877b392d..d4b10353e0c 100644 --- a/src/sage/interfaces/qepcad.py +++ b/src/sage/interfaces/qepcad.py @@ -687,7 +687,7 @@ def _update_command_info(): cache = {} with open(os.path.join(SAGE_LOCAL, 'share/qepcad', 'qepcad.help')) as help: - assert(help.readline().strip() == '@') + assert help.readline().strip() == '@' while True: cmd_line = help.readline() @@ -698,7 +698,7 @@ def _update_command_info(): break (cmd, id, phases, kind) = cmd_line.split() - assert(help.readline().strip() == '@') + assert help.readline().strip() == '@' help_text = '' help_line = help.readline() @@ -2451,8 +2451,8 @@ def __init__(self, parent, lines): saw_signs = True if saw_signs and 'Level' in line: (lev, n, colon, signs) = line.split() - assert(lev == 'Level' and colon == ':') - assert(int(n) == len(all_signs) + 1) + assert lev == 'Level' and colon == ':' + assert int(n) == len(all_signs) + 1 signs = signs.replace('+','1').replace('-','-1').replace(')',',)') all_signs.append(sage_eval(signs)) if 'PRIMITIVE' in line: @@ -2465,13 +2465,13 @@ def __init__(self, parent, lines): if 'Coordinate ' in line: (coord_n, val) = line.split('=') n = int(coord_n.split()[1]) - assert(n == len(all_coordinates) + 1) + assert n == len(all_coordinates) + 1 if n == self._level and saw_extended: grab_extended = True else: all_coordinates.append(val) elif grab_extended: - assert('=' in line) + assert '=' in line grab_extended = False all_coordinates.append(line.split('=')[1]) @@ -2752,6 +2752,4 @@ def sample_point_dict(self): """ points = self.sample_point() vars = self._parent._varlist - return dict([(vars[i], points[i]) for i in range(len(points))]) - diff --git a/src/sage/interfaces/r.py b/src/sage/interfaces/r.py index 565d6a1da93..72481d3e1ef 100644 --- a/src/sage/interfaces/r.py +++ b/src/sage/interfaces/r.py @@ -343,7 +343,7 @@ def _setup_r_to_sage_converter(): sage: r.options(width="60").sage() # optional - rpy2 {'DATA': {'width': 60}, '_Names': 'width'} - The conversion can handle "not a number", infintiy, imaginary values and + The conversion can handle "not a number", infinity, imaginary values and missing values:: sage: r(-17).sqrt().sage() # optional - rpy2 @@ -476,13 +476,12 @@ def __init__(self, sage: r == loads(dumps(r)) # optional - rpy2 True """ - Interface.__init__( self, - name = 'r', # The capitalized version of this is used for printing. + name='r', # The capitalized version of this is used for printing. ) self._seed = seed - self._initialized = False # done lazily + self._initialized = False # done lazily def _lazy_init(self): """ diff --git a/src/sage/interfaces/scilab.py b/src/sage/interfaces/scilab.py index c1144d40ab0..8349739633c 100644 --- a/src/sage/interfaces/scilab.py +++ b/src/sage/interfaces/scilab.py @@ -220,15 +220,15 @@ def __init__(self, maxread=None, script_subdirectory=None, sage: del sci_obj """ Expect.__init__(self, - name = 'scilab', - prompt = '-->', - command = "scilab -nw", - server = server, - server_tmpdir = server_tmpdir, - script_subdirectory = script_subdirectory, - restart_on_ctrlc = False, - verbose_start = False, - logfile = logfile, + name='scilab', + prompt='-->', + command="scilab -nw", + server=server, + server_tmpdir=server_tmpdir, + script_subdirectory=script_subdirectory, + restart_on_ctrlc=False, + verbose_start=False, + logfile=logfile, eval_using_file_cutoff=100) self._seed = seed @@ -561,4 +561,3 @@ def scilab_version(): 'scilab-...' """ return str(scilab('getversion()')).strip() - diff --git a/src/sage/interfaces/singular.py b/src/sage/interfaces/singular.py index 41e9c9ff251..9c9586d8bc7 100644 --- a/src/sage/interfaces/singular.py +++ b/src/sage/interfaces/singular.py @@ -1,31 +1,6 @@ r""" Interface to Singular -AUTHORS: - -- David Joyner and William Stein (2005): first version - -- Martin Albrecht (2006-03-05): code so singular.[tab] and x = - singular(...), x.[tab] includes all singular commands. - -- Martin Albrecht (2006-03-06): This patch adds the equality symbol to - singular. Also fix a problem in which " " as prompt means comparison - will break all further communication with Singular. - -- Martin Albrecht (2006-03-13): added current_ring() and - current_ring_name() - -- William Stein (2006-04-10): Fixed problems with ideal constructor - -- Martin Albrecht (2006-05-18): added sage_poly. - -- Simon King (2010-11-23): Reduce the overhead caused by waiting for - the Singular prompt by doing garbage collection differently. - -- Simon King (2011-06-06): Make conversion from Singular to Sage more flexible. - -- Simon King (2015): Extend pickling capabilities. - Introduction ------------ @@ -37,7 +12,6 @@ your computer; this should be the case, since Singular is included with Sage. The interface offers three pieces of functionality: - #. ``singular_console()`` - A function that dumps you into an interactive command-line Singular session. @@ -238,10 +212,6 @@ An Important Concept -------------------- -AUTHORS: - -- Neal Harris - The following illustrates an important concept: how Sage interacts with the data being used and returned by Singular. Let's compute a Groebner basis for some ideal, using Singular through Sage. @@ -325,6 +295,34 @@ [Ideal (z) of Multivariate Polynomial Ring in x, z over Number Field in p with defining polynomial p^2 - p - 1] sage: [ J.gens() for J in I.primary_decomposition("gtz")] [[z]] + +AUTHORS: + +- David Joyner and William Stein (2005): first version + +- Neal Harris (unknown): perhaps added "An Important Concept" + +- Martin Albrecht (2006-03-05): code so singular.[tab] and x = + singular(...), x.[tab] includes all singular commands. + +- Martin Albrecht (2006-03-06): This patch adds the equality symbol to + singular. Also fix a problem in which " " as prompt means comparison + will break all further communication with Singular. + +- Martin Albrecht (2006-03-13): added current_ring() and + current_ring_name() + +- William Stein (2006-04-10): Fixed problems with ideal constructor + +- Martin Albrecht (2006-05-18): added sage_poly. + +- Simon King (2010-11-23): Reduce the overhead caused by waiting for + the Singular prompt by doing garbage collection differently. + +- Simon King (2011-06-06): Make conversion from Singular to Sage more flexible. + +- Simon King (2015): Extend pickling capabilities. + """ # **************************************************************************** @@ -396,19 +394,19 @@ def __init__(self, maxread=None, script_subdirectory=None, prompt = '> ' Expect.__init__(self, terminal_echo=False, - name = 'singular', - prompt = prompt, + name='singular', + prompt=prompt, # no tty, fine grained cputime() # and do not display CTRL-C prompt - command = "{} -t --ticks-per-sec 1000 --cntrlc=a".format( + command="{} -t --ticks-per-sec 1000 --cntrlc=a".format( shlex.quote(sage.features.singular.Singular().absolute_filename())), - server = server, - server_tmpdir = server_tmpdir, - script_subdirectory = script_subdirectory, - restart_on_ctrlc = True, - verbose_start = False, - logfile = logfile, - eval_using_file_cutoff=100 if os.uname()[0]=="SunOS" else 1000) + server=server, + server_tmpdir=server_tmpdir, + script_subdirectory=script_subdirectory, + restart_on_ctrlc=True, + verbose_start=False, + logfile=logfile, + eval_using_file_cutoff=100 if os.uname()[0] == "SunOS" else 1000) self.__libs = [] self._prompt_wait = prompt self.__to_clear = [] # list of variable names that need to be cleared. @@ -1496,8 +1494,6 @@ def __bool__(self): P = self.parent() return P.eval('%s == 0' % self.name()) == '0' - - def sage_polystring(self): r""" If this Singular element is a polynomial, return a string @@ -2069,7 +2065,6 @@ def set_ring(self): """ self.parent().set_ring(self) - def sage_flattened_str_list(self): """ EXAMPLES:: @@ -2596,6 +2591,7 @@ def flush(self): """ sys.stdout.flush() + class SingularGBDefaultContext: """ Within this context all Singular Groebner basis calculations are diff --git a/src/sage/interfaces/sympy.py b/src/sage/interfaces/sympy.py index 74764b13716..2c847d56892 100644 --- a/src/sage/interfaces/sympy.py +++ b/src/sage/interfaces/sympy.py @@ -1181,7 +1181,7 @@ def sympy_set_to_list(set, vars): elif isinstance(set, (Union, Interval)): x = vars[0] if isinstance(set, Interval): - left,right,lclosed,rclosed = set._args + left, right, lclosed, rclosed = set._args if lclosed: rel1 = [x._sage_() > left._sage_()] else: @@ -1198,4 +1198,3 @@ def sympy_set_to_list(set, vars): if isinstance(set, Union): return [sympy_set_to_list(iv, vars) for iv in set._args] return set - diff --git a/src/sage/interfaces/tides.py b/src/sage/interfaces/tides.py index 6f29de666cc..906f95396e5 100644 --- a/src/sage/interfaces/tides.py +++ b/src/sage/interfaces/tides.py @@ -378,7 +378,7 @@ def remove_constants(l1,l2): def genfiles_mintides(integrator, driver, f, ics, initial, final, delta, - tolrel=1e-16, tolabs=1e-16, output = ''): + tolrel=1e-16, tolabs=1e-16, output=''): r""" Generate the needed files for the min_tides library. @@ -637,9 +637,10 @@ def genfiles_mintides(integrator, driver, f, ics, initial, final, delta, outfile.write('\treturn 0; \n }') outfile.close() + def genfiles_mpfr(integrator, driver, f, ics, initial, final, delta, - parameters = None , parameter_values = None, dig = 20, tolrel=1e-16, - tolabs=1e-16, output = ''): + parameters=None, parameter_values=None, dig=20, tolrel=1e-16, + tolabs=1e-16, output=''): r""" Generate the needed files for the mpfr module of the tides library. diff --git a/src/sage/knots/knotinfo.py b/src/sage/knots/knotinfo.py index ed0f90dc1b9..df893a488f6 100644 --- a/src/sage/knots/knotinfo.py +++ b/src/sage/knots/knotinfo.py @@ -324,7 +324,7 @@ class KnotInfoBase(Enum): def __gt__(self, other): r""" - Implement comparision of different items in order to have ``sorted`` work. + Implement comparison of different items in order to have ``sorted`` work. EXAMPLES:: diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index 72ecdb80473..10035f729ef 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -3759,7 +3759,7 @@ def _knotinfo_matching_list(self): # 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 + return [L], True # pd_notation is unique in the KnotInfo database if L.braid_index() <= br_ind: if self._markov_move_cmp(L.braid()): diff --git a/src/sage/libs/gap/all.py b/src/sage/libs/gap/all.py index fd40910d9e7..e69de29bb2d 100644 --- a/src/sage/libs/gap/all.py +++ b/src/sage/libs/gap/all.py @@ -1,4 +0,0 @@ - - - - diff --git a/src/sage/libs/gap/test_long.py b/src/sage/libs/gap/test_long.py index 5a929ab4585..262db5ad287 100644 --- a/src/sage/libs/gap/test_long.py +++ b/src/sage/libs/gap/test_long.py @@ -3,7 +3,6 @@ These stress test the garbage collection inside GAP """ - from sage.libs.gap.libgap import libgap @@ -26,8 +25,8 @@ def test_loop_2(): sage: from sage.libs.gap.test_long import test_loop_2 sage: test_loop_2() # long time (10s on sage.math, 2013) """ - G =libgap.FreeGroup(2) - a,b = G.GeneratorsOfGroup() + G = libgap.FreeGroup(2) + a, b = G.GeneratorsOfGroup() for i in range(100): rel = libgap([a**2, b**2, a*b*a*b]) H = G / rel @@ -47,12 +46,9 @@ def test_loop_3(): sage: test_loop_3() # long time (31s on sage.math, 2013) """ G = libgap.FreeGroup(2) - (a,b) = G.GeneratorsOfGroup() + a, b = G.GeneratorsOfGroup() for i in range(300000): - lis=libgap([]) + lis = libgap([]) lis.Add(a ** 2) lis.Add(b ** 2) lis.Add(b * a) - - - diff --git a/src/sage/libs/glpk/error.pyx b/src/sage/libs/glpk/error.pyx index 9f7df469a22..2e3ac1c386a 100644 --- a/src/sage/libs/glpk/error.pyx +++ b/src/sage/libs/glpk/error.pyx @@ -29,10 +29,10 @@ class GLPKError(MIPSolverException): EXAMPLES:: sage: from sage.libs.glpk.error import GLPKError - sage: raise GLPKError("trouble!") + sage: raise GLPKError("trouble") Traceback (most recent call last): ... - GLPKError: trouble! + GLPKError: trouble """ pass diff --git a/src/sage/libs/libecm.pyx b/src/sage/libs/libecm.pyx index 1deacb45b39..886a9b816e4 100644 --- a/src/sage/libs/libecm.pyx +++ b/src/sage/libs/libecm.pyx @@ -5,7 +5,7 @@ The Elliptic Curve Method for Integer Factorization (ECM) Sage includes GMP-ECM, which is a highly optimized implementation of Lenstra's elliptic curve factorization method. -See http://ecm.gforge.inria.fr/ for more about GMP-ECM. +See https://gitlab.inria.fr/zimmerma/ecm for more about GMP-ECM. This file provides a Cython interface to the GMP-ECM library. AUTHORS: diff --git a/src/sage/libs/lrcalc/lrcalc.py b/src/sage/libs/lrcalc/lrcalc.py index b541bfacd89..cf50d52a927 100644 --- a/src/sage/libs/lrcalc/lrcalc.py +++ b/src/sage/libs/lrcalc/lrcalc.py @@ -192,6 +192,7 @@ from sage.rings.integer import Integer import lrcalc + def _lrcalc_dict_to_sage(result): r""" Translate from lrcalc output format to Sage expected format. @@ -517,4 +518,3 @@ def lrskew(outer, inner, weight=None, maxrows=-1): w[j] += 1 if w == wt: yield ST.from_shape_and_word(shape, [i+1 for i in data]) - diff --git a/src/sage/libs/mpmath/all.py b/src/sage/libs/mpmath/all.py index c8d60c91142..cae40f79314 100644 --- a/src/sage/libs/mpmath/all.py +++ b/src/sage/libs/mpmath/all.py @@ -14,13 +14,12 @@ # Use mpmath internal functions for constants, to avoid unnecessary overhead _constants_funcs = { - 'glaisher' : glaisher_fixed, - 'khinchin' : khinchin_fixed, - 'twinprime' : twinprime_fixed, - 'mertens' : mertens_fixed + 'glaisher': glaisher_fixed, + 'khinchin': khinchin_fixed, + 'twinprime': twinprime_fixed, + 'mertens': mertens_fixed } def eval_constant(name, ring): prec = ring.precision() + 20 return ring(_constants_funcs[name](prec)) >> prec - diff --git a/src/sage/libs/singular/decl.pxd b/src/sage/libs/singular/decl.pxd index d6b2f28bf2a..8e3ac314b67 100644 --- a/src/sage/libs/singular/decl.pxd +++ b/src/sage/libs/singular/decl.pxd @@ -1134,8 +1134,16 @@ cdef extern from "singular/kernel/GBEngine/kstd1.h": cdef extern int Kstd1_deg # degBound, default 0 cdef extern int Kstd1_mu # multBound, default 0 +cdef extern from "singular/kernel/ideals.h": + ctypedef ideal * resolvente + cdef extern from "singular/kernel/GBEngine/syz.h": ctypedef struct syStrategy "ssyStrategy": + resolvente fullres; + resolvente minres; + int length; + int regularity; + short list_length; short references cdef extern from "singular/polys/ext_fields/transext.h": diff --git a/src/sage/libs/singular/function.pxd b/src/sage/libs/singular/function.pxd index 232bf198538..503384004d5 100644 --- a/src/sage/libs/singular/function.pxd +++ b/src/sage/libs/singular/function.pxd @@ -19,6 +19,7 @@ from sage.libs.singular.decl cimport leftv, idhdl, syStrategy, matrix, poly, ide from sage.libs.singular.decl cimport ring as singular_ring from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomialRing_libsingular, MPolynomial_libsingular +cdef new_sage_polynomial(ring, poly *p) cdef poly* access_singular_poly(p) except -1 cdef singular_ring* access_singular_ring(r) except -1 diff --git a/src/sage/libs/singular/function.pyx b/src/sage/libs/singular/function.pyx index 8908f3f499d..d7255b34440 100644 --- a/src/sage/libs/singular/function.pyx +++ b/src/sage/libs/singular/function.pyx @@ -7,16 +7,6 @@ or interprocess communication overhead. Users who do not want to call Singular functions directly, usually do not have to worry about this interface, since it is handled by higher level functions in Sage. -AUTHORS: - -- Michael Brickenstein (2009-07): initial implementation, overall design -- Martin Albrecht (2009-07): clean up, enhancements, etc. -- Michael Brickenstein (2009-10): extension to more Singular types -- Martin Albrecht (2010-01): clean up, support for attributes -- Simon King (2011-04): include the documentation provided by Singular as a code block. -- Burcin Erocal, Michael Brickenstein, Oleksandr Motsak, Alexander Dreyer, Simon King - (2011-09) plural support - EXAMPLES: The direct approach for loading a Singular function is to call the @@ -62,6 +52,16 @@ TESTS:: sage: std = singular_function('std') sage: loads(dumps(std)) == std True + +AUTHORS: + +- Michael Brickenstein (2009-07): initial implementation, overall design +- Martin Albrecht (2009-07): clean up, enhancements, etc +- Michael Brickenstein (2009-10): extension to more Singular types +- Martin Albrecht (2010-01): clean up, support for attributes +- Simon King (2011-04): include the documentation provided by Singular as a code block +- Burcin Erocal, Michael Brickenstein, Oleksandr Motsak, Alexander Dreyer, Simon King (2011-09): plural support + """ #***************************************************************************** @@ -82,36 +82,29 @@ from sage.cpython.string cimport str_to_bytes, char_to_str from sage.structure.sage_object cimport SageObject from sage.structure.richcmp cimport richcmp - -from sage.rings.integer cimport Integer +from sage.structure.sequence import Sequence, Sequence_generic from sage.modules.free_module_element cimport FreeModuleElement_generic_dense +from sage.rings.integer cimport Integer from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libsingular, new_MP from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomialRing_libsingular - from sage.rings.polynomial.plural cimport NCPolynomialRing_plural, NCPolynomial_plural, new_NCP from sage.rings.polynomial.multi_polynomial_ideal import NCPolynomialIdeal - from sage.rings.polynomial.multi_polynomial_ideal import MPolynomialIdeal - from sage.rings.polynomial.multi_polynomial_ideal_libsingular cimport sage_ideal_to_singular_ideal, singular_ideal_to_sage_sequence +from sage.rings.polynomial.multi_polynomial_sequence import PolynomialSequence, PolynomialSequence_generic from sage.libs.singular.decl cimport * - from sage.libs.singular.option import opt_ctx from sage.libs.singular.polynomial cimport singular_vector_maximal_component, singular_polynomial_check from sage.libs.singular.singular cimport sa2si, si2sa, si2sa_intvec - from sage.libs.singular.singular import error_messages from sage.interfaces.singular import get_docstring from sage.misc.verbose import get_verbose -from sage.structure.sequence import Sequence, Sequence_generic -from sage.rings.polynomial.multi_polynomial_sequence import PolynomialSequence, PolynomialSequence_generic - cdef poly* sage_vector_to_poly(v, ring *r) except -1: """ @@ -156,7 +149,7 @@ cdef class RingWrap: return "" def __dealloc__(self): - if self._ring!=NULL: + if self._ring != NULL: self._ring.ref -= 1 def ngens(self): @@ -292,6 +285,7 @@ cdef class RingWrap: """ rPrint(self._ring) + cdef class Resolution: """ A simple wrapper around Singular's resolutions. @@ -308,9 +302,10 @@ cdef class Resolution: sage: M = syz(I) sage: resolution = mres(M, 0) """ - #FIXME: still not working noncommutative + # FIXME: still not working noncommutative assert is_sage_wrapper_for_singular_ring(base_ring) self.base_ring = base_ring + def __repr__(self): """ EXAMPLES:: @@ -326,6 +321,7 @@ cdef class Resolution: """ return "" + def __dealloc__(self): """ EXAMPLES:: @@ -342,6 +338,7 @@ cdef class Resolution: if self._resolution != NULL: self._resolution.references -= 1 + cdef leftv* new_leftv(void *data, res_type): """ INPUT: @@ -487,6 +484,7 @@ def all_vectors(s): return False return True + cdef class Converter(SageObject): """ A :class:`Converter` interfaces between Sage objects and Singular @@ -686,8 +684,6 @@ cdef class Converter(SageObject): Convert singular matrix to matrix over the polynomial ring. """ from sage.matrix.constructor import Matrix - #cdef ring *singular_ring = (\ - # self._sage_ring)._ring ncols = mat.ncols nrows = mat.nrows result = Matrix(self._sage_ring, nrows, ncols) @@ -699,7 +695,6 @@ cdef class Converter(SageObject): return result cdef to_sage_vector_destructive(self, poly *p, free_module = None): - #cdef ring *r=self._ring._ring cdef int rank if free_module: rank = free_module.rank() @@ -716,20 +711,20 @@ cdef class Converter(SageObject): previous = NULL acc = NULL first = NULL - p_iter=p + p_iter = p while p_iter != NULL: if p_GetComp(p_iter, self._singular_ring) == i: - p_SetComp(p_iter,0, self._singular_ring) + p_SetComp(p_iter, 0, self._singular_ring) p_Setm(p_iter, self._singular_ring) if acc == NULL: first = p_iter else: acc.next = p_iter acc = p_iter - if p_iter==p: - p=pNext(p_iter) + if p_iter == p: + p = pNext(p_iter) if previous != NULL: - previous.next=pNext(p_iter) + previous.next = pNext(p_iter) p_iter = pNext(p_iter) acc.next = NULL else: @@ -750,7 +745,6 @@ cdef class Converter(SageObject): - ``r`` -- a SINGULAR ring - ``sage_ring`` -- a Sage ring matching r """ - #cdef MPolynomialRing_libsingular sage_ring = self._ring cdef int j cdef int rank=i.rank free_module = self._sage_ring ** rank @@ -758,12 +752,11 @@ cdef class Converter(SageObject): for j from 0 <= j < IDELEMS(i): p = self.to_sage_vector_destructive(i.m[j], free_module) - i.m[j]=NULL#save it from getting freed + i.m[j] = NULL # save it from getting freed l.append( p ) return Sequence(l, check=False, immutable=True) - cdef to_sage_integer_matrix(self, intvec* mat): """ Convert Singular matrix to matrix over the polynomial ring. @@ -780,7 +773,6 @@ cdef class Converter(SageObject): result[i,j] = mat.get(i*ncols+j) return result - cdef leftv *append_polynomial(self, p) except NULL: """ Append the polynomial ``p`` to the list. @@ -808,8 +800,6 @@ cdef class Converter(SageObject): cdef ideal *i cdef int j = 0 - - i = idInit(len(m),rank) for f in m: i.m[j] = sage_vector_to_poly(f, r) @@ -832,7 +822,6 @@ cdef class Converter(SageObject): return self._append(_r, RING_CMD) cdef leftv *append_matrix(self, mat) except NULL: - sage_ring = mat.base_ring() cdef ring *r= access_singular_ring(sage_ring) @@ -854,8 +843,6 @@ cdef class Converter(SageObject): cdef long _n = n return self._append(_n, INT_CMD) - - cdef leftv *append_list(self, l) except NULL: """ Append the list ``l`` to the list. @@ -948,31 +935,26 @@ cdef class Converter(SageObject): sage: sing_genus(I) # known bug -2 """ - #FIXME + # FIXME cdef MPolynomial_libsingular res_poly cdef int rtyp = to_convert.rtyp cdef lists *singular_list cdef Resolution res_resolution + if rtyp == IDEAL_CMD: return singular_ideal_to_sage_sequence(to_convert.data, self._singular_ring, self._sage_ring) - elif rtyp == POLY_CMD: - #FIXME + # FIXME res_poly = MPolynomial_libsingular(self._sage_ring) res_poly._poly = to_convert.data - to_convert.data = NULL - #prevent it getting free, when cleaning the leftv + to_convert.data = NULL # prevent it getting free, when cleaning the leftv return res_poly - elif rtyp == INT_CMD: return to_convert.data - elif rtyp == NUMBER_CMD: return si2sa(to_convert.data, self._singular_ring, self._sage_ring.base_ring()) - elif rtyp == INTVEC_CMD: - return si2sa_intvec(to_convert.data) - + return si2sa_intvec( to_convert.data) elif rtyp == STRING_CMD: # TODO: Need to determine what kind of data can be returned by a # STRING_CMD--is it just ASCII strings or can it be an arbitrary @@ -980,38 +962,26 @@ cdef class Converter(SageObject): ret = char_to_str(to_convert.data) return ret elif rtyp == VECTOR_CMD: - result = self.to_sage_vector_destructive( - to_convert.data) + result = self.to_sage_vector_destructive( to_convert.data) to_convert.data = NULL return result - - elif rtyp == RING_CMD or rtyp==QRING_CMD: return new_RingWrap( to_convert.data ) - elif rtyp == MATRIX_CMD: - return self.to_sage_matrix( to_convert.data ) - + return self.to_sage_matrix( to_convert.data ) elif rtyp == LIST_CMD: singular_list = to_convert.data ret = [] for i in xrange(singular_list.nr+1): - ret.append( - self.to_python( - &(singular_list.m[i]))) + ret.append(self.to_python(&(singular_list.m[i]))) return ret - - elif rtyp == MODUL_CMD: - return self.to_sage_module_element_sequence_destructive( - to_convert.data - ) + return self.to_sage_module_element_sequence_destructive( to_convert.data) elif rtyp == INTMAT_CMD: - return self.to_sage_integer_matrix( - to_convert.data) + return self.to_sage_integer_matrix( to_convert.data) elif rtyp == RESOLUTION_CMD: res_resolution = Resolution(self._sage_ring) - res_resolution._resolution = to_convert.data + res_resolution._resolution = to_convert.data res_resolution._resolution.references += 1 return res_resolution elif rtyp == NONE: @@ -1019,6 +989,7 @@ cdef class Converter(SageObject): else: raise NotImplementedError("rtyp %d not implemented."%(rtyp)) + cdef class BaseCallHandler: """ A call handler is an abstraction which hides the details of the @@ -1036,6 +1007,7 @@ cdef class BaseCallHandler: """ return False + cdef class LibraryCallHandler(BaseCallHandler): """ A call handler is an abstraction which hides the details of the @@ -1077,6 +1049,7 @@ cdef class LibraryCallHandler(BaseCallHandler): """ return False + cdef class KernelCallHandler(BaseCallHandler): """ A call handler is an abstraction which hides the details of the @@ -1163,9 +1136,11 @@ cdef class KernelCallHandler(BaseCallHandler): """ return True + # The Sage ring used as a dummy for singular function calls. cdef object dummy_ring + cdef class SingularFunction(SageObject): """ The base class for Singular functions either from the kernel or @@ -1540,6 +1515,7 @@ cdef inline call_function(SingularFunction self, tuple args, object R, bint sign return res + cdef class SingularLibraryFunction(SingularFunction): """ EXAMPLES:: @@ -1849,7 +1825,6 @@ def lib(name): if failure: raise NameError("Singular library {!r} not found".format(name)) - def list_of_functions(packages=False): """ Return a list of all function names currently available. @@ -1880,7 +1855,6 @@ def list_of_functions(packages=False): h = IDNEXT(h) return l - cdef inline RingWrap new_RingWrap(ring* r): cdef RingWrap ring_wrap_result = RingWrap.__new__(RingWrap) ring_wrap_result._ring = r @@ -1888,7 +1862,6 @@ cdef inline RingWrap new_RingWrap(ring* r): return ring_wrap_result - # Add support for _instancedoc_ from sage.misc.instancedoc import instancedoc instancedoc(SingularFunction) diff --git a/src/sage/libs/singular/polynomial.pyx b/src/sage/libs/singular/polynomial.pyx index fc6d8113d61..e012da4573c 100644 --- a/src/sage/libs/singular/polynomial.pyx +++ b/src/sage/libs/singular/polynomial.pyx @@ -25,7 +25,6 @@ import re plusminus_pattern = re.compile("([^\(^])([\+\-])") parenthvar_pattern = re.compile(r"\(([a-zA-Z][a-zA-Z0-9]*)\)") - from sage.cpython.string cimport bytes_to_str, str_to_bytes from sage.libs.singular.decl cimport number, ideal @@ -38,7 +37,6 @@ from sage.libs.singular.decl cimport p_GetComp, p_SetComp from sage.libs.singular.decl cimport pSubst from sage.libs.singular.decl cimport p_Normalize - from sage.libs.singular.singular cimport sa2si, si2sa, overflow_check from sage.misc.latex import latex @@ -80,7 +78,6 @@ cdef int singular_polynomial_add(poly **ret, poly *p, poly *q, ring *r): ret[0] = p_Add_q(p, q, r) return 0 - cdef int singular_polynomial_sub(poly **ret, poly *p, poly *q, ring *r): """ ``ret[0] = p-q`` where ``p`` and ``p`` in ``r``. @@ -108,7 +105,6 @@ cdef int singular_polynomial_sub(poly **ret, poly *p, poly *q, ring *r): ret[0] = p_Add_q(p, p_Neg(q, r), r) return 0 - cdef int singular_polynomial_rmul(poly **ret, poly *p, RingElement n, ring *r): """ ``ret[0] = n*p`` where ``n`` is a coefficient and ``p`` in ``r``. @@ -319,7 +315,6 @@ cdef int singular_polynomial_mul(poly** ret, poly *p, poly *q, ring *r) except - ret[0] = pp_Mult_qq(p, q, r) return 0 - cdef int singular_polynomial_div_coeff(poly** ret, poly *p, poly *q, ring *r) except -1: """ ``ret[0] = p/n`` where ``p`` and ``q`` in ``r`` and ``q`` constant. @@ -422,7 +417,6 @@ cdef int singular_polynomial_neg(poly **ret, poly *p, ring *r): ret[0] = p_Neg(p_Copy(p,r),r) return 0 - cdef object singular_polynomial_str(poly *p, ring *r): """ Return the string representation of ``p``. @@ -448,7 +442,6 @@ cdef object singular_polynomial_str(poly *p, ring *r): s = parenthvar_pattern.sub("\\1", s) return s - cdef object singular_polynomial_latex(poly *p, ring *r, object base, object latex_gens): r""" Return the LaTeX string representation of ``p``. diff --git a/src/sage/libs/singular/singular.pxd b/src/sage/libs/singular/singular.pxd index e590126d546..e1a55fbcd84 100644 --- a/src/sage/libs/singular/singular.pxd +++ b/src/sage/libs/singular/singular.pxd @@ -1,4 +1,5 @@ from sage.libs.singular.decl cimport ring, poly, number, intvec +from sage.libs.singular.function cimport Resolution from sage.rings.rational cimport Rational from sage.structure.element cimport Element @@ -32,6 +33,10 @@ cdef object si2sa_intvec(intvec *v) # dispatches to all the above. cdef object si2sa(number *n, ring *_ring, object base) +cdef list singular_monomial_exponents(poly *p, ring *r) +cpdef list si2sa_resolution(Resolution res) +cpdef tuple si2sa_resolution_graded(Resolution res, tuple degrees) + # ====================================== # Conversion from Sage to Singular types # ====================================== diff --git a/src/sage/libs/singular/singular.pyx b/src/sage/libs/singular/singular.pyx index f57126a6be4..d8ea7b07f3c 100644 --- a/src/sage/libs/singular/singular.pyx +++ b/src/sage/libs/singular/singular.pyx @@ -30,6 +30,7 @@ from libc.stdint cimport int64_t from sage.libs.singular.decl cimport * from sage.rings.polynomial.polydict import ETuple +from sage.libs.singular.function cimport new_sage_polynomial, access_singular_ring from sage.rings.rational_field import RationalField from sage.rings.integer_ring cimport IntegerRing_class @@ -421,7 +422,6 @@ cdef object si2sa_transext_QQ(number *n, ring *_ring, object base): return snumer/sdenom - cdef object si2sa_transext_FF(number *n, ring *_ring, object base): """ Create a sage element of a transcendental extension of a prime field from a @@ -505,7 +505,6 @@ cdef object si2sa_transext_FF(number *n, ring *_ring, object base): return snumer/sdenom - cdef object si2sa_NF(number *n, ring *_ring, object base): """ Create a sage element of a number field from a singular one. @@ -632,16 +631,282 @@ cdef inline object si2sa_ZZmod(number *n, ring *_ring, object base): return base(_ring.cf.cfInt(n,_ring.cf)) + +cdef list singular_monomial_exponents(poly *p, ring *r): + r""" + Return the list of exponents of monomial ``p``. + """ + cdef int v + cdef list ml = [None] * r.N + + for v in range(1, r.N + 1): + ml[v-1] = p_GetExp(p, v, r) + return ml + +cpdef list si2sa_resolution(Resolution res): + r""" + Pull the data from Singular resolution ``res`` to construct a Sage + resolution. + + INPUT: + + - ``res`` -- Singular resolution + + The procedure is destructive and ``res`` is not usable afterward. + + EXAMPLES:: + + sage: from sage.libs.singular.singular import si2sa_resolution + sage: from sage.libs.singular.function import singular_function + sage: module = singular_function("module") + sage: mres = singular_function('mres') + + sage: S. = PolynomialRing(QQ) + sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) + sage: mod = module(I) + sage: r = mres(mod, 0) + sage: si2sa_resolution(r) + [ + [ y x] + [-z -y] + [z^2 - y*w y*z - x*w y^2 - x*z], [ w z] + ] + """ + cdef ring *singular_ring + cdef syStrategy singular_res + cdef poly *p + cdef poly *p_iter + cdef poly *first + cdef poly *previous + cdef poly *acc + cdef resolvente mods + cdef ideal *mod + cdef int i, j, k, idx, rank, nrows, ncols + cdef bint zero_mat + cdef list degs, matdegs + + from sage.modules.free_module import FreeModule + from sage.matrix.constructor import matrix as _matrix + + singular_res = res._resolution[0] + sage_ring = res.base_ring + singular_ring = access_singular_ring(res.base_ring) + + if singular_res.minres != NULL: + mods = singular_res.minres + elif singular_res.fullres != NULL: + mods = singular_res.fullres + else: + raise ValueError('Singular resolution is not usable') + + cdef list res_mats = [] + + # length is the length of fullres. The length of minres + # can be shorter. Hence we avoid SEGFAULT by stopping + # at NULL pointer. + for idx in range(singular_res.length): + mod = mods[idx] + if mod == NULL: + break + rank = mod.rank + free_module = FreeModule(sage_ring, rank) + + nrows = rank + ncols = mod.ncols # IDELEMS(mod) + + mat = _matrix(sage_ring, nrows, ncols) + matdegs = [] + zero_mat = True + for j in range(ncols): + p = mod.m[j] + degs = [] + # code below copied and modified from to_sage_vector_destructive + # in sage.libs.singular.function.Converter + for i in range(1, rank + 1): + previous = NULL + acc = NULL + first = NULL + p_iter = p + while p_iter != NULL: + if p_GetComp(p_iter, singular_ring) == i: + p_SetComp(p_iter, 0, singular_ring) + p_Setm(p_iter, singular_ring) + if acc == NULL: + first = p_iter + else: + acc.next = p_iter + acc = p_iter + if p_iter == p: + p = pNext(p_iter) + if previous != NULL: + previous.next = pNext(p_iter) + p_iter = pNext(p_iter) + acc.next = NULL + else: + previous = p_iter + p_iter = pNext(p_iter) + + if zero_mat: + zero_mat = first == NULL + + mat[i - 1, j] = new_sage_polynomial(sage_ring, first) + + # Singular sometimes leaves zero matrix in the resolution. We can stop + # when one is seen. + if zero_mat: + break + + res_mats.append(mat) + + return res_mats + +cpdef tuple si2sa_resolution_graded(Resolution res, tuple degrees): + """ + Pull the data from Singular resolution ``res`` to construct a Sage + resolution. + + INPUT: + + - ``res`` -- Singular resolution + + - ``degrees`` -- list of integers or integer vectors + + The procedure is destructive, and ``res`` is not usable afterward. + + EXAMPLES:: + + sage: from sage.libs.singular.singular import si2sa_resolution_graded + sage: from sage.libs.singular.function import singular_function + sage: module = singular_function("module") + sage: mres = singular_function('mres') + + sage: S. = PolynomialRing(QQ) + sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) + sage: mod = module(I) + sage: r = mres(mod, 0) + sage: res_mats, res_degs = si2sa_resolution_graded(r, (1, 1, 1, 1)) + sage: res_mats + [ + [ y x] + [-z -y] + [z^2 - y*w y*z - x*w y^2 - x*z], [ w z] + ] + sage: res_degs + [[[2], [2], [2]], [[1, 1, 1], [1, 1, 1]]] + """ + cdef ring *singular_ring + cdef syStrategy singular_res + cdef poly *p + cdef poly *p_iter + cdef poly *first + cdef poly *previous + cdef poly *acc + cdef resolvente mods + cdef ideal *mod + cdef int i, j, k, idx, rank, nrows, ncols + cdef int ngens = len(degrees) + cdef bint zero_mat + cdef list matdegs, exps + + from sage.matrix.constructor import matrix as _matrix + + singular_res = res._resolution[0] + sage_ring = res.base_ring + singular_ring = access_singular_ring(res.base_ring) + + if singular_res.minres != NULL: + mods = singular_res.minres + elif singular_res.fullres != NULL: + mods = singular_res.fullres + else: + raise ValueError('Singular resolution is not usable') + + cdef list res_mats = [] + cdef list res_degs = [] + + # length is the length of fullres. The length of minres + # can be shorter. Hence we avoid SEGFAULT by stopping + # at NULL pointer. + for idx in range(singular_res.length): + mod = mods[idx] + if mod == NULL: + break + rank = mod.rank + free_module = sage_ring ** rank + + nrows = rank + ncols = mod.ncols # IDELEMS(mod) + + mat = _matrix(sage_ring, nrows, ncols) + matdegs = [] + zero_mat = True + for j in range(ncols): + p = mod.m[j] + degs = [] + # code below copied and modified from to_sage_vector_destructive + # in sage.libs.singular.function.Converter + for i in range(1, rank + 1): + previous = NULL + acc = NULL + first = NULL + p_iter = p + while p_iter != NULL: + if p_GetComp(p_iter, singular_ring) == i: + p_SetComp(p_iter, 0, singular_ring) + p_Setm(p_iter, singular_ring) + if acc == NULL: + first = p_iter + else: + acc.next = p_iter + acc = p_iter + if p_iter == p: + p = pNext(p_iter) + if previous != NULL: + previous.next = pNext(p_iter) + p_iter = pNext(p_iter) + acc.next = NULL + else: + previous = p_iter + p_iter = pNext(p_iter) + + if zero_mat: + zero_mat = first == NULL + + mat[i - 1, j] = new_sage_polynomial(sage_ring, first) + + # degree of a homogeneous polynomial can be computed from the + # first monomial + if first != NULL: + exps = singular_monomial_exponents(first, singular_ring) + deg = 0 + for k in range(ngens): + deg += exps[k] * degrees[k] + degs.append(deg) + else: + degs.append(None) + + matdegs.append(degs) # store degrees of the column + + # Singular sometimes leaves zero matrix in the resolution. We can stop + # when one is seen. + if zero_mat: + break + + res_mats.append(mat) + res_degs.append(matdegs) + + return (res_mats, res_degs) + + cdef number *sa2si_QQ(Rational r, ring *_ring): """ Create a singular number from a sage rational. INPUT: - - ``r`` - a sage rational number - - - ``_ ring`` - a (pointer to) a singular ring, where the resul will live + - ``r`` -- a sage rational number + - ``_ ring`` -- a (pointer to) a singular ring, where the resul will live OUTPUT: @@ -669,10 +934,9 @@ cdef number *sa2si_GFqGivaro(int quo, ring *_ring): INPUT: - - ``quo`` - a sage integer - - - ``_ ring`` - a (pointer to) a singular ring, where the resul will live + - ``quo`` -- a sage integer + - ``_ ring`` -- a (pointer to) a singular ring, where the resul will live OUTPUT: @@ -682,7 +946,6 @@ cdef number *sa2si_GFqGivaro(int quo, ring *_ring): generator. In this case, ``quo`` is the integer resulting from evaluating that polynomial in the characteristic of the field. - TESTS:: sage: F = FiniteField(5^2) @@ -739,10 +1002,9 @@ cdef number *sa2si_GFqNTLGF2E(FFgf2eE elem, ring *_ring): INPUT: - - ``elem`` - a sage element of a ntl_gf2e finite field - - - ``_ ring`` - a (pointer to) a singular ring, where the resul will live + - ``elem`` -- a sage element of a ntl_gf2e finite field + - ``_ ring`` -- a (pointer to) a singular ring, where the resul will live OUTPUT: @@ -805,10 +1067,9 @@ cdef number *sa2si_GFq_generic(object elem, ring *_ring): INPUT: - - ``elem`` - a sage element of a generic finite field - - - ``_ ring`` - a (pointer to) a singular ring, where the resul will live + - ``elem`` -- a sage element of a generic finite field + - ``_ ring`` -- a (pointer to) a singular ring, where the resul will live OUTPUT: @@ -872,16 +1133,14 @@ cdef number *sa2si_transext_QQ(object elem, ring *_ring): INPUT: - - ``elem`` - a sage element of a FractionField of polynomials over the rationals - - - ``_ ring`` - a (pointer to) a singular ring, where the resul will live + - ``elem`` -- a sage element of a FractionField of polynomials over the rationals + - ``_ ring`` -- a (pointer to) a singular ring, where the resul will live OUTPUT: - A (pointer to) a singular number - TESTS:: sage: F = PolynomialRing(QQ,'a,b').fraction_field() @@ -924,7 +1183,6 @@ cdef number *sa2si_transext_QQ(object elem, ring *_ring): sage: R(f) x + y + 1 """ - cdef int j cdef number *n1 cdef number *a @@ -1018,8 +1276,6 @@ cdef number *sa2si_transext_QQ(object elem, ring *_ring): return n1 - - cdef number *sa2si_transext_FF(object elem, ring *_ring): """ Create a singular number from a sage element of a transcendental extension @@ -1027,16 +1283,14 @@ cdef number *sa2si_transext_FF(object elem, ring *_ring): INPUT: - - ``elem`` - a sage element of a FractionField of polynomials over the rationals - - - ``_ ring`` - a (pointer to) a singular ring, where the resul will live + - ``elem`` -- a sage element of a FractionField of polynomials over the rationals + - ``_ ring`` -- a (pointer to) a singular ring, where the resul will live OUTPUT: - A (pointer to) a singular number - TESTS:: sage: F = PolynomialRing(FiniteField(7),'a,b').fraction_field() @@ -1123,17 +1377,15 @@ cdef number *sa2si_transext_FF(object elem, ring *_ring): return n1 - cdef number *sa2si_NF(object elem, ring *_ring): """ Create a singular number from a sage element of a number field. INPUT: - - ``elem`` - a sage element of a NumberField - - - ``_ ring`` - a (pointer to) a singular ring, where the resul will live + - ``elem`` -- a sage element of a NumberField + - ``_ ring`` -- a (pointer to) a singular ring, where the resul will live OUTPUT: @@ -1222,16 +1474,14 @@ cdef number *sa2si_ZZ(Integer d, ring *_ring): INPUT: - - ``elem`` - a sage Integer - - - ``_ ring`` - a (pointer to) a singular ring, where the resul will live + - ``elem`` -- a sage Integer + - ``_ ring`` -- a (pointer to) a singular ring, where the resul will live OUTPUT: - A (pointer to) a singular number - TESTS:: sage: P. = ZZ[] @@ -1430,14 +1680,13 @@ cdef number *sa2si(Element elem, ring * _ring): raise ValueError("cannot convert to SINGULAR number") - cdef object si2sa_intvec(intvec *v): r""" create a sage tuple from a singular vector of integers INPUT: - - ``v`` - a (pointer to) a singular intvec + - ``v`` -- a (pointer to) singular intvec OUTPUT: @@ -1495,7 +1744,6 @@ cdef int overflow_check(unsigned long e, ring *_ring) except -1: if unlikely(e > _ring.bitmask): raise OverflowError("exponent overflow (%d)"%(e)) - cdef init_libsingular(): """ This initializes the SINGULAR library. This is a hack to some diff --git a/src/sage/logic/logicparser.py b/src/sage/logic/logicparser.py index 08d9eb9c90d..b854f416127 100644 --- a/src/sage/logic/logicparser.py +++ b/src/sage/logic/logicparser.py @@ -82,7 +82,7 @@ # 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/ #***************************************************************************** import string @@ -703,5 +703,3 @@ def apply_func(tree, func): lval = tree[1] rval = tree[2] return func([tree[0], lval, rval]) - - diff --git a/src/sage/manifolds/differentiable/chart.py b/src/sage/manifolds/differentiable/chart.py index 105c1b08849..098f85516bf 100644 --- a/src/sage/manifolds/differentiable/chart.py +++ b/src/sage/manifolds/differentiable/chart.py @@ -442,7 +442,7 @@ def frame(self): sage: c_xy.frame() Coordinate frame (M, (∂/∂x,∂/∂y)) sage: type(c_xy.frame()) - + Check that ``c_xy.frame()`` is indeed the coordinate frame associated with the coordinates `(x,y)`:: @@ -487,7 +487,7 @@ def coframe(self): sage: c_xy.coframe() Coordinate coframe (M, (dx,dy)) sage: type(c_xy.coframe()) - + Check that ``c_xy.coframe()`` is indeed the coordinate coframe associated with the coordinates `(x, y)`:: diff --git a/src/sage/manifolds/differentiable/curve.py b/src/sage/manifolds/differentiable/curve.py index cf5f5fd754b..ca7cef3f626 100644 --- a/src/sage/manifolds/differentiable/curve.py +++ b/src/sage/manifolds/differentiable/curve.py @@ -965,11 +965,9 @@ def plot(self, chart=None, ambient_coords=None, mapping=None, prange=None, return self._graphics(plot_curve, ambient_coords, thickness=thickness, - aspect_ratio=aspect_ratio, color= color, + aspect_ratio=aspect_ratio, color=color, style=style, label_axes=label_axes) - - def _graphics(self, plot_curve, ambient_coords, thickness=1, aspect_ratio='automatic', color='red', style='-', label_axes=True): diff --git a/src/sage/manifolds/differentiable/degenerate_submanifold.py b/src/sage/manifolds/differentiable/degenerate_submanifold.py index 1a8db5f8aa9..5d5ebb3de0f 100644 --- a/src/sage/manifolds/differentiable/degenerate_submanifold.py +++ b/src/sage/manifolds/differentiable/degenerate_submanifold.py @@ -1200,14 +1200,14 @@ def shape_operator(self, screen=None): T = T.along(im) except ValueError: pass - T.set_name("A^*", latex_name = r'A^\ast') + T.set_name("A^*", latex_name=r'A^\ast') A = TangentTensor(T, im) self._shape_operator[screen._name] = A return A def gauss_curvature(self, screen=None): r""" - Gauss curvature is the product of all eigenfunctions of the shape operator. + Gauss curvature is the product of all eigenfunctions of the shape operator. INPUT: @@ -1245,8 +1245,8 @@ def gauss_curvature(self, screen=None): """ if self._ambient._dim-self._dim != 1: - raise ValueError("'gauss_curvature' is defined"+ - " only for hypersurfaces.") + raise ValueError("'gauss_curvature' is defined" + " only for hypersurfaces.") if screen is None: screen = self.default_screen() if screen._name not in self._gauss_curvature: diff --git a/src/sage/manifolds/differentiable/examples/symplectic_space.py b/src/sage/manifolds/differentiable/examples/symplectic_space.py index 34c557cf830..623979ed7f3 100644 --- a/src/sage/manifolds/differentiable/examples/symplectic_space.py +++ b/src/sage/manifolds/differentiable/examples/symplectic_space.py @@ -91,6 +91,28 @@ def __init__( sage: omega = M.symplectic_form() sage: omega.display() omega = -dq∧dp + + An isomomorphism of its tangent space (at any point) with an indefinite inner product space + with distinguished basis:: + + sage: Q_M_qp = omega[:]; Q_M_qp + [ 0 -1] + [ 1 0] + sage: W_M_qp = VectorSpace(RR, 2, inner_product_matrix=Q_M_qp); W_M_qp + Ambient quadratic space of dimension 2 over Real Field with 53 bits of precision + Inner product matrix: + [0.000000000000000 -1.00000000000000] + [ 1.00000000000000 0.000000000000000] + sage: T = M.tangent_space(M.point(), base_ring=RR); T + Tangent space at Point on the Standard symplectic space R2 + sage: phi_M_qp = T.isomorphism_with_fixed_basis(T.default_basis(), codomain=W_M_qp); phi_M_qp + Generic morphism: + From: Tangent space at Point on the Standard symplectic space R2 + To: Ambient quadratic space of dimension 2 over Real Field with 53 bits of precision + Inner product matrix: + [0.000000000000000 -1.00000000000000] + [ 1.00000000000000 0.000000000000000] + """ # Check that manifold is even dimensional if dimension % 2 == 1: diff --git a/src/sage/manifolds/differentiable/manifold.py b/src/sage/manifolds/differentiable/manifold.py index d5e0ebc6200..7fa589be987 100644 --- a/src/sage/manifolds/differentiable/manifold.py +++ b/src/sage/manifolds/differentiable/manifold.py @@ -3403,7 +3403,7 @@ def is_manifestly_parallelizable(self): """ return bool(self._covering_frames) - def tangent_space(self, point): + def tangent_space(self, point, base_ring=None): r""" Tangent space to ``self`` at a given point. @@ -3412,6 +3412,8 @@ def tangent_space(self, point): - ``point`` -- :class:`~sage.manifolds.point.ManifoldPoint`; point `p` on the manifold + - ``base_ring`` -- (default: the symbolic ring) the base ring + OUTPUT: - :class:`~sage.manifolds.differentiable.tangent_space.TangentSpace` @@ -3445,7 +3447,7 @@ def tangent_space(self, point): raise TypeError("{} is not a manifold point".format(point)) if point not in self: raise ValueError("{} is not a point on the {}".format(point, self)) - return TangentSpace(point) + return TangentSpace(point, base_ring=base_ring) def curve(self, coord_expression, param, chart=None, name=None, latex_name=None): diff --git a/src/sage/manifolds/differentiable/metric.py b/src/sage/manifolds/differentiable/metric.py index 686af0d3505..6dece42f0fe 100644 --- a/src/sage/manifolds/differentiable/metric.py +++ b/src/sage/manifolds/differentiable/metric.py @@ -647,7 +647,7 @@ def set(self, symbiform): raise TypeError("the argument must be a tensor field") if symbiform._tensor_type != (0,2): raise TypeError("the argument must be of tensor type (0,2)") - if symbiform._sym != [(0,1)]: + if symbiform._sym != ((0,1),): raise TypeError("the argument must be symmetric") if not symbiform._domain.is_subset(self._domain): raise TypeError("the symmetric bilinear form is not defined " + @@ -1220,15 +1220,13 @@ def weyl(self, name=None, latex_name=None): 3-dimensional differentiable manifold H^3 sage: C == 0 True - """ if self._weyl is None: n = self._ambient_domain.dimension() if n < 3: raise ValueError("the Weyl tensor is not defined for a " + "manifold of dimension n <= 2") - delta = self._domain.tangent_identity_field(dest_map= - self._vmodule._dest_map) + delta = self._domain.tangent_identity_field(dest_map=self._vmodule._dest_map) riem = self.riemann() ric = self.ricci() rscal = self.ricci_scalar() @@ -2301,7 +2299,7 @@ def set(self, symbiform): "values on a parallelizable domain") if symbiform._tensor_type != (0,2): raise TypeError("the argument must be of tensor type (0,2)") - if symbiform._sym != [(0,1)]: + if symbiform._sym != ((0,1),): raise TypeError("the argument must be symmetric") if symbiform._vmodule is not self._vmodule: raise TypeError("the symmetric bilinear form and the metric are " + @@ -2781,7 +2779,7 @@ def set(self, symbiform): raise TypeError("the argument must be a tensor field") if symbiform._tensor_type != (0,2): raise TypeError("the argument must be of tensor type (0,2)") - if symbiform._sym != [(0,1)]: + if symbiform._sym != ((0,1),): raise TypeError("the argument must be symmetric") if not symbiform._domain.is_subset(self._domain): raise TypeError("the symmetric bilinear form is not defined " + @@ -3019,7 +3017,7 @@ def set(self, symbiform): "values on a parallelizable domain") if symbiform._tensor_type != (0,2): raise TypeError("the argument must be of tensor type (0,2)") - if symbiform._sym != [(0,1)]: + if symbiform._sym != ((0,1),): raise TypeError("the argument must be symmetric") if symbiform._vmodule is not self._vmodule: raise TypeError("the symmetric bilinear form and the metric are " + diff --git a/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py b/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py index 2eaef7510c8..3aef31eb703 100644 --- a/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py +++ b/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py @@ -1173,7 +1173,7 @@ def ambient_second_fundamental_form(self): g.restrict(chart.domain()).contract(pf[j]) * self.scalar_field({chart: k.comp(chart.frame())[:][i, j]}) for i in range(self._dim) for j in range(self._dim)) - gam_rst._sym = [(0, 1)] + gam_rst._sym = ((0, 1),) self._ambient_second_fundamental_form.set_restriction(gam_rst) charts = iter(self.top_charts()) diff --git a/src/sage/manifolds/differentiable/tangent_space.py b/src/sage/manifolds/differentiable/tangent_space.py index 0825f867e5e..aa14cc90f70 100644 --- a/src/sage/manifolds/differentiable/tangent_space.py +++ b/src/sage/manifolds/differentiable/tangent_space.py @@ -162,6 +162,58 @@ class TangentSpace(FiniteRankFreeModule): sage: p2 == p True + An isomorphism of the tangent space with an inner product space with distinguished basis:: + + sage: g = M.metric('g') + sage: g[:] = ((1, 0), (0, 1)) + sage: Q_Tp_xy = g[c_xy.frame(),:](*p.coordinates(c_xy)); Q_Tp_xy + [1 0] + [0 1] + sage: W_Tp_xy = VectorSpace(SR, 2, inner_product_matrix=Q_Tp_xy) + sage: Tp.bases()[0] + Basis (∂/∂x,∂/∂y) on the Tangent space at Point p on the 2-dimensional differentiable manifold M + sage: phi_Tp_xy = Tp.isomorphism_with_fixed_basis(Tp.bases()[0], codomain=W_Tp_xy); phi_Tp_xy + Generic morphism: + From: Tangent space at Point p on the 2-dimensional differentiable manifold M + To: Ambient quadratic space of dimension 2 over Symbolic Ring + Inner product matrix: + [1 0] + [0 1] + + sage: Q_Tp_uv = g[c_uv.frame(),:](*p.coordinates(c_uv)); Q_Tp_uv + [1/2 0] + [ 0 1/2] + sage: W_Tp_uv = VectorSpace(SR, 2, inner_product_matrix=Q_Tp_uv) + sage: Tp.bases()[1] + Basis (∂/∂u,∂/∂v) on the Tangent space at Point p on the 2-dimensional differentiable manifold M + sage: phi_Tp_uv = Tp.isomorphism_with_fixed_basis(Tp.bases()[1], codomain=W_Tp_uv); phi_Tp_uv + Generic morphism: + From: Tangent space at Point p on the 2-dimensional differentiable manifold M + To: Ambient quadratic space of dimension 2 over Symbolic Ring + Inner product matrix: + [1/2 0] + [ 0 1/2] + + sage: t1, t2 = Tp.tensor((1,0)), Tp.tensor((1,0)) + sage: t1[:] = (8, 15) + sage: t2[:] = (47, 11) + sage: t1[Tp.bases()[0],:] + [8, 15] + sage: phi_Tp_xy(t1), phi_Tp_xy(t2) + ((8, 15), (47, 11)) + sage: phi_Tp_xy(t1).inner_product(phi_Tp_xy(t2)) + 541 + + sage: Tp_xy_to_uv = M.change_of_frame(c_xy.frame(), c_uv.frame()).at(p); Tp_xy_to_uv + Automorphism of the Tangent space at Point p on the 2-dimensional differentiable manifold M + sage: Tp.set_change_of_basis(Tp.bases()[0], Tp.bases()[1], Tp_xy_to_uv) + sage: t1[Tp.bases()[1],:] + [23, -7] + sage: phi_Tp_uv(t1), phi_Tp_uv(t2) + ((23, -7), (58, 36)) + sage: phi_Tp_uv(t1).inner_product(phi_Tp_uv(t2)) + 541 + .. SEEALSO:: :class:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule` @@ -170,7 +222,7 @@ class TangentSpace(FiniteRankFreeModule): """ Element = TangentVector - def __init__(self, point): + def __init__(self, point, base_ring=None): r""" Construct the tangent space at a given point. @@ -191,7 +243,9 @@ def __init__(self, point): latex_name = r"T_{%s}\,%s"%(point._latex_name, manif._latex_name) self._point = point self._manif = manif - FiniteRankFreeModule.__init__(self, SR, manif._dim, name=name, + if base_ring is None: + base_ring = SR + FiniteRankFreeModule.__init__(self, base_ring, manif._dim, name=name, latex_name=latex_name, start_index=manif._sindex) # Initialization of bases of the tangent space from existing vector @@ -252,6 +306,19 @@ def __init__(self, point): pass self._basis_changes[(basis1, basis2)] = auto + def construction(self): + """ + TESTS:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: p = M.point((3,-2), name='p') + sage: Tp = M.tangent_space(p) + sage: Tp.construction() is None + True + """ + return None + def _repr_(self): r""" String representation of ``self``. diff --git a/src/sage/manifolds/differentiable/tensorfield.py b/src/sage/manifolds/differentiable/tensorfield.py index d5b63c9d81d..a2aad4d4937 100644 --- a/src/sage/manifolds/differentiable/tensorfield.py +++ b/src/sage/manifolds/differentiable/tensorfield.py @@ -58,6 +58,7 @@ from sage.rings.integer import Integer from sage.rings.integer_ring import ZZ from sage.structure.element import ModuleElementWithMutability +from sage.tensor.modules.comp import CompWithSym from sage.tensor.modules.free_module_tensor import FreeModuleTensor from sage.tensor.modules.tensor_with_indices import TensorWithIndices @@ -495,40 +496,8 @@ def __init__( self._restrictions = {} # dict. of restrictions of self on subdomains # of self._domain, with the subdomains as keys # Treatment of symmetry declarations: - self._sym = [] - if sym is not None and sym != []: - if isinstance(sym[0], (int, Integer)): - # a single symmetry is provided as a tuple -> 1-item list: - sym = [tuple(sym)] - for isym in sym: - if len(isym) > 1: - for i in isym: - if i < 0 or i > self._tensor_rank - 1: - raise IndexError("invalid position: {}".format(i) + - " not in [0,{}]".format(self._tensor_rank-1)) - self._sym.append(tuple(isym)) - self._antisym = [] - if antisym is not None and antisym != []: - if isinstance(antisym[0], (int, Integer)): - # a single antisymmetry is provided as a tuple -> 1-item list: - antisym = [tuple(antisym)] - for isym in antisym: - if len(isym) > 1: - for i in isym: - if i < 0 or i > self._tensor_rank - 1: - raise IndexError("invalid position: {}".format(i) + - " not in [0,{}]".format(self._tensor_rank-1)) - self._antisym.append(tuple(isym)) - # Final consistency check: - index_list = [] - for isym in self._sym: - index_list += isym - for isym in self._antisym: - index_list += isym - if len(index_list) != len(set(index_list)): - # There is a repeated index position: - raise IndexError("incompatible lists of symmetries: the same " + - "position appears more than once") + self._sym, self._antisym = CompWithSym._canonicalize_sym_antisym( + self._tensor_rank, sym, antisym) # Initialization of derived quantities: self._init_derived() @@ -591,7 +560,7 @@ def _repr_(self): """ # Special cases - if self._tensor_type == (0,2) and self._sym == [(0,1)]: + if self._tensor_type == (0,2) and self._sym == ((0,1),): description = "Field of symmetric bilinear forms " if self._name is not None: description += self._name + " " @@ -957,13 +926,13 @@ def symmetries(self): elif len(self._sym) == 1: s = "symmetry: {}; ".format(self._sym[0]) else: - s = "symmetries: {}; ".format(self._sym) + s = "symmetries: {}; ".format(list(self._sym)) if not self._antisym: a = "no antisymmetry" elif len(self._antisym) == 1: a = "antisymmetry: {}".format(self._antisym[0]) else: - a = "antisymmetries: {}".format(self._antisym) + a = "antisymmetries: {}".format(list(self._antisym)) print(s + a) #### End of simple accessors ##### diff --git a/src/sage/manifolds/differentiable/tensorfield_module.py b/src/sage/manifolds/differentiable/tensorfield_module.py index 018856b8efa..d68264a472c 100644 --- a/src/sage/manifolds/differentiable/tensorfield_module.py +++ b/src/sage/manifolds/differentiable/tensorfield_module.py @@ -130,7 +130,8 @@ class TensorFieldModule(UniqueRepresentation, Parent): `T^{(2,0)}(M)` is not a free module:: - sage: isinstance(T20, FiniteRankFreeModule) + sage: from sage.tensor.modules.finite_rank_free_module import FiniteRankFreeModule_abstract + sage: isinstance(T20, FiniteRankFreeModule_abstract) False because `M = S^2` is not parallelizable:: @@ -142,7 +143,7 @@ class TensorFieldModule(UniqueRepresentation, Parent): free module, since `U` is parallelizable (being a coordinate domain):: sage: T20U = U.tensor_field_module((2,0)) - sage: isinstance(T20U, FiniteRankFreeModule) + sage: isinstance(T20U, FiniteRankFreeModule_abstract) True sage: U.is_manifestly_parallelizable() True @@ -658,7 +659,8 @@ class TensorFieldFreeModule(TensorFreeModule): `T^{(2,0)}(\RR^3)` is a free module:: - sage: isinstance(T20, FiniteRankFreeModule) + sage: from sage.tensor.modules.finite_rank_free_module import FiniteRankFreeModule_abstract + sage: isinstance(T20, FiniteRankFreeModule_abstract) True because `M = \RR^3` is parallelizable:: diff --git a/src/sage/manifolds/differentiable/vectorfield_module.py b/src/sage/manifolds/differentiable/vectorfield_module.py index cf3f1dc687d..1e097907967 100644 --- a/src/sage/manifolds/differentiable/vectorfield_module.py +++ b/src/sage/manifolds/differentiable/vectorfield_module.py @@ -773,7 +773,10 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, AutomorphismField from sage.manifolds.differentiable.metric import (PseudoRiemannianMetric, DegenerateMetric) - if tensor_type==(1,0): + from sage.tensor.modules.comp import CompWithSym + sym, antisym = CompWithSym._canonicalize_sym_antisym( + tensor_type[0] + tensor_type[1], sym, antisym) + if tensor_type == (1,0): return self.element_class(self, name=name, latex_name=latex_name) elif tensor_type == (0,1): @@ -783,31 +786,14 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, return self.automorphism(name=name, latex_name=latex_name) elif tensor_type[0] == 0 and tensor_type[1] > 1 and antisym: - if isinstance(antisym[0], (int, Integer)): - # a single antisymmetry is provided as a tuple or a - # range object; it is converted to a 1-item list: - antisym = [tuple(antisym)] - if isinstance(antisym, list): - antisym0 = antisym[0] - else: - antisym0 = antisym - if len(antisym0) == tensor_type[1]: + if len(antisym[0]) == tensor_type[1]: return self.alternating_form(tensor_type[1], name=name, latex_name=latex_name) elif tensor_type[0] > 1 and tensor_type[1] == 0 and antisym: - if isinstance(antisym[0], (int, Integer)): - # a single antisymmetry is provided as a tuple or a - # range object; it is converted to a 1-item list: - antisym = [tuple(antisym)] - if isinstance(antisym, list): - antisym0 = antisym[0] - else: - antisym0 = antisym - if len(antisym0) == tensor_type[0]: + if len(antisym[0]) == tensor_type[0]: return self.alternating_contravariant_tensor( - tensor_type[0], name=name, - latex_name=latex_name) - elif tensor_type==(0,2) and specific_type is not None: + tensor_type[0], name=name, latex_name=latex_name) + elif tensor_type == (0,2) and specific_type is not None: if issubclass(specific_type, PseudoRiemannianMetric): return self.metric(name, latex_name=latex_name) # NB: the signature is not treated @@ -816,9 +802,9 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, return self.metric(name, latex_name=latex_name, signature=(0, sign-1, 1)) # Generic case - return self.tensor_module(*tensor_type).element_class(self, - tensor_type, name=name, latex_name=latex_name, - sym=sym, antisym=antisym) + return self.tensor_module(*tensor_type).element_class( + self, tensor_type, name=name, latex_name=latex_name, + sym=sym, antisym=antisym) def alternating_contravariant_tensor(self, degree, name=None, latex_name=None): @@ -2088,6 +2074,9 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, AutomorphismField, AutomorphismFieldParal) from sage.manifolds.differentiable.metric import (PseudoRiemannianMetric, DegenerateMetric) + from sage.tensor.modules.comp import CompWithSym + sym, antisym = CompWithSym._canonicalize_sym_antisym( + tensor_type[0] + tensor_type[1], sym, antisym) if tensor_type == (1,0): return self.element_class(self, name=name, latex_name=latex_name) @@ -2098,31 +2087,14 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, (AutomorphismField, AutomorphismFieldParal)): return self.automorphism(name=name, latex_name=latex_name) elif tensor_type[0] == 0 and tensor_type[1] > 1 and antisym: - if isinstance(antisym[0], (int, Integer)): - # a single antisymmetry is provided as a tuple or a - # range object; it is converted to a 1-item list: - antisym = [tuple(antisym)] - if isinstance(antisym, list): - antisym0 = antisym[0] - else: - antisym0 = antisym - if len(antisym0) == tensor_type[1]: + if len(antisym[0]) == tensor_type[1]: return self.alternating_form(tensor_type[1], name=name, latex_name=latex_name) elif tensor_type[0] > 1 and tensor_type[1] == 0 and antisym: - if isinstance(antisym[0], (int, Integer)): - # a single antisymmetry is provided as a tuple or a - # range object; it is converted to a 1-item list: - antisym = [tuple(antisym)] - if isinstance(antisym, list): - antisym0 = antisym[0] - else: - antisym0 = antisym - if len(antisym0) == tensor_type[0]: + if len(antisym[0]) == tensor_type[0]: return self.alternating_contravariant_tensor( - tensor_type[0], name=name, - latex_name=latex_name) - elif tensor_type==(0,2) and specific_type is not None: + tensor_type[0], name=name, latex_name=latex_name) + elif tensor_type == (0,2) and specific_type is not None: if issubclass(specific_type, PseudoRiemannianMetric): return self.metric(name, latex_name=latex_name) # NB: the signature is not treated @@ -2131,9 +2103,9 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, return self.metric(name, latex_name=latex_name, signature=(0, sign-1, 1)) # Generic case - return self.tensor_module(*tensor_type).element_class(self, - tensor_type, name=name, latex_name=latex_name, - sym=sym, antisym=antisym) + return self.tensor_module(*tensor_type).element_class( + self, tensor_type, name=name, latex_name=latex_name, + sym=sym, antisym=antisym) def tensor_from_comp(self, tensor_type, comp, name=None, latex_name=None): diff --git a/src/sage/manifolds/differentiable/vectorframe.py b/src/sage/manifolds/differentiable/vectorframe.py index d6e4f08895a..6df9c54c0d1 100644 --- a/src/sage/manifolds/differentiable/vectorframe.py +++ b/src/sage/manifolds/differentiable/vectorframe.py @@ -386,7 +386,7 @@ def at(self, point): Dual basis (dx,dy) on the Tangent space at Point p on the 2-dimensional differentiable manifold M sage: type(fp) - + sage: fp[0] Linear form dx on the Tangent space at Point p on the 2-dimensional differentiable manifold M @@ -1362,7 +1362,7 @@ def at(self, point): Basis (∂/∂x,∂/∂y) on the Tangent space at Point p on the 2-dimensional differentiable manifold M sage: type(ep) - + sage: ep[0] Tangent vector ∂/∂x at Point p on the 2-dimensional differentiable manifold M diff --git a/src/sage/manifolds/local_frame.py b/src/sage/manifolds/local_frame.py index 36ee01ce279..4d7db5c7d55 100644 --- a/src/sage/manifolds/local_frame.py +++ b/src/sage/manifolds/local_frame.py @@ -331,7 +331,7 @@ def at(self, point): Dual basis (e^1,e^2) on the Fiber of E at Point p on the 2-dimensional topological manifold M sage: type(e_dual_p) - + sage: e_dual_p[1] Linear form e^1 on the Fiber of E at Point p on the 2-dimensional topological manifold M @@ -1045,7 +1045,7 @@ def at(self, point): Basis (e_0,e_1) on the Fiber of E at Point p on the 2-dimensional topological manifold M sage: type(ep) - + sage: ep[0] Vector e_0 in the fiber of E at Point p on the 2-dimensional topological manifold M diff --git a/src/sage/manifolds/utilities.py b/src/sage/manifolds/utilities.py index 8fe6c439066..75a07519b0d 100644 --- a/src/sage/manifolds/utilities.py +++ b/src/sage/manifolds/utilities.py @@ -35,7 +35,8 @@ from sage.functions.other import abs_symbolic from sage.misc.functional import sqrt from sage.functions.trig import cos, sin -from sage.rings.all import Rational +from sage.rings.rational import Rational + class SimplifySqrtReal(ExpressionTreeWalker): r""" diff --git a/src/sage/manifolds/vector_bundle_fiber.py b/src/sage/manifolds/vector_bundle_fiber.py index a79d42176e4..90a59e97169 100644 --- a/src/sage/manifolds/vector_bundle_fiber.py +++ b/src/sage/manifolds/vector_bundle_fiber.py @@ -242,6 +242,19 @@ def __init__(self, vector_bundle, point): pass self._basis_changes[(basis1, basis2)] = auto + def construction(self): + r""" + TESTS:: + + sage: M = Manifold(3, 'M', structure='top') + sage: X. = M.chart() + sage: p = M((0,0,0), name='p') + sage: E = M.vector_bundle(2, 'E') + sage: E.fiber(p).construction() is None + True + """ + return None + def _repr_(self): r""" String representation of ``self``. diff --git a/src/sage/matrix/benchmark.py b/src/sage/matrix/benchmark.py index 5058798da1a..86d09a63a49 100644 --- a/src/sage/matrix/benchmark.py +++ b/src/sage/matrix/benchmark.py @@ -30,7 +30,8 @@ timeout = 60 -def report(F, title, systems = ['sage', 'magma'], **kwds): + +def report(F, title, systems=['sage', 'magma'], **kwds): """ Run benchmarks with default arguments for each function in the list F. diff --git a/src/sage/matrix/matrix0.pyx b/src/sage/matrix/matrix0.pyx index 00037358faa..372ba91ec65 100644 --- a/src/sage/matrix/matrix0.pyx +++ b/src/sage/matrix/matrix0.pyx @@ -248,6 +248,22 @@ cdef class Matrix(sage.structure.element.Matrix): monomial_coefficients = dict + def items(self): + r""" + Return an iterable of ``((i,j), value)`` elements. + + This may (but is not guaranteed to) suppress zero values. + + EXAMPLES:: + + sage: a = matrix(QQ['x,y'], 2, range(6), sparse=True); a + [0 1 2] + [3 4 5] + sage: list(a.items()) + [((0, 1), 1), ((0, 2), 2), ((1, 0), 3), ((1, 1), 4), ((1, 2), 5)] + """ + return self._dict().items() + def _dict(self): """ Unsafe version of the dict method, mainly for internal use. diff --git a/src/sage/matrix/matrix1.pyx b/src/sage/matrix/matrix1.pyx index 287d58886d4..1f109a2e3cc 100644 --- a/src/sage/matrix/matrix1.pyx +++ b/src/sage/matrix/matrix1.pyx @@ -404,12 +404,12 @@ cdef class Matrix(Matrix0): EXAMPLES:: sage: M = matrix(ZZ,2,range(4)) - sage: polymake(M) # optional - polymake + sage: polymake(M) # optional - jupymake 0 1 2 3 sage: K. = QuadraticField(5) sage: M = matrix(K, [[1, 2], [sqrt5, 3]]) - sage: polymake(M) # optional - polymake + sage: polymake(M) # optional - jupymake 1 2 0+1r5 3 """ diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index 6887975bf35..ad0f7e58e15 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -2313,7 +2313,8 @@ cdef class Matrix(Matrix1): the implementation raises an error because correct results cannot be guaranteed. - To access the algorithm using the above defintion, use ``'definition'``. + To access the algorithm using the above definition, + use ``'definition'``. However, notice that this algorithm is usually very slow. By default, i.e. if no options are set, the implementation tries to diff --git a/src/sage/matrix/matrix_integer_dense_hnf.py b/src/sage/matrix/matrix_integer_dense_hnf.py index 735e7016eda..acdb0f09cc4 100644 --- a/src/sage/matrix/matrix_integer_dense_hnf.py +++ b/src/sage/matrix/matrix_integer_dense_hnf.py @@ -617,7 +617,7 @@ def hnf_square(A, proof): # weird cases where the det is large. # E.g., matrix all of whose rows but 1 are multiplied by some # fixed scalar n. - raise NotImplementedError("fallback to PARI!") + raise NotImplementedError("fallback to PARI") # H = W.hermite_form(algorithm='pari') else: H = W._hnf_mod(2 * g) diff --git a/src/sage/matrix/matrix_space.py b/src/sage/matrix/matrix_space.py index c29bcc64260..26f058d3aeb 100644 --- a/src/sage/matrix/matrix_space.py +++ b/src/sage/matrix/matrix_space.py @@ -36,9 +36,7 @@ import sys import operator -# Sage matrix imports -from . import matrix_generic_dense -from . import matrix_generic_sparse +# Sage matrix imports see :trac:`34283` # Sage imports import sage.structure.coerce @@ -303,7 +301,8 @@ def get_matrix_class(R, nrows, ncols, sparse, implementation): return matrix_mpolynomial_dense.Matrix_mpolynomial_dense # The fallback - return matrix_generic_dense.Matrix_generic_dense + from sage.matrix.matrix_generic_dense import Matrix_generic_dense + return Matrix_generic_dense # Deal with request for a specific implementation if implementation == 'flint': @@ -360,7 +359,8 @@ def get_matrix_class(R, nrows, ncols, sparse, implementation): raise ValueError("'linbox-double' matrices can only deal with order < %s" % matrix_modn_dense_double.MAX_MODULUS) if implementation == 'generic': - return matrix_generic_dense.Matrix_generic_dense + from sage.matrix.matrix_generic_dense import Matrix_generic_dense + return Matrix_generic_dense if implementation == 'gap': from .matrix_gap import Matrix_gap @@ -402,7 +402,8 @@ def get_matrix_class(R, nrows, ncols, sparse, implementation): return matrix_double_sparse.Matrix_double_sparse # the fallback - return matrix_generic_sparse.Matrix_generic_sparse + from sage.matrix.matrix_generic_sparse import Matrix_generic_sparse + return Matrix_generic_sparse @@ -2338,9 +2339,9 @@ def _polymake_init_(self): EXAMPLES:: - sage: polymake(MatrixSpace(QQ,3)) # optional - polymake + sage: polymake(MatrixSpace(QQ,3)) # optional - jupymake Matrix - sage: polymake(MatrixSpace(QuadraticField(5),3)) # optional - polymake + sage: polymake(MatrixSpace(QuadraticField(5),3)) # optional - jupymake Matrix """ from sage.interfaces.polymake import polymake diff --git a/src/sage/media/wav.py b/src/sage/media/wav.py index 7be09afdf41..2c35cb9320a 100644 --- a/src/sage/media/wav.py +++ b/src/sage/media/wav.py @@ -297,9 +297,8 @@ def plot(self, npoints=None, channel=0, plotjoined=True, **kwds): a plot object that can be shown. """ - - domain = self.domain(npoints = npoints) - values = self.values(npoints=npoints, channel = channel) + domain = self.domain(npoints=npoints) + values = self.values(npoints=npoints, channel=channel) points = zip(domain, values) L = list_plot(points, plotjoined=plotjoined, **kwds) @@ -373,13 +372,13 @@ def _copy(self, start, stop): channels_sliced = [self._channel_data[i][start:stop] for i in range(self._nchannels)] print(stop - start) - return Wave(nchannels = self._nchannels, - width = self._width, - framerate = self._framerate, - bytes = self._bytes[start:stop], - nframes = stop - start, - channel_data = channels_sliced, - name = self._name) + return Wave(nchannels=self._nchannels, + width=self._width, + framerate=self._framerate, + bytes=self._bytes[start:stop], + nframes=stop - start, + channel_data=channels_sliced, + name=self._name) def __copy__(self): return self._copy(0, self._nframes) diff --git a/src/sage/misc/cachefunc.pyx b/src/sage/misc/cachefunc.pyx index c0691cff9c5..9fa967ce737 100644 --- a/src/sage/misc/cachefunc.pyx +++ b/src/sage/misc/cachefunc.pyx @@ -849,8 +849,6 @@ cdef class CachedFunction(): sage: print(sage_getdoc(I.groebner_basis)) # indirect doctest Return the reduced Groebner basis of this ideal. ... - ALGORITHM: Uses Singular, Magma (if available), Macaulay2 (if - available), Giac (if available), or a toy implementation. Test that :trac:`15184` is fixed:: diff --git a/src/sage/misc/dev_tools.py b/src/sage/misc/dev_tools.py index b08b6857623..5eed16a8565 100644 --- a/src/sage/misc/dev_tools.py +++ b/src/sage/misc/dev_tools.py @@ -292,7 +292,7 @@ def find_object_modules(obj): if module_name: if module_name not in sys.modules: - raise ValueError("This should not happen!") + raise ValueError("this should never happen") d = sys.modules[module_name].__dict__ matching = sorted(key for key in d if d[key] is obj) if matching: @@ -339,7 +339,7 @@ def import_statements(*objects, **kwds): INPUT: - - ``*objects`` -- a sequence of objects or names. + - ``*objects`` -- a sequence of objects or comma-separated strings of names. - ``lazy`` -- a boolean (default: ``False``) Whether to print a lazy import statement. @@ -406,6 +406,12 @@ def import_statements(*objects, **kwds): # - sage.rings.integer_ring from sage.rings.integer_ring import Z + The strings are allowed to be comma-separated names, and parenthesis + are stripped for convenience:: + + sage: import_statements('(floor, ceil)') + from sage.functions.other import floor, ceil + Specifying a string is also useful for objects that are not imported in the Sage interpreter namespace by default. In this case, an object with that name is looked up in all the modules @@ -502,6 +508,7 @@ def import_statements(*objects, **kwds): detect deprecated stuff). So, if you use it, double check the answer and report weird behaviors. """ + import itertools import inspect from sage.misc.lazy_import import LazyImport @@ -518,7 +525,15 @@ def import_statements(*objects, **kwds): if kwds: raise TypeError("Unexpected '{}' argument".format(next(iter(kwds)))) - for obj in objects: + def expand_comma_separated_names(object): + if isinstance(object, str): + for w in object.strip('()').split(','): + yield w.strip() + else: + yield object + + for obj in itertools.chain.from_iterable(expand_comma_separated_names(object) + for object in objects): name = None # the name of the object # 1. if obj is a string, we look for an object that has that name diff --git a/src/sage/misc/latex_standalone.py b/src/sage/misc/latex_standalone.py index b529773b248..feb476a39d0 100644 --- a/src/sage/misc/latex_standalone.py +++ b/src/sage/misc/latex_standalone.py @@ -158,7 +158,7 @@ sage: from sage.misc.latex_standalone import TikzPicture sage: V = [[1,0,1],[1,0,0],[1,1,0],[0,0,-1],[0,1,0],[-1,0,0],[0,1,1],[0,0,1],[0,-1,0]] sage: P = Polyhedron(vertices=V).polar() - sage: s = P.projection().tikz([674,108,-731],112) + sage: s = P.projection().tikz([674,108,-731],112, output_type='LatexExpr') sage: t = TikzPicture(s) Open the image in a viewer (the returned value is a string giving the diff --git a/src/sage/misc/lazy_format.py b/src/sage/misc/lazy_format.py index 4a529aa1bc6..e3050695b25 100644 --- a/src/sage/misc/lazy_format.py +++ b/src/sage/misc/lazy_format.py @@ -31,7 +31,7 @@ class LazyFormat(str): sage: class IDontLikeBeingPrinted(): ....: def __repr__(self): - ....: raise ValueError("Don't ever try to print me !") + ....: raise ValueError("do not ever try to print me") There is no error when binding a lazy format with the broken object:: @@ -40,7 +40,7 @@ class LazyFormat(str): The error only occurs upon printing:: sage: lf - ) failed: ValueError: Don't ever try to print me !> + ) failed: ValueError: do not ever try to print me> .. rubric:: Common use case: @@ -63,7 +63,7 @@ class LazyFormat(str): ....: "%s doesn't contain 0"%IDontLikeBeingPrinted()) Traceback (most recent call last): ... - ValueError: Don't ever try to print me ! + ValueError: do not ever try to print me This behavior can induce major performance penalties when testing. Note that this issue does not impact the usual assert:: diff --git a/src/sage/misc/misc.py b/src/sage/misc/misc.py index 924403e47d4..c2c440daffb 100644 --- a/src/sage/misc/misc.py +++ b/src/sage/misc/misc.py @@ -575,7 +575,7 @@ def uniq(x): def _stable_uniq(L): """ Iterate over the elements of ``L``, yielding every element at most - once: keep only the first occurance of any item. + once: keep only the first occurrence of any item. The items must be hashable. diff --git a/src/sage/modular/abvar/abvar.py b/src/sage/modular/abvar/abvar.py index 885b029d938..05a9c16b9a1 100644 --- a/src/sage/modular/abvar/abvar.py +++ b/src/sage/modular/abvar/abvar.py @@ -29,46 +29,53 @@ # (at your option) any later version. # https://www.gnu.org/licenses/ # **************************************************************************** + from copy import copy from random import randrange +from sage.arith.functions import lcm as LCM +from sage.arith.misc import divisors, next_prime, is_prime +from sage.categories.modular_abelian_varieties import ModularAbelianVarieties +from sage.matrix.constructor import matrix +from sage.matrix.special import block_diagonal_matrix, identity_matrix from sage.misc.lazy_import import lazy_import - -from sage.categories.all import ModularAbelianVarieties -from sage.structure.sequence import Sequence, Sequence_generic -from sage.structure.richcmp import (richcmp_method, richcmp_not_equal, - rich_to_bool) -from sage.structure.parent import Parent -from .morphism import HeckeOperator, Morphism, DegeneracyMap -from .torsion_subgroup import RationalTorsionSubgroup, QQbarTorsionSubgroup -from .finite_subgroup import (FiniteSubgroup_lattice, FiniteSubgroup, - TorsionPoint) -from .cuspidal_subgroup import (CuspidalSubgroup, RationalCuspidalSubgroup, - RationalCuspSubgroup) -from sage.rings.all import ZZ, QQ, QQbar, Integer -from sage.arith.all import LCM, divisors, prime_range, next_prime -from sage.rings.ring import is_Ring -from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing -from sage.rings.infinity import infinity -from sage.modules.free_module import is_FreeModule -from sage.modular.arithgroup.all import (is_CongruenceSubgroup, is_Gamma0, - is_Gamma1, is_GammaH) -from sage.modular.modsym.all import ModularSymbols -from sage.modular.modsym.space import ModularSymbolsSpace +from sage.misc.misc_c import prod +from sage.modular.arithgroup.congroup_gamma0 import is_Gamma0 +from sage.modular.arithgroup.congroup_gamma1 import is_Gamma1 +from sage.modular.arithgroup.congroup_gammaH import is_GammaH +from sage.modular.arithgroup.congroup_generic import is_CongruenceSubgroup from sage.modular.modform.constructor import Newform -from sage.matrix.all import matrix, block_diagonal_matrix, identity_matrix +from sage.modular.modsym.modsym import ModularSymbols +from sage.modular.modsym.space import ModularSymbolsSpace +from sage.modular.quatalg.brandt import BrandtModule +from sage.modules.free_module import is_FreeModule from sage.modules.free_module_element import vector -from sage.misc.misc_c import prod -from sage.arith.misc import is_prime +from sage.rings.fast_arith import prime_range +from sage.rings.infinity import infinity +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.qqbar import QQbar +from sage.rings.rational_field import QQ +from sage.rings.ring import is_Ring from sage.schemes.elliptic_curves.constructor import EllipticCurve from sage.sets.primes import Primes - -from . import homspace -from . import lseries +from sage.structure.parent import Parent +from sage.structure.richcmp import richcmp_method, richcmp_not_equal, rich_to_bool +from sage.structure.sequence import Sequence, Sequence_generic lazy_import('sage.databases.cremona', ['cremona_letter_code', 'CremonaDatabase']) +from . import homspace +from . import lseries +from .morphism import HeckeOperator, Morphism, DegeneracyMap +from .torsion_subgroup import RationalTorsionSubgroup, QQbarTorsionSubgroup +from .finite_subgroup import (FiniteSubgroup_lattice, FiniteSubgroup, + TorsionPoint) +from .cuspidal_subgroup import (CuspidalSubgroup, RationalCuspidalSubgroup, + RationalCuspSubgroup) + def is_ModularAbelianVariety(x) -> bool: """ @@ -4929,7 +4936,6 @@ def brandt_module(self, p): if self.level().valuation(p) != 1: raise ValueError("p must exactly divide the level") M = self.level() / p - from sage.modular.all import BrandtModule V = BrandtModule(p, M) # now cut out version of self in B S = self.modular_symbols(sign=1) diff --git a/src/sage/modular/abvar/cuspidal_subgroup.py b/src/sage/modular/abvar/cuspidal_subgroup.py index ed88939c919..a2769d7eff7 100644 --- a/src/sage/modular/abvar/cuspidal_subgroup.py +++ b/src/sage/modular/abvar/cuspidal_subgroup.py @@ -69,11 +69,14 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -from .finite_subgroup import FiniteSubgroup -from sage.rings.all import infinity, QQ, ZZ -from sage.matrix.all import matrix +from sage.matrix.constructor import matrix from sage.modular.arithgroup.all import is_Gamma0 from sage.modular.cusps import Cusp +from sage.rings.infinity import infinity +from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ + +from .finite_subgroup import FiniteSubgroup class CuspidalSubgroup_generic(FiniteSubgroup): diff --git a/src/sage/modular/abvar/finite_subgroup.py b/src/sage/modular/abvar/finite_subgroup.py index 471386f3575..d2af5e29e00 100644 --- a/src/sage/modular/abvar/finite_subgroup.py +++ b/src/sage/modular/abvar/finite_subgroup.py @@ -103,8 +103,11 @@ from sage.structure.gens_py import abelian_iterator from sage.structure.sequence import Sequence from sage.structure.richcmp import richcmp_method, richcmp -from sage.rings.all import QQ, ZZ, QQbar, Integer -from sage.arith.all import lcm +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ +from sage.rings.qqbar import QQbar +from sage.rings.rational_field import QQ +from sage.arith.functions import lcm from sage.misc.misc_c import prod from sage.structure.element import coercion_model diff --git a/src/sage/modular/abvar/homology.py b/src/sage/modular/abvar/homology.py index 0e87e0da7c4..735bb48fb7f 100644 --- a/src/sage/modular/abvar/homology.py +++ b/src/sage/modular/abvar/homology.py @@ -50,9 +50,12 @@ # https://www.gnu.org/licenses/ # **************************************************************************** +from sage.modular.hecke.module import HeckeModule_free_module +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ +from sage.rings.ring import CommutativeRing from sage.structure.richcmp import richcmp_method, richcmp, richcmp_not_equal -from sage.modular.hecke.all import HeckeModule_free_module -from sage.rings.all import Integer, ZZ, QQ, CommutativeRing # TODO: we will probably also need homology that is *not* a Hecke module. diff --git a/src/sage/modular/abvar/torsion_subgroup.py b/src/sage/modular/abvar/torsion_subgroup.py index affd131e1b9..b2a5c35f8b9 100644 --- a/src/sage/modular/abvar/torsion_subgroup.py +++ b/src/sage/modular/abvar/torsion_subgroup.py @@ -88,16 +88,19 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -from sage.structure.richcmp import richcmp_method, richcmp +from sage.arith.misc import divisors, gcd +from sage.misc.misc_c import prod from sage.modular.abvar.torsion_point import TorsionPoint -from sage.modules.module import Module -from .finite_subgroup import FiniteSubgroup -from sage.rings.all import ZZ, QQ -from sage.sets.primes import Primes -from sage.modular.arithgroup.all import is_Gamma0, is_Gamma1 -from sage.all import divisors, gcd, prime_range -from sage.modular.dirichlet import DirichletGroup -from sage.misc.misc_c import prod +from sage.modular.arithgroup.all import is_Gamma0, is_Gamma1 +from sage.modular.dirichlet import DirichletGroup +from sage.modules.module import Module +from sage.rings.fast_arith import prime_range +from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ +from sage.sets.primes import Primes +from sage.structure.richcmp import richcmp_method, richcmp + +from .finite_subgroup import FiniteSubgroup @richcmp_method diff --git a/src/sage/modular/arithgroup/arithgroup_generic.py b/src/sage/modular/arithgroup/arithgroup_generic.py index 89b80dd9385..7e07c33e551 100644 --- a/src/sage/modular/arithgroup/arithgroup_generic.py +++ b/src/sage/modular/arithgroup/arithgroup_generic.py @@ -16,7 +16,7 @@ from sage.groups.old import Group from sage.categories.groups import Groups from sage.rings.integer_ring import ZZ -from sage.arith.all import lcm +from sage.arith.functions import lcm from sage.misc.cachefunc import cached_method from copy import copy # for making copies of lists of cusps from sage.modular.modsym.p1list import lift_to_sl2z diff --git a/src/sage/modular/arithgroup/arithgroup_perm.py b/src/sage/modular/arithgroup/arithgroup_perm.py index a1f16856cbe..0265f5b780a 100644 --- a/src/sage/modular/arithgroup/arithgroup_perm.py +++ b/src/sage/modular/arithgroup/arithgroup_perm.py @@ -98,14 +98,18 @@ # ################################################################################ +from sage.arith.functions import lcm +from sage.arith.misc import CRT_basis +from sage.groups.perm_gps.constructor import PermutationGroupElement as PermutationConstructor +from sage.groups.perm_gps.permgroup import PermutationGroup +from sage.groups.perm_gps.permgroup_element import PermutationGroupElement +from sage.misc.cachefunc import cached_method +from sage.misc.misc_c import prod +from sage.rings.integer_ring import ZZ + from .all import SL2Z from .arithgroup_generic import ArithmeticSubgroup -from sage.rings.integer_ring import ZZ -from sage.misc.cachefunc import cached_method -import sage.arith.all as arith -from sage.groups.perm_gps.permgroup_element import PermutationGroupElement -from sage.groups.perm_gps.constructor import PermutationGroupElement as PermutationConstructor Idm = SL2Z([1,0,0,1]) # identity @@ -206,8 +210,7 @@ def eval_sl2z_word(w): [ 66 -59] [ 47 -42] """ - from sage.all import prod - mat = [Lm,Rm] + mat = [Lm, Rm] w0 = Idm w1 = w return w0 * prod((mat[a[0]]**a[1] for a in w1), Idm) @@ -243,7 +246,6 @@ def word_of_perms(w, p1, p2): G = G2 p1 = G(p1) else: - from sage.groups.perm_gps.all import PermutationGroup G = PermutationGroup([p1,p2]) p1 = G(p1) p2 = G(p2) @@ -431,8 +433,6 @@ def ArithmeticSubgroup_Permutation( # Check transitivity. This is the most expensive check, so we do it # last. - from sage.groups.perm_gps.all import PermutationGroup - G = PermutationGroup(gens) if not G.is_transitive(): raise ValueError("Permutations do not generate a transitive group") @@ -676,7 +676,6 @@ def perm_group(self): sage: ap.HsuExample10().perm_group() Permutation Group with generators [(1,2)(3,4)(5,6)(7,8)(9,10), (1,8,3)(2,4,6)(5,7,10), (1,4)(2,5,9,10,8)(3,7,6), (1,7,9,10,6)(2,3)(4,5,8)] """ - from sage.groups.perm_gps.all import PermutationGroup # we set canonicalize to False as otherwise PermutationGroup changes the # order of the generators. return PermutationGroup([self.S2(), self.S3(), self.L(), self.R()], canonicalize=False) @@ -1288,7 +1287,7 @@ def generalised_level(self): sage: G.generalised_level() 3 """ - return arith.lcm(self.cusp_widths()) + return lcm(self.cusp_widths()) def congruence_closure(self): r""" @@ -1477,7 +1476,7 @@ def is_congruence(self): # e>1, m>1 onehalf = ZZ(2).inverse_mod(m) # i.e. 2^(-1) mod m onefifth = ZZ(5).inverse_mod(e) # i.e. 5^(-1) mod e - c,d = arith.CRT_basis([m, e]) + c, d = CRT_basis([m, e]) # c=0 mod e, c=1 mod m; d=1 mod e, d=0 mod m a = L**c b = R**c diff --git a/src/sage/modular/arithgroup/congroup_gamma.py b/src/sage/modular/arithgroup/congroup_gamma.py index 4625c7abc15..d6fbedbc16e 100644 --- a/src/sage/modular/arithgroup/congroup_gamma.py +++ b/src/sage/modular/arithgroup/congroup_gamma.py @@ -10,18 +10,21 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -from .congroup_generic import CongruenceSubgroup -from sage.misc.misc_c import prod -from sage.rings.all import ZZ, Zmod, QQ -from sage.rings.integer import GCD_list +from sage.arith.misc import gcd from sage.groups.matrix_gps.finitely_generated import MatrixGroup from sage.matrix.constructor import matrix +from sage.misc.misc_c import prod from sage.modular.cusps import Cusp -from sage.arith.all import gcd +from sage.rings.finite_rings.integer_mod_ring import Zmod +from sage.rings.integer import GCD_list +from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ from sage.structure.richcmp import richcmp_method, richcmp +from .congroup_generic import CongruenceSubgroup from .congroup_sl2z import SL2Z + _gamma_cache = {} def Gamma_constructor(N): r""" diff --git a/src/sage/modular/arithgroup/congroup_gamma0.py b/src/sage/modular/arithgroup/congroup_gamma0.py index aa404786fbc..1997f7f8cf6 100644 --- a/src/sage/modular/arithgroup/congroup_gamma0.py +++ b/src/sage/modular/arithgroup/congroup_gamma0.py @@ -10,18 +10,17 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -from .congroup_gammaH import GammaH_class -from .congroup_gamma1 import is_Gamma1 -from sage.modular.modsym.p1list import lift_to_sl2z -from .congroup_generic import CongruenceSubgroup - -from sage.modular.cusps import Cusp +from sage.arith.misc import kronecker_symbol, divisors, euler_phi, gcd, moebius from sage.misc.cachefunc import cached_method -from sage.rings.all import IntegerModRing, ZZ -from sage.arith.all import kronecker_symbol from sage.misc.misc_c import prod -import sage.modular.modsym.p1list -import sage.arith.all as arith +from sage.modular.cusps import Cusp +from sage.modular.modsym.p1list import lift_to_sl2z, P1List +from sage.rings.finite_rings.integer_mod_ring import IntegerModRing +from sage.rings.integer_ring import ZZ + +from .congroup_gamma1 import is_Gamma1 +from .congroup_gammaH import GammaH_class +from .congroup_generic import CongruenceSubgroup def is_Gamma0(x): @@ -211,7 +210,6 @@ def _list_of_elements_in_H(self): """ N = self.level() if N != 1: - gcd = arith.gcd H = [x for x in range(1, N) if gcd(x, N) == 1] else: H = [1] @@ -321,7 +319,7 @@ def coset_reps(self): if N == 1: # P1List isn't very happy working modulo 1 yield SL2Z([1,0,0,1]) else: - for z in sage.modular.modsym.p1list.P1List(N): + for z in P1List(N): yield SL2Z(lift_to_sl2z(z[0], z[1], N)) @cached_method @@ -455,8 +453,8 @@ def _find_cusps(self): N = self.level() s = [] - for d in arith.divisors(N): - w = arith.gcd(d, N//d) + for d in divisors(N): + w = gcd(d, N//d) if w == 1: if d == 1: s.append(Cusp(1,0)) @@ -466,8 +464,8 @@ def _find_cusps(self): s.append(Cusp(1,d)) else: for a in range(1, w): - if arith.gcd(a, w) == 1: - while arith.gcd(a, d//w) != 1: + if gcd(a, w) == 1: + while gcd(a, d//w) != 1: a += w s.append(Cusp(a,d)) return sorted(s) @@ -484,7 +482,7 @@ def ncusps(self): [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2] """ n = self.level() - return sum([arith.euler_phi(arith.gcd(d,n//d)) for d in n.divisors()]) + return sum([euler_phi(gcd(d,n//d)) for d in n.divisors()]) def nu2(self): @@ -595,7 +593,6 @@ def dimension_new_cusp_forms(self, k=2, p=0): sage: all(Gamma0(N).dimension_new_cusp_forms(2)==100 for N in L) True """ - from sage.arith.all import moebius from sage.functions.other import floor N = self.level() diff --git a/src/sage/modular/arithgroup/congroup_gamma1.py b/src/sage/modular/arithgroup/congroup_gamma1.py index ca1bf498514..84c19cff70d 100644 --- a/src/sage/modular/arithgroup/congroup_gamma1.py +++ b/src/sage/modular/arithgroup/congroup_gamma1.py @@ -17,7 +17,7 @@ from sage.misc.misc_c import prod from .congroup_gammaH import GammaH_class, is_GammaH, GammaH_constructor from sage.rings.integer_ring import ZZ -from sage.arith.all import euler_phi as phi, moebius, divisors +from sage.arith.misc import euler_phi as phi, moebius, divisors from sage.modular.dirichlet import DirichletGroup diff --git a/src/sage/modular/arithgroup/congroup_gammaH.py b/src/sage/modular/arithgroup/congroup_gammaH.py index 8db52a92fcc..af0c27474a3 100644 --- a/src/sage/modular/arithgroup/congroup_gammaH.py +++ b/src/sage/modular/arithgroup/congroup_gammaH.py @@ -20,7 +20,8 @@ # ################################################################################ -from sage.arith.all import euler_phi, lcm, gcd, divisors, get_inverse_mod, get_gcd, factor, xgcd +from sage.arith.functions import lcm +from sage.arith.misc import euler_phi, gcd, divisors, get_inverse_mod, get_gcd, factor, xgcd from sage.modular.modsym.p1list import lift_to_sl2z from .congroup_generic import CongruenceSubgroup from sage.modular.cusps import Cusp diff --git a/src/sage/modular/arithgroup/congroup_generic.py b/src/sage/modular/arithgroup/congroup_generic.py index 2cc5335a3f9..99dbc5036fb 100644 --- a/src/sage/modular/arithgroup/congroup_generic.py +++ b/src/sage/modular/arithgroup/congroup_generic.py @@ -21,12 +21,15 @@ # ################################################################################ -from sage.rings.all import QQ, ZZ, Zmod -from sage.arith.all import gcd -from sage.sets.set import Set +from sage.arith.misc import gcd from sage.groups.matrix_gps.all import MatrixGroup from sage.matrix.matrix_space import MatrixSpace from sage.misc.misc_c import prod +from sage.rings.finite_rings.integer_mod_ring import Zmod +from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ +from sage.sets.set import Set + from .arithgroup_generic import ArithmeticSubgroup diff --git a/src/sage/modular/arithgroup/congroup_sl2z.py b/src/sage/modular/arithgroup/congroup_sl2z.py index 062f835517c..69a977994d0 100644 --- a/src/sage/modular/arithgroup/congroup_sl2z.py +++ b/src/sage/modular/arithgroup/congroup_sl2z.py @@ -19,12 +19,14 @@ # ################################################################################ -from .congroup_gamma0 import Gamma0_class -from .arithgroup_element import ArithmeticSubgroupElement -from sage.rings.integer_ring import ZZ +from sage.arith.misc import gcd from sage.modular.cusps import Cusp -from sage.arith.all import gcd from sage.modular.modsym.p1list import lift_to_sl2z +from sage.rings.integer_ring import ZZ + +from .congroup_gamma0 import Gamma0_class +from .arithgroup_element import ArithmeticSubgroupElement + def is_SL2Z(x): r""" diff --git a/src/sage/modular/arithgroup/tests.py b/src/sage/modular/arithgroup/tests.py index 7fae4d139a5..009f3d569e2 100644 --- a/src/sage/modular/arithgroup/tests.py +++ b/src/sage/modular/arithgroup/tests.py @@ -349,7 +349,7 @@ def test_spanning_trees(self): sage: from sage.modular.arithgroup.tests import Test sage: Test().test_spanning_trees() #random """ - from sage.all import prod + from sage.misc.misc_c import prod from .all import SL2Z from .arithgroup_perm import S2m, S3m, Lm diff --git a/src/sage/modular/btquotients/btquotient.py b/src/sage/modular/btquotients/btquotient.py index 7b3fc44316c..c7564695c8a 100644 --- a/src/sage/modular/btquotients/btquotient.py +++ b/src/sage/modular/btquotients/btquotient.py @@ -41,32 +41,34 @@ from copy import copy from collections import deque -from sage.rings.integer import Integer +from sage.algebras.quatalg.quaternion_algebra import QuaternionAlgebra +from sage.arith.misc import gcd, xgcd, kronecker_symbol, fundamental_discriminant +from sage.graphs.graph import Graph +from sage.interfaces.magma import magma +from sage.libs.pari.all import pari from sage.matrix.constructor import Matrix from sage.matrix.matrix_space import MatrixSpace -from sage.structure.sage_object import SageObject -from sage.rings.all import ZZ, Zmod, QQ -from sage.misc.latex import latex -from sage.rings.padics.precision_error import PrecisionError -from sage.misc.misc_c import prod -from sage.structure.unique_representation import UniqueRepresentation from sage.misc.cachefunc import cached_method -from sage.arith.all import gcd, xgcd, kronecker_symbol, fundamental_discriminant -from sage.rings.padics.all import Qp, Zp -from sage.rings.finite_rings.finite_field_constructor import GF -from sage.algebras.quatalg.all import QuaternionAlgebra -from sage.quadratic_forms.all import QuadraticForm -from sage.graphs.graph import Graph -from sage.libs.pari.all import pari -from sage.interfaces.magma import magma +from sage.misc.latex import latex +from sage.misc.lazy_attribute import lazy_attribute from sage.misc.lazy_import import lazy_import -lazy_import("sage.plot.colors", "rainbow") -from sage.rings.number_field.all import NumberField +from sage.misc.misc_c import prod +from sage.misc.verbose import verbose from sage.modular.arithgroup.all import Gamma0 -from sage.misc.lazy_attribute import lazy_attribute -from sage.modular.dirichlet import DirichletGroup from sage.modular.arithgroup.congroup_gammaH import GammaH_constructor -from sage.misc.verbose import verbose +from sage.modular.dirichlet import DirichletGroup +lazy_import("sage.plot.colors", "rainbow") +from sage.quadratic_forms.quadratic_form import QuadraticForm +from sage.rings.finite_rings.finite_field_constructor import GF +from sage.rings.finite_rings.integer_mod_ring import Zmod +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ +from sage.rings.number_field.number_field import NumberField +from sage.rings.padics.factory import Qp, Zp +from sage.rings.padics.precision_error import PrecisionError +from sage.rings.rational_field import QQ +from sage.structure.sage_object import SageObject +from sage.structure.unique_representation import UniqueRepresentation class DoubleCosetReduction(SageObject): diff --git a/src/sage/modular/btquotients/pautomorphicform.py b/src/sage/modular/btquotients/pautomorphicform.py index 540da704d3f..890cdc3cd4c 100644 --- a/src/sage/modular/btquotients/pautomorphicform.py +++ b/src/sage/modular/btquotients/pautomorphicform.py @@ -40,26 +40,29 @@ p-adic automorphic form of cohomological weight 0 """ -from sage.modular.btquotients.btquotient import DoubleCosetReduction -from sage.structure.unique_representation import UniqueRepresentation -from sage.structure.richcmp import op_EQ, op_NE +from copy import copy +import sage.modular.hecke.hecke_operator + +from sage.matrix.constructor import Matrix, zero_matrix from sage.matrix.matrix_space import MatrixSpace -from sage.structure.element import ModuleElement +from sage.misc.verbose import verbose +from sage.modular.btquotients.btquotient import DoubleCosetReduction +from sage.modular.hecke.all import AmbientHeckeModule, HeckeModuleElement +from sage.modular.pollack_stevens.distributions import OverconvergentDistributions, Symk +from sage.modular.pollack_stevens.sigma0 import Sigma0ActionAdjuster from sage.modules.module import Module +from sage.rings.infinity import Infinity from sage.rings.integer import Integer -from sage.matrix.constructor import Matrix, zero_matrix -from sage.rings.all import Qp, QQ, ZZ -from copy import copy -from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.integer_ring import ZZ from sage.rings.laurent_series_ring import LaurentSeriesRing -from sage.modular.hecke.all import (AmbientHeckeModule, HeckeModuleElement) -from sage.rings.infinity import Infinity -import sage.modular.hecke.hecke_operator -from sage.misc.verbose import verbose +from sage.rings.padics.factory import Qp +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.rational_field import QQ from sage.rings.real_mpfr import RR -from sage.modular.pollack_stevens.sigma0 import Sigma0ActionAdjuster -from sage.modular.pollack_stevens.distributions import OverconvergentDistributions, Symk +from sage.structure.element import ModuleElement +from sage.structure.richcmp import op_EQ, op_NE +from sage.structure.unique_representation import UniqueRepresentation # Need this to be pickleable diff --git a/src/sage/modular/cusps.py b/src/sage/modular/cusps.py index b7cd341e800..27147942a6a 100644 --- a/src/sage/modular/cusps.py +++ b/src/sage/modular/cusps.py @@ -28,17 +28,18 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -from sage.rings.all import Rational, Integer, ZZ, QQ -from sage.rings.infinity import Infinity, InfinityRing - -from sage.structure.parent import Parent -from sage.misc.fast_methods import Singleton -from sage.structure.element import Element, is_InfinityElement -from sage.structure.richcmp import richcmp - from sage.libs.pari.all import pari, pari_gen +from sage.misc.fast_methods import Singleton from sage.modular.modsym.p1list import lift_to_sl2z_llong +from sage.rings.infinity import Infinity, InfinityRing +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ +from sage.rings.rational import Rational +from sage.rings.rational_field import QQ +from sage.structure.element import Element, is_InfinityElement from sage.structure.element import is_Matrix +from sage.structure.parent import Parent +from sage.structure.richcmp import richcmp class Cusp(Element): diff --git a/src/sage/modular/cusps_nf.py b/src/sage/modular/cusps_nf.py index acfa7a5ebb7..25d93cac929 100644 --- a/src/sage/modular/cusps_nf.py +++ b/src/sage/modular/cusps_nf.py @@ -972,7 +972,7 @@ def is_Gamma0_equivalent(self, other, N, Transformation=False): w = ((1 - f) * Aux) / (M1[2] * M2[2]) AuxCoeff[3] = u AuxCoeff[1] = w - from sage.matrix.all import Matrix + from sage.matrix.constructor import Matrix Maux = Matrix(k, 2, AuxCoeff) M1inv = Matrix(k, 2, M1).inverse() Mtrans = Matrix(k, 2, M2) * Maux * M1inv @@ -1053,7 +1053,7 @@ def Gamma0_NFCusps(N): g = (A * B).gens_reduced()[0] # for every divisor of N we have to find cusps - from sage.arith.all import divisors + from sage.arith.misc import divisors for d in divisors(N): # find delta prime coprime to B in inverse class of d*A # by searching in our list of auxiliary prime ideals @@ -1117,7 +1117,7 @@ def number_of_Gamma0_NFCusps(N): """ k = N.number_field() # The number of Gamma0(N)-sub-orbits for each Gamma-orbit: - from sage.arith.all import divisors + from sage.arith.misc import divisors Ugens = [k(u) for u in k.unit_group().gens()] s = sum([len((d + N / d).invertible_residues_mod(Ugens)) for d in divisors(N)]) diff --git a/src/sage/modular/dims.py b/src/sage/modular/dims.py index 55d085b1e9a..f2f63b3433c 100644 --- a/src/sage/modular/dims.py +++ b/src/sage/modular/dims.py @@ -45,15 +45,16 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -from sage.arith.all import factor, is_prime, valuation - +from sage.arith.misc import factor, is_prime, valuation from sage.misc.misc_c import prod -from sage.rings.all import Mod, Integer, IntegerModRing -from sage.rings.rational_field import frac -from . import dirichlet - from sage.modular.arithgroup.all import (Gamma0, Gamma1, is_ArithmeticSubgroup, is_GammaH) +from sage.rings.finite_rings.integer_mod import Mod +from sage.rings.finite_rings.integer_mod_ring import IntegerModRing +from sage.rings.integer import Integer +from sage.rings.rational_field import frac + +from . import dirichlet ########################################################################## # Helper functions for calculating dimensions of spaces of modular forms diff --git a/src/sage/modular/dirichlet.py b/src/sage/modular/dirichlet.py index fd6f3c1f7cd..491664102a8 100644 --- a/src/sage/modular/dirichlet.py +++ b/src/sage/modular/dirichlet.py @@ -57,34 +57,39 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -import sage.categories.all as cat -from sage.misc.misc_c import prod import sage.misc.prandom as random -from sage.modules.free_module import FreeModule import sage.modules.free_module_element as free_module_element -import sage.rings.all as rings +import sage.rings.abc import sage.rings.number_field.number_field as number_field -from sage.libs.pari import pari +from sage.arith.functions import lcm +from sage.arith.misc import bernoulli, kronecker, factor, gcd, fundamental_discriminant, euler_phi, valuation from sage.categories.map import Map -from sage.rings.rational_field import is_RationalField -import sage.rings.abc -from sage.rings.ring import is_Ring - -from sage.misc.functional import round +from sage.categories.objects import Objects +from sage.functions.other import binomial, factorial +from sage.libs.pari import pari from sage.misc.cachefunc import cached_method from sage.misc.fast_methods import WithEqualityById +from sage.misc.functional import round +from sage.misc.misc_c import prod +from sage.modules.free_module import FreeModule +from sage.rings.finite_rings.integer_mod import Mod +from sage.rings.finite_rings.integer_mod_ring import IntegerModRing +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ +from sage.rings.number_field.number_field import CyclotomicField +from sage.rings.power_series_ring import PowerSeriesRing +from sage.rings.rational_field import RationalField, QQ, is_RationalField +from sage.rings.ring import is_Ring from sage.structure.element import MultiplicativeGroupElement +from sage.structure.factory import UniqueFactory from sage.structure.gens_py import multiplicative_iterator from sage.structure.parent import Parent -from sage.structure.sequence import Sequence -from sage.structure.factory import UniqueFactory from sage.structure.richcmp import richcmp -from sage.arith.all import (binomial, bernoulli, kronecker, factor, gcd, - lcm, fundamental_discriminant, euler_phi, factorial, valuation) +from sage.structure.sequence import Sequence -def trivial_character(N, base_ring=rings.RationalField()): +def trivial_character(N, base_ring=RationalField()): r""" Return the trivial character of the given modulus, with values in the given base ring. @@ -126,12 +131,12 @@ def kronecker_character(d): - Jon Hanke (2006-08-06) """ - d = rings.Integer(d) + d = Integer(d) if d == 0: raise ValueError("d must be nonzero") D = fundamental_discriminant(d) - G = DirichletGroup(abs(D), rings.RationalField()) + G = DirichletGroup(abs(D), RationalField()) return G([kronecker(D, u) for u in G.unit_gens()]) @@ -149,11 +154,11 @@ def kronecker_character_upside_down(d): - Jon Hanke (2006-08-06) """ - d = rings.Integer(d) + d = Integer(d) if d <= 0: raise ValueError("d must be positive") - G = DirichletGroup(d, rings.RationalField()) + G = DirichletGroup(d, RationalField()) return G([kronecker(u.lift(), d) for u in G.unit_gens()]) @@ -709,7 +714,7 @@ def S(n): # This is better since it computes the same thing, but requires # no arith in a poly ring over a number field. prec = k + 2 - R = rings.PowerSeriesRing(rings.QQ, 't') + R = PowerSeriesRing(QQ, 't') t = R.gen() # g(t) = t/(e^{Nt}-1) g = t / ((N * t).exp(prec) - 1) @@ -789,7 +794,7 @@ def conductor(self): """ if self.modulus() == 1 or self.is_trivial(): - return rings.Integer(1) + return Integer(1) F = factor(self.modulus()) if len(F) > 1: return prod([d.conductor() for d in self.decomposition()]) @@ -803,7 +808,7 @@ def conductor(self): cond = p**(valuation(self.order(), p) + 1) if p == 2 and F[0][1] > 2 and self.values_on_gens()[1].multiplicative_order() != 1: cond *= 2 - return rings.Integer(cond) + return Integer(cond) @cached_method def fixed_field_polynomial(self, algorithm="pari"): @@ -994,7 +999,7 @@ def fixed_field_polynomial(self, algorithm="pari"): G, chi = self._pari_init_() K = pari.charker(G, chi) H = pari.galoissubcyclo(G, K) - P = PolynomialRing(rings.RationalField(), "x") + P = PolynomialRing(RationalField(), "x") x = P.gen() return H.sage({"x": x}) @@ -1352,7 +1357,7 @@ def gauss_sum(self, a=1): elif isinstance(K, sage.rings.abc.NumberField_cyclotomic) or is_RationalField(K): chi = chi.minimize_base_ring() n = lcm(m, G.zeta_order()) - L = rings.CyclotomicField(n) + L = CyclotomicField(n) zeta = L.gen(0) ** (n // m) else: raise NotImplementedError("Gauss sums only currently implemented when the base ring is a cyclotomic field, QQ, QQbar, or a complex field") @@ -1549,7 +1554,7 @@ def jacobi_sum(self, char, check=True): raise NotImplementedError("Characters must be from the same Dirichlet Group.") return sum([self(x) * char(1 - x) - for x in rings.IntegerModRing(self.modulus())]) + for x in IntegerModRing(self.modulus())]) def kloosterman_sum(self, a=1, b=0): r""" @@ -1602,7 +1607,7 @@ def kloosterman_sum(self, a=1, b=0): zo = G.zeta_order() m = G.modulus() g = 0 - L = rings.CyclotomicField(m.lcm(zo)) + L = CyclotomicField(m.lcm(zo)) zeta = L.gen(0) try: self(1) * zeta**(a + b) @@ -1612,7 +1617,7 @@ def kloosterman_sum(self, a=1, b=0): n = zeta.multiplicative_order() zeta = zeta**(n // m) for c in m.coprime_integers(m): - e = rings.Mod(c, m) + e = Mod(c, m) g += self(c) * zeta**int(a * e + b * e**(-1)) return g @@ -1657,7 +1662,7 @@ def kloosterman_sum_numerical(self, prec=53, a=1, b=0): m = G.modulus() zeta = CC.zeta(m) for c in m.coprime_integers(m): - e = rings.Mod(c, m) + e = Mod(c, m) z = zeta ** int(a * e + b * (e**(-1))) g += phi(self(c)) * z return g @@ -1836,7 +1841,7 @@ def maximize_base_ring(self): sage: DirichletGroup(20).base_ring() Cyclotomic Field of order 4 and degree 2 """ - g = rings.IntegerModRing(self.modulus()).unit_group_exponent() + g = IntegerModRing(self.modulus()).unit_group_exponent() if g == 1: g = 2 z = self.base_ring().zeta() @@ -1844,7 +1849,7 @@ def maximize_base_ring(self): m = lcm(g, n) if n == m: return self - K = rings.CyclotomicField(m) + K = CyclotomicField(m) return self.change_ring(K) def minimize_base_ring(self): @@ -1895,12 +1900,12 @@ def minimize_base_ring(self): p = R.characteristic() if p: - K = rings.IntegerModRing(p) + K = IntegerModRing(p) elif self.order() <= 2: - K = rings.QQ + K = QQ elif (isinstance(R, number_field.NumberField_generic) and euler_phi(self.order()) < R.absolute_degree()): - K = rings.CyclotomicField(self.order()) + K = CyclotomicField(self.order()) else: return self @@ -2449,15 +2454,15 @@ def create_key(self, N, base_ring=None, zeta=None, zeta_order=None, ... ValueError: modulus should be positive """ - modulus = rings.Integer(N) + modulus = Integer(N) if modulus <= 0: raise ValueError('modulus should be positive') if base_ring is None: if not (zeta is None and zeta_order is None): raise ValueError("zeta and zeta_order must be None if base_ring not specified") - e = rings.IntegerModRing(modulus).unit_group_exponent() - base_ring = rings.CyclotomicField(e) + e = IntegerModRing(modulus).unit_group_exponent() + base_ring = CyclotomicField(e) if integral: base_ring = base_ring.ring_of_integers() @@ -2473,7 +2478,7 @@ def create_key(self, N, base_ring=None, zeta=None, zeta_order=None, if not base_ring.is_integral_domain(): raise ValueError("base ring (= %s) must be an integral domain if only zeta_order is specified" % base_ring) - zeta_order = rings.Integer(zeta_order) + zeta_order = Integer(zeta_order) zeta = base_ring.zeta(zeta_order) return (base_ring, modulus, zeta, zeta_order) @@ -2560,7 +2565,7 @@ def __init__(self, base_ring, modulus, zeta, zeta_order): self._zeta = zeta self._zeta_order = zeta_order self._modulus = modulus - self._integers = rings.IntegerModRing(modulus) + self._integers = IntegerModRing(modulus) def __setstate__(self, state): """ @@ -2574,7 +2579,7 @@ def __setstate__(self, state): """ self._set_element_constructor() if '_zeta_order' in state: - state['_zeta_order'] = rings.Integer(state['_zeta_order']) + state['_zeta_order'] = Integer(state['_zeta_order']) super().__setstate__(state) @property @@ -2587,7 +2592,7 @@ def _module(self): sage: DirichletGroup(12)._module Vector space of dimension 2 over Ring of integers modulo 2 """ - return FreeModule(rings.IntegerModRing(self.zeta_order()), + return FreeModule(IntegerModRing(self.zeta_order()), len(self.unit_gens())) @property @@ -2890,7 +2895,7 @@ def decomposition(self): return Sequence([DirichletGroup(p**r, R) for p, r in factor(self.modulus())], cr=True, - universe=cat.Objects()) + universe=Objects()) def exponent(self): """ @@ -2938,13 +2943,13 @@ def _automorphisms(self): if p == 0: Auts = [e for e in range(1, n) if gcd(e, n) == 1] else: - if not rings.ZZ(p).is_prime(): + if not ZZ(p).is_prime(): raise NotImplementedError("Automorphisms for finite non-field base rings not implemented") # The automorphisms in characteristic p are # k-th powering for # k = 1, p, p^2, ..., p^(r-1), # where p^r = 1 (mod n), so r is the mult order of p modulo n. - r = rings.IntegerModRing(n)(p).multiplicative_order() + r = IntegerModRing(n)(p).multiplicative_order() Auts = [p**m for m in range(r)] return Auts @@ -3130,7 +3135,7 @@ def order(self): sage: DirichletGroup(37).order() 36 """ - ord = rings.Integer(1) + ord = Integer(1) for g in self.gens(): ord *= int(g.order()) return ord diff --git a/src/sage/modular/hecke/algebra.py b/src/sage/modular/hecke/algebra.py index 646f0ecb2cb..3699d2cb98e 100644 --- a/src/sage/modular/hecke/algebra.py +++ b/src/sage/modular/hecke/algebra.py @@ -28,7 +28,8 @@ import sage.rings.infinity from sage.matrix.constructor import matrix -from sage.arith.all import lcm, gcd +from sage.arith.functions import lcm +from sage.arith.misc import gcd from sage.misc.latex import latex from sage.matrix.matrix_space import MatrixSpace from sage.rings.ring import CommutativeAlgebra diff --git a/src/sage/modular/hecke/module.py b/src/sage/modular/hecke/module.py index 602f8f8b813..f559b6f1d3a 100644 --- a/src/sage/modular/hecke/module.py +++ b/src/sage/modular/hecke/module.py @@ -12,21 +12,22 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -from sage.rings.all import ZZ, QQ, CommutativeRing -import sage.arith.all as arith -from sage.misc.verbose import verbose -import sage.modules.module -from sage.structure.all import Sequence -import sage.matrix.matrix_space as matrix_space - import sage.misc.prandom as random +from sage.arith.misc import is_prime, factor, prime_divisors, gcd, primes, valuation, GCD, next_prime +from sage.matrix.matrix_space import MatrixSpace +from sage.misc.verbose import verbose +from sage.modules.free_module import FreeModule +from sage.modules.module import Module +from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ +from sage.rings.ring import CommutativeRing +from sage.structure.sequence import Sequence + from . import algebra from . import element from . import hecke_operator -from sage.modules.all import FreeModule - def is_HeckeModule(x): r""" @@ -45,7 +46,7 @@ def is_HeckeModule(x): return isinstance(x, HeckeModule_generic) -class HeckeModule_generic(sage.modules.module.Module): +class HeckeModule_generic(Module): r""" A very general base class for Hecke modules. @@ -84,7 +85,7 @@ def __init__(self, base_ring, level, category=None): else: assert category.is_subcategory(default_category), "%s is not a subcategory of %s" % (category, default_category) - sage.modules.module.Module.__init__(self, base_ring, category=category) + Module.__init__(self, base_ring, category=category) level = ZZ(level) if level <= 0: @@ -105,7 +106,7 @@ def __setstate__(self, state): if not self._is_category_initialized(): from sage.categories.hecke_modules import HeckeModules self._init_category_(HeckeModules(state['_base'])) - sage.modules.module.Module.__setstate__(self, state) + Module.__setstate__(self, state) def __hash__(self): r""" @@ -139,7 +140,7 @@ def _compute_hecke_matrix_prime_power(self, p, r, **kwds): """ # convert input arguments to int's. p, r = (int(p), int(r)) - if not arith.is_prime(p): + if not is_prime(p): raise ArithmeticError("p must be a prime") # T_{p^r} := T_p * T_{p^{r-1}} - eps(p)p^{k-1} T_{p^{r-2}}. pow = p**(r - 1) @@ -210,13 +211,13 @@ def _compute_hecke_matrix(self, n, **kwds): if n < 1: raise ValueError("Hecke operator T_%s is not defined." % n) if n == 1: - Mat = matrix_space.MatrixSpace(self.base_ring(), self.rank()) + Mat = MatrixSpace(self.base_ring(), self.rank()) return Mat(1) - if arith.is_prime(n): + if is_prime(n): return self._compute_hecke_matrix_prime(n, **kwds) - F = arith.factor(n) + F = factor(n) if len(F) == 1: # nontrivial prime power case return self._compute_hecke_matrix_prime_power(F[0][0], F[0][1], **kwds) @@ -389,7 +390,7 @@ def is_full_hecke_module(self): # dividing the level verbose("Determining if Hecke module is full.") N = self.level() - for p in arith.prime_divisors(N): + for p in prime_divisors(N): if not self.is_hecke_invariant(p): self._is_full_hecke_module = False return False @@ -420,7 +421,7 @@ def is_hecke_invariant(self, n): sage: [n for n in range(1,12) if S.is_hecke_invariant(n)] [1, 3, 5, 7, 9, 11] """ - if arith.gcd(n, self.level()) == 1: + if gcd(n, self.level()) == 1: return True if self.is_ambient(): return True @@ -684,7 +685,7 @@ def _is_hecke_equivariant_free_module(self, submodule): """ verbose("Determining if free module is Hecke equivariant.") bound = self.hecke_bound() - for p in arith.primes(bound + 1): + for p in primes(bound + 1): try: self.T(p).matrix().restrict(submodule, check=True) except ArithmeticError: @@ -843,8 +844,8 @@ def atkin_lehner_operator(self, d=None): raise ArithmeticError("d (=%s) must be a divisor of the level (=%s)" % (d, self.level())) N = self.level() - for p, e in arith.factor(d): - v = arith.valuation(N, p) + for p, e in factor(d): + v = valuation(N, p) if e < v: d *= p**(v - e) d = int(d) @@ -1004,8 +1005,8 @@ def decomposition(self, bound=None, anemic=True, height_guess=1, while U and p <= bound: verbose(mesg="p=%s" % p, t=time) if anemic: - while arith.GCD(p, self.level()) != 1: - p = arith.next_prime(p) + while GCD(p, self.level()) != 1: + p = next_prime(p) verbose("Decomposition using p=%s" % p) t = T.hecke_operator(p).matrix() Uprime = [] @@ -1027,7 +1028,7 @@ def decomposition(self, bound=None, anemic=True, height_guess=1, else: Uprime.append(W) # end for - p = arith.next_prime(p) + p = next_prime(p) U = Uprime # end while for i in range(len(U)): @@ -1137,7 +1138,7 @@ def dual_eigenvector(self, names='alpha', lift=True, nz=None): f = t.charpoly('x') if f.is_irreducible(): break - p = arith.next_prime(p) + p = next_prime(p) t += random.choice([-2, -1, 1, 2]) * self.dual_hecke_matrix(p) # Write down the eigenvector. @@ -1289,7 +1290,7 @@ def eigenvalue(self, n, name='alpha'): ev = self.__eigenvalues - if n == 1 or arith.is_prime(n): + if n == 1 or is_prime(n): Tn_e = self._eigen_nonzero_element(n) an = self._element_eigenvalue(Tn_e, name=name) _dict_set(ev, n, name, an) @@ -1300,7 +1301,7 @@ def eigenvalue(self, n, name='alpha'): # non-prime n and doing some big sum (i.e., computing T_n(e)). # Also by computing using the recurrence on eigenvalues # we use information about divisors. - F = arith.factor(n) + F = factor(n) prod = None for p, r in F: (p, r) = (int(p), int(r)) @@ -1443,7 +1444,7 @@ def diamond_bracket_matrix(self, d): d = int(d) % self.level() if d not in self._diamond_matrices: if self.character() is not None: - D = matrix_space.MatrixSpace(self.base_ring(), self.rank())(self.character()(d)) + D = MatrixSpace(self.base_ring(), self.rank())(self.character()(d)) else: D = self._compute_diamond_matrix(d) D.set_immutable() diff --git a/src/sage/modular/local_comp/liftings.py b/src/sage/modular/local_comp/liftings.py index e7f93896286..dbe80b59b14 100644 --- a/src/sage/modular/local_comp/liftings.py +++ b/src/sage/modular/local_comp/liftings.py @@ -7,7 +7,7 @@ """ from sage.rings.integer_ring import ZZ -from sage.arith.all import crt, inverse_mod +from sage.arith.misc import crt, inverse_mod from sage.modular.modsym.p1list import lift_to_sl2z diff --git a/src/sage/modular/local_comp/local_comp.py b/src/sage/modular/local_comp/local_comp.py index 131b5cc1a3f..693843b1afe 100644 --- a/src/sage/modular/local_comp/local_comp.py +++ b/src/sage/modular/local_comp/local_comp.py @@ -20,7 +20,10 @@ """ from sage.structure.sage_object import SageObject -from sage.rings.all import ZZ, QQbar, PolynomialRing, polygen +from sage.rings.integer_ring import ZZ +from sage.rings.polynomial.polynomial_ring import polygen +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.qqbar import QQbar from sage.misc.abstract_method import abstract_method from sage.misc.cachefunc import cached_method from sage.misc.verbose import verbose diff --git a/src/sage/modular/local_comp/smoothchar.py b/src/sage/modular/local_comp/smoothchar.py index c195b1aeccd..0e878b99277 100644 --- a/src/sage/modular/local_comp/smoothchar.py +++ b/src/sage/modular/local_comp/smoothchar.py @@ -40,21 +40,30 @@ sage: chi.multiplicative_order() +Infinity """ + import operator -from sage.structure.element import MultiplicativeGroupElement, parent -from sage.structure.parent import Parent -from sage.structure.sequence import Sequence -from sage.structure.richcmp import richcmp_not_equal, richcmp -from sage.rings.all import QQ, ZZ, Zmod, NumberField -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.functions import lcm from sage.arith.misc import crt from sage.categories.groups import Groups from sage.categories.rings import Rings +from sage.misc.abstract_method import abstract_method +from sage.misc.cachefunc import cached_method +from sage.misc.misc_c import prod from sage.misc.mrange import xmrange from sage.misc.verbose import verbose from sage.modular.dirichlet import DirichletGroup +from sage.rings.finite_rings.conway_polynomials import conway_polynomial +from sage.rings.finite_rings.integer_mod_ring import Zmod +from sage.rings.infinity import Infinity +from sage.rings.integer_ring import ZZ +from sage.rings.number_field.number_field import NumberField +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.rational_field import QQ +from sage.structure.element import MultiplicativeGroupElement, parent +from sage.structure.parent import Parent +from sage.structure.richcmp import richcmp_not_equal, richcmp +from sage.structure.sequence import Sequence class SmoothCharacterGeneric(MultiplicativeGroupElement): @@ -168,8 +177,6 @@ def multiplicative_order(self): sage: G.character(0, [1]).multiplicative_order() 1 """ - from sage.arith.all import lcm - from sage.rings.infinity import Infinity if self._values_on_gens[-1].multiplicative_order() == Infinity: return Infinity else: @@ -1468,7 +1475,6 @@ def number_field(self): sage: SmoothCharacterGroupUnramifiedQuadratic(2, QQ, 'c').number_field() Number Field in c with defining polynomial x^2 + x + 1 """ - from sage.rings.all import conway_polynomial, PolynomialRing fbar = conway_polynomial(self.prime(), 2) f = PolynomialRing(QQ, 'x')([a.lift() for a in fbar]) return NumberField(f, self._name) diff --git a/src/sage/modular/local_comp/type_space.py b/src/sage/modular/local_comp/type_space.py index fc2319727d7..87b3996dc04 100644 --- a/src/sage/modular/local_comp/type_space.py +++ b/src/sage/modular/local_comp/type_space.py @@ -15,19 +15,23 @@ """ import operator + +from sage.arith.misc import crt +from sage.matrix.constructor import matrix +from sage.misc.cachefunc import cached_method, cached_function from sage.modular.arithgroup.all import GammaH -from sage.modular.modform.element import Newform from sage.modular.modform.constructor import ModularForms +from sage.modular.modform.element import Newform from sage.modular.modsym.modsym import ModularSymbols -from sage.rings.all import ZZ, Zmod, QQ from sage.rings.fast_arith import prime_range -from sage.arith.all import crt +from sage.rings.finite_rings.integer_mod_ring import Zmod +from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ from sage.structure.sage_object import SageObject -from sage.matrix.constructor import matrix -from sage.misc.cachefunc import cached_method, cached_function from .liftings import lift_gen_to_gamma1, lift_ramified + @cached_function def example_type_space(example_no = 0): r""" diff --git a/src/sage/modular/modform/ambient.py b/src/sage/modular/modform/ambient.py index febb0be8d37..df2bf8b66ab 100644 --- a/src/sage/modular/modform/ambient.py +++ b/src/sage/modular/modform/ambient.py @@ -68,17 +68,16 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -import sage.modular.arithgroup.all as arithgroup -import sage.modular.dirichlet as dirichlet -import sage.modular.hecke.all as hecke -import sage.modular.modsym.all as modsym -import sage.modules.free_module as free_module -import sage.rings.all as rings -from sage.arith.all import is_prime, sigma +from sage.arith.misc import is_prime, sigma from sage.matrix.constructor import matrix - -from sage.structure.sequence import Sequence from sage.misc.cachefunc import cached_method +from sage.modular.arithgroup.all import is_CongruenceSubgroup, is_Gamma0, is_Gamma1 +from sage.modular.dirichlet import TrivialCharacter +from sage.modular.hecke.ambient_module import AmbientHeckeModule +from sage.modular.modsym.modsym import ModularSymbols +from sage.modules.free_module import VectorSpace +from sage.rings.integer import Integer +from sage.structure.sequence import Sequence from . import defaults from . import eisenstein_submodule @@ -88,7 +87,7 @@ class ModularFormsAmbient(space.ModularFormsSpace, - hecke.AmbientHeckeModule): + AmbientHeckeModule): """ An ambient space of modular forms. """ @@ -103,12 +102,12 @@ def __init__(self, group, weight, base_ring, character=None, eis_only=False): sage: m.is_ambient() True """ - if not arithgroup.is_CongruenceSubgroup(group): + if not is_CongruenceSubgroup(group): raise TypeError('group (=%s) must be a congruence subgroup' % group) - weight = rings.Integer(weight) + weight = Integer(weight) - if character is None and arithgroup.is_Gamma0(group): - character = dirichlet.TrivialCharacter(group.level(), base_ring) + if character is None and is_Gamma0(group): + character = TrivialCharacter(group.level(), base_ring) self._eis_only = eis_only space.ModularFormsSpace.__init__(self, group, weight, character, base_ring) @@ -116,7 +115,7 @@ def __init__(self, group, weight, base_ring, character=None, eis_only=False): d = self._dim_eisenstein() else: d = self.dimension() - hecke.AmbientHeckeModule.__init__(self, base_ring, d, group.level(), weight) + AmbientHeckeModule.__init__(self, base_ring, d, group.level(), weight) def _repr_(self): """ @@ -302,7 +301,7 @@ def is_ambient(self): """ return True - @cached_method(key=lambda self, sign: rings.Integer(sign)) # convert sign to an Integer before looking this up in the cache + @cached_method(key=lambda self, sign: Integer(sign)) # convert sign to an Integer before looking this up in the cache def modular_symbols(self, sign=0): """ Return the corresponding space of modular symbols with the given @@ -323,8 +322,8 @@ def modular_symbols(self, sign=0): sage: ModularForms(1,12).modular_symbols() Modular Symbols space of dimension 3 for Gamma_0(1) of weight 12 with sign 0 over Rational Field """ - sign = rings.Integer(sign) - return modsym.ModularSymbols(group=self.group(), + sign = Integer(sign) + return ModularSymbols(group=self.group(), weight=self.weight(), sign=sign, base_ring=self.base_ring()) @@ -344,7 +343,7 @@ def module(self): Vector space of dimension 27 over Finite Field in b of size 7^2 """ d = self.dimension() - return free_module.VectorSpace(self.base_ring(), d) + return VectorSpace(self.base_ring(), d) # free_module -- stupid thing: there are functions in classes # ModularFormsSpace and HeckeModule that both do much the same @@ -428,7 +427,7 @@ def set_precision(self, n): """ if n < 0: raise ValueError("n (=%s) must be >= 0" % n) - self.__prec = rings.Integer(n) + self.__prec = Integer(n) #################################################################### # Computation of Special Submodules @@ -461,7 +460,7 @@ def eisenstein_submodule(self): """ return eisenstein_submodule.EisensteinSubmodule(self) - @cached_method(key=lambda self, p: (rings.Integer(p) if p is not None else p)) # convert p to an Integer before looking this up in the cache + @cached_method(key=lambda self, p: (Integer(p) if p is not None else p)) # convert p to an Integer before looking this up in the cache def new_submodule(self, p=None): """ Return the new or `p`-new submodule of this ambient @@ -512,7 +511,7 @@ def new_submodule(self, p=None): NotImplementedError """ if p is not None: - p = rings.Integer(p) + p = Integer(p) if not p.is_prime(): raise ValueError("p (=%s) must be a prime or None." % p) return self.cuspidal_submodule().new_submodule(p) + self.eisenstein_submodule().new_submodule(p) @@ -584,7 +583,7 @@ def _dim_cuspidal(self): """ if self._eis_only: return 0 - if arithgroup.is_Gamma1(self.group()) and self.character() is not None: + if is_Gamma1(self.group()) and self.character() is not None: return self.group().dimension_cusp_forms(self.weight(), self.character()) else: @@ -615,7 +614,7 @@ def _dim_eisenstein(self): sage: ModularForms(GammaH(40, [21]), 1).dimension() # indirect doctest 16 """ - if arithgroup.is_Gamma1(self.group()) and self.character() is not None: + if is_Gamma1(self.group()) and self.character() is not None: return self.group().dimension_eis(self.weight(), self.character()) else: return self.group().dimension_eis(self.weight()) @@ -637,7 +636,7 @@ def _dim_new_cuspidal(self): sage: m._dim_cuspidal() 22 """ - if arithgroup.is_Gamma1(self.group()) and self.character() is not None: + if is_Gamma1(self.group()) and self.character() is not None: return self.group().dimension_new_cusp_forms(self.weight(), self.character()) else: return self.group().dimension_new_cusp_forms(self.weight()) @@ -663,7 +662,7 @@ def _dim_new_eisenstein(self): sage: m._dim_eisenstein() 8 """ - if arithgroup.is_Gamma0(self.group()) and self.weight() == 2: + if is_Gamma0(self.group()) and self.weight() == 2: if is_prime(self.level()): d = 1 else: @@ -694,7 +693,7 @@ def eisenstein_params(self): """ eps = self.character() if eps is None: - if arithgroup.is_Gamma1(self.group()): + if is_Gamma1(self.group()): eps = self.level() else: raise NotImplementedError diff --git a/src/sage/modular/modform/cuspidal_submodule.py b/src/sage/modular/modform/cuspidal_submodule.py index decc402a839..d40af39c52e 100644 --- a/src/sage/modular/modform/cuspidal_submodule.py +++ b/src/sage/modular/modform/cuspidal_submodule.py @@ -37,10 +37,13 @@ # # http://www.gnu.org/licenses/ ######################################################################### -from sage.rings.all import QQ, Integer -from sage.misc.all import cached_method + +from sage.matrix.constructor import Matrix +from sage.matrix.special import identity_matrix +from sage.misc.cachefunc import cached_method from sage.misc.verbose import verbose -from sage.matrix.all import Matrix, identity_matrix +from sage.rings.integer import Integer +from sage.rings.rational_field import QQ from .submodule import ModularFormsSubmodule from . import vm_basis diff --git a/src/sage/modular/modform/eis_series.py b/src/sage/modular/modform/eis_series.py index e4a8e7c9592..65d43a39fda 100644 --- a/src/sage/modular/modform/eis_series.py +++ b/src/sage/modular/modform/eis_series.py @@ -12,12 +12,17 @@ # https://www.gnu.org/licenses/ # **************************************************************************** +from sage.arith.functions import lcm +from sage.arith.misc import bernoulli, divisors, is_squarefree from sage.misc.misc import cputime -import sage.modular.dirichlet as dirichlet from sage.modular.arithgroup.congroup_gammaH import GammaH_class -from sage.rings.all import Integer, CyclotomicField, ZZ, QQ -from sage.arith.all import bernoulli, divisors, is_squarefree, lcm +from sage.modular.dirichlet import DirichletGroup +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ +from sage.rings.number_field.number_field import CyclotomicField from sage.rings.power_series_ring import PowerSeriesRing +from sage.rings.rational_field import QQ + from .eis_series_cython import eisenstein_series_poly, Ek_ZZ @@ -227,7 +232,7 @@ def __find_eisen_chars(character, k): return V # Now include all pairs (chi,chi^(-1)) such that cond(chi)^2 divides N: # TODO: Optimize -- this is presumably way too hard work below. - G = dirichlet.DirichletGroup(N) + G = DirichletGroup(N) for chi in G: if not chi.is_trivial(): f = chi.conductor() @@ -298,7 +303,7 @@ def __find_eisen_chars_gammaH(N, H, k): [((1, 1), (-1, -1), 1), ((-1, 1), (1, -1), 1), ((1, -1), (-1, 1), 1), ((-1, -1), (1, 1), 1)] """ params = [] - for chi in dirichlet.DirichletGroup(N): + for chi in DirichletGroup(N): if all(chi(h) == 1 for h in H): params += __find_eisen_chars(chi, k) return params @@ -339,7 +344,7 @@ def __find_eisen_chars_gamma1(N, k): """ pairs = [] s = (-1)**k - G = dirichlet.DirichletGroup(N) + G = DirichletGroup(N) E = list(G) parity = [c(-1) for c in E] for i in range(len(E)): diff --git a/src/sage/modular/modform/eis_series_cython.pyx b/src/sage/modular/modform/eis_series_cython.pyx index 3557c6b8dc1..013ae32e62c 100644 --- a/src/sage/modular/modform/eis_series_cython.pyx +++ b/src/sage/modular/modform/eis_series_cython.pyx @@ -5,10 +5,10 @@ Eisenstein Series (optimized compiled functions) from cysignals.memory cimport check_allocarray, sig_free from cysignals.signals cimport sig_check +from sage.arith.misc import primes, bernoulli from sage.rings.rational_field import QQ from sage.rings.power_series_ring import PowerSeriesRing from sage.rings.integer cimport Integer -from sage.arith.all import primes, bernoulli from sage.rings.fast_arith cimport prime_range from cpython.list cimport PyList_GET_ITEM diff --git a/src/sage/modular/modform/eisenstein_submodule.py b/src/sage/modular/modform/eisenstein_submodule.py index 0d606affd6d..17a5c506a81 100644 --- a/src/sage/modular/modform/eisenstein_submodule.py +++ b/src/sage/modular/modform/eisenstein_submodule.py @@ -3,13 +3,14 @@ The Eisenstein Subspace """ -from sage.structure.all import Sequence +from sage.arith.functions import lcm +from sage.arith.misc import euler_phi +from sage.categories.objects import Objects +from sage.matrix.constructor import Matrix from sage.misc.cachefunc import cached_method -import sage.rings.all as rings -from sage.categories.all import Objects -from sage.matrix.all import Matrix -from sage.rings.all import CyclotomicField -from sage.arith.all import lcm, euler_phi +from sage.rings.integer import Integer +from sage.rings.number_field.number_field import CyclotomicField +from sage.structure.sequence import Sequence from . import eis_series from . import element @@ -328,7 +329,7 @@ def _compute_q_expansion_basis(self, prec=None, new=False): if prec is None: prec = self.prec() else: - prec = rings.Integer(prec) + prec = Integer(prec) if new: E = self.new_eisenstein_series() diff --git a/src/sage/modular/modform/element.py b/src/sage/modular/modform/element.py index 90043b2bb12..855a46ad1ef 100644 --- a/src/sage/modular/modform/element.py +++ b/src/sage/modular/modform/element.py @@ -32,29 +32,34 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -from . import defaults - -import sage.modular.hecke.element as element - -from sage.arith.all import lcm, divisors, moebius, sigma, factor, crt +from sage.arith.functions import lcm +from sage.arith.misc import divisors, moebius, sigma, factor, crt from sage.arith.srange import xsrange +from sage.combinat.integer_vector_weighted import WeightedIntegerVectors +from sage.matrix.constructor import Matrix from sage.matrix.constructor import matrix -from sage.misc.misc_c import prod from sage.misc.cachefunc import cached_method +from sage.misc.misc_c import prod from sage.misc.verbose import verbose from sage.modular.dirichlet import DirichletGroup from sage.modular.modsym.modsym import ModularSymbols from sage.modular.modsym.p1list import lift_to_sl2z from sage.modular.modsym.space import is_ModularSymbolsSpace from sage.modules.free_module_element import vector -from sage.rings.all import ZZ, QQ, Integer, RealField, ComplexField, PowerSeriesRing +from sage.rings.complex_mpfr import ComplexField from sage.rings.fast_arith import prime_range +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ from sage.rings.morphism import RingHomomorphism from sage.rings.number_field.number_field_morphisms import NumberFieldEmbedding +from sage.rings.power_series_ring import PowerSeriesRing +from sage.rings.rational_field import QQ +from sage.rings.real_mpfr import RealField from sage.structure.element import coercion_model, ModuleElement, Element from sage.structure.richcmp import richcmp, op_NE, op_EQ -from sage.matrix.constructor import Matrix -from sage.combinat.integer_vector_weighted import WeightedIntegerVectors + +import sage.modular.hecke.element as element +from . import defaults def is_ModularFormElement(x): @@ -129,7 +134,6 @@ def delta_lseries(prec=53, max_imaginary_part=0, raise ValueError('algorithm must be "gp" or "pari"') - class ModularForm_abstract(ModuleElement): """ Constructor for generic class of a modular form. This @@ -186,7 +190,7 @@ def is_homogeneous(self): True """ return True - is_modular_form = is_homogeneous #alias + is_modular_form = is_homogeneous # alias def _repr_(self): """ @@ -333,23 +337,23 @@ def coefficients(self, X): sage: f = EisensteinForms(e, 3).eisenstein_series()[0] sage: f.coefficients([0,1]) [15/11*zeta10^3 - 9/11*zeta10^2 - 26/11*zeta10 - 10/11, - 1] + 1] sage: f.coefficients([0,1,2,3]) [15/11*zeta10^3 - 9/11*zeta10^2 - 26/11*zeta10 - 10/11, - 1, - 4*zeta10 + 1, - -9*zeta10^3 + 1] + 1, + 4*zeta10 + 1, + -9*zeta10^3 + 1] sage: f.coefficients([2,3]) [4*zeta10 + 1, - -9*zeta10^3 + 1] + -9*zeta10^3 + 1] Running this twice once revealed a bug, so we test it:: sage: f.coefficients([0,1,2,3]) [15/11*zeta10^3 - 9/11*zeta10^2 - 26/11*zeta10 - 10/11, - 1, - 4*zeta10 + 1, - -9*zeta10^3 + 1] + 1, + 4*zeta10 + 1, + -9*zeta10^3 + 1] """ try: self.__coefficients @@ -357,11 +361,11 @@ def coefficients(self, X): self.__coefficients = {} if isinstance(X, Integer): X = list(range(1, X + 1)) - Y = [n for n in X if n not in self.__coefficients] + Y = [n for n in X if n not in self.__coefficients] v = self._compute(Y) for i in range(len(v)): self.__coefficients[Y[i]] = v[i] - return [ self.__coefficients[x] for x in X ] + return [self.__coefficients[x] for x in X] def __getitem__(self, n): """ @@ -425,11 +429,12 @@ def padded_list(self, n): EXAMPLES:: sage: CuspForms(1,12).0.padded_list(20) - [0, 1, -24, 252, -1472, 4830, -6048, -16744, 84480, -113643, -115920, 534612, -370944, -577738, 401856, 1217160, 987136, -6905934, 2727432, 10661420] + [0, 1, -24, 252, -1472, 4830, -6048, -16744, 84480, -113643, + -115920, 534612, -370944, -577738, 401856, 1217160, 987136, + -6905934, 2727432, 10661420] """ return self.q_expansion(n).padded_list(n) - def _latex_(self): """ Return the LaTeX expression of self. @@ -468,8 +473,8 @@ def character(self, compute=True): chi = self.parent().character() if (chi is not None) or (not compute): return chi - else: # do the expensive computation - G = DirichletGroup(self.parent().level(), base_ring = self.parent().base_ring()) + else: # do the expensive computation + G = DirichletGroup(self.parent().level(), base_ring=self.parent().base_ring()) gens = G.unit_gens() i = self.valuation() vals = [] @@ -663,10 +668,9 @@ def atkin_lehner_eigenvalue(self, d=None, embedding=None): sage: CuspForms(2, 8).0.atkin_lehner_eigenvalue() Traceback (most recent call last): ... - NotImplementedError: Don't know how to compute Atkin-Lehner matrix acting on this space (try using a newform constructor instead) + NotImplementedError: don't know how to compute Atkin-Lehner matrix acting on this space (try using a newform constructor instead) """ - raise NotImplementedError("Don't know how to compute Atkin-Lehner matrix acting on this space" \ - + " (try using a newform constructor instead)") + raise NotImplementedError("don't know how to compute Atkin-Lehner matrix acting on this space (try using a newform constructor instead)") # The methods period() and lseries() below currently live # in ModularForm_abstract so they are inherited by Newform (which @@ -781,7 +785,7 @@ def period(self, M, prec=53): sage: E.period(gamma) Traceback (most recent call last): ... - NotImplementedError: Don't know how to compute Atkin-Lehner matrix acting on this space (try using a newform constructor instead) + NotImplementedError: don't know how to compute Atkin-Lehner matrix acting on this space (try using a newform constructor instead) sage: E = EllipticCurve('19a1') sage: M = Gamma0(19)([10, 1, 19, 2]) @@ -829,13 +833,12 @@ def period(self, M, prec=53): coeff = self.coefficients(numterms) return sum((coeff[n - 1] / n) - *((eps - 1) * mu_N ** n - + mu_dN ** n * (mu_d ** (n * b) - eps * mu_d ** (n * c))) + * ((eps - 1) * mu_N ** n + + mu_dN ** n * (mu_d ** (n * b) - eps * mu_d ** (n * c))) for n in range(1, numterms + 1)) - def lseries(self, embedding=0, prec=53, - max_imaginary_part=0, - max_asymp_coeffs=40): + def lseries(self, embedding=0, prec=53, max_imaginary_part=0, + max_asymp_coeffs=40): r""" Return the L-series of the weight k cusp form `f` on `\Gamma_0(N)`. @@ -991,14 +994,10 @@ def lseries(self, embedding=0, prec=53, if self.is_cuspidal(): poles = [] # cuspidal else: - poles = [l] # non-cuspidal - - L = Dokchitser(conductor = N, - gammaV = [0, 1], - weight = l, - eps = e, - poles = poles, - prec = prec) + poles = [l] # non-cuspidal + + L = Dokchitser(conductor=N, gammaV=[0, 1], weight=l, eps=e, poles=poles, + prec=prec) # Find out how many coefficients of the Dirichlet series are needed # in order to compute to the required precision num_coeffs = L.num_coeffs() @@ -1017,10 +1016,10 @@ def lseries(self, embedding=0, prec=53, max_asymp_coeffs=max_asymp_coeffs) L.check_functional_equation() if K == QQ: - L.rename('L-series associated to the cusp form %s'%self) + L.rename('L-series associated to the cusp form %s' % self) else: - L.rename('L-series associated to the cusp form %s, %s=%s' \ - % (self, K.variable_name(), emb(K.gen()))) + L.rename('L-series associated to the cusp form %s, %s=%s' + % (self, K.variable_name(), emb(K.gen()))) return L def symsquare_lseries(self, chi=None, embedding=0, prec=53): @@ -1102,7 +1101,7 @@ def symsquare_lseries(self, chi=None, embedding=0, prec=53): else: assert chi.is_primitive() chi = chi.change_ring(C) - eps = chi.gauss_sum()**3 / chi.base_ring()(chi.conductor())**QQ( (3, 2) ) + eps = chi.gauss_sum()**3 / chi.base_ring()(chi.conductor())**QQ((3, 2)) N = chi.conductor()**3 if (chi is None) or chi.is_even(): @@ -1120,13 +1119,13 @@ def symsquare_lseries(self, chi=None, embedding=0, prec=53): # utility function for Dirichlet convolution of series def dirichlet_convolution(A, B): return [sum(A[d-1] * B[n/d - 1] for d in divisors(n)) - for n in range(1, 1 + min(len(A), len(B)))] + for n in range(1, 1 + min(len(A), len(B)))] # The Dirichlet series for \zeta(2 s - 2 k + 2) - riemann_series = [ n**(weight - 1) if n.is_square() else 0 - for n in xsrange(1, lcoeffs_prec + 1) ] + riemann_series = [n**(weight - 1) if n.is_square() else 0 + for n in xsrange(1, lcoeffs_prec + 1)] # The Dirichlet series for 1 / \zeta(s - k + 1) - mu_series = [ moebius(n) * n**(weight - 1) for n in xsrange(1, lcoeffs_prec + 1) ] + mu_series = [moebius(n) * n**(weight - 1) for n in xsrange(1, lcoeffs_prec + 1)] conv_series = dirichlet_convolution(mu_series, riemann_series) dirichlet_series = dirichlet_convolution(conv_series, F_series) @@ -1143,8 +1142,8 @@ def dirichlet_convolution(A, B): pari_precode = "hhh(n) = " + str(dirichlet_series) + "[n] * " + pari_precode_chi - L.init_coeffs( "hhh(k)", w="conj(hhh(k))", - pari_precode=pari_precode) + L.init_coeffs("hhh(k)", w="conj(hhh(k))", + pari_precode=pari_precode) return L @@ -1363,6 +1362,7 @@ def cm_discriminant(self): raise ValueError("Not a CM form") return -self.__cm_char.conductor() + class Newform(ModularForm_abstract): # The reasons why Newform does not inherit from ModularFormElement # should really be documented somewhere. @@ -1406,8 +1406,8 @@ def __init__(self, parent, component, names, check=True): raise ValueError("component must be cuspidal") if not component.is_simple(): raise ValueError("component must be simple") - extension_field = component.eigenvalue(1,name=names).parent() - if extension_field != parent.base_ring(): # .degree() != 1 and rings.is_NumberField(extension_field): + extension_field = component.eigenvalue(1, name=names).parent() + if extension_field != parent.base_ring(): # .degree() != 1 and rings.is_NumberField(extension_field): assert extension_field.base_field() == parent.base_ring() extension_field = parent.base_ring().extension(extension_field.relative_polynomial(), names=names) self.__name = names @@ -1471,12 +1471,10 @@ def __eq__(self, other): True """ - if (not isinstance(other, ModularForm_abstract) - or self.weight() != other.weight()): + if (not isinstance(other, ModularForm_abstract) or self.weight() != other.weight()): return False if isinstance(other, Newform): - if (self.level() != other.level() or - self.character() != other.character()): + if (self.level() != other.level() or self.character() != other.character()): return False # The two parents may have different Sturm bounds in case # one of them is a space of cusp forms with character @@ -1803,9 +1801,9 @@ def _atkin_lehner_eigenvalue_from_qexp(self, Q): l = ZZ(1) M = self.character().conductor() for p, e in Q.factor(): - if p.divides(M): # principal series at p + if p.divides(M): # principal series at p l *= (p**(self.weight() - 2) / self[p])**e - else: # special at p + else: # special at p l *= -self[p] return l @@ -1845,15 +1843,15 @@ def _atkin_lehner_eigenvalue_from_modsym(self, Q): W = self.group().atkin_lehner_matrix(Q) if Q0 == 1: - L = [ W ] + L = [W] else: L = [] for a in xsrange(Q0): if a.gcd(Q0) > 1: continue aa = crt(a, 1, Q, N.prime_to_m_part(Q)) - diam = matrix(ZZ, 2, lift_to_sl2z(0,aa,N) ) - L.append( (W * diam * matrix(QQ, 2, [1,a/Q0,0,1]) ).change_ring(ZZ) ) + diam = matrix(ZZ, 2, lift_to_sl2z(0, aa, N)) + L.append((W * diam * matrix(QQ, 2, [1, a/Q0, 0, 1])).change_ring(ZZ)) W = A._matrix_of_operator_on_modular_symbols(A, [x.list() for x in L]) e = S.dual_eigenvector(names=self._name()) @@ -2152,7 +2150,7 @@ def atkin_lehner_eigenvalue(self, d=None, normalization='analytic', embedding=No G = R(1) if not R(d**(self.weight()-2)).is_square(): raise ValueError("Unable to compute square root. Try specifying an embedding into a larger ring") - ratio = R(d**(self.weight()-2)).sqrt() * embedding(self.character()( crt(1, d//d0, d, N//d) )) / G + ratio = R(d**(self.weight()-2)).sqrt() * embedding(self.character()(crt(1, d//d0, d, N//d))) / G return embedding(w) / ratio def twist(self, chi, level=None, check=True): @@ -2344,7 +2342,7 @@ def minimal_twist(self, p=None): continue try: g = self.twist(chi, level=N//p**(r-c)) - candidates.append( (g, chi) ) + candidates.append((g, chi)) except ValueError: continue @@ -2353,7 +2351,7 @@ def minimal_twist(self, p=None): l = l.next_prime() if l == p: continue - candidates = [(h, chi) for (h, chi) in candidates if h[l] == chi(l)*self[l] ] + 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] @@ -2403,6 +2401,7 @@ def local_component(self, p, twist_factor=None): 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""" @@ -2446,7 +2445,7 @@ def _compute_q_expansion(self, prec): sage: f._compute_q_expansion(10) q - 2*q^2 - 3*q^3 + 2*q^4 - 2*q^5 + 6*q^6 - q^7 + 6*q^9 + O(q^10) """ - return self.parent()._q_expansion(element = self.element(), prec=prec) + return self.parent()._q_expansion(element=self.element(), prec=prec) def _add_(self, other): """ @@ -2588,15 +2587,14 @@ def atkin_lehner_eigenvalue(self, d=None, embedding=None): sage: CuspForms(2, 8).0.atkin_lehner_eigenvalue() Traceback (most recent call last): ... - NotImplementedError: Don't know how to compute Atkin-Lehner matrix acting on this space (try using a newform constructor instead) + NotImplementedError: don't know how to compute Atkin-Lehner matrix acting on this space (try using a newform constructor instead) """ if d is None: d = self.level() try: f = self.parent().atkin_lehner_operator(d)(self) except NotImplementedError: - raise NotImplementedError("Don't know how to compute Atkin-Lehner matrix acting on this space" \ - + " (try using a newform constructor instead)") + raise NotImplementedError("don't know how to compute Atkin-Lehner matrix acting on this space (try using a newform constructor instead)") w = self.element().nonzero_positions()[0] t = f.element()[w] / self.element()[w] if f.element() == self.element() * t: @@ -2689,7 +2687,6 @@ def twist(self, chi, level=None): """ from sage.modular.all import CuspForms, ModularForms - from sage.rings.all import PowerSeriesRing R = coercion_model.common_parent(self.base_ring(), chi.base_ring()) N = self.level() Q = chi.modulus() @@ -2830,7 +2827,7 @@ def atkin_lehner_eigenvalue(self, d=None, embedding=None): else: # The space of modular symbols attached to E is # one-dimensional. - w = self.__E.modular_symbol_space().atkin_lehner_operator(d).matrix()[0,0] + w = self.__E.modular_symbol_space().atkin_lehner_operator(d).matrix()[0, 0] return w @@ -2895,13 +2892,12 @@ def __init__(self, parent, vector, t, chi, psi): if chi.parent().base_ring() != K or psi.parent().base_ring() != K: raise ArithmeticError("Incompatible base rings") t = int(t) - if parent.weight() == 2 and chi.is_trivial() \ - and psi.is_trivial() and t==1: + if parent.weight() == 2 and chi.is_trivial() and psi.is_trivial() and t == 1: raise ArithmeticError("If chi and psi are trivial and k=2, then t must be >1.") ModularFormElement.__init__(self, parent, vector) self.__chi = chi self.__psi = psi - self.__t = t + self.__t = t def _compute_q_expansion(self, prec=None): """ @@ -2936,7 +2932,7 @@ def _compute(self, X): """ if self.weight() == 2 and (self.__chi.is_trivial() and self.__psi.is_trivial()): return self.__compute_weight2_trivial_character(X) - else: # general case + else: # general case return self.__compute_general_case(X) def __compute_weight2_trivial_character(self, X): @@ -3150,6 +3146,7 @@ def new_level(self): return factor(self.__t)[0][0] return self.L() * self.M() + class GradedModularFormElement(ModuleElement): r""" The element class for ``ModularFormsRing``. A ``GradedModularFormElement`` is basically a @@ -3250,13 +3247,13 @@ def __init__(self, parent, forms_datum): if parent.group().is_subgroup(f.group()) and parent.base_ring().has_coerce_map_from(f.base_ring()): forms_dictionary[k] = f else: - raise ValueError('the group and/or the base ring of at least one modular form (%s) is not consistant with the base space'%(f)) + raise ValueError('the group and/or the base ring of at least one modular form (%s) is not consistant with the base space' % (f)) else: - raise ValueError('at least one key (%s) of the defining dictionary does not correspond to the weight of its value (%s). Real weight: %s'%(k, f, f.weight())) + raise ValueError('at least one key (%s) of the defining dictionary does not correspond to the weight of its value (%s). Real weight: %s' % (k, f, f.weight())) else: - raise ValueError('at least one value (%s) of the defining dictionary is not a `ModularFormElement`'%(f)) + raise ValueError('at least one value (%s) of the defining dictionary is not a `ModularFormElement`' % (f)) else: - raise ValueError('at least one key (%s) of the defining dictionary is not an integer'%(k)) + raise ValueError('at least one key (%s) of the defining dictionary is not an integer' % (k)) elif isinstance(forms_datum, list): for f in forms_datum: if is_ModularFormElement(f): @@ -3266,12 +3263,12 @@ def __init__(self, parent, forms_datum): if parent.group().is_subgroup(f.group()) and parent.base_ring().has_coerce_map_from(f.base_ring()): forms_dictionary[f.weight()] = forms_dictionary.get(f.weight(), 0) + f else: - raise ValueError('the group and/or the base ring of at least one modular form (%s) is not consistant with the base space'%(f)) + raise ValueError('the group and/or the base ring of at least one modular form (%s) is not consistant with the base space' % (f)) else: forms_dictionary[ZZ(0)] = parent.base_ring().coerce(f) else: raise TypeError('the defining data structure should be a list or a dictionary') - self._forms_dictionary = {k:f for k,f in forms_dictionary.items() if not f.is_zero()} #remove the zero values + self._forms_dictionary = {k: f for k, f in forms_dictionary.items() if not f.is_zero()} # remove the zero values Element.__init__(self, parent) def __bool__(self): @@ -3371,7 +3368,7 @@ def q_expansion(self, prec=None): Pow = PowerSeriesRing(self.base_ring(), name=defaults.DEFAULT_VARIABLE) return Pow(self._forms_dictionary.get(0, Pow.zero())) + sum(f.q_expansion(prec) for k, f in self._forms_dictionary.items() if k != 0) - qexp = q_expansion #alias + qexp = q_expansion # alias def _repr_(self): r""" @@ -3431,7 +3428,7 @@ def __getitem__(self, weight): if weight < 0: raise ValueError("the weight must be non-negative") return self._forms_dictionary.get(weight, self.parent().zero()) - homogeneous_component = __getitem__ #alias + homogeneous_component = __getitem__ # alias def __call__(self, x, prec=None): r""" @@ -3478,7 +3475,7 @@ def _add_(self, other): GM = self.__class__ f_self = self._forms_dictionary f_other = other._forms_dictionary - f_sum = { k : f_self.get(k, 0) + f_other.get(k, 0) for k in set(f_self) | set(f_other)} + f_sum = {k: f_self.get(k, 0) + f_other.get(k, 0) for k in set(f_self) | set(f_other)} return GM(self.parent(), f_sum) def __neg__(self): @@ -3496,7 +3493,7 @@ def __neg__(self): """ GM = self.__class__ f_self = self._forms_dictionary - minus_self = {k:-f for k,f in f_self.items()} + minus_self = {k: -f for k, f in f_self.items()} return GM(self.parent(), minus_self) def _mul_(self, other): @@ -3553,7 +3550,7 @@ def _lmul_(self, c): """ GM = self.__class__ f_self = self._forms_dictionary - f_mul = {k:c*f for k,f in f_self.items()} + f_mul = {k: c*f for k, f in f_self.items()} return GM(self.parent(), f_mul) def _richcmp_(self, other, op): @@ -3653,7 +3650,7 @@ def is_homogeneous(self): False """ return len(self._forms_dictionary) <= 1 - is_modular_form = is_homogeneous #alias + is_modular_form = is_homogeneous # alias def _homogeneous_to_polynomial(self, names, gens): r""" @@ -3697,7 +3694,7 @@ def _homogeneous_to_polynomial(self, names, gens): if not self.base_ring() == QQ: raise NotImplementedError("conversion to polynomial are not implemented if the base ring is not Q") M = self.parent() - k = self.weight() #only if self is homogeneous + k = self.weight() # only if self is homogeneous poly_parent = M.polynomial_ring(names, gens) if k == 0: return poly_parent(self[k]) diff --git a/src/sage/modular/modform/half_integral.py b/src/sage/modular/modform/half_integral.py index 3bef523c33b..432b4318046 100644 --- a/src/sage/modular/modform/half_integral.py +++ b/src/sage/modular/modform/half_integral.py @@ -8,7 +8,7 @@ - William Stein (2007-08) """ -from sage.matrix.all import MatrixSpace +from sage.matrix.matrix_space import MatrixSpace from sage.modular.dirichlet import DirichletGroup from . import constructor diff --git a/src/sage/modular/modform/hecke_operator_on_qexp.py b/src/sage/modular/modform/hecke_operator_on_qexp.py index c6ab38d0905..6a61cbb46f2 100644 --- a/src/sage/modular/modform/hecke_operator_on_qexp.py +++ b/src/sage/modular/modform/hecke_operator_on_qexp.py @@ -12,13 +12,16 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -from sage.modular.dirichlet import DirichletGroup, is_DirichletCharacter -from sage.rings.all import ZZ, Integer, Infinity, CyclotomicField -from sage.arith.all import divisors, gcd - +from sage.arith.misc import divisors, gcd +from sage.matrix.constructor import matrix +from sage.matrix.matrix_space import MatrixSpace +from sage.rings.infinity import Infinity +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ +from sage.rings.number_field.number_field import CyclotomicField from sage.rings.power_series_ring_element import is_PowerSeries -from sage.matrix.all import matrix, MatrixSpace +from sage.modular.dirichlet import DirichletGroup, is_DirichletCharacter from .element import is_ModularFormElement def hecke_operator_on_qexp(f, n, k, eps = None, diff --git a/src/sage/modular/modform/l_series_gross_zagier_coeffs.pyx b/src/sage/modular/modform/l_series_gross_zagier_coeffs.pyx index 25d36c8fd52..3246146c9a0 100644 --- a/src/sage/modular/modform/l_series_gross_zagier_coeffs.pyx +++ b/src/sage/modular/modform/l_series_gross_zagier_coeffs.pyx @@ -4,8 +4,9 @@ from cysignals.signals cimport sig_check, sig_on, sig_off from sage.rings.fast_arith cimport arith_llong cdef arith_llong arith = arith_llong() -from sage.rings.all import ZZ, PowerSeriesRing -from sage.arith.all import kronecker_symbol +from sage.arith.misc import kronecker_symbol +from sage.rings.integer_ring import ZZ +from sage.rings.power_series_ring import PowerSeriesRing from libc.math cimport ceil, floor, sqrt from libc.string cimport memcpy diff --git a/src/sage/modular/modform/numerical.py b/src/sage/modular/modform/numerical.py index 7b38100be5b..1f7e8decd55 100644 --- a/src/sage/modular/modform/numerical.py +++ b/src/sage/modular/modform/numerical.py @@ -12,7 +12,7 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -from sage.arith.all import prime_range +from sage.rings.fast_arith import prime_range from sage.matrix.constructor import matrix from sage.misc.verbose import verbose from sage.misc.cachefunc import cached_method @@ -20,7 +20,9 @@ from sage.modular.arithgroup.all import Gamma0 from sage.modular.modsym.all import ModularSymbols from sage.modules.all import vector -from sage.rings.all import CDF, Integer, QQ +from sage.rings.complex_double import CDF +from sage.rings.integer import Integer +from sage.rings.rational_field import QQ from sage.structure.richcmp import richcmp_method, richcmp from sage.structure.sage_object import SageObject from sage.structure.sequence import Sequence diff --git a/src/sage/modular/modform/ring.py b/src/sage/modular/modform/ring.py index db1c76ab382..4c8865fd745 100644 --- a/src/sage/modular/modform/ring.py +++ b/src/sage/modular/modform/ring.py @@ -20,27 +20,28 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -from sage.structure.richcmp import richcmp_method, richcmp -from sage.rings.all import Integer, QQ, ZZ +from random import shuffle + +from sage.categories.graded_algebras import GradedAlgebras +from sage.misc.cachefunc import cached_method from sage.misc.misc_c import prod +from sage.misc.superseded import deprecated_function_alias from sage.misc.verbose import verbose -from sage.misc.cachefunc import cached_method from sage.modular.arithgroup.all import Gamma0, is_CongruenceSubgroup -from .constructor import ModularForms -from .element import is_ModularFormElement, GradedModularFormElement -from .space import is_ModularFormsSpace -from random import shuffle - +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ from sage.rings.polynomial.multi_polynomial import MPolynomial from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.polynomial.term_order import TermOrder from sage.rings.power_series_poly import PowerSeries_poly - +from sage.rings.rational_field import QQ from sage.structure.parent import Parent +from sage.structure.richcmp import richcmp_method, richcmp -from sage.categories.graded_algebras import GradedAlgebras +from .constructor import ModularForms +from .element import is_ModularFormElement, GradedModularFormElement +from .space import is_ModularFormsSpace -from sage.misc.superseded import deprecated_function_alias def _span_of_forms_in_weight(forms, weight, prec, stop_dim=None, use_random=False): r""" diff --git a/src/sage/modular/modform/space.py b/src/sage/modular/modform/space.py index 42ed9a9072e..5dfd3f568d8 100644 --- a/src/sage/modular/modform/space.py +++ b/src/sage/modular/modform/space.py @@ -57,27 +57,30 @@ # http://www.gnu.org/licenses/ ######################################################################### -from sage.structure.all import Sequence -from sage.structure.richcmp import (richcmp_method, richcmp, rich_to_bool, - richcmp_not_equal) +from sage.arith.misc import gcd +from sage.matrix.constructor import zero_matrix from sage.misc.cachefunc import cached_method import sage.modular.hecke.all as hecke import sage.modular.arithgroup.all as arithgroup import sage.modular.dirichlet as dirichlet -import sage.rings.all as rings +from sage.rings.infinity import PlusInfinity +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ +from sage.rings.power_series_ring import PowerSeriesRing from sage.rings.power_series_ring_element import is_PowerSeries +from sage.rings.rational_field import QQ +from sage.rings.ring import Ring + +from sage.structure.all import Sequence +from sage.structure.richcmp import (richcmp_method, richcmp, rich_to_bool, + richcmp_not_equal) from .element import ModularFormElement, Newform from . import defaults from . import hecke_operator_on_qexp -from sage.matrix.constructor import zero_matrix -from sage.arith.all import gcd -from sage.rings.infinity import PlusInfinity -from sage.rings.integer import Integer - WARN=False def is_ModularFormsSpace(x): @@ -140,7 +143,7 @@ def __init__(self, group, weight, character, base_ring, category=None): weight = Integer(weight) if not ((character is None) or isinstance(character, dirichlet.DirichletCharacter)): raise TypeError("character must be a Dirichlet character") - if not isinstance(base_ring, rings.Ring): + if not isinstance(base_ring, Ring): raise TypeError("base_ring must be a ring") self.__sturm_bound = None self.__weight, self.__group, self.__character = weight, group, character @@ -399,7 +402,7 @@ def __normalize_prec(self, prec): if prec is None: prec = self.prec() else: - prec = rings.Integer(prec) + prec = Integer(prec) if prec < 0: raise ValueError("prec (=%s) must be at least 0"%prec) return prec @@ -689,7 +692,7 @@ def q_expansion_basis(self, prec=None): pass prec = -1 # big enough to determine forms else: - prec = rings.Integer(self.__normalize_prec(prec)) + prec = Integer(self.__normalize_prec(prec)) if prec == 0: z = self._q_expansion_ring()(0,prec) @@ -807,10 +810,10 @@ def q_integral_basis(self, prec=None): q - 2*q^2 - q^3 + 2*q^4 + O(q^5) ] """ - if not self.base_ring() == rings.QQ: + if not self.base_ring() == QQ: raise TypeError("the base ring must be Q") prec = self.__normalize_prec(prec) - R = rings.PowerSeriesRing(rings.ZZ, name=defaults.DEFAULT_VARIABLE) + R = PowerSeriesRing(ZZ, name=defaults.DEFAULT_VARIABLE) if prec == 0: z = R(0,prec) return Sequence([z]*int(self.dimension()), cr=True) @@ -827,7 +830,7 @@ def q_integral_basis(self, prec=None): B = self.q_expansion_basis(prec) # It's over Q; we just need to intersect it with ZZ^n. - A = rings.ZZ**prec + A = ZZ**prec gens = [f.padded_list(prec) for f in B] C = A.span(gens) D = C.saturation() @@ -849,7 +852,7 @@ def _q_expansion_ring(self): sage: M._q_expansion_ring() Power Series Ring in q over Rational Field """ - return rings.PowerSeriesRing(self.base_ring(), name=defaults.DEFAULT_VARIABLE) + return PowerSeriesRing(self.base_ring(), name=defaults.DEFAULT_VARIABLE) @cached_method def _q_expansion_zero(self): diff --git a/src/sage/modular/modform/theta.py b/src/sage/modular/modform/theta.py index e41532e2da2..9ebad5cfabc 100644 --- a/src/sage/modular/modform/theta.py +++ b/src/sage/modular/modform/theta.py @@ -5,7 +5,9 @@ William Stein """ -from sage.rings.all import Integer, ZZ, PowerSeriesRing +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ +from sage.rings.power_series_ring import PowerSeriesRing from math import sqrt diff --git a/src/sage/modular/modform/vm_basis.py b/src/sage/modular/modform/vm_basis.py index 4bd9e1921df..be690b7b503 100644 --- a/src/sage/modular/modform/vm_basis.py +++ b/src/sage/modular/modform/vm_basis.py @@ -26,16 +26,23 @@ # the License, or (at your option) any later version. # http://www.gnu.org/licenses/ #***************************************************************************** + import math -from sage.rings.all import QQ, ZZ, Integer, \ - PolynomialRing, PowerSeriesRing, O as bigO -from sage.structure.all import Sequence from sage.libs.flint.fmpz_poly import Fmpz_poly from sage.misc.verbose import verbose +from sage.rings.big_oh import O as bigO +from sage.rings.finite_rings.integer_mod_ring import Integers +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.power_series_ring import PowerSeriesRing +from sage.rings.rational_field import QQ +from sage.structure.all import Sequence from .eis_series_cython import eisenstein_series_poly + def victor_miller_basis(k, prec=10, cusp_only=False, var='q'): r""" Compute and return the Victor Miller basis for modular forms of @@ -303,8 +310,6 @@ def _delta_poly_modulo(N, prec=10): for n in range(stop+1): v[n*(n+1)//2] = ((N-1)*(2*n+1) if (n & 1) else (2*n+1)) - from sage.rings.all import Integers - P = PolynomialRing(Integers(N), 'q') f = P(v) t = verbose('made series') diff --git a/src/sage/modular/modform/weight1.py b/src/sage/modular/modform/weight1.py index 14b2fa4f85d..b7a2caa7a41 100644 --- a/src/sage/modular/modform/weight1.py +++ b/src/sage/modular/modform/weight1.py @@ -12,7 +12,8 @@ """ from sage.misc.cachefunc import cached_function -from sage.rings.all import PowerSeriesRing, ZZ +from sage.rings.integer_ring import ZZ +from sage.rings.power_series_ring import PowerSeriesRing from sage.misc.verbose import verbose from sage.structure.sequence import Sequence from sage.modular.arithgroup.all import Gamma0, GammaH diff --git a/src/sage/modular/modform_hecketriangle/abstract_ring.py b/src/sage/modular/modform_hecketriangle/abstract_ring.py index 52922f6a1f2..5a9b0e86258 100644 --- a/src/sage/modular/modform_hecketriangle/abstract_ring.py +++ b/src/sage/modular/modform_hecketriangle/abstract_ring.py @@ -16,14 +16,15 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +from sage.algebras.free_algebra import FreeAlgebra +from sage.misc.cachefunc import cached_method +from sage.rings.fraction_field import FractionField from sage.rings.infinity import infinity from sage.rings.integer_ring import ZZ +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.power_series_ring import PowerSeriesRing from sage.rings.rational_field import QQ -from sage.rings.all import FractionField, PolynomialRing, PowerSeriesRing -from sage.algebras.free_algebra import FreeAlgebra - from sage.structure.parent import Parent -from sage.misc.cachefunc import cached_method from .constructor import FormsRing, FormsSpace from .series_constructor import MFSeriesConstructor diff --git a/src/sage/modular/modform_hecketriangle/abstract_space.py b/src/sage/modular/modform_hecketriangle/abstract_space.py index 20455acff34..e7c59012429 100644 --- a/src/sage/modular/modform_hecketriangle/abstract_space.py +++ b/src/sage/modular/modform_hecketriangle/abstract_space.py @@ -16,20 +16,20 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -from sage.rings.integer_ring import ZZ -from sage.rings.rational_field import QQ -from sage.rings.infinity import infinity -from sage.rings.all import AlgebraicField, I -from sage.rings.polynomial.polynomial_ring import is_PolynomialRing -from sage.rings.power_series_ring import is_PowerSeriesRing -from sage.rings.laurent_series_ring import is_LaurentSeriesRing -from sage.modules.free_module_element import is_FreeModuleElement from sage.matrix.constructor import matrix +from sage.misc.cachefunc import cached_method +from sage.modules.free_module_element import is_FreeModuleElement from sage.modules.free_module_element import vector +from sage.rings.imaginary_unit import I +from sage.rings.infinity import infinity from sage.rings.integer import Integer -from sage.structure.all import parent - -from sage.misc.cachefunc import cached_method +from sage.rings.integer_ring import ZZ +from sage.rings.laurent_series_ring import is_LaurentSeriesRing +from sage.rings.polynomial.polynomial_ring import is_PolynomialRing +from sage.rings.power_series_ring import is_PowerSeriesRing +from sage.rings.qqbar import AlgebraicField +from sage.rings.rational_field import QQ +from sage.structure.element import parent from .abstract_ring import FormsRing_abstract @@ -2276,8 +2276,8 @@ def rationalize_series(self, laurent_series, coeff_bound = 1e-10, denom_factor = True """ - from sage.rings.all import prime_range from sage.misc.misc_c import prod + from sage.rings.fast_arith import prime_range from warnings import warn denom_factor = ZZ(denom_factor) diff --git a/src/sage/modular/modform_hecketriangle/graded_ring.py b/src/sage/modular/modform_hecketriangle/graded_ring.py index 13d00dbbef6..6da06db1b63 100644 --- a/src/sage/modular/modform_hecketriangle/graded_ring.py +++ b/src/sage/modular/modform_hecketriangle/graded_ring.py @@ -20,7 +20,7 @@ from sage.rings.infinity import infinity from sage.rings.ring import CommutativeAlgebra -from sage.categories.all import CommutativeAlgebras +from sage.categories.commutative_algebras import CommutativeAlgebras from sage.structure.unique_representation import UniqueRepresentation from .hecke_triangle_groups import HeckeTriangleGroup diff --git a/src/sage/modular/modform_hecketriangle/graded_ring_element.py b/src/sage/modular/modform_hecketriangle/graded_ring_element.py index 233fbc7fa75..d7b3a5094b6 100644 --- a/src/sage/modular/modform_hecketriangle/graded_ring_element.py +++ b/src/sage/modular/modform_hecketriangle/graded_ring_element.py @@ -16,23 +16,21 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -from sage.rings.integer_ring import ZZ +from sage.functions.log import exp +from sage.geometry.hyperbolic_space.hyperbolic_interface import HyperbolicPlane +from sage.misc.cachefunc import cached_method +from sage.misc.inherit_comparison import InheritComparisonClasscallMetaclass +from sage.modules.free_module_element import vector +from sage.rings.big_oh import O from sage.rings.infinity import infinity -from sage.rings.all import LaurentSeries, O -from sage.functions.all import exp +from sage.rings.integer_ring import ZZ +from sage.rings.laurent_series_ring_element import LaurentSeries from sage.rings.number_field.number_field import QuadraticField -from sage.symbolic.all import pi - +from sage.structure.element import CommutativeAlgebraElement from sage.structure.parent_gens import localvars from sage.structure.richcmp import op_NE, op_EQ -from sage.structure.element import CommutativeAlgebraElement from sage.structure.unique_representation import UniqueRepresentation - -from sage.modules.free_module_element import vector -from sage.geometry.hyperbolic_space.hyperbolic_interface import HyperbolicPlane - -from sage.misc.cachefunc import cached_method -from sage.misc.inherit_comparison import InheritComparisonClasscallMetaclass +from sage.symbolic.constants import pi from .constructor import rational_type, FormsSpace, FormsRing from .series_constructor import MFSeriesConstructor diff --git a/src/sage/modular/modform_hecketriangle/hecke_triangle_groups.py b/src/sage/modular/modform_hecketriangle/hecke_triangle_groups.py index f1783ed9457..c1fc60f724c 100644 --- a/src/sage/modular/modform_hecketriangle/hecke_triangle_groups.py +++ b/src/sage/modular/modform_hecketriangle/hecke_triangle_groups.py @@ -16,20 +16,24 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -from sage.rings.integer_ring import ZZ -from sage.rings.rational_field import QQ -from sage.rings.infinity import infinity -from sage.rings.all import AA, AlgebraicField, I, PolynomialRing, NumberField -from sage.functions.all import cos, exp, sec +from sage.arith.misc import divisors from sage.functions.gamma import psi1 -from sage.symbolic.all import pi +from sage.functions.log import exp +from sage.functions.trig import cos, sec +from sage.groups.matrix_gps.finitely_generated import FinitelyGeneratedMatrixGroup_generic from sage.matrix.constructor import matrix +from sage.misc.cachefunc import cached_method from sage.misc.latex import latex from sage.misc.misc_c import prod - -from sage.groups.matrix_gps.finitely_generated import FinitelyGeneratedMatrixGroup_generic +from sage.rings.imaginary_unit import I +from sage.rings.infinity import infinity +from sage.rings.integer_ring import ZZ +from sage.rings.number_field.number_field import NumberField +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.qqbar import AA, AlgebraicField +from sage.rings.rational_field import QQ from sage.structure.unique_representation import UniqueRepresentation -from sage.misc.cachefunc import cached_method +from sage.symbolic.constants import pi from .hecke_triangle_group_element import HeckeTriangleGroupElement, cyclic_representative, coerce_AA @@ -943,7 +947,6 @@ def _conjugacy_representatives(self, max_block_length=ZZ(0), D=None): from sage.combinat.partition import OrderedPartitions from sage.combinat.combinat import tuples - from sage.arith.all import divisors if D is not None: max_block_length = max(coerce_AA(0), coerce_AA((D + 4)/(self.lam()**2))).sqrt().floor() diff --git a/src/sage/modular/modform_hecketriangle/series_constructor.py b/src/sage/modular/modform_hecketriangle/series_constructor.py index 4181a802bbe..3b698833b2b 100644 --- a/src/sage/modular/modform_hecketriangle/series_constructor.py +++ b/src/sage/modular/modform_hecketriangle/series_constructor.py @@ -20,16 +20,15 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +from sage.arith.misc import bernoulli, sigma, rising_factorial +from sage.misc.cachefunc import cached_method +from sage.rings.big_oh import O +from sage.rings.infinity import infinity from sage.rings.integer_ring import ZZ +from sage.rings.power_series_ring import PowerSeriesRing from sage.rings.rational_field import QQ -from sage.rings.infinity import infinity -from sage.rings.all import PowerSeriesRing -from sage.rings.big_oh import O -from sage.arith.all import bernoulli, sigma, rising_factorial - from sage.structure.sage_object import SageObject from sage.structure.unique_representation import UniqueRepresentation -from sage.misc.cachefunc import cached_method from .hecke_triangle_groups import HeckeTriangleGroup diff --git a/src/sage/modular/modsym/ambient.py b/src/sage/modular/modsym/ambient.py index 5022b0fc07e..6408405d520 100644 --- a/src/sage/modular/modsym/ambient.py +++ b/src/sage/modular/modsym/ambient.py @@ -71,23 +71,19 @@ class ``ModularSymbolsAmbient``, derived from # # https://www.gnu.org/licenses/ ################################################################################ -# Sage packages + +import sage.modular.arithgroup.all as arithgroup + +from sage.arith.misc import is_prime, divisors, number_of_divisors, crt +from sage.categories.homset import Hom +from sage.matrix.matrix_space import MatrixSpace from sage.misc.cachefunc import cached_method -import sage.misc.latex as latex +from sage.misc.latex import latex from sage.misc.verbose import verbose - -import sage.matrix.matrix_space as matrix_space from sage.modular.arithgroup.arithgroup_element import M2Z -import sage.modules.free_module_element as free_module_element -import sage.modules.free_module as free_module -import sage.modular.arithgroup.all as arithgroup -import sage.modular.dirichlet as dirichlet -import sage.modular.hecke.all as hecke -from sage.rings.all import Integer, QQ, ZZ, Ring -from sage.arith.all import is_prime, divisors, number_of_divisors, crt -import sage.rings.polynomial.multi_polynomial_element -import sage.structure.formal_sum as formal_sum -import sage.categories.all as cat +from sage.modular.arithgroup.congroup_generic import is_CongruenceSubgroup +from sage.modular.dirichlet import TrivialCharacter, is_DirichletCharacter +from sage.modular.hecke.ambient_module import AmbientHeckeModule from sage.modular.cusps import Cusp from sage.modular.modsym.apply import apply_to_monomial from sage.modular.modsym.manin_symbol import ManinSymbol @@ -95,7 +91,15 @@ class ``ModularSymbolsAmbient``, derived from ManinSymbolList_gamma1, ManinSymbolList_gamma_h, ManinSymbolList_character) - +from sage.modules.free_module import is_FreeModule +from sage.modules.free_module_element import FreeModuleElement +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ +from sage.rings.polynomial.multi_polynomial import is_MPolynomial +from sage.rings.rational_field import QQ +from sage.rings.ring import Ring +from sage.structure.factorization import Factorization +from sage.structure.formal_sum import FormalSum from . import boundary from . import element @@ -108,7 +112,7 @@ class ``ModularSymbolsAmbient``, derived from from . import subspace -class ModularSymbolsAmbient(ModularSymbolsSpace, hecke.AmbientHeckeModule): +class ModularSymbolsAmbient(ModularSymbolsSpace, AmbientHeckeModule): r""" An ambient space of modular symbols for a congruence subgroup of `SL_2(\ZZ)`. @@ -171,7 +175,7 @@ def __init__(self, group, weight, sign, base_ring, raise TypeError("base_ring must be a commutative ring") if character is None and arithgroup.is_Gamma0(group): - character = dirichlet.TrivialCharacter(group.level(), base_ring) + character = TrivialCharacter(group.level(), base_ring) ModularSymbolsSpace.__init__(self, group, weight, character, sign, base_ring, @@ -192,7 +196,7 @@ def __init__(self, group, weight, sign, base_ring, "ModularSymbolsAmbient: group = %s, weight = %s, sign = %s, base_ring = %s, character = %s"%( group, weight, sign, base_ring, character) - hecke.AmbientHeckeModule.__init__(self, base_ring, rank, group.level(), weight, category=category) + AmbientHeckeModule.__init__(self, base_ring, rank, group.level(), weight, category=category) def new_submodule(self, p=None): r""" @@ -220,7 +224,7 @@ def new_submodule(self, p=None): # If not in one of those cases, use the generic code. if self.level().is_prime() and self.weight() == 2: return self - return hecke.AmbientHeckeModule.new_submodule(self, p=p) + return AmbientHeckeModule.new_submodule(self, p=p) def manin_symbols(self): """ @@ -444,7 +448,7 @@ def _element_constructor_(self, x, computed_with_hecke=False): """ - if isinstance(x, free_module_element.FreeModuleElement): + if isinstance(x, FreeModuleElement): if x.degree() != self.dimension(): raise TypeError("Incompatible degrees: x has degree %s\ but modular symbols space has dimension %s"%( @@ -466,11 +470,11 @@ def _element_constructor_(self, x, computed_with_hecke=False): elif isinstance(x, tuple): return self.manin_symbol(x) - elif isinstance(x, formal_sum.FormalSum): + elif isinstance(x, FormalSum): return sum([c*self(y) for c, y in x], self(0)) elif isinstance(x, list): - if len(x) == 3 and sage.rings.polynomial.multi_polynomial_element.is_MPolynomial(x[0]): + if len(x) == 3 and is_MPolynomial(x[0]): return self.modular_symbol_sum(x) else: return self.modular_symbol(x) @@ -1067,7 +1071,7 @@ def __heilbronn_operator(self, M, H, t=1): """ - MS = matrix_space.MatrixSpace(self.base_ring(), self.dimension(), M.dimension()) + MS = MatrixSpace(self.base_ring(), self.dimension(), M.dimension()) hom = self.Hom(M) if self.dimension() == 0 or M.dimension() == 0: A = MS(0) @@ -1145,9 +1149,9 @@ def _latex_(self): """ return "\\mathrm{ModSym}_{%s}(%s,%s;%s)"%(self.weight(), - latex.latex(self.group()), - latex.latex(list(self.character().values_on_gens())), - latex.latex(self.base_ring())) + latex(self.group()), + latex(list(self.character().values_on_gens())), + latex(self.base_ring())) def _matrix_of_operator_on_modular_symbols(self, codomain, R): r""" @@ -1193,14 +1197,14 @@ def _matrix_of_operator_on_modular_symbols(self, codomain, R): """ rows = [] for b in self.basis(): - v = formal_sum.FormalSum(0, check=False) + v = FormalSum(0, check=False) for c, x in b.modular_symbol_rep(): for g in R: y = x.apply(g) v += y*c w = codomain(v).element() rows.append(w) - M = matrix_space.MatrixSpace(self.base_ring(), len(rows), codomain.degree(), sparse=False) + M = MatrixSpace(self.base_ring(), len(rows), codomain.degree(), sparse=False) return M(rows) def _compute_atkin_lehner_matrix(self, d): @@ -1324,7 +1328,7 @@ def boundary_map(self): # compute boundary map B = self.boundary_space() I = [B(b) for b in self.basis()] - W = matrix_space.MatrixSpace(self.base_ring(), len(I), B.rank(), sparse=True) + W = MatrixSpace(self.base_ring(), len(I), B.rank(), sparse=True) # Note -- the underlying elements have degree the number of distinct # cusps known when the element was computed. This isn't constant, @@ -1335,7 +1339,7 @@ def boundary_map(self): E = sum([ list(x) + [zero]*(n - len(x)) for x in E ], []) A = W( E ) - H = cat.Hom(self, B) + H = Hom(self, B) self.__boundary_map = H(A, "boundary map") return self.__boundary_map @@ -1800,7 +1804,7 @@ def factorization(self): r = self.dimension() s = sum(A.rank() * mult for A, mult in D) - D = sage.structure.all.Factorization(D, cr=True, sort=False) + D = Factorization(D, cr=True, sort=False) D.sort() assert r == s, "bug in factorization -- self has dimension %s, but sum of dimensions of factors is %s\n%s" % (r, s, D) self._factorization = D @@ -2144,7 +2148,7 @@ def submodule(self, M, dual_free_module=None, check=True): Stein, 2007-07-27 """ if check: - if not free_module.is_FreeModule(M): + if not is_FreeModule(M): V = self.free_module() if not isinstance(M, (list,tuple)): M = M.gens() @@ -2182,7 +2186,7 @@ def twisted_winding_element(self, i, eps): sage: M.twisted_winding_element(0,eps) 2*(1,23) - 2*(1,32) + 2*(1,34) """ - if not dirichlet.is_DirichletCharacter(eps): + if not is_DirichletCharacter(eps): raise TypeError("eps must be a Dirichlet character.") if (i < 0) or (i > self.weight() - 2): raise ValueError("i must be between 0 and k-2.") @@ -2714,7 +2718,7 @@ def _degeneracy_raising_matrix_1(self, M): # 2. The map is # [P,pi(g)] |--> sum_{h in H} [P, pi(h*g)] # - MS = matrix_space.MatrixSpace(self.base_ring(), self.dimension(), M.dimension()) + MS = MatrixSpace(self.base_ring(), self.dimension(), M.dimension()) if self.dimension() == 0 or M.dimension() == 0: return MS(0) rows = [] @@ -3322,13 +3326,13 @@ def _degeneracy_raising_matrix_1(self, M): # 2. The map is # [P,pi(g)] |--> sum_{h in H} [P, pi(h*g)] # - MS = matrix_space.MatrixSpace(self.base_ring(), self.dimension(), M.dimension()) + MS = MatrixSpace(self.base_ring(), self.dimension(), M.dimension()) if self.dimension() == 0 or M.dimension() == 0: return MS(0) rows = [] B = self.manin_basis() syms = self.manin_symbols() - G = matrix_space.MatrixSpace(ZZ, 2) + G = MatrixSpace(ZZ, 2) H = [G(h) for h in H] for n in B: z = M(0) @@ -3686,7 +3690,7 @@ def _matrix_of_operator_on_modular_symbols(self, codomain, R, character_twist=Fa eps = self.character() rows = [] for b in self.basis(): - v = formal_sum.FormalSum(0, check=False) + v = FormalSum(0, check=False) for c, x in b.modular_symbol_rep(): for g in R: y = x.apply(g) @@ -3696,7 +3700,7 @@ def _matrix_of_operator_on_modular_symbols(self, codomain, R, character_twist=Fa v += y*c w = codomain(v).element() rows.append(w) - M = matrix_space.MatrixSpace(self.base_ring(), len(rows), codomain.degree(), sparse=False) + M = MatrixSpace(self.base_ring(), len(rows), codomain.degree(), sparse=False) return M(rows) def _degeneracy_raising_matrix_1(self, M): @@ -3734,13 +3738,13 @@ def _degeneracy_raising_matrix_1(self, M): # 2. The map is # [P,pi(g)] |--> sum_{h in H} [P, pi(h*g)] # - MS = matrix_space.MatrixSpace(self.base_ring(), self.dimension(), M.dimension()) + MS = MatrixSpace(self.base_ring(), self.dimension(), M.dimension()) if self.dimension() == 0 or M.dimension() == 0: return MS(0) rows = [] B = self.manin_basis() syms = self.manin_symbols() - G = matrix_space.MatrixSpace(ZZ, 2) + G = MatrixSpace(ZZ, 2) H = [G(h) for h in H] eps = self.character() # note: in my thesis I twisted by eps^(-1), which is definitely a mistake # since twisting by eps gives the right answer and by eps^(-1) does not. diff --git a/src/sage/modular/modsym/g1list.py b/src/sage/modular/modsym/g1list.py index b1ccf4f1cf1..87e86e94814 100644 --- a/src/sage/modular/modsym/g1list.py +++ b/src/sage/modular/modsym/g1list.py @@ -18,7 +18,8 @@ # # http://www.gnu.org/licenses/ #***************************************************************************** -from sage.arith.all import GCD + +from sage.arith.misc import GCD from sage.structure.richcmp import richcmp_method, richcmp from sage.structure.sage_object import SageObject from sage.misc.persist import register_unpickle_override diff --git a/src/sage/modular/modsym/hecke_operator.py b/src/sage/modular/modsym/hecke_operator.py index 2e293c50d95..6d416eca8e3 100644 --- a/src/sage/modular/modsym/hecke_operator.py +++ b/src/sage/modular/modsym/hecke_operator.py @@ -13,7 +13,7 @@ ########################################################################## import sage.modular.hecke.hecke_operator -from sage.arith.all import is_prime +from sage.arith.misc import is_prime from . import heilbronn diff --git a/src/sage/modular/modsym/manin_symbol.pyx b/src/sage/modular/modsym/manin_symbol.pyx index 86927b91959..5d6df57c47d 100644 --- a/src/sage/modular/modsym/manin_symbol.pyx +++ b/src/sage/modular/modsym/manin_symbol.pyx @@ -20,11 +20,13 @@ factor. For general matrices (such as `T=[0,1,-1,-1]` and `T^2=[-1,-1;0,1]`) the image of a monomial Manin symbol is expressed as a formal sum of monomial Manin symbols, with integer coefficients. """ + +from sage.misc.persist import register_unpickle_override from sage.modular.cusps import Cusp -from sage.rings.all import Infinity, ZZ +from sage.rings.infinity import Infinity from sage.rings.integer cimport Integer +from sage.rings.integer_ring import ZZ from sage.structure.element cimport Element -from sage.misc.persist import register_unpickle_override from sage.structure.richcmp cimport richcmp_not_equal, richcmp diff --git a/src/sage/modular/modsym/p1list_nf.py b/src/sage/modular/modsym/p1list_nf.py index c366a15fbb6..222caacca80 100644 --- a/src/sage/modular/modsym/p1list_nf.py +++ b/src/sage/modular/modsym/p1list_nf.py @@ -997,7 +997,7 @@ def p1NFlist(N): #N.residues() = iterator through the residues mod N L = L+[MSymbol(N, k(1), r, check=False) for r in N.residues()] - from sage.arith.all import divisors + from sage.arith.misc import divisors for D in divisors(N): if not D.is_trivial() and D!=N: #we find Dp ideal coprime to N, in inverse class to D diff --git a/src/sage/modular/modsym/relation_matrix.py b/src/sage/modular/modsym/relation_matrix.py index dafb471b48f..99b3ebfd7b6 100644 --- a/src/sage/modular/modsym/relation_matrix.py +++ b/src/sage/modular/modsym/relation_matrix.py @@ -22,13 +22,13 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -import sage.matrix.matrix_space as matrix_space -from sage.rings.all import Ring +from sage.matrix.matrix_space import MatrixSpace from sage.misc.search import search -from sage.rings.rational_field import is_RationalField from sage.misc.verbose import verbose - from sage.modular.modsym.manin_symbol_list import ManinSymbolList +from sage.rings.rational_field import is_RationalField +from sage.rings.ring import Ring + SPARSE = True @@ -248,8 +248,7 @@ def T_relation_matrix_wtk_g0(syms, mod, field, sparse): entries[(row, j0)] = v[j0] row += 1 - MAT = matrix_space.MatrixSpace(field, row, - len(syms), sparse=True) + MAT = MatrixSpace(field, row, len(syms), sparse=True) R = MAT(entries) if not sparse: R = R.dense_matrix() @@ -330,7 +329,7 @@ def gens_to_basis_matrix(syms, relation_matrix, mod, field, sparse): verbose("done doing setup", tm) tm = verbose("now forming quotient matrix") - M = matrix_space.MatrixSpace(field, len(syms), len(basis), sparse=sparse) + M = MatrixSpace(field, len(syms), len(basis), sparse=sparse) B = M(0) cols_index = dict([(basis[i], i) for i in range(len(basis))]) diff --git a/src/sage/modular/modsym/space.py b/src/sage/modular/modsym/space.py index cd9ed42cf91..555a9be6aed 100644 --- a/src/sage/modular/modsym/space.py +++ b/src/sage/modular/modsym/space.py @@ -22,32 +22,32 @@ # # https://www.gnu.org/licenses/ # **************************************************************************** + +from sage.arith.misc import divisors, next_prime from sage.categories.fields import Fields -import sage.modules.free_module as free_module -import sage.matrix.matrix_space as matrix_space -from sage.modules.free_module_element import FreeModuleElement -from sage.modules.free_module import EchelonMatrixKey +from sage.matrix.matrix_space import MatrixSpace +from sage.misc.cachefunc import cached_method from sage.misc.misc_c import prod -import sage.modular.hecke.all as hecke -from sage.arith.all import divisors, next_prime +from sage.modules.free_module import EchelonMatrixKey, FreeModule, VectorSpace +from sage.modules.free_module_element import FreeModuleElement from sage.rings.fast_arith import prime_range +from sage.rings.finite_rings.integer_mod_ring import Zmod from sage.rings.integer_ring import ZZ -from sage.rings.rational_field import QQ from sage.rings.integer import Integer from sage.rings.infinity import infinity -from sage.rings.all import PowerSeriesRing, Zmod from sage.rings.number_field.number_field_base import is_NumberField +from sage.rings.power_series_ring import PowerSeriesRing +from sage.rings.rational_field import QQ from sage.structure.all import Sequence, SageObject from sage.structure.richcmp import (richcmp_method, richcmp, rich_to_bool, richcmp_not_equal) from sage.modular.arithgroup.all import Gamma0, is_Gamma0 # for Sturm bound given a character +from sage.modular.hecke.module import HeckeModule_free_module from sage.modular.modsym.element import ModularSymbolsElement from . import hecke_operator -from sage.misc.cachefunc import cached_method - def is_ModularSymbolsSpace(x): r""" @@ -65,7 +65,7 @@ def is_ModularSymbolsSpace(x): @richcmp_method -class ModularSymbolsSpace(hecke.HeckeModule_free_module): +class ModularSymbolsSpace(HeckeModule_free_module): r""" Base class for spaces of modular symbols. """ @@ -86,7 +86,7 @@ def __init__(self, group, weight, character, sign, base_ring, category=None): self.__group = group self.__character = character self.__sign = sign - hecke.HeckeModule_free_module.__init__(self, base_ring, group.level(), weight, category=category) + HeckeModule_free_module.__init__(self, base_ring, group.level(), weight, category=category) def __richcmp__(self, other, op): """ @@ -1113,7 +1113,7 @@ def _q_expansion_module_integral(self, prec): [] """ V = self.q_expansion_module(prec, QQ) - return free_module.FreeModule(ZZ, V.degree()).span(V.basis()).saturation() + return FreeModule(ZZ, V.degree()).span(V.basis()).saturation() def congruence_number(self, other, prec=None): r""" @@ -1382,8 +1382,8 @@ def _q_expansion_basis_hecke_dual(self, prec): d = prec - 1 K = self.base_ring() - A = free_module.VectorSpace(K, prec - 1) - M = matrix_space.MatrixSpace(K, prec - 1, self.dimension()) + A = VectorSpace(K, prec - 1) + M = MatrixSpace(K, prec - 1, self.dimension()) V = A.zero_submodule() i = self.dimension() - 1 diff --git a/src/sage/modular/multiple_zeta.py b/src/sage/modular/multiple_zeta.py index 8080c1b689e..dcd3d681eac 100644 --- a/src/sage/modular/multiple_zeta.py +++ b/src/sage/modular/multiple_zeta.py @@ -814,6 +814,19 @@ def some_elements(self): """ return self([]), self([2]), self([3]), self([4]), self((1, 2)) + def an_element(self): + r""" + Return an element of the algebra. + + EXAMPLES:: + + sage: M = Multizetas(QQ) + sage: M.an_element() + ζ() + ζ(1,2) + 1/2*ζ(5) + """ + cf = self.base_ring().an_element() + return self([]) + self([1, 2]) + cf * self([5]) + def product_on_basis(self, w1, w2): r""" Compute the product of two monomials. @@ -1790,6 +1803,29 @@ def D_on_basis(self, k, w): coprod += left.regularise().tensor(right.regularise()) return coprod + def D(self, k): + """ + Return the operator `D_k`. + + INPUT: + + - ``k`` -- an odd integer, at least 3 + + EXAMPLES:: + + sage: from sage.modular.multiple_zeta import Multizetas_iterated + sage: M = Multizetas_iterated(QQ) + sage: D3 = M.D(3) + sage: elt = M((1,0,1,0,0)) + 2 * M((1,1,0,0,1,0)) + sage: D3(elt) + -6*I(100) # I(110) + 3*I(100) # I(10) + """ + def map_on_basis(elt): + return self.D_on_basis(k, elt) + cod = Multizetas_iterated(self.base_ring()).tensor_square() + return self.module_morphism(map_on_basis, position=0, + codomain=cod) + @cached_method def phi_extended(self, w): r""" diff --git a/src/sage/modular/overconvergent/genus0.py b/src/sage/modular/overconvergent/genus0.py index 341cc835b6f..0e30f40d23b 100644 --- a/src/sage/modular/overconvergent/genus0.py +++ b/src/sage/modular/overconvergent/genus0.py @@ -168,25 +168,38 @@ # Distributed under the terms of the GNU General Public License (GPL) # https://www.gnu.org/licenses/ # **************************************************************************** -from sage.matrix.all import matrix, MatrixSpace, diagonal_matrix -from sage.misc.verbose import verbose -from sage.misc.cachefunc import cached_method -from sage.modular.all import (trivial_character, EtaProduct, - j_invariant_qexp, hecke_operator_on_qexp) + +import weakref + +import sage.rings.abc + +from sage.matrix.constructor import matrix +from sage.matrix.matrix_space import MatrixSpace +from sage.matrix.special import diagonal_matrix +from sage.misc.cachefunc import cached_method +from sage.misc.verbose import verbose from sage.modular.arithgroup.all import is_Gamma0, is_Gamma1 +from sage.modular.dirichlet import trivial_character +from sage.modular.etaproducts import EtaProduct from sage.modular.modform.element import ModularFormElement -from sage.modules.all import vector -from sage.modules.module import Module -from sage.structure.element import Vector, ModuleElement -from sage.structure.richcmp import richcmp -from sage.plot.plot import plot +from sage.modular.modform.hecke_operator_on_qexp import hecke_operator_on_qexp +from sage.modular.modform.j_invariant import j_invariant_qexp +from sage.modules.free_module_element import vector +from sage.modules.module import Module +from sage.plot.plot import plot +from sage.rings.big_oh import O +from sage.rings.infinity import Infinity from sage.rings.integer_ring import ZZ +from sage.rings.padics.factory import Qp as pAdicField +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.power_series_ring import PowerSeriesRing from sage.rings.rational_field import QQ -import sage.rings.abc -from sage.rings.all import (O, Infinity, pAdicField, PolynomialRing, PowerSeriesRing) -import weakref +from sage.structure.element import Vector, ModuleElement +from sage.structure.richcmp import richcmp from .weightspace import WeightSpace_constructor as WeightSpace, WeightCharacter + + __ocmfdict = {} #################### diff --git a/src/sage/modular/overconvergent/hecke_series.py b/src/sage/modular/overconvergent/hecke_series.py index e4028f8d10b..2da21b57b77 100644 --- a/src/sage/modular/overconvergent/hecke_series.py +++ b/src/sage/modular/overconvergent/hecke_series.py @@ -69,17 +69,20 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -from sage.functions.all import floor, ceil -from sage.arith.all import valuation -from sage.rings.all import ZZ, Zmod, Infinity, Integer -from sage.rings.finite_rings.finite_field_constructor import GF -from sage.modular.modform.all import ModularForms, ModularFormsRing, delta_qexp, eisenstein_series_qexp -from sage.modular.dims import dimension_modular_forms -from sage.misc.functional import dimension, transpose, charpoly +from sage.arith.misc import valuation +from sage.functions.other import floor, ceil from sage.matrix.constructor import matrix, random_matrix from sage.matrix.matrix_space import MatrixSpace +from sage.misc.functional import dimension, transpose, charpoly from sage.misc.misc import cputime from sage.misc.verbose import verbose +from sage.modular.dims import dimension_modular_forms +from sage.modular.modform.all import ModularForms, ModularFormsRing, delta_qexp, eisenstein_series_qexp +from sage.rings.finite_rings.finite_field_constructor import GF +from sage.rings.finite_rings.integer_mod_ring import Zmod +from sage.rings.infinity import Infinity +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ # AUXILIARY CODE: SPACES OF MODULAR FORMS AND LINEAR ALGEBRA diff --git a/src/sage/modular/overconvergent/weightspace.py b/src/sage/modular/overconvergent/weightspace.py index 14515dda101..e7716b173a6 100644 --- a/src/sage/modular/overconvergent/weightspace.py +++ b/src/sage/modular/overconvergent/weightspace.py @@ -65,16 +65,20 @@ # **************************************************************************** import weakref -from sage.structure.parent import Parent -from sage.structure.element import Element -from sage.structure.richcmp import richcmp +from sage.arith.misc import divisors +from sage.categories.sets_cat import Sets +from sage.misc.cachefunc import cached_method from sage.modular.dirichlet import DirichletGroup, trivial_character -from sage.rings.all import ZZ, QQ, IntegerModRing, Qp, Infinity -from sage.arith.all import divisors +from sage.rings.finite_rings.integer_mod_ring import IntegerModRing +from sage.rings.infinity import Infinity +from sage.rings.integer_ring import ZZ +from sage.rings.padics.factory import Qp from sage.rings.padics.padic_generic_element import pAdicGenericElement -from sage.misc.cachefunc import cached_method from sage.rings.padics.precision_error import PrecisionError -from sage.categories.sets_cat import Sets +from sage.rings.rational_field import QQ +from sage.structure.element import Element +from sage.structure.parent import Parent +from sage.structure.richcmp import richcmp _wscache = {} diff --git a/src/sage/modular/pollack_stevens/dist.pyx b/src/sage/modular/pollack_stevens/dist.pyx index 1ee57f0c9c7..c52b9db5f0a 100644 --- a/src/sage/modular/pollack_stevens/dist.pyx +++ b/src/sage/modular/pollack_stevens/dist.pyx @@ -33,7 +33,7 @@ from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ from sage.rings.power_series_ring import PowerSeriesRing from sage.rings.finite_rings.integer_mod_ring import Zmod -from sage.arith.all import binomial, bernoulli +from sage.arith.misc import binomial, bernoulli from sage.modules.free_module_element import vector, zero_vector from sage.matrix.matrix cimport Matrix from sage.matrix.matrix_space import MatrixSpace diff --git a/src/sage/modular/pollack_stevens/modsym.py b/src/sage/modular/pollack_stevens/modsym.py index 1b6b2164b1a..96bdcfc3e07 100644 --- a/src/sage/modular/pollack_stevens/modsym.py +++ b/src/sage/modular/pollack_stevens/modsym.py @@ -36,24 +36,27 @@ # the License, or (at your option) any later version. # https://www.gnu.org/licenses/ # ***************************************************************************** + import operator -from sage.structure.element import ModuleElement -from sage.structure.richcmp import op_EQ, op_NE -from sage.rings.integer_ring import ZZ -from sage.rings.rational_field import QQ + +from sage.arith.misc import next_prime, gcd, kronecker +from sage.categories.action import Action from sage.misc.cachefunc import cached_method +from sage.misc.verbose import verbose +from sage.rings.integer_ring import ZZ from sage.rings.padics.factory import Qp -from sage.rings.polynomial.all import PolynomialRing from sage.rings.padics.padic_generic import pAdicGeneric -from sage.arith.all import next_prime, gcd, kronecker -from sage.misc.verbose import verbose from sage.rings.padics.precision_error import PrecisionError +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.rational_field import QQ +from sage.structure.element import ModuleElement +from sage.structure.richcmp import op_EQ, op_NE -from sage.categories.action import Action from .manin_map import ManinMap from .sigma0 import Sigma0 from .fund_domain import M2Z + minusproj = [1, 0, 0, -1] diff --git a/src/sage/modular/pollack_stevens/padic_lseries.py b/src/sage/modular/pollack_stevens/padic_lseries.py index 4a3beb12fdf..0692e81aef3 100644 --- a/src/sage/modular/pollack_stevens/padic_lseries.py +++ b/src/sage/modular/pollack_stevens/padic_lseries.py @@ -20,12 +20,12 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -from sage.rings.padics.all import pAdicField +from sage.arith.misc import kronecker, binomial from sage.rings.integer_ring import ZZ -from sage.rings.rational_field import QQ -from sage.rings.power_series_ring import PowerSeriesRing -from sage.arith.all import binomial, kronecker +from sage.rings.padics.factory import Qp as pAdicField from sage.rings.padics.precision_error import PrecisionError +from sage.rings.power_series_ring import PowerSeriesRing +from sage.rings.rational_field import QQ from sage.structure.sage_object import SageObject diff --git a/src/sage/modular/pollack_stevens/sigma0.py b/src/sage/modular/pollack_stevens/sigma0.py index 08bffb21415..ab8e24959cd 100644 --- a/src/sage/modular/pollack_stevens/sigma0.py +++ b/src/sage/modular/pollack_stevens/sigma0.py @@ -295,7 +295,7 @@ def matrix(self): """ return self._mat - def inverse(self): + def __invert__(self): r""" Return the inverse of ``self``. @@ -305,7 +305,7 @@ def inverse(self): sage: from sage.modular.pollack_stevens.sigma0 import Sigma0 sage: s = Sigma0(3)([1,4,3,13]) - sage: s.inverse() + sage: s.inverse() # indirect doctest [13 -4] [-3 1] sage: Sigma0(3)([1, 0, 0, 3]).inverse() diff --git a/src/sage/modular/quasimodform/ring.py b/src/sage/modular/quasimodform/ring.py index cb92b17c88e..8f0d8baed62 100644 --- a/src/sage/modular/quasimodform/ring.py +++ b/src/sage/modular/quasimodform/ring.py @@ -100,26 +100,29 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -from sage.modular.arithgroup.all import Gamma0, is_CongruenceSubgroup +from sage.categories.graded_algebras import GradedAlgebras + +from sage.modular.arithgroup.congroup_gamma0 import Gamma0_constructor as Gamma0 +from sage.modular.arithgroup.congroup_generic import is_CongruenceSubgroup from sage.modular.modform.element import GradedModularFormElement, ModularFormElement from sage.modular.modform.space import ModularFormsSpace from sage.modular.modform.ring import ModularFormsRing -from sage.rings.all import Integer, QQ, ZZ -from sage.rings.polynomial.polynomial_element import Polynomial +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ from sage.rings.polynomial.multi_polynomial import MPolynomial +from sage.rings.polynomial.polynomial_element import Polynomial from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.polynomial.term_order import TermOrder from sage.rings.power_series_poly import PowerSeries_poly +from sage.rings.rational_field import QQ from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation -from sage.categories.graded_algebras import GradedAlgebras - - from .element import QuasiModularFormsElement + class QuasiModularForms(Parent, UniqueRepresentation): r""" The graded ring of quasimodular forms for the full modular group diff --git a/src/sage/modular/quatalg/brandt.py b/src/sage/modular/quatalg/brandt.py index f123eaaf16a..996e9287697 100644 --- a/src/sage/modular/quatalg/brandt.py +++ b/src/sage/modular/quatalg/brandt.py @@ -202,21 +202,25 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -# imports -from sage.misc.misc_c import prod -from sage.misc.verbose import verbose -from sage.rings.all import Integer, ZZ, QQ, PolynomialRing, GF, CommutativeRing - from sage.algebras.quatalg.quaternion_algebra import QuaternionAlgebra, basis_for_quaternion_lattice from sage.algebras.quatalg.quaternion_algebra_cython import rational_matrix_from_rational_quaternions - -from sage.arith.all import gcd, factor, prime_divisors, kronecker, next_prime -from sage.modular.hecke.all import (AmbientHeckeModule, HeckeSubmodule, - HeckeModuleElement) +from sage.arith.misc import gcd, factor, prime_divisors, kronecker, next_prime +from sage.matrix.constructor import matrix +from sage.matrix.matrix_space import MatrixSpace +from sage.misc.cachefunc import cached_method +from sage.misc.misc_c import prod +from sage.misc.verbose import verbose from sage.modular.dirichlet import TrivialCharacter -from sage.matrix.all import MatrixSpace, matrix +from sage.modular.hecke.ambient_module import AmbientHeckeModule +from sage.modular.hecke.element import HeckeModuleElement +from sage.modular.hecke.submodule import HeckeSubmodule +from sage.rings.finite_rings.finite_field_constructor import GF +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.rational_field import QQ +from sage.rings.ring import CommutativeRing from sage.structure.richcmp import richcmp, richcmp_method -from sage.misc.cachefunc import cached_method cache = {} diff --git a/src/sage/modular/ssmod/ssmod.py b/src/sage/modular/ssmod/ssmod.py index c0198b3fdfd..50865f10c80 100644 --- a/src/sage/modular/ssmod/ssmod.py +++ b/src/sage/modular/ssmod/ssmod.py @@ -66,17 +66,18 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -import sage.modular.hecke.all as hecke +from sage.arith.misc import kronecker, next_prime +from sage.libs.pari.all import pari +from sage.matrix.matrix_space import MatrixSpace +from sage.modular.arithgroup.all import Gamma0 +from sage.modular.hecke.module import HeckeModule_free_module from sage.rings.finite_rings.finite_field_constructor import FiniteField from sage.rings.integer import Integer from sage.rings.integer_ring import ZZ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing -from sage.arith.all import kronecker, next_prime -from sage.matrix.matrix_space import MatrixSpace -from sage.modular.arithgroup.all import Gamma0 -from sage.libs.pari.all import pari from sage.structure.richcmp import richcmp_method, richcmp + ZZy = PolynomialRing(ZZ, 'y') @@ -364,7 +365,7 @@ def supersingular_j(FF): @richcmp_method -class SupersingularModule(hecke.HeckeModule_free_module): +class SupersingularModule(HeckeModule_free_module): r""" The module of supersingular points in a given characteristic, with given level structure. @@ -410,8 +411,8 @@ def __init__(self, prime=2, level=1, base_ring=ZZ): self.__finite_field = FiniteField(prime**2, 'a') self.__level = level self.__hecke_matrices = {} - hecke.HeckeModule_free_module.__init__(self, base_ring, - prime * level, weight=2) + HeckeModule_free_module.__init__(self, base_ring, + prime * level, weight=2) def _repr_(self): """ diff --git a/src/sage/modules/fg_pid/fgp_morphism.py b/src/sage/modules/fg_pid/fgp_morphism.py index 8eaf32ed7bd..5f61c221d93 100644 --- a/src/sage/modules/fg_pid/fgp_morphism.py +++ b/src/sage/modules/fg_pid/fgp_morphism.py @@ -507,8 +507,8 @@ def __init__(self, X, Y, category=None): if category is None: from sage.modules.free_module import is_FreeModule if is_FreeModule(X) and is_FreeModule(Y): - from sage.categories.all import FreeModules - category = FreeModules(X.base_ring()) + from sage.categories.modules_with_basis import ModulesWithBasis + category = ModulesWithBasis(X.base_ring()) else: from sage.categories.all import Modules category = Modules(X.base_ring()) diff --git a/src/sage/modules/fp_graded/homspace.py b/src/sage/modules/fp_graded/homspace.py index ee3e4dfa59f..08e30a17dba 100755 --- a/src/sage/modules/fp_graded/homspace.py +++ b/src/sage/modules/fp_graded/homspace.py @@ -94,7 +94,7 @@ def _element_constructor_(self, values): Defn: g[1] |--> Sq(1)*g[2] + g[3] g[3] |--> Sq(2)*g[3] - One can construct a homomorphism from another homomorhism:: + One can construct a homomorphism from another homomorphism:: sage: g = homset(f) sage: f == g diff --git a/src/sage/modules/fp_graded/morphism.py b/src/sage/modules/fp_graded/morphism.py index 0417bd72347..fde00303230 100755 --- a/src/sage/modules/fp_graded/morphism.py +++ b/src/sage/modules/fp_graded/morphism.py @@ -955,7 +955,7 @@ def solve(self, x): if self.is_zero(): return None - # Handle the case where both the morhism and the element is non-trivial. + # Handle the case where both morphism and element are non-trivial. n = x.degree() - self.degree() f_n = self.vector_presentation(n) diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index 9946a588c85..b849f1e86ee 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -191,7 +191,9 @@ import sage.rings.infinity import sage.rings.integer from sage.categories.principal_ideal_domains import PrincipalIdealDomains +from sage.categories.integral_domains import IntegralDomains from sage.categories.infinite_enumerated_sets import InfiniteEnumeratedSets +from sage.misc.lazy_attribute import lazy_attribute from sage.misc.randstate import current_randstate from sage.structure.factory import UniqueFactory from sage.structure.sequence import Sequence @@ -236,6 +238,17 @@ def create_object(self, version, key): sage: TestSuite(ZZ^6).run() sage: TestSuite(RDF^3).run() + + Check that :trac:`34380` is fixed:: + + sage: R. = QQ[] + sage: Q = R.quo(R.ideal([x^2 - y^2 - 1])) + sage: Q.is_integral_domain() + True + sage: Q2 = FreeModule(Q, 2) + sage: from sage.modules.free_module import FreeModule_ambient_domain + sage: isinstance(Q2, FreeModule_ambient_domain) + True """ base_ring, rank, sparse, inner_product_matrix = key @@ -254,29 +267,29 @@ def create_object(self, version, key): "done from the right side.") #raise TypeError, "The base_ring must be a commutative ring." - try: - if not sparse and isinstance(base_ring, sage.rings.abc.RealDoubleField): - return RealDoubleVectorSpace_class(rank) + if not sparse and isinstance(base_ring, sage.rings.abc.RealDoubleField): + return RealDoubleVectorSpace_class(rank) - elif not sparse and isinstance(base_ring, sage.rings.abc.ComplexDoubleField): - return ComplexDoubleVectorSpace_class(rank) + if not sparse and isinstance(base_ring, sage.rings.abc.ComplexDoubleField): + return ComplexDoubleVectorSpace_class(rank) - elif base_ring.is_field(): + try: + if base_ring.is_field(): return FreeModule_ambient_field(base_ring, rank, sparse=sparse) + except NotImplementedError: + pass - elif base_ring in PrincipalIdealDomains(): - return FreeModule_ambient_pid(base_ring, rank, sparse=sparse) + if base_ring in PrincipalIdealDomains(): + return FreeModule_ambient_pid(base_ring, rank, sparse=sparse) - elif isinstance(base_ring, sage.rings.abc.Order) \ - and base_ring.is_maximal() and base_ring.class_number() == 1: - return FreeModule_ambient_pid(base_ring, rank, sparse=sparse) + if (isinstance(base_ring, sage.rings.abc.Order) + and base_ring.is_maximal() and base_ring.class_number() == 1): + return FreeModule_ambient_pid(base_ring, rank, sparse=sparse) - elif isinstance(base_ring, ring.IntegralDomain) or base_ring.is_integral_domain(): - return FreeModule_ambient_domain(base_ring, rank, sparse=sparse) - else: - return FreeModule_ambient(base_ring, rank, sparse=sparse) - except NotImplementedError: - return FreeModule_ambient(base_ring, rank, sparse=sparse) + if isinstance(base_ring, ring.IntegralDomain) or base_ring in IntegralDomains(): + return FreeModule_ambient_domain(base_ring, rank, sparse=sparse) + + return FreeModule_ambient(base_ring, rank, sparse=sparse) FreeModuleFactory_with_standard_basis = FreeModuleFactory("FreeModule") @@ -471,6 +484,21 @@ def FreeModule(base_ring, rank_or_basis_keys=None, sparse=False, inner_product_m sage: _.category() Category of finite dimensional vector spaces over Rational Field + sage: FreeModule(QQ, [1, 2, 3, 4], with_basis=None) + 4-dimensional vector space over the Rational Field + sage: _.category() + Category of finite dimensional vector spaces over Rational Field + + TESTS:: + + sage: FreeModule(QQ, ['a', 2, 3, 4], with_basis=None) + Traceback (most recent call last): + ... + NotImplementedError: FiniteRankFreeModule only supports integer ranges as basis_keys, got ['a', 2, 3, 4] + sage: FreeModule(QQ, [1, 3, 5], with_basis=None) + Traceback (most recent call last): + ... + NotImplementedError: FiniteRankFreeModule only supports integer ranges as basis_keys, got [1, 3, 5] """ if rank_or_basis_keys is not None: try: @@ -481,6 +509,19 @@ def FreeModule(base_ring, rank_or_basis_keys=None, sparse=False, inner_product_m if inner_product_matrix is not None: raise NotImplementedError from sage.tensor.modules.finite_rank_free_module import FiniteRankFreeModule + if basis_keys: + if not all(key in sage.rings.integer_ring.ZZ for key in basis_keys): + raise NotImplementedError(f'FiniteRankFreeModule only supports integer ranges as basis_keys, got {basis_keys}') + start_index = min(basis_keys) + end_index = max(basis_keys) + rank = end_index - start_index + 1 + # Check that the ordered list of basis_keys is the range from start_index to end_index + if (len(basis_keys) != rank + or not all(key == index + for key, index in zip(basis_keys, + range(start_index, end_index + 1)))): + raise NotImplementedError(f'FiniteRankFreeModule only supports integer ranges as basis_keys, got {basis_keys}') + return FiniteRankFreeModule(base_ring, rank, start_index=start_index, **args) return FiniteRankFreeModule(base_ring, rank, **args) elif with_basis == 'standard': if rank is not None: @@ -687,7 +728,7 @@ def span(gens, base_ring=None, check=True, already_echelonized=False): gens = list(gens) R = base_ring except TypeError: - raise TypeError("generators must be given as an iterable structure!") + raise TypeError("generators must be given as an iterable structure") if R not in PrincipalIdealDomains(): raise TypeError("The base_ring (= %s) must be a principal ideal " @@ -828,14 +869,14 @@ def __init__(self, base_ring, degree, sparse=False, category=None): if degree < 0: raise ValueError("degree (=%s) must be nonnegative" % degree) - if category is None: - from sage.categories.all import FreeModules - category = FreeModules(base_ring.category()).FiniteDimensional() - try: - if base_ring.is_finite() or degree == 0: - category = category.Enumerated().Finite() - except Exception: - pass + from sage.categories.modules_with_basis import ModulesWithBasis + modules_category = ModulesWithBasis(base_ring.category()).FiniteDimensional() + try: + if base_ring.is_finite() or degree == 0: + modules_category = modules_category.Enumerated().Finite() + except (ValueError, TypeError, AttributeError, NotImplementedError): + pass + category = modules_category.or_subcategory(category, join=True) if not hasattr(self, 'Element'): self.Element = element_class(base_ring, sparse) @@ -1742,8 +1783,6 @@ def quotient_module(self, sub, check=True): from .quotient_module import QuotientModule_free_ambient return QuotientModule_free_ambient(self, sub) - quotient = quotient_module - def __truediv__(self, sub): """ Return the quotient of ``self`` by the given submodule sub. @@ -1759,6 +1798,72 @@ def __truediv__(self, sub): """ return self.quotient(sub, check=True) + def free_resolution(self, *args, **kwds): + r""" + Return a free resolution of ``self``. + + For input options, see + :class:`~sage.homology.free_resolution.FreeResolution`. + + EXAMPLES:: + + sage: S. = PolynomialRing(QQ) + sage: M = S**2 + sage: N = M.submodule([vector([x - y, z]), vector([y * z, x * z])]) + sage: res = N.free_resolution() + sage: res + S^2 <-- S^2 <-- 0 + sage: ascii_art(res.chain_complex()) + [x - y y*z] + [ z x*z] + 0 <-- C_0 <-------------- C_1 <-- 0 + """ + from sage.rings.polynomial.multi_polynomial_libsingular import MPolynomialRing_libsingular + if isinstance(self.base_ring(), MPolynomialRing_libsingular): + from sage.homology.free_resolution import FiniteFreeResolution_singular + return FiniteFreeResolution_singular(self, *args, **kwds) + + if isinstance(self, FreeModule_generic): + from sage.homology.free_resolution import FiniteFreeResolution_free_module + return FiniteFreeResolution_free_module(self, *args, **kwds) + + raise NotImplementedError("the module must be a free module or " + "have the base ring be a polynomial ring using Singular") + + + def graded_free_resolution(self, *args, **kwds): + r""" + Return a graded free resolution of ``self``. + + For input options, see + :class:`~sage.homology.graded_resolution.GradedFiniteFreeResolution`. + + EXAMPLES:: + + sage: S. = PolynomialRing(QQ) + sage: M = S**2 + sage: N = M.submodule([vector([x - y, z]), vector([y * z, x * z])]) + sage: N.graded_free_resolution(shifts=[1, -1]) + S(-1)⊕S(1) <-- S(-2)⊕S(-3) <-- 0 + sage: N.graded_free_resolution(shifts=[2, 3]) + S(-2)⊕S(-3) <-- S(-3)⊕S(-4) <-- 0 + + sage: N = M.submodule([vector([x^3 - y^6, z^2]), vector([y * z, x])]) + sage: N.graded_free_resolution(degrees=[2, 1, 3], shifts=[2, 3]) + S(-2)⊕S(-3) <-- S(-6)⊕S(-8) <-- 0 + """ + from sage.rings.polynomial.multi_polynomial_libsingular import MPolynomialRing_libsingular + if isinstance(self.base_ring(), MPolynomialRing_libsingular): + from sage.homology.graded_resolution import GradedFiniteFreeResolution_singular + return GradedFiniteFreeResolution_singular(self, *args, **kwds) + + if isinstance(self, FreeModule_generic): + from sage.homology.graded_resolution import GradedFiniteFreeResolution_free_module + return GradedFiniteFreeResolution_free_module(self, *args, **kwds) + + raise NotImplementedError("the module must be a free module or " + "have the base ring be a polynomial ring using Singular") + class FreeModule_generic(Module_free_ambient): """ @@ -3460,7 +3565,7 @@ class FreeModule_generic_domain(FreeModule_generic): """ Base class for free modules over an integral domain. """ - def __init__(self, base_ring, rank, degree, sparse=False, coordinate_ring=None): + def __init__(self, base_ring, rank, degree, sparse=False, coordinate_ring=None, category=None): """ Create a free module over an integral domain. @@ -3471,7 +3576,7 @@ def __init__(self, base_ring, rank, degree, sparse=False, coordinate_ring=None): sage: FreeModule(PolynomialRing(GF(7),'x'), 2) Ambient free module of rank 2 over the principal ideal domain Univariate Polynomial Ring in x over Finite Field of size 7 """ - FreeModule_generic.__init__(self, base_ring, rank, degree, sparse, coordinate_ring) + FreeModule_generic.__init__(self, base_ring, rank, degree, sparse, coordinate_ring, category=category) def __add__(self, other): r""" @@ -3554,7 +3659,7 @@ class FreeModule_generic_pid(FreeModule_generic_domain): """ Base class for all free modules over a PID. """ - def __init__(self, base_ring, rank, degree, sparse=False, coordinate_ring=None): + def __init__(self, base_ring, rank, degree, sparse=False, coordinate_ring=None, category=None): """ Create a free module over a PID. @@ -3565,7 +3670,7 @@ def __init__(self, base_ring, rank, degree, sparse=False, coordinate_ring=None): sage: FreeModule(PolynomialRing(GF(7),'x'), 2) Ambient free module of rank 2 over the principal ideal domain Univariate Polynomial Ring in x over Finite Field of size 7 """ - super().__init__(base_ring, rank, degree, sparse, coordinate_ring) + super().__init__(base_ring, rank, degree, sparse, coordinate_ring, category=category) def index_in(self, other): """ @@ -4140,7 +4245,7 @@ def vector_space_span_of_basis(self, basis, check=True): """ return FreeModule_submodule_with_basis_field(self.ambient_vector_space(), basis, check=check) - def quotient(self, sub, check=True, **kwds): + def quotient_module(self, sub, check=True, **kwds): """ Return the quotient of ``self`` by the given submodule sub. @@ -4179,7 +4284,7 @@ class FreeModule_generic_field(FreeModule_generic_pid): """ Base class for all free modules over fields. """ - def __init__(self, base_field, dimension, degree, sparse=False): + def __init__(self, base_field, dimension, degree, sparse=False, category=None): """ Creates a vector space over a field. @@ -4200,7 +4305,7 @@ def __init__(self, base_field, dimension, degree, sparse=False): """ if not isinstance(base_field, ring.Field): raise TypeError("The base_field (=%s) must be a field"%base_field) - super().__init__(base_field, dimension, degree, sparse=sparse) + super().__init__(base_field, dimension, degree, sparse=sparse, category=category) def _Hom_(self, Y, category): r""" @@ -4954,7 +5059,7 @@ def __truediv__(self, sub): """ return self.quotient(sub, check=True) - def quotient(self, sub, check=True): + def quotient_module(self, sub, check=True): """ Return the quotient of ``self`` by the given subspace sub. @@ -5167,7 +5272,7 @@ class FreeModule_ambient(FreeModule_generic): """ Ambient free module over a commutative ring. """ - def __init__(self, base_ring, rank, sparse=False, coordinate_ring=None): + def __init__(self, base_ring, rank, sparse=False, coordinate_ring=None, category=None): """ The free module of given rank over the given base_ring. @@ -5203,7 +5308,7 @@ def __init__(self, base_ring, rank, sparse=False, coordinate_ring=None): True """ FreeModule_generic.__init__(self, base_ring, rank=rank, - degree=rank, sparse=sparse, coordinate_ring=coordinate_ring) + degree=rank, sparse=sparse, coordinate_ring=coordinate_ring, category=category) def __hash__(self): """ @@ -5872,7 +5977,7 @@ class FreeModule_ambient_domain(FreeModule_generic_domain, FreeModule_ambient): Ambient free module of rank 3 over the principal ideal domain Univariate Polynomial Ring in x over Finite Field of size 5 """ - def __init__(self, base_ring, rank, sparse=False, coordinate_ring=None): + def __init__(self, base_ring, rank, sparse=False, coordinate_ring=None, category=None): """ Create the ambient free module of given rank over the given integral domain. @@ -5882,7 +5987,7 @@ def __init__(self, base_ring, rank, sparse=False, coordinate_ring=None): sage: A = FreeModule(PolynomialRing(GF(5),'x'), 3) sage: TestSuite(A).run() """ - FreeModule_ambient.__init__(self, base_ring, rank, sparse, coordinate_ring) + FreeModule_ambient.__init__(self, base_ring, rank, sparse, coordinate_ring, category=category) def _repr_(self): """ @@ -6041,7 +6146,7 @@ class FreeModule_ambient_pid(FreeModule_generic_pid, FreeModule_ambient_domain): """ Ambient free module over a principal ideal domain. """ - def __init__(self, base_ring, rank, sparse=False, coordinate_ring=None): + def __init__(self, base_ring, rank, sparse=False, coordinate_ring=None, category=None): """ Create the ambient free module of given rank over the given principal ideal domain. @@ -6074,7 +6179,7 @@ def __init__(self, base_ring, rank, sparse=False, coordinate_ring=None): """ FreeModule_ambient_domain.__init__(self, base_ring=base_ring, - rank=rank, sparse=sparse, coordinate_ring=coordinate_ring) + rank=rank, sparse=sparse, coordinate_ring=coordinate_ring, category=category) def _repr_(self): """ @@ -6133,7 +6238,7 @@ class FreeModule_ambient_field(FreeModule_generic_field, FreeModule_ambient_pid) """ """ - def __init__(self, base_field, dimension, sparse=False): + def __init__(self, base_field, dimension, sparse=False, category=None): """ Create the ambient vector space of given dimension over the given field. @@ -6151,7 +6256,7 @@ def __init__(self, base_field, dimension, sparse=False): sage: QQ^3 Vector space of dimension 3 over Rational Field """ - FreeModule_ambient_pid.__init__(self, base_field, dimension, sparse=sparse) + FreeModule_ambient_pid.__init__(self, base_field, dimension, sparse=sparse, category=category) def _repr_(self): """ @@ -6312,7 +6417,8 @@ class FreeModule_submodule_with_basis_pid(FreeModule_generic_pid): [ 4 5 6] """ def __init__(self, ambient, basis, check=True, - echelonize=False, echelonized_basis=None, already_echelonized=False): + echelonize=False, echelonized_basis=None, already_echelonized=False, + category=None): r""" See :class:`FreeModule_submodule_with_basis_pid` for documentation. @@ -6342,6 +6448,25 @@ def __init__(self, ambient, basis, check=True, sage: w = sqrt(2) * V([1,1]) sage: 3 * w (3*sqrt(2), 3*sqrt(2)) + + TESTS: + + Test that the category is determined as intended:: + + sage: from sage.modules.free_module import FreeModule_ambient_pid, FreeModule_submodule_with_basis_pid + sage: V = FreeModule_ambient_pid(QQ, 3, category=Algebras(QQ)) + sage: V.category() + Category of finite dimensional algebras with basis over Rational Field + sage: W = FreeModule_submodule_with_basis_pid(V, [[1,2,3]]) + sage: W.category() + Join of + Category of finite dimensional vector spaces with basis over (number fields and quotient fields and metric spaces) and + Category of subobjects of sets + sage: W = FreeModule_submodule_with_basis_pid(V, [[1,2,3]], category=Algebras(QQ)) + sage: W.category() + Join of + Category of finite dimensional algebras with basis over Rational Field and + Category of subobjects of sets """ if not isinstance(ambient, FreeModule_ambient_pid): raise TypeError("ambient (=%s) must be ambient." % ambient) @@ -6365,9 +6490,20 @@ def __init__(self, ambient, basis, check=True, if echelonize and not already_echelonized: basis = self._echelonized_basis(ambient, basis) + # Adapted from Module_free_ambient.__init__ + from sage.categories.modules_with_basis import ModulesWithBasis + modules_category = ModulesWithBasis(R.category()).FiniteDimensional() + try: + if R.is_finite() or len(basis) == 0: + modules_category = modules_category.Enumerated().Finite() + except (ValueError, TypeError, AttributeError, NotImplementedError): + pass + modules_category = modules_category.Subobjects() + category = modules_category.or_subcategory(category, join=True) + FreeModule_generic_pid.__init__(self, base_ring=R, coordinate_ring=R_coord, rank=len(basis), degree=ambient.degree(), - sparse=ambient.is_sparse()) + sparse=ambient.is_sparse(), category=category) C = self.element_class w = [C(self, x.list(), coerce=False, copy=False) for x in basis] self.__basis = basis_seq(self, w) @@ -6658,6 +6794,121 @@ def ambient_module(self): """ return self.__ambient_module + # Sets.Subquotients.ParentMethods + def ambient(self): + """ + Return the ambient module or space for ``self``. + + EXAMPLES:: + + sage: M = ZZ^3 + sage: W = M.span_of_basis([[1,2,3],[4,5,6]]); W + Free module of degree 3 and rank 2 over Integer Ring + User basis matrix: + [1 2 3] + [4 5 6] + sage: W.ambient() + Ambient free module of rank 3 over the principal ideal domain Integer Ring + + Now we create a submodule of the ambient vector space, rather than + ``M`` itself:: + + sage: W = M.span_of_basis([[1,2,3/2],[4,5,6]]); W + Free module of degree 3 and rank 2 over Integer Ring + User basis matrix: + [ 1 2 3/2] + [ 4 5 6] + sage: W.ambient() + Vector space of dimension 3 over Rational Field + + A submodule of a submodule:: + + sage: M = ZZ^3 + sage: W = M.span_of_basis([[1,2,3],[4,5,6]]); W + Free module of degree 3 and rank 2 over Integer Ring + User basis matrix: + [1 2 3] + [4 5 6] + sage: U = W.span_of_basis([[5,7,9]]); U + Free module of degree 3 and rank 1 over Integer Ring + User basis matrix: + [5 7 9] + sage: U.ambient() + Ambient free module of rank 3 over the principal ideal domain Integer Ring + + """ + if self.base_ring() == self.coordinate_ring(): + return self.ambient_module() + else: + return self.ambient_vector_space() + + # Sets.Subquotients.ParentMethods + @lazy_attribute + def lift(self): + r""" + The lift (embedding) map from ``self`` to the ambient module or space. + + EXAMPLES:: + + sage: M = ZZ^3 + sage: W = M.span_of_basis([[1,2,3],[4,5,6]]); W + Free module of degree 3 and rank 2 over Integer Ring + User basis matrix: + [1 2 3] + [4 5 6] + sage: W.lift + Generic morphism: + From: Free module of degree 3 and rank 2 over Integer Ring + User basis matrix: + [1 2 3] + [4 5 6] + To: Ambient free module of rank 3 over the principal ideal domain Integer Ring + sage: w = W([5,7,9]) + sage: m = W.lift(w); m + (5, 7, 9) + sage: m.parent() + Ambient free module of rank 3 over the principal ideal domain Integer Ring + """ + ambient = self.ambient() + return self.module_morphism(function=ambient, codomain=ambient) + + # Sets.Subquotients.ParentMethods + @lazy_attribute + def retract(self): + r""" + The retract map from the ambient space. + + This is a partial map, which gives an error for elements not in the subspace. + + Calling this map on elements of the ambient space is the same as calling the + element constructor of ``self``. + + EXAMPLES:: + + sage: M = ZZ^3 + sage: W = M.span_of_basis([[1,2,3],[4,5,6]]); W + Free module of degree 3 and rank 2 over Integer Ring + User basis matrix: + [1 2 3] + [4 5 6] + sage: W.retract + Generic morphism: + From: Ambient free module of rank 3 over the principal ideal domain Integer Ring + To: Free module of degree 3 and rank 2 over Integer Ring + User basis matrix: + [1 2 3] + [4 5 6] + sage: m = M([5, 7, 9]) + sage: w = W.retract(m); w + (5, 7, 9) + sage: w.parent() + Free module of degree 3 and rank 2 over Integer Ring + User basis matrix: + [1 2 3] + [4 5 6] + """ + return self.ambient().module_morphism(function=self, codomain=self) + def relations(self): r""" Return the submodule defining the relations of ``self`` as a @@ -7281,7 +7532,8 @@ class FreeModule_submodule_pid(FreeModule_submodule_with_basis_pid): sage: v = W.0 + W.1 sage: TestSuite(v).run() """ - def __init__(self, ambient, gens, check=True, already_echelonized=False): + def __init__(self, ambient, gens, check=True, already_echelonized=False, + category=None): """ Create an embedded free module over a PID. @@ -7296,7 +7548,8 @@ def __init__(self, ambient, gens, check=True, already_echelonized=False): [0 3 6] """ FreeModule_submodule_with_basis_pid.__init__(self, ambient, basis=gens, - echelonize=True, already_echelonized=already_echelonized) + echelonize=True, already_echelonized=already_echelonized, + category=category) def _repr_(self): """ @@ -7434,7 +7687,8 @@ class FreeModule_submodule_with_basis_field(FreeModule_generic_field, FreeModule sage: TestSuite(W).run() """ def __init__(self, ambient, basis, check=True, - echelonize=False, echelonized_basis=None, already_echelonized=False): + echelonize=False, echelonized_basis=None, already_echelonized=False, + category=None): """ Create a vector space with given basis. @@ -7450,7 +7704,8 @@ def __init__(self, ambient, basis, check=True, """ FreeModule_submodule_with_basis_pid.__init__( self, ambient, basis=basis, check=check, echelonize=echelonize, - echelonized_basis=echelonized_basis, already_echelonized=already_echelonized) + echelonized_basis=echelonized_basis, already_echelonized=already_echelonized, + category=category) def _repr_(self): """ @@ -7631,7 +7886,7 @@ class FreeModule_submodule_field(FreeModule_submodule_with_basis_field): sage: vector(QQ, W.coordinates(v)) * W.basis_matrix() (1, 5, 9) """ - def __init__(self, ambient, gens, check=True, already_echelonized=False): + def __init__(self, ambient, gens, check=True, already_echelonized=False, category=None): """ Create an embedded vector subspace with echelonized basis. @@ -7648,7 +7903,8 @@ def __init__(self, ambient, gens, check=True, already_echelonized=False): if is_FreeModule(gens): gens = gens.gens() FreeModule_submodule_with_basis_field.__init__(self, ambient, basis=gens, check=check, - echelonize=not already_echelonized, already_echelonized=already_echelonized) + echelonize=not already_echelonized, already_echelonized=already_echelonized, + category=category) def _repr_(self): """ diff --git a/src/sage/modules/quotient_module.py b/src/sage/modules/quotient_module.py index f7aa99210a1..10db2189997 100644 --- a/src/sage/modules/quotient_module.py +++ b/src/sage/modules/quotient_module.py @@ -19,12 +19,11 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -from sage.structure.richcmp import rich_to_bool, richcmp - from .free_module import (Module_free_ambient, FreeModule_ambient, FreeModule_ambient_field) + ############################################################################### # # Quotients of ambient free modules over a domain @@ -80,7 +79,7 @@ def __init__(self, module, sub): v = [C(self._free_cover, x.list(), coerce=False, copy=False) for x in sub.gens()] w = [C(self._free_cover, x.list(), coerce=False, copy=False) for x in module.free_relations().gens()] self._relations = self._free_cover.submodule(v + w, check=False) - else: # Otherwise module should be a free module + else: # Otherwise module should be a free module self._free_cover = module self._relations = sub @@ -448,7 +447,7 @@ def _repr_(self): sage: Q._repr_() 'Vector space quotient V/W of dimension 1 over Finite Field in a of size 3^2 where\nV: Vector space of degree 3 and dimension 2 over Finite Field in a of size 3^2\nUser basis matrix:\n[1 0 a]\n[a a 1]\nW: Vector space of degree 3 and dimension 1 over Finite Field in a of size 3^2\nBasis matrix:\n[ 1 1 a + 2]' """ - return "%s space quotient V/W of dimension %s over %s where\nV: %s\nW: %s"%( + return "%s space quotient V/W of dimension %s over %s where\nV: %s\nW: %s" % ( "Sparse vector" if self.is_sparse() else "Vector", self.dimension(), self.base_ring(), self.V(), self.W()) @@ -675,4 +674,3 @@ def relations(self): return self._sub W = relations - diff --git a/src/sage/modules/tutorial_free_modules.py b/src/sage/modules/tutorial_free_modules.py index 88810b2e42b..d58acd4ae9f 100644 --- a/src/sage/modules/tutorial_free_modules.py +++ b/src/sage/modules/tutorial_free_modules.py @@ -162,7 +162,7 @@ (2, 3) sage: f.support() - [0, 1, 2] + SupportView({0: 2, 1: 2, 2: 3}) sage: f.monomials() [a[0], a[1], a[2]] sage: f.coefficients() diff --git a/src/sage/modules/with_basis/indexed_element.pyx b/src/sage/modules/with_basis/indexed_element.pyx index 6b861a42da1..d5ccebde9ef 100644 --- a/src/sage/modules/with_basis/indexed_element.pyx +++ b/src/sage/modules/with_basis/indexed_element.pyx @@ -5,17 +5,18 @@ An element in an indexed free module AUTHORS: - Travis Scrimshaw (03-2017): Moved code from :mod:`sage.combinat.free_module`. +- Travis Scrimshaw (29-08-2022): Implemented ``copy`` as the identity map. """ -#***************************************************************************** -# Copyright (C) 2017 Travis Scrimshaw +# **************************************************************************** +# Copyright (C) 2017, 2022 Travis Scrimshaw # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # http://www.gnu.org/licenses/ -#***************************************************************************** +# **************************************************************************** from sage.structure.element cimport parent from sage.structure.richcmp cimport richcmp, rich_to_bool @@ -24,17 +25,37 @@ from cpython.object cimport Py_NE, Py_EQ from sage.misc.repr import repr_lincomb from sage.misc.cachefunc import cached_method from sage.misc.lazy_attribute import lazy_attribute +from sage.misc.superseded import deprecation from sage.typeset.ascii_art import AsciiArt, empty_ascii_art, ascii_art from sage.typeset.unicode_art import UnicodeArt, empty_unicode_art, unicode_art from sage.categories.all import Category, Sets, ModulesWithBasis from sage.data_structures.blas_dict cimport add, negate, scal, axpy + cdef class IndexedFreeModuleElement(ModuleElement): + r""" + Element class for :class:`~sage.combinat.free_module.CombinatorialFreeModule` + + TESTS:: + + sage: import collections.abc + sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) + sage: B = F.basis() + sage: f = B['a'] + 3*B['c']; f + B['a'] + 3*B['c'] + sage: isinstance(f, collections.abc.Sized) + True + sage: isinstance(f, collections.abc.Iterable) + True + sage: isinstance(f, collections.abc.Collection) # known bug - will be fixed by removing __contains__ + False + """ def __init__(self, M, x): """ - Create a combinatorial module element. This should never be - called directly, but only through the parent combinatorial - free module's :meth:`__call__` method. + Create a combinatorial module element. + + This should never be called directly, but only through the + parent combinatorial free module's :meth:`__call__` method. TESTS:: @@ -59,19 +80,17 @@ cdef class IndexedFreeModuleElement(ModuleElement): sage: [i for i in sorted(f)] [('a', 1), ('c', 3)] - :: - sage: s = SymmetricFunctions(QQ).schur() sage: a = s([2,1]) + s([3]) sage: [i for i in sorted(a)] [([2, 1], 1), ([3], 1)] """ - return iter(self._monomial_coefficients.iteritems()) + return iter(self._monomial_coefficients.items()) def __contains__(self, x): """ - Returns whether or not a combinatorial object x indexing a basis - element is in the support of self. + Return whether or not a combinatorial object ``x`` indexing a basis + element is in the support of ``self``. EXAMPLES:: @@ -79,12 +98,13 @@ cdef class IndexedFreeModuleElement(ModuleElement): sage: B = F.basis() sage: f = B['a'] + 3*B['c'] sage: 'a' in f + doctest:warning... + DeprecationWarning: using 'index in vector' is deprecated; use 'index in vector.support()' instead + See https://trac.sagemath.org/34509 for details. True sage: 'b' in f False - :: - sage: s = SymmetricFunctions(QQ).schur() sage: a = s([2,1]) + s([3]) sage: Partition([2,1]) in a @@ -92,7 +112,8 @@ cdef class IndexedFreeModuleElement(ModuleElement): sage: Partition([1,1,1]) in a False """ - return x in self._monomial_coefficients and self._monomial_coefficients[x] != 0 + deprecation(34509, "using 'index in vector' is deprecated; use 'index in vector.support()' instead") + return x in self.support() def __hash__(self): """ @@ -128,7 +149,7 @@ cdef class IndexedFreeModuleElement(ModuleElement): hash value of the parent. See :trac:`15959`. """ if not self._hash_set: - self._hash = hash(frozenset(self._monomial_coefficients.iteritems())) + self._hash = hash(frozenset(self._monomial_coefficients.items())) self._hash_set = True return self._hash @@ -147,7 +168,8 @@ cdef class IndexedFreeModuleElement(ModuleElement): def __setstate__(self, state): r""" For unpickling old ``CombinatorialFreeModuleElement`` classes. - See :trac:`22632` and register_unpickle_override below. + + See :trac:`22632` and ``register_unpickle_override`` below. EXAMPLES:: @@ -177,9 +199,35 @@ cdef class IndexedFreeModuleElement(ModuleElement): 2*B['x'] + 2*B['y'] """ self._set_parent(state[0]) - for k, v in state[1].iteritems(): + for k, v in state[1].items(): setattr(self, k, v) + def __copy__(self): + r""" + Return ``self`` since ``self`` is immutable. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) + sage: x = F.an_element() + sage: copy(x) is x + True + """ + return self + + def __deepcopy__(self, memo=None): + r""" + Return ``self`` since ``self`` is immutable. + + EXAMPLES:: + + sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) + sage: x = F.an_element() + sage: deepcopy(x) is x + True + """ + return self + cpdef dict monomial_coefficients(self, bint copy=True): """ Return the internal dictionary which has the combinatorial objects @@ -230,7 +278,7 @@ cdef class IndexedFreeModuleElement(ModuleElement): def _sorted_items_for_printing(self): """ - Returns the items (i.e terms) of ``self``, sorted for printing + Return the items (i.e. terms) of ``self``, sorted for printing. EXAMPLES:: @@ -247,7 +295,7 @@ cdef class IndexedFreeModuleElement(ModuleElement): .. SEEALSO:: :meth:`_repr_`, :meth:`_latex_`, :meth:`print_options` """ print_options = self._parent.print_options() - v = list(self._monomial_coefficients.iteritems()) + v = list(self._monomial_coefficients.items()) try: v.sort(key=lambda monomial_coeff: print_options['sorting_key'](monomial_coeff[0]), @@ -334,7 +382,7 @@ cdef class IndexedFreeModuleElement(ModuleElement): except AttributeError: one_basis = None - for (monomial,c) in terms: + for monomial, c in terms: b = repr_monomial(monomial) # PCR if c != 0: break_points = [] @@ -593,8 +641,8 @@ cdef class IndexedFreeModuleElement(ModuleElement): if op == Py_NE: return True - v = sorted(self._monomial_coefficients.iteritems()) - w = sorted(elt._monomial_coefficients.iteritems()) + v = sorted(self._monomial_coefficients.items()) + w = sorted(elt._monomial_coefficients.items()) return richcmp(v, w, op) cpdef _add_(self, other): @@ -911,11 +959,12 @@ cdef class IndexedFreeModuleElement(ModuleElement): D = self._monomial_coefficients if not B.is_field(): return type(self)(F, {k: c._divide_if_possible(x) - for k, c in D.iteritems()}) + for k, c in D.items()}) x_inv = B(x) ** -1 return type(self)(F, scal(x_inv, D)) + def _unpickle_element(C, d): """ Unpickle an element in ``C`` given by ``d``. diff --git a/src/sage/modules/with_basis/invariant.py b/src/sage/modules/with_basis/invariant.py index e44c1ece287..33af946e8a0 100644 --- a/src/sage/modules/with_basis/invariant.py +++ b/src/sage/modules/with_basis/invariant.py @@ -4,7 +4,8 @@ # **************************************************************************** # Copyright (C) 2021 Trevor K. Karn -# Travis Scrimshaw +# 2021 Travis Scrimshaw +# 2022 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 @@ -109,7 +110,7 @@ class FiniteDimensionalInvariantModule(SubmoduleWithBasis): sage: M = algebras.Exterior(QQ, 'x', 3) sage: def cyclic_ext_action(g, m): ....: # cyclically permute generators - ....: return M.prod([M.monomial((g(j+1)-1,)) for j in m]) + ....: return M.prod([M.monomial(FrozenBitset([g(j+1)-1])) for j in m]) If you care about being able to exploit the algebra structure of the exterior algebra (i.e. if you want to multiply elements together), you @@ -262,9 +263,29 @@ def _invariant_map(g, x): category=category, *args, **kwargs) + def construction(self): + r""" + Return the functorial construction of ``self``. + + EXAMPLES:: + + sage: G = CyclicPermutationGroup(3) + sage: R = G.regular_representation(); R + Left Regular Representation of Cyclic group of order 3 as a permutation group over Integer Ring + sage: I = R.invariant_module() + sage: I.construction() + (EquivariantSubobjectConstructionFunctor, + Left Regular Representation of Cyclic group of order 3 as a permutation group over Integer Ring) + """ + from sage.categories.pushout import EquivariantSubobjectConstructionFunctor + return (EquivariantSubobjectConstructionFunctor(self._semigroup, + self._action, + self._side), + self.ambient()) + def _repr_(self): r""" - Return a string representaion of ``self``. + Return a string representation of ``self``. EXAMPLES:: @@ -398,7 +419,7 @@ def _mul_(self, other): sage: G = CyclicPermutationGroup(3); G.rename('G') sage: M = algebras.Exterior(QQ, 'x', 3) - sage: def on_basis(g,m): return M.prod([M.monomial((g(j+1)-1,)) for j in m]) # cyclically permute generators + sage: def on_basis(g,m): return M.prod([M.monomial(FrozenBitset([g(j+1)-1])) for j in m]) # cyclically permute generators sage: R = Representation(G, M, on_basis, category=Algebras(QQ).WithBasis().FiniteDimensional(), side='right') sage: I = R.invariant_module(); I.rename('I') sage: B = I.basis() @@ -466,7 +487,6 @@ def _acted_upon_(self, scalar, self_on_left=False): sage: g = G.an_element(); g (1,2,3) sage: M = CombinatorialFreeModule(QQ, [1,2,3]) - sage: E = algebras.Exterior(QQ, 'x', 3) sage: from sage.modules.with_basis.representation import Representation sage: R = Representation(G, M, lambda g,x: M.monomial(g(x))) sage: I = R.invariant_module() @@ -480,7 +500,8 @@ def _acted_upon_(self, scalar, self_on_left=False): [2*B[0], 2*B[0], 2*B[0]] - sage: def on_basis(g,m): return E.prod([E.monomial((g(j+1)-1,)) for j in m]) # cyclically permute generators + sage: E = algebras.Exterior(QQ, 'x', 3) + sage: def on_basis(g,m): return E.prod([E.monomial(FrozenBitset([g(j+1)-1])) for j in m]) # cyclically permute generators sage: R = Representation(G, E, on_basis, category=Algebras(QQ).WithBasis().FiniteDimensional()) sage: I = R.invariant_module() sage: B = I.basis() @@ -528,7 +549,7 @@ def _acted_upon_(self, scalar, self_on_left=False): sage: [b._acted_upon_(G((1,3,2)), self_on_left=True) for b in I.basis()] [B[0]] - sage: def on_basis(g,m): return E.prod([E.monomial((g(j+1)-1,)) for j in m]) # cyclically permute generators + sage: def on_basis(g,m): return E.prod([E.monomial(FrozenBitset([g(j+1)-1])) for j in m]) # cyclically permute generators sage: R = Representation(G, E, on_basis, category=Algebras(QQ).WithBasis().FiniteDimensional(), side='right') sage: I = R.invariant_module() sage: B = I.basis() @@ -687,7 +708,7 @@ class FiniteDimensionalTwistedInvariantModule(SubmoduleWithBasis): sage: G = SymmetricGroup(3); G.rename('S3') sage: E = algebras.Exterior(QQ, 'x', 3); E.rename('E') - sage: def action(g,m): return E.prod([E.monomial((g(j+1)-1,)) for j in m]) + sage: def action(g,m): return E.prod([E.monomial(FrozenBitset([g(j+1)-1])) for j in m]) sage: from sage.modules.with_basis.representation import Representation sage: EA = Representation(G, E, action, category=Algebras(QQ).WithBasis().FiniteDimensional()) sage: T = EA.twisted_invariant_module([2,0,-1]) diff --git a/src/sage/modules/with_basis/representation.py b/src/sage/modules/with_basis/representation.py index 89721adffae..815871e038d 100644 --- a/src/sage/modules/with_basis/representation.py +++ b/src/sage/modules/with_basis/representation.py @@ -276,7 +276,7 @@ def __init__(self, semigroup, module, on_basis, side="left", **kwargs): 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: on_basis = lambda g,m: M.prod([M.monomial(FrozenBitset([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 @@ -451,9 +451,9 @@ def product_by_coercion(self, left, right): ... 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 + group indexed by Subsets of {0,1,...,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' + group indexed by Subsets of {0,1,...,3} over Rational Field' sage: from sage.categories.algebras import Algebras sage: category = Algebras(QQ).FiniteDimensional().WithBasis() diff --git a/src/sage/monoids/free_monoid_element.py b/src/sage/monoids/free_monoid_element.py index 465adf1dd1a..23a6cc709c7 100644 --- a/src/sage/monoids/free_monoid_element.py +++ b/src/sage/monoids/free_monoid_element.py @@ -21,7 +21,7 @@ # See the GNU General Public License for more details; the full text # is available at: # -# http://www.gnu.org/licenses/ +# https://www.gnu.org/licenses/ #***************************************************************************** from sage.rings.integer import Integer @@ -48,7 +48,7 @@ class FreeMonoidElement(MonoidElement): sage: x**(-1) Traceback (most recent call last): ... - TypeError: bad operand type for unary ~: 'FreeMonoid_with_category.element_class' + NotImplementedError """ def __init__(self, F, x, check=True): """ @@ -99,7 +99,7 @@ def __hash__(self): def __iter__(self): """ - Returns an iterator which yields tuples of variable and exponent. + Return an iterator which yields tuples of variable and exponent. EXAMPLES:: @@ -265,6 +265,19 @@ def _mul_(self, y): z._element_list = x_elt[:k] + [ m ] + y_elt[1:] return z + def __invert__(self): + """ + EXAMPLES:: + + sage: a = FreeMonoid(5, 'a').gens() + sage: x = a[0]*a[1]*a[4]**3 + sage: x**(-1) + Traceback (most recent call last): + ... + NotImplementedError + """ + raise NotImplementedError + def __len__(self): """ Return the degree of the monoid element ``self``, where each diff --git a/src/sage/numerical/backends/cvxopt_backend.pyx b/src/sage/numerical/backends/cvxopt_backend.pyx index c3e19f9d9f0..59938bf175b 100644 --- a/src/sage/numerical/backends/cvxopt_backend.pyx +++ b/src/sage/numerical/backends/cvxopt_backend.pyx @@ -914,7 +914,7 @@ cdef class CVXOPTBackend(GenericBackend): return self.col_name_var[index] return "x_" + repr(index) - cpdef variable_upper_bound(self, int index, value = None): + cpdef variable_upper_bound(self, int index, value = False): """ Return or define the upper bound on a variable @@ -923,7 +923,7 @@ cdef class CVXOPTBackend(GenericBackend): - ``index`` (integer) -- the variable's id - ``value`` -- real value, or ``None`` to mean that the - variable has not upper bound. When set to ``None`` + variable has not upper bound. When set to ``False`` (default), the method returns the current value. EXAMPLES:: @@ -943,7 +943,7 @@ cdef class CVXOPTBackend(GenericBackend): else: return self.col_upper_bound[index] - cpdef variable_lower_bound(self, int index, value = None): + cpdef variable_lower_bound(self, int index, value = False): """ Return or define the lower bound on a variable @@ -952,7 +952,7 @@ cdef class CVXOPTBackend(GenericBackend): - ``index`` (integer) -- the variable's id - ``value`` -- real value, or ``None`` to mean that the - variable has not lower bound. When set to ``None`` + variable has not lower bound. When set to ``False`` (default), the method returns the current value. EXAMPLES:: diff --git a/src/sage/numerical/backends/generic_backend.pyx b/src/sage/numerical/backends/generic_backend.pyx index 083492018fe..ac167aefa8d 100644 --- a/src/sage/numerical/backends/generic_backend.pyx +++ b/src/sage/numerical/backends/generic_backend.pyx @@ -1314,7 +1314,7 @@ cdef class GenericBackend: - ``index`` (integer) -- the variable's id - ``value`` -- real value, or ``None`` to mean that the - variable has not upper bound. When set to ``None`` + variable has not upper bound. When set to ``False`` (default), the method returns the current value. EXAMPLES:: @@ -1340,7 +1340,7 @@ cdef class GenericBackend: - ``index`` (integer) -- the variable's id - ``value`` -- real value, or ``None`` to mean that the - variable has not lower bound. When set to ``None`` + variable has not lower bound. When set to ``False`` (default), the method returns the current value. EXAMPLES:: diff --git a/src/sage/numerical/backends/generic_sdp_backend.pyx b/src/sage/numerical/backends/generic_sdp_backend.pyx index bff1e940b8f..45a56b3b6e3 100644 --- a/src/sage/numerical/backends/generic_sdp_backend.pyx +++ b/src/sage/numerical/backends/generic_sdp_backend.pyx @@ -717,13 +717,12 @@ cpdef GenericSDPBackend get_solver(solver=None, base_ring=None): sage: from sage.numerical.backends.generic_sdp_backend import GenericSDPBackend sage: class MockSDPBackend(GenericSDPBackend): ....: def solve(self): - ....: raise RuntimeError("SDP is too slow!") + ....: raise RuntimeError("SDP is too slow") sage: P = SemidefiniteProgram(solver=MockSDPBackend) sage: P.solve() Traceback (most recent call last): ... - RuntimeError: SDP is too slow! - + RuntimeError: SDP is too slow """ if solver is None: solver = default_sdp_solver() diff --git a/src/sage/numerical/backends/interactivelp_backend.pyx b/src/sage/numerical/backends/interactivelp_backend.pyx index e431c604b0c..89b823f2491 100644 --- a/src/sage/numerical/backends/interactivelp_backend.pyx +++ b/src/sage/numerical/backends/interactivelp_backend.pyx @@ -985,7 +985,7 @@ cdef class InteractiveLPBackend: - ``index`` (integer) -- the variable's id - ``value`` -- real value, or ``None`` to mean that the - variable has not upper bound. When set to ``None`` + variable has not upper bound. When set to ``False`` (default), the method returns the current value. EXAMPLES:: @@ -1029,7 +1029,7 @@ cdef class InteractiveLPBackend: - ``index`` (integer) -- the variable's id - ``value`` -- real value, or ``None`` to mean that the - variable has no lower bound. When set to ``None`` + variable has no lower bound. When set to ``False`` (default), the method returns the current value. EXAMPLES:: diff --git a/src/sage/numerical/backends/ppl_backend.pyx b/src/sage/numerical/backends/ppl_backend.pyx index b83aa82cbf9..9d59c226743 100644 --- a/src/sage/numerical/backends/ppl_backend.pyx +++ b/src/sage/numerical/backends/ppl_backend.pyx @@ -1070,7 +1070,7 @@ cdef class PPLBackend(GenericBackend): - ``index`` (integer) -- the variable's id - ``value`` -- real value, or ``None`` to mean that the - variable has not upper bound. When set to ``None`` + variable has not upper bound. When set to ``False`` (default), the method returns the current value. EXAMPLES:: @@ -1102,7 +1102,7 @@ cdef class PPLBackend(GenericBackend): - ``index`` (integer) -- the variable's id - ``value`` -- real value, or ``None`` to mean that the - variable has not lower bound. When set to ``None`` + variable has not lower bound. When set to ``False`` (default), the method returns the current value. EXAMPLES:: diff --git a/src/sage/numerical/interactive_simplex_method.py b/src/sage/numerical/interactive_simplex_method.py index 3bbc044ccab..0210d9aea87 100644 --- a/src/sage/numerical/interactive_simplex_method.py +++ b/src/sage/numerical/interactive_simplex_method.py @@ -641,7 +641,7 @@ def __init__(self, A, b, c, x="x", sage: P = InteractiveLPProblem(A, b, c, ["C", "B"], variable_type=">=") sage: TestSuite(P).run() """ - super(InteractiveLPProblem, self).__init__() + super().__init__() A = matrix(A) b = vector(b) c = vector(c) @@ -1973,7 +1973,7 @@ def __init__(self, A, b, c, x="x", problem_type="max", if problem_type not in ("max", "-max"): raise ValueError("problems in standard form must be of (negative) " "maximization type") - super(InteractiveLPProblemStandardForm, self).__init__( + super().__init__( A, b, c, x, problem_type=problem_type, constraint_type="<=", @@ -2733,7 +2733,7 @@ def __init__(self): sage: P = InteractiveLPProblemStandardForm(A, b, c) sage: D = P.initial_dictionary() # indirect doctest """ - super(LPAbstractDictionary, self).__init__() + super().__init__() self._entering = None self._leaving = None @@ -3892,7 +3892,7 @@ def __init__(self, A, b, c, objective_value, sage: D = LPDictionary(A, b, c, 0, R.gens()[2:], R.gens()[:2], "z") sage: TestSuite(D).run() """ - super(LPDictionary, self).__init__() + super().__init__() # We are going to change stuff while InteractiveLPProblem has immutable data. A = copy(A) b = copy(b) @@ -4503,7 +4503,7 @@ def __init__(self, problem, basic_variables): if problem.auxiliary_variable() == problem.decision_variables()[0]: raise ValueError("revised dictionaries should not be constructed " "for auxiliary problems") - super(LPRevisedDictionary, self).__init__() + super().__init__() self._problem = problem R = problem.coordinate_ring() self._x_B = vector(R, [variable(R, v) for v in basic_variables]) @@ -4706,7 +4706,7 @@ def _preupdate_output(self, direction): \end{equation*} """ return HtmlFragment("\n".join([ - super(LPRevisedDictionary, self)._preupdate_output(direction), + super()._preupdate_output(direction), r"\begin{equation*}", r"B_\mathrm{new}^{-1} = E^{-1} B_\mathrm{old}^{-1} = ", latex(self.E_inverse()), diff --git a/src/sage/numerical/linear_functions.pyx b/src/sage/numerical/linear_functions.pyx index d35272b6f3f..a5c0c0336d0 100644 --- a/src/sage/numerical/linear_functions.pyx +++ b/src/sage/numerical/linear_functions.pyx @@ -1427,7 +1427,7 @@ cdef class LinearConstraint(LinearFunctionOrConstraint): x_0 + 2*x_1 <= -5 + x_2 """ assert len(terms) > 0 - super(LinearConstraint, self).__init__(parent) + super().__init__(parent) self.equality = equality LF = parent.linear_functions_parent() self.constraints = [ LF(term) for term in terms ] diff --git a/src/sage/numerical/linear_tensor_constraints.py b/src/sage/numerical/linear_tensor_constraints.py index fb44e669637..ce09731cc6e 100644 --- a/src/sage/numerical/linear_tensor_constraints.py +++ b/src/sage/numerical/linear_tensor_constraints.py @@ -160,7 +160,7 @@ def __init__(self, parent, lhs, rhs, equality): sage: b[2] * vector([1,2]) + 2*b[3] <= 0 (1.0, 2.0)*x_0 + (2.0, 2.0)*x_1 <= (0.0, 0.0) """ - super(LinearTensorConstraint, self).__init__(parent) + super().__init__(parent) self._lhs = lhs self._rhs = rhs self._equality = equality diff --git a/src/sage/numerical/optimize.py b/src/sage/numerical/optimize.py index 7a24fb6ab55..021c5b33aa9 100644 --- a/src/sage/numerical/optimize.py +++ b/src/sage/numerical/optimize.py @@ -926,7 +926,7 @@ def binpacking(items, maximum=1, k=None, solver=None, verbose=0, sage: binpacking([0.2,0.3,0.8,0.9], k=2) Traceback (most recent call last): ... - ValueError: this problem has no solution ! + ValueError: this problem has no solution We can also provide a dictionary keyed by items and associating to each item its weight. Then, the bins contain the name of the items inside it :: @@ -953,7 +953,7 @@ def binpacking(items, maximum=1, k=None, solver=None, verbose=0, raise TypeError("parameter items must be a list or a dictionary.") if max(weight.values()) > maximum: - raise ValueError("this problem has no solution !") + raise ValueError("this problem has no solution") if k is None: from sage.functions.other import ceil @@ -983,7 +983,7 @@ def binpacking(items, maximum=1, k=None, solver=None, verbose=0, try: p.solve(log=verbose) except MIPSolverException: - raise ValueError("this problem has no solution !") + raise ValueError("this problem has no solution") box = p.get_values(box, convert=bool, tolerance=integrality_tolerance) diff --git a/src/sage/numerical/sdp.pyx b/src/sage/numerical/sdp.pyx index dc04e26a06b..207298d7ece 100644 --- a/src/sage/numerical/sdp.pyx +++ b/src/sage/numerical/sdp.pyx @@ -1237,7 +1237,7 @@ cdef class SDPVariable(Element): sage: p.new_variable() SDPVariable """ - super(SDPVariable, self).__init__(parent) + super().__init__(parent) self._dict = {} self._p = sdp self._name = name diff --git a/src/sage/plot/arrow.py b/src/sage/plot/arrow.py index a332b0578c5..13b3b07d241 100644 --- a/src/sage/plot/arrow.py +++ b/src/sage/plot/arrow.py @@ -426,7 +426,7 @@ def __init__(self, condition_func, pe_list): path effect that is only applied when the condition_func returns True. """ - super(ConditionalStroke, self).__init__() + super().__init__() self._pe_list = pe_list self._condition_func = condition_func diff --git a/src/sage/plot/colors.py b/src/sage/plot/colors.py index e3f54f53277..bc89be406ea 100644 --- a/src/sage/plot/colors.py +++ b/src/sage/plot/colors.py @@ -1123,7 +1123,7 @@ def __dir__(self): True """ methods = ['__dir__', '__getattr__'] - return dir(super(ColorsDict, self)) + methods + sorted(self) + return dir(super()) + methods + sorted(self) colors = ColorsDict() @@ -1482,7 +1482,7 @@ def __dir__(self): methods = ['load_maps', '__dir__', '__len__', '__iter__', '__contains__', '__getitem__', '__getattr__', '__setitem__', '__delitem__'] - return dir(super(Colormaps, self)) + methods + sorted(self) + return dir(super()) + methods + sorted(self) def __len__(self): """ diff --git a/src/sage/plot/contour_plot.py b/src/sage/plot/contour_plot.py index 06d9cb97d53..3cf3d0f932d 100644 --- a/src/sage/plot/contour_plot.py +++ b/src/sage/plot/contour_plot.py @@ -822,7 +822,7 @@ def f(x,y): return cos(x) + sin(y) g = contour_plot(f, (-pi,pi), (-pi,pi), fill=False, axes=True) sphinx_plot(g) - If you are plotting a sole countour and if all of your data lie on + If you are plotting a sole contour and if all of your data lie on one side of it, then (as part of :trac:`21042`) a heuristic may be used to improve the result; in that case, a warning is emitted:: diff --git a/src/sage/plot/hyperbolic_polygon.py b/src/sage/plot/hyperbolic_polygon.py index 11a090a0fad..1726525ff43 100644 --- a/src/sage/plot/hyperbolic_polygon.py +++ b/src/sage/plot/hyperbolic_polygon.py @@ -229,7 +229,7 @@ def hyperbolic_polygon(pts, model="UHP", resolution=200, **options): P = hyperbolic_polygon([1,I,-1,-I], model="PD", color='green', fill=True, linestyle="-") sphinx_plot(P) - Klein model is also supported via the paraeter ``model``. + Klein model is also supported via the parameter ``model``. Show a hyperbolic polygon in the Klein model with coordinates `1`, `e^{i\pi/3}`, `e^{i2\pi/3}`, `-1`, `e^{i4\pi/3}`, `e^{i5\pi/3}`:: @@ -253,7 +253,7 @@ def hyperbolic_polygon(pts, model="UHP", resolution=200, **options): P = hyperbolic_polygon([p1,p2,p3,p4,p5,p6], model="KM", fill=True, color='purple') sphinx_plot(P) - Hyperboloid model is supported partially, via the paraeter ``model``. + Hyperboloid model is supported partially, via the parameter ``model``. Show a hyperbolic polygon in the hyperboloid model with coordinates `(3,3,\sqrt(19))`, `(3,-3,\sqrt(19))`, `(-3,-3,\sqrt(19))`, `(-3,3,\sqrt(19))`:: diff --git a/src/sage/plot/misc.py b/src/sage/plot/misc.py index d5fb53223f6..a64457ef661 100644 --- a/src/sage/plot/misc.py +++ b/src/sage/plot/misc.py @@ -177,7 +177,7 @@ def try_make_fast(f): return FastCallablePlotWrapper(ff, imag_tol=imaginary_tolerance) elif isinstance(f, Wrapper_cdf): # Already a fast-callable, just wrap it. This can happen - # if, for example, a symolic expression is passed to a + # if, for example, a symbolic expression is passed to a # higher-level plot() function that converts it to a # fast-callable with expr._plot_fast_callable() before # we ever see it. diff --git a/src/sage/plot/plot3d/implicit_surface.pyx b/src/sage/plot/plot3d/implicit_surface.pyx index a3b4a2ea27a..aaa2db0c3a8 100644 --- a/src/sage/plot/plot3d/implicit_surface.pyx +++ b/src/sage/plot/plot3d/implicit_surface.pyx @@ -1153,7 +1153,7 @@ cdef class ImplicitSurface(IndexFaceSet): def threejs_repr(self, render_params): r""" - Return a represention of the surface suitable for plotting with three.js. + Return a representation of the surface suitable for plotting with three.js. EXAMPLES:: diff --git a/src/sage/plot/plot3d/parametric_surface.pyx b/src/sage/plot/plot3d/parametric_surface.pyx index 7d112049848..ad6ea410e4d 100644 --- a/src/sage/plot/plot3d/parametric_surface.pyx +++ b/src/sage/plot/plot3d/parametric_surface.pyx @@ -369,7 +369,7 @@ cdef class ParametricSurface(IndexFaceSet): def threejs_repr(self, render_params): r""" - Return a represention of the surface suitable for plotting with three.js. + Return a representation of the surface suitable for plotting with three.js. EXAMPLES:: diff --git a/src/sage/quadratic_forms/quadratic_form__local_representation_conditions.py b/src/sage/quadratic_forms/quadratic_form__local_representation_conditions.py index 418365f3d60..5d39650ecb4 100644 --- a/src/sage/quadratic_forms/quadratic_form__local_representation_conditions.py +++ b/src/sage/quadratic_forms/quadratic_form__local_representation_conditions.py @@ -5,7 +5,7 @@ # Class for keeping track of the local conditions for representability # # of numbers by a quadratic form over ZZ (and eventually QQ also). # ######################################################################## - +from __future__ import annotations from copy import deepcopy from sage.arith.all import prime_divisors, valuation, is_square @@ -16,10 +16,9 @@ from sage.rings.rational_field import QQ - class QuadraticFormLocalRepresentationConditions(): """ - Creates a class for dealing with the local conditions of a + A class for dealing with the local conditions of a quadratic form, and checking local representability of numbers. EXAMPLES:: @@ -92,23 +91,21 @@ class QuadraticFormLocalRepresentationConditions(): sage: L = [m for m in range(100) if Q0.is_locally_represented_number(m)] sage: L [0] - """ - def __init__(self, Q): """ Takes a QuadraticForm and computes its local conditions (if - they don't already exist). The recompute_flag overrides the + they do not already exist). The recompute_flag overrides the previously computed conditions if they exist, and stores the new conditions. INPUT: - Q -- Quadratic form over ZZ + - Q -- Quadratic form over ZZ OUTPUT: - a QuadraticFormLocalRepresentationConditions object + a QuadraticFormLocalRepresentationConditions object EXAMPLES:: @@ -118,14 +115,11 @@ def __init__(self, Q): This form represents the p-adic integers Z_p for all primes p except []. For these and the reals, we have: Reals: [0, +Infinity] - """ - # Check that the form Q is integer-valued (we can relax this later) if Q.base_ring() != ZZ: raise TypeError("We require that the quadratic form be defined over ZZ (integer-values) for now.") - # Basic structure initialization self.local_repn_array = [] # List of all local conditions self.dim = Q.dim() # We allow this to be any non-negative integer. @@ -135,18 +129,17 @@ def __init__(self, Q): if self.dim == 0: self.coeff = None return - elif self.dim == 1: - self.coeff = Q[0,0] + if self.dim == 1: + self.coeff = Q[0, 0] return - else: - self.coeff = None + self.coeff = None # Compute the local conditions at the real numbers (i.e. "p = infinity") # ---------------------------------------------------------------------- M = Q.matrix() E = M.eigenspaces_left() - M_eigenvalues = [E[i][0] for i in range(len(E))] + M_eigenvalues = [E[i][0] for i in range(len(E))] pos_flag = infinity neg_flag = infinity @@ -160,30 +153,27 @@ def __init__(self, Q): real_vec = [infinity, pos_flag, neg_flag, None, None, None, None, None, None] self.local_repn_array.append(real_vec) - # Compute the local conditions for representability: # -------------------------------------------------- N = Q.level() level_primes = prime_divisors(N) # Make a table of local normal forms for each p | N - local_normal_forms = [Q.local_normal_form(p) for p in level_primes] + local_normal_forms = [Q.local_normal_form(p) for p in level_primes] # Check local representability conditions for each prime - for i in range(len(level_primes)): - p = level_primes[i] + for i, p in enumerate(level_primes): tmp_local_repn_vec = [p, None, None, None, None, None, None, None, None] sqclass = self.squareclass_vector(p) # Check the representability in each Z_p squareclass - for j in range(len(sqclass)): - m = sqclass[j] + for j, m in enumerate(sqclass): k = 0 repn_flag = False while ((not repn_flag) and (m < 4 * N * p * p)): - if (local_normal_forms[i].local_density(p, m) > 0): - tmp_local_repn_vec[j+1] = k + if local_normal_forms[i].local_density(p, m) > 0: + tmp_local_repn_vec[j + 1] = k repn_flag = True k = k + 1 m = m * p * p @@ -191,7 +181,7 @@ def __init__(self, Q): # If we're not represented, write "infinity" to signify # that this squareclass is fully obstructed if not repn_flag: - tmp_local_repn_vec[j+1] = infinity + tmp_local_repn_vec[j + 1] = infinity # Test if the conditions at p give exactly Z_p when dim >=3, or # if we represent the elements of even valuation >= 2 when dim = 2. @@ -207,21 +197,18 @@ def __init__(self, Q): self.local_repn_array.append(tmp_local_repn_vec) self.exceptional_primes.append(p) - - def __repr__(self): + def __repr__(self) -> str: r""" Print the local conditions. - INPUT: - - none - OUTPUT: - string + string - TO DO: Improve the output for the real numbers, and special output for locally universality. - Also give names to the squareclasses, so it's clear what the output means! =) + .. TODO:: + + Improve the output for the real numbers, and special output for locally universality. + Also give names to the squareclasses, so it's clear what the output means! =) EXAMPLES:: @@ -230,7 +217,6 @@ def __repr__(self): sage: C = QuadraticFormLocalRepresentationConditions(Q) sage: C.__repr__() 'This 2-dimensional form represents the p-adic integers of even\nvaluation for all primes p except [2].\nFor these and the reals, we have:\n Reals: [0, +Infinity]\n p = 2: [0, +Infinity, 0, +Infinity, 0, +Infinity, 0, +Infinity]\n' - """ if self.dim == 0: out_str = "This 0-dimensional form only represents zero." @@ -254,19 +240,17 @@ def __repr__(self): return out_str - - - def __eq__(self, right): + def __eq__(self, right) -> bool: """ - Determines if two sets of local conditions are equal. + Determine if two sets of local conditions are equal. INPUT: - right -- a QuadraticFormLocalRepresentationConditions object + - right -- a QuadraticFormLocalRepresentationConditions object OUTPUT: - boolean + boolean EXAMPLES:: @@ -296,26 +280,24 @@ def __eq__(self, right): # Check equality by dimension if self.dim == 0: return True - elif self.dim == 1: + if self.dim == 1: return self.coeff == right.coeff # Compare coefficients in dimension 1 (since ZZ has only one unit square) - else: - return (self.exceptional_primes == right.exceptional_primes) \ - and (self.local_repn_array == right.local_repn_array) + return ((self.exceptional_primes == right.exceptional_primes) + and (self.local_repn_array == right.local_repn_array)) - - def squareclass_vector(self, p): + def squareclass_vector(self, p) -> list: """ - Gives a vector of integers which are normalized + Return a list of integers which are normalized representatives for the `p`-adic rational squareclasses (or the real squareclasses) at the prime `p`. INPUT: - `p` -- a positive prime number or "infinity". + - `p` -- a positive prime number or "infinity" OUTPUT: - a list of integers + a list of integers EXAMPLES:: @@ -324,29 +306,25 @@ def squareclass_vector(self, p): sage: C = QuadraticFormLocalRepresentationConditions(Q) sage: C.squareclass_vector(5) [1, 2, 5, 10] - """ if p == infinity: return [1, -1] - elif p == 2: + if p == 2: return [1, 3, 5, 7, 2, 6, 10, 14] - else: - r = least_quadratic_nonresidue(p) - return [1, r, p, p*r] + r = least_quadratic_nonresidue(p) + return [1, r, p, p * r] - - - def local_conditions_vector_for_prime(self, p): + def local_conditions_vector_for_prime(self, p) -> list: """ - Returns a local representation vector for the (possibly infinite) prime `p`. + Return a local representation vector for the (possibly infinite) prime `p`. INPUT: - `p` -- a positive prime number. (Is 'infinity' allowed here?) + - `p` -- a positive prime number. (Is 'infinity' allowed here?) OUTPUT: - a list of integers + a list of integers EXAMPLES:: @@ -357,7 +335,6 @@ def local_conditions_vector_for_prime(self, p): [2, 0, 0, 0, +Infinity, 0, 0, 0, 0] sage: C.local_conditions_vector_for_prime(3) [3, 0, 0, 0, 0, None, None, None, None] - """ # Check if p is non-generic if p in self.exceptional_primes: @@ -380,31 +357,30 @@ def local_conditions_vector_for_prime(self, p): v = [p, None, None, None, None, None, None, None, None] sqclass = self.squareclass_vector(p) - for i in range(len(sqclass)): - if QQ(self.coeff / sqclass[i]).is_padic_square(p): # Note:This should happen only once! - nu = valuation(self.coeff / sqclass[i], p) / 2 + for i, sqi in enumerate(sqclass): + if QQ(self.coeff / sqi).is_padic_square(p): # Note:This should happen only once! + nu = valuation(self.coeff / sqi, p) / 2 # UNUSED VARIABLE ! else: - v[i+1] = infinity + v[i + 1] = infinity elif self.dim == 0: if p == 2: return [2, infinity, infinity, infinity, infinity, infinity, infinity, infinity, infinity] - else: - return [p, infinity, infinity, infinity, infinity, None, None, None, None] + return [p, infinity, infinity, infinity, infinity, None, None, None, None] - raise RuntimeError("Error... The dimension stored should be a non-negative integer!") + raise RuntimeError("the stored dimension should be a non-negative integer") - def is_universal_at_prime(self, p): + def is_universal_at_prime(self, p) -> bool: """ - Determines if the (integer-valued/rational) quadratic form represents all of `Z_p`. + Determine if the (integer-valued/rational) quadratic form represents all of `Z_p`. INPUT: - `p` -- a positive prime number or "infinity". + - `p` -- a positive prime number or "infinity". OUTPUT: - boolean + boolean EXAMPLES:: @@ -417,14 +393,13 @@ def is_universal_at_prime(self, p): True sage: C.is_universal_at_prime(infinity) False - """ # Check if the prime behaves generically for n >= 3. - if (self.dim >= 3) and not (p in self.exceptional_primes): + if self.dim >= 3 and p not in self.exceptional_primes: return True # Check if the prime behaves generically for n <= 2. - if (self.dim <= 2) and not (p in self.exceptional_primes): + if self.dim <= 2 and p not in self.exceptional_primes: return False # Check if the prime is "infinity" (for the reals) @@ -432,7 +407,7 @@ def is_universal_at_prime(self, p): v = self.local_repn_array[0] if p != v[0]: raise RuntimeError("Error... The first vector should be for the real numbers!") - return (v[1:3] == [0,0]) # True iff the form is indefinite + return (v[1:3] == [0, 0]) # True iff the form is indefinite # Check non-generic "finite" primes v = self.local_conditions_vector_for_prime(p) @@ -442,18 +417,13 @@ def is_universal_at_prime(self, p): Zp_univ_flag = False return Zp_univ_flag - - def is_universal_at_all_finite_primes(self): + def is_universal_at_all_finite_primes(self) -> bool: """ - Determines if the quadratic form represents `Z_p` for all finite/non-archimedean primes. - - INPUT: - - none + Determine if the quadratic form represents `Z_p` for all finite/non-archimedean primes. OUTPUT: - boolean + boolean EXAMPLES:: @@ -470,31 +440,24 @@ def is_universal_at_all_finite_primes(self): sage: C = QuadraticFormLocalRepresentationConditions(Q) sage: C.is_universal_at_all_finite_primes() True - """ # Check if dim <= 2. if self.dim <= 2: return False # Check that all non-generic finite primes are universal - univ_flag = True - for p in self.exceptional_primes[1:]: # Omit p = "infinity" here - univ_flag = univ_flag and self.is_universal_at_prime(p) - return univ_flag - + # Omit p = "infinity" here + return all(self.is_universal_at_prime(p) + for p in self.exceptional_primes[1:]) - def is_universal_at_all_places(self): + def is_universal_at_all_places(self) -> bool: """ - Determines if the quadratic form represents `Z_p` for all + Determine if the quadratic form represents `Z_p` for all finite/non-archimedean primes, and represents all real numbers. - INPUT: - - none - OUTPUT: - boolean + boolean EXAMPLES:: @@ -520,34 +483,29 @@ def is_universal_at_all_places(self): sage: C = QuadraticFormLocalRepresentationConditions(Q) # long time (8.5 s) sage: C.is_universal_at_all_places() # long time True - """ # Check if dim <= 2. if self.dim <= 2: return False # Check that all non-generic finite primes are universal - for p in self.exceptional_primes: - if not self.is_universal_at_prime(p): - return False - return True + return all(self.is_universal_at_prime(p) + for p in self.exceptional_primes) - - - def is_locally_represented_at_place(self, m, p): + def is_locally_represented_at_place(self, m, p) -> bool: """ - Determines if the rational number m is locally represented by the + Determine if the rational number `m` is locally represented by the quadratic form at the (possibly infinite) prime `p`. INPUT: - `m` -- an integer + - `m` -- an integer - `p` -- a positive prime number or "infinity". + - `p` -- a positive prime number or "infinity". OUTPUT: - boolean + boolean EXAMPLES:: @@ -565,7 +523,6 @@ def is_locally_represented_at_place(self, m, p): True sage: C.is_locally_represented_at_place(0, infinity) True - """ # Sanity Check if m not in QQ: @@ -583,9 +540,8 @@ def is_locally_represented_at_place(self, m, p): if self.dim == 1: m1 = QQ(m) / self.coeff if p == infinity: - return (m1 > 0) - else: - return (valuation(m1, p) >= 0) and m1.is_padic_square(p) + return m1 > 0 + return (valuation(m1, p) >= 0) and m1.is_padic_square(p) # >= 2-dim'l forms local_vec = self.local_conditions_vector_for_prime(p) @@ -594,31 +550,31 @@ def is_locally_represented_at_place(self, m, p): if p == infinity: if m > 0: return local_vec[1] == 0 - elif m < 0: + if m < 0: return local_vec[2] == 0 - else: # m == 0 - return True + # m == 0 + return True # Check at a finite place sqclass = self.squareclass_vector(p) for s in sqclass: - if (QQ(m)/s).is_padic_square(p): - nu = valuation(m//s, p) + if (QQ(m) / s).is_padic_square(p): + nu = valuation(m // s, p) return local_vec[sqclass.index(s) + 1] <= (nu / 2) - def is_locally_represented(self, m): + def is_locally_represented(self, m) -> bool: """ - Determines if the rational number `m` is locally represented by + Determine if the rational number `m` is locally represented by the quadratic form (allowing vectors with coefficients in `Z_p` at all places). INPUT: - `m` -- an integer + - `m` -- an integer OUTPUT: - boolean + boolean EXAMPLES:: @@ -634,7 +590,6 @@ def is_locally_represented(self, m): True sage: C.is_locally_represented(QQ(1)/QQ(2)) False - """ # Representing zero if m == 0: @@ -665,15 +620,14 @@ def is_locally_represented(self, m): # If we got here, we're locally represented! return True - - -# -------------------- End of QuadraticFormLocalRepresentationConditions Class ---------------------- - +# --- End of QuadraticFormLocalRepresentationConditions Class --- def local_representation_conditions(self, recompute_flag=False, silent_flag=False): """ - WARNING: THIS ONLY WORKS CORRECTLY FOR FORMS IN >=3 VARIABLES, + .. WARNING:: + + THIS ONLY WORKS CORRECTLY FOR FORMS IN >=3 VARIABLES, WHICH ARE LOCALLY UNIVERSAL AT ALMOST ALL PRIMES! This class finds the local conditions for a number to be integrally @@ -718,14 +672,10 @@ def local_representation_conditions(self, recompute_flag=False, silent_flag=Fals positive reals are represented). The real vector always appears, and is listed before the other ones. - INPUT: - - none - OUTPUT: - A list of 9-element vectors describing the representation - obstructions at primes dividing the level. + A list of 9-element vectors describing the representation + obstructions at primes dividing the level. EXAMPLES:: @@ -745,7 +695,6 @@ def local_representation_conditions(self, recompute_flag=False, silent_flag=Fals Reals: [0, +Infinity] p = 2: [0, +Infinity, 0, +Infinity, 0, +Infinity, 0, +Infinity] - sage: Q1 = DiagonalQuadraticForm(ZZ, [1,1,1]) sage: Q1.local_representation_conditions() This form represents the p-adic integers Z_p for all primes p except @@ -778,9 +727,8 @@ def local_representation_conditions(self, recompute_flag=False, silent_flag=Fals This form represents the p-adic integers Z_p for all primes p except []. For these and the reals, we have: Reals: [0, +Infinity] - """ - # Recompute the local conditions if they don't exist or the recompute_flag is set. + # Recompute the local conditions if they do not exist or the recompute_flag is set. if not hasattr(self, "__local_representability_conditions") or recompute_flag: self.__local_representability_conditions = QuadraticFormLocalRepresentationConditions(self) @@ -789,17 +737,17 @@ def local_representation_conditions(self, recompute_flag=False, silent_flag=Fals return self.__local_representability_conditions -def is_locally_universal_at_prime(self, p): +def is_locally_universal_at_prime(self, p) -> bool: """ - Determines if the (integer-valued/rational) quadratic form represents all of `Z_p`. + Determine if the (integer-valued/rational) quadratic form represents all of `Z_p`. INPUT: - `p` -- a positive prime number or "infinity". + - `p` -- a positive prime number or "infinity". OUTPUT: - boolean + boolean EXAMPLES:: @@ -830,24 +778,18 @@ def is_locally_universal_at_prime(self, p): sage: Q = DiagonalQuadraticForm(ZZ, [1,1,-1]) sage: Q.is_locally_universal_at_prime(infinity) True - """ self.local_representation_conditions(silent_flag=True) return self.__local_representability_conditions.is_universal_at_prime(p) - -def is_locally_universal_at_all_primes(self): +def is_locally_universal_at_all_primes(self) -> bool: """ - Determines if the quadratic form represents `Z_p` for all finite/non-archimedean primes. - - INPUT: - - none + Determine if the quadratic form represents `Z_p` for all finite/non-archimedean primes. OUTPUT: - boolean + boolean EXAMPLES:: @@ -866,25 +808,19 @@ def is_locally_universal_at_all_primes(self): sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1]) sage: Q.is_locally_universal_at_all_primes() False - """ self.local_representation_conditions(silent_flag=True) return self.__local_representability_conditions.is_universal_at_all_finite_primes() - -def is_locally_universal_at_all_places(self): +def is_locally_universal_at_all_places(self) -> bool: """ - Determines if the quadratic form represents `Z_p` for all + Determine if the quadratic form represents `Z_p` for all finite/non-archimedean primes, and represents all real numbers. - INPUT: - - none - OUTPUT: - boolean + boolean EXAMPLES:: @@ -903,27 +839,25 @@ def is_locally_universal_at_all_places(self): sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1,1,-1]) sage: Q.is_locally_universal_at_all_places() # long time (8.5 s) True - """ self.local_representation_conditions(silent_flag=True) return self.__local_representability_conditions.is_universal_at_all_places() - -def is_locally_represented_number_at_place(self, m, p): +def is_locally_represented_number_at_place(self, m, p) -> bool: """ - Determines if the rational number m is locally represented by the + Determine if the rational number `m` is locally represented by the quadratic form at the (possibly infinite) prime `p`. INPUT: - `m` -- an integer + - `m` -- an integer - `p` -- a prime number > 0 or 'infinity' + - `p` -- a prime number > 0 or 'infinity' OUTPUT: - boolean + boolean EXAMPLES:: @@ -952,24 +886,23 @@ def is_locally_represented_number_at_place(self, m, p): True sage: Q.is_locally_represented_number_at_place(7, 5) # long time True - """ self.local_representation_conditions(silent_flag=True) return self.__local_representability_conditions.is_locally_represented_at_place(m, p) - -def is_locally_represented_number(self, m): +def is_locally_represented_number(self, m) -> bool: """ - Determines if the rational number m is locally represented by the quadratic form. + Determine if the rational number `m` is locally represented + by the quadratic form. INPUT: - `m` -- an integer + - `m` -- an integer OUTPUT: - boolean + boolean EXAMPLES:: diff --git a/src/sage/quadratic_forms/quadratic_form__neighbors.py b/src/sage/quadratic_forms/quadratic_form__neighbors.py index 6c38d7784ee..96a5203101d 100644 --- a/src/sage/quadratic_forms/quadratic_form__neighbors.py +++ b/src/sage/quadratic_forms/quadratic_form__neighbors.py @@ -248,7 +248,7 @@ def neighbor_iteration(seeds, p, mass=None, max_classes=ZZ(10)**3, - ``max_classes`` -- (default: ``1000``) break the computation when ``max_classes`` are found - - ``algorithm`` -- (optional) one of 'orbits', 'random', 'exaustion' + - ``algorithm`` -- (optional) one of 'orbits', 'random', 'exhaustion' - ``max_random_trys`` -- (default: ``1000``) the maximum number of neighbors computed for a single lattice @@ -265,7 +265,7 @@ def neighbor_iteration(seeds, p, mass=None, max_classes=ZZ(10)**3, 46 sage: mass = Q.conway_mass() sage: g1 = neighbor_iteration([Q],3, mass=mass, algorithm = 'random') # long time - sage: g2 = neighbor_iteration([Q],3, algorithm = 'exaustion') # long time + sage: g2 = neighbor_iteration([Q],3, algorithm = 'exhaustion') # long time sage: g3 = neighbor_iteration([Q],3, algorithm = 'orbits') sage: mass == sum(1/q.number_of_automorphisms() for q in g1) # long time True @@ -304,7 +304,7 @@ def p_divisible_vectors(Q, max_neighbors): yield from iter(v.lift() for v in Q.orbits_lines_mod_p(p) if v != 0 and Q(v.lift()).valuation(p) > 0) return - elif algorithm == 'exaustion': + elif algorithm == 'exhaustion': def p_divisible_vectors(Q, max_neighbors): k = 0 v = Q.find_primitive_p_divisible_vector__next(p) diff --git a/src/sage/quivers/representation.py b/src/sage/quivers/representation.py index fdd9bfda98e..78ed8ce6d2a 100644 --- a/src/sage/quivers/representation.py +++ b/src/sage/quivers/representation.py @@ -776,7 +776,7 @@ def create_object(self, version, key, **extra_args): Representation with dimension vector (0, 0) """ if len(key) < 4: - raise ValueError("invalid key used in QuiverRepFactory!") + raise ValueError("invalid key used in QuiverRepFactory") # Get the quiver P = key[1] @@ -807,8 +807,7 @@ def create_object(self, version, key, **extra_args): # Create and return the module return QuiverRep_with_dual_path_basis(key[0], P, key[3]) - else: - raise ValueError("invalid key used in QuiverRepFactory!") + raise ValueError("invalid key used in QuiverRepFactory") QuiverRep = QuiverRepFactory("sage.quivers.representation.QuiverRep") diff --git a/src/sage/repl/display/formatter.py b/src/sage/repl/display/formatter.py index adde08d27d2..2910fb8ee19 100644 --- a/src/sage/repl/display/formatter.py +++ b/src/sage/repl/display/formatter.py @@ -100,7 +100,7 @@ def __init__(self, *args, **kwds): ... RuntimeError: check failed: current backend is invalid """ - super(SageDisplayFormatter, self).__init__(*args, **kwds) + super().__init__(*args, **kwds) from sage.repl.rich_output.display_manager import get_display_manager self.dm = get_display_manager() from sage.repl.rich_output.backend_ipython import BackendIPython @@ -277,7 +277,7 @@ def __init__(self, *args, **kwds): sage: shell.quit() """ - super(SagePlainTextFormatter, self).__init__(*args, **kwds) + super().__init__(*args, **kwds) def __call__(self, obj): r""" diff --git a/src/sage/repl/display/pretty_print.py b/src/sage/repl/display/pretty_print.py index 804115051ed..ec2aefbd7ad 100644 --- a/src/sage/repl/display/pretty_print.py +++ b/src/sage/repl/display/pretty_print.py @@ -105,8 +105,8 @@ def __init__(self, output, max_width, newline, max_seq_length=None): sage: foo """ - super(SagePrettyPrinter, self).__init__( - output, max_width, newline, max_seq_length=max_seq_length) + super().__init__(output, max_width, newline, + max_seq_length=max_seq_length) self.stack = [] def pretty(self, obj): diff --git a/src/sage/repl/interpreter.py b/src/sage/repl/interpreter.py index d7ba014361d..fe4cff3add5 100644 --- a/src/sage/repl/interpreter.py +++ b/src/sage/repl/interpreter.py @@ -229,7 +229,7 @@ def system_raw(self, cmd): 0 sage: shell.quit() """ - return super(SageShellOverride, self).system_raw(cmd) + return super().system_raw(cmd) class SageNotebookInteractiveShell(SageShellOverride, InteractiveShell): @@ -387,7 +387,7 @@ def run_cell(self, *args, **kwds): True sage: shell.quit() """ - super(SageTestShell, self).run_cell(*args, **kwds) + super().run_cell(*args, **kwds) ################################################################### @@ -480,7 +480,7 @@ def __init__(self, *args, **kwds): sage: ift._sage_import_re.findall('sage(a) + maxima(b)') ['sage(', 'maxima('] """ - super(InterfaceShellTransformer, self).__init__(*args, **kwds) + super().__init__(*args, **kwds) self.temporary_objects = set() self._sage_import_re = re.compile(r'(?:sage|%s)\(' % self.shell.interface.name()) @@ -739,7 +739,7 @@ def load_config_file(self, *args, **kwds): sage: SageTerminalApp().load_config_file() sage: os.environ['IPYTHONDIR'] = IPYTHONDIR """ - super(SageTerminalApp, self).load_config_file(*args, **kwds) + super().load_config_file(*args, **kwds) newconfig = sage_ipython_config.default() # merge in the config loaded from file newconfig.merge(self.config) diff --git a/src/sage/repl/ipython_kernel/interact.py b/src/sage/repl/ipython_kernel/interact.py index 9b94bc4d9b3..4a6960ece3a 100644 --- a/src/sage/repl/ipython_kernel/interact.py +++ b/src/sage/repl/ipython_kernel/interact.py @@ -123,7 +123,7 @@ def __init__(self, *args, **kwds): options["manual"] = (p_auto_update.default is False) self.__signature = sig.replace(parameters=params.values()) - super(sage_interactive, self).__init__(f, options, **kwds) + super().__init__(f, options, **kwds) if self.manual: # In Sage, manual interacts are always run once self.on_displayed(self.update) @@ -197,7 +197,7 @@ def widget_from_single_value(cls, abbrev, *args, **kwds): if isinstance(abbrev, Color): return SageColorPicker(value=abbrev.html_color()) # Get widget from IPython if possible - widget = super(sage_interactive, cls).widget_from_single_value(abbrev, *args, **kwds) + widget = super().widget_from_single_value(abbrev, *args, **kwds) if widget is not None or isinstance(abbrev, Iterable): return widget # If IPython didn't construct a widget and the abbrev is not an @@ -255,7 +255,7 @@ def n(x): else: return x abbrev = tuple(n(x) for x in abbrev) - return super(sage_interactive, cls).widget_from_tuple(abbrev, *args, **kwds) + return super().widget_from_tuple(abbrev, *args, **kwds) @classmethod # Behaves like a staticmethod, but we need super() def widget_from_iterable(cls, abbrev, *args, **kwds): @@ -283,7 +283,7 @@ def widget_from_iterable(cls, abbrev, *args, **kwds): """ if isinstance(abbrev, Iterator): return SelectionSlider(options=list(abbrev)) - return super(sage_interactive, cls).widget_from_iterable(abbrev, *args, **kwds) + return super().widget_from_iterable(abbrev, *args, **kwds) # @interact decorator diff --git a/src/sage/repl/ipython_kernel/kernel.py b/src/sage/repl/ipython_kernel/kernel.py index e38fc522a72..a4642014e90 100644 --- a/src/sage/repl/ipython_kernel/kernel.py +++ b/src/sage/repl/ipython_kernel/kernel.py @@ -48,7 +48,7 @@ def __init__(self, **kwds): sage: SageKernel.__new__(SageKernel) """ - super(SageKernel, self).__init__(**kwds) + super().__init__(**kwds) SageJupyterCustomizations(self.shell) @property diff --git a/src/sage/repl/ipython_kernel/widgets.py b/src/sage/repl/ipython_kernel/widgets.py index dfe126a2760..3ba45df9b04 100644 --- a/src/sage/repl/ipython_kernel/widgets.py +++ b/src/sage/repl/ipython_kernel/widgets.py @@ -118,7 +118,7 @@ def __init__(self, *args, **kwds): <... 'dict'> """ self.__transform = kwds.pop("transform", None) - return super(TransformWidget, self).__init__(*args, **kwds) + return super().__init__(*args, **kwds) def get_value(self): """ @@ -412,7 +412,7 @@ def __init__(self, nrows, ncols, make_widget, description=u"", transform=None): """ if nrows < 1 or ncols < 1: raise ValueError("Grid requires a positive number of rows and columns") - super(Grid, self).__init__(transform=transform) + super().__init__(transform=transform) label = Label(description) link((label, "value"), (self, "description")) diff --git a/src/sage/repl/rich_output/preferences.py b/src/sage/repl/rich_output/preferences.py index 410ee7322da..f26962dc29b 100644 --- a/src/sage/repl/rich_output/preferences.py +++ b/src/sage/repl/rich_output/preferences.py @@ -106,8 +106,8 @@ def __init__(self, name, allowed_values, doc=None): self.underscore_name = '_{0}'.format(name) self.allowed_values = tuple(allowed_values) self.__doc__ = doc = self._make_doc(doc) - super(Property, self).__init__( - fget=self.getter, fset=self.setter, fdel=self.deleter, doc=doc) + super().__init__(fget=self.getter, fset=self.setter, + fdel=self.deleter, doc=doc) def _make_doc(self, doc): """ diff --git a/src/sage/repl/rich_output/pretty_print.py b/src/sage/repl/rich_output/pretty_print.py index 154f17b8290..4fcbfe4d5af 100644 --- a/src/sage/repl/rich_output/pretty_print.py +++ b/src/sage/repl/rich_output/pretty_print.py @@ -62,7 +62,7 @@ class SequencePrettyPrinter(SageObject): def __init__(self, *args, **kwds): r""" - Pretty Printer for Muliple Arguments. + Pretty Printer for Multiple Arguments. INPUT/OUTPUT: diff --git a/src/sage/rings/all.py b/src/sage/rings/all.py index 6e6b81eab7b..500ab0b615a 100644 --- a/src/sage/rings/all.py +++ b/src/sage/rings/all.py @@ -146,7 +146,8 @@ lazy_import('sage.rings.laurent_series_ring_element', 'LaurentSeries', deprecation=33602) # Lazy Laurent series ring -lazy_import('sage.rings.lazy_series_ring', ['LazyLaurentSeriesRing', 'LazyDirichletSeriesRing']) +lazy_import('sage.rings.lazy_series_ring', ['LazyLaurentSeriesRing', 'LazyTaylorSeriesRing', + 'LazySymmetricFunctions', 'LazyDirichletSeriesRing']) # Tate algebras from .tate_algebra import TateAlgebra diff --git a/src/sage/rings/asymptotic/asymptotic_ring.py b/src/sage/rings/asymptotic/asymptotic_ring.py index ed8504ebc95..4c5deb5ce21 100644 --- a/src/sage/rings/asymptotic/asymptotic_ring.py +++ b/src/sage/rings/asymptotic/asymptotic_ring.py @@ -428,7 +428,6 @@ class NoConvergenceError(RuntimeError): A special :python:`RuntimeError` which is raised when an algorithm does not converge/stop. """ - pass class AsymptoticExpansion(CommutativeAlgebraElement): @@ -666,7 +665,7 @@ def __init__(self, parent, summands, simplify=True, convert=True): sage: 1 + (-1)^x + 2^x + (-2)^x 2^x + 2^x*(-1)^x + (-1)^x + 1 """ - super(AsymptoticExpansion, self).__init__(parent=parent) + super().__init__(parent=parent) from sage.data_structures.mutable_poset import MutablePoset if not isinstance(summands, MutablePoset): @@ -716,7 +715,6 @@ def summands(self): """ return self._summands_ - def __hash__(self): r""" A hash value for this element. @@ -736,7 +734,6 @@ def __hash__(self): """ return hash(str(self)) - def __bool__(self): r""" Return whether this asymptotic expansion is not identically zero. @@ -761,8 +758,6 @@ def __bool__(self): """ return bool(self._summands_) - - def __eq__(self, other): r""" Return whether this asymptotic expansion is equal to ``other``. @@ -805,7 +800,6 @@ def __eq__(self, other): except (TypeError, ValueError): return False - def __ne__(self, other): r""" Return whether this asymptotic expansion is not equal to ``other``. @@ -838,7 +832,6 @@ def __ne__(self, other): """ return not self == other - def has_same_summands(self, other): r""" Return whether this asymptotic expansion and ``other`` have the @@ -887,7 +880,6 @@ def has_same_summands(self, other): lambda self, other: self._has_same_summands_(other)) - def _has_same_summands_(self, other): r""" Return whether this :class:`AsymptoticExpansion` has the same @@ -961,7 +953,6 @@ def _simplify_(self): """ self._summands_.merge(reverse=True) - def _repr_(self, latex=False): r""" A representation string for this asymptotic expansion. @@ -996,7 +987,6 @@ def _repr_(self, latex=False): return '0' return s - def _latex_(self): r""" A LaTeX-representation string for this asymptotic expansion. @@ -1015,7 +1005,6 @@ def _latex_(self): """ return self._repr_(latex=True) - def show(self): r""" Pretty-print this asymptotic expansion. @@ -1162,7 +1151,6 @@ def _add_(self, other): return self.parent()(self.summands.union(other.summands), simplify=True, convert=False) - def _sub_(self, other): r""" Subtract ``other`` from this asymptotic expansion. @@ -1191,7 +1179,6 @@ def _sub_(self, other): """ return self + self.parent().coefficient_ring(-1)*other - def _mul_term_(self, term): r""" Helper method: multiply this asymptotic expansion by the @@ -1408,10 +1395,8 @@ def __invert__(self, precision=None): return result._mul_term_(imax_elem) - invert = __invert__ - def truncate(self, precision=None): r""" Truncate this asymptotic expansion. @@ -1704,7 +1689,7 @@ def __pow__(self, exponent, precision=None): except (TypeError, ValueError): pass else: - return super(AsymptoticExpansion, self).__pow__(exponent) + return super().__pow__(exponent) from sage.rings.rational_field import QQ try: @@ -1926,7 +1911,6 @@ def sqrt(self, precision=None): from sage.rings.rational_field import QQ return self.pow(QQ(1)/QQ(2), precision=precision) - def O(self): r""" Convert all terms in this asymptotic expansion to `O`-terms. @@ -1972,7 +1956,6 @@ def O(self): return sum(self.parent().create_summand('O', growth=element) for element in self.summands.maximal_elements()) - def log(self, base=None, precision=None, locals=None): r""" The logarithm of this asymptotic expansion. @@ -2106,7 +2089,6 @@ def log(self, base=None, precision=None, locals=None): return result - def is_exact(self): r""" Return whether all terms of this expansion are exact. @@ -2132,7 +2114,6 @@ def is_exact(self): """ return all(T.is_exact() for T in self.summands) - def is_little_o_of_one(self): r""" Return whether this expansion is of order `o(1)`. @@ -2173,7 +2154,6 @@ def is_little_o_of_one(self): """ return all(term.is_little_o_of_one() for term in self.summands.maximal_elements()) - def rpow(self, base, precision=None, locals=None): r""" Return the power of ``base`` to this asymptotic expansion. @@ -2290,7 +2270,6 @@ def inverted_factorials(): return result * large_result - def _main_term_relative_error_(self, return_inverse_main_term=False): r""" Split this asymptotic expansion into `m(1+x)` with `x=o(1)`. @@ -2363,7 +2342,6 @@ def _main_term_relative_error_(self, return_inverse_main_term=False): else: return (max_elem, x) - @staticmethod def _power_series_(coefficients, start, ratio, ratio_start, precision): r""" @@ -2438,7 +2416,6 @@ def _power_series_(coefficients, start, ratio, ratio_start, precision): result = new_result return result - def exp(self, precision=None): r""" Return the exponential of (i.e., the power of `e` to) this asymptotic expansion. @@ -2508,7 +2485,6 @@ def exp(self, precision=None): """ return self.rpow('e', precision=precision) - def substitute(self, rules=None, domain=None, **kwds): r""" Substitute the given ``rules`` in this asymptotic expansion. @@ -2669,7 +2645,7 @@ def substitute(self, rules=None, domain=None, **kwds): # init and process keyword arguments gens = self.parent().gens() - locals = kwds or dict() + locals = kwds or {} # update with rules if isinstance(rules, dict): @@ -2784,7 +2760,6 @@ def _substitute_(self, rules): from .misc import substitute_raise_exception substitute_raise_exception(self, e) - def compare_with_values(self, variable, function, values, rescaled=True, ring=RIF): """ @@ -2944,7 +2919,6 @@ def function(arg): return points - def plot_comparison(self, variable, function, values, rescaled=True, ring=RIF, relative_tolerance=0.025, **kwargs): r""" @@ -3041,7 +3015,6 @@ def plot_comparison(self, variable, function, values, rescaled=True, return list_plot(points, **kwargs) - def symbolic_expression(self, R=None): r""" Return this asymptotic expansion as a symbolic expression. @@ -3096,10 +3069,8 @@ def symbolic_expression(self, R=None): for g in self.parent().gens()), domain=R) - _symbolic_ = symbolic_expression # will be used by SR._element_constructor_ - def map_coefficients(self, f, new_coefficient_ring=None): r""" Return the asymptotic expansion obtained by applying ``f`` to @@ -3154,7 +3125,6 @@ def mapping(term): S.map(mapping) return P(S, simplify=False, convert=False) - def factorial(self): r""" Return the factorial of this asymptotic expansion. @@ -3256,7 +3226,6 @@ def factorial(self): 'Cannot build the factorial of {} since it is not ' 'univariate.'.format(self)) - def variable_names(self): r""" Return the names of the variables of this asymptotic expansion. @@ -3291,7 +3260,6 @@ def variable_names(self): from itertools import groupby return tuple(v for v, _ in groupby(vars)) - def _singularity_analysis_(self, var, zeta, precision=None): r""" Return the asymptotic growth of the coefficients of some @@ -3597,7 +3565,6 @@ class AsymptoticRing(Algebra, UniqueRepresentation, WithLocals): # enable the category framework for elements Element = AsymptoticExpansion - @staticmethod def __classcall__(cls, growth_group=None, coefficient_ring=None, names=None, category=None, default_prec=None, @@ -3736,12 +3703,11 @@ def format_names(N): if locals is not None: locals = cls._convert_locals_(locals) - return super(AsymptoticRing, - cls).__classcall__(cls, growth_group, coefficient_ring, - category=category, - default_prec=default_prec, - term_monoid_factory=term_monoid_factory, - locals=locals) + return super().__classcall__(cls, growth_group, coefficient_ring, + category=category, + default_prec=default_prec, + term_monoid_factory=term_monoid_factory, + locals=locals) def __init__(self, growth_group, coefficient_ring, category, default_prec, @@ -3770,9 +3736,8 @@ def __init__(self, growth_group, coefficient_ring, self._default_prec_ = default_prec self._term_monoid_factory_ = term_monoid_factory self._locals_ = locals - super(AsymptoticRing, self).__init__(base_ring=coefficient_ring, - category=category) - + super().__init__(base_ring=coefficient_ring, + category=category) @property def growth_group(self): @@ -3791,7 +3756,6 @@ def growth_group(self): """ return self._growth_group_ - @property def coefficient_ring(self): r""" @@ -3805,7 +3769,6 @@ def coefficient_ring(self): """ return self._coefficient_ring_ - @property def default_prec(self): r""" @@ -3826,7 +3789,6 @@ def default_prec(self): """ return self._default_prec_ - @property def term_monoid_factory(self): r""" @@ -3844,7 +3806,6 @@ def term_monoid_factory(self): """ return self._term_monoid_factory_ - def term_monoid(self, type): r""" Return the term monoid of this asymptotic ring of specified ``type``. @@ -3874,7 +3835,6 @@ def term_monoid(self, type): TermMonoid = self.term_monoid_factory return TermMonoid(type, asymptotic_ring=self) - def change_parameter(self, **kwds): r""" Return an asymptotic ring with a change in one or more of the given parameters. @@ -3908,7 +3868,7 @@ def change_parameter(self, **kwds): """ parameters = ('growth_group', 'coefficient_ring', 'default_prec', 'term_monoid_factory') - values = dict() + values = {} category = self.category() values['category'] = category locals = self._locals_ @@ -3998,7 +3958,6 @@ def _create_element_in_extension_(self, term, old_term_parent=None): coefficient_ring=term.parent().coefficient_ring) return parent(term, simplify=False, convert=False) - def _element_constructor_(self, data, simplify=True, convert=True): r""" Convert a given object to this asymptotic ring. @@ -4223,7 +4182,6 @@ def _element_constructor_(self, data, simplify=True, convert=True): return self.create_summand('exact', data) - def _coerce_map_from_(self, R): r""" Return whether ``R`` coerces into this asymptotic ring. @@ -4277,7 +4235,6 @@ def _coerce_map_from_(self, R): self.coefficient_ring.has_coerce_map_from(R.coefficient_ring): return True - def _repr_(self): r""" A representation string of this asymptotic ring. @@ -4303,7 +4260,6 @@ def _repr_(self): G = repr(self.growth_group) return 'Asymptotic Ring %s over %s' % (G, self.coefficient_ring) - def _an_element_(self): r""" Return an element of this asymptotic ring. @@ -4330,7 +4286,6 @@ def _an_element_(self): return self(E.an_element(), simplify=False, convert=False)**3 + \ self(O.an_element(), simplify=False, convert=False) - def some_elements(self): r""" Return some elements of this term monoid. @@ -4369,7 +4324,6 @@ def some_elements(self): for e, o in cantor_product( E.some_elements(), O.some_elements())) - def gens(self): r""" Return a tuple with generators of this asymptotic ring. @@ -4404,7 +4358,6 @@ def gens(self): coefficient=self.coefficient_ring(1)) for g in self.growth_group.gens_monomial()) - def gen(self, n=0): r""" Return the ``n``-th generator of this asymptotic ring. @@ -4425,7 +4378,6 @@ def gen(self, n=0): """ return self.gens()[n] - def ngens(self): r""" Return the number of generators of this asymptotic ring. @@ -4446,7 +4398,6 @@ def ngens(self): """ return len(self.growth_group.gens_monomial()) - def coefficients_of_generating_function(self, function, singularities, precision=None, return_singular_expansions=False, error_term=None): @@ -4592,7 +4543,6 @@ def coefficients_of_generating_function(self, function, singularities, precision else: return result - def create_summand(self, type, data=None, **kwds): r""" Create a simple asymptotic expansion consisting of a single @@ -4686,7 +4636,6 @@ def create_summand(self, type, data=None, **kwds): except ZeroCoefficientError: return self.zero() - def variable_names(self): r""" Return the names of the variables. @@ -4703,7 +4652,6 @@ def variable_names(self): """ return self.growth_group.variable_names() - def construction(self): r""" Return the construction of this asymptotic ring. @@ -4822,7 +4770,6 @@ class AsymptoticRingFunctor(ConstructionFunctor): rank = 13 - def __init__(self, growth_group, default_prec=None, category=None, term_monoid_factory=None, locals=None, @@ -4848,9 +4795,7 @@ def __init__(self, growth_group, self._locals_ = locals from sage.categories.rings import Rings - super(ConstructionFunctor, self).__init__( - Rings(), Rings()) - + super().__init__(Rings(), Rings()) def _repr_(self): r""" @@ -4877,7 +4822,6 @@ def _repr_(self): return '{}<{}>'.format(self.cls.__name__, self.growth_group._repr_(condense=True)) - def _apply_functor(self, coefficient_ring): r""" Apply this functor to the given ``coefficient_ring``. @@ -4930,7 +4874,6 @@ def _apply_functor(self, coefficient_ring): kwds[parameter] = value return self.cls(**kwds) - def merge(self, other): r""" Merge this functor with ``other`` if possible. @@ -5027,7 +4970,6 @@ def merge(self, other): category=category, cls=self.cls) - def __eq__(self, other): r""" Return whether this functor is equal to ``other``. @@ -5057,7 +4999,6 @@ def __eq__(self, other): and self._category_ == other._category_ and self.cls == other.cls) - def __ne__(self, other): r""" Return whether this functor is not equal to ``other``. diff --git a/src/sage/rings/asymptotic/asymptotics_multivariate_generating_functions.py b/src/sage/rings/asymptotic/asymptotics_multivariate_generating_functions.py index e80235a7d9d..ec4c5ce878f 100644 --- a/src/sage/rings/asymptotic/asymptotics_multivariate_generating_functions.py +++ b/src/sage/rings/asymptotic/asymptotics_multivariate_generating_functions.py @@ -336,7 +336,7 @@ def __init__(self, parent, numerator, denominator_factored, reduce=True): sage: f = FFPD(x, df) sage: TestSuite(f).run() """ - super(FractionWithFactoredDenominator, self).__init__(parent) + super().__init__(parent) from sage.rings.semirings.non_negative_integer_semiring import NN self._numerator = parent._numerator_ring(numerator) @@ -1940,7 +1940,7 @@ def asymptotics_smooth(self, p, alpha, N, asy_var, coordinate=None, sub_final=[Tstar, atP], rekey=AA) Phitu_derivs = diff_all(Phitu, T, N - 1 + v, sub=hderivs1, sub_final=[Tstar, atP], - zero_order=v + 1 , rekey=BB) + zero_order=v + 1, rekey=BB) AABB_derivs = At_derivs AABB_derivs.update(Phitu_derivs) AABB_derivs[AA] = At.subs(Tstar).subs(atP) @@ -2007,7 +2007,7 @@ def asymptotics_smooth(self, p, alpha, N, asy_var, coordinate=None, AABB_derivs[BB] = Phitu.subs(Tstar).subs(atP) if verbose: print("Computing second order differential operator actions...") - DD = diff_op(AA, BB, AABB_derivs, T, a_inv, 1 , N) + DD = diff_op(AA, BB, AABB_derivs, T, a_inv, 1, N) # Plug above into asymptotic formula. L = [] @@ -3066,8 +3066,8 @@ def __classcall_private__(cls, denominator_ring, numerator_ring=None, category=N 'denominator ring {}'.format( numerator_ring, denominator_ring)) category = Rings().or_subcategory(category) - return super(FractionWithFactoredDenominatorRing, cls).__classcall__(cls, - denominator_ring, numerator_ring, category) + return super().__classcall__(cls, denominator_ring, + numerator_ring, category) def __init__(self, denominator_ring, numerator_ring=None, category=None): r""" @@ -3792,7 +3792,7 @@ def subs_all(f, sub, simplify=False): sage: var('x, y') (x, y) sage: a = {'foo': x**2 + y**2, 'bar': x - y} - sage: b = {x: 1 , y: 2} + sage: b = {x: 1, y: 2} sage: subs_all(a, b) {'bar': -1, 'foo': 5} """ diff --git a/src/sage/rings/asymptotic/growth_group_cartesian.py b/src/sage/rings/asymptotic/growth_group_cartesian.py index 3f2e6d7e692..645d9723e68 100644 --- a/src/sage/rings/asymptotic/growth_group_cartesian.py +++ b/src/sage/rings/asymptotic/growth_group_cartesian.py @@ -512,7 +512,7 @@ def convert_factors(data, raw_data): # room for other parents (e.g. polynomial ring et al.) try: - return super(GenericProduct, self)._element_constructor_(data) + return super()._element_constructor_(data) except (TypeError, ValueError): pass if isinstance(data, (tuple, list, CartesianProduct.Element)): @@ -1441,9 +1441,7 @@ def __init__(self, sets, category, **kwargs): sage: type(GrowthGroup('x^ZZ * log(x)^ZZ')) # indirect doctest """ - super(UnivariateProduct, self).__init__( - sets, category, order='lex', **kwargs) - + super().__init__(sets, category, order='lex', **kwargs) CartesianProduct = CartesianProductGrowthGroups @@ -1474,8 +1472,6 @@ def __init__(self, sets, category, **kwargs): sage: type(GrowthGroup('x^ZZ * y^ZZ')) # indirect doctest """ - super(MultivariateProduct, self).__init__( - sets, category, order='product', **kwargs) - + super().__init__(sets, category, order='product', **kwargs) CartesianProduct = CartesianProductGrowthGroups diff --git a/src/sage/rings/asymptotic/misc.py b/src/sage/rings/asymptotic/misc.py index 33388f9f183..8117d8ede30 100644 --- a/src/sage/rings/asymptotic/misc.py +++ b/src/sage/rings/asymptotic/misc.py @@ -17,7 +17,7 @@ ============================== """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2015 Daniel Krenn # # This program is free software: you can redistribute it and/or modify @@ -25,8 +25,7 @@ # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # https://www.gnu.org/licenses/ -#***************************************************************************** - +# **************************************************************************** from sage.misc.cachefunc import cached_method from sage.structure.sage_object import SageObject @@ -269,7 +268,7 @@ def is_balanced(s): return False return bool(open == 0) - factors = list() + factors = [] balanced = True if string and op is not None and string.startswith(op): raise ValueError("'%s' is invalid since it starts with a '%s'." % @@ -357,10 +356,8 @@ def add_parentheses(s, op): if any(sig in s for sig in signals) or latex and s.startswith(r'\frac'): if latex: return r'\left({}\right)'.format(s) - else: - return '({})'.format(s) - else: - return s + return '({})'.format(s) + return s return add_parentheses(left, op) + op + add_parentheses(right, op) @@ -843,7 +840,7 @@ def __init__(self, asymptotic_ring=None, var=None, exact_part=0): exact_part = asymptotic_ring.zero() self.exact_part = exact_part - super(NotImplementedOZero, self).__init__(message) + super().__init__(message) class NotImplementedBZero(NotImplementedError): @@ -913,7 +910,7 @@ def __init__(self, asymptotic_ring=None, var=None, exact_part=0): exact_part = asymptotic_ring.zero() self.exact_part = exact_part - super(NotImplementedBZero, self).__init__(message) + super().__init__(message) def transform_category(category, @@ -1081,7 +1078,7 @@ def __getitem__(self, key): """ try: - return super(Locals, self).__getitem__(key) + return super().__getitem__(key) except KeyError as ke: try: return self.default_locals()[key] @@ -1188,8 +1185,7 @@ def _convert_locals_(locals): """ if locals is None: return Locals() - else: - return Locals(locals) + return Locals(locals) def locals(self, locals=None): r""" diff --git a/src/sage/rings/asymptotic/term_monoid.py b/src/sage/rings/asymptotic/term_monoid.py index 36b3d2e4f33..b5b39091371 100644 --- a/src/sage/rings/asymptotic/term_monoid.py +++ b/src/sage/rings/asymptotic/term_monoid.py @@ -1864,7 +1864,7 @@ def _element_constructor_(self, data, *args, **kwds): f'takes one positional argument, ' f'another positional argument is deprecated, ' f'but {len(args)+1} were given') - elif len(args) == 1: + if len(args) == 1: from sage.misc.superseded import deprecation deprecation(32215, "Passing 'coefficient' as a positional argument is deprecated; " @@ -4877,7 +4877,7 @@ def _absorb_(self, other): if not (self.growth >= other.growth): raise ArithmeticError(f'{self} cannot absorb {other}') - valid_from_new = dict() + valid_from_new = {} for variable_name in set().union(self.valid_from.keys(), other.valid_from.keys()): if variable_name in self.valid_from and other.valid_from: valid_from_new[variable_name] = (max(self.valid_from[variable_name], other.valid_from[variable_name])) @@ -5339,7 +5339,7 @@ def __init__(self, name, sage: type(MyTermMonoid('B', G, QQ)) """ - super(TermMonoidFactory, self).__init__(name) + super().__init__(name) if exact_term_monoid_class is None: exact_term_monoid_class = ExactTermMonoid diff --git a/src/sage/rings/finite_rings/finite_field_constructor.py b/src/sage/rings/finite_rings/finite_field_constructor.py index e6129689ad5..685c385cf2e 100644 --- a/src/sage/rings/finite_rings/finite_field_constructor.py +++ b/src/sage/rings/finite_rings/finite_field_constructor.py @@ -494,19 +494,20 @@ def __init__(self, *args, **kwds): super().__init__(*args, **kwds) def create_key_and_extra_args(self, order, name=None, modulus=None, names=None, - impl=None, proof=None, check_irreducible=True, + impl=None, proof=None, + check_prime=True, check_irreducible=True, prefix=None, repr=None, elem_cache=None, **kwds): """ EXAMPLES:: sage: GF.create_key_and_extra_args(9, 'a') - ((9, ('a',), x^2 + 2*x + 2, 'givaro', 3, 2, True, None, 'poly', True), {}) + ((9, ('a',), x^2 + 2*x + 2, 'givaro', 3, 2, True, None, 'poly', True, True, True), {}) The order `q` can also be given as a pair `(p,n)`:: sage: GF.create_key_and_extra_args((3, 2), 'a') - ((9, ('a',), x^2 + 2*x + 2, 'givaro', 3, 2, True, None, 'poly', True), {}) + ((9, ('a',), x^2 + 2*x + 2, 'givaro', 3, 2, True, None, 'poly', True, True, True), {}) We do not take invalid keyword arguments and raise a value error to better ensure uniqueness:: @@ -520,9 +521,9 @@ def create_key_and_extra_args(self, order, name=None, modulus=None, names=None, using givaro:: sage: GF.create_key_and_extra_args(16, 'a', impl='ntl', repr='poly') - ((16, ('a',), x^4 + x + 1, 'ntl', 2, 4, True, None, None, None), {}) + ((16, ('a',), x^4 + x + 1, 'ntl', 2, 4, True, None, None, None, True, True), {}) sage: GF.create_key_and_extra_args(16, 'a', impl='ntl', elem_cache=False) - ((16, ('a',), x^4 + x + 1, 'ntl', 2, 4, True, None, None, None), {}) + ((16, ('a',), x^4 + x + 1, 'ntl', 2, 4, True, None, None, None, True, True), {}) sage: GF(16, impl='ntl') is GF(16, impl='ntl', repr='foo') True @@ -541,61 +542,56 @@ def create_key_and_extra_args(self, order, name=None, modulus=None, names=None, but we ignore them as they are not used, see :trac:`21433`:: sage: GF.create_key_and_extra_args(9, 'a', structure=None) - ((9, ('a',), x^2 + 2*x + 2, 'givaro', 3, 2, True, None, 'poly', True), {}) + ((9, ('a',), x^2 + 2*x + 2, 'givaro', 3, 2, True, None, 'poly', True, True, True), {}) TESTS:: - sage: GF.create_key_and_extra_args((6, 1), 'a') + sage: GF((6, 1), 'a') # implicit doctest Traceback (most recent call last): ... ValueError: the order of a finite field must be a prime power - sage: GF.create_key_and_extra_args((9, 1), 'a') + sage: GF((9, 1), 'a') # implicit doctest Traceback (most recent call last): ... ValueError: the order of a finite field must be a prime power - sage: GF.create_key_and_extra_args((5, 0), 'a') + sage: GF((5, 0), 'a') # implicit doctest Traceback (most recent call last): ... ValueError: the order of a finite field must be a prime power - sage: GF.create_key_and_extra_args((3, 2, 1), 'a') + sage: GF((3, 2, 1), 'a') # implicit doctest Traceback (most recent call last): ... ValueError: wrong input for finite field constructor """ import sage.arith.all - from sage.structure.proof.all import WithProof, arithmetic - if proof is None: - proof = arithmetic() + for key, val in kwds.items(): if key not in ['structure', 'implementation', 'prec', 'embedding', 'latex_names']: raise TypeError("create_key_and_extra_args() 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 prescribed %s is not implemented" % key) + + from sage.structure.proof.all import WithProof, arithmetic + if proof is None: + proof = arithmetic() with WithProof('arithmetic', proof): if isinstance(order, tuple): if len(order) != 2: raise ValueError('wrong input for finite field constructor') - p, n = order - p = Integer(p) - if not p.is_prime() or n < 1: + p, n = map(Integer, order) + if p < 2 or n < 1: raise ValueError("the order of a finite field must be a prime power") - n = Integer(n) order = p**n else: order = Integer(order) - if order <= 1: + if order < 2: raise ValueError("the order of a finite field must be at least 2") - if order.is_prime(): - p = order - n = Integer(1) - else: - p, n = order.is_prime_power(get_data=True) - if n == 0: - raise ValueError("the order of a finite field must be a prime power") + p, n = order.perfect_power() # at this point, order = p**n + # note that we haven't tested p for primality if n == 1: if impl is None: @@ -651,11 +647,11 @@ def create_key_and_extra_args(self, order, name=None, modulus=None, names=None, if modulus.degree() != n: raise ValueError("the degree of the modulus does not equal the degree of the field") - if check_irreducible and not modulus.is_irreducible(): - raise ValueError("finite field modulus must be irreducible but it is not") # If modulus is x - 1 for impl="modn", set it to None - if impl == 'modn' and modulus[0] == -1: + if impl == 'modn' and modulus.list() == [-1,1]: modulus = None + if modulus is None: + check_irreducible = False # Check extra arguments for givaro and setup their defaults # TODO: ntl takes a repr, but ignores it @@ -669,7 +665,7 @@ def create_key_and_extra_args(self, order, name=None, modulus=None, names=None, repr = None elem_cache = None - return (order, name, modulus, impl, p, n, proof, prefix, repr, elem_cache), {} + return (order, name, modulus, impl, p, n, proof, prefix, repr, elem_cache, check_prime, check_irreducible), {} def create_object(self, version, key, **kwds): """ @@ -734,6 +730,7 @@ def create_object(self, version, key, **kwds): # as they are otherwise ignored repr = 'poly' elem_cache = (order < 500) + check_prime = check_irreducible = False elif len(key) == 8: # For backward compatibility of pickles (see trac #21433) order, name, modulus, impl, _, p, n, proof = key @@ -742,8 +739,19 @@ def create_object(self, version, key, **kwds): # as they are otherwise ignored repr = kwds.get('repr', 'poly') elem_cache = kwds.get('elem_cache', (order < 500)) - else: + check_prime = check_irreducible = False + elif len(key) == 10: order, name, modulus, impl, p, n, proof, prefix, repr, elem_cache = key + check_prime = check_irreducible = False + else: + order, name, modulus, impl, p, n, proof, prefix, repr, elem_cache, check_prime, check_irreducible = key + + from sage.structure.proof.all import WithProof + with WithProof('arithmetic', proof): + if check_prime and not p.is_prime(): + raise ValueError("the order of a finite field must be a prime power") + if check_irreducible and not modulus.is_irreducible(): + raise ValueError("finite field modulus must be irreducible but it is not") if impl == 'modn': if n != 1: @@ -759,7 +767,6 @@ def create_object(self, version, key, **kwds): # passed in when checking for primality, factoring, etc. # Otherwise, we would have to complicate all of their # constructors with check options. - from sage.structure.proof.all import WithProof with WithProof('arithmetic', proof): if impl == 'givaro': K = FiniteField_givaro(order, name, modulus, repr, elem_cache) diff --git a/src/sage/rings/ideal.py b/src/sage/rings/ideal.py index 8bb1de564f3..8e584ee0141 100644 --- a/src/sage/rings/ideal.py +++ b/src/sage/rings/ideal.py @@ -1203,6 +1203,42 @@ def _macaulay2_init_(self, macaulay2=None): gens = ['0'] return macaulay2.ideal(gens) + def free_resolution(self, *args, **kwds): + r""" + Return a free resolution of ``self``. + + For input options, see + :class:`~sage.homology.free_resolution.FreeResolution`. + + EXAMPLES:: + + sage: R. = PolynomialRing(QQ) + sage: I = R.ideal([x^4 + 3*x^2 + 2]) + sage: I.free_resolution() + S^1 <-- S^1 <-- 0 + """ + if not self.is_principal(): + raise NotImplementedError("the ideal must be a principal ideal") + from sage.homology.free_resolution import FiniteFreeResolution_free_module + return FiniteFreeResolution_free_module(self, *args, **kwds) + + def graded_free_resolution(self, *args, **kwds): + r""" + Return a graded free resolution of ``self``. + + For input options, see + :class:`~sage.homology.graded_resolution.GradedFiniteFreeResolution`. + + EXAMPLES:: + + sage: R. = PolynomialRing(QQ) + sage: I = R.ideal([x^3]) + sage: I.graded_free_resolution() + S(0) <-- S(-3) <-- 0 + """ + from sage.homology.graded_resolution import GradedFiniteFreeResolution_free_module + return GradedFiniteFreeResolution_free_module(self, *args, **kwds) + class Ideal_principal(Ideal_generic): """ @@ -1257,10 +1293,11 @@ def is_principal(self): """ return True - def gen(self): + def gen(self, i=0): r""" - Returns the generator of the principal ideal. The generators are - elements of the ring containing the ideal. + Return the generator of the principal ideal. + + The generator is an element of the ring containing the ideal. EXAMPLES: @@ -1288,6 +1325,8 @@ def gen(self): sage: b.base_ring() Rational Field """ + if i: + raise ValueError(f"i (={i}) must be 0") return self.gens()[0] def __contains__(self, x): @@ -1816,3 +1855,4 @@ def FieldIdeal(R): raise TypeError("Cannot construct field ideal for R.base_ring().order()==infinity") return R.ideal([x**q - x for x in R.gens() ]) + diff --git a/src/sage/rings/integer_ring.pyx b/src/sage/rings/integer_ring.pyx index 889a16295a7..7ba77ca2aa2 100644 --- a/src/sage/rings/integer_ring.pyx +++ b/src/sage/rings/integer_ring.pyx @@ -1506,7 +1506,7 @@ cdef class IntegerRing_class(PrincipalIdealDomain): EXAMPLES:: - sage: polymake(ZZ) # optional - polymake # indirect doctest + sage: polymake(ZZ) # optional - jupymake # indirect doctest Integer """ diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index c5034928ab6..6bfb8730c39 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -2,9 +2,15 @@ r""" Lazy Series -A lazy series is a series whose coefficients are computed on demand. -Therefore, unlike the usual Laurent/power/etc. series in Sage, -lazy series have infinite precision. +Coefficients of lazy series are computed on demand. They have +infinite precision, although equality can only be decided in special +cases. + +AUTHORS: + +- Kwankyu Lee (2019-02-24): initial version +- Tejasvi Chebrolu, Martin Rubey, Travis Scrimshaw (2021-08): + refactored and expanded functionality EXAMPLES: @@ -57,6 +63,31 @@ sage: g = (f + f^-1)*(f - f^-1); g 4*z + 6*z^2 + 8*z^3 + 19*z^4 + 38*z^5 + 71*z^6 + O(z^7) +We call lazy power series whose coefficients are known to be +eventually constant 'exact'. In some cases, computations with such +series are much faster. Moreover, these are the series where +equality can be decided. For example:: + + sage: L. = LazyTaylorSeriesRing(ZZ) + sage: f = 1 + 2*z^2 / (1 - z) + sage: f - 2 / (1 - z) + 1 + 2*z + 0 + +However, multivariate Taylor series are actually represented as +streams of multivariate polynomials. Therefore, the only exact +series in this case are polynomials:: + + sage: L. = LazyTaylorSeriesRing(ZZ) + sage: 1 / (1-x) + 1 + x + x^2 + x^3 + x^4 + x^5 + x^6 + O(x,y)^7 + +A similar statement is true for lazy symmetric functions:: + + sage: h = SymmetricFunctions(QQ).h() + sage: L = LazySymmetricFunctions(h) + sage: 1 / (1-L(h[1])) + h[] + h[1] + (h[1,1]) + (h[1,1,1]) + (h[1,1,1,1]) + (h[1,1,1,1,1]) + (h[1,1,1,1,1,1]) + O^7 + We can change the base ring:: sage: h = g.change_ring(QQ) @@ -69,16 +100,12 @@ sage: hinv.valuation() -1 -AUTHORS: - -- Kwankyu Lee (2019-02-24): initial version -- Tejasvi Chebrolu, Martin Rubey, Travis Scrimshaw (2021-08): - refactored and expanded functionality - """ # **************************************************************************** # Copyright (C) 2019 Kwankyu Lee +# 2022 Martin Rubey +# 2022 Travis Scrimshaw # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -90,9 +117,11 @@ from sage.structure.element import Element, parent from sage.structure.richcmp import op_EQ, op_NE from sage.functions.other import factorial +from sage.misc.misc_c import prod from sage.arith.power import generic_power from sage.rings.infinity import infinity from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.data_structures.stream import ( @@ -111,7 +140,8 @@ Stream_shift, Stream_function, Stream_dirichlet_convolve, - Stream_dirichlet_invert + Stream_dirichlet_invert, + Stream_plethysm ) class LazyModuleElement(Element): @@ -214,7 +244,7 @@ def __getitem__(self, n): [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] """ - R = self.parent()._coeff_ring + R = self.parent()._internal_poly_ring.base_ring() if isinstance(n, slice): if n.stop is None: raise NotImplementedError("cannot list an infinite set") @@ -300,7 +330,8 @@ def map_coefficients(self, func, ring=None): degree=coeff_stream._degree, constant=BR(c)) return P.element_class(P, coeff_stream) - coeff_stream = Stream_map_coefficients(self._coeff_stream, func, P._coeff_ring) + R = P._internal_poly_ring.base_ring() + coeff_stream = Stream_map_coefficients(self._coeff_stream, func, R) return P.element_class(P, coeff_stream) def truncate(self, d): @@ -318,12 +349,12 @@ def truncate(self, d): sage: L. = LazyLaurentSeriesRing(ZZ, sparse=False) sage: alpha = 1/(1-z) sage: alpha - 1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7) + 1 + z + z^2 + O(z^3) sage: beta = alpha.truncate(5) sage: beta 1 + z + z^2 + z^3 + z^4 sage: alpha - beta - z^5 + z^6 + O(z^7) + z^5 + z^6 + z^7 + O(z^8) sage: M = L(lambda n: n, valuation=0); M z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) sage: M.truncate(4) @@ -348,7 +379,7 @@ def truncate(self, d): v = coeff_stream._approximate_order initial_coefficients = [coeff_stream[i] for i in range(v, d)] return P.element_class(P, Stream_exact(initial_coefficients, P._sparse, - order=v)) + order=v)) def shift(self, n): r""" @@ -690,6 +721,22 @@ def define(self, s): sage: [F[i] for i in range(1, 16)] [1, 1, 1, 3, 1, 5, 1, 10, 3, 5, 1, 24, 1, 5, 5] + We can compute the Frobenius character of unlabeled trees:: + + sage: m = SymmetricFunctions(QQ).m() + sage: s = SymmetricFunctions(QQ).s() + sage: L = LazySymmetricFunctions(m) + sage: E = L(lambda n: s[n], valuation=0) + sage: X = L(s[1]) + sage: A = L(None); A.define(X*E(A, check=False)) + sage: A[:6] + [0, + m[1], + 2*m[1, 1] + m[2], + 9*m[1, 1, 1] + 5*m[2, 1] + 2*m[3], + 64*m[1, 1, 1, 1] + 34*m[2, 1, 1] + 18*m[2, 2] + 13*m[3, 1] + 4*m[4], + 625*m[1, 1, 1, 1, 1] + 326*m[2, 1, 1, 1] + 171*m[2, 2, 1] + 119*m[3, 1, 1] + 63*m[3, 2] + 35*m[4, 1] + 9*m[5]] + TESTS:: sage: L. = LazyLaurentSeriesRing(ZZ, sparse=True) @@ -722,6 +769,9 @@ def define(self, s): raise ValueError("series already defined") self._coeff_stream._target = s._coeff_stream + # an alias for compatibility with padics + set = define + def _repr_(self): r""" Return a string representation of ``self``. @@ -858,7 +908,6 @@ def _unicode_art_(self): return UnicodeArt('Uninitialized Lazy Laurent Series') return self._format_series(unicode_art, True) - def change_ring(self, ring): r""" Return ``self`` with coefficients converted to elements of ``ring``. @@ -906,6 +955,16 @@ def change_ring(self, ring): Lazy Dirichlet Series Ring in z over Rational Field sage: t^-1 1/2 - 1/2/2^z - 1/2/3^z - 1/2/5^z + 1/2/6^z - 1/2/7^z + O(1/(8^z)) + + A Taylor series example:: + + sage: L. = LazyTaylorSeriesRing(ZZ) + sage: s = 2 + z + sage: t = s.change_ring(QQ) + sage: t^-1 + 1/2 - 1/4*z + 1/8*z^2 - 1/16*z^3 + 1/32*z^4 - 1/64*z^5 + 1/128*z^6 + O(z^7) + sage: t.parent() + Lazy Taylor Series Ring in z over Rational Field """ P = self.parent() Q = type(P)(ring, names=P.variable_names(), sparse=P._sparse) @@ -988,16 +1047,19 @@ def _add_(self, other): and isinstance(right, Stream_exact)): approximate_order = min(left.order(), right.order()) degree = max(left._degree, right._degree) - initial_coefficients = [left[i] + right[i] for i in range(approximate_order, degree)] + initial_coefficients = [left[i] + right[i] + for i in range(approximate_order, degree)] constant = left._constant + right._constant if not any(initial_coefficients) and not constant: return P.zero() - coeff_stream = Stream_exact(initial_coefficients, P._sparse, - constant=constant, - degree=degree, - order=approximate_order) + coeff_stream = Stream_exact(initial_coefficients, + P._sparse, + constant=constant, + degree=degree, + order=approximate_order) return P.element_class(P, coeff_stream) - return P.element_class(P, Stream_add(self._coeff_stream, other._coeff_stream)) + return P.element_class(P, Stream_add(self._coeff_stream, + other._coeff_stream)) def _sub_(self, other): """ @@ -1068,10 +1130,11 @@ def _sub_(self, other): constant = left._constant - right._constant if not any(initial_coefficients) and not constant: return P.zero() - coeff_stream = Stream_exact(initial_coefficients, P._sparse, - constant=constant, - degree=degree, - order=approximate_order) + coeff_stream = Stream_exact(initial_coefficients, + P._sparse, + constant=constant, + degree=degree, + order=approximate_order) return P.element_class(P, coeff_stream) if left == right: return P.zero() @@ -1212,8 +1275,10 @@ def _acted_upon_(self, scalar, self_on_left): else: c = scalar * coeff_stream._constant initial_coefficients = [scalar * val for val in init_coeffs] - return P.element_class(P, Stream_exact(initial_coefficients, P._sparse, - order=v, constant=c, + return P.element_class(P, Stream_exact(initial_coefficients, + P._sparse, + order=v, + constant=c, degree=coeff_stream._degree)) if self_on_left or R.is_commutative(): return P.element_class(P, Stream_lmul(coeff_stream, scalar)) @@ -1272,7 +1337,8 @@ def _neg_(self): if isinstance(coeff_stream, Stream_exact): initial_coefficients = [-v for v in coeff_stream._initial_coefficients] constant = -coeff_stream._constant - coeff_stream = Stream_exact(initial_coefficients, P._sparse, + coeff_stream = Stream_exact(initial_coefficients, + P._sparse, constant=constant, degree=coeff_stream._degree, order=coeff_stream.order()) @@ -1302,10 +1368,10 @@ def exp(self): ... ValueError: can only compose with a positive valuation series - sage: L. = LazyTaylorSeriesRing(QQ) # not tested - sage: exp(x+y)[4].factor() # not tested + sage: L. = LazyTaylorSeriesRing(QQ) + sage: exp(x+y)[4].factor() (1/24) * (x + y)^4 - sage: exp(x/(1-y)).finite_part(3) # not tested + sage: exp(x/(1-y)).polynomial(3) 1/6*x^3 + x^2*y + x*y^2 + 1/2*x^2 + x*y + x + 1 TESTS:: @@ -1329,9 +1395,9 @@ def log(self): sage: log(1/(1-z)) z + 1/2*z^2 + 1/3*z^3 + 1/4*z^4 + 1/5*z^5 + 1/6*z^6 + 1/7*z^7 + O(z^8) - sage: L. = LazyTaylorSeriesRing(QQ) # not tested - sage: log((1 + x/(1-y))).finite_part(3) # not tested - 1/3*x^3 - x^2*y + x*y^2 + (-1/2)*x^2 + x*y + x + sage: L. = LazyTaylorSeriesRing(QQ) + sage: log((1 + x/(1-y))).polynomial(3) + 1/3*x^3 - x^2*y + x*y^2 - 1/2*x^2 + x*y + x TESTS:: @@ -1366,9 +1432,9 @@ def sin(self): ... ValueError: can only compose with a positive valuation series - sage: L. = LazyTaylorSeriesRing(QQ) # not tested - sage: sin(x/(1-y)).finite_part(3) # not tested - (-1/6)*x^3 + x*y^2 + x*y + x + sage: L. = LazyTaylorSeriesRing(QQ) + sage: sin(x/(1-y)).polynomial(3) + -1/6*x^3 + x*y^2 + x*y + x TESTS:: @@ -1392,9 +1458,9 @@ def cos(self): sage: cos(z) 1 - 1/2*z^2 + 1/24*z^4 - 1/720*z^6 + O(z^7) - sage: L. = LazyTaylorSeriesRing(QQ) # not tested - sage: cos(x/(1-y)).finite_part(4) # not tested - 1/24*x^4 + (-3/2)*x^2*y^2 - x^2*y + (-1/2)*x^2 + 1 + sage: L. = LazyTaylorSeriesRing(QQ) + sage: cos(x/(1-y)).polynomial(4) + 1/24*x^4 - 3/2*x^2*y^2 - x^2*y - 1/2*x^2 + 1 TESTS:: @@ -1412,14 +1478,14 @@ def tan(self): r""" Return the tangent of ``self``. - EXAMPLES: + EXAMPLES:: sage: L. = LazyLaurentSeriesRing(QQ) sage: tan(z) z + 1/3*z^3 + 2/15*z^5 + 17/315*z^7 + O(z^8) - sage: L. = LazyTaylorSeriesRing(QQ) # not tested - sage: tan(x/(1-y)).finite_part(5) # not tested + sage: L. = LazyTaylorSeriesRing(QQ) + sage: tan(x/(1-y)).polynomial(5) 2/15*x^5 + 2*x^3*y^2 + x*y^4 + x^3*y + x*y^3 + 1/3*x^3 + x*y^2 + x*y + x TESTS:: @@ -1484,8 +1550,8 @@ def sec(self): sage: sec(z) 1 + 1/2*z^2 + 5/24*z^4 + 61/720*z^6 + O(z^7) - sage: L. = LazyTaylorSeriesRing(QQ) # not tested - sage: sec(x/(1-y)).finite_part(4) # not tested + sage: L. = LazyTaylorSeriesRing(QQ) + sage: sec(x/(1-y)).polynomial(4) 5/24*x^4 + 3/2*x^2*y^2 + x^2*y + 1/2*x^2 + 1 TESTS:: @@ -1508,8 +1574,8 @@ def arcsin(self): sage: arcsin(z) z + 1/6*z^3 + 3/40*z^5 + 5/112*z^7 + O(z^8) - sage: L. = LazyTaylorSeriesRing(QQ) # not tested - sage: asin(x/(1-y)) # not tested + sage: L. = LazyTaylorSeriesRing(QQ) + sage: asin(x/(1-y)) x + x*y + (1/6*x^3+x*y^2) + (1/2*x^3*y+x*y^3) + (3/40*x^5+x^3*y^2+x*y^4) + (3/8*x^5*y+5/3*x^3*y^3+x*y^5) + (5/112*x^7+9/8*x^5*y^2+5/2*x^3*y^4+x*y^6) + O(x,y)^8 @@ -1546,8 +1612,8 @@ def arccos(self): sage: arccos(z/(1-z)) 1/2*pi - z - z^2 - 7/6*z^3 - 3/2*z^4 - 83/40*z^5 - 73/24*z^6 + O(z^7) - sage: L. = LazyTaylorSeriesRing(SR) # not tested - sage: arccos(x/(1-y)) # not tested + sage: L. = LazyTaylorSeriesRing(SR) + sage: arccos(x/(1-y)) 1/2*pi + (-x) + (-x*y) + ((-1/6)*x^3-x*y^2) + ((-1/2)*x^3*y-x*y^3) + ((-3/40)*x^5-x^3*y^2-x*y^4) + ((-3/8)*x^5*y+(-5/3)*x^3*y^3-x*y^5) + O(x,y)^7 @@ -1570,11 +1636,10 @@ def arctan(self): sage: arctan(z) z - 1/3*z^3 + 1/5*z^5 - 1/7*z^7 + O(z^8) - sage: L. = LazyTaylorSeriesRing(QQ) # not tested - sage: atan(x/(1-y)) # not tested - x + x*y + ((-1/3)*x^3+x*y^2) + (-x^3*y+x*y^3) - + (1/5*x^5+(-2)*x^3*y^2+x*y^4) + (x^5*y+(-10/3)*x^3*y^3+x*y^5) - + ((-1/7)*x^7+3*x^5*y^2+(-5)*x^3*y^4+x*y^6) + O(x,y)^8 + sage: L. = LazyTaylorSeriesRing(QQ) + sage: atan(x/(1-y)) + x + x*y + (-1/3*x^3+x*y^2) + (-x^3*y+x*y^3) + (1/5*x^5-2*x^3*y^2+x*y^4) + + (x^5*y-10/3*x^3*y^3+x*y^5) + (-1/7*x^7+3*x^5*y^2-5*x^3*y^4+x*y^6) + O(x,y)^8 TESTS:: @@ -1610,8 +1675,8 @@ def arccot(self): sage: arccot(z/(1-z)) 1/2*pi - z - z^2 - 2/3*z^3 + 4/5*z^5 + 4/3*z^6 + O(z^7) - sage: L. = LazyTaylorSeriesRing(SR) # not tested - sage: acot(x/(1-y)) # not tested + sage: L. = LazyTaylorSeriesRing(SR) + sage: acot(x/(1-y)) 1/2*pi + (-x) + (-x*y) + (1/3*x^3-x*y^2) + (x^3*y-x*y^3) + ((-1/5)*x^5+2*x^3*y^2-x*y^4) + (-x^5*y+10/3*x^3*y^3-x*y^5) + O(x,y)^7 @@ -1636,8 +1701,8 @@ def sinh(self): sage: sinh(z) z + 1/6*z^3 + 1/120*z^5 + 1/5040*z^7 + O(z^8) - sage: L. = LazyTaylorSeriesRing(QQ) # not tested - sage: sinh(x/(1-y)) # not tested + sage: L. = LazyTaylorSeriesRing(QQ) + sage: sinh(x/(1-y)) x + x*y + (1/6*x^3+x*y^2) + (1/2*x^3*y+x*y^3) + (1/120*x^5+x^3*y^2+x*y^4) + (1/24*x^5*y+5/3*x^3*y^3+x*y^5) + (1/5040*x^7+1/8*x^5*y^2+5/2*x^3*y^4+x*y^6) + O(x,y)^8 @@ -1664,8 +1729,8 @@ def cosh(self): sage: cosh(z) 1 + 1/2*z^2 + 1/24*z^4 + 1/720*z^6 + O(z^7) - sage: L. = LazyTaylorSeriesRing(QQ) # not tested - sage: cosh(x/(1-y)) # not tested + sage: L. = LazyTaylorSeriesRing(QQ) + sage: cosh(x/(1-y)) 1 + 1/2*x^2 + x^2*y + (1/24*x^4+3/2*x^2*y^2) + (1/6*x^4*y+2*x^2*y^3) + (1/720*x^6+5/12*x^4*y^2+5/2*x^2*y^4) + O(x,y)^7 @@ -1691,11 +1756,10 @@ def tanh(self): sage: tanh(z) z - 1/3*z^3 + 2/15*z^5 - 17/315*z^7 + O(z^8) - sage: L. = LazyTaylorSeriesRing(QQ) # not tested - sage: tanh(x/(1-y)) # not tested - x + x*y + ((-1/3)*x^3+x*y^2) + (-x^3*y+x*y^3) - + (2/15*x^5+(-2)*x^3*y^2+x*y^4) + (2/3*x^5*y+(-10/3)*x^3*y^3+x*y^5) - + ((-17/315)*x^7+2*x^5*y^2+(-5)*x^3*y^4+x*y^6) + O(x,y)^8 + sage: L. = LazyTaylorSeriesRing(QQ) + sage: tanh(x/(1-y)) + x + x*y + (-1/3*x^3+x*y^2) + (-x^3*y+x*y^3) + (2/15*x^5-2*x^3*y^2+x*y^4) + + (2/3*x^5*y-10/3*x^3*y^3+x*y^5) + (-17/315*x^7+2*x^5*y^2-5*x^3*y^4+x*y^6) + O(x,y)^8 TESTS:: @@ -1755,11 +1819,10 @@ def sech(self): sage: sech(z) 1 - 1/2*z^2 + 5/24*z^4 - 61/720*z^6 + O(z^7) - sage: L. = LazyTaylorSeriesRing(QQ) # not tested - sage: sech(x/(1-y)) # not tested - 1 + ((-1/2)*x^2) + (-x^2*y) + (5/24*x^4+(-3/2)*x^2*y^2) - + (5/6*x^4*y+(-2)*x^2*y^3) + ((-61/720)*x^6+25/12*x^4*y^2+(-5/2)*x^2*y^4) - + O(x,y)^7 + sage: L. = LazyTaylorSeriesRing(QQ) + sage: sech(x/(1-y)) + 1 + (-1/2*x^2) + (-x^2*y) + (5/24*x^4-3/2*x^2*y^2) + (5/6*x^4*y-2*x^2*y^3) + + (-61/720*x^6+25/12*x^4*y^2-5/2*x^2*y^4) + O(x,y)^7 TESTS:: @@ -1797,7 +1860,6 @@ def csch(self): sage: L. = LazyLaurentSeriesRing(SR); x = var("x") sage: csch(z)[0:6] == csch(x).series(x, 6).coefficients(sparse=False) True - """ from sage.arith.misc import bernoulli from .lazy_series_ring import LazyLaurentSeriesRing @@ -1822,11 +1884,15 @@ def arcsinh(self): sage: asinh(z) z - 1/6*z^3 + 3/40*z^5 - 5/112*z^7 + O(z^8) - sage: L. = LazyTaylorSeriesRing(QQ) # not tested - sage: asinh(x/(1-y)) # not tested - x + x*y + ((-1/6)*x^3+x*y^2) + ((-1/2)*x^3*y+x*y^3) - + (3/40*x^5-x^3*y^2+x*y^4) + (3/8*x^5*y+(-5/3)*x^3*y^3+x*y^5) - + ((-5/112)*x^7+9/8*x^5*y^2+(-5/2)*x^3*y^4+x*y^6) + O(x,y)^8 + ``arcsinh`` is an alias:: + + sage: arcsinh(z) + z - 1/6*z^3 + 3/40*z^5 - 5/112*z^7 + O(z^8) + + sage: L. = LazyTaylorSeriesRing(QQ) + sage: asinh(x/(1-y)) + x + x*y + (-1/6*x^3+x*y^2) + (-1/2*x^3*y+x*y^3) + (3/40*x^5-x^3*y^2+x*y^4) + + (3/8*x^5*y-5/3*x^3*y^3+x*y^5) + (-5/112*x^7+9/8*x^5*y^2-5/2*x^3*y^4+x*y^6) + O(x,y)^8 TESTS:: @@ -1856,8 +1922,13 @@ def arctanh(self): sage: atanh(z) z + 1/3*z^3 + 1/5*z^5 + 1/7*z^7 + O(z^8) - sage: L. = LazyTaylorSeriesRing(QQ) # not tested - sage: atanh(x/(1-y)) # not tested + ``arctanh`` is an alias:: + + sage: arctanh(z) + z + 1/3*z^3 + 1/5*z^5 + 1/7*z^7 + O(z^8) + + sage: L. = LazyTaylorSeriesRing(QQ) + sage: atanh(x/(1-y)) x + x*y + (1/3*x^3+x*y^2) + (x^3*y+x*y^3) + (1/5*x^5+2*x^3*y^2+x*y^4) + (x^5*y+10/3*x^3*y^3+x*y^5) + (1/7*x^7+3*x^5*y^2+5*x^3*y^4+x*y^6) + O(x,y)^8 @@ -1891,6 +1962,11 @@ def hypergeometric(self, a, b): sage: z.hypergeometric([], []) - exp(z) O(z^7) + sage: L. = LazyTaylorSeriesRing(QQ) + sage: (x+y).hypergeometric([1, 1], [1]).polynomial(4) + x^4 + 4*x^3*y + 6*x^2*y^2 + 4*x*y^3 + y^4 + x^3 + 3*x^2*y + + 3*x*y^2 + y^3 + x^2 + 2*x*y + y^2 + x + y + 1 + TESTS:: sage: L. = LazyLaurentSeriesRing(SR); x = var("x") @@ -1919,7 +1995,8 @@ def __pow__(self, n): INPUT: - - ``n`` -- integer; the power to which to raise the series + - ``n`` -- the power to which to raise the series; this may be a + rational number, an element of the base ring, or an other series EXAMPLES:: @@ -1934,13 +2011,36 @@ def __pow__(self, n): 1 + 2/3/2^s + 2/3/3^s + 5/9/4^s + 2/3/5^s + 4/9/6^s + 2/3/7^s + O(1/(8^s)) sage: f^3 - Z O(1/(8^s)) + + sage: L. = LazyLaurentSeriesRing(QQ) + sage: f = 1 + z + sage: f^(1 / 2) + 1 + 1/2*z - 1/8*z^2 + 1/16*z^3 - 5/128*z^4 + 7/256*z^5 - 21/1024*z^6 + O(z^7) + + sage: f^f + 1 + z + z^2 + 1/2*z^3 + 1/3*z^4 + 1/12*z^5 + 3/40*z^6 + O(z^7) + + sage: q = ZZ['q'].fraction_field().gen() + sage: L. = LazyLaurentSeriesRing(q.parent()) + sage: f = (1 - z)^q + sage: f + 1 - q*z + ((q^2 - q)/2)*z^2 + ((-q^3 + 3*q^2 - 2*q)/6)*z^3 + + ((q^4 - 6*q^3 + 11*q^2 - 6*q)/24)*z^4 + + ((-q^5 + 10*q^4 - 35*q^3 + 50*q^2 - 24*q)/120)*z^5 + + ((q^6 - 15*q^5 + 85*q^4 - 225*q^3 + 274*q^2 - 120*q)/720)*z^6 + + O(z^7) """ if n in ZZ: return generic_power(self, n) from .lazy_series_ring import LazyLaurentSeriesRing P = LazyLaurentSeriesRing(self.base_ring(), "z", sparse=self.parent()._sparse) - exp = P(lambda k: 1/factorial(ZZ(k)), valuation=0) + + if n in QQ or n in self.base_ring(): + f = P(lambda k: prod(n - i for i in range(k)) / ZZ(k).factorial(), valuation=0) + return f(self - 1) + + exp = P(lambda k: 1 / ZZ(k).factorial(), valuation=0) return exp(self.log() * n) def sqrt(self): @@ -1953,12 +2053,12 @@ def sqrt(self): sage: sqrt(1+z) 1 + 1/2*z - 1/8*z^2 + 1/16*z^3 - 5/128*z^4 + 7/256*z^5 - 21/1024*z^6 + O(z^7) - sage: L. = LazyTaylorSeriesRing(QQ) # not tested - sage: sqrt(1+x/(1-y)) # not tested - 1 + 1/2*x + ((-1/8)*x^2+1/2*x*y) + (1/16*x^3+(-1/4)*x^2*y+1/2*x*y^2) - + ((-5/128)*x^4+3/16*x^3*y+(-3/8)*x^2*y^2+1/2*x*y^3) - + (7/256*x^5+(-5/32)*x^4*y+3/8*x^3*y^2+(-1/2)*x^2*y^3+1/2*x*y^4) - + ((-21/1024)*x^6+35/256*x^5*y+(-25/64)*x^4*y^2+5/8*x^3*y^3+(-5/8)*x^2*y^4+1/2*x*y^5) + sage: L. = LazyTaylorSeriesRing(QQ) + sage: sqrt(1+x/(1-y)) + 1 + 1/2*x + (-1/8*x^2+1/2*x*y) + (1/16*x^3-1/4*x^2*y+1/2*x*y^2) + + (-5/128*x^4+3/16*x^3*y-3/8*x^2*y^2+1/2*x*y^3) + + (7/256*x^5-5/32*x^4*y+3/8*x^3*y^2-1/2*x^2*y^3+1/2*x*y^4) + + (-21/1024*x^6+35/256*x^5*y-25/64*x^4*y^2+5/8*x^3*y^3-5/8*x^2*y^4+1/2*x*y^5) + O(x,y)^7 This also works for Dirichlet series:: @@ -1971,7 +2071,7 @@ def sqrt(self): sage: f*f - Z O(1/(8^s)) """ - return self ** (1/ZZ(2)) + return self ** QQ((1, 2)) # == 1/2 class LazyCauchyProductSeries(LazyModuleElement): @@ -1983,14 +2083,14 @@ class LazyCauchyProductSeries(LazyModuleElement): sage: L. = LazyLaurentSeriesRing(ZZ) sage: f = 1 / (1 - z) sage: f - 1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7) + 1 + z + z^2 + O(z^3) sage: f * (1 - z) - 1 + O(z^7) + 1 sage: L. = LazyLaurentSeriesRing(ZZ, sparse=True) sage: f = 1 / (1 - z) sage: f - 1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7) + 1 + z + z^2 + O(z^3) """ def valuation(self): r""" @@ -2014,9 +2114,10 @@ def valuation(self): 1 sage: (M - M).valuation() +Infinity - """ - return self._coeff_stream.order() + if isinstance(self._coeff_stream, Stream_zero): + return self._coeff_stream.order() + return ZZ(self._coeff_stream.order()) def _mul_(self, other): """ @@ -2093,9 +2194,9 @@ def _mul_(self, other): # Check some trivial products if isinstance(left, Stream_zero) or isinstance(right, Stream_zero): return P.zero() - if isinstance(left, Stream_exact) and left._initial_coefficients == (P._coeff_ring.one(),) and left.order() == 0: + if isinstance(left, Stream_exact) and left._initial_coefficients == (P._internal_poly_ring.base_ring().one(),) and left.order() == 0: return other # self == 1 - if isinstance(right, Stream_exact) and right._initial_coefficients == (P._coeff_ring.one(),) and right.order() == 0: + if isinstance(right, Stream_exact) and right._initial_coefficients == (P._internal_poly_ring.base_ring().one(),) and right.order() == 0: return self # right == 1 # The product is exact if and only if both factors are exact @@ -2135,8 +2236,10 @@ def _mul_(self, other): c += left._constant * ir[-1] else: c = left._constant # this is zero - coeff_stream = Stream_exact(initial_coefficients, P._sparse, - order=lv+rv, constant=c) + coeff_stream = Stream_exact(initial_coefficients, + P._sparse, + order=lv + rv, + constant=c) return P.element_class(P, coeff_stream) return P.element_class(P, Stream_cauchy_mul(left, right)) @@ -2210,13 +2313,15 @@ def __pow__(self, n): # # alternatively: # return P(self.finite_part() ** ZZ(n)) P = self.parent() - ret = cs._polynomial_part(P._laurent_poly_ring) ** ZZ(n) + ret = cs._polynomial_part(P._internal_poly_ring) ** ZZ(n) val = ret.valuation() deg = ret.degree() + 1 initial_coefficients = [ret[i] for i in range(val, deg)] - return P.element_class(P, Stream_exact(initial_coefficients, P._sparse, + return P.element_class(P, Stream_exact(initial_coefficients, + P._sparse, constant=cs._constant, - degree=deg, order=val)) + degree=deg, + order=val)) return super().__pow__(n) @@ -2272,24 +2377,30 @@ def __invert__(self): if not initial_coefficients: i = ~coeff_stream._constant v = -coeff_stream.order() - c = P._coeff_ring.zero() - coeff_stream = Stream_exact((i, -i), P._sparse, - order=v, constant=c) + c = P._internal_poly_ring.base_ring().zero() + coeff_stream = Stream_exact((i, -i), + P._sparse, + order=v, + constant=c) return P.element_class(P, coeff_stream) if len(initial_coefficients) == 1 and not coeff_stream._constant: i = ~initial_coefficients[0] v = -coeff_stream.order() - c = P._coeff_ring.zero() - coeff_stream = Stream_exact((i,), P._sparse, - order=v, constant=c) + c = P._internal_poly_ring.base_ring().zero() + coeff_stream = Stream_exact((i,), + P._sparse, + order=v, + constant=c) return P.element_class(P, coeff_stream) if (len(initial_coefficients) == 2 and not (initial_coefficients[0] + initial_coefficients[1]) and not coeff_stream._constant): v = -coeff_stream.order() c = ~initial_coefficients[0] - coeff_stream = Stream_exact((), P._sparse, - order=v, constant=c) + coeff_stream = Stream_exact((), + P._sparse, + order=v, + constant=c) return P.element_class(P, coeff_stream) # (f^-1)^-1 = f @@ -2310,8 +2421,11 @@ def _div_(self, other): Lazy Laurent series that have a dense implementation can be divided:: sage: L. = LazyLaurentSeriesRing(ZZ, sparse=False) - sage: z/(1 - z) - z + z^2 + z^3 + z^4 + z^5 + z^6 + z^7 + O(z^8) + sage: z / (1 - z) + z + z^2 + z^3 + O(z^4) + sage: 1 / (z*(1-z)) + z^-1 + 1 + z + O(z^2) + sage: M = L(lambda n: n, 0); M z + 2*z^2 + 3*z^3 + 4*z^4 + 5*z^5 + 6*z^6 + O(z^7) sage: N = L(lambda n: 1, 0); N @@ -2332,8 +2446,9 @@ def _div_(self, other): If the division of exact Lazy Laurent series yields a Laurent polynomial, it is represented as an exact series:: - sage: 1/z - z^-1 + sage: L. = LazyLaurentSeriesRing(QQ) + sage: (3*z^-3 + 3*z^-2 + 2*z^2 + 2*z^3) / (6*z + 4*z^6) + 1/2*z^-4 + 1/2*z^-3 sage: m = z^2 + 2*z + 1 sage: n = z + 1 @@ -2348,6 +2463,15 @@ def _div_(self, other): e[] + e[1]*z + e[1, 1]*z^2 + e[1, 1, 1]*z^3 + e[1, 1, 1, 1]*z^4 + e[1, 1, 1, 1, 1]*z^5 + e[1, 1, 1, 1, 1, 1]*z^6 + O(e[]*z^7) + Examples for multivariate Taylor series:: + + sage: L. = LazyTaylorSeriesRing(QQ) + sage: 1 / (1 - y) + 1 + y + y^2 + y^3 + y^4 + y^5 + y^6 + O(x,y)^7 + + sage: (x + y) / (1 - y) + (x+y) + (x*y+y^2) + (x*y^2+y^3) + (x*y^3+y^4) + (x*y^4+y^5) + (x*y^5+y^6) + (x*y^6+y^7) + O(x,y)^8 + """ if isinstance(other._coeff_stream, Stream_zero): raise ZeroDivisionError("cannot divide by 0") @@ -2357,23 +2481,63 @@ def _div_(self, other): if isinstance(left, Stream_zero): return P.zero() right = other._coeff_stream + R = P._internal_poly_ring if (isinstance(left, Stream_exact) - and isinstance(right, Stream_exact)): - if not left._constant and not right._constant: - R = P._laurent_poly_ring - pl = left._polynomial_part(R) - pr = right._polynomial_part(R) - try: - ret = pl / pr - ret = P._laurent_poly_ring(ret) - except (TypeError, ValueError, NotImplementedError): - # We cannot divide the polynomials, so the result must be a series - pass - else: - initial_coefficients = [ret[i] for i in range(ret.valuation(), ret.degree() + 1)] - return P.element_class(P, Stream_exact(initial_coefficients, P._sparse, - order=ret.valuation(), - constant=left._constant)) + and isinstance(right, Stream_exact) + and hasattr(R, "_gcd_univariate_polynomial")): + z = R.gen() + num = left._polynomial_part(R) * (1-z) + left._constant * z**left._degree + den = right._polynomial_part(R) * (1-z) + right._constant * z**right._degree + # num / den is not necessarily reduced, but gcd and // seems to work: + # sage: a = var("a"); R. = SR[] + # sage: (a*z - a)/(z - 1) + # (a*z - a)/(z - 1) + # sage: gcd((a*z - a), (z - 1)) + # z - 1 + g = num.gcd(den) + # apparently, the gcd is chosen so that den // g is is + # actually a polynomial, but we do not rely on this + num = num // g + den = den // g + exponents = den.exponents() + if len(exponents) == 1: + d = den[exponents[0]] + initial_coefficients = [c / d for c in num] + order = num.valuation() - den.valuation() + return P.element_class(P, Stream_exact(initial_coefficients, + P._sparse, + order=order, + constant=0)) + + if (len(exponents) == 2 + and exponents[0] + 1 == exponents[1] + and den[exponents[0]] == -den[exponents[1]]): + quo, rem = num.quo_rem(den) + # rem is a unit, i.e., in the Laurent case c*z^v + v_rem = rem.exponents()[0] + c = rem[v_rem] + constant = P.base_ring()(c / den[exponents[0]]) + v = v_rem - exponents[0] + if quo: + d = quo.degree() + m = d - v + 1 + if m > 0: + quo += R([constant]*m).shift(v) + v = d + 1 + if quo: + order = quo.valuation() + else: + order = 0 + return P.element_class(P, Stream_exact(list(quo), + P._sparse, + order=order, + degree=v, + constant=constant)) + return P.element_class(P, Stream_exact([], + P._sparse, + order=v, + degree=v, + constant=constant)) return P.element_class(P, Stream_cauchy_mul(left, Stream_cauchy_invert(right))) @@ -2450,7 +2614,7 @@ class LazyLaurentSeries(LazyCauchyProductSeries): sage: TestSuite(f).run() """ - def __call__(self, g): + def __call__(self, g, *, check=True): r""" Return the composition of ``self`` with ``g``. @@ -2626,19 +2790,19 @@ def __call__(self, g): sage: f = z^-2 + 1 + z sage: g = 1/(y*(1-y)); g - y^-1 + 1 + y + y^2 + y^3 + y^4 + y^5 + O(y^6) + y^-1 + 1 + y + O(y^2) sage: f(g) - y^-1 + 2 + y + 2*y^2 - y^3 + 2*y^4 + y^5 + O(y^6) - sage: g^-2 + 1 + g - y^-1 + 2 + y + 2*y^2 - y^3 + 2*y^4 + y^5 + O(y^6) + y^-1 + 2 + y + 2*y^2 - y^3 + 2*y^4 + y^5 + y^6 + y^7 + O(y^8) + sage: g^-2 + 1 + g == f(g) + True sage: f = z^-3 + z^-2 + 1 sage: g = 1/(y^2*(1-y)); g - y^-2 + y^-1 + 1 + y + y^2 + y^3 + y^4 + O(y^5) + y^-2 + y^-1 + 1 + O(y) sage: f(g) - 1 + y^4 - 2*y^5 + 2*y^6 + O(y^7) - sage: g^-3 + g^-2 + 1 - 1 + y^4 - 2*y^5 + 2*y^6 + O(y^7) + 1 + y^4 - 2*y^5 + 2*y^6 - 3*y^7 + 3*y^8 - y^9 + sage: g^-3 + g^-2 + 1 == f(g) + True sage: z(y) y @@ -2820,21 +2984,24 @@ def __call__(self, g): raise NotImplementedError("can only compose with a lazy series") # Perhaps we just don't yet know if the valuation is positive - if g._coeff_stream._approximate_order <= 0: - if any(g._coeff_stream[i] for i in range(g._coeff_stream._approximate_order, 1)): - raise ValueError("can only compose with a positive valuation series") - g._coeff_stream._approximate_order = 1 + if check: + if g._coeff_stream._approximate_order <= 0: + if any(g._coeff_stream[i] for i in range(g._coeff_stream._approximate_order, 1)): + raise ValueError("can only compose with a positive valuation series") + g._coeff_stream._approximate_order = 1 if isinstance(g, LazyDirichletSeries): - if g._coeff_stream._approximate_order == 1: - if g._coeff_stream[1] != 0: - raise ValueError("can only compose with a positive valuation series") - g._coeff_stream._approximate_order = 2 + if check: + if g._coeff_stream._approximate_order == 1: + if g._coeff_stream[1] != 0: + raise ValueError("can only compose with a positive valuation series") + g._coeff_stream._approximate_order = 2 # we assume that the valuation of self[i](g) is at least i def coefficient(n): return sum(self[i] * (g**i)[n] for i in range(n+1)) - coeff_stream = Stream_function(coefficient, P._coeff_ring, P._sparse, 1) + R = P._internal_poly_ring.base_ring() + coeff_stream = Stream_function(coefficient, R, P._sparse, 1) return P.element_class(P, coeff_stream) coeff_stream = Stream_cauchy_compose(self._coeff_stream, g._coeff_stream) @@ -3018,7 +3185,7 @@ def polynomial(self, degree=None, name=None): sage: z = L.gen() sage: f = (1 + z)/(z^3 - z^5) sage: f - z^-3 + z^-2 + z^-1 + 1 + z + z^2 + z^3 + O(z^4) + z^-3 + z^-2 + z^-1 + O(1) sage: f.polynomial(5) z^-3 + z^-2 + z^-1 + 1 + z + z^2 + z^3 + z^4 + z^5 sage: f.polynomial(0) @@ -3043,11 +3210,12 @@ def polynomial(self, degree=None, name=None): """ S = self.parent() + if isinstance(self._coeff_stream, Stream_zero): + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + return PolynomialRing(S.base_ring(), name=name).zero() + if degree is None: - if isinstance(self._coeff_stream, Stream_zero): - from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - return PolynomialRing(S.base_ring(), name=name).zero() - elif isinstance(self._coeff_stream, Stream_exact) and not self._coeff_stream._constant: + if isinstance(self._coeff_stream, Stream_exact) and not self._coeff_stream._constant: m = self._coeff_stream._degree else: raise ValueError("not a polynomial") @@ -3104,6 +3272,655 @@ def _format_series(self, formatter, format_strings=False): return strformat("O({})".format(formatter(z**m))) return formatter(poly) + strformat(" + O({})".format(formatter(z**m))) +class LazyTaylorSeries(LazyCauchyProductSeries): + r""" + A Taylor series where the coefficients are computed lazily. + + EXAMPLES:: + + sage: L. = LazyTaylorSeriesRing(ZZ) + sage: f = 1 / (1 - x^2 + y^3); f + 1 + x^2 + (-y^3) + x^4 + (-2*x^2*y^3) + (x^6+y^6) + O(x,y)^7 + sage: P. = PowerSeriesRing(ZZ, default_prec=101) + sage: g = 1 / (1 - x^2 + y^3); f[100] - g[100] + 0 + + Lazy Taylor series is picklable:: + + sage: g = loads(dumps(f)) + sage: g + 1 + x^2 + (-y^3) + x^4 + (-2*x^2*y^3) + (x^6+y^6) + O(x,y)^7 + sage: g == f + True + """ + def __call__(self, *g, check=True): + r""" + Return the composition of ``self`` with ``g``. + + The arity of ``self`` must be equal to the number of + arguments provided. + + Given two Taylor Series `f` and `g` over the same base ring, the + composition `(f \circ g)(z) = f(g(z))` is defined if and only if: + + - `g = 0` and `val(f) >= 0`, + - `g` is non-zero and `f` has only finitely many non-zero coefficients, + - `g` is non-zero and `val(g) > 0`. + + INPUT: + + - ``g`` -- other series, all can be coerced into the same parent + + EXAMPLES:: + + sage: L. = LazyTaylorSeriesRing(QQ) + sage: M. = LazyTaylorSeriesRing(ZZ) + sage: g1 = 1 / (1 - x) + sage: g2 = x + y^2 + sage: p = a^2 + b + 1 + sage: p(g1, g2) - g1^2 - g2 - 1 + O(x,y,z)^7 + + The number of mappings from a set with `m` elements to a set + with `n` elements:: + + sage: M. = LazyTaylorSeriesRing(QQ) + sage: Ea = M(lambda n: 1/factorial(n)) + sage: Ex = L(lambda n: 1/factorial(n)*x^n) + sage: Ea(Ex*y)[5] + 1/24*x^4*y + 2/3*x^3*y^2 + 3/4*x^2*y^3 + 1/6*x*y^4 + 1/120*y^5 + + So, there are `3! 2! 2/3 = 8` mappings from a three element + set to a two element set. + + We perform the composition with a lazy Laurent series:: + + sage: N. = LazyLaurentSeriesRing(QQ) + sage: f1 = 1 / (1 - w) + sage: f2 = cot(w / (1 - w)) + sage: p(f1, f2) + w^-1 + 1 + 5/3*w + 8/3*w^2 + 164/45*w^3 + 23/5*w^4 + 5227/945*w^5 + O(w^6) + + We perform the composition with a lazy Dirichlet series:: + + sage: D = LazyDirichletSeriesRing(QQ, "s") + sage: g = D(constant=1)-1; g + 1/(2^s) + 1/(3^s) + 1/(4^s) + O(1/(5^s)) + sage: f = 1 / (1 - x - y*z); f + 1 + x + (x^2+y*z) + (x^3+2*x*y*z) + (x^4+3*x^2*y*z+y^2*z^2) + + (x^5+4*x^3*y*z+3*x*y^2*z^2) + + (x^6+5*x^4*y*z+6*x^2*y^2*z^2+y^3*z^3) + + O(x,y,z)^7 + sage: fog = f(g, g, g); fog + 1 + 1/(2^s) + 1/(3^s) + 3/4^s + 1/(5^s) + 5/6^s + O(1/(7^s)) + sage: fg = 1 / (1 - g - g*g); fg + 1 + 1/(2^s) + 1/(3^s) + 3/4^s + 1/(5^s) + 5/6^s + 1/(7^s) + O(1/(8^s)) + sage: fog - fg + O(1/(7^s)) + + sage: f = 1 / (1 - 2*a) + sage: f(g) + 1 + 2/2^s + 2/3^s + 6/4^s + 2/5^s + 10/6^s + 2/7^s + O(1/(8^s)) + sage: 1 / (1 - 2*g) + 1 + 2/2^s + 2/3^s + 6/4^s + 2/5^s + 10/6^s + 2/7^s + O(1/(8^s)) + + The output parent is always the common parent between the base ring + of `f` and the parent of `g` or extended to the corresponding + lazy series:: + + sage: T. = LazyTaylorSeriesRing(QQ) + sage: R. = ZZ[] + sage: S. = R[] + sage: L. = LaurentPolynomialRing(ZZ) + sage: parent(x(a, b)) + Multivariate Polynomial Ring in a, b, c over Rational Field + sage: parent(x(CC(2), a)) + Multivariate Polynomial Ring in a, b, c over Complex Field with 53 bits of precision + sage: parent(x(0, 0)) + Rational Field + sage: f = 1 / (1 - x - y); f + 1 + (x+y) + (x^2+2*x*y+y^2) + (x^3+3*x^2*y+3*x*y^2+y^3) + + (x^4+4*x^3*y+6*x^2*y^2+4*x*y^3+y^4) + + (x^5+5*x^4*y+10*x^3*y^2+10*x^2*y^3+5*x*y^4+y^5) + + (x^6+6*x^5*y+15*x^4*y^2+20*x^3*y^3+15*x^2*y^4+6*x*y^5+y^6) + + O(x,y)^7 + sage: f(a^2, b*c) + 1 + (a^2+b*c) + (a^4+2*a^2*b*c+b^2*c^2) + (a^6+3*a^4*b*c+3*a^2*b^2*c^2+b^3*c^3) + O(a,b,c)^7 + sage: f(v, v^2) + 1 + v + 2*v^2 + 3*v^3 + 5*v^4 + 8*v^5 + 13*v^6 + O(v^7) + sage: f(z, z^2 + z) + 1 + 2*z + 5*z^2 + 12*z^3 + 29*z^4 + 70*z^5 + 169*z^6 + O(z^7) + sage: three = T(3)(a^2, b); three + 3 + sage: parent(three) + Multivariate Polynomial Ring in a, b, c over Rational Field + + TESTS:: + + sage: L. = LazyTaylorSeriesRing(ZZ) + sage: f = 1 / (1 - x - y) + sage: f(f) + Traceback (most recent call last): + ... + ValueError: arity of must be equal to the number of arguments provided + + sage: f(1, x*y) + Traceback (most recent call last): + ... + ValueError: can only compose with a positive valuation series + + This test will pass once pushouts are implemented:: + + sage: R. = QQ[] + sage: f(1/2*a, x) + Traceback (most recent call last): + ... + TypeError: no common canonical parent for objects with parents: ... + + """ + if len(g) != len(self.parent().variable_names()): + raise ValueError("arity of must be equal to the number of arguments provided") + + # Find a good parent for the result + from sage.structure.element import get_coercion_model + cm = get_coercion_model() + P = cm.common_parent(self.base_ring(), *[parent(h) for h in g]) + + # f has finite length + if isinstance(self._coeff_stream, Stream_exact) and not self._coeff_stream._constant: + # constant polynomial + poly = self.polynomial() + if poly.is_constant(): + return P(poly) + return P(poly(g)) + + # f now has (potentially) infinitely many terms + # Lift the resulting parent to a lazy series (if possible) + # Also make sure each element of g is a LazyModuleElement + from sage.rings.polynomial.polynomial_ring import PolynomialRing_general + from sage.rings.polynomial.multi_polynomial_ring_base import MPolynomialRing_base + from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing_univariate + from sage.rings.lazy_series_ring import LazySeriesRing + if not isinstance(P, LazySeriesRing): + fP = parent(self) + if fP._laurent_poly_ring.has_coerce_map_from(P): + S = fP._laurent_poly_ring + P = fP + if isinstance(P, (PolynomialRing_general, MPolynomialRing_base)): + from sage.rings.lazy_series_ring import LazyTaylorSeriesRing + S = P + try: + sparse = S.is_sparse() + except AttributeError: + sparse = fP.is_sparse() + P = LazyTaylorSeriesRing(S.base_ring(), S.variable_names(), sparse) + elif isinstance(P, LaurentPolynomialRing_univariate): + from sage.rings.lazy_series_ring import LazyLaurentSeriesRing + S = P + P = LazyLaurentSeriesRing(S.base_ring(), S.variable_names(), fP.is_sparse()) + else: + raise ValueError("unable to evaluate the series at {}".format(g)) + g = [P(S(h)) for h in g] + else: + g = [P(h) for h in g] + R = P._internal_poly_ring.base_ring() + + if check: + for h in g: + if h._coeff_stream._approximate_order == 0: + if h[0]: + raise ValueError("can only compose with a positive valuation series") + h._coeff_stream._approximate_order = 1 + + if isinstance(h, LazyDirichletSeries): + if h._coeff_stream._approximate_order == 1: + if h._coeff_stream[1] != 0: + raise ValueError("can only compose with a positive valuation series") + h._coeff_stream._approximate_order = 2 + + + # We now ahave that every element of g has a _coeff_stream + sorder = self._coeff_stream._approximate_order + if len(g) == 1: + g0 = g[0] + if isinstance(g0, LazyDirichletSeries): + # we assume that the valuation of self[i](g) is at least i + def coefficient(n): + return sum(self[i] * (g0**i)[n] for i in range(n+1)) + coeff_stream = Stream_function(coefficient, R, P._sparse, 1) + return P.element_class(P, coeff_stream) + + coeff_stream = Stream_cauchy_compose(self._coeff_stream, g0._coeff_stream) + return P.element_class(P, coeff_stream) + + # The arity is at least 2 + gv = min(h._coeff_stream._approximate_order for h in g) + + def coefficient(n): + r = R.zero() + for i in range(n//gv+1): + # Make sure the element returned from the composition is in P + r += P(self[i](g))[n] + return r + coeff_stream = Stream_function(coefficient, R, P._sparse, sorder * gv) + return P.element_class(P, coeff_stream) + + compose = __call__ + + def _format_series(self, formatter, format_strings=False): + """ + Return nonzero ``self`` formatted by ``formatter``. + + TESTS:: + + sage: L. = LazyTaylorSeriesRing(QQ) + sage: f = 1 / (2 - x^2 + y) + sage: f._format_series(repr) + '1/2 + (-1/4*y) + (1/4*x^2+1/8*y^2) + (-1/4*x^2*y-1/16*y^3) + (1/8*x^4+3/16*x^2*y^2+1/32*y^4) + (-3/16*x^4*y-1/8*x^2*y^3-1/64*y^5) + (1/16*x^6+3/16*x^4*y^2+5/64*x^2*y^4+1/128*y^6) + O(x,y)^7' + + sage: f = (2 - x^2 + y) + sage: f._format_series(repr) + '2 + y + (-x^2)' + """ + P = self.parent() + cs = self._coeff_stream + v = cs._approximate_order + if isinstance(cs, Stream_exact): + if not cs._constant: + m = cs._degree + else: + m = cs._degree + P.options.constant_length + else: + m = v + P.options.display_length + + atomic_repr = P._internal_poly_ring.base_ring()._repr_option('element_is_atomic') + mons = [P._monomial(self[i], i) for i in range(v, m) if self[i]] + if not isinstance(cs, Stream_exact) or cs._constant: + if P._internal_poly_ring.base_ring() is P.base_ring(): + bigO = ["O(%s)" % P._monomial(1, m)] + else: + bigO = ["O(%s)^%s" % (', '.join(str(g) for g in P._names), m)] + else: + bigO = [] + + from sage.misc.latex import latex + from sage.typeset.unicode_art import unicode_art + from sage.typeset.ascii_art import ascii_art + from sage.misc.repr import repr_lincomb + from sage.typeset.symbols import ascii_left_parenthesis, ascii_right_parenthesis + from sage.typeset.symbols import unicode_left_parenthesis, unicode_right_parenthesis + if formatter == repr: + poly = repr_lincomb([(1, m) for m in mons + bigO], strip_one=True) + elif formatter == latex: + poly = repr_lincomb([(1, m) for m in mons + bigO], is_latex=True, strip_one=True) + elif formatter == ascii_art: + if atomic_repr: + poly = ascii_art(*(mons + bigO), sep = " + ") + else: + def parenthesize(m): + a = ascii_art(m) + h = a.height() + return ascii_art(ascii_left_parenthesis.character_art(h), + a, ascii_right_parenthesis.character_art(h)) + poly = ascii_art(*([parenthesize(m) for m in mons] + bigO), sep = " + ") + elif formatter == unicode_art: + if atomic_repr: + poly = unicode_art(*(mons + bigO), sep = " + ") + else: + def parenthesize(m): + a = unicode_art(m) + h = a.height() + return unicode_art(unicode_left_parenthesis.character_art(h), + a, unicode_right_parenthesis.character_art(h)) + poly = unicode_art(*([parenthesize(m) for m in mons] + bigO), sep = " + ") + + return poly + + def polynomial(self, degree=None, names=None): + r""" + Return ``self`` as a polynomial if ``self`` is actually so. + + INPUT: + + - ``degree`` -- ``None`` or an integer + - ``names`` -- names of the variables; if it is ``None``, the name of + the variables of the series is used + + OUTPUT: + + If ``degree`` is not ``None``, the terms of the series of degree greater + than ``degree`` are truncated first. If ``degree`` is ``None`` and the + series is not a polynomial polynomial, a ``ValueError`` is raised. + + EXAMPLES:: + + sage: L. = LazyTaylorSeriesRing(ZZ) + sage: f = x^2 + y*x - x + 2; f + 2 + (-x) + (x^2+x*y) + sage: f.polynomial() + x^2 + x*y - x + 2 + + TESTS:: + + sage: g = 1 / (1 + x + y + x*y) + sage: g3 = g.truncate(4); g3 + 1 + (-x-y) + (x^2+x*y+y^2) + (-x^3-x^2*y-x*y^2-y^3) + sage: g.polynomial() + Traceback (most recent call last): + ... + ValueError: not a polynomial + sage: g3.polynomial() + -x^3 - x^2*y - x*y^2 - y^3 + x^2 + x*y + y^2 - x - y + 1 + sage: L.zero().polynomial() + 0 + sage: g3.polynomial() == g.polynomial(3) + True + sage: g3.polynomial(0) + 1 + """ + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + S = self.parent() + if names is None: + names = S.variable_names() + R = PolynomialRing(S.base_ring(), names=names) + if isinstance(self._coeff_stream, Stream_zero): + return R.zero() + + if degree is None: + if (isinstance(self._coeff_stream, Stream_exact) + and not self._coeff_stream._constant): + m = self._coeff_stream._degree + else: + raise ValueError("not a polynomial") + else: + m = degree + 1 + + if names is None: + names = S.variable_names() + + return R.sum(self[:m]) + + +class LazyCompletionGradedAlgebraElement(LazyCauchyProductSeries): + """ + An element of a completion of a graded algebra that is computed lazily. + """ + def _format_series(self, formatter, format_strings=False): + r""" + Return nonzero ``self`` formatted by ``formatter``. + + TESTS:: + + sage: h = SymmetricFunctions(ZZ).h() + sage: e = SymmetricFunctions(ZZ).e() + sage: L = LazySymmetricFunctions(tensor([h, e])) + sage: f = L(lambda n: sum(tensor([h[k], e[n-k]]) for k in range(n+1))) + sage: f._format_series(repr) + '(h[]#e[]) + + (h[]#e[1]+h[1]#e[]) + + (h[]#e[2]+h[1]#e[1]+h[2]#e[]) + + (h[]#e[3]+h[1]#e[2]+h[2]#e[1]+h[3]#e[]) + + (h[]#e[4]+h[1]#e[3]+h[2]#e[2]+h[3]#e[1]+h[4]#e[]) + + (h[]#e[5]+h[1]#e[4]+h[2]#e[3]+h[3]#e[2]+h[4]#e[1]+h[5]#e[]) + + (h[]#e[6]+h[1]#e[5]+h[2]#e[4]+h[3]#e[3]+h[4]#e[2]+h[5]#e[1]+h[6]#e[]) + + O^7' + """ + P = self.parent() + cs = self._coeff_stream + v = cs._approximate_order + if isinstance(cs, Stream_exact): + if not cs._constant: + m = cs._degree + else: + m = cs._degree + P.options.constant_length + else: + m = v + P.options.display_length + + atomic_repr = P._internal_poly_ring.base_ring()._repr_option('element_is_atomic') + mons = [P._monomial(self[i], i) for i in range(v, m) if self[i]] + if not isinstance(cs, Stream_exact) or cs._constant: + if P._internal_poly_ring.base_ring() is P.base_ring(): + bigO = ["O(%s)" % P._monomial(1, m)] + else: + bigO = ["O^%s" % m] + else: + bigO = [] + + from sage.misc.latex import latex + from sage.typeset.unicode_art import unicode_art + from sage.typeset.ascii_art import ascii_art + from sage.misc.repr import repr_lincomb + from sage.typeset.symbols import ascii_left_parenthesis, ascii_right_parenthesis + from sage.typeset.symbols import unicode_left_parenthesis, unicode_right_parenthesis + if formatter == repr: + poly = repr_lincomb([(1, m) for m in mons + bigO], strip_one=True) + elif formatter == latex: + poly = repr_lincomb([(1, m) for m in mons + bigO], is_latex=True, strip_one=True) + elif formatter == ascii_art: + if atomic_repr: + poly = ascii_art(*(mons + bigO), sep = " + ") + else: + def parenthesize(m): + a = ascii_art(m) + h = a.height() + return ascii_art(ascii_left_parenthesis.character_art(h), + a, ascii_right_parenthesis.character_art(h)) + poly = ascii_art(*([parenthesize(m) for m in mons] + bigO), sep = " + ") + elif formatter == unicode_art: + if atomic_repr: + poly = unicode_art(*(mons + bigO), sep = " + ") + else: + def parenthesize(m): + a = unicode_art(m) + h = a.height() + return unicode_art(unicode_left_parenthesis.character_art(h), + a, unicode_right_parenthesis.character_art(h)) + poly = unicode_art(*([parenthesize(m) for m in mons] + bigO), sep = " + ") + + return poly + + +class LazySymmetricFunction(LazyCompletionGradedAlgebraElement): + r""" + A symmetric function where each degree is computed lazily. + + EXAMPLES:: + + sage: s = SymmetricFunctions(ZZ).s() + sage: L = LazySymmetricFunctions(s) + """ + def __call__(self, *args, check=True): + r""" + Return the composition of ``self`` with ``args``. + + The arity of ``self`` must be equal to the number of + arguments provided. + + Given two lazy symmetric functions `f` and `g` over the same + base ring, the composition (or plethysm) `(f \circ g)` is + defined if and only if: + + - `g = 0`, + - `g` is non-zero and `f` has only finitely many non-zero coefficients, + - `g` is non-zero and `val(g) > 0`. + + INPUT: + + - ``args`` -- other (lazy) symmetric functions + + EXAMPLES:: + + sage: P. = QQ[] + sage: s = SymmetricFunctions(P).s() + sage: L = LazySymmetricFunctions(s) + sage: f = s[2] + sage: g = s[3] + sage: L(f)(L(g)) - L(f(g)) + 0 + + sage: f = s[2] + s[2,1] + sage: g = s[1] + s[2,2] + sage: L(f)(L(g)) - L(f(g)) + 0 + + sage: L(f)(g) - L(f(g)) + 0 + + sage: f = s[2] + s[2,1] + sage: g = s[1] + s[2,2] + sage: L(f)(L(q*g)) - L(f(q*g)) + 0 + + The Frobenius character of the permutation action on set + partitions is a plethysm:: + + sage: s = SymmetricFunctions(QQ).s() + sage: S = LazySymmetricFunctions(s) + sage: E1 = S(lambda n: s[n], valuation=1) + sage: E = 1 + E1 + sage: P = E(E1) + sage: [s(x) for x in P[:5]] + [s[], s[1], 2*s[2], s[2, 1] + 3*s[3], 2*s[2, 2] + 2*s[3, 1] + 5*s[4]] + + TESTS:: + + sage: s = SymmetricFunctions(QQ).s() + sage: S = LazySymmetricFunctions(s) + sage: f = 1 / (1 - S(s[2])) + sage: g = f(s[2]); g + s[] + (s[2,2]+s[4]) + O^7 + sage: S(sum(f[i](s[2]) for i in range(5))).truncate(10) == g.truncate(10) + True + sage: f = 1 / (1 - S(s[2])) + sage: g = S(s[1]) / (1 - S(s[1])) + sage: h = f(g) + sage: h + s[] + s[2] + (s[1,1,1]+2*s[2,1]+s[3]) + + (2*s[1,1,1,1]+4*s[2,1,1]+5*s[2,2]+5*s[3,1]+3*s[4]) + + (2*s[1,1,1,1,1]+10*s[2,1,1,1]+14*s[2,2,1]+18*s[3,1,1]+16*s[3,2]+14*s[4,1]+4*s[5]) + + (3*s[1,1,1,1,1,1]+22*s[2,1,1,1,1]+38*s[2,2,1,1]+28*s[2,2,2]+48*s[3,1,1,1]+82*s[3,2,1]+25*s[3,3]+51*s[4,1,1]+56*s[4,2]+31*s[5,1]+9*s[6]) + + O^7 + sage: f(0) + 1 + sage: f(s(1)) + Traceback (most recent call last): + ... + ValueError: can only compose with a positive valuation series + """ + if len(args) != self.parent()._arity: + raise ValueError("arity must be equal to the number of arguments provided") + from sage.combinat.sf.sfa import is_SymmetricFunction + if not all(isinstance(g, LazySymmetricFunction) or is_SymmetricFunction(g) or not g for g in args): + raise ValueError("all arguments must be (possibly lazy) symmetric functions") + + if isinstance(self._coeff_stream, Stream_zero): + return self + + if len(args) == 1: + g = args[0] + P = g.parent() + + # Handle other types of 0s + if not isinstance(g, LazySymmetricFunction) and not g: + return P(self[0].leading_coefficient()) + + if isinstance(self._coeff_stream, Stream_exact) and not self._coeff_stream._constant: + f = self.symmetric_function() + if is_SymmetricFunction(g): + return f(g) + # g must be a LazySymmetricFunction + if isinstance(g._coeff_stream, Stream_exact) and not g._coeff_stream._constant: + gs = g.symmetric_function() + return P(f(gs)) + + if isinstance(g, LazySymmetricFunction): + R = P._laurent_poly_ring + else: + from sage.rings.lazy_series_ring import LazySymmetricFunctions + R = g.parent() + P = LazySymmetricFunctions(R) + g = P(g) + + # self has (potentially) infinitely many terms + if check: + if g._coeff_stream._approximate_order == 0: + if g[0]: + raise ValueError("can only compose with a positive valuation series") + g._coeff_stream._approximate_order = 1 + + ps = P._laurent_poly_ring.realization_of().p() + coeff_stream = Stream_plethysm(self._coeff_stream, g._coeff_stream, ps) + else: + raise NotImplementedError("only implemented for arity 1") + + return P.element_class(P, coeff_stream) + + plethysm = __call__ + + def symmetric_function(self, degree=None): + r""" + Return ``self`` as a symmetric function if ``self`` is actually so. + + INPUT: + + - ``degree`` -- ``None`` or an integer + + OUTPUT: + + If ``degree`` is not ``None``, the terms of the series of degree greater + than ``degree`` are truncated first. If ``degree`` is ``None`` and the + series is not a polynomial polynomial, a ``ValueError`` is raised. + + EXAMPLES:: + + sage: s = SymmetricFunctions(QQ).s() + sage: S = LazySymmetricFunctions(s) + sage: elt = S(s[2]) + sage: elt.symmetric_function() + s[2] + + TESTS:: + + sage: s = SymmetricFunctions(QQ).s() + sage: S = LazySymmetricFunctions(s) + sage: elt = S(s[2]) + sage: elt.symmetric_function() + s[2] + sage: f = 1 / (1 - elt) + sage: f + s[] + s[2] + (s[2,2]+s[3,1]+s[4]) + (s[2,2,2]+2*s[3,2,1]+s[3,3]+s[4,1,1]+3*s[4,2]+2*s[5,1]+s[6]) + O^7 + sage: f.symmetric_function() + Traceback (most recent call last): + ... + ValueError: not a symmetric function + + sage: f4 = f.truncate(5); f4 + s[] + s[2] + (s[2,2]+s[3,1]+s[4]) + sage: f4.symmetric_function() + s[] + s[2] + s[2, 2] + s[3, 1] + s[4] + sage: f4.symmetric_function() == f.symmetric_function(4) + True + sage: S.zero().symmetric_function() + 0 + sage: f4.symmetric_function(0) + s[] + """ + S = self.parent() + R = S._laurent_poly_ring + + if isinstance(self._coeff_stream, Stream_zero): + return R.zero() + + if degree is None: + if (isinstance(self._coeff_stream, Stream_exact) + and not self._coeff_stream._constant): + m = self._coeff_stream._degree + else: + raise ValueError("not a symmetric function") + else: + m = degree + 1 + + return R.sum(self[:m]) class LazyDirichletSeries(LazyModuleElement): r""" @@ -3146,8 +3963,10 @@ def valuation(self): sage: (g*g).valuation() 2*log(2) """ + if isinstance(self._coeff_stream, Stream_zero): + return self._coeff_stream.order() from sage.functions.log import log - return log(self._coeff_stream.order()) + return log(ZZ(self._coeff_stream.order())) def _mul_(self, other): """ @@ -3189,8 +4008,14 @@ def _mul_(self, other): sage: L. = LazyLaurentSeriesRing(D) sage: 1/(1-t*zeta) - (1 + O(1/(8^s))) + (1 + 1/(2^s) + 1/(3^s) + 1/(4^s) + 1/(5^s) + 1/(6^s) + 1/(7^s) + O(1/(8^s)))*t + (1 + 2/2^s + 2/3^s + 3/4^s + 2/5^s + 4/6^s + 2/7^s + O(1/(8^s)))*t^2 + (1 + 3/2^s + 3/3^s + 6/4^s + 3/5^s + 9/6^s + 3/7^s + O(1/(8^s)))*t^3 + (1 + 4/2^s + 4/3^s + 10/4^s + 4/5^s + 16/6^s + 4/7^s + O(1/(8^s)))*t^4 + (1 + 5/2^s + 5/3^s + 15/4^s + 5/5^s + 25/6^s + 5/7^s + O(1/(8^s)))*t^5 + (1 + 6/2^s + 6/3^s + 21/4^s + 6/5^s + 36/6^s + 6/7^s + O(1/(8^s)))*t^6 + O(t^7) - + (1 + O(1/(8^s))) + + (1 + 1/(2^s) + 1/(3^s) + 1/(4^s) + 1/(5^s) + 1/(6^s) + 1/(7^s) + O(1/(8^s)))*t + + (1 + 2/2^s + 2/3^s + 3/4^s + 2/5^s + 4/6^s + 2/7^s + O(1/(8^s)))*t^2 + + (1 + 3/2^s + 3/3^s + 6/4^s + 3/5^s + 9/6^s + 3/7^s + O(1/(8^s)))*t^3 + + (1 + 4/2^s + 4/3^s + 10/4^s + 4/5^s + 16/6^s + 4/7^s + O(1/(8^s)))*t^4 + + (1 + 5/2^s + 5/3^s + 15/4^s + 5/5^s + 25/6^s + 5/7^s + O(1/(8^s)))*t^5 + + (1 + 6/2^s + 6/3^s + 21/4^s + 6/5^s + 36/6^s + 6/7^s + O(1/(8^s)))*t^6 + + O(t^7) """ P = self.parent() left = self._coeff_stream @@ -3201,12 +4026,12 @@ def _mul_(self, other): return other if (isinstance(left, Stream_exact) and not left._constant - and left._initial_coefficients == (P._coeff_ring.one(),) + and left._initial_coefficients == (P._internal_poly_ring.base_ring().one(),) and left.order() == 1): return other # self == 1 if (isinstance(right, Stream_exact) and not right._constant - and right._initial_coefficients == (P._coeff_ring.one(),) + and right._initial_coefficients == (P._internal_poly_ring.base_ring().one(),) and right.order() == 1): return self # other == 1 coeff = Stream_dirichlet_convolve(left, right) @@ -3240,7 +4065,7 @@ def __invert__(self): P = self.parent() return P.element_class(P, Stream_dirichlet_invert(self._coeff_stream)) - def __call__(self, p): + def __call__(self, p, *, check=True): r""" Return the composition of ``self`` with a linear polynomial ``p``. @@ -3339,7 +4164,8 @@ def coefficient(m): return coeff_stream[n] * n ** (-b) except ValueError: return ZZ.zero() - return P.element_class(P, Stream_function(coefficient, P._coeff_ring, P._sparse, 1)) + R = P._internal_poly_ring.base_ring() + return P.element_class(P, Stream_function(coefficient, R, P._sparse, 1)) def _format_series(self, formatter, format_strings=False): """ @@ -3373,9 +4199,9 @@ def _format_series(self, formatter, format_strings=False): sage: L. = LazyLaurentSeriesRing(QQ) sage: D = LazyDirichletSeriesRing(L, "s") sage: f = D([2, 0, 1/(1-z), 3]); f - (2)/1^s + ((1+z+z^2+z^3+z^4+z^5+z^6+O(z^7))/3^s) + (3)/4^s + (2)/1^s + ((1+z+z^2+O(z^3))/3^s) + (3)/4^s sage: f._format_series(ascii_art) - ((2)/1^s) + ((1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7))/3^s) + ((3)/4^s) + ((2)/1^s) + ((1 + z + z^2 + O(z^3))/3^s) + ((3)/4^s) """ P = self.parent() cs = self._coeff_stream @@ -3388,10 +4214,10 @@ def _format_series(self, formatter, format_strings=False): else: m = v + P.options.display_length - atomic_repr = P._coeff_ring._repr_option('element_is_atomic') + atomic_repr = P._internal_poly_ring.base_ring()._repr_option('element_is_atomic') mons = [P._monomial(self[i], i) for i in range(v, m) if self[i]] if not isinstance(cs, Stream_exact) or cs._constant: - if P._coeff_ring is P.base_ring(): + if P._internal_poly_ring.base_ring() is P.base_ring(): bigO = ["O(%s)" % P._monomial(1, m)] else: bigO = ["O(%s)^%s" % (', '.join(str(g) for g in P._names), m)] diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 9863af2d81a..94a3c4ac465 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -1,6 +1,19 @@ r""" Lazy Series Rings +We provide lazy implementations for various `\NN`-graded rings. + +.. csv-table:: + :class: contentstable + :widths: 30, 70 + :delim: | + + :class:`LazyLaurentSeriesRing` | The ring of lazy Laurent series. + :class:`LazyTaylorSeriesRing` | The ring of (possibly multivariate) lazy Taylor series. + :class:`LazyCompletionGradedAlgebra` | The completion of a graded alebra consisting of formal series. + :class:`LazySymmetricFunctions` | The ring of (possibly multivariate) lazy symmetric functions. + :class:`LazyDirichletSeriesRing` | The ring of lazy Dirichlet series. + AUTHORS: - Kwankyu Lee (2019-02-24): initial version @@ -10,6 +23,8 @@ # **************************************************************************** # Copyright (C) 2019 Kwankyu Lee +# 2022 Martin Rubey +# 2022 Travis Scrimshaw # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -26,7 +41,8 @@ from sage.categories.rings import Rings from sage.categories.integral_domains import IntegralDomains from sage.categories.fields import Fields -from sage.categories.complete_discrete_valuation import CompleteDiscreteValuationFields +from sage.categories.complete_discrete_valuation import (CompleteDiscreteValuationFields, + CompleteDiscreteValuationRings) from sage.misc.cachefunc import cached_method @@ -35,6 +51,9 @@ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.lazy_series import (LazyModuleElement, LazyLaurentSeries, + LazyTaylorSeries, + LazyCompletionGradedAlgebraElement, + LazySymmetricFunction, LazyDirichletSeries) from sage.structure.global_options import GlobalOptions from sage.symbolic.ring import SR @@ -232,14 +251,14 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, constant=No sage: L(L(1), valuation=-4) z^-4 sage: L(1/(1-z), valuation=-4) - z^-4 + z^-3 + z^-2 + z^-1 + 1 + z + z^2 + O(z^3) + z^-4 + z^-3 + z^-2 + O(z^-1) sage: L(z^-3/(1-z), valuation=-4) - z^-4 + z^-3 + z^-2 + z^-1 + 1 + z + z^2 + O(z^3) + z^-4 + z^-3 + z^-2 + O(z^-1) sage: L(z^3/(1-z), valuation=-4) - z^-4 + z^-3 + z^-2 + z^-1 + 1 + z + z^2 + O(z^3) + z^-4 + z^-3 + z^-2 + O(z^-1) sage: L(z^3/(1-z), valuation=0) - 1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7) + 1 + z + z^2 + O(z^3) sage: L = LazyLaurentSeriesRing(ZZ, 'z') sage: L(lambda n: 1/(n+1), degree=3) @@ -426,6 +445,250 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, constant=No raise ValueError(f"unable to convert {x} into {self}") + def undefined(self, valuation=None): + r""" + Return an uninitialized series. + + INPUT: + + - ``valuation`` -- integer; a lower bound for the valuation of the series + + Power series can be defined recursively (see + :meth:`sage.rings.lazy_series.LazyModuleElement.define()` for + more examples). + + EXAMPLES:: + + sage: L. = LazyTaylorSeriesRing(QQ) + sage: s = L.undefined(1) + sage: s.define(z + (s^2+s(z^2))/2) + sage: s + z + z^2 + z^3 + 2*z^4 + 3*z^5 + 6*z^6 + 11*z^7 + O(z^8) + """ + return self(None, valuation=valuation) + + unknown = undefined + + class options(GlobalOptions): + r""" + Set and display the options for lazy series. + + If no parameters are set, then the function returns a copy of + the options dictionary. + + The ``options`` to lazy series can be accessed as using + :class:`LazySeriesRing.options`. + + @OPTIONS@ + + EXAMPLES:: + + sage: LLS. = LazyLaurentSeriesRing(QQ) + sage: LLS.options + Current options for lazy series rings + - constant_length: 3 + - display_length: 7 + + sage: LLS.options.display_length + 7 + sage: f = 1 / (1 + z) + sage: f + 1 - z + z^2 - z^3 + z^4 - z^5 + z^6 + O(z^7) + sage: LLS.options.display_length = 10 + sage: f + 1 - z + z^2 - z^3 + z^4 - z^5 + z^6 - z^7 + z^8 - z^9 + O(z^10) + sage: g = LLS(lambda n: n^2, valuation=-2, degree=5, constant=42) + sage: g + 4*z^-2 + z^-1 + z + 4*z^2 + 9*z^3 + 16*z^4 + 42*z^5 + 42*z^6 + 42*z^7 + O(z^8) + sage: h = 1 / (1 - z) # This is exact + sage: h + 1 + z + z^2 + O(z^3) + sage: LLS.options.constant_length = 1 + sage: g + 4*z^-2 + z^-1 + z + 4*z^2 + 9*z^3 + 16*z^4 + 42*z^5 + O(z^6) + sage: h + 1 + O(z) + sage: LazyLaurentSeriesRing.options._reset() + sage: LazyLaurentSeriesRing.options.display_length + 7 + """ + NAME = 'lazy series rings' + module = 'sage.rings.lazy_series_ring' + display_length = dict(default=7, + description='the number of coefficients to display from the valuation', + checker=lambda x: x in ZZ and x > 0) + constant_length = dict(default=3, + description='the number of coefficients to display for nonzero constant series', + checker=lambda x: x in ZZ and x > 0) + + @cached_method + def one(self): + r""" + Return the constant series `1`. + + EXAMPLES:: + + sage: L = LazyLaurentSeriesRing(ZZ, 'z') + sage: L.one() + 1 + + sage: L = LazyTaylorSeriesRing(ZZ, 'z') + sage: L.one() + 1 + + sage: m = SymmetricFunctions(ZZ).m() + sage: L = LazySymmetricFunctions(m) + sage: L.one() + m[] + + """ + R = self.base_ring() + coeff_stream = Stream_exact([R.one()], self._sparse, constant=R.zero(), order=0) + return self.element_class(self, coeff_stream) + + @cached_method + def zero(self): + r""" + Return the zero series. + + EXAMPLES:: + + sage: L = LazyLaurentSeriesRing(ZZ, 'z') + sage: L.zero() + 0 + + sage: s = SymmetricFunctions(ZZ).s() + sage: L = LazySymmetricFunctions(s) + sage: L.zero() + 0 + + sage: L = LazyDirichletSeriesRing(ZZ, 'z') + sage: L.zero() + 0 + + sage: L = LazyTaylorSeriesRing(ZZ, 'z') + sage: L.zero() + 0 + """ + return self.element_class(self, Stream_zero(self._sparse)) + + def characteristic(self): + """ + Return the characteristic of this lazy power series ring, which + is the same as the characteristic of its base ring. + + EXAMPLES:: + + sage: L. = LazyLaurentSeriesRing(ZZ) + sage: L.characteristic() + 0 + + sage: R. = LazyLaurentSeriesRing(GF(11)); R + Lazy Laurent Series Ring in w over Finite Field of size 11 + sage: R.characteristic() + 11 + + sage: R. = LazyTaylorSeriesRing(GF(7)); R + Multivariate Lazy Taylor Series Ring in x, y over Finite Field of size 7 + sage: R.characteristic() + 7 + + sage: L = LazyDirichletSeriesRing(ZZ, "s") + sage: L.characteristic() + 0 + """ + return self.base_ring().characteristic() + + def _coerce_map_from_(self, S): + """ + Return ``True`` if a coercion from ``S`` exists. + + EXAMPLES:: + + sage: L = LazyLaurentSeriesRing(GF(2), 'z') + sage: L.has_coerce_map_from(ZZ) + True + sage: L.has_coerce_map_from(GF(2)) + True + + sage: L = LazyTaylorSeriesRing(GF(2), 'z') + sage: L.has_coerce_map_from(ZZ) + True + sage: L.has_coerce_map_from(GF(2)) + True + + sage: s = SymmetricFunctions(GF(2)).s() + sage: L = LazySymmetricFunctions(s) + sage: L.has_coerce_map_from(ZZ) + True + sage: L.has_coerce_map_from(GF(2)) + True + """ + if self.base_ring().has_coerce_map_from(S): + return True + + R = self._laurent_poly_ring + return R.has_coerce_map_from(S) + + def _coerce_map_from_base_ring(self): + """ + Return a coercion map from the base ring of ``self``. + + EXAMPLES:: + + sage: L = LazyLaurentSeriesRing(QQ, 'z') + sage: phi = L._coerce_map_from_base_ring() + sage: phi(2) + 2 + sage: phi(2, valuation=-2) + 2*z^-2 + sage: phi(2, valuation=-2, constant=3, degree=1) + 2*z^-2 + 3*z + 3*z^2 + 3*z^3 + O(z^4) + + sage: L = LazyDirichletSeriesRing(QQ, 'z') + sage: phi = L._coerce_map_from_base_ring() + sage: phi(2) + 2 + sage: phi(2, valuation=2) + 2/2^z + sage: phi(2, valuation=2, constant=4) + 2/2^z + 4/3^z + 4/4^z + 4/5^z + O(1/(6^z)) + """ + # Return a DefaultConvertMap_unique; this can pass additional + # arguments to _element_constructor_, unlike the map returned + # by UnitalAlgebras.ParentMethods._coerce_map_from_base_ring. + return self._generic_coerce_map(self.base_ring()) + + def is_sparse(self): + """ + Return whether ``self`` is sparse or not. + + EXAMPLES:: + + sage: L = LazyLaurentSeriesRing(ZZ, 'z', sparse=False) + sage: L.is_sparse() + False + + sage: L = LazyLaurentSeriesRing(ZZ, 'z', sparse=True) + sage: L.is_sparse() + True + """ + return self._sparse + + def is_exact(self): + """ + Return if ``self`` is exact or not. + + EXAMPLES:: + + sage: L = LazyLaurentSeriesRing(ZZ, 'z') + sage: L.is_exact() + True + sage: L = LazyLaurentSeriesRing(RR, 'z') + sage: L.is_exact() + False + """ + return self.base_ring().is_exact() class LazyLaurentSeriesRing(LazySeriesRing): """ @@ -445,7 +708,7 @@ class LazyLaurentSeriesRing(LazySeriesRing): sage: L. = LazyLaurentSeriesRing(QQ) sage: 1 / (1 - z) - 1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7) + 1 + z + z^2 + O(z^3) sage: 1 / (1 - z) == 1 / (1 - z) True sage: L in Fields @@ -533,7 +796,8 @@ class LazyLaurentSeriesRing(LazySeriesRing): sage: L(f, valuation=1, degree=4, constant=5) z - z^2 + z^3 + 5*z^4 + 5*z^5 + 5*z^6 + O(z^7) - Power series can be defined recursively (see :meth:`define()` for + Power series can be defined recursively (see + :meth:`sage.rings.lazy_series.LazyModuleElement.define()` for more examples):: sage: L. = LazyLaurentSeriesRing(ZZ) @@ -572,6 +836,7 @@ class LazyLaurentSeriesRing(LazySeriesRing): sage: L. = LazyLaurentSeriesRing(ZZ, sparse=False) sage: L.is_sparse() False + """ Element = LazyLaurentSeries @@ -603,7 +868,6 @@ def __init__(self, base_ring, names, sparse=True, category=None): sage: L = LazyLaurentSeriesRing(E, 't') # not tested """ self._sparse = sparse - self._coeff_ring = base_ring # We always use the dense because our CS_exact is implemented densely self._laurent_poly_ring = LaurentPolynomialRing(base_ring, names) self._internal_poly_ring = self._laurent_poly_ring @@ -648,40 +912,6 @@ def _latex_(self): from sage.misc.latex import latex return latex(self.base_ring()) + r"(\!({})\!)".format(self.variable_name()) - def characteristic(self): - """ - Return the characteristic of this lazy power series ring, which - is the same as the characteristic of its base ring. - - EXAMPLES:: - - sage: L. = LazyLaurentSeriesRing(ZZ) - sage: L.characteristic() - 0 - sage: R. = LazyLaurentSeriesRing(GF(11)); R - Lazy Laurent Series Ring in w over Finite Field of size 11 - sage: R.characteristic() - 11 - - """ - return self.base_ring().characteristic() - - def is_sparse(self): - """ - Return whether ``self`` is sparse or not. - - EXAMPLES:: - - sage: L = LazyLaurentSeriesRing(ZZ, 'z', sparse=False) - sage: L.is_sparse() - False - - sage: L = LazyLaurentSeriesRing(ZZ, 'z', sparse=True) - sage: L.is_sparse() - True - """ - return self._sparse - @cached_method def gen(self, n=0): r""" @@ -729,62 +959,24 @@ def gens(self): sage: L.gens() (z,) sage: 1/(1 - z) - 1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7) + 1 + z + z^2 + O(z^3) """ return tuple([self.gen(n) for n in range(self.ngens())]) - def _coerce_map_from_(self, S): + def _an_element_(self): """ - Return ``True`` if a coercion from ``S`` exists. + Return a Laurent series in ``self``. EXAMPLES:: - sage: L = LazyLaurentSeriesRing(GF(2), 'z') - sage: L.has_coerce_map_from(ZZ) - True - sage: L.has_coerce_map_from(GF(2)) - True + sage: L = LazyLaurentSeriesRing(ZZ, 'z') + sage: L.an_element() + z^-2 + 3*z^-1 + 2*z + z^2 + z^3 + z^4 + z^5 + O(z^6) """ - if self.base_ring().has_coerce_map_from(S): - return True - - R = self._laurent_poly_ring - return R.has_coerce_map_from(S) - - def _coerce_map_from_base_ring(self): - """ - Return a coercion map from the base ring of ``self``. - - EXAMPLES:: - - sage: L = LazyLaurentSeriesRing(QQ, 'z') - sage: phi = L._coerce_map_from_base_ring() - sage: phi(2) - 2 - sage: phi(2, valuation=-2) - 2*z^-2 - sage: phi(2, valuation=-2, constant=3, degree=1) - 2*z^-2 + 3*z + 3*z^2 + 3*z^3 + O(z^4) - """ - # Return a DefaultConvertMap_unique; this can pass additional - # arguments to _element_constructor_, unlike the map returned - # by UnitalAlgebras.ParentMethods._coerce_map_from_base_ring. - return self._generic_coerce_map(self.base_ring()) - - def _an_element_(self): - """ - Return a Laurent series in ``self``. - - EXAMPLES:: - - sage: L = LazyLaurentSeriesRing(ZZ, 'z') - sage: L.an_element() - z^-2 + 3*z^-1 + 2*z + z^2 + z^3 + z^4 + z^5 + O(z^6) - """ - R = self.base_ring() - coeff_stream = Stream_exact([R.an_element(), 3, 0, 2*R.an_element(), 1], - self._sparse, order=-2, constant=R.one()) - return self.element_class(self, coeff_stream) + R = self.base_ring() + coeff_stream = Stream_exact([R.an_element(), 3, 0, 2*R.an_element(), 1], + self._sparse, order=-2, constant=R.one()) + return self.element_class(self, coeff_stream) def some_elements(self): """ @@ -813,7 +1005,7 @@ def some_elements(self): [0, 1, z, z^-3 + z^-1 + 2 + z + z^2 + z^3, z^2 + z^3 + z^4 + z^5 + O(z^6), - z^-3 + z^-2 + z^-1 + 2 + 2*z + 2*z^2 + 2*z^3 + O(z^4), + z^-3 + z^-2 + z^-1 + 2 + 2*z + 2*z^2 + O(z^3), z^-2 + z^-1 + z + z^2 + z^4 + O(z^5)] """ z = self.gen() @@ -821,77 +1013,6 @@ def some_elements(self): (1 - 2*z**-3)/(1 - z + 3*z**2), self(lambda n: n**2, valuation=-2)] return elts - @cached_method - def one(self): - r""" - Return the constant series `1`. - - EXAMPLES:: - - sage: L = LazyLaurentSeriesRing(ZZ, 'z') - sage: L.one() - 1 - """ - R = self.base_ring() - coeff_stream = Stream_exact([R.one()], self._sparse, constant=R.zero(), degree=1) - return self.element_class(self, coeff_stream) - - @cached_method - def zero(self): - r""" - Return the zero series. - - EXAMPLES:: - - sage: L = LazyLaurentSeriesRing(ZZ, 'z') - sage: L.zero() - 0 - """ - return self.element_class(self, Stream_zero(self._sparse)) - - # add options to class - class options(GlobalOptions): - r""" - Set and display the options for Lazy Laurent series. - - If no parameters are set, then the function returns a copy of - the options dictionary. - - The ``options`` to Lazy Laurent series can be accessed as using - :class:`LazyLaurentSeriesRing.options` of :class:`LazyLaurentSeriesRing`. - - @OPTIONS@ - - EXAMPLES:: - - sage: LLS. = LazyLaurentSeriesRing(QQ) - sage: LLS.options.display_length - 7 - sage: f = 1/(1-z) - sage: f - 1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7) - sage: LLS.options.display_length = 10 - sage: f - 1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + z^7 + z^8 + z^9 + O(z^10) - sage: g = LLS(lambda n: n^2, valuation=-2, degree=5, constant=42) - sage: g - 4*z^-2 + z^-1 + z + 4*z^2 + 9*z^3 + 16*z^4 + 42*z^5 + 42*z^6 + 42*z^7 + O(z^8) - sage: LLS.options.constant_length = 1 - sage: g - 4*z^-2 + z^-1 + z + 4*z^2 + 9*z^3 + 16*z^4 + 42*z^5 + O(z^6) - sage: LazyLaurentSeriesRing.options._reset() - sage: LazyLaurentSeriesRing.options.display_length - 7 - """ - NAME = 'LazyLaurentSeriesRing' - module = 'sage.rings.lazy_series_ring' - display_length = dict(default=7, - description='the number of coefficients to display from the valuation', - checker=lambda x: x in ZZ and x > 0) - constant_length = dict(default=3, - description='the number of coefficients to display for nonzero constant series', - checker=lambda x: x in ZZ and x > 0) - def series(self, coefficient, valuation, degree=None, constant=None): r""" Return a lazy Laurent series. @@ -993,9 +1114,731 @@ def _monomial(self, c, n): """ return self._laurent_poly_ring(c).shift(n) +###################################################################### + +class LazyTaylorSeriesRing(LazySeriesRing): + """ + The ring of (possibly multivariate) lazy Taylor series. + + INPUT: + + - ``base_ring`` -- base ring of this Taylor series ring + - ``names`` -- name(s) of the generator of this Taylor series ring + - ``sparse`` -- (default: ``True``) whether this series is sparse or not + + EXAMPLES:: + + sage: LazyTaylorSeriesRing(ZZ, 't') + Lazy Taylor Series Ring in t over Integer Ring + + sage: L. = LazyTaylorSeriesRing(QQ); L + Multivariate Lazy Taylor Series Ring in x, y over Rational Field + """ + Element = LazyTaylorSeries + + def __init__(self, base_ring, names, sparse=True, category=None): + """ + Initialize ``self``. + + TESTS:: + + sage: L = LazyTaylorSeriesRing(ZZ, 't') + sage: TestSuite(L).run(skip=['_test_elements', '_test_associativity', '_test_distributivity', '_test_zero']) + """ + from sage.structure.category_object import normalize_names + names = normalize_names(-1, names) + self._sparse = sparse + self._laurent_poly_ring = PolynomialRing(base_ring, names) + if len(names) == 1: + self._internal_poly_ring = self._laurent_poly_ring + else: + coeff_ring = PolynomialRing(base_ring, names) + self._internal_poly_ring = PolynomialRing(coeff_ring, "DUMMY_VARIABLE") + category = Algebras(base_ring.category()) + if base_ring in Fields(): + category &= CompleteDiscreteValuationRings() + elif base_ring in IntegralDomains(): + category &= IntegralDomains() + elif base_ring in Rings().Commutative(): + category = category.Commutative() + + if base_ring.is_zero(): + category = category.Finite() + else: + category = category.Infinite() + Parent.__init__(self, base=base_ring, names=names, + category=category) + + def _repr_(self): + """ + String representation of this Taylor series ring. + + EXAMPLES:: + + sage: LazyTaylorSeriesRing(GF(2), 'z') + Lazy Taylor Series Ring in z over Finite Field of size 2 + """ + BR = self.base_ring() + if len(self.variable_names()) == 1: + return "Lazy Taylor Series Ring in {} over {}".format(self.variable_name(), BR) + generators_rep = ", ".join(self.variable_names()) + return "Multivariate Lazy Taylor Series Ring in {} over {}".format(generators_rep, BR) + + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: L = LazyTaylorSeriesRing(GF(2), 'z') + sage: latex(L) + \Bold{F}_{2} [\![z]\!] + """ + from sage.misc.latex import latex + generators_rep = ", ".join(self.variable_names()) + return latex(self.base_ring()) + r"[\![{}]\!]".format(generators_rep) + + def _monomial(self, c, n): + r""" + Return the interpretation of the coefficient ``c`` at index ``n``. + + EXAMPLES:: + + sage: L = LazyTaylorSeriesRing(ZZ, 'z') + sage: L._monomial(2, 3) + 2*z^3 + """ + m = len(self.variable_names()) + L = self._laurent_poly_ring + if m == 1: + return L(c) * L.gen() ** n + return L(c) + + @cached_method + def gen(self, n=0): + """ + Return the ``n``-th generator of ``self``. + + EXAMPLES:: + + sage: L = LazyTaylorSeriesRing(ZZ, 'z') + sage: L.gen() + z + sage: L.gen(3) + Traceback (most recent call last): + ... + IndexError: there is only one generator + """ + m = len(self.variable_names()) + if n > m: + if m == 1: + raise IndexError("there is only one generator") + raise IndexError("there are only %s generators" % m) + + R = self._laurent_poly_ring + BR = self.base_ring() + if len(self.variable_names()) == 1: + coeff_stream = Stream_exact([BR.one()], self._sparse, constant=BR.zero(), order=1) + else: + coeff_stream = Stream_exact([R.gen(n)], self._sparse, constant=BR.zero(), order=1) + return self.element_class(self, coeff_stream) + + def ngens(self): + r""" + Return the number of generators of ``self``. + + EXAMPLES:: + + sage: L. = LazyTaylorSeriesRing(ZZ) + sage: L.ngens() + 1 + """ + return len(self.variable_names()) + + @cached_method + def gens(self): + """ + Return the generators of ``self``. + + EXAMPLES:: + + sage: L = LazyTaylorSeriesRing(ZZ, 'x,y') + sage: L.gens() + (x, y) + """ + return tuple([self.gen(n) for n in range(self.ngens())]) + + def _element_constructor_(self, x=None, valuation=None, constant=None, degree=None, check=True): + """ + Construct a Taylor series from ``x``. + + INPUT: + + - ``x`` -- data used to the define a Taylor series + - ``valuation`` -- integer (optional); integer; a lower bound for the valuation of the series + - ``constant`` -- (optional) the eventual constant of the series + - ``degree`` -- (optional) the degree when the series is ``constant`` + - ``check`` -- (optional) check that coefficients are homogeneous of the correct degree when they are retrieved + + EXAMPLES:: + + sage: L = LazyTaylorSeriesRing(GF(2), 'z') + sage: L(2) + 0 + sage: L(3) + 1 + + sage: L = LazyTaylorSeriesRing(ZZ, 'z') + sage: L(lambda i: i, 5, 1, 10) + 5*z^5 + 6*z^6 + 7*z^7 + 8*z^8 + 9*z^9 + z^10 + z^11 + z^12 + O(z^13) + sage: L(lambda i: i, 5, (1, 10)) + 5*z^5 + 6*z^6 + 7*z^7 + 8*z^8 + 9*z^9 + z^10 + z^11 + z^12 + O(z^13) + + sage: X = L(constant=5, degree=2); X + 5*z^2 + 5*z^3 + 5*z^4 + O(z^5) + sage: X.valuation() + 2 + + sage: e = L(lambda n: n+1); e + 1 + 2*z + 3*z^2 + 4*z^3 + 5*z^4 + 6*z^5 + 7*z^6 + O(z^7) + sage: f = e^-1; f + 1 - 2*z + z^2 + O(z^7) + sage: f.coefficient(10) + 0 + sage: f[20] + 0 + + sage: L(valuation=2, constant=1) + z^2 + z^3 + z^4 + O(z^5) + sage: L(constant=1) + 1 + z + z^2 + O(z^3) + + Alternatively, ``x`` can be a list of elements of the base ring. + Then these elements are read as coefficients of the terms of + degrees starting from the ``valuation``. In this case, ``constant`` + may be just an element of the base ring instead of a tuple or can be + simply omitted if it is zero:: + + sage: f = L([1,2,3,4], 1); f + z + 2*z^2 + 3*z^3 + 4*z^4 + + sage: g = L([1,3,5,7,9], 5, -1); g + z^5 + 3*z^6 + 5*z^7 + 7*z^8 + 9*z^9 - z^10 - z^11 - z^12 + O(z^13) + + .. TODO:: + + Add a method to change the sparse/dense implementation. + + Finally, ``x`` can be a polynomial:: + + sage: P. = QQ[] + sage: p = x + 3*x^2 + x^5 + sage: L. = LazyTaylorSeriesRing(ZZ) + sage: L(p) + x + 3*x^2 + x^5 + + sage: L(p, valuation=0) + 1 + 3*x + x^4 + + sage: P. = QQ[] + sage: p = x + y^2 + x*y + sage: L. = LazyTaylorSeriesRing(ZZ) + sage: L(p) + x + (x*y+y^2) + + TESTS:: + + sage: L. = LazyTaylorSeriesRing(ZZ) + sage: L(constant=1) + Traceback (most recent call last): + ... + ValueError: constant must be zero for multivariate Taylor series + + sage: L(lambda n: 0) + O(x,y)^7 + + sage: L(lambda n: n)[3]; + Traceback (most recent call last): + ... + ValueError: coefficient 3 at degree 3 is not a homogeneous polynomial + + sage: L([1, 2, 3]); + Traceback (most recent call last): + ... + ValueError: unable to convert [1, 2, 3] into a lazy Taylor series + + sage: L(lambda n: n, degree=3); + Traceback (most recent call last): + ... + ValueError: coefficients must be homogeneous polynomials of the correct degree + + """ + if valuation is not None: + if valuation < 0: + raise ValueError("the valuation of a Taylor series must be positive") + if len(self.variable_names()) > 1: + raise ValueError("valuation must not be specified for multivariate Taylor series") + if len(self.variable_names()) > 1: + valuation = 0 + + R = self._laurent_poly_ring + BR = self.base_ring() + if x is None: + assert degree is None + coeff_stream = Stream_uninitialized(self._sparse, valuation) + return self.element_class(self, coeff_stream) + try: + # Try to build stuff using the polynomial ring constructor + x = R(x) + except (TypeError, ValueError): + pass + if isinstance(constant, (tuple, list)): + constant, degree = constant + if constant is not None: + if len(self.variable_names()) > 1 and constant: + raise ValueError("constant must be zero for multivariate Taylor series") + constant = BR(constant) + if x in R: + if not x and not constant: + coeff_stream = Stream_zero(self._sparse) + else: + if not x: + coeff_stream = Stream_exact([], self._sparse, + order=valuation, + degree=degree, + constant=constant) + return self.element_class(self, coeff_stream) + + if len(self.variable_names()) == 1: + v = x.valuation() + d = x.degree() + p_list = [x[i] for i in range(v, d + 1)] + if valuation is not None: + v = valuation + else: + p_dict = x.homogeneous_components() + v = min(p_dict.keys()) + d = max(p_dict.keys()) + p_list = [p_dict.get(i, 0) for i in range(v, d + 1)] + + coeff_stream = Stream_exact(p_list, self._sparse, + order=v, + constant=constant, + degree=degree) + return self.element_class(self, coeff_stream) + + if isinstance(x, LazyTaylorSeries): + if x._coeff_stream._is_sparse is self._sparse: + return self.element_class(self, x._coeff_stream) + # TODO: Implement a way to make a self._sparse copy + raise NotImplementedError("cannot convert between sparse and dense") + if callable(x): + if valuation is None: + valuation = 0 + if degree is not None: + if constant is None: + constant = ZZ.zero() + if len(self.variable_names()) == 1: + p = [BR(x(i)) for i in range(valuation, degree)] + else: + p = [R(x(i)) for i in range(valuation, degree)] + if not all(e.is_homogeneous() and e.degree() == i + for i, e in enumerate(p, valuation)): + raise ValueError("coefficients must be homogeneous polynomials of the correct degree") + coeff_stream = Stream_exact(p, self._sparse, + order=valuation, + constant=constant, + degree=degree) + return self.element_class(self, coeff_stream) + coeff_ring = self._internal_poly_ring.base_ring() + if check and len(self.variable_names()) > 1: + def y(n): + e = R(x(n)) + if not e or e.is_homogeneous() and e.degree() == n: + return e + raise ValueError("coefficient %s at degree %s is not a homogeneous polynomial" % (e, n)) + coeff_stream = Stream_function(y, coeff_ring, self._sparse, valuation) + else: + coeff_stream = Stream_function(x, coeff_ring, self._sparse, valuation) + return self.element_class(self, coeff_stream) + raise ValueError(f"unable to convert {x} into a lazy Taylor series") + + def _an_element_(self): + """ + Return a Taylor series in ``self``. + + EXAMPLES:: + + sage: L = LazyTaylorSeriesRing(ZZ, 'z') + sage: L.an_element() + z + z^2 + z^3 + z^4 + O(z^5) + """ + c = self.base_ring().an_element() + R = self._laurent_poly_ring + coeff_stream = Stream_exact([R.one()], self._sparse, order=1, constant=c) + return self.element_class(self, coeff_stream) + + +###################################################################### + +class LazyCompletionGradedAlgebra(LazySeriesRing): + r""" + The completion of a graded alebra consisting of formal series. + + For a graded algebra `A`, we can form a completion of `A` consisting of + all formal series of `A` such that each homogeneous component is + a finite linear combination of basis elements of `A`. + + INPUT: + + - ``basis`` -- a graded algebra + - ``names`` -- name(s) of the alphabets + - ``sparse`` -- (default: ``True``) whether we use a sparse or + a dense representation + + EXAMPLES:: + + sage: NCSF = NonCommutativeSymmetricFunctions(QQ) + sage: S = NCSF.Complete() + sage: L = S.formal_series_ring() + sage: L + Lazy completion of Non-Commutative Symmetric Functions over the Rational Field in the Complete basis + + sage: f = 1 / (1 - L(S[1])) + sage: f + S[] + S[1] + (S[1,1]) + (S[1,1,1]) + (S[1,1,1,1]) + (S[1,1,1,1,1]) + (S[1,1,1,1,1,1]) + O^7 + sage: g = 1 / (1 - L(S[2])) + sage: g + S[] + S[2] + (S[2,2]) + (S[2,2,2]) + O^7 + sage: f * g + S[] + S[1] + (S[1,1]+S[2]) + (S[1,1,1]+S[1,2]) + + (S[1,1,1,1]+S[1,1,2]+S[2,2]) + (S[1,1,1,1,1]+S[1,1,1,2]+S[1,2,2]) + + (S[1,1,1,1,1,1]+S[1,1,1,1,2]+S[1,1,2,2]+S[2,2,2]) + O^7 + sage: g * f + S[] + S[1] + (S[1,1]+S[2]) + (S[1,1,1]+S[2,1]) + + (S[1,1,1,1]+S[2,1,1]+S[2,2]) + (S[1,1,1,1,1]+S[2,1,1,1]+S[2,2,1]) + + (S[1,1,1,1,1,1]+S[2,1,1,1,1]+S[2,2,1,1]+S[2,2,2]) + O^7 + sage: f * g - g * f + (S[1,2]-S[2,1]) + (S[1,1,2]-S[2,1,1]) + + (S[1,1,1,2]+S[1,2,2]-S[2,1,1,1]-S[2,2,1]) + + (S[1,1,1,1,2]+S[1,1,2,2]-S[2,1,1,1,1]-S[2,2,1,1]) + O^7 + """ + Element = LazyCompletionGradedAlgebraElement + + def __init__(self, basis, sparse=True, category=None): + """ + Initialize ``self``. + + TESTS:: + + sage: s = SymmetricFunctions(ZZ).s() + sage: L = LazySymmetricFunctions(s) + sage: TestSuite(L).run(skip=['_test_elements', '_test_associativity', '_test_distributivity', '_test_zero']) + """ + base_ring = basis.base_ring() + if basis in Algebras.TensorProducts: + self._arity = len(basis._sets) + else: + if basis not in Algebras.Graded: + raise ValueError("basis should be a graded algebra") + self._arity = 1 + category = Algebras(base_ring.category()) + if base_ring in Fields(): + category &= CompleteDiscreteValuationRings() + elif base_ring in IntegralDomains(): + category &= IntegralDomains() + elif base_ring in Rings().Commutative(): + category = category.Commutative() + + if base_ring.is_zero(): + category = category.Finite() + else: + category = category.Infinite() + Parent.__init__(self, base=base_ring, category=category) + self._sparse = sparse + self._laurent_poly_ring = basis + if self._laurent_poly_ring not in Rings().Commutative(): + from sage.algebras.free_algebra import FreeAlgebra + self._internal_poly_ring = FreeAlgebra(self._laurent_poly_ring, 1, "DUMMY_VARIABLE") + else: + self._internal_poly_ring = PolynomialRing(self._laurent_poly_ring, "DUMMY_VARIABLE") + + def _repr_(self): + """ + String representation of the lazy symmetric functions ring. + + EXAMPLES:: + + sage: s = SymmetricFunctions(GF(2)).s() + sage: LazySymmetricFunctions(s) + Lazy completion of Symmetric Functions over Finite Field of size 2 in the Schur basis + """ + return "Lazy completion of {}".format(self._laurent_poly_ring) + + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: s = SymmetricFunctions(GF(2)).s() + sage: L = LazySymmetricFunctions(s) + sage: latex(L) + \text{\texttt{Symmetric{ }Functions{ }over{ }Finite{ }Field{ }of{ }size{ }2{ }in{ }the{ }Schur{ }basis}} + """ + from sage.misc.latex import latex + return latex(self._laurent_poly_ring) + + def _monomial(self, c, n): + r""" + Return the interpretation of the coefficient ``c`` at index ``n``. + + EXAMPLES:: + + sage: m = SymmetricFunctions(ZZ).m() + sage: s = SymmetricFunctions(ZZ).s() + sage: L = LazySymmetricFunctions(m) + sage: L._monomial(s[2,1], 3) + 2*m[1, 1, 1] + m[2, 1] + """ + L = self._laurent_poly_ring + return L(c) + + def _element_constructor_(self, x=None, valuation=None, degree=None, check=True): + r""" + Construct a lazy element in ``self`` from ``x``. + + INPUT: + + - ``x`` -- data used to the define a lazy element + - ``valuation`` -- integer (optional); integer; a lower bound for + the valuation of the series + - ``degree`` -- (optional) the degree when the lazy element + has finite support + - ``check`` -- (optional) check that coefficients are homogeneous of + the correct degree when they are retrieved + + EXAMPLES:: + + sage: m = SymmetricFunctions(GF(2)).m() + sage: L = LazySymmetricFunctions(m) + sage: L(2) + 0 + sage: L(3) + m[] + + sage: m = SymmetricFunctions(ZZ).m() + sage: L = LazySymmetricFunctions(m) + sage: f = L(lambda i: m([i]), valuation=5, degree=10); f + m[5] + m[6] + m[7] + m[8] + m[9] + + sage: f.coefficient(6) + m[6] + sage: f[20] + 0 + + Alternatively, ``x`` can be a list of elements of the base ring. + Then these elements are read as coefficients of the terms of + degrees starting from the ``valuation``:: + + sage: f = L([m[1],m[2],m[3]], valuation=1); f + m[1] + m[2] + m[3] + + .. TODO:: + + Add a method to change the sparse/dense implementation. + + Finally, ``x`` can be a symmetric function:: + + sage: m = SymmetricFunctions(ZZ).m() + sage: s = SymmetricFunctions(ZZ).s() + sage: L = LazySymmetricFunctions(m) + sage: L(s.an_element()) + 2*m[] + 2*m[1] + (3*m[1,1]+3*m[2]) + + TESTS:: + + sage: e = SymmetricFunctions(ZZ).e() + sage: h = SymmetricFunctions(ZZ).h() + sage: L = LazySymmetricFunctions(tensor([h, e])) + sage: L(lambda n: 0) + O^7 + + sage: L(lambda n: tensor([h[n], e([])]) + tensor([h([]), e[n]]), degree=3) + (2*h[]#e[]) + (h[]#e[1]+h[1]#e[]) + (h[]#e[2]+h[2]#e[]) + + sage: L(lambda n: n)[3]; + Traceback (most recent call last): + ... + ValueError: coefficient 3*h[] # e[] should be an element of homogeneous degree 3 but has degree 0 + + sage: L([1, 2, 3]); + Traceback (most recent call last): + ... + ValueError: coefficient 2*h[] # e[] should be an element of homogeneous degree 1 but has degree 0 + + sage: L(lambda n: n, degree=3); + Traceback (most recent call last): + ... + ValueError: coefficient h[] # e[] should be an element of homogeneous degree 1 but has degree 0 + """ + if valuation is None: + valuation = 0 + if valuation < 0: + raise ValueError("the valuation of a lazy completion element must be nonnegative") + + R = self._laurent_poly_ring + if x is None: + assert degree is None + coeff_stream = Stream_uninitialized(self._sparse, valuation) + return self.element_class(self, coeff_stream) + try: + # Try to build stuff using the polynomial ring constructor + x = R(x) + except (TypeError, ValueError, NotImplementedError): + pass + if x in R: + if not x: + coeff_stream = Stream_zero(self._sparse) + else: + p_dict = {} + if self._arity == 1: + for f in x.terms(): + d = f.degree() + p_dict[d] = p_dict.get(d, 0) + f + else: + for f in x.terms(): + try: + d = f.degree() + except (TypeError, ValueError, AttributeError): + # FIXME: Fallback for symmetric functions in multiple variables + d = sum(p.size() for p in f.support()) + p_dict[d] = p_dict.get(d, 0) + f + v = min(p_dict) + d = max(p_dict) + p_list = [p_dict.get(i, 0) for i in range(v, d + 1)] + + coeff_stream = Stream_exact(p_list, self._sparse, + order=v, + constant=0, + degree=degree) + return self.element_class(self, coeff_stream) + + if isinstance(x, self.Element): + if x._coeff_stream._is_sparse is self._sparse: + return self.element_class(self, x._coeff_stream) + # TODO: Implement a way to make a self._sparse copy + raise NotImplementedError("cannot convert between sparse and dense") + + if self._arity == 1: + def check_homogeneous_of_degree(f, d): + if not f: + return + try: + d1 = f.homogeneous_degree() + if d1 == d: + return + except ValueError: + raise ValueError("coefficient %s should be an element of homogeneous degree %s" % (f, d)) + raise ValueError("coefficient %s should be an element of homogeneous degree %s but has degree %s" % (f, d, d1)) + else: + def check_homogeneous_of_degree(f, d): + if not f: + return + for m in f.monomials(): + try: + d1 = m.degree() + except AttributeError: + # FIXME: Fallback for symmetric functions in multiple variables + for t in m.support(): + d1 = sum(p.size() for p in t) + if d1 != d: + raise ValueError("coefficient %s should be an element of homogeneous degree %s but has degree %s" % (f, d, d1)) + except (TypeError, ValueError): + raise ValueError("coefficient %s is not homogeneous") + if d1 != d: + raise ValueError("coefficient %s should be an element of homogeneous degree %s but has degree %s" % (f, d, d1)) + + if isinstance(x, (tuple, list)): + if degree is None: + degree = valuation + len(x) + p = [R(e) for e in x] + for i, e in enumerate(p, valuation): + check_homogeneous_of_degree(e, i) + coeff_stream = Stream_exact(p, self._sparse, + order=valuation, + constant=0, + degree=degree) + return self.element_class(self, coeff_stream) + if callable(x): + if degree is not None: + p = [R(x(i)) for i in range(valuation, degree)] + for i, e in enumerate(p, valuation): + check_homogeneous_of_degree(e, i) + coeff_stream = Stream_exact(p, self._sparse, + order=valuation, + constant=0, + degree=degree) + return self.element_class(self, coeff_stream) + coeff_ring = self._internal_poly_ring.base_ring() + if check: + def y(n): + e = R(x(n)) + check_homogeneous_of_degree(e, n) + return e + + coeff_stream = Stream_function(y, coeff_ring, self._sparse, valuation) + else: + coeff_stream = Stream_function(x, coeff_ring, self._sparse, valuation) + return self.element_class(self, coeff_stream) + raise ValueError(f"unable to convert {x} into a lazy completion element") + + def _an_element_(self): + """ + Return a lazy symmetric function in ``self``. + + EXAMPLES:: + + sage: m = SymmetricFunctions(ZZ).m() + sage: L = LazySymmetricFunctions(m) + sage: L.an_element() + m[] + """ + R = self._laurent_poly_ring + coeff_stream = Stream_exact([R.one()], self._sparse, order=1, constant=0) + return self.element_class(self, coeff_stream) + + +###################################################################### + +class LazySymmetricFunctions(LazyCompletionGradedAlgebra): + """ + The ring of lazy symmetric functions. + + INPUT: + + - ``basis`` -- the ring of symmetric functions + - ``names`` -- name(s) of the alphabets + - ``sparse`` -- (default: ``True``) whether we use a sparse or a dense representation + + EXAMPLES:: + + sage: s = SymmetricFunctions(ZZ).s() + sage: LazySymmetricFunctions(s) + Lazy completion of Symmetric Functions over Integer Ring in the Schur basis + + sage: m = SymmetricFunctions(ZZ).m() + sage: LazySymmetricFunctions(tensor([s, m])) + Lazy completion of Symmetric Functions over Integer Ring in the Schur basis # Symmetric Functions over Integer Ring in the monomial basis + """ + Element = LazySymmetricFunction + + +###################################################################### + class LazyDirichletSeriesRing(LazySeriesRing): """ - Lazy Dirichlet series ring. + The ring of lazy Dirichlet series. INPUT: @@ -1023,7 +1866,6 @@ def __init__(self, base_ring, names, sparse=True, category=None): raise ValueError("positive characteristic not allowed for Dirichlet series") self._sparse = sparse - self._coeff_ring = base_ring # TODO: it would be good to have something better than the symbolic ring self._laurent_poly_ring = SR self._internal_poly_ring = PolynomialRing(base_ring, names, sparse=True) @@ -1048,18 +1890,22 @@ def _repr_(self): """ return "Lazy Dirichlet Series Ring in {} over {}".format(self.variable_name(), self.base_ring()) - def characteristic(self): - """ - Return the characteristic of this lazy power series ring, which - is the same as the characteristic of its base ring. + @cached_method + def one(self): + r""" + Return the constant series `1`. EXAMPLES:: - sage: L = LazyDirichletSeriesRing(ZZ, "s") - sage: L.characteristic() - 0 + sage: L = LazyDirichletSeriesRing(ZZ, 'z') + sage: L.one() + 1 + sage: ~L.one() + 1 + O(1/(8^z)) """ - return self.base_ring().characteristic() + R = self.base_ring() + coeff_stream = Stream_exact([R.one()], self._sparse, constant=R.zero(), order=1) + return self.element_class(self, coeff_stream) def _coerce_map_from_(self, S): """ @@ -1075,29 +1921,8 @@ def _coerce_map_from_(self, S): """ if self.base_ring().has_coerce_map_from(S): return True - return False - def _coerce_map_from_base_ring(self): - r""" - Return a coercion map from the base ring of ``self``. - - EXAMPLES:: - - sage: L = LazyDirichletSeriesRing(QQ, 'z') - sage: phi = L._coerce_map_from_base_ring() - sage: phi(2) - 2 - sage: phi(2, valuation=2) - 2/2^z - sage: phi(2, valuation=2, constant=4) - 2/2^z + 4/3^z + 4/4^z + 4/5^z + O(1/(6^z)) - """ - # Return a DefaultConvertMap_unique; this can pass additional - # arguments to _element_constructor_, unlike the map returned - # by UnitalAlgebras.ParentMethods._coerce_map_from_base_ring. - return self._generic_coerce_map(self.base_ring()) - def _element_constructor_(self, x=None, valuation=None, degree=None, constant=None, coefficients=None): r""" Construct a Dirichlet series from ``x``. @@ -1225,35 +2050,6 @@ def _an_element_(self): c = self.base_ring().an_element() return self.element_class(self, Stream_exact([], self._sparse, constant=c, order=4)) - @cached_method - def one(self): - """ - Return the constant series `1`. - - EXAMPLES:: - - sage: L = LazyDirichletSeriesRing(ZZ, 'z') - sage: L.one() - 1 - sage: ~L.one() - 1 + O(1/(8^z)) - """ - R = self.base_ring() - return self.element_class(self, Stream_exact([R.one()], self._sparse, order=1)) - - @cached_method - def zero(self): - """ - Return the zero series. - - EXAMPLES:: - - sage: L = LazyDirichletSeriesRing(ZZ, 'z') - sage: L.zero() - 0 - """ - return self.element_class(self, Stream_zero(self._sparse)) - def _monomial(self, c, n): r""" Return the interpretation of the coefficient ``c`` at index ``n``. @@ -1270,4 +2066,3 @@ def _monomial(self, c, n): except (ValueError, TypeError): return '({})/{}^{}'.format(self.base_ring()(c), n, self.variable_name()) - options = LazyLaurentSeriesRing.options diff --git a/src/sage/rings/noncommutative_ideals.pyx b/src/sage/rings/noncommutative_ideals.pyx index af446f23663..29599c2ac0f 100644 --- a/src/sage/rings/noncommutative_ideals.pyx +++ b/src/sage/rings/noncommutative_ideals.pyx @@ -337,14 +337,26 @@ class Ideal_nc(Ideal_generic): return self.__side def __mul__(self, other): - """ + r""" + Multiply ``self`` with ``other``. + Multiplication of a one-sided ideal with its ring from the other side yields a two-sided ideal. + Let `L` (resp. `R`) be a left (resp. right) ideal, then the product + `LR` is a twosided ideal generated by `x y`, where `x` (resp. `y`) + is a generator of `L` (resp. `R`). + + .. TODO:: + + The product of left (resp. right) ideals is a left + (resp. right) ideal. However, these do not necessarily have + simple generating sets. + TESTS:: sage: MS = MatrixSpace(QQ,2,2) - sage: IL = MS*[2*MS.0,3*MS.1]; IL + sage: IL = MS * [2*MS.0,3*MS.1]; IL Left Ideal ( [2 0] @@ -361,7 +373,7 @@ class Ideal_nc(Ideal_generic): [0 1] ) of Full MatrixSpace of 2 by 2 dense matrices over Rational Field - sage: IL*MS # indirect doctest + sage: IL * MS Twosided Ideal ( [2 0] @@ -371,12 +383,26 @@ class Ideal_nc(Ideal_generic): [0 0] ) of Full MatrixSpace of 2 by 2 dense matrices over Rational Field - sage: IR*IR + + sage: IL * IR + Twosided Ideal + ( + [0 3] + [0 0] + ) + of Full MatrixSpace of 2 by 2 dense matrices over Rational Field + + sage: IR * IR Traceback (most recent call last): ... - NotImplementedError: Cannot multiply non-commutative ideals. - + NotImplementedError: cannot multiply non-commutative ideals """ + if isinstance(other, Ideal_nc) and self.ring() == other.ring(): + if self.side() == "left" and other.side() == "right": + gens = [z for z in (x * y for x in self.gens() for y in other.gens()) if z] + return self.ring().ideal(gens, side='twosided') + raise NotImplementedError("cannot multiply non-commutative ideals") + if not isinstance(other, Ideal_nc): # Perhaps other is a ring and thus has its own # multiplication. @@ -390,4 +416,6 @@ class Ideal_nc(Ideal_generic): if other.side() == 'left': return other return other.ring().ideal(other.gens(), side='twosided') - raise NotImplementedError("Cannot multiply non-commutative ideals.") + + raise NotImplementedError("cannot multiply non-commutative ideals") + diff --git a/src/sage/rings/number_field/class_group.py b/src/sage/rings/number_field/class_group.py index 018ff5f5c62..da255ee6fa2 100644 --- a/src/sage/rings/number_field/class_group.py +++ b/src/sage/rings/number_field/class_group.py @@ -184,7 +184,7 @@ def inverse(self): sage: ~G(2, a) Fractional ideal class (2, a^2 + 2*a - 1) """ - m = AbelianGroupElement.inverse(self) + m = AbelianGroupElement.__invert__(self) m._value = (~self.ideal()).reduce_equiv() return m diff --git a/src/sage/rings/number_field/number_field.py b/src/sage/rings/number_field/number_field.py index 66dddb5597e..5a38cb794b6 100644 --- a/src/sage/rings/number_field/number_field.py +++ b/src/sage/rings/number_field/number_field.py @@ -3413,7 +3413,7 @@ def dirichlet_group(self): m = self.conductor() d = self.degree() A = _splitting_classes_gens_(self,m,d) - # d could be improve to be the exponenent of the Galois group rather than the degree, but I do not see how to how about it already. + # d could be improved to be the exponent of the Galois group rather than the degree, but I do not see how to go about that yet. G = DirichletGroup(m, CyclotomicField(d)) H = [G(1)] for chi in G: @@ -6002,12 +6002,21 @@ def decomposition_type(self, p): 2 sage: M.decomposition_type(Q1) [(2, 5, 1)] + + Check that :trac:`34514` is fixed:: + + sage: K. = NumberField(x^4 + 18*x^2 - 1) + sage: R. = K[] + sage: L. = K.extension(y^2 + 9*a^3 - 2*a^2 + 162*a - 38) + sage: [L.decomposition_type(i) for i in K.primes_above(3)] + [[(1, 1, 2)], [(1, 1, 2)], [(1, 2, 1)]] """ v0 = self.base_ring().valuation(p) e0 = v0.value_group().gen().denominator() - f0 = v0.residue_field().degree() + # Ideally we would compute f using the degree, but residue fields of relative extensions are currently implemented using polynomial quotient rings (this will hopefully be improved after #28485). + C0 = v0.residue_field().cardinality() valuations = v0.extensions(self) - ef = [(v.value_group().gen().denominator() // e0, v.residue_field().degree() // f0) for v in valuations] + ef = [(v.value_group().gen().denominator() // e0, v.residue_field().cardinality().exact_log(C0)) for v in valuations] return sorted([(e, f, g) for ((e, f), g) in Counter(ef).items()]) def gen(self, n=0): @@ -11957,7 +11966,7 @@ def _polymake_init_(self): EXAMPLES:: sage: Z = QuadraticField(7) - sage: polymake(Z) # optional - polymake # indirect doctest + sage: polymake(Z) # optional - jupymake # indirect doctest QuadraticExtension """ 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 0cea74a50eb..24fc7db909e 100644 --- a/src/sage/rings/number_field/number_field_element_quadratic.pyx +++ b/src/sage/rings/number_field/number_field_element_quadratic.pyx @@ -314,12 +314,12 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute): EXAMPLES:: sage: K. = QuadraticField(5) - sage: polymake(3+2*sqrt5) # optional - polymake + sage: polymake(3+2*sqrt5) # optional - jupymake 3+2r5 - sage: polymake(2**100/7 - 2*sqrt5/3**50) # optional - polymake + sage: polymake(2**100/7 - 2*sqrt5/3**50) # optional - jupymake 1267650600228229401496703205376/7-2/717897987691852588770249r5 sage: K. = QuadraticField(-1) - sage: polymake(i) # optional - polymake + sage: polymake(i) # optional - jupymake Traceback (most recent call last): ... TypeError: Negative values for the root of the extension ... Bad Thing... @@ -331,9 +331,9 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute): sage: x = polygen(QQ, 'x') sage: K = NumberField(x^2 - x -1, 'a', embedding=(1-AA(5).sqrt())/2) sage: L = NumberField(x^2 - x -1, 'a', embedding=(1+AA(5).sqrt())/2) - sage: polymake(K.gen()) # optional - polymake + sage: polymake(K.gen()) # optional - jupymake 1/2-1/2r5 - sage: polymake(L.gen()) # optional - polymake + sage: polymake(L.gen()) # optional - jupymake 1/2+1/2r5 """ cdef Integer a = Integer.__new__(Integer) diff --git a/src/sage/rings/padics/padic_lattice_element.py b/src/sage/rings/padics/padic_lattice_element.py index 5f679a5ea72..ace965211bf 100644 --- a/src/sage/rings/padics/padic_lattice_element.py +++ b/src/sage/rings/padics/padic_lattice_element.py @@ -501,7 +501,7 @@ def _richcmp_(self, other, op): def is_equal_to(self, other, prec): r""" - Return ``True`` if this element is indisting + Return ``True`` if this element is indistinguishable from ``other`` at precision ``prec`` EXAMPLES:: diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index 659e903ce75..ac21b71553f 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -165,7 +165,7 @@ def InfinitePolynomial(A, p): """ from sage.structure.element import parent - if hasattr(A,'_P'): + if hasattr(A, '_P'): if parent(p) is A._P or (A._P.base_ring().has_coerce_map_from(parent(p))): return InfinitePolynomial_dense(A, p) # MPolynomialRing_polydict is crab. So, in that case, use sage_eval @@ -173,14 +173,14 @@ def InfinitePolynomial(A, p): if isinstance(A._P, MPolynomialRing_polydict): from sage.rings.polynomial.infinite_polynomial_ring import GenDictWithBasering from sage.misc.sage_eval import sage_eval - p = sage_eval(repr(p), GenDictWithBasering(A._P,A._P.gens_dict())) + p = sage_eval(repr(p), GenDictWithBasering(A._P, A._P.gens_dict())) return InfinitePolynomial_dense(A, p) else: # Now there remains to fight the oddities and bugs of libsingular. PP = p.parent() if A._P.has_coerce_map_from(PP): - if A._P.ngens() == PP.ngens(): # coercion is sometimes by position! - f = PP.hom(PP.variable_names(),A._P) + if A._P.ngens() == PP.ngens(): # coercion is sometimes by position! + f = PP.hom(PP.variable_names(), A._P) try: return InfinitePolynomial_dense(A, f(p)) except (ValueError, TypeError): @@ -205,6 +205,7 @@ def InfinitePolynomial(A, p): return InfinitePolynomial_dense(A, sage_eval(repr(p), GenDictWithBasering(A._P, A._P.gens_dict()))) return InfinitePolynomial_sparse(A, p) + class InfinitePolynomial_sparse(RingElement): """ Element of a sparse Polynomial Ring with a Countably Infinite Number of Variables. @@ -317,8 +318,8 @@ def __call__(self, *args, **kwargs): x_100 + x_0 """ - #Replace any InfinitePolynomials by their underlying polynomials - if hasattr(self._p,'variables'): + # Replace any InfinitePolynomials by their underlying polynomials + if hasattr(self._p, 'variables'): V = [str(x) for x in self._p.variables()] else: V = [] @@ -327,19 +328,19 @@ def __call__(self, *args, **kwargs): if isinstance(value, InfinitePolynomial_sparse): kwargs[kw] = value._p V.append(kw) - if hasattr(value._p,'variables'): + if hasattr(value._p, 'variables'): V.extend([str(x) for x in value._p.variables()]) args = list(args) for i, arg in enumerate(args): if isinstance(arg, InfinitePolynomial_sparse): args[i] = arg._p - if hasattr(arg._p,'variables'): + if hasattr(arg._p, 'variables'): V.extend([str(x) for x in arg._p.variables()]) - V=list(set(V)) + V = list(set(V)) V.sort(key=self.parent().varname_key, reverse=True) if V: from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - R = PolynomialRing(self._p.base_ring(),V,order=self.parent()._order) + R = PolynomialRing(self._p.base_ring(), V, order=self.parent()._order) else: return self res = R(self._p)(*args, **kwargs) @@ -422,14 +423,15 @@ def __getattr__(self, s): True """ - if s=='__members__': + if s == '__members__': return dir(self._p) - if s=='__methods__': - return [X for X in dir(self._p) if hasattr(self._p,X) and ('method' in str(type(getattr(self._p,X))))] + if s == '__methods__': + return [X for X in dir(self._p) if hasattr(self._p, X) + and ('method' in str(type(getattr(self._p, X))))] try: - return getattr(self._p,s) + return getattr(self._p, s) except AttributeError: - raise AttributeError('%s has no attribute %s'%(self.__class__, s)) + raise AttributeError('%s has no attribute %s' % (self.__class__, s)) def ring(self): """ @@ -558,22 +560,27 @@ def _add_(self, x): sage: x[1] + x[2] # indirect doctest x_2 + x_1 + Check adding from a different parent:: + + sage: Y. = PolynomialRing(QQ) + sage: x[0] - x_0 + 0 """ # One may need a new parent for self._p and x._p try: - return InfinitePolynomial_sparse(self.parent(),self._p+x._p) + return InfinitePolynomial_sparse(self.parent(), self._p + x._p) except Exception: pass - ## We can now assume that self._p and x._p actually are polynomials, - ## hence, their parent is not simply the underlying ring. + # We can now assume that self._p and x._p actually are polynomials, + # hence, their parent is not simply the underlying ring. VarList = list(set(self._p.parent().variable_names()).union(set(x._p.parent().variable_names()))) VarList.sort(key=self.parent().varname_key, reverse=True) if VarList: from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - R = PolynomialRing(self._p.base_ring(),VarList,order=self.parent()._order) + R = PolynomialRing(self._p.base_ring(), VarList, order=self.parent()._order) else: R = self._p.base_ring() - return InfinitePolynomial_sparse(self.parent(),R(self._p) + R(x._p)) + return InfinitePolynomial_sparse(self.parent(), R(self._p) + R(x._p)) def _mul_(self, x): """ @@ -585,19 +592,19 @@ def _mul_(self, x): """ try: - return InfinitePolynomial_sparse(self.parent(),self._p*x._p) + return InfinitePolynomial_sparse(self.parent(), self._p * x._p) except Exception: pass - ## We can now assume that self._p and x._p actually are polynomials, - ## hence, their parent is not just the underlying ring. + # We can now assume that self._p and x._p actually are polynomials, + # hence, their parent is not just the underlying ring. VarList = list(set(self._p.parent().variable_names()).union(set(x._p.parent().variable_names()))) - VarList.sort(key=self.parent().varname_key,reverse=True) + VarList.sort(key=self.parent().varname_key, reverse=True) if VarList: from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - R = PolynomialRing(self._p.base_ring(),VarList,order=self.parent()._order) + R = PolynomialRing(self._p.base_ring(), VarList, order=self.parent()._order) else: R = self._p.base_ring() - return InfinitePolynomial_sparse(self.parent(),R(self._p) * R(x._p)) + return InfinitePolynomial_sparse(self.parent(), R(self._p) * R(x._p)) def gcd(self, x): """ @@ -615,7 +622,7 @@ def gcd(self, x): P = self.parent() self._p = P._P(self._p) x._p = P._P(x._p) - return InfinitePolynomial_sparse(self.parent(),self._p.gcd(x._p)) + return InfinitePolynomial_sparse(self.parent(), self._p.gcd(x._p)) def _rmul_(self, left): """ @@ -626,7 +633,7 @@ def _rmul_(self, left): 4 """ - return InfinitePolynomial_sparse(self.parent(),left*self._p) + return InfinitePolynomial_sparse(self.parent(), left * self._p) def _lmul_(self, right): """ @@ -637,7 +644,7 @@ def _lmul_(self, right): 4*alpha_3 """ - return InfinitePolynomial_sparse(self.parent(),self._p*right) + return InfinitePolynomial_sparse(self.parent(), self._p * right) def _div_(self, x): r""" @@ -681,15 +688,38 @@ def _div_(self, x): """ if not x.variables(): p = self.base_ring()(x._p) - divisor = self.base_ring().one()/p # use induction + divisor = self.base_ring().one() / p # use induction OUTP = self.parent().tensor_with_ring(divisor.base_ring()) - return OUTP(self)*OUTP(divisor) + return OUTP(self) * OUTP(divisor) else: from sage.rings.fraction_field_element import FractionFieldElement field = self.parent().fraction_field() # there remains a problem in reduction return FractionFieldElement(field, self, x, reduce=False) + def _floordiv_(self, x): + """ + EXAMPLES:: + + sage: X. = InfinitePolynomialRing(ZZ) + sage: x[2]//x[2] # indirect doctest + 1 + """ + try: + return InfinitePolynomial_sparse(self.parent(), self._p // x._p) + except Exception: + pass + # We can now assume that self._p and x._p actually are polynomials, + # hence, their parent is not just the underlying ring. + VarList = list(set(self._p.parent().variable_names()).union(set(x._p.parent().variable_names()))) + VarList.sort(key=self.parent().varname_key, reverse=True) + if VarList: + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + R = PolynomialRing(self._p.base_ring(), VarList, order=self.parent()._order) + else: + R = self._p.base_ring() + return InfinitePolynomial_sparse(self.parent(), R(self._p) // R(x._p)) + def _sub_(self, x): """ EXAMPLES:: @@ -700,19 +730,19 @@ def _sub_(self, x): """ try: - return InfinitePolynomial_sparse(self.parent(),self._p-x._p) + return InfinitePolynomial_sparse(self.parent(), self._p - x._p) except Exception: pass - ## We can now assume that self._p and x._p actually are polynomials, - ## hence, their parent is not just the underlying ring. + # We can now assume that self._p and x._p actually are polynomials, + # hence, their parent is not just the underlying ring. VarList = list(set(self._p.parent().variable_names()).union(x._p.parent().variable_names())) - VarList.sort(key=self.parent().varname_key,reverse=True) + VarList.sort(key=self.parent().varname_key, reverse=True) if VarList: from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - R = PolynomialRing(self._p.base_ring(),VarList, order=self.parent()._order) + R = PolynomialRing(self._p.base_ring(), VarList, order=self.parent()._order) else: R = self._p.base_ring() - return InfinitePolynomial_sparse(self.parent(),R(self._p) - R(x._p)) + return InfinitePolynomial_sparse(self.parent(), R(self._p) - R(x._p)) def __pow__(self, n): """ @@ -738,15 +768,18 @@ def __pow__(self, n): if callable(n): if (self._p.parent() == self._p.base_ring()): return self - if not (hasattr(self._p,'variables') and self._p.variables()): + if not (hasattr(self._p, 'variables') and self._p.variables()): return self - if hasattr(n,'to_cycles') and hasattr(n,'__len__'): # duck typing Permutation + if hasattr(n, 'to_cycles') and hasattr(n, '__len__'): # duck typing Permutation # auxiliary function, necessary since n(m) raises an error if m>len(n) l = len(n) - p = lambda m: n(m) if 0=i: + if Lbig[j] >= i: ExpoBigSave = [e for e in Fbig[Lbig[j]]] ExpoBig = Fbig[Lbig[j]] found = True for k in gens: - if ExpoBig[k]len(n) l = len(n) - p = lambda m: n(m) if 0 right_r - q,r = self.__u.quo_rem(right.__u) + cdef LaurentPolynomial_univariate right = other + q, r = self.__u.quo_rem(right.__u) cdef LaurentPolynomial_univariate ql, qr ql = self._new_c() ql.__u = q @@ -1363,9 +1384,9 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): ql.__normalize() qr = self._new_c() qr.__u = r - qr.__n = 0 + qr.__n = self.__n qr.__normalize() - return (ql, qr) + return ql, qr cpdef _richcmp_(self, right_r, int op): r""" diff --git a/src/sage/rings/polynomial/msolve.py b/src/sage/rings/polynomial/msolve.py index b32f9f8505a..65ae859f751 100644 --- a/src/sage/rings/polynomial/msolve.py +++ b/src/sage/rings/polynomial/msolve.py @@ -3,19 +3,17 @@ Solution of polynomial systems using msolve `msolve `_ is a multivariate polynomial system solver -developed mainly by Jérémy Berthomieu (Sorbonne University), Christian Eder -(TU Kaiserslautern), and Mohab Safey El Din (Sorbonne University). +based on Gröbner bases. This module provide implementations of some operations on polynomial ideals -based on msolve. Currently the only supported operation is the computation of -the variety of zero-dimensional ideal over the rationals. +based on msolve. Note that msolve must be installed separately. .. SEEALSO:: -- :mod:`sage.features.msolve` -- :mod:`sage.rings.polynomial.multi_polynomial_ideal` + - :mod:`sage.features.msolve` + - :mod:`sage.rings.polynomial.multi_polynomial_ideal` """ import os @@ -28,23 +26,134 @@ from sage.misc.converting_dict import KeyConvertingDict from sage.misc.sage_eval import sage_eval from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.finite_rings.finite_field_base import FiniteField from sage.rings.rational_field import QQ from sage.rings.real_arb import RealBallField from sage.rings.real_double import RealDoubleField_class from sage.rings.real_mpfr import RealField_class from sage.rings.real_mpfi import RealIntervalField_class, RealIntervalField +from sage.structure.sequence import Sequence +def _run_msolve(ideal, options): + r""" + Internal utility function + """ + + base = ideal.base_ring() + if not (base is QQ or isinstance(base, FiniteField) and + base.is_prime_field() and base.characteristic() < 2**31): + raise NotImplementedError(f"unsupported base field: {base}") + + # Run msolve + + msolve().require() + + drlpolring = ideal.ring().change_ring(order='degrevlex') + polys = ideal.change_ring(drlpolring).gens() + msolve_in = tempfile.NamedTemporaryFile(mode='w', + encoding='ascii', delete=False) + command = ["msolve", "-f", msolve_in.name] + options + try: + print(",".join(drlpolring.variable_names()), file=msolve_in) + print(base.characteristic(), file=msolve_in) + print(*(pol._repr_().replace(" ", "") for pol in polys), + sep=',\n', file=msolve_in) + msolve_in.close() + msolve_out = subprocess.run(command, capture_output=True, text=True) + finally: + os.unlink(msolve_in.name) + msolve_out.check_returncode() + + return msolve_out.stdout + +def groebner_basis_degrevlex(ideal): + r""" + Compute a degrevlex Gröbner basis using msolve + + EXAMPLES:: + + sage: from sage.rings.polynomial.msolve import groebner_basis_degrevlex + + sage: R. = PolynomialRing(GF(101), 3, order='lex') + sage: I = sage.rings.ideal.Katsura(R,3) + sage: gb = groebner_basis_degrevlex(I); gb # optional - msolve + [a + 2*b + 2*c - 1, b*c - 19*c^2 + 10*b + 40*c, + b^2 - 41*c^2 + 20*b - 20*c, c^3 + 28*c^2 - 37*b + 13*c] + sage: gb.universe() is R # optional - msolve + False + sage: gb.universe().term_order() # optional - msolve + Degree reverse lexicographic term order + sage: ideal(gb).transformed_basis(other_ring=R) # optional - msolve + [c^4 + 38*c^3 - 6*c^2 - 6*c, 30*c^3 + 32*c^2 + b - 14*c, + a + 2*b + 2*c - 1] + + TESTS:: + + sage: R. = PolynomialRing(GF(536870909), 2) + sage: I = Ideal([ foo^2 - 1, bar^2 - 1 ]) + sage: I.groebner_basis(algorithm='msolve') # optional - msolve + [bar^2 - 1, foo^2 - 1] + + sage: R. = PolynomialRing(QQ, 2) + sage: I = Ideal([ x*y - 1, (x-2)^2 + (y-1)^2 - 1]) + sage: I.groebner_basis(algorithm='msolve') # optional - msolve + Traceback (most recent call last): + ... + NotImplementedError: unsupported base field: Rational Field + """ + + base = ideal.base_ring() + if not (isinstance(base, FiniteField) and base.is_prime_field() and + base.characteristic() < 2**31): + raise NotImplementedError(f"unsupported base field: {base}") + + drlpolring = ideal.ring().change_ring(order='degrevlex') + msolve_out = _run_msolve(ideal, ["-g", "2"]) + gbasis = sage_eval(msolve_out[:-2], locals=drlpolring.gens_dict()) + return Sequence(gbasis) -def _variety(ideal, ring, proof): +def variety(ideal, ring, *, proof=True): r""" Compute the variety of a zero-dimensional ideal using msolve. Part of the initial implementation was loosely based on the example interfaces available as part of msolve, with the authors' permission. + EXAMPLES:: + + sage: from sage.rings.polynomial.msolve import variety + sage: p = 536870909 + sage: R. = PolynomialRing(GF(p), 2, order='lex') + sage: I = Ideal([ x*y - 1, (x-2)^2 + (y-1)^2 - 1]) + sage: sorted(variety(I, GF(p^2), proof=False), key=str) # optional - msolve + [{x: 1, y: 1}, + {x: 254228855*z2 + 114981228, y: 232449571*z2 + 402714189}, + {x: 267525699, y: 473946006}, + {x: 282642054*z2 + 154363985, y: 304421338*z2 + 197081624}] + TESTS:: - sage: K. = PolynomialRing(QQ, 2, order='lex') + sage: p = 536870909 + sage: R. = PolynomialRing(GF(p), 2, order='lex') + sage: I = Ideal([ x*y - 1, (x-2)^2 + (y-1)^2 - 1]) + + sage: sorted(I.variety(algorithm="msolve", proof=False), key=str) # optional - msolve + [{x: 1, y: 1}, {x: 267525699, y: 473946006}] + + sage: K. = GF(p^2) + sage: sorted(I.variety(K, algorithm="msolve", proof=False), key=str) # optional - msolve + [{x: 1, y: 1}, + {x: 118750849*a + 194048031, y: 510295713*a + 18174854}, + {x: 267525699, y: 473946006}, + {x: 418120060*a + 75297182, y: 26575196*a + 44750050}] + + sage: R. = PolynomialRing(GF(2147483659), 2, order='lex') + sage: ideal([x, y]).variety(algorithm="msolve", proof=False) + Traceback (most recent call last): + ... + NotImplementedError: unsupported base field: Finite Field of size 2147483659 + + sage: R. = PolynomialRing(QQ, 2, order='lex') sage: I = Ideal([ x*y - 1, (x-2)^2 + (y-1)^2 - 1]) sage: I.variety(algorithm='msolve', proof=False) # optional - msolve @@ -98,67 +207,46 @@ def _variety(ideal, ring, proof): ... ValueError: positive-dimensional ideal - sage: K. = PolynomialRing(RR, 2, order='lex') + sage: R. = PolynomialRing(RR, 2, order='lex') sage: Ideal(x, y).variety(algorithm='msolve', proof=False) Traceback (most recent call last): ... NotImplementedError: unsupported base field: Real Field with 53 bits of precision - sage: K. = PolynomialRing(QQ, 2, order='lex') + sage: R. = PolynomialRing(QQ, 2, order='lex') sage: Ideal(x, y).variety(ZZ, algorithm='msolve', proof=False) Traceback (most recent call last): ... ValueError: no coercion from base field Rational Field to output ring Integer Ring """ - # Normalize and check input + proof = sage.structure.proof.proof.get_flag(proof, "polynomial") + if proof: + raise ValueError("msolve relies on heuristics; please use proof=False") base = ideal.base_ring() if ring is None: ring = base - proof = sage.structure.proof.proof.get_flag(proof, "polynomial") - if proof: - raise ValueError("msolve relies on heuristics; please use proof=False") - # As of msolve 0.2.4, prime fields seem to be supported, by I cannot - # make sense of msolve's output in the positive characteristic case. - # if not (base is QQ or isinstance(base, FiniteField) and - # base.is_prime_field() and base.characteristic() < 2**31): - if base is not QQ: - raise NotImplementedError(f"unsupported base field: {base}") if not ring.has_coerce_map_from(base): raise ValueError( f"no coercion from base field {base} to output ring {ring}") - # Run msolve - - msolve().require() - - drlpolring = ideal.ring().change_ring(order='degrevlex') - polys = ideal.change_ring(drlpolring).gens() - msolve_in = tempfile.NamedTemporaryFile(mode='w', - encoding='ascii', delete=False) - command = ["msolve", "-f", msolve_in.name] if isinstance(ring, (RealIntervalField_class, RealBallField, RealField_class, RealDoubleField_class)): parameterization = False - command += ["-p", str(ring.precision())] + options = ["-p", str(ring.precision())] else: parameterization = True - command += ["-P", "1"] - try: - print(",".join(drlpolring.variable_names()), file=msolve_in) - print(base.characteristic(), file=msolve_in) - print(*(pol._repr_().replace(" ", "") for pol in polys), - sep=',\n', file=msolve_in) - msolve_in.close() - msolve_out = subprocess.run(command, capture_output=True, text=True) - finally: - os.unlink(msolve_in.name) - msolve_out.check_returncode() + options = ["-P", "1"] + + msolve_out = _run_msolve(ideal, options) # Interpret output - data = sage_eval(msolve_out.stdout[:-2]) + try: + data = sage_eval(msolve_out[:-2]) + except SyntaxError: + raise NotImplementedError(f"unsupported msolve output format: {data}") dim = data[0] if dim == -1: @@ -172,23 +260,32 @@ def _variety(ideal, ring, proof): if parameterization: - def to_poly(p, upol=PolynomialRing(base, 't')): - assert len(p[1]) == p[0] + 1 - return upol(p[1]) + def to_poly(p, d=1, *, upol=PolynomialRing(base, 't')): + assert len(p[1]) == p[0] + 1 or p == [-1, [0]] + return upol(p[1])/d - if len(data) != 3: + try: + [char, nvars, deg, vars, _, [one, [elim, den, param]]] = data[1] + except (IndexError, ValueError): raise NotImplementedError( f"unsupported msolve output format: {data}") - [dim1, nvars, _, vars, _, [one, elim, den, param]] = data[1] - assert dim1.is_zero() + assert char == ideal.base_ring().characteristic() assert one.is_one() assert len(vars) == nvars ringvars = out_ring.variable_names() assert sorted(vars[:len(ringvars)]) == sorted(ringvars) vars = [out_ring(name) for name in vars[:len(ringvars)]] elim = to_poly(elim) + # Criterion suggested by Mohab Safey El Din to avoid cases where there + # is no rational parameterization or where the one returned by msolve + # has a significant probability of being incorrect. + if deg >= char > 0 or 0 < char <= 2**17 and deg != elim.degree(): + raise NotImplementedError(f"characteristic {char} too small") den = to_poly(den) - param = [to_poly(f)/d for [f, d] in param] + # As of msolve 0.4.4, param is of the form [pol, denom] in char 0, but + # [pol] in char p > 0. My understanding is that both cases will + # eventually use the same format, so let's not be too picky. + param = [to_poly(*f) for f in param] elim_roots = elim.roots(ring, multiplicities=False) variety = [] for rt in elim_roots: @@ -201,10 +298,9 @@ def to_poly(p, upol=PolynomialRing(base, 't')): else: - if len(data) != 2 or data[1][0] != 1: + if len(data[1]) < 2 or len(data[1]) != data[1][0] + 1: raise NotImplementedError( f"unsupported msolve output format: {data}") - _, [_, variety] = data if isinstance(ring, (RealIntervalField_class, RealBallField)): to_out_ring = ring else: @@ -213,7 +309,8 @@ def to_poly(p, upol=PolynomialRing(base, 't')): to_out_ring = lambda iv: ring.coerce(myRIF(iv).center()) vars = out_ring.gens() variety = [[to_out_ring(iv) for iv in point] - for point in variety] + for l in data[1][1:] + for point in l] return [KeyConvertingDict(out_ring, zip(vars, point)) for point in variety] diff --git a/src/sage/rings/polynomial/multi_polynomial_ideal.py b/src/sage/rings/polynomial/multi_polynomial_ideal.py index 10c0db501af..4e1937a4645 100644 --- a/src/sage/rings/polynomial/multi_polynomial_ideal.py +++ b/src/sage/rings/polynomial/multi_polynomial_ideal.py @@ -1775,6 +1775,82 @@ def syzygy_module(self): S = syz(self) return matrix(self.ring(), S) + @require_field + def free_resolution(self, *args, **kwds): + r""" + Return a free resolution of ``self``. + + For input options, see + :class:`~sage.homology.free_resolution.FreeResolution`. + + EXAMPLES:: + + sage: R. = PolynomialRing(QQ) + sage: f = 2*x^2 + y + sage: g = y + sage: h = 2*f + g + sage: I = R.ideal([f,g,h]) + sage: res = I.free_resolution() + sage: res + S^1 <-- S^2 <-- S^1 <-- 0 + sage: ascii_art(res.chain_complex()) + [-x^2] + [ y x^2] [ y] + 0 <-- C_0 <---------- C_1 <------- C_2 <-- 0 + + sage: q = ZZ['q'].fraction_field().gen() + sage: R. = q.parent()[] + sage: I = R.ideal([x^2+y^2+z^2, x*y*z^4]) + sage: I.free_resolution() + Traceback (most recent call last): + ... + NotImplementedError: the ring must be a polynomial ring using Singular + """ + from sage.rings.polynomial.multi_polynomial_libsingular import MPolynomialRing_libsingular + if isinstance(self.ring(), MPolynomialRing_libsingular): + from sage.homology.free_resolution import FiniteFreeResolution_singular + return FiniteFreeResolution_singular(self, *args, **kwds) + raise NotImplementedError("the ring must be a polynomial ring using Singular") + + @require_field + def graded_free_resolution(self, *args, **kwds): + r""" + Return a graded free resolution of ``self``. + + For input options, see + :class:`~sage.homology.graded_resolution.GradedFreeResolution`. + + EXAMPLES:: + + sage: R. = PolynomialRing(QQ) + sage: f = 2*x^2 + y^2 + sage: g = y^2 + sage: h = 2*f + g + sage: I = R.ideal([f,g,h]) + sage: I.graded_free_resolution(shifts=[1]) + S(-1) <-- S(-3)⊕S(-3) <-- S(-5) <-- 0 + + sage: f = 2*x^2 + y + sage: g = y + sage: h = 2*f + g + sage: I = R.ideal([f,g,h]) + sage: I.graded_free_resolution(degrees=[1,2]) + S(0) <-- S(-2)⊕S(-2) <-- S(-4) <-- 0 + + sage: q = ZZ['q'].fraction_field().gen() + sage: R. = q.parent()[] + sage: I = R.ideal([x^2+y^2+z^2, x*y*z^4]) + sage: I.graded_free_resolution() + Traceback (most recent call last): + ... + NotImplementedError: the ring must be a polynomial ring using Singular + """ + from sage.rings.polynomial.multi_polynomial_libsingular import MPolynomialRing_libsingular + if isinstance(self.ring(), MPolynomialRing_libsingular): + from sage.homology.graded_resolution import GradedFiniteFreeResolution_singular + return GradedFiniteFreeResolution_singular(self, *args, **kwds) + raise NotImplementedError("the ring must be a polynomial ring using Singular") + @handle_AA_and_QQbar @singular_gb_standard_options @libsingular_gb_standard_options @@ -2373,9 +2449,6 @@ def variety(self, ring=None, *, algorithm="triangular_decomposition", proof=True sage: I.variety(ring=AA) [{y: 1, x: 1}, {y: 0.3611030805286474?, x: 2.769292354238632?}] - sage: I.variety(RBF, algorithm='msolve', proof=False) # optional - msolve - [{x: [2.76929235423863 +/- 2.08e-15], y: [0.361103080528647 +/- 4.53e-16]}, - {x: 1.000000000000000, y: 1.000000000000000}] and a total of four intersections:: @@ -2394,6 +2467,13 @@ def variety(self, ring=None, *, algorithm="triangular_decomposition", proof=True {y: 0.3611030805286474?, x: 2.769292354238632?}, {y: 1, x: 1}] + We can also use the external program msolve to compute the variety. + See :mod:`~sage.rings.polynomial.msolve` for more information. :: + + sage: I.variety(RBF, algorithm='msolve', proof=False) # optional - msolve + [{x: [2.76929235423863 +/- 2.08e-15], y: [0.361103080528647 +/- 4.53e-16]}, + {x: 1.000000000000000, y: 1.000000000000000}] + Computation over floating point numbers may compute only a partial solution, or even none at all. Notice that x values are missing from the following variety:: @@ -2437,6 +2517,26 @@ def variety(self, ring=None, *, algorithm="triangular_decomposition", proof=True sage: v["y"] -7.464101615137755? + msolve also works over finite fields:: + + sage: R. = PolynomialRing(GF(536870909), 2, order='lex') + sage: I = Ideal([ x^2 - 1, y^2 - 1 ]) + sage: sorted(I.variety(algorithm='msolve', proof=False), key=str) # optional - msolve + [{x: 1, y: 1}, + {x: 1, y: 536870908}, + {x: 536870908, y: 1}, + {x: 536870908, y: 536870908}] + + but may fail in small characteristic, especially with ideals of high + degree with respect to the characteristic:: + + sage: R. = PolynomialRing(GF(3), 2, order='lex') + sage: I = Ideal([ x^2 - 1, y^2 - 1 ]) + sage: I.variety(algorithm='msolve', proof=False) # optional - msolve + Traceback (most recent call last): + ... + NotImplementedError: characteristic 3 too small + ALGORITHM: - With ``algorithm`` = ``"triangular_decomposition"`` (default), @@ -2453,7 +2553,7 @@ def variety(self, ring=None, *, algorithm="triangular_decomposition", proof=True return self._variety_triangular_decomposition(ring) elif algorithm == "msolve": from . import msolve - return msolve._variety(self, ring, proof) + return msolve.variety(self, ring, proof=proof) else: raise ValueError(f"unknown algorithm {algorithm!r}") @@ -3988,6 +4088,10 @@ def groebner_basis(self, algorithm='', deg_bound=None, mult_bound=None, prot=Fal 'macaulay2:mgb' Macaulay2's ``GroebnerBasis`` command with the strategy "MGB" (if available) + 'msolve' + `msolve `_ (if available, degrevlex order, + prime fields) + 'magma:GroebnerBasis' Magma's ``Groebnerbasis`` command (if available) @@ -4111,6 +4215,15 @@ def groebner_basis(self, algorithm='', deg_bound=None, mult_bound=None, prot=Fal sage: I.groebner_basis('macaulay2:mgb') # optional - macaulay2 [c^3 + 28*c^2 - 37*b + 13*c, b^2 - 41*c^2 + 20*b - 20*c, b*c - 19*c^2 + 10*b + 40*c, a + 2*b + 2*c - 1] + Over prime fields of small characteristic, we can also use + `msolve `_ (if available in the system program + search path):: + + sage: R. = PolynomialRing(GF(101), 3) + sage: I = sage.rings.ideal.Katsura(R,3) # regenerate to prevent caching + sage: I.groebner_basis('msolve') # optional - msolve + [a + 2*b + 2*c - 1, b*c - 19*c^2 + 10*b + 40*c, b^2 - 41*c^2 + 20*b - 20*c, c^3 + 28*c^2 - 37*b + 13*c] + :: sage: I = sage.rings.ideal.Katsura(P,3) # regenerate to prevent caching @@ -4248,8 +4361,8 @@ def groebner_basis(self, algorithm='', deg_bound=None, mult_bound=None, prot=Fal ALGORITHM: - Uses Singular, Magma (if available), Macaulay2 (if available), - Giac (if available), or a toy implementation. + Uses Singular, one of the other systems listed above (if available), + or a toy implementation. TESTS: @@ -4329,6 +4442,15 @@ def groebner_basis(self, algorithm='', deg_bound=None, mult_bound=None, prot=Fal sage: I = sage.rings.ideal.Katsura(P,3) # regenerate to prevent caching sage: I.groebner_basis('magma:GroebnerBasis') # optional - magma [a + (-60)*c^3 + 158/7*c^2 + 8/7*c - 1, b + 30*c^3 + (-79/7)*c^2 + 3/7*c, c^4 + (-10/21)*c^3 + 1/84*c^2 + 1/84*c] + + msolve currently supports the degrevlex order only:: + + sage: R. = PolynomialRing(GF(101), 3, order='lex') + sage: I = sage.rings.ideal.Katsura(R,3) # regenerate to prevent caching + sage: I.groebner_basis('msolve') # optional - msolve + Traceback (most recent call last): + ... + NotImplementedError: msolve only supports the degrevlex order (use transformed_basis()) """ from sage.rings.polynomial.multi_polynomial_sequence import PolynomialSequence from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing @@ -4414,7 +4536,14 @@ def groebner_basis(self, algorithm='', deg_bound=None, mult_bound=None, prot=Fal elif algorithm == 'giac:gbasis': from sage.libs.giac import groebner_basis as groebner_basis_libgiac gb = groebner_basis_libgiac(self, prot=prot, *args, **kwds) - + elif algorithm == 'msolve': + if self.ring().term_order() != 'degrevlex': + raise NotImplementedError("msolve only supports the degrevlex order " + "(use transformed_basis())") + if not (deg_bound is mult_bound is None) or prot: + raise NotImplementedError("unsupported options for msolve") + from . import msolve + return msolve.groebner_basis_degrevlex(self, *args, **kwds) else: raise NameError("Algorithm '%s' unknown."%algorithm) @@ -5224,7 +5353,6 @@ class MPolynomialIdeal_quotient(MPolynomialIdeal): Ideal (w^2 + x + z - 1) of Quotient of Multivariate Polynomial Ring in x, y, z, w over Rational Field by the ideal (x*y - z^2, y^2 - w^2) """ - def reduce(self, f): r""" Reduce an element modulo a Gröbner basis for this ideal. @@ -5330,3 +5458,4 @@ def __richcmp__(self, other, op): return not (contained and contains) else: # remaining case < return contained and not contains + diff --git a/src/sage/rings/polynomial/pbori/pbori.pyx b/src/sage/rings/polynomial/pbori/pbori.pyx index 9e3723c5c4b..e369473c3c4 100644 --- a/src/sage/rings/polynomial/pbori/pbori.pyx +++ b/src/sage/rings/polynomial/pbori/pbori.pyx @@ -182,7 +182,6 @@ and naturally the second option is faster. from cpython.object cimport Py_EQ, Py_NE from cython.operator cimport dereference as deref from cysignals.memory cimport sig_malloc, sig_free -from cysignals.signals cimport sig_on, sig_off from sage.ext.cplusplus cimport ccrepr import operator @@ -412,7 +411,7 @@ cdef class BooleanPolynomialRing(MPolynomialRing_base): pbnames = tuple(names) names = [name.replace('(', '').replace(')', '') for name in pbnames] - MPolynomialRing_base.__init__(self, GF(2), n, names, order) + MPolynomialRing_base.__init__(self, GF((2,1)), n, names, order) counter = 0 for i in range(len(order.blocks()) - 1): @@ -1929,7 +1928,7 @@ class BooleanMonomialMonoid(UniqueRepresentation, Monoid_class): cdef BooleanMonomial m self._ring = polring from sage.categories.monoids import Monoids - Parent.__init__(self, GF(2), names=polring._names, category=Monoids().Commutative()) + Parent.__init__(self, GF((2,1)), names=polring._names, category=Monoids().Commutative()) m = new_BM(self, polring) m._pbmonom = PBMonom(polring._pbring) @@ -5037,9 +5036,7 @@ class BooleanPolynomialIdeal(MPolynomialIdeal): else: if "redsb" not in kwds: kwds["redsb"]=True - sig_on() gb = self._groebner_basis(**kwds) - sig_off() if kwds.get("deg_bound", False) is False: g = GroebnerStrategy(gb[0].ring()) diff --git a/src/sage/rings/polynomial/polynomial_element.pyx b/src/sage/rings/polynomial/polynomial_element.pyx index 4d5bd945ef0..7f781c65060 100644 --- a/src/sage/rings/polynomial/polynomial_element.pyx +++ b/src/sage/rings/polynomial/polynomial_element.pyx @@ -126,7 +126,7 @@ from sage.misc.cachefunc import cached_function from sage.categories.map cimport Map from sage.categories.morphism cimport Morphism -from sage.misc.superseded import deprecation_cython as deprecation +from sage.misc.superseded import deprecation_cython as deprecation, deprecated_function_alias from sage.misc.cachefunc import cached_method from sage.rings.number_field.order import is_NumberFieldOrder @@ -8925,7 +8925,7 @@ cdef class Polynomial(CommutativeAlgebraElement): else: raise NotImplementedError("%s does not provide an xgcd implementation for univariate polynomials"%self.base_ring()) - def rational_reconstruct(self, m, n_deg=None, d_deg=None): + def rational_reconstruction(self, m, n_deg=None, d_deg=None): r""" Return a tuple of two polynomials ``(n, d)`` where ``self * d`` is congruent to ``n`` modulo ``m`` and @@ -8950,7 +8950,7 @@ cdef class Polynomial(CommutativeAlgebraElement): sage: z = PolynomialRing(QQ, 'z').gen() sage: p = -z**16 - z**15 - z**14 + z**13 + z**12 + z**11 - z**5 - z**4 - z**3 + z**2 + z + 1 sage: m = z**21 - sage: n, d = p.rational_reconstruct(m) + sage: n, d = p.rational_reconstruction(m) sage: print((n ,d)) (z^4 + 2*z^3 + 3*z^2 + 2*z + 1, z^10 + z^9 + z^8 + z^7 + z^6 + z^5 + z^4 + z^3 + z^2 + z + 1) sage: print(((p*d - n) % m ).is_zero()) @@ -8961,7 +8961,7 @@ cdef class Polynomial(CommutativeAlgebraElement): sage: z = PolynomialRing(ZZ, 'z').gen() sage: p = -z**16 - z**15 - z**14 + z**13 + z**12 + z**11 - z**5 - z**4 - z**3 + z**2 + z + 1 sage: m = z**21 - sage: n, d = p.rational_reconstruct(m) + sage: n, d = p.rational_reconstruction(m) sage: print((n ,d)) (z^4 + 2*z^3 + 3*z^2 + 2*z + 1, z^10 + z^9 + z^8 + z^7 + z^6 + z^5 + z^4 + z^3 + z^2 + z + 1) sage: print(((p*d - n) % m ).is_zero()) @@ -8973,12 +8973,12 @@ cdef class Polynomial(CommutativeAlgebraElement): sage: x = P.gen() sage: p = 7*x^5 - 10*x^4 + 16*x^3 - 32*x^2 + 128*x + 256 sage: m = x^5 - sage: n, d = p.rational_reconstruct(m, 3, 2) + sage: n, d = p.rational_reconstruction(m, 3, 2) sage: print((n ,d)) (-32*x^3 + 384*x^2 + 2304*x + 2048, 5*x + 8) sage: print(((p*d - n) % m ).is_zero()) True - sage: n, d = p.rational_reconstruct(m, 4, 0) + sage: n, d = p.rational_reconstruction(m, 4, 0) sage: print((n ,d)) (-10*x^4 + 16*x^3 - 32*x^2 + 128*x + 256, 1) sage: print(((p*d - n) % m ).is_zero()) @@ -8993,7 +8993,7 @@ cdef class Polynomial(CommutativeAlgebraElement): sage: # p = (1 + t^2*z + z^4) / (1 - t*z) sage: p = (1 + t^2*z + z^4)*(1 - t*z).inverse_mod(z^9) sage: m = z^9 - sage: n, d = p.rational_reconstruct(m) + sage: n, d = p.rational_reconstruction(m) sage: print((n ,d)) (-1/t*z^4 - t*z - 1/t, z - 1/t) sage: print(((p*d - n) % m ).is_zero()) @@ -9002,7 +9002,7 @@ cdef class Polynomial(CommutativeAlgebraElement): sage: n = -10*t^2*z^4 + (-t^2 + t - 1)*z^3 + (-t - 8)*z^2 + z + 2*t^2 - t sage: d = z^4 + (2*t + 4)*z^3 + (-t + 5)*z^2 + (t^2 + 2)*z + t^2 + 2*t + 1 sage: prec = 9 - sage: nc, dc = Pz((n.subs(z = w)/d.subs(z = w) + O(w^prec)).list()).rational_reconstruct(z^prec) + sage: nc, dc = Pz((n.subs(z = w)/d.subs(z = w) + O(w^prec)).list()).rational_reconstruction(z^prec) sage: print( (nc, dc) == (n, d) ) True @@ -9014,7 +9014,7 @@ cdef class Polynomial(CommutativeAlgebraElement): sage: # p = (1 + t^2*z + z^4) / (1 - t*z) mod z^9 sage: p = (1 + t^2*z + z^4) * sum((t*z)**i for i in range(9)) sage: m = z^9 - sage: n, d = p.rational_reconstruct(m,) + sage: n, d = p.rational_reconstruction(m,) sage: print((n ,d)) (-z^4 - t^2*z - 1, t*z - 1) sage: print(((p*d - n) % m ).is_zero()) @@ -9025,7 +9025,7 @@ cdef class Polynomial(CommutativeAlgebraElement): sage: x = PolynomialRing(Qp(5),'x').gen() sage: p = 4*x^5 + 3*x^4 + 2*x^3 + 2*x^2 + 4*x + 2 sage: m = x^6 - sage: n, d = p.rational_reconstruct(m, 3, 2) + sage: n, d = p.rational_reconstruction(m, 3, 2) sage: print(((p*d - n) % m ).is_zero()) True @@ -9036,34 +9036,34 @@ cdef class Polynomial(CommutativeAlgebraElement): sage: x = P.gen() sage: p = P(exp(z).list()) sage: m = x^5 - sage: n, d = p.rational_reconstruct(m, 4, 0) + sage: n, d = p.rational_reconstruction(m, 4, 0) sage: print((n ,d)) (1/24*x^4 + 1/6*x^3 + 1/2*x^2 + x + 1, 1) sage: print(((p*d - n) % m ).is_zero()) True sage: m = x^3 - sage: n, d = p.rational_reconstruct(m, 1, 1) + sage: n, d = p.rational_reconstruction(m, 1, 1) sage: print((n ,d)) (-x - 2, x - 2) sage: print(((p*d - n) % m ).is_zero()) True sage: p = P(log(1-z).list()) sage: m = x^9 - sage: n, d = p.rational_reconstruct(m, 4, 4) + sage: n, d = p.rational_reconstruction(m, 4, 4) sage: print((n ,d)) (25/6*x^4 - 130/3*x^3 + 105*x^2 - 70*x, x^4 - 20*x^3 + 90*x^2 - 140*x + 70) sage: print(((p*d - n) % m ).is_zero()) True sage: p = P(sqrt(1+z).list()) sage: m = x^6 - sage: n, d = p.rational_reconstruct(m, 3, 2) + sage: n, d = p.rational_reconstruction(m, 3, 2) sage: print((n ,d)) (1/6*x^3 + 3*x^2 + 8*x + 16/3, x^2 + 16/3*x + 16/3) sage: print(((p*d - n) % m ).is_zero()) True sage: p = P(exp(2*z).list()) sage: m = x^7 - sage: n, d = p.rational_reconstruct(m, 3, 3) + sage: n, d = p.rational_reconstruction(m, 3, 3) sage: print((n ,d)) (-x^3 - 6*x^2 - 15*x - 15, x^3 - 6*x^2 + 15*x - 15) sage: print(((p*d - n) % m ).is_zero()) @@ -9076,26 +9076,26 @@ cdef class Polynomial(CommutativeAlgebraElement): sage: x = P.gen() sage: p = P(exp(2*z).list()) sage: m = x^7 - sage: n, d = p.rational_reconstruct( m, 3, 3) + sage: n, d = p.rational_reconstruction(m, 3, 3) sage: print((n ,d)) # absolute tolerance 1e-10 (-x^3 - 6.0*x^2 - 15.0*x - 15.0, x^3 - 6.0*x^2 + 15.0*x - 15.0) .. SEEALSO:: * :mod:`sage.matrix.berlekamp_massey`, - * :meth:`sage.rings.polynomial.polynomial_zmod_flint.Polynomial_zmod_flint.rational_reconstruct` + * :meth:`sage.rings.polynomial.polynomial_zmod_flint.Polynomial_zmod_flint.rational_reconstruction` """ P = self.parent() if not P.base_ring().is_field(): if not P.base_ring().is_integral_domain(): - raise NotImplementedError("rational_reconstruct() " + raise NotImplementedError("rational_reconstruction() " "is only implemented when the base ring is a field " "or a integral domain, " "a workaround is to do a multimodular approach") Pf = P.base_extend(P.base_ring().fraction_field()) sF = Pf(self) mF = Pf(m) - n, d = sF.rational_reconstruct( mF, n_deg, d_deg) + n, d = sF.rational_reconstruction(mF, n_deg, d_deg) l = lcm([n.denominator(), d.denominator()]) n *= l d *= l @@ -9135,6 +9135,8 @@ cdef class Polynomial(CommutativeAlgebraElement): t1 = t1 / c return t1, t0 + rational_reconstruct = deprecated_function_alias(12696, rational_reconstruction) + def variables(self): """ Return the tuple of variables occurring in this polynomial. diff --git a/src/sage/rings/polynomial/polynomial_quotient_ring.py b/src/sage/rings/polynomial/polynomial_quotient_ring.py index bb5d8356be6..93e4a741925 100644 --- a/src/sage/rings/polynomial/polynomial_quotient_ring.py +++ b/src/sage/rings/polynomial/polynomial_quotient_ring.py @@ -321,7 +321,7 @@ class of the category, and store the current class of the quotient sage: isinstance(Q.an_element(),Q.element_class) True sage: [s for s in dir(Q.category().element_class) if not s.startswith('_')] - ['cartesian_product', 'inverse_of_unit', 'is_idempotent', 'is_one', 'is_unit', 'lift', 'powers'] + ['cartesian_product', 'inverse', 'inverse_of_unit', 'is_idempotent', 'is_one', 'is_unit', 'lift', 'powers'] sage: first_class = Q.__class__ We try to find out whether `Q` is a field. Indeed it is, and thus its category, @@ -339,6 +339,7 @@ class of the category, and store the current class of the quotient 'euclidean_degree', 'factor', 'gcd', + 'inverse', 'inverse_of_unit', 'is_idempotent', 'is_one', diff --git a/src/sage/rings/polynomial/polynomial_quotient_ring_element.py b/src/sage/rings/polynomial/polynomial_quotient_ring_element.py index 846cd727986..803e78f6e13 100644 --- a/src/sage/rings/polynomial/polynomial_quotient_ring_element.py +++ b/src/sage/rings/polynomial/polynomial_quotient_ring_element.py @@ -714,3 +714,27 @@ def trace(self): 389 """ return self.matrix().trace() + + def rational_reconstruction(self, *args, **kwargs): + r""" + Compute a rational reconstruction of this polynomial quotient + ring element to its cover ring. + + This method is a thin convenience wrapper around + :meth:`Polynomial.rational_reconstruction`. + + EXAMPLES:: + + sage: R. = GF(65537)[] + sage: m = x^11 + 25345*x^10 + 10956*x^9 + 13873*x^8 + 23962*x^7 + 17496*x^6 + 30348*x^5 + 7440*x^4 + 65438*x^3 + 7676*x^2 + 54266*x + 47805 + sage: f = 20437*x^10 + 62630*x^9 + 63241*x^8 + 12820*x^7 + 42171*x^6 + 63091*x^5 + 15288*x^4 + 32516*x^3 + 2181*x^2 + 45236*x + 2447 + sage: f_mod_m = R.quotient(m)(f) + sage: f_mod_m.rational_reconstruction() + (51388*x^5 + 29141*x^4 + 59341*x^3 + 7034*x^2 + 14152*x + 23746, + x^5 + 15208*x^4 + 19504*x^3 + 20457*x^2 + 11180*x + 28352) + """ + m = self.parent().modulus() + R = m.parent() + f = R(self._polynomial) + return f.rational_reconstruction(m, *args, **kwargs) + diff --git a/src/sage/rings/polynomial/polynomial_zmod_flint.pxd b/src/sage/rings/polynomial/polynomial_zmod_flint.pxd index d8cef40282f..c6a92f3df6c 100644 --- a/src/sage/rings/polynomial/polynomial_zmod_flint.pxd +++ b/src/sage/rings/polynomial/polynomial_zmod_flint.pxd @@ -14,4 +14,4 @@ cdef class Polynomial_zmod_flint(Polynomial_template): cdef int _set_list(self, x) except -1 cdef int _set_fmpz_poly(self, fmpz_poly_t) except -1 cpdef Polynomial _mul_trunc_opposite(self, Polynomial_zmod_flint other, length) - cpdef rational_reconstruct(self, m, n_deg=?, d_deg=?) + cpdef rational_reconstruction(self, m, n_deg=?, d_deg=?) diff --git a/src/sage/rings/polynomial/polynomial_zmod_flint.pyx b/src/sage/rings/polynomial/polynomial_zmod_flint.pyx index 748d8ce37c7..001ace2194c 100644 --- a/src/sage/rings/polynomial/polynomial_zmod_flint.pyx +++ b/src/sage/rings/polynomial/polynomial_zmod_flint.pyx @@ -45,6 +45,8 @@ from sage.structure.element cimport parent from sage.structure.element import coerce_binop from sage.rings.polynomial.polynomial_integer_dense_flint cimport Polynomial_integer_dense_flint +from sage.misc.superseded import deprecated_function_alias + # We need to define this stuff before including the templating stuff # to make sure the function get_cparent is found since it is used in # 'polynomial_template.pxi'. @@ -585,7 +587,7 @@ cdef class Polynomial_zmod_flint(Polynomial_template): nmod_poly_pow_trunc(&ans.x, &self.x, n, prec) return ans - cpdef rational_reconstruct(self, m, n_deg=0, d_deg=0): + cpdef rational_reconstruction(self, m, n_deg=0, d_deg=0): """ Construct a rational function n/d such that `p*d` is equivalent to `n` modulo `m` where `p` is this polynomial. @@ -594,7 +596,7 @@ cdef class Polynomial_zmod_flint(Polynomial_template): sage: P. = GF(5)[] sage: p = 4*x^5 + 3*x^4 + 2*x^3 + 2*x^2 + 4*x + 2 - sage: n, d = p.rational_reconstruct(x^9, 4, 4); n, d + sage: n, d = p.rational_reconstruction(x^9, 4, 4); n, d (3*x^4 + 2*x^3 + x^2 + 2*x, x^4 + 3*x^3 + x^2 + x) sage: (p*d % x^9) == n True @@ -638,6 +640,8 @@ cdef class Polynomial_zmod_flint(Polynomial_template): return t1, t0 + rational_reconstruct = deprecated_function_alias(12696, rational_reconstruction) + @cached_method def is_irreducible(self): """ @@ -736,8 +740,15 @@ cdef class Polynomial_zmod_flint(Polynomial_template): sage: (x^2 + 1).factor() (x + 2) * (x + 3) + It also works for prime-power moduli:: + + sage: R. = Zmod(23^5)[] + sage: (x^3 + 1).factor() + (x + 1) * (x^2 + 6436342*x + 1) + TESTS:: + sage: R. = GF(5)[] sage: (2*x^2 + 2).factor() (2) * (x + 2) * (x + 3) sage: P. = Zmod(10)[] @@ -745,10 +756,20 @@ cdef class Polynomial_zmod_flint(Polynomial_template): Traceback (most recent call last): ... NotImplementedError: factorization of polynomials over rings with composite characteristic is not implemented - """ - if not self.base_ring().is_field(): - raise NotImplementedError("factorization of polynomials over rings with composite characteristic is not implemented") + R = self.base_ring() + + if not R.is_field(): + p,e = R.characteristic().is_prime_power(get_data=True) + if not e: + raise NotImplementedError("factorization of polynomials over rings with composite characteristic is not implemented") + + # Factoring is well-defined for prime-power moduli. + # For simplicity we reuse the implementation for p-adics; + # presumably this can be done faster. + from sage.rings.padics.factory import Zp + f = self.change_ring(Zp(p, prec=e)) + return f.factor().base_change(self.parent()) return factor_helper(self) diff --git a/src/sage/rings/power_series_poly.pxd b/src/sage/rings/power_series_poly.pxd index f19d3567f9a..80c441798a1 100644 --- a/src/sage/rings/power_series_poly.pxd +++ b/src/sage/rings/power_series_poly.pxd @@ -1,6 +1,10 @@ from .power_series_ring_element cimport PowerSeries from sage.rings.polynomial.polynomial_element cimport Polynomial - +from sage.categories.action cimport Action cdef class PowerSeries_poly(PowerSeries): cdef Polynomial __f + +cdef class BaseRingFloorDivAction(Action): + pass + diff --git a/src/sage/rings/power_series_poly.pyx b/src/sage/rings/power_series_poly.pyx index 198c8e99438..affd674afda 100644 --- a/src/sage/rings/power_series_poly.pyx +++ b/src/sage/rings/power_series_poly.pyx @@ -4,7 +4,6 @@ Power Series Methods The class ``PowerSeries_poly`` provides additional methods for univariate power series. """ - from .power_series_ring_element cimport PowerSeries from sage.structure.element cimport Element, ModuleElement, RingElement from .infinity import infinity, is_Infinite @@ -540,8 +539,8 @@ cdef class PowerSeries_poly(PowerSeries): prec = self._mul_prec(right_r) return PowerSeries_poly(self._parent, self.__f * (right_r).__f, - prec = prec, - check = True) # check, since truncation may be needed + prec=prec, + check=True) # check, since truncation may be needed cpdef _rmul_(self, Element c): """ @@ -1120,7 +1119,7 @@ cdef class PowerSeries_poly(PowerSeries): .. SEEALSO:: * :mod:`sage.matrix.berlekamp_massey`, - * :meth:`sage.rings.polynomial.polynomial_zmod_flint.Polynomial_zmod_flint.rational_reconstruct` + * :meth:`sage.rings.polynomial.polynomial_zmod_flint.Polynomial_zmod_flint.rational_reconstruction` EXAMPLES:: @@ -1173,7 +1172,7 @@ cdef class PowerSeries_poly(PowerSeries): polyring = self.parent()._poly_ring() z = polyring.gen() c = self.polynomial() - u, v = c.rational_reconstruct(z**(n + m + 1), m, n) + u, v = c.rational_reconstruction(z**(n + m + 1), m, n) return u / v def _symbolic_(self, ring): @@ -1230,3 +1229,68 @@ def make_powerseries_poly_v0(parent, f, prec, is_gen): t """ return PowerSeries_poly(parent, f, prec, 0, is_gen) + +cdef class BaseRingFloorDivAction(Action): + """ + The floor division action of the base ring on a formal power series. + """ + cpdef _act_(self, g, x): + r""" + Let ``g`` act on ``x`` under ``self``. + + Regardless of whether this is a left or right action, the acting + element comes first. + + INPUT: + + - ``g`` -- an object with parent ``self.G`` + - ``x`` -- an object with parent ``self.US()`` + + .. WARNING:: + + This is meant to be a fast internal function, so the + conditions on the input are not checked! + + EXAMPLES: + + One gets the correct parent with floor division:: + + sage: A = ZZ[['t']] + sage: f = A([3*2**n for n in range(6)]).O(6) + sage: g = f // 3; g + 1 + 2*t + 4*t^2 + 8*t^3 + 16*t^4 + 32*t^5 + O(t^6) + sage: g.parent() + Power Series Ring in t over Integer Ring + + whereas the parent is larger with division:: + + sage: parent(f/3) + Power Series Ring in t over Rational Field + + Floor division in case that the power series is not divisible by the divisor:: + + sage: f = A([2**n for n in range(6)]).O(6) + sage: g = f // 3; g + t^2 + 2*t^3 + 5*t^4 + 10*t^5 + O(t^6) + + Another example:: + + sage: s = polygen(QQ,'s') + sage: A = s.parent()[['t']] + sage: f = A([(s+2)*(s+n) for n in range(5)]).O(5) + sage: g = f // (s + 2); g + s + (s + 1)*t + (s + 2)*t^2 + (s + 3)*t^3 + (s + 4)*t^4 + O(t^5) + sage: g.parent() + Power Series Ring in t over Univariate Polynomial Ring in s + over Rational Field + + sage: R. = PowerSeriesRing(QQ) + sage: t // 2 + 1/2*t + """ + cdef PowerSeries_poly elt = x + prec = x.prec() + P = self.US() + g = P.base_ring()(g) + return type(x)(P, elt.__f // g, prec=prec, check=False) + diff --git a/src/sage/rings/power_series_ring.py b/src/sage/rings/power_series_ring.py index 0000a2daa38..711c5fe3c6a 100644 --- a/src/sage/rings/power_series_ring.py +++ b/src/sage/rings/power_series_ring.py @@ -1288,6 +1288,31 @@ def fraction_field(self): laurent = self.laurent_series_ring() return laurent.change_ring(self.base_ring().fraction_field()) + def _get_action_(self, other, op, self_is_left): + r""" + Return the actions on ``self`` by ``other`` under ``op``. + + EXAMPLES:: + + sage: R. = PowerSeriesRing(ZZ) + sage: import operator + sage: act = coercion_model.get_action(R, ZZ, operator.floordiv); act + Right action by Integer Ring on Power Series Ring in t over Integer Ring + sage: type(act) + + sage: coercion_model.get_action(ZZ, R, operator.floordiv) is None + True + + sage: R. = PowerSeriesRing(QQ) + sage: coercion_model.get_action(R, ZZ, operator.floordiv) + Right action by Integer Ring on Power Series Ring in t over Rational Field + """ + import operator + if op is operator.floordiv and self_is_left and self.base_ring().has_coerce_map_from(other): + from sage.rings.power_series_poly import BaseRingFloorDivAction + # Floor division by coefficient. + return BaseRingFloorDivAction(other, self, is_left=False) + return super()._get_action_(other, op, self_is_left) class PowerSeriesRing_over_field(PowerSeriesRing_domain): _default_category = CompleteDiscreteValuationRings() diff --git a/src/sage/rings/qqbar.py b/src/sage/rings/qqbar.py index 704b77ce5fd..7b7c957fdf4 100644 --- a/src/sage/rings/qqbar.py +++ b/src/sage/rings/qqbar.py @@ -4710,6 +4710,34 @@ def radical_expression(self): roots = candidates interval_field = interval_field.to_prec(interval_field.prec() * 2) + def _maxima_init_(self, I=None): + r""" + EXAMPLES:: + + sage: maxima(AA(7)) + 7 + sage: maxima(QQbar(sqrt(5/2))) + sqrt(10)/2 + sage: maxima(AA(-sqrt(5))) + -sqrt(5) + sage: maxima(QQbar(sqrt(-2))) + sqrt(2)*%i + sage: maxima(AA(2+sqrt(5))) + sqrt(5)+2 + sage: maxima(QQ[x](x^7 - x - 1).roots(AA, False)[0]) + Traceback (most recent call last): + ... + NotImplementedError: cannot find radical expression + """ + try: + return self._rational_()._maxima_init_() + except ValueError: + pass + rad = self.radical_expression() + if isinstance(rad.parent(), sage.rings.abc.SymbolicRing): + return rad._maxima_init_() + raise NotImplementedError('cannot find radical expression') + class AlgebraicNumber(AlgebraicNumber_base): r""" diff --git a/src/sage/rings/rational_field.py b/src/sage/rings/rational_field.py index ca84b20e165..55e7a46319d 100644 --- a/src/sage/rings/rational_field.py +++ b/src/sage/rings/rational_field.py @@ -1575,7 +1575,7 @@ def _polymake_init_(self): EXAMPLES:: - sage: polymake(QQ) #optional - polymake # indirect doctest + sage: polymake(QQ) #optional - jupymake # indirect doctest Rational """ diff --git a/src/sage/rings/real_double.pyx b/src/sage/rings/real_double.pyx index c641eb3f710..fa121ddf547 100644 --- a/src/sage/rings/real_double.pyx +++ b/src/sage/rings/real_double.pyx @@ -421,7 +421,7 @@ cdef class RealDoubleField_class(sage.rings.abc.RealDoubleField): EXAMPLES:: - sage: polymake(RDF) #optional - polymake # indirect doctest + sage: polymake(RDF) #optional - jupymake # indirect doctest Float """ return '"Float"' diff --git a/src/sage/rings/ring.pyx b/src/sage/rings/ring.pyx index 1851af15fdb..75b0854d630 100644 --- a/src/sage/rings/ring.pyx +++ b/src/sage/rings/ring.pyx @@ -312,7 +312,7 @@ cdef class Ring(ParentWithGens): sage: F. = FreeAlgebra(ZZ, 3) sage: I = F*[x*y+y*z,x^2+x*y-y*x-y^2]*F - sage: Q = sage.rings.ring.Ring.quotient(F,I) + sage: Q = F.quotient(I) sage: Q.ideal_monoid() Monoid of ideals of Quotient of Free Algebra on 3 generators (x, y, z) over Integer Ring by the ideal (x*y + y*z, x^2 + x*y - y*x - y^2) sage: F. = FreeAlgebra(ZZ, implementation='letterplace') @@ -615,111 +615,6 @@ cdef class Ring(ParentWithGens): return I return self._zero_ideal - def quotient(self, I, names=None, **kwds): - """ - Create the quotient of this ring by a twosided ideal ``I``. - - INPUT: - - - ``I`` -- a twosided ideal of this ring, `R`. - - - ``names`` -- (optional) names of the generators of the quotient (if - there are multiple generators, you can specify a single character - string and the generators are named in sequence starting with 0). - - - further named arguments that may be passed to the quotient ring - constructor. - - EXAMPLES:: - - sage: R. = PolynomialRing(ZZ) - sage: I = R.ideal([4 + 3*x + x^2, 1 + x^2]) - sage: S = R.quotient(I, 'a') - sage: S.gens() - (a,) - - sage: R. = PolynomialRing(QQ,2) - sage: S. = R.quotient((x^2, y)) - sage: S - Quotient of Multivariate Polynomial Ring in x, y over Rational Field by the ideal (x^2, y) - sage: S.gens() - (a, 0) - sage: a == b - False - """ - import sage.rings.quotient_ring - return sage.rings.quotient_ring.QuotientRing(self, I, names=names, **kwds) - - def quo(self, I, names=None, **kwds): - """ - Create the quotient of `R` by the ideal `I`. This is a synonym for :meth:`.quotient` - - EXAMPLES:: - - sage: R. = PolynomialRing(QQ,2) - sage: S. = R.quo((x^2, y)) - sage: S - Quotient of Multivariate Polynomial Ring in x, y over Rational Field by the ideal (x^2, y) - sage: S.gens() - (a, 0) - sage: a == b - False - """ - return self.quotient(I, names=names, **kwds) - - def __truediv__(self, I): - """ - Dividing one ring by another is not supported because there is no good - way to specify generator names. - - EXAMPLES:: - - sage: QQ['x'] / ZZ - Traceback (most recent call last): - ... - TypeError: Use self.quo(I) or self.quotient(I) to construct the quotient ring. - """ - raise TypeError("Use self.quo(I) or self.quotient(I) to construct the quotient ring.") - - def quotient_ring(self, I, names=None, **kwds): - """ - Return the quotient of self by the ideal `I` of ``self``. - (Synonym for ``self.quotient(I)``.) - - INPUT: - - - ``I`` -- an ideal of `R` - - - ``names`` -- (optional) names of the generators of the quotient. (If - there are multiple generators, you can specify a single character - string and the generators are named in sequence starting with 0.) - - - further named arguments that may be passed to the quotient ring - constructor. - - OUTPUT: - - - ``R/I`` -- the quotient ring of `R` by the ideal `I` - - EXAMPLES:: - - sage: R. = PolynomialRing(ZZ) - sage: I = R.ideal([4 + 3*x + x^2, 1 + x^2]) - sage: S = R.quotient_ring(I, 'a') - sage: S.gens() - (a,) - - sage: R. = PolynomialRing(QQ,2) - sage: S. = R.quotient_ring((x^2, y)) - sage: S - Quotient of Multivariate Polynomial Ring in x, y over Rational Field by the ideal (x^2, y) - sage: S.gens() - (a, 0) - sage: a == b - False - """ - return self.quotient(I, names, **kwds) - def zero(self): """ Return the zero element of this ring (cached). diff --git a/src/sage/rings/tate_algebra.py b/src/sage/rings/tate_algebra.py index fd0147eaf88..9097b2a4e07 100644 --- a/src/sage/rings/tate_algebra.py +++ b/src/sage/rings/tate_algebra.py @@ -1275,8 +1275,8 @@ def random_element(self, degree=2, terms=5, integral=False, prec=None): gens = [ self.element_class(self, g) for g in self._integer_ring._gens ] return self.element_class(self, polring.random_element(degree, terms)(*gens), prec) - def is_integral_domain(self): - """ + def is_integral_domain(self, proof=True): + r""" Return ``True`` since any Tate algebra is an integral domain. EXAMPLES:: @@ -1285,5 +1285,14 @@ def is_integral_domain(self): sage: A.is_integral_domain() True + TESTS: + + Check that :trac:`34372` is fixed:: + + sage: A. = TateAlgebra(Zp(3)) + sage: R. = PolynomialRing(A) + sage: R.is_integral_domain(proof=True) + True """ return True + diff --git a/src/sage/schemes/affine/affine_space.py b/src/sage/schemes/affine/affine_space.py index b02ae6538fc..a4c48b334c3 100644 --- a/src/sage/schemes/affine/affine_space.py +++ b/src/sage/schemes/affine/affine_space.py @@ -11,7 +11,9 @@ # **************************************************************************** from sage.functions.orthogonal_polys import chebyshev_T, chebyshev_U -from sage.rings.all import (PolynomialRing, ZZ, Integer) +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.rational_field import is_RationalField from sage.rings.polynomial.polynomial_ring import is_PolynomialRing from sage.rings.polynomial.multi_polynomial_ring import is_MPolynomialRing diff --git a/src/sage/schemes/berkovich/berkovich_space.py b/src/sage/schemes/berkovich/berkovich_space.py index c19b8bca9f8..73369fda8d4 100644 --- a/src/sage/schemes/berkovich/berkovich_space.py +++ b/src/sage/schemes/berkovich/berkovich_space.py @@ -647,11 +647,11 @@ def __init__(self, base, ideal=None): ideal = None self._base_type = 'padic field' if base.dimension_relative() != 1: - raise ValueError("base of projective Berkovich space must be " + \ + raise ValueError("base of projective Berkovich space must be " "projective space of dimension 1 over Qp or a number field") self._p = prime self._ideal = ideal - Parent.__init__(self, base = base, category=TopologicalSpaces()) + Parent.__init__(self, base=base, category=TopologicalSpaces()) def base_ring(self): r""" diff --git a/src/sage/schemes/curves/affine_curve.py b/src/sage/schemes/curves/affine_curve.py index f09cde624eb..a329e927d51 100644 --- a/src/sage/schemes/curves/affine_curve.py +++ b/src/sage/schemes/curves/affine_curve.py @@ -137,7 +137,7 @@ from sage.matrix.constructor import matrix -from sage.rings.all import degree_lowest_rational_function +from sage.rings.polynomial.multi_polynomial_element import degree_lowest_rational_function from sage.rings.number_field.number_field import NumberField from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.qqbar import number_field_elements_from_algebraics, QQbar @@ -395,7 +395,7 @@ def local_coordinates(self, pt, n): y0 = F(pt[1]) astr = ["a"+str(i) for i in range(1,2*n)] x,y = R.gens() - R0 = PolynomialRing(F,2*n+2,names = [str(x),str(y),"t"]+astr) + R0 = PolynomialRing(F, 2 * n + 2, names=[str(x), str(y), "t"] + astr) vars0 = R0.gens() t = vars0[2] yt = y0*t**0+add([vars0[i]*t**(i-2) for i in range(3,2*n+2)]) @@ -1675,7 +1675,7 @@ def tangent_line(self, p): if Tp.dimension() > 1: raise ValueError("the curve is not smooth at {}".format(p)) - from sage.schemes.curves.all import Curve + from sage.schemes.curves.constructor import Curve # translate to p I = [] @@ -1785,7 +1785,9 @@ def riemann_surface(self, **kwargs): Riemann surface defined by polynomial f = x^3 + 3*y^3 + 5 = 0, with 53 bits of precision """ from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface - return RiemannSurface(self.defining_polynomial(),**kwargs) + S = RiemannSurface(self.defining_polynomial(),**kwargs) + S._curve = self + return S class AffinePlaneCurve_finite_field(AffinePlaneCurve_field): @@ -1930,15 +1932,13 @@ def rational_points(self, algorithm="enum"): return sorted(set(pnts)) elif algorithm == "all": - - S_enum = self.rational_points(algorithm = "enum") - S_bn = self.rational_points(algorithm = "bn") + S_enum = self.rational_points(algorithm="enum") + S_bn = self.rational_points(algorithm="bn") if S_enum != S_bn: raise RuntimeError("Bug in rational_points -- different algorithms give different answers for curve %s!" % self) return S_enum - else: - raise ValueError("No algorithm '%s' known"%algorithm) + raise ValueError("No algorithm '%s' known" % algorithm) class IntegralAffineCurve(AffineCurve_field): @@ -2095,7 +2095,7 @@ def _nonsingular_model(self): y |--> z^2 z |--> z) """ - from sage.rings.function_field.all import FunctionField + from sage.rings.function_field.constructor import FunctionField k = self.base_ring() I = self.defining_ideal() diff --git a/src/sage/schemes/curves/projective_curve.py b/src/sage/schemes/curves/projective_curve.py index 529721d8958..f9ff5944a90 100644 --- a/src/sage/schemes/curves/projective_curve.py +++ b/src/sage/schemes/curves/projective_curve.py @@ -139,17 +139,16 @@ from sage.misc.lazy_attribute import lazy_attribute from sage.misc.cachefunc import cached_method -from sage.categories.all import hom from sage.categories.fields import Fields +from sage.categories.homset import hom, Hom, End from sage.categories.number_fields import NumberFields -from sage.categories.homset import Hom, End from sage.interfaces.singular import singular from sage.matrix.constructor import matrix from builtins import sum as add from sage.misc.sage_eval import sage_eval -from sage.rings.all import degree_lowest_rational_function +from sage.rings.polynomial.multi_polynomial_element import degree_lowest_rational_function from sage.rings.integer_ring import IntegerRing from sage.rings.number_field.number_field import NumberField from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing @@ -704,7 +703,7 @@ def local_coordinates(self, pt, n): y0 = F(pt[1]) astr = ["a"+str(i) for i in range(1,2*n)] x,y = R.gens() - R0 = PolynomialRing(F,2*n+2,names = [str(x),str(y),"t"]+astr) + R0 = PolynomialRing(F, 2 * n + 2, names=[str(x), str(y), "t"] + astr) vars0 = R0.gens() t = vars0[2] yt = y0*t**0 + add([vars0[i]*t**(i-2) for i in range(3,2*n+2)]) @@ -1877,7 +1876,7 @@ def rational_points_iterator(self): """ g = self.defining_polynomial() K = g.parent().base_ring() - from sage.rings.polynomial.all import PolynomialRing + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing R = PolynomialRing(K,'X') X = R.gen() one = K.one() @@ -2148,8 +2147,8 @@ def rational_points(self, algorithm="enum", sort=True): if algorithm == "bn": return self._points_via_singular(sort=sort) elif algorithm == "all": - S_enum = self.rational_points(algorithm = "enum") - S_bn = self.rational_points(algorithm = "bn") + S_enum = self.rational_points(algorithm="enum") + S_bn = self.rational_points(algorithm="bn") if S_enum != S_bn: raise RuntimeError("Bug in rational_points -- different\ algorithms give different answers for\ diff --git a/src/sage/schemes/curves/zariski_vankampen.py b/src/sage/schemes/curves/zariski_vankampen.py index 7a5b298ce07..01e50f43ea0 100644 --- a/src/sage/schemes/curves/zariski_vankampen.py +++ b/src/sage/schemes/curves/zariski_vankampen.py @@ -13,7 +13,7 @@ choose several base points and a system of paths joining them that generate all the necessary loops around the points of the discriminant. The group is generated by the free groups over these points, and -braids over this paths gives relations between these generators. +braids over these paths give relations between these generators. This big group presentation is simplified at the end. AUTHORS: @@ -37,27 +37,27 @@ # (at your option) any later version. # https://www.gnu.org/licenses/ # **************************************************************************** +import itertools +from copy import copy +from sage.combinat.permutation import Permutation +from sage.geometry.voronoi_diagram import VoronoiDiagram +from sage.graphs.graph import Graph from sage.groups.braid import BraidGroup +from sage.groups.free_group import FreeGroup from sage.groups.perm_gps.permgroup_named import SymmetricGroup -from sage.rings.rational_field import QQ -from sage.rings.qqbar import QQbar -from sage.parallel.decorate import parallel +from sage.misc.cachefunc import cached_function from sage.misc.flatten import flatten -from sage.groups.free_group import FreeGroup from sage.misc.misc_c import prod +from sage.parallel.decorate import parallel +from sage.rings.complex_interval_field import ComplexIntervalField from sage.rings.complex_mpfr import ComplexField +from sage.rings.qqbar import QQbar +from sage.rings.rational_field import QQ from sage.rings.real_mpfr import RealField -from sage.rings.complex_interval_field import ComplexIntervalField -from sage.combinat.permutation import Permutation -import itertools -from sage.geometry.voronoi_diagram import VoronoiDiagram -from sage.graphs.graph import Graph -from sage.misc.cachefunc import cached_function -from copy import copy -roots_interval_cache = dict() +roots_interval_cache = {} def braid_from_piecewise(strands): @@ -67,7 +67,8 @@ def braid_from_piecewise(strands): INPUT: - ``strands`` -- a list of lists of tuples ``(t, c1, c2)``, where ``t`` - is a number between 0 and 1, and ``c1`` and ``c2`` are rationals or algebraic reals. + is a number between 0 and 1, and ``c1`` and ``c2`` are rationals + or algebraic reals. OUTPUT: @@ -123,11 +124,13 @@ def sgn(x, y): l1 = [a[0] for a in M] l2 = [a[1] for a in M] cruces = [] - for j in range(len(l2)): + for j, l2j in enumerate(l2): + l1j = l1[j] for k in range(j): - if l2[j] < l2[k]: - t = (l1[j][0] - l1[k][0])/((l2[k][0]-l2[j][0]) + (l1[j][0] - l1[k][0])) - s = sgn(l1[k][1]*(1 - t) + t*l2[k][1], l1[j][1]*(1 - t) + t*l2[j][1]) + if l2j < l2[k]: + t = (l1j[0] - l1[k][0]) / ((l2[k][0] - l2j[0]) + (l1j[0] - l1[k][0])) + s = sgn(l1[k][1] * (1 - t) + t * l2[k][1], + l1j[1] * (1 - t) + t * l2j[1]) cruces.append([t, k, j, s]) if cruces: cruces.sort() @@ -141,10 +144,10 @@ def sgn(x, y): while crossesl: crossesl.sort() c = crossesl.pop(0) - braid.append(c[3]*min(map(P, [c[1] + 1, c[2] + 1]))) + braid.append(c[3] * min(map(P, [c[1] + 1, c[2] + 1]))) P = G(Permutation([(c[1] + 1, c[2] + 1)])) * P - crossesl = [(P(cr[2]+1) - P(cr[1]+1), cr[1], cr[2], cr[3]) - for cr in crossesl] + crossesl = [(P(cr[2] + 1) - P(cr[1] + 1), + cr[1], cr[2], cr[3]) for cr in crossesl] B = BraidGroup(len(L)) return B(braid) @@ -185,8 +188,9 @@ def discrim(f): @cached_function def corrected_voronoi_diagram(points): r""" - Compute a Voronoi diagram of a set of points with rational coordinates, such - that the given points are granted to lie one in each bounded region. + Compute a Voronoi diagram of a set of points with rational coordinates. + + The given points are granted to lie one in each bounded region. INPUT: @@ -215,18 +219,18 @@ def corrected_voronoi_diagram(points): P(1/1000000, 0): A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 5 vertices, P(2, 0): A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 5 vertices, P(7, 0): A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 2 vertices and 2 rays} - """ prec = 53 point_coordinates = [(p.real(), p.imag()) for p in points] while True: RF = RealField(prec) - apprpoints = {(QQ(RF(p[0])), QQ(RF(p[1]))): p for p in point_coordinates} + apprpoints = {(QQ(RF(p[0])), QQ(RF(p[1]))): p + for p in point_coordinates} added_points = 3 * max(map(abs, flatten(apprpoints))) + 1 - configuration = list(apprpoints.keys())+[(added_points, 0), - (-added_points, 0), - (0, added_points), - (0, -added_points)] + configuration = list(apprpoints.keys()) + [(added_points, 0), + (-added_points, 0), + (0, added_points), + (0, -added_points)] V = VoronoiDiagram(configuration) valid = True for r in V.regions().items(): @@ -277,19 +281,18 @@ def segments(points): (-5/2*I + 5/2, 5/2*I + 5/2), (5/2*I + 5/2, 144713866144468523/66040650000519163*I + 167101179147960739/132081300001038326)] - """ V = corrected_voronoi_diagram(tuple(points)) - res = set([]) + res = set() for region in V.regions().values(): if region.rays(): continue - segments = region.facets() - for s in segments: + for s in region.facets(): t = tuple((tuple(v.vector()) for v in s.vertices())) if t not in res and not tuple(reversed(t)) in res: res.add(t) - return [(r[0]+QQbar.gen()*r[1], s[0]+QQbar.gen()*s[1]) for (r, s) in res] + return [(r[0] + QQbar.gen() * r[1], + s[0] + QQbar.gen() * s[1]) for r, s in res] def followstrand(f, factors, x0, x1, y0a, prec=53): @@ -336,7 +339,6 @@ def followstrand(f, factors, x0, x1, y0a, prec=53): (0.5303300858899107, -1.0076747107983448, -0.17588022709184917), (0.7651655429449553, -1.015686131039112, -0.25243563967299404), (1.0, -1.026166099551513, -0.3276894025360433)] - """ if f.degree() == 1: CF = ComplexField(prec) @@ -349,7 +351,7 @@ def followstrand(f, factors, x0, x1, y0a, prec=53): CIF = ComplexIntervalField(prec) CC = ComplexField(prec) G = f.change_ring(QQbar).change_ring(CIF) - (x, y) = G.parent().gens() + x, y = G.parent().gens() g = G.subs({x: (1 - x) * CIF(x0) + x * CIF(x1)}) coefs = [] deg = g.total_degree() @@ -407,7 +409,6 @@ def newton(f, x0, i0): The interval `x_0-\frac{f(x_0)}{f'(I_0)}` - EXAMPLES:: sage: from sage.schemes.curves.zariski_vankampen import newton @@ -422,9 +423,8 @@ def newton(f, x0, i0): (-0.0147727272727274, 0.00982142857142862) sage: n.imag().endpoints() (0.000000000000000, -0.000000000000000) - """ - return x0 - f(x0)/f.derivative()(i0) + return x0 - f(x0) / f.derivative()(i0) @parallel @@ -441,8 +441,9 @@ def roots_interval(f, x0): The intervals are taken as big as possible to be able to detect when two approximate roots of `f(x_0, y)` correspond to the same exact root. - The result is given as a dictionary, where the keys are approximations to the roots - with rational real and imaginary parts, and the values are intervals containing them. + The result is given as a dictionary, where the keys are + approximations to the roots with rational real and imaginary + parts, and the values are intervals containing them. EXAMPLES:: @@ -467,32 +468,32 @@ def roots_interval(f, x0): -0.0669872981077806 + 1.29903810567666*I, -0.933012701892219 + 1.29903810567666*I, -0.0669872981077806 + 0.433012701892219*I)] - """ x, y = f.parent().gens() I = QQbar.gen() - fx = QQbar[y](f.subs({x: QQ(x0.real())+I*QQ(x0.imag())})) + fx = QQbar[y](f.subs({x: QQ(x0.real()) + I * QQ(x0.imag())})) roots = fx.roots(QQbar, multiplicities=False) result = {} - for i in range(len(roots)): - r = roots[i] + for i, r in enumerate(roots): prec = 53 IF = ComplexIntervalField(prec) CF = ComplexField(prec) divisor = 4 - diam = min((CF(r)-CF(r0)).abs() for r0 in roots[:i]+roots[i+1:]) / divisor - envelop = IF(diam)*IF((-1, 1), (-1, 1)) - while not newton(fx, r, r+envelop) in r+envelop: + diam = min((CF(r) - CF(r0)).abs() + for r0 in roots[:i] + roots[i + 1:]) / divisor + envelop = IF(diam) * IF((-1, 1), (-1, 1)) + while not newton(fx, r, r + envelop) in r + envelop: prec += 53 IF = ComplexIntervalField(prec) CF = ComplexField(prec) divisor *= 2 - diam = min([(CF(r)-CF(r0)).abs() for r0 in roots[:i]+roots[i+1:]])/divisor - envelop = IF(diam)*IF((-1, 1), (-1, 1)) - qapr = QQ(CF(r).real())+QQbar.gen()*QQ(CF(r).imag()) - if qapr not in r+envelop: + diam = min((CF(r) - CF(r0)).abs() + for r0 in roots[:i] + roots[i + 1:]) / divisor + envelop = IF(diam) * IF((-1, 1), (-1, 1)) + qapr = QQ(CF(r).real()) + QQbar.gen() * QQ(CF(r).imag()) + if qapr not in r + envelop: raise ValueError("Could not approximate roots with exact values") - result[qapr] = r+envelop + result[qapr] = r + envelop return result @@ -500,7 +501,6 @@ def roots_interval_cached(f, x0): r""" Cached version of :func:`roots_interval`. - TESTS:: sage: from sage.schemes.curves.zariski_vankampen import roots_interval, roots_interval_cached, roots_interval_cache @@ -515,7 +515,6 @@ def roots_interval_cached(f, x0): 1: 1.? + 0.?*I} sage: (f, 1) in roots_interval_cache True - """ global roots_interval_cache try: @@ -602,16 +601,15 @@ def braid_in_segment(g, x0, x1): sage: B = zvk.braid_in_segment(g.factor(),CC(p1),CC(p2)) # optional - sirocco sage: B # optional - sirocco s5*s3^-1 - """ - (x, y) = g.value().parent().gens() + _, y = g.value().parent().gens() I = QQbar.gen() X0 = QQ(x0.real()) + I * QQ(x0.imag()) X1 = QQ(x1.real()) + I * QQ(x1.imag()) intervals = {} precision = {} y0s = [] - for (f, naux) in g: + for f, _ in g: if f.variables() == (y,): F0 = QQbar[y](f.base_ring()[y](f)) else: @@ -633,8 +631,8 @@ def braid_in_segment(g, x0, x1): initialintervals = roots_interval_cached(g.value(), X0) finalintervals = roots_interval_cached(g.value(), X1) for cs in complexstrands: - ip = cs[0][1] + I*cs[0][2] - fp = cs[-1][1] + I*cs[-1][2] + ip = cs[0][1] + I * cs[0][2] + fp = cs[-1][1] + I * cs[-1][2] matched = 0 for center, interval in initialintervals.items(): if ip in interval: @@ -652,7 +650,7 @@ def braid_in_segment(g, x0, x1): if matched == 0: raise ValueError("unable to match braid endpoint with root") if matched > 1: - raise ValueError("braid endpoint mathes more than one root") + raise ValueError("braid endpoint matches more than one root") initialbraid = braid_from_piecewise(initialstrands) finalbraid = braid_from_piecewise(finalstrands) @@ -661,7 +659,7 @@ def braid_in_segment(g, x0, x1): def orient_circuit(circuit): r""" - Reverses a circuit if it goes clockwise; otherwise leaves it unchanged. + Reverse a circuit if it goes clockwise; otherwise leave it unchanged. INPUT: @@ -711,16 +709,16 @@ def orient_circuit(circuit): """ prec = 53 - vectors = [v[1].vector()-v[0].vector() for v in circuit] + vectors = [v[1].vector() - v[0].vector() for v in circuit] while True: CIF = ComplexIntervalField(prec) - totalangle = sum((CIF(*vectors[i])/CIF(*vectors[i-1])).argument() for i in range(len(vectors))) + totalangle = sum((CIF(*vectors[i]) / CIF(*vectors[i - 1])).argument() + for i in range(len(vectors))) if totalangle < 0: return list(reversed([(c[1], c[0]) + c[2:] for c in circuit])) - elif totalangle > 0: + if totalangle > 0: return circuit - else: - prec *= 2 + prec *= 2 def geometric_basis(G, E, p): @@ -731,15 +729,15 @@ def geometric_basis(G, E, p): - ``G`` -- the graph of the bounded regions of a Voronoi Diagram - - ``E`` -- the subgraph of ``G`` formed by the edges that touch an unbounded - region + - ``E`` -- the subgraph of ``G`` formed by the edges that touch + an unbounded region - ``p`` -- a vertex of ``E`` OUTPUT: A geometric basis. It is formed by a list of sequences of paths. Each path is a list of vertices, that form a closed path in `G`, based at - `p`, that goes to a region, surrounds it, and comes back by the same path it - came. The concatenation of all these paths is equivalent to `E`. + `p`, that goes to a region, surrounds it, and comes back by the same + path it came. The concatenation of all these paths is equivalent to `E`. EXAMPLES:: @@ -790,11 +788,10 @@ def geometric_basis(G, E, p): A vertex at (-1/2, 1/2), A vertex at (-2, 2), A vertex at (-2, -2)]] - """ EC = [v[0] for v in orient_circuit(E.eulerian_circuit())] i = EC.index(p) - EC = EC[i:]+EC[:i+1] # A counterclockwise eulerian circuit on the boundary, based at p + EC = EC[i:] + EC[:i + 1] # A counterclockwise eulerian circuit on the boundary, based at p if G.size() == E.size(): if E.is_cycle(): return [EC] @@ -808,17 +805,18 @@ def geometric_basis(G, E, p): if len(E.neighbors(v)) > 2: I.add_vertex(v) - for i in range(len(EC)): # q and r are the points we will cut through + for i, ECi in enumerate(EC): # q and r are the points we will cut through if EC[i] in I: q = EC[i] connecting_path = EC[:i] break - elif EC[-i] in I: + if EC[-i] in I: q = EC[-i] connecting_path = list(reversed(EC[-i:])) break - distancequotients = [(E.distance(q, v)**2/I.distance(q, v), v) for v in E if v in I.connected_component_containing_vertex(q) and not v == q] + distancequotients = [(E.distance(q, v)**2 / I.distance(q, v), v) for v in E + if v in I.connected_component_containing_vertex(q) and not v == q] r = max(distancequotients)[1] cutpath = I.shortest_path(q, r) Gcut = copy(G) @@ -839,16 +837,16 @@ def geometric_basis(G, E, p): if n in G2 or n in cutpath: G2.add_edge(v, n, None) - if EC[EC.index(q)+1] in G2: + if EC[EC.index(q) + 1] in G2: G1, G2 = G2, G1 E1, E2 = Ecut.connected_components_subgraphs() - if EC[EC.index(q)+1] in E2: + if EC[EC.index(q) + 1] in E2: E1, E2 = E2, E1 - for i in range(len(cutpath)-1): - E1.add_edge(cutpath[i], cutpath[i+1], None) - E2.add_edge(cutpath[i], cutpath[i+1], None) + for i in range(len(cutpath) - 1): + E1.add_edge(cutpath[i], cutpath[i + 1], None) + E2.add_edge(cutpath[i], cutpath[i + 1], None) for v in [q, r]: for n in E.neighbors(v): @@ -860,14 +858,16 @@ def geometric_basis(G, E, p): gb1 = geometric_basis(G1, E1, q) gb2 = geometric_basis(G2, E2, q) - resul = [connecting_path + path + list(reversed(connecting_path)) for path in gb1 + gb2] + reverse_connecting = list(reversed(connecting_path)) + resul = [connecting_path + path + reverse_connecting + for path in gb1 + gb2] for r in resul: i = 0 - while i < len(r)-2: - if r[i] == r[i+2]: + while i < len(r) - 2: + if r[i] == r[i + 2]: r.pop(i) r.pop(i) - if i > 0: + if i: i -= 1 else: i += 1 @@ -876,12 +876,12 @@ def geometric_basis(G, E, p): def braid_monodromy(f): r""" - Compute the braid monodromy of a projection of the curve defined by a polynomial + Compute the braid monodromy of a projection of the curve defined by a polynomial. INPUT: - - ``f`` -- a polynomial with two variables, over a number field with an embedding - in the complex numbers. + - ``f`` -- a polynomial with two variables, over a number field + with an embedding in the complex numbers OUTPUT: @@ -904,10 +904,9 @@ def braid_monodromy(f): s1*s0*(s1*s2)^2*(s0*s2^-1*s1*s2*s1*s2^-1)^2*(s2^-1*s1^-1)^2*s0^-1*s1^-1, s1*s0*(s1*s2)^2*s2*s1^-1*s2^-1*s1^-1*s0^-1*s1^-1, s1*s0*s2*s0^-1*s2*s1^-1] - """ global roots_interval_cache - (x, y) = f.parent().gens() + x, y = f.parent().gens() F = f.base_ring() g = f.radical() d = g.degree(y) @@ -925,22 +924,24 @@ def braid_monodromy(f): E = E.union(reg.vertex_graph()) p = next(E.vertex_iterator()) geombasis = geometric_basis(G, E, p) - segs = set([]) + segs = set() for p in geombasis: for s in zip(p[:-1], p[1:]): if (s[1], s[0]) not in segs: segs.add((s[0], s[1])) I = QQbar.gen() - segs = [(a[0]+I*a[1], b[0]+I*b[1]) for (a, b) in segs] + segs = [(a[0] + I * a[1], b[0] + I * b[1]) for a, b in segs] vertices = list(set(flatten(segs))) tocacheverts = [(g, v) for v in vertices] populate_roots_interval_cache(tocacheverts) gfac = g.factor() try: - braidscomputed = list(braid_in_segment([(gfac, seg[0], seg[1]) for seg in segs])) + braidscomputed = (braid_in_segment([(gfac, seg[0], seg[1]) + for seg in segs])) except ChildProcessError: # hack to deal with random fails first time - braidscomputed = list(braid_in_segment([(gfac, seg[0], seg[1]) for seg in segs])) - segsbraids = dict() + braidscomputed = (braid_in_segment([(gfac, seg[0], seg[1]) + for seg in segs])) + segsbraids = {} for braidcomputed in braidscomputed: seg = (braidcomputed[0][0][1], braidcomputed[0][0][2]) beginseg = (QQ(seg[0].real()), QQ(seg[0].imag())) @@ -952,9 +953,9 @@ def braid_monodromy(f): result = [] for path in geombasis: braidpath = B.one() - for i in range(len(path)-1): + for i in range(len(path) - 1): x0 = tuple(path[i].vector()) - x1 = tuple(path[i+1].vector()) + x1 = tuple(path[i + 1].vector()) braidpath = braidpath * segsbraids[(x0, x1)] result.append(braidpath) return result @@ -978,7 +979,8 @@ def fundamental_group(f, simplified=True, projective=False): of the curve will be computed, otherwise, the fundamental group of the complement in the affine plane will be computed - If ``simplified`` is ``False``, a Zariski-VanKampen presentation is returned. + If ``simplified`` is ``False``, a Zariski-VanKampen presentation + is returned. OUTPUT: @@ -1020,9 +1022,9 @@ def fundamental_group(f, simplified=True, projective=False): """ g = f if projective: - x,y = g.parent().gens() + x, y = g.parent().gens() while g.degree(y) < g.degree(): - g = g.subs({x : x + y}) + g = g.subs({x: x + y}) bm = braid_monodromy(g) n = bm[0].parent().strands() F = FreeGroup(n) @@ -1030,7 +1032,7 @@ def fundamental_group(f, simplified=True, projective=False): @parallel def relation(x, b): return x * b / x - relations = list(relation([(x, b) for x in F.gens() for b in bm])) + relations = (relation([(x, b) for x in F.gens() for b in bm])) R = [r[1] for r in relations] if projective: R.append(prod(F.gens())) diff --git a/src/sage/schemes/elliptic_curves/constructor.py b/src/sage/schemes/elliptic_curves/constructor.py index 46de5dd8957..1ecdc082cd5 100644 --- a/src/sage/schemes/elliptic_curves/constructor.py +++ b/src/sage/schemes/elliptic_curves/constructor.py @@ -615,7 +615,7 @@ def EllipticCurve_from_j(j, minimal_twist=True): minimal conductor; when there is more than one curve with minimal conductor, the curve returned is the one whose label comes first if the curves are in the CremonaDatabase, otherwise - the one whose minimal a-invarinats are first lexicographically. + the one whose minimal a-invariants are first lexicographically. If `j` is not in `\QQ` this parameter is ignored. OUTPUT: @@ -1065,7 +1065,7 @@ def EllipticCurve_from_cubic(F, P=None, morphism=True): (-1/3*z : 3*x : -1/1008*x + 1/1008*y + 1/378*z) """ from sage.schemes.curves.constructor import Curve - from sage.matrix.all import Matrix + from sage.matrix.constructor import Matrix from sage.schemes.elliptic_curves.weierstrass_transform import \ WeierstrassTransformationWithInverse diff --git a/src/sage/schemes/elliptic_curves/descent_two_isogeny.pyx b/src/sage/schemes/elliptic_curves/descent_two_isogeny.pyx index af82dd3d91c..0c3a7a72385 100644 --- a/src/sage/schemes/elliptic_curves/descent_two_isogeny.pyx +++ b/src/sage/schemes/elliptic_curves/descent_two_isogeny.pyx @@ -1045,10 +1045,10 @@ cdef int count(mpz_t c_mpz, mpz_t d_mpz, mpz_t *p_list, unsigned long p_list_len return 0 def two_descent_by_two_isogeny(E, - int global_limit_small = 10, - int global_limit_large = 10000, - int verbosity = 0, - bint selmer_only = 0, bint proof = 1): + int global_limit_small=10, + int global_limit_large=10000, + int verbosity=0, + bint selmer_only=0, bint proof=1): """ Given an elliptic curve E with a two-isogeny phi : E --> E' and dual isogeny phi', runs a two-isogeny descent on E, returning n1, n2, n1' and n2'. Here @@ -1148,9 +1148,10 @@ def two_descent_by_two_isogeny(E, return two_descent_by_two_isogeny_work(c, d, global_limit_small, global_limit_large, verbosity, selmer_only, proof) + def two_descent_by_two_isogeny_work(Integer c, Integer d, - int global_limit_small = 10, int global_limit_large = 10000, - int verbosity = 0, bint selmer_only = 0, bint proof = 1): + int global_limit_small=10, int global_limit_large=10000, + int verbosity=0, bint selmer_only=0, bint proof=1): """ Do all the work in doing a two-isogeny descent. diff --git a/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py b/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py index b46fe3c7bdc..9c3cb195eff 100644 --- a/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py +++ b/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py @@ -571,6 +571,9 @@ class EllipticCurveIsogeny(EllipticCurveHom): over a number field, then the codomain is a global minimal model where this exists. + - ``"short_weierstrass"``: The codomain is a short Weierstrass curve, + assuming one exists. + - ``"montgomery"``: The codomain is an (untwisted) Montgomery curve, assuming one exists over this field. @@ -1058,8 +1061,9 @@ def _eval(self, P): if self._domain.defining_polynomial()(*P): raise ValueError(f"{P} not on {self._domain}") + k = Sequence(P).universe() + if not P: - k = Sequence(tuple(P)).universe() return self._codomain(0).change_ring(k) Q = P.xy() @@ -1082,7 +1086,6 @@ def _eval(self, P): if self.__post_isomorphism is not None: Q = baseWI.__call__(self.__post_isomorphism, Q) - k = Sequence(tuple(P) + tuple(Q)).universe() return self._codomain.base_extend(k).point(Q) def _call_(self, P): @@ -2652,6 +2655,36 @@ def scaling_factor(self): sc *= self.__post_isomorphism.scaling_factor() return sc + def as_morphism(self): + r""" + Return this isogeny as a morphism of projective schemes. + + EXAMPLES:: + + sage: k = GF(11) + sage: E = EllipticCurve(k, [1,1]) + sage: Q = E(6,5) + sage: phi = E.isogeny(Q) + sage: mor = phi.as_morphism() + sage: mor.domain() == E + True + sage: mor.codomain() == phi.codomain() + True + sage: mor(Q) == phi(Q) + True + + TESTS:: + + sage: mor(0*Q) + (0 : 1 : 0) + sage: mor(1*Q) + (0 : 1 : 0) + """ + from sage.schemes.curves.constructor import Curve + X_affine = Curve(self.domain()).affine_patch(2) + Y_affine = Curve(self.codomain()).affine_patch(2) + return X_affine.hom(self.rational_maps(), Y_affine).homogenize(2) + def kernel_polynomial(self): r""" Return the kernel polynomial of this isogeny. @@ -3724,7 +3757,7 @@ def fill_isogeny_matrix(M): [ 6 3 2 18 1 9] [ 6 3 18 2 9 1] """ - from sage.matrix.all import Matrix + from sage.matrix.constructor import Matrix from sage.rings.infinity import Infinity n = M.nrows() diff --git a/src/sage/schemes/elliptic_curves/ell_field.py b/src/sage/schemes/elliptic_curves/ell_field.py index 5c06e94f88a..68b8375daee 100644 --- a/src/sage/schemes/elliptic_curves/ell_field.py +++ b/src/sage/schemes/elliptic_curves/ell_field.py @@ -1090,6 +1090,14 @@ def isogeny(self, kernel, codomain=None, degree=None, model=None, check=True, al Kohel's algorithm is currently only implemented for cyclic isogenies, with the exception of `[2]`. + - √élu Algorithm (see + :mod:`~sage.schemes.elliptic_curves.hom_velusqrt`): + A variant of Vélu's formulas with essentially square-root + instead of linear complexity (in the degree). Currently only + available over finite fields. The input must be a single + kernel point of odd order `\geq 5`. + This algorithm is selected using ``algorithm="velusqrt"``. + - Factored Isogenies (*experimental* --- see :mod:`~sage.schemes.elliptic_curves.hom_composite`): Given a list of points which generate a composite-order @@ -1131,20 +1139,35 @@ def isogeny(self, kernel, codomain=None, degree=None, model=None, check=True, al over a number field, then the codomain is a global minimal model where this exists. + - ``"short_weierstrass"``: The codomain is a short Weierstrass curve, + assuming one exists. + - ``"montgomery"``: The codomain is an (untwisted) Montgomery curve, assuming one exists over this field. - ``check`` (default: ``True``) -- check whether the input is valid. Setting this to ``False`` can lead to significant speedups. - - ``algorithm`` (optional) -- When ``algorithm="factored"`` is - passed, decompose the isogeny into prime-degree steps. - The ``degree`` parameter is not supported by - ``algorithm="factored"``. + - ``algorithm`` -- string (optional). By default (when ``algorithm`` + is omitted), the "traditional" implementation + :class:`~sage.schemes.elliptic_curves.ell_curve_isogeny.EllipticCurveIsogeny` + is used. The other choices are: + + - ``"velusqrt"``: Use + :class:`~sage.schemes.elliptic_curves.hom_velusqrt.EllipticCurveHom_velusqrt`. + + - ``"factored"``: Use + :class:`~sage.schemes.elliptic_curves.hom_composite.EllipticCurveHom_composite` + to decompose the isogeny into prime-degree steps. + + The ``degree`` parameter is not supported when an ``algorithm`` + is specified. OUTPUT: An isogeny between elliptic curves. This is a morphism of curves. + (In all cases, the returned object will be an instance of + :class:`~sage.schemes.elliptic_curves.hom.EllipticCurveHom`.) EXAMPLES:: @@ -1221,9 +1244,12 @@ def isogeny(self, kernel, codomain=None, degree=None, model=None, check=True, al sage: phi.codomain()._order 170141183460469231746191640949390434666 """ + if algorithm is not None and degree is not None: + raise TypeError('cannot pass "degree" and "algorithm" parameters simultaneously') + if algorithm == "velusqrt": + from sage.schemes.elliptic_curves.hom_velusqrt import EllipticCurveHom_velusqrt + return EllipticCurveHom_velusqrt(self, kernel, codomain=codomain, model=model) if algorithm == "factored": - if degree is not None: - raise TypeError('algorithm="factored" does not support the "degree" parameter') from sage.schemes.elliptic_curves.hom_composite import EllipticCurveHom_composite return EllipticCurveHom_composite(self, kernel, codomain=codomain, model=model) try: @@ -1782,7 +1808,7 @@ class of curves. If the j-invariant is not unique in the isogeny from warnings import warn from sage.graphs.graph import DiGraph, Graph - from sage.matrix.all import Matrix + from sage.matrix.constructor import Matrix # warn users if things are getting big if l == 2: @@ -1859,6 +1885,10 @@ def compute_model(E, name): For this choice, ``E`` must be defined over a number field. See :meth:`~sage.schemes.elliptic_curves.ell_number_field.EllipticCurve_number_field.global_minimal_model`. + - ``"short_weierstrass"``: Return a short Weierstrass model of ``E`` + assuming one exists. + See :meth:`~sage.schemes.elliptic_curves.ell_generic.EllipticCurve_generic.short_weierstrass_model`. + - ``"montgomery"``: Return an (untwisted) Montgomery model of ``E`` assuming one exists over this field. See :meth:`~sage.schemes.elliptic_curves.ell_generic.EllipticCurve_generic.montgomery_model`. @@ -1873,6 +1903,8 @@ def compute_model(E, name): sage: E = EllipticCurve([12/7, 405/49, 0, -81/8, 135/64]) sage: compute_model(E, 'minimal') Elliptic Curve defined by y^2 = x^3 - x^2 - 7*x + 10 over Rational Field + sage: compute_model(E, 'short_weierstrass') + Elliptic Curve defined by y^2 = x^3 - 48114*x + 4035015 over Rational Field sage: compute_model(E, 'montgomery') Elliptic Curve defined by y^2 = x^3 + 5*x^2 + x over Rational Field """ @@ -1885,6 +1917,9 @@ def compute_model(E, name): raise ValueError('can only compute minimal model for curves over number fields') return E.global_minimal_model(semi_global=True) + if name == 'short_weierstrass': + return E.short_weierstrass_model() + if name == 'montgomery': return E.montgomery_model() diff --git a/src/sage/schemes/elliptic_curves/ell_number_field.py b/src/sage/schemes/elliptic_curves/ell_number_field.py index 351aab52aad..edbd1960907 100644 --- a/src/sage/schemes/elliptic_curves/ell_number_field.py +++ b/src/sage/schemes/elliptic_curves/ell_number_field.py @@ -794,7 +794,7 @@ def _scale_by_units(self): fu = K.units() c4, c6 = self.c_invariants() - from sage.matrix.all import Matrix + from sage.matrix.constructor import Matrix from sage.modules.free_module_element import vector prec = 1000 # initial value, will be increased if necessary diff --git a/src/sage/schemes/elliptic_curves/ell_rational_field.py b/src/sage/schemes/elliptic_curves/ell_rational_field.py index 38088228122..fc54013b7ba 100644 --- a/src/sage/schemes/elliptic_curves/ell_rational_field.py +++ b/src/sage/schemes/elliptic_curves/ell_rational_field.py @@ -1095,7 +1095,7 @@ def _modular_symbol_normalize(self, sign, normalize, implementation, nap): raise ValueError("Implementation should be one of 'sage', 'num' or 'eclib'") return (sign, normalize, implementation, nap) - @cached_method(key = _modular_symbol_normalize) + @cached_method(key=_modular_symbol_normalize) def modular_symbol(self, sign=+1, normalize=None, implementation='eclib', nap=0): r""" Return the modular symbol map associated to this elliptic curve @@ -1964,7 +1964,7 @@ def three_selmer_rank(self, algorithm='UseSUnits'): """ from sage.interfaces.magma import magma E = magma(self) - return Integer(E.ThreeSelmerGroup(MethodForFinalStep = magma('"%s"'%algorithm)).Ngens()) + return Integer(E.ThreeSelmerGroup(MethodForFinalStep=magma('"%s"' % algorithm)).Ngens()) def rank(self, use_database=True, verbose=False, only_use_mwrank=True, @@ -2301,8 +2301,8 @@ def _compute_gens(self, proof, if not only_use_mwrank: try: verbose_verbose("Trying to compute rank.") - r = self.rank(only_use_mwrank = False) - verbose_verbose("Got r = %s."%r) + r = self.rank(only_use_mwrank=False) + verbose_verbose("Got r = %s." % r) if r == 0: verbose_verbose("Rank = 0, so done.") return [], True @@ -2438,7 +2438,7 @@ def ngens(self, proof=None): Generator 1 is [29604565304828237474403861024284371796799791624792913256602210:-256256267988926809388776834045513089648669153204356603464786949:490078023219787588959802933995928925096061616470779979261000]; height 95.98037... Regulator = 95.980... """ - return len(self.gens(proof = proof)) + return len(self.gens(proof=proof)) def regulator(self, proof=None, precision=53, **kwds): r""" @@ -2646,7 +2646,7 @@ def saturation(self, points, verbose=False, max_prime=-1, min_prime=2): from sage.libs.eclib.all import mwrank_MordellWeil mw = mwrank_MordellWeil(c, verbose) mw.process(v) # by default, this does no saturation yet - ok, index, unsat = mw.saturate(max_prime=max_prime, min_prime = min_prime) + ok, index, unsat = mw.saturate(max_prime=max_prime, min_prime=min_prime) if not ok: print("Failed to saturate failed at the primes {}".format(unsat)) sat = [Emin(P) for P in mw.points()] @@ -6630,7 +6630,7 @@ def S_integral_x_coords_with_abs_bounded_by(abs_bound): M = U.transpose()*M*U # NB "lambda" is a reserved word in Python! - lamda = min(M.charpoly(algorithm="hessenberg").roots(multiplicities = False)) + lamda = min(M.charpoly(algorithm="hessenberg").roots(multiplicities=False)) max_S = max(S) len_S += 1 #Counting infinity (always "included" in S) if verbose: diff --git a/src/sage/schemes/elliptic_curves/heegner.py b/src/sage/schemes/elliptic_curves/heegner.py index 4701c66f5de..2b699fd6cf8 100644 --- a/src/sage/schemes/elliptic_curves/heegner.py +++ b/src/sage/schemes/elliptic_curves/heegner.py @@ -3520,7 +3520,7 @@ def point_exact(self, prec=53, algorithm='lll', var='a', optimize=False): M = K.extension(gg, names='b') y = M.gen()/dd x = M(x) - L = M.absolute_field(names = var) + L = M.absolute_field(names=var) phi = L.structure()[1] x = phi(x) y = phi(y) @@ -4364,13 +4364,9 @@ def mod(self, p, prec=53): # do actual calculation if self.conductor() == 1: - - P = self._trace_exact_conductor_1(prec = prec) + P = self._trace_exact_conductor_1(prec=prec) return E.change_ring(GF(p))(P) - - else: - - raise NotImplementedError + raise NotImplementedError ## def congruent_rational_point(self, n, prec=53): ## r""" diff --git a/src/sage/schemes/elliptic_curves/height.py b/src/sage/schemes/elliptic_curves/height.py index e948474c8b9..06d83bc7759 100644 --- a/src/sage/schemes/elliptic_curves/height.py +++ b/src/sage/schemes/elliptic_curves/height.py @@ -1216,7 +1216,7 @@ def S(self, xi1, xi2, v): ([0.0781194447253472, 0.0823423732016403] U [0.917657626798360, 0.921880555274653]) """ L = self.E.period_lattice(v) - w1, w2 = L.basis(prec = v.codomain().prec()) + w1, w2 = L.basis(prec=v.codomain().prec()) beta = L.elliptic_exponential(w1/2)[0] if xi2 < beta: return UnionOfIntervals([]) diff --git a/src/sage/schemes/elliptic_curves/hom.py b/src/sage/schemes/elliptic_curves/hom.py index de40a534220..8d707e8e6b7 100644 --- a/src/sage/schemes/elliptic_curves/hom.py +++ b/src/sage/schemes/elliptic_curves/hom.py @@ -10,6 +10,7 @@ - :class:`~sage.schemes.elliptic_curves.ell_curve_isogeny.EllipticCurveIsogeny` - :class:`~sage.schemes.elliptic_curves.weierstrass_morphism.WeierstrassIsomorphism` - :class:`~sage.schemes.elliptic_curves.hom_composite.EllipticCurveHom_composite` +- :class:`~sage.schemes.elliptic_curves.hom_velusqrt.EllipticCurveHom_velusqrt` AUTHORS: @@ -21,11 +22,14 @@ """ from sage.misc.cachefunc import cached_method -from sage.structure.richcmp import richcmp_not_equal, richcmp +from sage.structure.richcmp import richcmp_not_equal, richcmp, op_EQ, op_NE from sage.categories.morphism import Morphism -import sage.schemes.elliptic_curves.weierstrass_morphism as wm +from sage.arith.misc import integer_floor + +from sage.rings.finite_rings import finite_field_base +from sage.rings.number_field import number_field_base class EllipticCurveHom(Morphism): @@ -93,13 +97,29 @@ def _composition_(self, other, homset): return Morphism._composition_(self, other, homset) + @staticmethod + def _comparison_impl(left, right, op): + """ + Called by :meth:`_richcmp_`. + + TESTS:: + + sage: from sage.schemes.elliptic_curves.hom import EllipticCurveHom + sage: EllipticCurveHom._comparison_impl(None, None, None) + NotImplemented + """ + return NotImplemented + def _richcmp_(self, other, op): r""" Compare :class:`EllipticCurveHom` objects. ALGORITHM: - This method compares domains, codomains, and :meth:`rational_maps`. + The method first makes sure that domain, codomain and degree match. + Then, it determines if there is a specialized comparison method by + trying :meth:`_comparison_impl` on either input. If not, it falls + back to comparing :meth:`rational_maps`. EXAMPLES:: @@ -137,13 +157,19 @@ def _richcmp_(self, other, op): [True, True] sage: [a == b for a in (wE,mE) for b in (wF,mF)] [False, False, False, False] + + .. SEEALSO:: + + - :meth:`_comparison_impl` + - :func:`compare_via_evaluation` """ - # We cannot just compare kernel polynomials, as was done until - # Trac #11327, as then phi and -phi compare equal, and - # similarly with phi and any composition of phi with an - # automorphism of its codomain, or any post-isomorphism. - # Comparing domains, codomains and rational maps seems much - # safer. + if not isinstance(self, EllipticCurveHom) or not isinstance(other, EllipticCurveHom): + raise TypeError(f'cannot compare {type(self)} to {type(other)}') + + if op == op_NE: + return not self._richcmp_(other, op_EQ) + + # We first compare domain, codomain, and degree; cf. Trac #11327 lx, rx = self.domain(), other.domain() if lx != rx: @@ -157,6 +183,18 @@ def _richcmp_(self, other, op): if lx != rx: return richcmp_not_equal(lx, rx, op) + # Do self or other have specialized comparison methods? + + ret = self._comparison_impl(self, other, op) + if ret is not NotImplemented: + return ret + + ret = other._comparison_impl(self, other, op) + if ret is not NotImplemented: + return ret + + # If not, fall back to comparing rational maps; cf. Trac #11327 + return richcmp(self.rational_maps(), other.rational_maps(), op) @@ -587,6 +625,7 @@ def __neg__(self): sage: psi.rational_maps() == (f, -g) True """ + import sage.schemes.elliptic_curves.weierstrass_morphism as wm a1,_,a3,_,_ = self.codomain().a_invariants() return wm.WeierstrassIsomorphism(self.codomain(), (-1,0,-a1,-a3)) * self @@ -621,3 +660,72 @@ def __hash__(self): Isogeny of degree 7 from Elliptic Curve defined by y^2 + x*y = x^3 - x^2 - 107*x + 552 over Rational Field to Elliptic Curve defined by y^2 + x*y = x^3 - x^2 - 5252*x - 178837 over Rational Field """ return hash((self.domain(), self.codomain(), self.kernel_polynomial())) + + +def compare_via_evaluation(left, right): + r""" + Test if two elliptic-curve morphisms are equal by evaluating + them at enough points. + + INPUT: + + - ``left``, ``right`` -- :class:`EllipticCurveHom` objects + + ALGORITHM: + + We use the fact that two isogenies of equal degree `d` must be + the same if and only if they behave identically on more than + `4d` points. (It suffices to check this on a few points that + generate a large enough subgroup.) + + If the domain curve does not have sufficiently many rational + points, the base field is extended first: Taking an extension + of degree `O(\log(d))` suffices. + + EXAMPLES:: + + sage: E = EllipticCurve(GF(83), [1,0]) + sage: phi = E.isogeny(12*E.0, model='montgomery'); phi + Isogeny of degree 7 from Elliptic Curve defined by y^2 = x^3 + x over Finite Field of size 83 to Elliptic Curve defined by y^2 = x^3 + 70*x^2 + x over Finite Field of size 83 + sage: psi = phi.dual(); psi + Isogeny of degree 7 from Elliptic Curve defined by y^2 = x^3 + 70*x^2 + x over Finite Field of size 83 to Elliptic Curve defined by y^2 = x^3 + x over Finite Field of size 83 + sage: from sage.schemes.elliptic_curves.hom_composite import EllipticCurveHom_composite + sage: mu = EllipticCurveHom_composite.from_factors([phi, psi]) + sage: from sage.schemes.elliptic_curves.hom import compare_via_evaluation + sage: compare_via_evaluation(mu, E.multiplication_by_m_isogeny(7)) + True + + .. SEEALSO:: + + - :meth:`sage.schemes.elliptic_curves.hom_composite.EllipticCurveHom_composite._richcmp_` + """ + if left.domain() != right.domain(): + return False + if left.codomain() != right.codomain(): + return False + if left.degree() != right.degree(): + return False + + E = left.domain() + F = E.base_ring() + + if isinstance(F, finite_field_base.FiniteField): + q = F.cardinality() + d = left.degree() + e = integer_floor(1 + 2 * (2*d.sqrt() + 1).log(q)) # from Hasse bound + e = next(i for i,n in enumerate(E.count_points(e+1), 1) if n > 4*d) + EE = E.base_extend(F.extension(e)) + Ps = EE.gens() + return all(left._eval(P) == right._eval(P) for P in Ps) + + elif isinstance(F, number_field_base.NumberField): + for _ in range(100): + P = E.lift_x(F.random_element(), extend=True) + if not P.has_finite_order(): + return left._eval(P) == right._eval(P) + else: + assert False, "couldn't find a point of infinite order" + + else: + raise NotImplementedError('not implemented for this base field') + diff --git a/src/sage/schemes/elliptic_curves/hom_composite.py b/src/sage/schemes/elliptic_curves/hom_composite.py index 8184e86a7dc..32b1fa9e0bb 100644 --- a/src/sage/schemes/elliptic_curves/hom_composite.py +++ b/src/sage/schemes/elliptic_curves/hom_composite.py @@ -79,19 +79,14 @@ documentation and tests, equality testing """ -from sage.structure.richcmp import op_EQ, op_NE +from sage.structure.richcmp import op_EQ from sage.misc.cachefunc import cached_method from sage.structure.sequence import Sequence -from sage.arith.misc import prod, integer_floor -from sage.functions.log import log -from sage.functions.other import sqrt - -from sage.rings.finite_rings import finite_field_base -from sage.rings.number_field import number_field_base +from sage.arith.misc import prod from sage.schemes.elliptic_curves.ell_generic import EllipticCurve_generic -from sage.schemes.elliptic_curves.hom import EllipticCurveHom +from sage.schemes.elliptic_curves.hom import EllipticCurveHom, compare_via_evaluation from sage.schemes.elliptic_curves.ell_curve_isogeny import EllipticCurveIsogeny from sage.schemes.elliptic_curves.weierstrass_morphism import WeierstrassIsomorphism @@ -428,10 +423,12 @@ def _eval(self, P): """ if self._domain.defining_polynomial()(*P): raise ValueError(f'{P} not on {self._domain}') + k = Sequence(P).universe() + Q = P for phi in self._phis: Q = phi._eval(Q) - k = Sequence(tuple(P) + tuple(Q)).universe() + return self._codomain.base_extend(k)(*Q) def _repr_(self): @@ -483,6 +480,8 @@ def factors(self): return self._phis + # EllipticCurveHom methods + @staticmethod def _composition_impl(left, right): """ @@ -531,28 +530,19 @@ def _composition_impl(left, right): return EllipticCurveHom_composite.from_factors(right.factors() + (left,)) return NotImplemented - - # EllipticCurveHom methods - - def _richcmp_(self, other, op): + @staticmethod + def _comparison_impl(left, right, op): r""" - Compare this composite isogeny to another elliptic-curve morphism. - - ALGORITHM: + Compare a composite isogeny to another elliptic-curve morphism. - Over finite fields and number fields, we use the fact that two - isogenies of equal degree `d` are the same if and only if they - act identically on more than `4d` points. (It suffices to check - this on a few points that generate a large enough subgroup.) + Called by :meth:`EllipticCurveHom._richcmp_`. - If the domain curve does not have sufficiently many rational - points, the base field is extended first. Since an extension of - degree `O(\log(d))` suffices, the complexity is polynomial in - the representation size of this morphism. + ALGORITHM: - Over more general base fields, we fall back to comparing the - results of :meth:`rational_maps`, which takes time at least - linear in the degree. + If possible, we use + :func:`~sage.schemes.elliptic_curves.hom.compare_via_evaluation`. + The complexity in that case is polynomial in the representation + size of this morphism. TESTS:: @@ -578,41 +568,12 @@ def _richcmp_(self, other, op): sage: phi2 * phi1 == psi2 * psi1 True """ - if op == op_NE: - return not self._richcmp_(other, op_EQ) if op != op_EQ: return NotImplemented - - if self.domain() != other.domain(): - return False - if self.codomain() != other.codomain(): - return False - if self.degree() != other.degree(): - return False - - E = self.domain() - F = E.base_ring() - - if isinstance(F, finite_field_base.FiniteField): - q = F.cardinality() - d = self.degree() - e = integer_floor(1 + 2 * log(2*sqrt(d) + 1, q)) # from Hasse bound - e = next(i for i,n in enumerate(E.count_points(e+1), 1) if n > 4*d) - EE = E.base_extend(F.extension(e)) - Ps = EE.gens() - return all(self._eval(P) == other._eval(P) for P in Ps) - - elif isinstance(F, number_field_base.NumberField): - for _ in range(100): - P = E.lift_x(F.random_element(), extend=True) - if not P.has_finite_order(): - return self._eval(P) == other._eval(P) - else: - assert False, "_richcmp_() couldn't find a point of infinite order" - - else: - # fall back to generic method - return self.rational_maps() == other.rational_maps() + try: + return compare_via_evaluation(left, right) + except NotImplementedError: + return NotImplemented def rational_maps(self): """ diff --git a/src/sage/schemes/elliptic_curves/hom_velusqrt.py b/src/sage/schemes/elliptic_curves/hom_velusqrt.py new file mode 100644 index 00000000000..20688dbb4d0 --- /dev/null +++ b/src/sage/schemes/elliptic_curves/hom_velusqrt.py @@ -0,0 +1,1250 @@ +r""" +√élu Algorithm for Elliptic-Curve Isogenies + +The √élu algorithm computes isogenies of elliptic curves in time +`\tilde O(\sqrt\ell)` rather than naïvely `O(\ell)`, where `\ell` +is the degree. + +The core idea is to reindex the points in the kernel subgroup in +a baby-step-giant-step manner, then use fast resultant computations +to evaluate "elliptic polynomials" +(see :class:`FastEllipticPolynomial`) +in essentially square-root time. + +Based on experiments with Sage version 9.7, +the isogeny degree where +:class:`EllipticCurveHom_velusqrt` +begins to outperform +:class:`~sage.schemes.elliptic_curves.ell_curve_isogeny.EllipticCurveIsogeny` +can be as low as `\approx 100`, +but is typically closer to `\approx 1000`, +depending on the exact situation. + +REFERENCES: [BDLS2020]_ + +EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.hom_velusqrt import EllipticCurveHom_velusqrt + sage: E = EllipticCurve(GF(6666679), [5,5]) + sage: K = E(9970, 1003793, 1) + sage: K.order() + 10009 + sage: phi = EllipticCurveHom_velusqrt(E, K) + sage: phi + Elliptic-curve isogeny (using √élu) of degree 10009: + From: Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field of size 6666679 + To: Elliptic Curve defined by y^2 = x^3 + 227975*x + 3596133 over Finite Field of size 6666679 + sage: phi.codomain() + Elliptic Curve defined by y^2 = x^3 + 227975*x + 3596133 over Finite Field of size 6666679 + +Note that the isogeny is usually not identical to the one computed by +:class:`~sage.schemes.elliptic_curves.ell_curve_isogeny.EllipticCurveIsogeny`:: + + sage: psi = EllipticCurveIsogeny(E, K) + sage: psi + Isogeny of degree 10009 + from Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field of size 6666679 + to Elliptic Curve defined by y^2 = x^3 + 5344836*x + 3950273 over Finite Field of size 6666679 + +However, they are certainly separable isogenies with the same kernel +and must therefore be equal *up to post-isomorphism*:: + + sage: isos = psi.codomain().isomorphisms(phi.codomain()) + sage: sum(iso * psi == phi for iso in isos) + 1 + +Just like +:class:`~sage.schemes.elliptic_curves.ell_curve_isogeny.EllipticCurveIsogeny`, +the constructor supports a ``model`` keyword argument:: + + sage: E = EllipticCurve(GF(6666679), [1,1]) + sage: K = E(9091, 517864) + sage: phi = EllipticCurveHom_velusqrt(E, K, model='montgomery') + sage: phi + Elliptic-curve isogeny (using √élu) of degree 2999: + From: Elliptic Curve defined by y^2 = x^3 + x + 1 over Finite Field of size 6666679 + To: Elliptic Curve defined by y^2 = x^3 + 1559358*x^2 + x over Finite Field of size 6666679 + +Internally, :class:`EllipticCurveHom_velusqrt` works on short +Weierstraß curves, but it performs the conversion automatically:: + + sage: E = EllipticCurve(GF(101), [1,2,3,4,5]) + sage: K = E(1, 2) + sage: K.order() + 37 + sage: EllipticCurveHom_velusqrt(E, K) + Elliptic-curve isogeny (using √élu) of degree 37: + From: Elliptic Curve defined by y^2 + x*y + 3*y = x^3 + 2*x^2 + 4*x + 5 over Finite Field of size 101 + To: Elliptic Curve defined by y^2 = x^3 + 66*x + 86 over Finite Field of size 101 + +However, this does imply not all elliptic curves are supported. +Curves without a short Weierstraß model exist in characteristics +`2` and `3`:: + + sage: F. = GF(3^3) + sage: E = EllipticCurve(F, [1,1,1,1,1]) + sage: P = E(t^2+2, 1) + sage: P.order() + 19 + sage: EllipticCurveHom_velusqrt(E, P) + Traceback (most recent call last): + ... + NotImplementedError: only implemented for curves having a short Weierstrass model + +Furthermore, the implementation is restricted to finite fields, +since this appears to be the most relevant application for the +√élu algorithm:: + + sage: E = EllipticCurve('26b1') + sage: P = E(1,0) + sage: P.order() + 7 + sage: EllipticCurveHom_velusqrt(E, P) + Traceback (most recent call last): + ... + NotImplementedError: only implemented for elliptic curves over finite fields + +.. NOTE:: + + Currently :class:`EllipticCurveHom_velusqrt` does not implement + all methods of :class:`EllipticCurveHom`. This will hopefully + change in the future. + +AUTHORS: + +- Lorenz Panny (2022) +""" + +# **************************************************************************** +# Copyright (C) 2022 Lorenz Panny +# +# 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.sequence import Sequence +from sage.structure.all import coercion_model as cm + +from sage.misc.misc_c import prod + +from sage.structure.richcmp import op_EQ + +from sage.rings.integer import Integer + +from sage.schemes.elliptic_curves.constructor import EllipticCurve +from sage.schemes.elliptic_curves.ell_finite_field import EllipticCurve_finite_field +from sage.schemes.elliptic_curves.hom import EllipticCurveHom, compare_via_evaluation + + +#TODO: This is general. It should be elsewhere. +class ProductTree: + r""" + A simple product tree. + + INPUT: + + - ``leaves`` -- a sequence of elements in a common ring + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.hom_velusqrt import ProductTree + sage: R. = GF(101)[] + sage: vs = [x - i for i in range(1,10)] + sage: tree = ProductTree(vs) + sage: tree.value() + x^9 + 56*x^8 + 62*x^7 + 44*x^6 + 47*x^5 + 42*x^4 + 15*x^3 + 11*x^2 + 12*x + 13 + sage: tree.remainders(x^7 + x + 1) + [3, 30, 70, 27, 58, 72, 98, 98, 23] + sage: tree.remainders(x^100) + [1, 1, 1, 1, 1, 1, 1, 1, 1] + + :: + + sage: vs = prime_range(100) + sage: tree = ProductTree(vs) + sage: tree.value().factor() + 2 * 3 * 5 * 7 * 11 * 13 * 17 * 19 * 23 * 29 * 31 * 37 * 41 * 43 * 47 * 53 * 59 * 61 * 67 * 71 * 73 * 79 * 83 * 89 * 97 + sage: tree.remainders(3599) + [1, 2, 4, 1, 2, 11, 12, 8, 11, 3, 3, 10, 32, 30, 27, 48, 0, 0, 48, 49, 22, 44, 30, 39, 10] + + We can access the individual layers of the tree:: + + sage: tree.layers + [(2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97), + (6, 35, 143, 323, 667, 1147, 1763, 2491, 3599, 4757, 5767, 7387, 97), + (210, 46189, 765049, 4391633, 17120443, 42600829, 97), + (9699690, 3359814435017, 729345064647247, 97), + (32589158477190044730, 70746471270782959), + (2305567963945518424753102147331756070,)] + + .. NOTE:: + + Use this class if you need the :meth:`remainders` method. + To compute just the product, :func:`prod` is likely faster. + """ + def __init__(self, leaves): + r""" + Initialize a product tree having the given ring elements + as its leaves. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.hom_velusqrt import ProductTree + sage: vs = prime_range(100) + sage: tree = ProductTree(vs) + """ + V = tuple(leaves) + self.layers = [V] + while len(V) > 1: + V = tuple(prod(V[i:i+2]) for i in range(0,len(V),2)) + self.layers.append(V) + + def __len__(self): + r""" + Return the number of leaves of this product tree. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.hom_velusqrt import ProductTree + sage: R. = GF(101)[] + sage: vs = [x - i for i in range(1,10)] + sage: tree = ProductTree(vs) + sage: len(tree) + 9 + sage: len(tree) == len(vs) + True + sage: len(tree.remainders(x^2)) + 9 + """ + return len(self.layers[0]) + + def value(self): + r""" + Return the value represented by this product tree + (i.e., the product of all leaves). + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.hom_velusqrt import ProductTree + sage: R. = GF(101)[] + sage: vs = [x - i for i in range(1,10)] + sage: tree = ProductTree(vs) + sage: tree.value() + x^9 + 56*x^8 + 62*x^7 + 44*x^6 + 47*x^5 + 42*x^4 + 15*x^3 + 11*x^2 + 12*x + 13 + sage: tree.value() == prod(vs) + True + """ + assert len(self.layers[-1]) == 1 + return self.layers[-1][0] + + def remainders(self, x): + r""" + Given a value `x`, return a list of all remainders of `x` + modulo the leaves of this product tree. + + The base ring must support the ``%`` operator for this + method to work. + + INPUT: + + - ``x`` -- an element of the base ring of this product tree + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.hom_velusqrt import ProductTree + sage: vs = prime_range(100) + sage: tree = ProductTree(vs) + sage: n = 1085749272377676749812331719267 + sage: tree.remainders(n) + [1, 1, 2, 1, 9, 1, 7, 15, 8, 20, 15, 6, 27, 11, 2, 6, 0, 25, 49, 5, 51, 4, 19, 74, 13] + sage: [n % v for v in vs] + [1, 1, 2, 1, 9, 1, 7, 15, 8, 20, 15, 6, 27, 11, 2, 6, 0, 25, 49, 5, 51, 4, 19, 74, 13] + """ + X = [x] + for V in reversed(self.layers): + X = [X[i//2] % V[i] for i in range(len(V))] + return X + +#TODO: This is general. It should be elsewhere. +def prod_with_derivative(pairs): + r""" + Given a list of pairs `(f, \partial f)` of ring elements, return + the pair `(\prod f, \partial \prod f)`, assuming `\partial` is an + operator obeying the standard product rule. + + This function is entirely algebraic, hence still works when the + elements `f` and `\partial f` are all passed through some ring + homomorphism first. (See the polynomial-evaluation example below.) + + INPUT: + + - ``pairs`` -- a sequence of tuples `(f, \partial f)` of elements + of a common ring + + ALGORITHM: + + This function wraps the given pairs in a thin helper class that + automatically applies the product rule whenever multiplication + is invoked, then calls :func:`prod` on the wrapped pairs. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.hom_velusqrt import prod_with_derivative + sage: R. = ZZ[] + sage: fs = [x^2 + 2*x + 3, 4*x + 5, 6*x^7 + 8*x + 9] + sage: prod(fs) + 24*x^10 + 78*x^9 + 132*x^8 + 90*x^7 + 32*x^4 + 140*x^3 + 293*x^2 + 318*x + 135 + sage: prod(fs).derivative() + 240*x^9 + 702*x^8 + 1056*x^7 + 630*x^6 + 128*x^3 + 420*x^2 + 586*x + 318 + sage: F, dF = prod_with_derivative((f, f.derivative()) for f in fs) + sage: F + 24*x^10 + 78*x^9 + 132*x^8 + 90*x^7 + 32*x^4 + 140*x^3 + 293*x^2 + 318*x + 135 + sage: dF + 240*x^9 + 702*x^8 + 1056*x^7 + 630*x^6 + 128*x^3 + 420*x^2 + 586*x + 318 + + The main reason for this function to exist is that it allows us to + *evaluate* the derivative of a product of polynomials at a point + `\alpha` without ever fully expanding the product *as a polynomial*:: + + sage: alpha = 42 + sage: F(alpha) + 442943981574522759 + sage: dF(alpha) + 104645261461514994 + sage: us = [f(alpha) for f in fs] + sage: vs = [f.derivative()(alpha) for f in fs] + sage: prod_with_derivative(zip(us, vs)) + (442943981574522759, 104645261461514994) + """ + class _aux: + def __init__(self, f, df): + self.f, self.df = f, df + + def __mul__(self, other): + return _aux(self.f * other.f, self.df * other.f + self.f * other.df) + + def __iter__(self): + yield self.f + yield self.df + + return tuple(prod(_aux(*tup) for tup in pairs)) + + +def _choose_IJK(n): + r""" + Helper function to choose an "index system" for the set + `\{1,3,5,7,...,n-2\}` where `n \geq 5` is an odd integer. + + INPUT: + + - ``n`` -- odd :class:`~sage.rings.integer.Integer` `\geq 5` + + REFERENCES: [BDLS2020]_, Examples 4.7 and 4.12 + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.hom_velusqrt import _choose_IJK + sage: IJK = _choose_IJK(101); IJK + (range(10, 91, 20), range(1, 10, 2), range(101, 101, 2)) + sage: I,J,K = IJK + sage: sorted([i + s*j for i in iter(I) for j in iter(J) for s in (+1,-1)] + list(iter(K))) + [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, + 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95, 97, 99] + + TESTS:: + + sage: for n in range(5,1000,2): + ....: I,J,K = _choose_IJK(ZZ(n)) + ....: assert sorted([i + s*j for i in iter(I) for j in iter(J) for s in (+1,-1)] + list(iter(K))) == sorted(range(1,n,2)) + """ + if n % 2 != 1 or n < 5: + raise ValueError('n must be odd and >= 5') + b = (n-1).isqrt() // 2 + c = (n-1) // (4*b) + I = range(2*b, 2*b*(2*c-1)+1, 4*b) + J = range(1, 2*b, 2) + K = range(4*b*c+1, n, 2) + return I, J, K + +def _points_range(rr, P, Q=None): + r""" + Return an iterator yielding all points `Q + [i]P` where `i` runs + through the :class:`range` object ``rr``. + + INPUT: + + - ``rr`` -- :class:`range` object defining a sequence `S \subseteq \ZZ` + - ``P`` -- element of an additive abelian group + - ``Q`` -- element of the same group, or ``None`` + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.hom_velusqrt import _points_range + sage: E = EllipticCurve(GF(1123), [4,5]) + sage: P = E(1, 75) + sage: 2*P + (1038 : 498 : 1) + sage: 5*P + (236 : 598 : 1) + sage: 8*P + (717 : 530 : 1) + sage: list(_points_range(range(2,10,3), P)) + [(1038 : 498 : 1), (236 : 598 : 1), (717 : 530 : 1)] + sage: Q = E(7, 202) + sage: Q + 2*P + (65 : 717 : 1) + sage: Q + 5*P + (1119 : 788 : 1) + sage: Q + 8*P + (949 : 315 : 1) + sage: list(_points_range(range(2,10,3), P, Q)) + [(65 : 717 : 1), (1119 : 788 : 1), (949 : 315 : 1)] + """ + if not rr: + return + a,b,s = rr.start, rr.stop, rr.step + R = a*P if Q is None else Q + a*P + yield R + sP = s*P + for _ in range(a+s, b, s): + yield (R := R + sP) + +class FastEllipticPolynomial: + r""" + A class to represent and evaluate an *elliptic polynomial*, + and optionally its derivative, in essentially square-root time. + + The elliptic polynomials computed by this class are of the form + + .. MATH:: + + h_S(Z) = \prod_{i\in S} (Z - x(Q + [i]P)) + + where `P` is a point of odd order `n \geq 5` and `Q` is either ``None``, + in which case it is assumed to be `\infty`, or an arbitrary point which is + not a multiple of `P`. + + The index set `S` is chosen as follows: + + - If `Q` is given, then `S = \{0,1,2,3,...,n-1\}`. + + - If `Q` is omitted, then `S = \{1,3,5,...,n-2\}`. Note that in this case, + `h_{\{1,2,3,...,n-1\}}` can be computed as `h_S^2` since `n` is odd. + + INPUT: + + - ``E`` -- an elliptic curve in short Weierstraß form + - ``n`` -- an odd integer `\geq 5` + - ``P`` -- a point on `E` + - ``Q`` -- a point on `E`, or ``None`` + + ALGORITHM: [BDLS2020]_, Algorithm 2 + + .. NOTE:: + + Currently only implemented for short Weierstraß curves. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.hom_velusqrt import FastEllipticPolynomial + sage: E = EllipticCurve(GF(71), [5,5]) + sage: P = E(4, 35) + sage: hP = FastEllipticPolynomial(E, P.order(), P); hP + Fast elliptic polynomial prod(Z - x(i*P) for i in range(1,n,2)) with n = 19, P = (4 : 35 : 1) + sage: hP(7) + 19 + sage: prod(7 - (i*P).xy()[0] for i in range(1,P.order(),2)) + 19 + + Passing `Q` changes the index set:: + + sage: Q = E(0, 17) + sage: hPQ = FastEllipticPolynomial(E, P.order(), P, Q) + sage: hPQ(7) + 58 + sage: prod(7 - (Q+i*P).xy()[0] for i in range(P.order())) + 58 + + The call syntax has an optional keyword argument ``derivative``, which + will make the function return the pair `(h_S(\alpha), h_S'(\alpha))` + instead of just `h_S(\alpha)`:: + + sage: hP(7, derivative=True) + (19, 15) + sage: R. = E.base_field()[] + sage: HP = prod(Z - (i*P).xy()[0] for i in range(1,P.order(),2)) + sage: HP + Z^9 + 16*Z^8 + 57*Z^7 + 6*Z^6 + 45*Z^5 + 31*Z^4 + 46*Z^3 + 10*Z^2 + 28*Z + 41 + sage: HP(7) + 19 + sage: HP.derivative()(7) + 15 + + :: + + sage: hPQ(7, derivative=True) + (58, 62) + sage: R. = E.base_field()[] + sage: HPQ = prod(Z - (Q+i*P).xy()[0] for i in range(P.order())) + sage: HPQ + Z^19 + 53*Z^18 + 67*Z^17 + 39*Z^16 + 56*Z^15 + 32*Z^14 + 44*Z^13 + 6*Z^12 + 27*Z^11 + 29*Z^10 + 38*Z^9 + 48*Z^8 + 38*Z^7 + 43*Z^6 + 21*Z^5 + 25*Z^4 + 33*Z^3 + 49*Z^2 + 60*Z + sage: HPQ(7) + 58 + sage: HPQ.derivative()(7) + 62 + + The input can be an element of any algebra over the base ring:: + + sage: R. = GF(71)[] + sage: S. = R.quotient(T^2) + sage: hP(7 + t) + 15*t + 19 + """ + def __init__(self, E, n, P, Q=None): + r""" + Initialize this elliptic polynomial and precompute some + input-independent data required for evaluation. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.hom_velusqrt import FastEllipticPolynomial + sage: E = EllipticCurve(GF(71), [5,5]) + sage: P = E(0, 17) + sage: FastEllipticPolynomial(E, P.order(), P) + Fast elliptic polynomial prod(Z - x(i*P) for i in range(1,n,2)) with n = 57, P = (0 : 17 : 1) + """ + if any(E.a_invariants()[:-2]): + raise NotImplementedError('only implemented for short Weierstrass curves') + + n = Integer(n) + + if Q is None: + IJK = _choose_IJK(n) # [1,3,5,7,...,n-4,n-2] + else: + IJK = _choose_IJK(2*n+1) # [1,3,5,7,...,2n-1] = [0,1,2,3,...,n-2,n-1] + + self.base = E.base_ring() + R, Z = self.base['x'].objgen() + + # Cassels, Lectures on Elliptic Curves, p.132 + A,B = E.a_invariants()[-2:] + Fs = lambda X,Y: ( + (X - Y)**2, + -2 * (X*Y + A) * (X + Y) - 4*B, + (X*Y - A)**2 - 4*B*(X+Y), + ) + + I, J, K = IJK + xI = (R.xy()[0] for R in _points_range(I, P, Q)) + xJ = [R.xy()[0] for R in _points_range(J, P )] + xK = (R.xy()[0] for R in _points_range(K, P, Q)) + + self.hItree = ProductTree(Z - xi for xi in xI) + + self.EJparts = [Fs(Z,xj) for xj in xJ] + + DJ = prod(F0j for F0j,_,_ in self.EJparts) + self.DeltaIJ = self._hI_resultant(DJ) + + self.hK = R(prod(Z - xk for xk in xK)) + self.dhK = self.hK.derivative() + + if Q is None: + self._repr = f"Fast elliptic polynomial prod(Z - x(i*P) for i in range(1,n,2)) with {n = }, {P = }" + else: + self._repr = f"Fast elliptic polynomial prod(Z - x(Q+i*P) for i in range(n)) with {n = }, {P = }, {Q = }" + + def __call__(self, alpha, *, derivative=False): + r""" + Evaluate this elliptic polynomial at a point `\alpha`, + and if ``derivative`` is set to ``True`` also return + the evaluation of the derivative at `\alpha`. + + INPUT: + + - ``alpha`` -- an element of any algebra over the base ring + - ``derivative`` -- boolean (default: ``False``) + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.hom_velusqrt import FastEllipticPolynomial + sage: E = EllipticCurve(GF(71), [5,5]) + sage: P = E(4, 35) + sage: hP = FastEllipticPolynomial(E, P.order(), P); hP + Fast elliptic polynomial prod(Z - x(i*P) for i in range(1,n,2)) with n = 19, P = (4 : 35 : 1) + sage: hP(7) + 19 + sage: hP(7, derivative=True) + (19, 15) + """ + base = cm.common_parent(self.base, alpha) + + EJparts = [tuple(F.base_extend(base) for F in part) for part in self.EJparts] + + EJfacs = [(F0j * alpha + F1j) * alpha + F2j for F0j,F1j,F2j in EJparts] + if not derivative: + EJ = prod(EJfacs) + else: + dEJfacs = [2 * F0j * alpha + F1j for F0j,F1j,_ in EJparts] + EJ, dEJ = prod_with_derivative(zip(EJfacs, dEJfacs)) + + EJrems = self.hItree.remainders(EJ) + R = self._hI_resultant(EJ, EJrems) + hK = self.hK(alpha) + res = hK * R / self.DeltaIJ + + if not derivative: + return res + + dEJrems = self.hItree.remainders(dEJ) + cnt = EJrems.count(0) + if cnt == 0: + dR = sum(R // EJrem * dEJrem for EJrem, dEJrem in zip(EJrems, dEJrems)) + elif cnt == 1: + dR = prod(EJrem or dEJrem for EJrem, dEJrem in zip(EJrems, dEJrems)) + else: + dR = 0 + dhK = self.dhK(alpha) + dres = (dhK * R + hK * dR) / self.DeltaIJ + + return res, dres + + def _hI_resultant(self, poly, rems=None): + r""" + Internal helper function to evaluate a resultant with `h_I` quickly, + using the product tree constructed in :meth:`__init__`. + + INPUT: + + - ``poly`` -- an element of the base ring of this product tree, + which must be a polynomial ring supporting ``%`` + - ``rems`` -- result of ``self.hItree.remainders(poly)``, or ``None`` + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.hom_velusqrt import FastEllipticPolynomial + sage: E = EllipticCurve(GF(71), [5,5]) + sage: P = E(4, 35) + sage: hP = FastEllipticPolynomial(E, P.order(), P) + sage: f = GF(71)['x']([5,4,3,2,1]) + sage: hP._hI_resultant(f) + 66 + sage: prod(f(r) for fi in hP.hItree.layers[0] + ....: for r in fi.roots(multiplicities=False)) + 66 + + :: + + sage: Q = E(0, 17) + sage: hPQ = FastEllipticPolynomial(E, P.order(), P, Q) + sage: f = GF(71)['x']([9,8,7,6,5,4,3,2,1]) + sage: hPQ._hI_resultant(f) + 36 + sage: prod(f(r) for fi in hPQ.hItree.layers[0] + ....: for r in fi.roots(multiplicities=False)) + 36 + """ + if rems is None: + rems = self.hItree.remainders(poly) + r = prod(rems) + s = -1 if len(self.hItree)%2 == 1 == poly.degree() else 1 + assert r.is_constant() + return s * r[0] + + def __repr__(self): + r""" + Return a string representation of this elliptic polynomial. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.hom_velusqrt import FastEllipticPolynomial + sage: E = EllipticCurve(GF(71), [5,5]) + sage: P = E(4, 35) + sage: FastEllipticPolynomial(E, P.order(), P) + Fast elliptic polynomial prod(Z - x(i*P) for i in range(1,n,2)) with n = 19, P = (4 : 35 : 1) + sage: Q = E(0, 17) + sage: FastEllipticPolynomial(E, P.order(), P, Q) + Fast elliptic polynomial prod(Z - x(Q+i*P) for i in range(n)) with n = 19, P = (4 : 35 : 1), Q = (0 : 17 : 1) + """ + return self._repr + + +def _point_outside_subgroup(P): + r""" + Simple helper function to return a point on an elliptic + curve `E` that is not a multiple of a given point `P`. + The base field is extended if (and only if) necessary. + + INPUT: + + - ``P`` -- a point on an elliptic curve over a finite field + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.hom_velusqrt import _point_outside_subgroup + sage: E = EllipticCurve(GF(71), [5,5]) + sage: P = E(4, 35) + sage: Q = _point_outside_subgroup(P); Q # random + (14 : 11 : 1) + sage: Q.curve()(P).discrete_log(Q) + Traceback (most recent call last): + ... + ValueError: ECDLog problem has no solution (...) + + An example where `P` generates `E(\mathbb F_q)`:: + + sage: E.

= EllipticCurve(GF(71), [5,5]) + sage: P.order() == E.cardinality() + True + sage: Q = _point_outside_subgroup(P); Q # random + (35*z2 + 7 : 24*z2 + 7 : 1) + sage: Q.curve()(P).discrete_log(Q) + Traceback (most recent call last): + ... + ValueError: ECDLog problem has no solution (...) + + An example where the group is non-cyclic: + + sage: E. = EllipticCurve(GF(71^2), [0,1]) + sage: E.abelian_group() + Additive abelian group isomorphic to Z/72 + Z/72 embedded in Abelian group of points on Elliptic Curve defined by y^2 = x^3 + 1 over Finite Field in z2 of size 71^2 + sage: P = E.random_point() + sage: Q = _point_outside_subgroup(P); Q # random + (18*z2 + 46 : 58*z2 + 61 : 1) + sage: Q in E + True + sage: P.discrete_log(Q) + Traceback (most recent call last): + ... + ValueError: ECDLog problem has no solution (...) + + .. NOTE:: + + The field extension is only needed when `P` generates the + entire rational subgroup of `E`. But in that case, the + isogeny defined by `P` is simply `\pi-1` (where `\pi` is + Frobenius). Thus, once `\pi-1` can be represented in Sage, + we may just return that in + :meth:`~sage.schemes.elliptic_curves.ell_field.EllipticCurve_field.isogeny` + rather than insisting on using Îlu. + """ + E = P.curve() + n = P.order() + if n == E.order(): + d = 2 + (n == 7 and E.base_field().cardinality() == 3) + F = E.base_field().extension(d) + E = E.base_extend(F) + P = E(P) +# assert E.cardinality() > n + for _ in range(1000): + Q = E.random_point() + if n*Q or not P.weil_pairing(Q,n).is_one(): + return Q + else: + raise NotImplementedError('could not find a point outside the kernel') + +class EllipticCurveHom_velusqrt(EllipticCurveHom): + r""" + This class implements separable odd-degree isogenies of elliptic + curves over finite fields using the Îlu algorithm. + + The complexity is `\tilde O(\sqrt{\ell})` base-field operations, + where `\ell` is the degree. + + REFERENCES: [BDLS2020]_ + + INPUT: + + - ``E`` -- an elliptic curve over a finite field + - ``P`` -- a point on `E` of odd order `\geq 9` + - ``codomain`` -- codomain elliptic curve (optional) + - ``model`` -- string (optional); input to + :meth:`~sage.schemes.elliptic_curves.ell_field.compute_model` + - ``Q`` -- a point on `E` outside `\langle P\rangle`, or ``None`` + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.hom_velusqrt import EllipticCurveHom_velusqrt + sage: F. = GF(10009^3) + sage: E = EllipticCurve(F, [t,t]) + sage: K = E(2154*t^2 + 5711*t + 2899, 7340*t^2 + 4653*t + 6935) + sage: phi = EllipticCurveHom_velusqrt(E, K); phi + Elliptic-curve isogeny (using Îlu) of degree 601: + From: Elliptic Curve defined by y^2 = x^3 + t*x + t over Finite Field in t of size 10009^3 + To: Elliptic Curve defined by y^2 = x^3 + (263*t^2+3173*t+4759)*x + (3898*t^2+6111*t+9443) over Finite Field in t of size 10009^3 + sage: phi(K) + (0 : 1 : 0) + sage: P = E(2, 3163*t^2 + 7293*t + 5999) + sage: phi(P) + (6085*t^2 + 855*t + 8720 : 8078*t^2 + 9889*t + 6030 : 1) + sage: Q = E(6, 5575*t^2 + 6607*t + 9991) + sage: phi(Q) + (626*t^2 + 9749*t + 1291 : 5931*t^2 + 8549*t + 3111 : 1) + sage: phi(P + Q) + (983*t^2 + 4894*t + 4072 : 5047*t^2 + 9325*t + 336 : 1) + sage: phi(P) + phi(Q) + (983*t^2 + 4894*t + 4072 : 5047*t^2 + 9325*t + 336 : 1) + + TESTS: + + Check on a random example that the isogeny is a well-defined + group homomorphism with the correct kernel:: + + sage: from sage.schemes.elliptic_curves.hom_velusqrt import _random_example_for_testing + sage: E, K = _random_example_for_testing() + sage: phi = EllipticCurveHom_velusqrt(E, K) + sage: not phi(K) + True + sage: not phi(randrange(2^99) * K) + True + sage: P = E.random_point() + sage: phi(P) in phi.codomain() + True + sage: Q = E.random_point() + sage: phi(Q) in phi.codomain() + True + sage: phi(P + Q) == phi(P) + phi(Q) + True + + Check that the isogeny preserves the field of definition:: + + sage: Sequence(K).universe() == phi.domain().base_field() + True + sage: phi.codomain().base_field() == phi.domain().base_field() + True + + Check that the isogeny affects the Weil pairing in the correct way:: + + sage: m = lcm(P.order(), Q.order()) + sage: e1 = P.weil_pairing(Q, m) + sage: e2 = phi(P).weil_pairing(phi(Q), m) + sage: e2 == e1^phi.degree() + True + + Check that the isogeny matches (up to isomorphism) the one from + :class:`~sage.schemes.elliptic_curves.ell_curve_isogeny.EllipticCurveIsogeny`:: + + sage: psi = EllipticCurveIsogeny(E, K) + sage: check = lambda iso: all(iso(psi(Q)) == phi(Q) for Q in E.gens()) + sage: any(map(check, psi.codomain().isomorphisms(phi.codomain()))) + True + + .. SEEALSO:: + + :class:`~sage.schemes.elliptic_curves.ell_curve_isogeny.EllipticCurveIsogeny` + """ + def __init__(self, E, P, *, codomain=None, model=None, Q=None): + r""" + Initialize this Îlu isogeny from a kernel point of odd order. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.hom_velusqrt import EllipticCurveHom_velusqrt + sage: E = EllipticCurve(GF(71), [5,5]) + sage: P = E(-2, 22) + sage: EllipticCurveHom_velusqrt(E, P) + Elliptic-curve isogeny (using Îlu) of degree 19: + From: Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field of size 71 + To: Elliptic Curve defined by y^2 = x^3 + 13*x + 11 over Finite Field of size 71 + + :: + + sage: E.

= EllipticCurve(GF(419), [1,0]) + sage: K = 4*P + sage: EllipticCurveHom_velusqrt(E, K) + Elliptic-curve isogeny (using Îlu) of degree 105: + From: Elliptic Curve defined by y^2 = x^3 + x over Finite Field of size 419 + To: Elliptic Curve defined by y^2 = x^3 + 301*x + 86 over Finite Field of size 419 + sage: E2 = EllipticCurve(GF(419), [0,6,0,385,42]) + sage: EllipticCurveHom_velusqrt(E, K, codomain=E2) + Elliptic-curve isogeny (using Îlu) of degree 105: + From: Elliptic Curve defined by y^2 = x^3 + x over Finite Field of size 419 + To: Elliptic Curve defined by y^2 = x^3 + 6*x^2 + 385*x + 42 over Finite Field of size 419 + sage: EllipticCurveHom_velusqrt(E, K, model="montgomery") + Elliptic-curve isogeny (using Îlu) of degree 105: + From: Elliptic Curve defined by y^2 = x^3 + x over Finite Field of size 419 + To: Elliptic Curve defined by y^2 = x^3 + 6*x^2 + x over Finite Field of size 419 + + Note that the implementation in fact also works in almost all + cases when the degree is `5` or `7`. The reason we restrict to + degrees `\geq 9` is that (only!) when trying to compute a + `7`-isogeny from a rational point on an elliptic curve defined + over `\GF{3}`, the point `Q` required in the formulas has to be + defined over a cubic extension rather than an at most quadratic + extension, which can result in the constructed isogeny being + irrational. See :trac:`34467`. The assertion in the following + example currently fails if the minimum degree is lowered:: + + sage: E = EllipticCurve(GF(3), [2,1]) + sage: P, = E.gens() + sage: P.order() + 7 + sage: psi = E.isogeny(P) + sage: phi = E.isogeny(P, algorithm='velusqrt') # not tested + sage: phi._Q.base_ring() # not tested + Finite Field in z3 of size 3^3 + sage: assert phi.codomain().is_isomorphic(psi.codomain()) # not tested + """ + if not isinstance(E, EllipticCurve_finite_field): + raise NotImplementedError('only implemented for elliptic curves over finite fields') + + if codomain is not None and model is not None: + raise ValueError('cannot specify a codomain curve and model name simultaneously') + + try: + self._raw_domain = E.short_weierstrass_model() + except ValueError: + raise NotImplementedError('only implemented for curves having a short Weierstrass model') + self._pre_iso = E.isomorphism_to(self._raw_domain) + + try: + P = E(P) + except TypeError: + raise ValueError('given kernel point P does not lie on E') + self._P = self._pre_iso(P) + + self._degree = self._P.order() + if self._degree % 2 != 1 or self._degree < 9: + raise NotImplementedError('only implemented for odd degrees >= 9') + + if Q is not None: + self._Q = E(Q) + EE = E + else: + self._Q = _point_outside_subgroup(self._P) # may extend base field + EE = self._Q.curve() + self._P = EE(self._P) + + self._base_ring = EE.base_ring() + + self._h0 = FastEllipticPolynomial(EE, self._degree, self._P) + self._h1 = FastEllipticPolynomial(EE, self._degree, self._P, self._Q) + + self._domain = E + self._compute_codomain(model=model) + + if codomain is not None: + self._post_iso = self._codomain.isomorphism_to(codomain) * self._post_iso + self._codomain = codomain + + super().__init__(self._domain, self._codomain) + + def _raw_eval(self, x, y=None): + r""" + Evaluate the "inner" Îlu isogeny (i.e., without applying + pre- and post-isomorphism) at either just an `x`-coordinate + or a pair `(x,y)` of coordinates. + + If the given point lies in the kernel, the empty tuple + ``()`` is returned. + + No checking of the input coordinates is performed. + + ALGORITHM: + + - [Ren2018]_, Theorem 1 + - :class:`FastEllipticPolynomial` + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.hom_velusqrt import EllipticCurveHom_velusqrt + sage: E = EllipticCurve(GF(65537), [1,1]) + sage: P = E(2112, 803) + sage: phi = EllipticCurveHom_velusqrt(E, P, Q=(32924,0)) + sage: phi._raw_domain is E + True + sage: phi._raw_codomain + Elliptic Curve defined by y^2 = x^3 + ... over Finite Field of size 65537 + sage: Q = E(42, 15860) + sage: phi._raw_eval(Q.xy()[0]) + 11958 + sage: phi._raw_eval(*Q.xy()) + (11958, 42770) + sage: phi._raw_codomain.defining_polynomial()(*phi._raw_eval(*Q.xy()), 1) + 0 + + No checking is performed:: + + sage: E.defining_polynomial()(123, 456, 1) + 50907 + sage: phi._raw_eval(123, 456) + (3805, 29941) + """ + if y is None: + h0 = self._h0(x) + h1 = self._h1(x) + else: + h0, h0d = self._h0(x, derivative=True) + h1, h1d = self._h1(x, derivative=True) + +# assert h0 == prod(x - ( i*self._P).xy()[0] for i in range(1,self._P.order(),2)) +# assert h1 == prod(x - (self._Q+i*self._P).xy()[0] for i in range( self._P.order() )) + + if not h0: + return () + + xx = h1 / h0**2 + + if y is None: + return xx + +# assert h0d == sum(prod(x - ( i*self._P).xy()[0] for i in range(1,self._P.order(),2) if i!=j) for j in range(1,self._P.order(),2)) +# assert h1d == sum(prod(x - (self._Q+i*self._P).xy()[0] for i in range( self._P.order() ) if i!=j) for j in range( self._P.order() )) + + yy = y * (h1d - 2 * h1 / h0 * h0d) / h0**2 + + return xx, yy + + def _compute_codomain(self, model=None): + r""" + Helper method to compute the codomain of this Îlu isogeny + once the data for :meth:`_raw_eval` has been initialized. + + Called by the constructor. + + INPUT: + + - ``model`` -- string (optional); input to + :meth:`~sage.schemes.elliptic_curves.ell_field.compute_model` + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.hom_velusqrt import EllipticCurveHom_velusqrt + sage: E = EllipticCurve(GF(71), [0,5,0,1,0]) + sage: P = E(4, 19) + sage: phi = EllipticCurveHom_velusqrt(E, P) + sage: phi._raw_codomain + Elliptic Curve defined by y^2 = x^3 + ... over Finite Field of size 71 + sage: phi._codomain + Elliptic Curve defined by y^2 = x^3 + 8*x + 34 over Finite Field of size 71 + sage: phi.codomain() + Elliptic Curve defined by y^2 = x^3 + 8*x + 34 over Finite Field of size 71 + + Passing a ``model`` parameter is supported:: + + sage: phi._compute_codomain('montgomery') + sage: phi + Elliptic-curve isogeny (using Îlu) of degree 19: + From: Elliptic Curve defined by y^2 = x^3 + 5*x^2 + x over Finite Field of size 71 + To: Elliptic Curve defined by y^2 = x^3 + 40*x^2 + x over Finite Field of size 71 + + TESTS:: + + sage: F. = GF(5^2) + sage: E = EllipticCurve([3*t, 2*t+4, 3*t+2, t+4, 3*t]) + sage: K = E(3*t, 2) + sage: EllipticCurveHom_velusqrt(E, K) # indirect doctest + Elliptic-curve isogeny (using Îlu) of degree 19: + From: Elliptic Curve defined by y^2 + 3*t*x*y + (3*t+2)*y = x^3 + (2*t+4)*x^2 + (t+4)*x + 3*t over Finite Field in t of size 5^2 + To: Elliptic Curve defined by y^2 = x^3 + (4*t+3)*x + 2 over Finite Field in t of size 5^2 + """ + R, Z = self._base_ring['Z'].objgen() + poly = self._raw_domain.two_division_polynomial().monic()(Z) + + f = 1 + for g,_ in poly.factor(): + if g.degree() == 1: + f *= Z - self._raw_eval(-g[0]) + else: + K, X0 = self._base_ring.extension(g,'T').objgen() + imX0 = self._raw_eval(X0) + try: + imX0 = imX0.polynomial() # K is a FiniteField + except AttributeError: + imX0 = imX0.lift() # K is a PolynomialQuotientRing + V = R['V'].gen() + f *= (Z - imX0(V)).resultant(g(V)) + + a6,a4,a2,_ = f.monic().list() + + self._raw_codomain = EllipticCurve(self._domain.base_ring(), [0,a2,0,a4,a6]) + + if model is None: + model = 'short_weierstrass' + + from sage.schemes.elliptic_curves.ell_field import compute_model + self._codomain = compute_model(self._raw_codomain, model) + self._post_iso = self._raw_codomain.isomorphism_to(self._codomain) + + def _eval(self, P): + r""" + Evaluate this Îlu isogeny at a point. + + INPUT: + + - ``P`` -- point on the domain, defined over any algebra over the base field + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.hom_velusqrt import EllipticCurveHom_velusqrt + sage: E = EllipticCurve(GF(71), [0,5,0,1,0]) + sage: K = E(4, 19) + sage: phi = EllipticCurveHom_velusqrt(E, K, model='montgomery') + sage: phi + Elliptic-curve isogeny (using Îlu) of degree 19: + From: Elliptic Curve defined by y^2 = x^3 + 5*x^2 + x over Finite Field of size 71 + To: Elliptic Curve defined by y^2 = x^3 + 40*x^2 + x over Finite Field of size 71 + sage: phi(K) + (0 : 1 : 0) + sage: phi(5*K) + (0 : 1 : 0) + sage: phi(E(0)) + (0 : 1 : 0) + sage: phi(E(0,0)) + (0 : 0 : 1) + sage: phi(E(7,13)) + (70 : 31 : 1) + + TESTS:: + + sage: P,Q = (E.random_point() for _ in 'PQ') + sage: assert phi(P) in phi.codomain() + sage: assert phi(Q) in phi.codomain() + sage: assert phi(P + Q) == phi(P) + phi(Q) + + Randomized testing:: + + sage: from sage.schemes.elliptic_curves.hom_velusqrt import EllipticCurveHom_velusqrt + sage: from sage.schemes.elliptic_curves.hom_velusqrt import _random_example_for_testing + sage: E, K = _random_example_for_testing() + sage: phi = EllipticCurveHom_velusqrt(E, K) + sage: phi.degree() == K.order() + True + sage: P = E.random_point() + sage: phi(P) in phi.codomain() + True + sage: Q = E.random_point() + sage: phi(Q) in phi.codomain() + True + sage: phi(P + Q) == phi(P) + phi(Q) + True + """ + if self._domain.defining_polynomial()(*P): + raise ValueError(f'{P} not on {self._domain}') + + k = Sequence(P).universe() + + if not P: + return self._codomain(0).change_ring(k) + + P = self._pre_iso._eval(P) + + xy = self._raw_eval(*P.xy()) + + if xy == (): + return self._codomain(0).change_ring(k) + + return self._post_iso._eval(Sequence(xy, k) + [1]) + + _call_ = _eval + + def _repr_(self): + r""" + Return basic information about this Îlu isogeny as a string. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.hom_velusqrt import EllipticCurveHom_velusqrt + sage: E.

= EllipticCurve(GF(71), [5,5]) + sage: phi = EllipticCurveHom_velusqrt(E, P) + sage: phi # indirect doctest + Elliptic-curve isogeny (using √élu) of degree 57: + From: Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field of size 71 + To: Elliptic Curve defined by y^2 = x^3 + 19*x + 45 over Finite Field of size 71 + """ + return f'Elliptic-curve isogeny (using √élu) of degree {self._degree}:' \ + f'\n From: {self._domain}' \ + f'\n To: {self._codomain}' + + @staticmethod + def _comparison_impl(left, right, op): + r""" + Compare a √élu isogeny to another elliptic-curve morphism. + + Called by :meth:`EllipticCurveHom._richcmp_`. + + INPUT: + + - ``left, right`` -- :class:`~sage.schemes.elliptic_curves.hom.EllipticCurveHom` objects + + ALGORITHM: + + :func:`~sage.schemes.elliptic_curves.hom.compare_via_evaluation` + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.hom_velusqrt import EllipticCurveHom_velusqrt + sage: E = EllipticCurve(GF(101), [5,5,5,5,5]) + sage: phi = EllipticCurveHom_velusqrt(E, E.lift_x(11)); phi + Elliptic-curve isogeny (using √élu) of degree 59: + From: Elliptic Curve defined by y^2 + 5*x*y + 5*y = x^3 + 5*x^2 + 5*x + 5 over Finite Field of size 101 + To: Elliptic Curve defined by y^2 = x^3 + 15*x + 25 over Finite Field of size 101 + sage: psi = EllipticCurveHom_velusqrt(E, E.lift_x(-1)); psi + Elliptic-curve isogeny (using √élu) of degree 59: + From: Elliptic Curve defined by y^2 + 5*x*y + 5*y = x^3 + 5*x^2 + 5*x + 5 over Finite Field of size 101 + To: Elliptic Curve defined by y^2 = x^3 + 15*x + 25 over Finite Field of size 101 + sage: phi == psi + True + """ + if op != op_EQ: + return NotImplemented + return compare_via_evaluation(left, right) + + +def _random_example_for_testing(): + r""" + Function to generate somewhat random valid √élu inputs + for testing purposes. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.hom_velusqrt import _random_example_for_testing + sage: E, K = _random_example_for_testing() + sage: E # random + Elliptic Curve defined by y^2 + (t^3+6*t^2)*x*y + (t^3+3*t^2+2*t+2)*y = x^3 + (6*t^3+2*t^2+t)*x^2 + (3*t^3+2*t^2+6*t+1)*x + (t^3+2*t^2+2) over Finite Field in t of size 7^4 + sage: E.short_weierstrass_model() + Elliptic Curve defined by y^2 = x^3 + ... over Finite Field ... + sage: K # random + (3*t^3 + 4*t^2 + 4*t + 3 : 6*t^3 + 5*t^2 + 5*t : 1) + sage: K.order() # random + 101 + sage: K in E + True + sage: K.order() % 2 + 1 + sage: 5 <= K.order() + True + """ + from sage.all import prime_range, choice, randrange, GF, gcd + while True: + p = choice(prime_range(2, 100)) + e = randrange(1,5) + F,t = GF((p,e),'t').objgen() + try: + E = EllipticCurve([F.random_element() for _ in range(5)]) + except ArithmeticError: + continue + try: + E.short_weierstrass_model() + except ValueError: + continue + if E.cardinality() < 9: + continue + A = E.abelian_group() + ds = max(A.invariants()).prime_to_m_part(2).divisors() + ds = [d for d in ds if 9 <= d < 1000] + if ds: + deg = choice(ds) + break + G = A.torsion_subgroup(deg) + while True: + v = [randrange(deg) for _ in range(G.ngens())] + if gcd([deg] + v) == 1: + break + K = G(v).element() + assert K.order() == deg + return E, K + diff --git a/src/sage/schemes/elliptic_curves/isogeny_class.py b/src/sage/schemes/elliptic_curves/isogeny_class.py index a5eb209bc69..bb5ae25a56e 100644 --- a/src/sage/schemes/elliptic_curves/isogeny_class.py +++ b/src/sage/schemes/elliptic_curves/isogeny_class.py @@ -403,7 +403,7 @@ def graph(self): from sage.graphs.graph import Graph if not self.E.base_field() is QQ: - M = self.matrix(fill = False) + M = self.matrix(fill=False) n = len(self) G = Graph(M, format='weighted_adjacency_matrix') D = dict([(v,self.curves[v]) for v in G.vertices(sort=False)]) @@ -416,11 +416,10 @@ def graph(self): G.relabel(list(range(1, n + 1))) return G - - M = self.matrix(fill = False) + M = self.matrix(fill=False) n = M.nrows() # = M.ncols() G = Graph(M, format='weighted_adjacency_matrix') - N = self.matrix(fill = True) + N = self.matrix(fill=True) D = dict([(v,self.curves[v]) for v in G.vertices(sort=False)]) # The maximum degree classifies the shape of the isogeny # graph, though the number of vertices is often enough. @@ -535,7 +534,8 @@ def reorder(self, order): return self if isinstance(order, str): if order == "lmfdb": - reordered_curves = sorted(self.curves, key = lambda E: E.a_invariants()) + reordered_curves = sorted(self.curves, + key=lambda E: E.a_invariants()) else: reordered_curves = list(self.E.isogeny_class(algorithm=order)) elif isinstance(order, (list, tuple, IsogenyClass_EC)): @@ -1068,7 +1068,8 @@ def _compute(self): raise RuntimeError("unable to find %s in the database" % self.E) # All curves will have the same conductor and isogeny class, # and there are most 8 of them, so lexicographic sorting is okay. - self.curves = tuple(sorted(curves, key = lambda E: E.cremona_label())) + self.curves = tuple(sorted(curves, + key=lambda E: E.cremona_label())) self._mat = None elif algorithm == "sage": curves = [self.E.minimal_model()] diff --git a/src/sage/schemes/elliptic_curves/mod_sym_num.pyx b/src/sage/schemes/elliptic_curves/mod_sym_num.pyx index dd452869e2e..ea646b7fbe3 100644 --- a/src/sage/schemes/elliptic_curves/mod_sym_num.pyx +++ b/src/sage/schemes/elliptic_curves/mod_sym_num.pyx @@ -970,7 +970,7 @@ cdef class ModularSymbolNumerical: ans = self._evaluate_approx(ra, eps) if prec > self._om1.parent().prec(): - L = self._E.period_lattice().basis(prec = prec) + L = self._E.period_lattice().basis(prec=prec) self._om1 = L[0] self._om2 = L[1].imag() cinf = self._E.real_components() @@ -1168,7 +1168,7 @@ cdef class ModularSymbolNumerical: # have to make sure that when twisting by a # prime ell, the twisted curve does not have # additive reduction. Otherwise, unitary - # cusps will become non-movalble. + # cusps will become non-movable. if D != 1: Nt = Et.conductor() for ell in D.prime_divisors(): @@ -2156,10 +2156,8 @@ cdef class ModularSymbolNumerical: ans = su return CC(ans) - - def _from_r_to_rr_approx(self, Rational r, Rational rr, double eps, - method = None, int use_partials=2): + method=None, int use_partials=2): r""" Given a cusp `r` this computes the integral `\lambda(r\to r')` from `r` to `r'` to the given precision ``eps``. @@ -2313,7 +2311,7 @@ cdef class ModularSymbolNumerical: if method == "indirect" or method == "both": verbose(" using the indirect integration from %s to %s " - "with %s terms to sum"%(r, rr, T1+T2), level =2) + "with %s terms to sum"%(r, rr, T1+T2), level=2) #self.nc_indirect += 1 ans2 = ( self._from_ioo_to_r_approx(r, eps/2, use_partials=use_partials) @@ -2474,7 +2472,7 @@ cdef class ModularSymbolNumerical: # (key=lambda r,sign,use_partials:(r,sign)) lead to a compiler crash @cached_method - def _value_ioo_to_r(self, Rational r, int sign = 0, + def _value_ioo_to_r(self, Rational r, int sign=0, int use_partials=2): r""" Return `[r]^+` or `[r]^-` for a rational `r`. @@ -2532,7 +2530,7 @@ cdef class ModularSymbolNumerical: return self._round(lap, sign, True) @cached_method - def _value_r_to_rr(self, Rational r, Rational rr, int sign = 0, + def _value_r_to_rr(self, Rational r, Rational rr, int sign=0, int use_partials=2): r""" Return the rational number `[r']^+ - [r]^+`. However the @@ -2603,7 +2601,7 @@ cdef class ModularSymbolNumerical: return self._round(lap, sign, True) @cached_method - def transportable_symbol(self, Rational r, Rational rr, int sign = 0): + def transportable_symbol(self, Rational r, Rational rr, int sign=0): r""" Return the symbol `[r']^+ - [r]^+` where `r'=\gamma(r)` for some `\gamma\in\Gamma_0(N)`. These symbols can be computed by transporting @@ -2860,8 +2858,7 @@ cdef class ModularSymbolNumerical: res -= self._value_ioo_to_r(rr,sign, use_partials=2) return res - - def manin_symbol(self, llong u, llong v, int sign = 0): + def manin_symbol(self, llong u, llong v, int sign=0): r""" Given a pair `(u,v)` presenting a point in `\mathbb{P}^1(\mathbb{Z}/N\mathbb{Z})` and hence a coset of @@ -3755,7 +3752,7 @@ def _test_against_table(range_of_conductors, other_implementation="sage", list_o Mr = M(r) M2r = M(r, sign=-1) if verb: - print("r={} : ({},{}),({}, {})".format(r,mr,m2r,Mr,M2r), end= " ", flush=True) + print("r={} : ({},{}),({}, {})".format(r,mr,m2r,Mr,M2r), end=" ", flush=True) if mr != Mr or m2r != M2r: print (("B u g : curve = {}, cusp = {}, sage's symbols" + "({},{}), our symbols ({}, {})").format(C.label(), r, diff --git a/src/sage/schemes/elliptic_curves/padic_lseries.py b/src/sage/schemes/elliptic_curves/padic_lseries.py index 3910da83476..f8e16b3407a 100644 --- a/src/sage/schemes/elliptic_curves/padic_lseries.py +++ b/src/sage/schemes/elliptic_curves/padic_lseries.py @@ -143,7 +143,7 @@ class pAdicLseries(SageObject): sage: lp == loads(dumps(lp)) True """ - def __init__(self, E, p, implementation = 'eclib', normalize='L_ratio'): + def __init__(self, E, p, implementation='eclib', normalize='L_ratio'): r""" INPUT: @@ -337,7 +337,7 @@ def modular_symbol(self, r, sign=+1, quadratic_twist=+1): return -sum([kronecker_symbol(D, u) * m(r + ZZ(u) / D) for u in range(1, -D)]) - def measure(self, a, n, prec, quadratic_twist=+1, sign = +1): + def measure(self, a, n, prec, quadratic_twist=+1, sign=+1): r""" Return the measure on `\ZZ_p^{\times}` defined by @@ -1550,9 +1550,9 @@ def __phi_bpr(self, prec=0): print("Warning: Very large value for the precision.") if prec == 0: prec = floor((log(10000)/log(p))) - verbose("prec set to %s"%prec) + verbose("prec set to %s" % prec) eh = E.formal() - om = eh.differential(prec = p**prec+3) + om = eh.differential(prec=p**prec+3) verbose("differential computed") xt = eh.x(prec=p**prec + 3) et = xt*om diff --git a/src/sage/schemes/elliptic_curves/padics.py b/src/sage/schemes/elliptic_curves/padics.py index 509d94e9e33..d4b38156669 100644 --- a/src/sage/schemes/elliptic_curves/padics.py +++ b/src/sage/schemes/elliptic_curves/padics.py @@ -98,8 +98,10 @@ def _normalize_padic_lseries(self, p, normalize, implementation, precision): raise ValueError("Implementation should be one of 'sage', 'eclib', 'num' or 'pollackstevens'") return (p, normalize, implementation, precision) + @cached_method(key=_normalize_padic_lseries) -def padic_lseries(self, p, normalize = None, implementation = 'eclib', precision = None): +def padic_lseries(self, p, normalize=None, implementation='eclib', + precision=None): r""" Return the `p`-adic `L`-series of self at `p`, which is an object whose approx method computes @@ -209,16 +211,16 @@ def padic_lseries(self, p, normalize = None, implementation = 'eclib', precision if implementation in ['sage', 'eclib', 'num']: if self.ap(p) % p != 0: Lp = plseries.pAdicLseriesOrdinary(self, p, - normalize = normalize, implementation = implementation) + normalize=normalize, implementation=implementation) else: Lp = plseries.pAdicLseriesSupersingular(self, p, - normalize = normalize, implementation = implementation) + normalize=normalize, implementation=implementation) else: phi = self.pollack_stevens_modular_symbol(sign=0) if phi.parent().level() % p == 0: - Phi = phi.lift(p, precision, eigensymbol = True) + Phi = phi.lift(p, precision, eigensymbol=True) else: - Phi = phi.p_stabilize_and_lift(p, precision, eigensymbol = True) + Phi = phi.p_stabilize_and_lift(p, precision, eigensymbol=True) Lp = Phi.padic_lseries() #mm TODO should this pass precision on too ? Lp._cinf = self.real_components() return Lp diff --git a/src/sage/schemes/elliptic_curves/period_lattice.py b/src/sage/schemes/elliptic_curves/period_lattice.py index 704fe7e4d0b..85c76603092 100644 --- a/src/sage/schemes/elliptic_curves/period_lattice.py +++ b/src/sage/schemes/elliptic_curves/period_lattice.py @@ -802,7 +802,7 @@ def is_rectangular(self): return self.real_flag == +1 raise RuntimeError("Not defined for non-real lattices.") - def real_period(self, prec = None, algorithm='sage'): + def real_period(self, prec=None, algorithm='sage'): """ Return the real period of this period lattice. @@ -840,8 +840,9 @@ def real_period(self, prec = None, algorithm='sage'): return self.basis(prec,algorithm)[0] raise RuntimeError("Not defined for non-real lattices.") - def omega(self, prec = None, bsd_normalise = False): - r"""Return the real or complex volume of this period lattice. + def omega(self, prec=None, bsd_normalise=False): + r""" + Return the real or complex volume of this period lattice. INPUT: @@ -974,7 +975,7 @@ def basis_matrix(self, prec=None, normalised=False): [ 2.49021256085505 0.000000000000000] [0.000000000000000 -1.97173770155165] """ - from sage.matrix.all import Matrix + from sage.matrix.constructor import Matrix if normalised: return Matrix([list(w) for w in self.normalised_basis(prec)]) @@ -1014,7 +1015,7 @@ def complex_area(self, prec=None): w1,w2 = self.basis(prec) return (w1*w2.conjugate()).imag().abs() - def sigma(self, z, prec = None, flag=0): + def sigma(self, z, prec=None, flag=0): r""" Return the value of the Weierstrass sigma function for this elliptic curve period lattice. @@ -1177,7 +1178,7 @@ def coordinates(self, z, rounding=None): except TypeError: raise TypeError("%s is not a complex number"%z) prec = C.precision() - from sage.matrix.all import Matrix + from sage.matrix.constructor import Matrix from sage.modules.free_module_element import vector if self.real_flag: w1,w2 = self.basis(prec) diff --git a/src/sage/schemes/elliptic_curves/saturation.py b/src/sage/schemes/elliptic_curves/saturation.py index 07b4738c079..9f5b59ac404 100644 --- a/src/sage/schemes/elliptic_curves/saturation.py +++ b/src/sage/schemes/elliptic_curves/saturation.py @@ -674,10 +674,10 @@ def p_projections(Eq, Plist, p, debug=False): if debug: print("Cyclic case, taking dlogs to base {} of order {}".format(g,pp)) # logs are well-defined mod pp, hence mod p - v = [dlog(pt, g, ord = pp, operation = '+') for pt in pts] + v = [dlog(pt, g, ord=pp, operation='+') for pt in pts] if debug: print("dlogs: {}".format(v)) - return [vector(Fp,v)] + return [vector(Fp, v)] # We make no assumption about which generator order divides the # other, since conventions differ! @@ -693,12 +693,14 @@ def p_projections(Eq, Plist, p, debug=False): # roots of unity with p1|p2, together with discrete log in the # multiplicative group. - zeta = g1.weil_pairing(g2,p2) # a primitive p1'th root of unity + zeta = g1.weil_pairing(g2, p2) # a primitive p1'th root of unity if debug: print("wp of gens = {} with order {}".format(zeta, zeta.multiplicative_order())) - assert zeta.multiplicative_order() == p1, "Weil pairing error during saturation: p={}, G={}, Plist={}".format(p,G,Plist) + assert zeta.multiplicative_order() == p1, "Weil pairing error during saturation: p={}, G={}, Plist={}".format(p, G, Plist) # logs are well-defined mod p1, hence mod p - return [vector(Fp, [dlog(pt.weil_pairing(g1,p2), zeta, ord = p1, operation = '*') for pt in pts]), - vector(Fp, [dlog(pt.weil_pairing(g2,p2), zeta, ord = p1, operation = '*') for pt in pts])] + return [vector(Fp, [dlog(pt.weil_pairing(g1, p2), zeta, + ord=p1, operation='*') for pt in pts]), + vector(Fp, [dlog(pt.weil_pairing(g2, p2), zeta, + ord=p1, operation='*') for pt in pts])] diff --git a/src/sage/schemes/elliptic_curves/weierstrass_morphism.py b/src/sage/schemes/elliptic_curves/weierstrass_morphism.py index 13617317e4b..33549debee1 100644 --- a/src/sage/schemes/elliptic_curves/weierstrass_morphism.py +++ b/src/sage/schemes/elliptic_curves/weierstrass_morphism.py @@ -534,9 +534,12 @@ def __init__(self, E=None, urst=None, F=None): self._degree = Integer(1) EllipticCurveHom.__init__(self, self._domain, self._codomain) - def _richcmp_(self, other, op): + @staticmethod + def _comparison_impl(left, right, op): r""" - Standard comparison function for the WeierstrassIsomorphism class. + Compare an isomorphism to another elliptic-curve morphism. + + Called by :meth:`EllipticCurveHom._richcmp_`. EXAMPLES:: @@ -562,20 +565,20 @@ def _richcmp_(self, other, op): sage: a == c True """ - if isinstance(other, WeierstrassIsomorphism): - lx = self._domain - rx = other._domain - if lx != rx: - return richcmp_not_equal(lx, rx, op) + if not isinstance(left, WeierstrassIsomorphism) or not isinstance(right, WeierstrassIsomorphism): + return NotImplemented - lx = self._codomain - rx = other._codomain - if lx != rx: - return richcmp_not_equal(lx, rx, op) + lx = left._domain + rx = right._domain + if lx != rx: + return richcmp_not_equal(lx, rx, op) - return baseWI.__richcmp__(self, other, op) + lx = left._codomain + rx = right._codomain + if lx != rx: + return richcmp_not_equal(lx, rx, op) - return EllipticCurveHom._richcmp_(self, other, op) + return baseWI.__richcmp__(left, right, op) def _eval(self, P): r""" @@ -604,9 +607,9 @@ def _eval(self, P): """ if self._domain.defining_polynomial()(*P): raise ValueError(f'{P} not on {self._domain}') + k = Sequence(P).universe() Q = baseWI.__call__(self, P) - k = Sequence(tuple(P) + tuple(Q)).universe() return self._codomain.base_extend(k).point(Q) def __call__(self, P): diff --git a/src/sage/schemes/generic/algebraic_scheme.py b/src/sage/schemes/generic/algebraic_scheme.py index f0d73d0dcd3..22dbacd8fe0 100644 --- a/src/sage/schemes/generic/algebraic_scheme.py +++ b/src/sage/schemes/generic/algebraic_scheme.py @@ -128,8 +128,9 @@ from sage.categories.number_fields import NumberFields -from sage.rings.all import ZZ, QQbar from sage.rings.ideal import is_Ideal +from sage.rings.integer_ring import ZZ +from sage.rings.qqbar import QQbar from sage.rings.rational_field import is_RationalField from sage.rings.finite_rings.finite_field_constructor import is_FiniteField from sage.rings.number_field.order import is_NumberFieldOrder @@ -142,7 +143,8 @@ from sage.calculus.functions import jacobian -from sage.arith.all import gcd, lcm +from sage.arith.functions import lcm +from sage.arith.misc import gcd import sage.schemes.affine from . import ambient_space diff --git a/src/sage/schemes/generic/ambient_space.py b/src/sage/schemes/generic/ambient_space.py index f6b5f91c686..bcf742c8b25 100644 --- a/src/sage/schemes/generic/ambient_space.py +++ b/src/sage/schemes/generic/ambient_space.py @@ -12,7 +12,9 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -from sage.rings.all import Integer, ZZ, CommutativeRing +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ +from sage.rings.ring import CommutativeRing from sage.schemes.generic.scheme import Scheme diff --git a/src/sage/schemes/generic/homset.py b/src/sage/schemes/generic/homset.py index a1aa946614e..601e80b7f49 100644 --- a/src/sage/schemes/generic/homset.py +++ b/src/sage/schemes/generic/homset.py @@ -105,12 +105,12 @@ class SchemeHomsetFactory(UniqueFactory): TESTS:: sage: Hom.base() - Integer Ring + Rational Field sage: Hom.base_ring() - Integer Ring + Rational Field """ - def create_key_and_extra_args(self, X, Y, category=None, base=ZZ, + def create_key_and_extra_args(self, X, Y, category=None, base=None, check=True, as_point_homset=False): """ Create a key that uniquely determines the Hom-set. @@ -142,17 +142,20 @@ def create_key_and_extra_args(self, X, Y, category=None, base=ZZ, sage: SHOMfactory = SchemeHomsetFactory('test') sage: key, extra = SHOMfactory.create_key_and_extra_args(A3,A2,check=False) sage: key - (..., ..., Category of schemes over Integer Ring, False) + (..., ..., Category of schemes over Rational Field, False) sage: extra {'X': Affine Space of dimension 3 over Rational Field, 'Y': Affine Space of dimension 2 over Rational Field, - 'base_ring': Integer Ring, + 'base_ring': Rational Field, 'check': False} """ if isinstance(X, CommutativeRing): X = AffineScheme(X) if isinstance(Y, CommutativeRing): Y = AffineScheme(Y) + if base is None: + from sage.structure.element import coercion_model + base = coercion_model.common_parent(X.base_ring(), Y.base_ring()) if is_AffineScheme(base): base_spec = base base_ring = base.coordinate_ring() @@ -383,7 +386,7 @@ def _element_constructor_(self, x, check=True): return self.domain()._morphism(self, x, check=check) from sage.categories.map import Map - from sage.categories.all import Rings + from sage.categories.rings import Rings if isinstance(x, Map) and x.category_for().is_subcategory(Rings()): # x is a morphism of Rings return SchemeMorphism_spec(self, x, check=check) diff --git a/src/sage/schemes/generic/morphism.py b/src/sage/schemes/generic/morphism.py index 4fdeb0e84b1..85d2c34db59 100644 --- a/src/sage/schemes/generic/morphism.py +++ b/src/sage/schemes/generic/morphism.py @@ -479,6 +479,48 @@ def is_endomorphism(self) -> bool: """ return self.parent().is_endomorphism_set() + def base_ring(self): + r""" + Return the base ring of ``self``, that is, the ring over which + the defining polynomials of ``self`` are defined. + + OUTPUT: + + - ring + + EXAMPLES:: + + sage: P. = ProjectiveSpace(QQ, 1) + sage: H = Hom(P,P) + sage: f = H([3/5*x^2, 6*y^2]) + sage: f.base_ring() + Rational Field + + :: + + sage: R. = PolynomialRing(ZZ, 1) + sage: P. = ProjectiveSpace(R, 1) + sage: H = Hom(P, P) + sage: f = H([3*x^2, y^2]) + sage: f.base_ring() + Multivariate Polynomial Ring in t over Integer Ring + + Points have correct base rings too (:trac:`34336`):: + + sage: x = P(t, 5); x + (t : 5) + sage: x.base_ring() + Multivariate Polynomial Ring in t over Integer Ring + + :: + + sage: E = EllipticCurve(GF((17,2)), [1,2,3,4,5]) + sage: P = E.random_point() + sage: P.base_ring() + Finite Field in z2 of size 17^2 + """ + return self.domain().base_ring() + def _composition(self, right): """ A helper for multiplying maps by composition. @@ -777,7 +819,7 @@ def __init__(self, parent, phi, check=True): """ SchemeMorphism.__init__(self, parent) if check: - from sage.categories.all import Rings + from sage.categories.rings import Rings if not (isinstance(phi, Map) and phi.category_for().is_subcategory(Rings())): raise TypeError("phi (=%s) must be a ring homomorphism" % phi) if phi.domain() != parent.codomain().coordinate_ring(): @@ -1240,34 +1282,6 @@ def __copy__(self): """ return self.parent()(self._polys) - def base_ring(self): - r""" - Return the base ring of ``self``, that is, the ring over which the coefficients - of ``self`` is given as polynomials. - - OUTPUT: - - - ring - - EXAMPLES:: - - sage: P.=ProjectiveSpace(QQ,1) - sage: H=Hom(P,P) - sage: f=H([3/5*x^2,6*y^2]) - sage: f.base_ring() - Rational Field - - :: - - sage: R.=PolynomialRing(ZZ,1) - sage: P.=ProjectiveSpace(R,1) - sage: H=Hom(P,P) - sage: f=H([3*x^2,y^2]) - sage: f.base_ring() - Multivariate Polynomial Ring in t over Integer Ring - """ - return self.domain().base_ring() - def coordinate_ring(self): r""" Returns the coordinate ring of the ambient projective space diff --git a/src/sage/schemes/generic/scheme.py b/src/sage/schemes/generic/scheme.py index 2793babda4a..31102ff5954 100644 --- a/src/sage/schemes/generic/scheme.py +++ b/src/sage/schemes/generic/scheme.py @@ -21,7 +21,8 @@ from sage.structure.parent import Parent from sage.misc.cachefunc import cached_method -from sage.rings.all import (ZZ, CommutativeRing) +from sage.rings.integer_ring import ZZ +from sage.rings.ring import CommutativeRing from sage.rings.ideal import is_Ideal from sage.structure.unique_representation import UniqueRepresentation @@ -100,7 +101,7 @@ def __init__(self, X=None, category=None): """ from sage.schemes.generic.morphism import is_SchemeMorphism from sage.categories.map import Map - from sage.categories.all import Rings + from sage.categories.rings import Rings if X is None: self._base_ring = ZZ @@ -126,9 +127,9 @@ def __init__(self, X=None, category=None): category = default_category else: assert category.is_subcategory(default_category), \ - "%s is not a subcategory of %s"%(category, default_category) + "%s is not a subcategory of %s" % (category, default_category) - Parent.__init__(self, self.base_ring(), category = category) + Parent.__init__(self, self.base_ring(), category=category) def union(self, X): """ @@ -1055,7 +1056,7 @@ def _an_element_(self): Point on Spectrum of Integer Ring defined by the Principal ideal (811) of Integer Ring """ if self.coordinate_ring() is ZZ: - from sage.arith.all import random_prime + from sage.arith.misc import random_prime return self(ZZ.ideal(random_prime(1000))) return self(self.coordinate_ring().zero_ideal()) @@ -1212,7 +1213,7 @@ def hom(self, x, Y=None): (2, r) """ from sage.categories.map import Map - from sage.categories.all import Rings + from sage.categories.rings import Rings if is_Scheme(x): return self.Hom(x).natural_map() diff --git a/src/sage/schemes/hyperelliptic_curves/constructor.py b/src/sage/schemes/hyperelliptic_curves/constructor.py index f5478623bdf..5aa3ad0abd4 100644 --- a/src/sage/schemes/hyperelliptic_curves/constructor.py +++ b/src/sage/schemes/hyperelliptic_curves/constructor.py @@ -260,14 +260,15 @@ def HyperellipticCurve(f, h=0, names=None, PP=None, check_squarefree=True): if g in genus_classes: superclass.append(genus_classes[g]) - cls_name.append("g%s"%g) + cls_name.append("g%s" % g) - for name,test,cls in fields: + for name, test, cls in fields: if test(R): superclass.append(cls) cls_name.append(name) break class_name = "_".join(cls_name) - cls = dynamic_class(class_name, tuple(superclass), HyperellipticCurve_generic, doccls = HyperellipticCurve) + cls = dynamic_class(class_name, tuple(superclass), + HyperellipticCurve_generic, doccls=HyperellipticCurve) return cls(PP, f, h, names=names, genus=g) diff --git a/src/sage/schemes/hyperelliptic_curves/hypellfrob.pyx b/src/sage/schemes/hyperelliptic_curves/hypellfrob.pyx index 785b7812835..18ad918ac62 100644 --- a/src/sage/schemes/hyperelliptic_curves/hypellfrob.pyx +++ b/src/sage/schemes/hyperelliptic_curves/hypellfrob.pyx @@ -37,7 +37,7 @@ from libcpp.vector cimport vector from sage.libs.ntl.ntl_ZZ_pContext import ZZ_pContext_factory from sage.libs.ntl.all import ZZ, ZZX -from sage.matrix.all import Matrix +from sage.matrix.constructor import Matrix from sage.rings.all import Qp, O as big_oh from sage.arith.all import is_prime diff --git a/src/sage/schemes/hyperelliptic_curves/hyperelliptic_generic.py b/src/sage/schemes/hyperelliptic_curves/hyperelliptic_generic.py index b9d32c897b0..ad3b7d59a53 100644 --- a/src/sage/schemes/hyperelliptic_curves/hyperelliptic_generic.py +++ b/src/sage/schemes/hyperelliptic_curves/hyperelliptic_generic.py @@ -514,7 +514,7 @@ def local_coordinates_at_weierstrass(self, P, prec=20, name='t'): c -= (pol(c) - t2)/pol_prime(c) return (c, t.add_bigoh(prec)) - def local_coordinates_at_infinity(self, prec = 20, name = 't'): + def local_coordinates_at_infinity(self, prec=20, name='t'): """ For the genus `g` hyperelliptic curve `y^2 = f(x)`, return `(x(t), y(t))` such that `(y(t))^2 = f(x(t))`, where `t = x^g/y` is @@ -569,7 +569,7 @@ def local_coordinates_at_infinity(self, prec = 20, name = 't'): y = x**g/t return x+O(t**(prec+2)) , y+O(t**(prec+2)) - def local_coord(self, P, prec = 20, name = 't'): + def local_coord(self, P, prec=20, name='t'): """ Calls the appropriate local_coordinates function diff --git a/src/sage/schemes/hyperelliptic_curves/hyperelliptic_padic_field.py b/src/sage/schemes/hyperelliptic_curves/hyperelliptic_padic_field.py index f69234cdc04..373394b20af 100644 --- a/src/sage/schemes/hyperelliptic_curves/hyperelliptic_padic_field.py +++ b/src/sage/schemes/hyperelliptic_curves/hyperelliptic_padic_field.py @@ -702,9 +702,9 @@ def coleman_integrals_on_basis(self, P, Q, algorithm=None): # MW = monsky_washnitzer.MonskyWashnitzerDifferentialRing(S) # return MW.invariant_differential() - def coleman_integral(self, w, P, Q, algorithm = 'None'): + def coleman_integral(self, w, P, Q, algorithm='None'): r""" - Returns the Coleman integral `\int_P^Q w` + Return the Coleman integral `\int_P^Q w`. INPUT: diff --git a/src/sage/schemes/hyperelliptic_curves/mestre.py b/src/sage/schemes/hyperelliptic_curves/mestre.py index eb0b1b671eb..31ba672800c 100644 --- a/src/sage/schemes/hyperelliptic_curves/mestre.py +++ b/src/sage/schemes/hyperelliptic_curves/mestre.py @@ -24,7 +24,7 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -from sage.matrix.all import Matrix +from sage.matrix.constructor import Matrix from sage.schemes.plane_conics.constructor import Conic from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.schemes.hyperelliptic_curves.constructor import HyperellipticCurve diff --git a/src/sage/schemes/plane_conics/con_field.py b/src/sage/schemes/plane_conics/con_field.py index 3ed2b06e960..05022c45113 100644 --- a/src/sage/schemes/plane_conics/con_field.py +++ b/src/sage/schemes/plane_conics/con_field.py @@ -8,21 +8,21 @@ - Nick Alexander (2008-01-08) """ -#***************************************************************************** -# Copyright (C) 2008 Nick Alexander -# Copyright (C) 2009/2010 Marco Streng +# ***************************************************************************** +# Copyright (C) 2008 Nick Alexander +# Copyright (C) 2009/2010 Marco Streng # -# Distributed under the terms of the GNU General Public License (GPL) +# 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. +# 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 of the GPL is available at: +# The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ -#***************************************************************************** +# http://www.gnu.org/licenses/ +# ***************************************************************************** from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing @@ -35,12 +35,12 @@ from sage.matrix.constructor import Matrix from sage.structure.element import is_Matrix -from sage.schemes.curves.projective_curve import ProjectivePlaneCurve +from sage.schemes.curves.projective_curve import ProjectivePlaneCurve_field from sage.categories.fields import Fields _Fields = Fields() -class ProjectiveConic_field(ProjectivePlaneCurve): +class ProjectiveConic_field(ProjectivePlaneCurve_field): r""" Create a projective plane conic curve over a field. See ``Conic`` for full documentation. @@ -68,7 +68,7 @@ def __init__(self, A, f): sage: c = Conic([1, 1, 1]); c Projective Conic Curve over Rational Field defined by x^2 + y^2 + z^2 """ - ProjectivePlaneCurve.__init__(self, A, f) + super().__init__(A, f) self._coefficients = [f[(2,0,0)], f[(1,1,0)], f[(1,0,1)], f[(0,2,0)], f[(0,1,1)], f[(0,0,2)]] self._parametrization = None @@ -76,9 +76,6 @@ def __init__(self, A, f): self._rational_point = None - - - def _repr_type(self): r""" Returns ``'Projective Conic'``, which is the first part of the @@ -128,7 +125,7 @@ def base_extend(self, S): # if (and only if) there is no point in the cache. pt = con.point(pt) return con - return ProjectivePlaneCurve.base_extend(self, S) + return super().base_extend(S) def cache_point(self, p): r""" @@ -168,7 +165,6 @@ def coefficients(self): """ return self._coefficients - def derivative_matrix(self): r""" Gives the derivative of the defining polynomial of @@ -357,7 +353,7 @@ def diagonalization(self, names=None): names = self.defining_polynomial().parent().variable_names() from .constructor import Conic D, T = self.diagonal_matrix() - con = Conic(D, names = names) + con = Conic(D, names=names) return con, con.hom(T, self), self.hom(T.inverse(), con) def gens(self): @@ -386,8 +382,8 @@ def gens(self): """ return self.coordinate_ring().gens() - def has_rational_point(self, point = False, - algorithm = 'default', read_cache = True): + def has_rational_point(self, point=False, + algorithm='default', read_cache=True): r""" Returns True if and only if the conic ``self`` has a point over its base field `B`. @@ -523,21 +519,21 @@ def has_rational_point(self, point = False, if d == 0: return True, self.point([0,1,0]) return True, self.point([0, ((e**2-4*d*f).sqrt()-e)/(2*d), 1], - check = False) + check=False) return True if isinstance(B, sage.rings.abc.RealField): D, T = self.diagonal_matrix() [a, b, c] = [D[0,0], D[1,1], D[2,2]] if a == 0: - ret = True, self.point(T*vector([1,0,0]), check = False) + ret = True, self.point(T*vector([1,0,0]), check=False) elif a*c <= 0: ret = True, self.point(T*vector([(-c/a).sqrt(),0,1]), - check = False) + check=False) elif b == 0: - ret = True, self.point(T*vector([0,1,0]), check = False) + ret = True, self.point(T*vector([0,1,0]), check=False) elif b*c <= 0: ret = True, self.point(T*vector([0,(-c/b).sqrt(),0,1]), - check = False) + check=False) else: ret = False, None if point: @@ -546,7 +542,7 @@ def has_rational_point(self, point = False, raise NotImplementedError("has_rational_point not implemented for " \ "conics over base field %s" % B) - def has_singular_point(self, point = False): + def has_singular_point(self, point=False): r""" Return True if and only if the conic ``self`` has a rational singular point. @@ -701,8 +697,8 @@ def hom(self, x, Y=None): "map from self (= %s) to Y (= %s)" % \ (x, self, Y)) x = Sequence(x*vector(self.ambient_space().gens())) - return self.Hom(Y)(x, check = False) - return ProjectivePlaneCurve.hom(self, x, Y) + return self.Hom(Y)(x, check=False) + return super().hom(x, Y) def is_diagonal(self): r""" @@ -743,7 +739,6 @@ def is_smooth(self): return self.defining_polynomial()([e, c, b]) != 0 return self.determinant() != 0 - def _magma_init_(self, magma): """ Internal function. Returns a string to initialize this @@ -782,7 +777,6 @@ def _magma_init_(self, magma): magma_coeffs = [coeffs[i]._magma_init_(magma) for i in [0, 3, 5, 1, 4, 2]] return 'Conic([%s|%s])' % (kmn,','.join(magma_coeffs)) - def matrix(self): r""" Returns a matrix `M` such that `(x, y, z) M (x, y, z)^t` @@ -874,10 +868,6 @@ def parametrization(self, point=None, morphism=True): sage: f([1,1]) (0 : 0 : 1) sage: g([0,0,1]) - Traceback (most recent call last): - ... - ValueError: [0, 0] does not define a point in Projective Space of dimension 1 over Finite Field of size 2 since all entries are zero - sage: g.representatives()[1]([0,0,1]) (1 : 1) An example with ``morphism = False`` :: @@ -938,7 +928,7 @@ def parametrization(self, point=None, morphism=True): if not morphism: return par P1 = ProjectiveSpace(self.base_ring(), 1, 'x,y') - return P1.hom(par[0],self), self.Hom(P1)(par[1], check = False) + return P1.hom(par[0],self), self.Hom(P1)(par[1], check=False) def point(self, v, check=True): r""" @@ -963,12 +953,11 @@ def point(self, v, check=True): """ if is_Vector(v): v = Sequence(v) - p = ProjectivePlaneCurve.point(self, v, check=check) + p = super().point(v, check=check) if self._rational_point is None: self._rational_point = p return p - def random_rational_point(self, *args1, **args2): r""" Return a random rational point of the conic ``self``. @@ -1006,8 +995,8 @@ def random_rational_point(self, *args1, **args2): """ if not self.is_smooth(): - raise NotImplementedError("Sorry, random points not implemented " \ - "for non-smooth conics") + raise NotImplementedError("Sorry, random points not implemented " + "for non-smooth conics") par = self.parametrization() x = 0 y = 0 @@ -1015,10 +1004,9 @@ def random_rational_point(self, *args1, **args2): while x == 0 and y == 0: x = B.random_element(*args1, **args2) y = B.random_element(*args1, **args2) - return par[0]([x,y]) + return par[0]([x, y]) - - def rational_point(self, algorithm = 'default', read_cache = True): + def rational_point(self, algorithm='default', read_cache=True): r""" Return a point on ``self`` defined over the base field. @@ -1132,14 +1120,13 @@ def rational_point(self, algorithm = 'default', read_cache = True): ... ValueError: Conic Projective Conic Curve over Real Field with 53 bits of precision defined by x^2 + y^2 + z^2 has no rational points over Real Field with 53 bits of precision! """ - bl,pt = self.has_rational_point(point = True, algorithm = algorithm, - read_cache = read_cache) + bl,pt = self.has_rational_point(point=True, algorithm=algorithm, + read_cache=read_cache) if bl: return pt raise ValueError("Conic %s has no rational points over %s!" % \ (self, self.ambient_space().base_ring())) - def singular_point(self): r""" Returns a singular rational point of ``self`` @@ -1160,7 +1147,7 @@ def singular_point(self): ... ValueError: The conic self (= Projective Conic Curve over Rational Field defined by x^2 + x*y + y^2 + x*z + y*z + z^2) has no rational singular point """ - b = self.has_singular_point(point = True) + b = self.has_singular_point(point=True) if not b[0]: raise ValueError("The conic self (= %s) has no rational " \ "singular point" % self) @@ -1196,7 +1183,6 @@ def symmetric_matrix(self): [ b/2, d , e/2 ], [ c/2, e/2, f ]]) - def upper_triangular_matrix(self): r""" The upper-triangular matrix `M` such that `(x y z) M (x y z)^t` diff --git a/src/sage/schemes/plane_conics/con_number_field.py b/src/sage/schemes/plane_conics/con_number_field.py index 5390c77d804..4d2c55f36d9 100644 --- a/src/sage/schemes/plane_conics/con_number_field.py +++ b/src/sage/schemes/plane_conics/con_number_field.py @@ -369,7 +369,7 @@ def is_locally_solvable(self, p): if ret == -1: if self._local_obstruction is None: from sage.categories.map import Map - from sage.categories.all import Rings + from sage.categories.rings import Rings from sage.rings.qqbar import AA from sage.rings.real_lazy import RLF diff --git a/src/sage/schemes/plane_conics/con_rational_field.py b/src/sage/schemes/plane_conics/con_rational_field.py index 5c35f342c14..9a75336901f 100644 --- a/src/sage/schemes/plane_conics/con_rational_field.py +++ b/src/sage/schemes/plane_conics/con_rational_field.py @@ -190,7 +190,7 @@ def has_rational_point(self, point=False, obstruction=False, read_cache=read_cache) if point or obstruction: from sage.categories.map import Map - from sage.categories.all import Rings + from sage.categories.rings import Rings if isinstance(ret[1], Map) and ret[1].category_for().is_subcategory(Rings()): # ret[1] is a morphism of Rings ret[1] = -1 @@ -222,7 +222,7 @@ def is_locally_solvable(self, p) -> bool: True """ from sage.categories.map import Map - from sage.categories.all import Rings + from sage.categories.rings import Rings D, T = self.diagonal_matrix() abc = [D[j, j] for j in range(3)] diff --git a/src/sage/schemes/product_projective/rational_point.py b/src/sage/schemes/product_projective/rational_point.py index fd9153da37e..b75ec0cde05 100644 --- a/src/sage/schemes/product_projective/rational_point.py +++ b/src/sage/schemes/product_projective/rational_point.py @@ -59,8 +59,9 @@ from sage.schemes.product_projective.space import is_ProductProjectiveSpaces from sage.misc.mrange import xmrange from sage.misc.misc_c import prod -from sage.arith.all import next_prime, previous_prime, crt -from sage.rings.all import ZZ, RR +from sage.arith.misc import next_prime, previous_prime, crt +from sage.rings.integer_ring import ZZ +from sage.rings.real_mpfr import RR from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF from sage.parallel.ncpus import ncpus from sage.parallel.use_fork import p_iter_fork diff --git a/src/sage/schemes/product_projective/space.py b/src/sage/schemes/product_projective/space.py index a7eb86ce43f..7bd6ea88ec1 100644 --- a/src/sage/schemes/product_projective/space.py +++ b/src/sage/schemes/product_projective/space.py @@ -1,14 +1,14 @@ r""" Products of projective spaces -This class builds on the projective space class and its point and morphism classes. +This class builds on the projective space class and its point and morphism +classes. -Products of projective spaces of varying dimension are convenient -ambient spaces for complete intersections. +Products of projective spaces of varying dimension are convenient ambient +spaces for complete intersections. -Group actions on them, and -the interplay with representation theory, provide many interesting -examples of algebraic varieties. +Group actions on them, and the interplay with representation theory, provide +many interesting examples of algebraic varieties. EXAMPLES: @@ -28,6 +28,7 @@ sage: P2xP2.coordinate_ring().inject_variables() Defining x0, x1, x2, y0, y1, y2 """ + #***************************************************************************** # Copyright (C) 2014 Volker Braun # Copyright (C) 2014 Ben Hutz @@ -39,10 +40,12 @@ # http://www.gnu.org/licenses/ #***************************************************************************** - from sage.misc.cachefunc import cached_method from sage.misc.misc_c import prod -from sage.rings.all import (PolynomialRing, QQ, Integer, CommutativeRing) +from sage.rings.integer import Integer +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.rational_field import QQ +from sage.rings.ring import CommutativeRing from sage.rings.finite_rings.finite_field_constructor import is_FiniteField from sage.categories.fields import Fields from sage.rings.polynomial.polydict import ETuple @@ -81,16 +84,17 @@ def ProductProjectiveSpaces(n, R=None, names='x'): r""" Return the Cartesian product of projective spaces. - Can input either a list of projective space over the same base \ - ring or the list of dimensions, the base ring, and the variable names. + The input ``n`` is either a list of projective space over the same base + ring or the list of dimensions, ``R`` the base ring, and ``names`` the + variable names. INPUT: - - ``n`` -- a list of integers or a list of projective spaces. + - ``n`` -- a list of integers or a list of projective spaces - - ``R`` -- a ring. + - ``R`` -- a ring - - ``names`` -- a string or list of strings. + - ``names`` -- a string or list of strings EXAMPLES:: @@ -120,7 +124,7 @@ def ProductProjectiveSpaces(n, R=None, names='x'): if R is None: R = QQ # default is the rationals if isinstance(n[0], ProjectiveSpace_ring): - #this should be a list of projective spaces + # this should be a list of projective spaces names = [] N = [] R = None @@ -146,7 +150,7 @@ def ProductProjectiveSpaces(n, R=None, names='x'): if not isinstance(R, CommutativeRing): raise ValueError("must be a commutative ring") from sage.structure.category_object import normalize_names - n_vars = sum(d+1 for d in n) + n_vars = sum(d + 1 for d in n) if isinstance(names, str): names = normalize_names(n_vars, names) else: @@ -154,7 +158,7 @@ def ProductProjectiveSpaces(n, R=None, names='x'): if len(name_list) == len(n): names = [] for name, dim in zip(name_list, n): - names += normalize_names(dim+1, name) + names += normalize_names(dim + 1, name) else: n_vars = sum(1+d for d in n) names = normalize_names(n_vars, name_list) @@ -190,18 +194,18 @@ class ProductProjectiveSpaces_ring(AmbientSpace): sage: f(Q) (4 : 1 , 1 : 2 : 1) """ - def __init__(self, N, R = QQ, names = None): + def __init__(self, N, R=QQ, names=None): r""" The Python constructor. INPUT: - - ``N`` - a list or tuple of positive integers. + - ``N`` -- a list or tuple of positive integers - - ``R`` - a ring. + - ``R`` -- a ring - - ``names`` - a tuple or list of strings. This must either be a single variable name - or the complete list of variables. + - ``names`` -- a tuple or list of strings; this must either be a single + variable name or the complete list of variables EXAMPLES:: @@ -236,8 +240,9 @@ def __init__(self, N, R = QQ, names = None): for i in range(len(N)): self._components.append(ProjectiveSpace(N[i],R,names[start:start+N[i]+1])) start += N[i]+1 - #Note that the coordinate ring should really be the tensor product of the component - #coordinate rings. But we just deal with them as multihomogeneous polynomial rings + # Note that the coordinate ring should really be the tensor product of + # the component coordinate rings. But we just deal with them as + # multihomogeneous polynomial rings. self._coordinate_ring = PolynomialRing(R,sum(N)+ len(N),names) self._assign_names(names) @@ -245,29 +250,23 @@ def _repr_(self): r""" Return a string representation of this space. - OUTPUT: String. - EXAMPLES:: sage: ProductProjectiveSpaces([1, 1, 1], ZZ, ['x', 'y', 'z', 'u', 'v', 'w']) Product of projective spaces P^1 x P^1 x P^1 over Integer Ring """ - return ''.join([ - 'Product of projective spaces ', - ' x '.join('P^{0}'.format(d) for d in self._dims), - ' over ', - str(self.base_ring())]) + return ''.join(['Product of projective spaces ', + ' x '.join('P^{0}'.format(d) for d in self._dims), + ' over ', str(self.base_ring())]) def _repr_generic_point(self, v=None): """ Return a string representation of the generic point on this product space. - If ``v`` is None, the representation of the generic point of + If ``v`` is ``None``, the representation of the generic point of the product space is returned. - OUTPUT: String. - EXAMPLES:: sage: T = ProductProjectiveSpaces([1, 2, 1], QQ, 'x') @@ -292,9 +291,9 @@ def _latex_(self): {\mathbf P}_{\Bold{Z}}^1 \times {\mathbf P}_{\Bold{Z}}^2 \times {\mathbf P}_{\Bold{Z}}^3 """ - return '%s' % " \\times ".join(PS._latex_() for PS in self) + return " \\times ".join(PS._latex_() for PS in self) - def _latex_generic_point(self, v = None): + def _latex_generic_point(self, v=None): """ Return a LaTeX representation of the generic point on this product space. @@ -318,9 +317,9 @@ def __getitem__(self, i): INPUT: - - ``i`` - a positive integer. + - ``i`` -- a positive integer - OUTPUT: A projective space. + OUTPUT: a projective space EXAMPLES:: @@ -416,7 +415,7 @@ def __mul__(self, right): INPUT: - - ``right`` - a projective space, product of projective spaces, or subscheme. + - ``right`` -- a projective space, product of projective spaces, or subscheme OUTPUT: a product of projective spaces or subscheme @@ -476,7 +475,7 @@ def components(self): r""" Return the components of this product of projective spaces. - OUTPUT: A list of projective spaces. + OUTPUT: a list of projective spaces EXAMPLES:: @@ -491,7 +490,7 @@ def dimension_relative(self): r""" Return the relative dimension of the product of projective spaces. - OUTPUT: A positive integer. + OUTPUT: a positive integer EXAMPLES:: @@ -505,7 +504,7 @@ def dimension_absolute(self): r""" Return the absolute dimension of the product of projective spaces. - OUTPUT: A positive integer. + OUTPUT: a positive integer EXAMPLES:: @@ -526,7 +525,7 @@ def dimension_relative_components(self): r""" Return the relative dimension of the product of projective spaces. - OUTPUT: A list of positive integers. + OUTPUT: a list of positive integers EXAMPLES:: @@ -540,7 +539,7 @@ def dimension_absolute_components(self): r""" Return the absolute dimension of the product of projective spaces. - OUTPUT: A list of positive integers. + OUTPUT: a list of positive integers EXAMPLES:: @@ -561,7 +560,7 @@ def num_components(self): r""" Returns the number of components of this space. - OUTPUT: An integer. + OUTPUT: an integer EXAMPLES:: @@ -578,7 +577,7 @@ def ngens(self): This is the number of variables in the coordinate ring of the projective space. - OUTPUT: An integer. + OUTPUT: an integer EXAMPLES:: @@ -594,9 +593,9 @@ def _factors(self, v): INPUT: - - ``v`` -- a list or tuple. + - ``v`` -- a list or tuple - OUTPUT: A list of lists. + OUTPUT: a list of lists EXAMPLES:: @@ -622,11 +621,9 @@ def _degree(self, polynomial): INPUT: - A polynomial in :meth:`coordinate_ring`. + - ``polynomial`` -- a polynomial in the coordinate_ring - OUTPUT: - - A tuple of integers, one for each projective space component. A + OUTPUT: A tuple of integers, one for each projective space component. A ``ValueError`` is raised if the polynomial is not multihomogeneous. EXAMPLES:: @@ -832,7 +829,7 @@ def subscheme(self, X): INPUT: - - ``X`` - a list or tuple of equations. + - ``X`` -- a list or tuple of equations OUTPUT: @@ -872,7 +869,7 @@ def change_ring(self, R): INPUT: - - ``R`` -- commutative ring or morphism. + - ``R`` -- commutative ring or morphism OUTPUT: @@ -893,7 +890,7 @@ def change_ring(self, R): new_components = [P.change_ring(R) for P in self._components] return ProductProjectiveSpaces(new_components) - def affine_patch(self, I, return_embedding = False): + def affine_patch(self, I, return_embedding=False): r""" Return the `I^{th}` affine patch of this projective space product where ``I`` is a multi-index. @@ -1152,15 +1149,13 @@ def points_of_bounded_height(self, **kwds): INPUT: - - ``bound`` - a real number + - ``bound`` -- a real number - - ``tolerance`` - a rational number in (0,1] used in doyle-krumm algorithm-4 + - ``tolerance`` -- a rational number in (0,1] used in doyle-krumm algorithm-4 - - ``precision`` - the precision to use for computing the elements of bounded height of number fields. - - OUTPUT: + - ``precision`` -- the precision to use for computing the elements of bounded height of number fields. - - an iterator of points in this space + OUTPUT: an iterator of points in this space EXAMPLES:: diff --git a/src/sage/schemes/product_projective/subscheme.py b/src/sage/schemes/product_projective/subscheme.py index 1ac8018f8ab..963feea3d09 100644 --- a/src/sage/schemes/product_projective/subscheme.py +++ b/src/sage/schemes/product_projective/subscheme.py @@ -282,7 +282,7 @@ def is_smooth(self, point=None): """ raise NotImplementedError("Not Implemented") - def affine_patch(self, I, return_embedding = False): + def affine_patch(self, I, return_embedding=False): r""" Return the `I^{th}` affine patch of this projective scheme where 'I' is a multi-index. diff --git a/src/sage/schemes/projective/projective_homset.py b/src/sage/schemes/projective/projective_homset.py index cb6420d80f1..0f7b6abce52 100644 --- a/src/sage/schemes/projective/projective_homset.py +++ b/src/sage/schemes/projective/projective_homset.py @@ -104,7 +104,7 @@ def points(self, **kwds): - a list of rational points of a projective scheme .. WARNING:: - + For numerically inexact fields such as ComplexField or RealField the list of points returned is very likely to be incomplete. It may also contain repeated points due to tolerances. @@ -643,7 +643,7 @@ def base_extend(self, R): Abelian group of points on Elliptic Curve defined by y^2 + y = x^3 - x over Rational Field sage: Hom.base_ring() - Integer Ring + Rational Field sage: Hom.base_extend(QQ) Traceback (most recent call last): ... diff --git a/src/sage/schemes/projective/projective_morphism.py b/src/sage/schemes/projective/projective_morphism.py index ce9ff261fc4..fb3c7d4781b 100644 --- a/src/sage/schemes/projective/projective_morphism.py +++ b/src/sage/schemes/projective/projective_morphism.py @@ -45,6 +45,8 @@ - Kwankyu Lee (2020-02): added indeterminacy_locus() and image() +- Kwankyu Lee (2022-05): added graph(), projective_degrees(), and degree() + """ # **************************************************************************** @@ -59,21 +61,14 @@ # http://www.gnu.org/licenses/ # **************************************************************************** - import sys - from sage.arith.all import gcd, lcm - from sage.interfaces.singular import singular - from sage.misc.misc_c import prod from sage.misc.cachefunc import cached_method from sage.misc.lazy_attribute import lazy_attribute - from sage.ext.fast_callable import fast_callable - from sage.calculus.functions import jacobian - import sage.rings.abc from sage.rings.integer import Integer from sage.rings.algebraic_closure_finite_field import AlgebraicClosureFiniteField_generic @@ -87,9 +82,8 @@ from sage.rings.qqbar import QQbar, number_field_elements_from_algebraics from sage.rings.quotient_ring import QuotientRing_generic from sage.rings.rational_field import QQ - +from sage.modules.free_module_element import vector from sage.schemes.generic.morphism import SchemeMorphism_polynomial - from sage.categories.finite_fields import FiniteFields from sage.categories.number_fields import NumberFields from sage.categories.homset import Hom, End @@ -2249,6 +2243,43 @@ class SchemeMorphism_polynomial_projective_subscheme_field(SchemeMorphism_polyno """ Morphisms from subschemes of projective spaces defined over fields. """ + def __call__(self, x): + """ + Apply this morphism to the point ``x``. + + INPUT: + + - ``x`` -- a point in the domain of definition + + OUTPUT: the image of the point ``x`` under the morphism + + TESTS:: + + sage: R. = QQ[] + sage: C = Curve(7*x^2 + 2*y*z + z^2) + sage: f, g = C.parametrization() + sage: g([0, -1, 2]) + (1 : 0) + sage: f([1, 0]) + (0 : -1/2 : 1) + sage: _ == C([0, -1, 2]) + True + """ + try: + reprs = self.representatives() + except NotImplementedError: # Singular does not support the base field + try: + return super(SchemeMorphism_polynomial_projective_subscheme_field, self).__call__(x) + except ValueError: + raise ValueError('cannot apply the morphism to this point') + + for m in reprs: + try: + return super(SchemeMorphism_polynomial_projective_subscheme_field, m).__call__(x) + except ValueError: + pass + raise ValueError('the morphism is not defined at this point') + @cached_method def representatives(self): """ @@ -2353,13 +2384,13 @@ def representatives(self): reprs.append(hom([f / f0 for f in r[1:]])) return reprs - if not X.is_irreducible(): - raise ValueError("domain is not an irreducible scheme") - if not (X.base_ring() in _NumberFields or X.base_ring() in _FiniteFields): raise NotImplementedError("base ring {} is not supported by Singular".format(X.base_ring())) + if not X.is_irreducible(): + raise ValueError("domain is not an irreducible scheme") + # prepare homogeneous coordinate ring of X in Singular from sage.rings.polynomial.term_order import TermOrder T = TermOrder('degrevlex') @@ -2540,7 +2571,7 @@ def image(self): if not Y.is_projective(): e = Y.projective_embedding(0) - return (e*self).image().affine_patch(0, Y.ambient_space()) + return (e * self).image().affine_patch(0, Y.ambient_space()) k = self.base_ring() @@ -2565,3 +2596,124 @@ def image(self): gens = [g.subs(dict(zip(R.gens()[n:],T.gens()))) for g in j] return AY.subscheme(gens) + + @cached_method + def graph(self): + """ + Return the graph of this morphism. + + The graph is a subscheme of the product of the ambient spaces of the + domain and the codomain. If the ambient space of the codomain is an + affine space, it is first embedded into a projective space. + + EXAMPLES: + + We get the standard quadratic curve as the graph of a quadratic function + of an affine line. :: + + sage: A1. = AffineSpace(1, QQ) + sage: X = A1.subscheme(0) # affine line + sage: phi = X.hom([x^2], A1) + sage: mor = phi.homogenize(0) + sage: G = mor.graph(); G + Closed subscheme of Product of projective spaces P^1 x P^1 over Rational Field defined by: + x1^2*x2 - x0^2*x3 + sage: G.affine_patch([0, 0]) + Closed subscheme of Affine Space of dimension 2 over Rational Field defined by: + x0^2 - x1 + """ + X = self.domain() + Y = self.codomain() + + if not Y.is_projective(): + e = Y.projective_embedding(0) + return (e * self).graph() + + AX = X.ambient_space() + AY = Y.ambient_space() + + n = AX.dimension() + m = AY.dimension() + + if any(v in AX.variable_names() for v in AY.variable_names()): + from sage.schemes.product_projective.space import ProductProjectiveSpaces + AXY = ProductProjectiveSpaces([n, m], self.base_ring()) + else: + AXY = AX * AY # product of projective spaces + + R = AXY.coordinate_ring() + F = [R(f) for f in self.defining_polynomials()] + g = R.gens() + + # Suppose R = k[x_0, ..., x_n, y_0, ..., y_m]. Then the bihomogeneous + # ideal of the graph is + # + # I + (y_iF_j - y_jF_i : 0 <= i, j <= m) + # + # saturated with respect to (F_0, F_1, ..., F_m). + n1 = n + 1 + m1 = m + 1 + I = X.defining_ideal().change_ring(R) + h = [g[n1 + i] * F[j] - g[n1 + j] * F[i] for i in range(m1) for j in range(i + 1, m1)] + J, _ = (I + R.ideal(h)).saturation(R.ideal(F)) + + return AXY.subscheme(J) + + @cached_method + def projective_degrees(self): + """ + Return the projective degrees of this rational map. + + EXAMPLES:: + + sage: k = GF(11) + sage: E = EllipticCurve(k,[1,1]) + sage: Q = E(6,5) + sage: phi = E.multiplication_by_m_isogeny(2) + sage: mor = phi.as_morphism() + sage: mor.projective_degrees() + (12, 3) + """ + X = self.domain() + Y = self.codomain() + + if not Y.is_projective(): # Y is affine + e = Y.projective_embedding(0) + return (e * self).projective_degrees() + + AX = X.ambient_space() + AY = Y.ambient_space() + xn = AX.ngens() + yn = AY.ngens() + + G = self.graph() + I = G.defining_ideal() # a bihomogeneous ideal + + degrees = xn*[vector([1,0])] + yn*[vector([0,1])] + res = I.graded_free_resolution(degrees=degrees, algorithm='shreyer') + kpoly = res.K_polynomial() + + L = kpoly.parent() + t1, t2 = L.gens() + poly = kpoly.substitute({t1: 1 - t1, t2: 1 - t2}) + + n = AX.dimension() + m = AY.dimension() + k = X.dimension() + return tuple(poly.monomial_coefficient(L.monomial(n - i, m - k + i)) for i in range(k + 1)) + + def degree(self): + """ + Return the degree of this rational map. + + EXAMPLES:: + + sage: k = GF(11) + sage: E = EllipticCurve(k,[1,1]) + sage: Q = E(6,5) + sage: phi = E.multiplication_by_m_isogeny(2) + sage: mor = phi.as_morphism() + sage: mor.degree() + 4 + """ + return self.projective_degrees()[0] // self.image().degree() diff --git a/src/sage/schemes/projective/projective_rational_point.py b/src/sage/schemes/projective/projective_rational_point.py index bc0b33e8dfb..93789be8d65 100644 --- a/src/sage/schemes/projective/projective_rational_point.py +++ b/src/sage/schemes/projective/projective_rational_point.py @@ -56,8 +56,10 @@ #***************************************************************************** -from sage.arith.all import gcd, srange, next_prime, previous_prime, crt -from sage.rings.all import ZZ, RR +from sage.arith.misc import gcd, next_prime, previous_prime, crt +from sage.arith.srange import srange +from sage.rings.integer_ring import ZZ +from sage.rings.real_mpfr import RR from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF from sage.misc.mrange import cartesian_product_iterator from sage.misc.misc_c import prod diff --git a/src/sage/schemes/projective/projective_space.py b/src/sage/schemes/projective/projective_space.py index 90aaf8493bb..ed44ebcd6aa 100644 --- a/src/sage/schemes/projective/projective_space.py +++ b/src/sage/schemes/projective/projective_space.py @@ -79,16 +79,17 @@ # http://www.gnu.org/licenses/ # **************************************************************************** -from sage.arith.all import gcd, binomial, srange -from sage.rings.all import PolynomialRing +from sage.arith.misc import gcd, binomial +from sage.arith.srange import srange + +from sage.rings.finite_rings.finite_field_constructor import is_FiniteField from sage.rings.integer import Integer from sage.rings.integer_ring import ZZ - -from sage.rings.ring import CommutativeRing -from sage.rings.rational_field import is_RationalField from sage.rings.polynomial.multi_polynomial_ring import is_MPolynomialRing from sage.rings.polynomial.polynomial_ring import is_PolynomialRing -from sage.rings.finite_rings.finite_field_constructor import is_FiniteField +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.ring import CommutativeRing +from sage.rings.rational_field import is_RationalField from sage.categories.fields import Fields from sage.categories.number_fields import NumberFields diff --git a/src/sage/schemes/projective/projective_subscheme.py b/src/sage/schemes/projective/projective_subscheme.py index fec6ea3558c..3c0faa498a6 100644 --- a/src/sage/schemes/projective/projective_subscheme.py +++ b/src/sage/schemes/projective/projective_subscheme.py @@ -201,9 +201,10 @@ def dimension(self): self.__dimension = self.defining_ideal().dimension() - 1 return self.__dimension - def affine_patch(self, i, AA = None): + def affine_patch(self, i, AA=None): r""" Return the `i^{th}` affine patch of this projective scheme. + This is the intersection with this `i^{th}` affine patch of its ambient space. @@ -598,7 +599,7 @@ def nth_iterate(self, f, n): raise TypeError("must be a forward orbit") return self.orbit(f,[n,n+1])[0] - def _forward_image(self, f, check = True): + def _forward_image(self, f, check=True): r""" Compute the forward image of this subscheme by the morphism ``f``. @@ -771,11 +772,11 @@ def _forward_image(self, f, check = True): m = CR_codom.ngens() #can't call eliminate if the base ring is polynomial so we do it ourselves #with a lex ordering - R = PolynomialRing(f.base_ring(), n+m, 'tempvar', order = 'lex') + R = PolynomialRing(f.base_ring(), n + m, 'tempvar', order='lex') Rvars = R.gens()[0 : n] - phi = CR_dom.hom(Rvars,R) + phi = CR_dom.hom(Rvars, R) zero = n*[0] - psi = R.hom(zero + list(CR_codom.gens()),CR_codom) + psi = R.hom(zero + list(CR_codom.gens()), CR_codom) #set up ideal L = R.ideal([phi(t) for t in self.defining_polynomials()] + [R.gen(n+i) - phi(f[i]) for i in range(m)]) G = L.groebner_basis() # eliminate diff --git a/src/sage/schemes/riemann_surfaces/riemann_surface.py b/src/sage/schemes/riemann_surfaces/riemann_surface.py index d71f608fb9d..f85a7295dbc 100644 --- a/src/sage/schemes/riemann_surfaces/riemann_surface.py +++ b/src/sage/schemes/riemann_surfaces/riemann_surface.py @@ -17,11 +17,15 @@ period lattice numerically, by determining integer (near) solutions to the relevant approximate linear equations. +One can also calculate the Abel-Jacobi map on the Riemann surface, and there +is basic functionality to interface with divisors of curves to facilitate this. + AUTHORS: - Alexandre Zotine, Nils Bruin (2017-06-10): initial version - Nils Bruin, Jeroen Sijsling (2018-01-05): algebraization, isomorphisms - Linden Disney-Hogg, Nils Bruin (2021-06-23): efficient integration +- Linden Disney-Hogg, Nils Bruin (2022-09-07): Abel-Jacobi map EXAMPLES: @@ -30,7 +34,7 @@ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface sage: R. = QQ[] sage: f = x^4-x^3*y+2*x^3+2*x^2*y+2*x^2-2*x*y^2+4*x*y-y^3+3*y^2+2*y+1 - sage: S = RiemannSurface(f,prec=100) + sage: S = RiemannSurface(f, prec=100) sage: M = S.riemann_matrix() We test the usual properties, i.e., that the period matrix is symmetric and that @@ -55,6 +59,44 @@ sage: all(len(a.minpoly().roots(K)) == a.minpoly().degree() for a in A) True +We can look at an extended example of the Abel-Jacobi functionality. We will +demonstrate a particular half-canonical divisor on Klein's Curve, known in +the literature:: + + sage: f = x^3*y + y^3 + x + sage: S = RiemannSurface(f, integration_method='rigorous') + sage: BL = S.places_at_branch_locus(); BL + [Place (x, y, y^2), + Place (x^7 + 27/4, y + 4/9*x^5, y^2 + 4/3*x^3), + Place (x^7 + 27/4, y - 2/9*x^5, y^2 + 1/3*x^3)] + +We can read off out the output of ``places_at_branch_locus`` to choose our +divisor, and we can calculate the canonical divisor using curve functionality:: + + sage: P0 = 1*BL[0] + sage: from sage.schemes.curves.constructor import Curve + sage: C = Curve(f) + sage: F = C.function_field() + sage: K = (F(x).differential()).divisor() - F(f.derivative(y)).divisor() + sage: Pinf, Pinf_prime = C.places_at_infinity() + sage: if K-3*Pinf-1*Pinf_prime: Pinf, Pinf_prime = (Pinf_prime, Pinf); + sage: D = P0 + 2*Pinf - Pinf_prime + +Note we could check using exact techniques that `2D = K`:: + + sage: Z = K - 2*D + sage: (Z.degree() == 0, len(Z.basis_differential_space()) == S.genus, len(Z.basis_function_space()) == 1) + (True, True, True) + +We can also check this using our Abel-Jacobi functions:: + + sage: avoid = C.places_at_infinity() + sage: Zeq, _ = S.strong_approximation(Z, avoid) + sage: Zlist = S.divisor_to_divisor_list(Zeq) + sage: AJ = S.abel_jacobi(Zlist) # long time (1 second) + sage: S.reduce_over_period_lattice(AJ).norm() < 1e-10 # long time + True + REFERENCES: The initial version of this code was developed alongside [BSZ2019]_. @@ -70,8 +112,10 @@ # **************************************************************************** from scipy.spatial import Voronoi +from sage.arith.functions import lcm from sage.arith.misc import GCD, algdep from sage.ext.fast_callable import fast_callable +from sage.functions.log import lambert_w from sage.graphs.graph import Graph from sage.groups.matrix_gps.finitely_generated import MatrixGroup from sage.groups.perm_gps.permgroup_named import SymmetricGroup @@ -79,15 +123,21 @@ from sage.matrix.special import block_matrix from sage.misc.cachefunc import cached_method from sage.misc.flatten import flatten +from sage.misc.functional import numerical_approx from sage.misc.misc_c import prod from sage.modules.free_module import VectorSpace +from sage.modules.free_module_integer import IntegerLattice from sage.numerical.gauss_legendre import integrate_vector, integrate_vector_N from sage.rings.complex_mpfr import ComplexField, CDF +from sage.rings.function_field.constructor import FunctionField +from sage.rings.function_field.divisor import FunctionFieldDivisor +from sage.rings.infinity import Infinity from sage.rings.integer_ring import ZZ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.qqbar import number_field_elements_from_algebraics from sage.rings.rational_field import QQ from sage.rings.real_mpfr import RealField +from sage.schemes.curves.constructor import Curve import sage.libs.mpmath.all as mpall @@ -182,9 +232,9 @@ def bisect(L, t): if t < L[min][0] or t > L[max][0]: raise ValueError("value for t out of range") # Main loop. - while (min < max-1): + while min < max - 1: # Bisect. - mid = (max+min)//2 + mid = (max + min) // 2 # If it's equal, return the index we bisected to. if t == L[mid][0]: return mid @@ -252,7 +302,7 @@ class ConvergenceError(ValueError): def differential_basis_baker(f): r""" - Compute a differential bases for a curve that is nonsingular outside (1:0:0),(0:1:0),(0:0:1) + Compute a differential basis for a curve that is nonsingular outside (1:0:0),(0:1:0),(0:0:1) Baker's theorem tells us that if a curve has its singularities at the coordinate vertices and meets some further easily tested genericity criteria, @@ -275,10 +325,10 @@ def differential_basis_baker(f): sage: from sage.schemes.riemann_surfaces.riemann_surface import differential_basis_baker sage: R. = QQ[] - sage: f = x^3+y^3+x^5*y^5 + sage: f = x^3 + y^3 + x^5*y^5 sage: differential_basis_baker(f) [y^2, x*y, x*y^2, x^2, x^2*y, x^2*y^2, x^2*y^3, x^3*y^2, x^3*y^3] - sage: f = y^2-(x-3)^2*x + sage: f = y^2 - (x-3)^2*x sage: differential_basis_baker(f) is None True sage: differential_basis_baker(x^2+y^2-1) @@ -308,9 +358,10 @@ def differential_basis_baker(f): if len(B.monomials()) > 1: return None from sage.geometry.polyhedron.constructor import Polyhedron + D = {(k[0], k[1]): v for k, v in f.dict().items()} P = Polyhedron(D) - kT = k['t'] + kT = k["t"] # here we check the additional genericity conditions: that the polynomials # along the edges of the Newton polygon are square-free. for e in P.bounded_edges(): @@ -318,11 +369,116 @@ def differential_basis_baker(f): if not h.is_squarefree(): return None x, y = f.parent().gens() - return [x**(a[0] - 1) * y**(a[1] - 1) for a in P.integral_points() - if P.interior_contains(a)] + return [ + x**(a[0] - 1) * y**(a[1] - 1) + for a in P.integral_points() + if P.interior_contains(a) + ] + + +def find_closest_element(item, lst): + r""" + Return the index of the closest element of a list. + + Given ``List`` and ``item``, return the index of the element ``l`` of ``List`` + which minimises ``(item-l).abs()``. If there are multiple such elements, the + first is returned. + + INPUT: + + - ``item`` -- value to minimize the distance to over the list + + - ``lst`` -- list to look for closest element in + + EXAMPLES:: + + sage: from sage.schemes.riemann_surfaces.riemann_surface import find_closest_element + sage: i = 5 + sage: l = list(range(10)) + sage: i == find_closest_element(i, l) + True + + Note that this method does no checks on the input, but will fail for inputs + where the absolute value or subtraction do not make sense. + """ + dists = [(item - l).abs() for l in lst] + return dists.index(min(dists)) + + +def reparameterize_differential_minpoly(minpoly, z0): + r""" + Rewrites a minimal polynomial to write is around `z_0`. + + Given a minimal polynomial `m(z,g)`, where `g` corresponds to a differential + on the surface (that is, it is represented as a rational function, and + implicitly carries a factor `dz`), we rewrite the minpoly in terms of + variables `\bar{z}, \bar{g}` s.t now `\bar{z}=0 \Leftrightarrow z=z_0`. + + INPUT: + + - ``minpoly`` -- a polynomial in two variables, where the first variables + corresponds to the base coordinate on the Riemann surface + - ``z0`` -- complex number or infinity; the point about which to + reparameterize + + OUTPUT: + + A polynomial in two variables giving the reparameterize minimal polynomial. + + EXAMPLES: + + On the curve given by `w^2 - z^3 + 1 = 0`, we have differential + `\frac{dz}{2w} = \frac{dz}{2\sqrt{z^3-1}}` + with minimal polynomial `g^2(z^3-1) - 1/4=0`. We can make the substitution + `\bar{z}=z^{-1}` to parameterise the differential about `z=\infty` as + + .. MATH:: + + `\frac{-\bar{z}^{-2} d\bar{z}}{2\sqrt{\bar{z}^{-3}-1}} = \frac{-d\bar{z}}{2\sqrt{\bar{z}(1-\bar{z}^3)}}`. + + Hence the transformed differential should have minimal polynomial + `\bar{g}^2 \bar{z} (1 - \bar{z}^3) - 1/4 = 0`, and we can check this:: + + sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface, reparameterize_differential_minpoly + sage: R. = QQ[] + sage: S = RiemannSurface(w^2-z^3+1) + sage: minpoly = S._cohomology_basis_bounding_data[1][0][2] + sage: z0 = Infinity + sage: reparameterize_differential_minpoly(minpoly, z0) + -zbar^4*gbar^2 + zbar*gbar^2 - 1/4 + We can further check that reparameterising about `0` is the identity + operation:: -class RiemannSurface(): + sage: reparameterize_differential_minpoly(minpoly, 0)(*minpoly.parent().gens()) == minpoly + True + + .. NOTE:: + + As part of the routine, when reparameterising about infinity, a + rational function is reduced and then the numerator is taken. Over + an inexact ring this is numerically unstable, and so it is advisable + to only reparameterize about infinity over an exact ring. + """ + P = minpoly.parent() + F = PolynomialRing(P.base_ring(), [str(v) + "bar" for v in P.gens()]) + + try: + Inf = bool(z0 == z0.parent()(Infinity)) + except TypeError: + Inf = False + + if Inf: + F = F.fraction_field() + mt = F(minpoly(F.gen(0)**(-1), -F.gen(0)**2 * F.gen(1))) + mt.reduce() + mt = mt.numerator() + else: + mt = minpoly(F.gen(0) + z0, F.gen(1)) + return mt + + +class RiemannSurface(object): r""" Construct a Riemann Surface. This is specified by the zeroes of a bivariate polynomial with rational coefficients `f(z,w) = 0`. @@ -336,18 +492,19 @@ class RiemannSurface(): - ``prec`` -- the desired precision of computations on the surface in bits (default: 53) - - ``certification`` -- a boolean (default: True) value indicating whether - homotopy continuation is certified or not. Uncertified homotopy - continuation can be faster. + - ``certification`` -- a boolean (default: ``True``) value indicating + whether homotopy continuation is certified or not. Uncertified + homotopy continuation can be faster. - - ``differentials`` -- (default: None). If specified, provides a list of - polynomials `h` such that `h/(df/dw) dz` is a regular differential on the - Riemann surface. This is taken as a basis of the regular differentials, so - the genus is assumed to be equal to the length of this list. The results - from the homology basis computation are checked against this value. - Providing this parameter makes the computation independent from Singular. - For a nonsingular plane curve of degree `d`, an appropriate set is given - by the monomials of degree up to `d-3`. + - ``differentials`` -- (default: ``None``). If specified, provides a list + of polynomials `h` such that `h/(df/dw) dz` is a regular + differential on the Riemann surface. This is taken as a basis of + the regular differentials, so the genus is assumed to be equal + to the length of this list. The results from the homology basis + computation are checked against this value. Providing this + parameter makes the computation independent from Singular. For + a nonsingular plane curve of degree `d`, an appropriate set is + given by the monomials of degree up to `d-3`. - ``integration_method`` -- (default: ``'rigorous'``). String specifying the integration method to use when calculating the integrals of differentials. @@ -378,12 +535,12 @@ class RiemannSurface(): sage: Qt. = QQ[] sage: K. = NumberField(t^2-t+3,embedding=CC(0.5+1.6*I)) sage: R. = K[] - sage: f = y^2+y-(x^3+(1-a)*x^2-(2+a)*x-2) - sage: S = RiemannSurface(f,prec=100,differentials=[1]) + sage: f = y^2 + y - (x^3 + (1-a)*x^2 - (2+a)*x - 2) + sage: S = RiemannSurface(f, prec=100, differentials=[1]) sage: A = S.endomorphism_basis() sage: len(A) 2 - sage: all( len(T.minpoly().roots(K)) > 0 for T in A) + sage: all(len(T.minpoly().roots(K)) > 0 for T in A) True The ``'heuristic'`` integration method uses the method ``integrate_vector`` @@ -409,7 +566,7 @@ class RiemannSurface(): rigorous method, but for slower computations the rigorous method can be much faster:: - sage: f = z*w^3+z^3+w + sage: f = z*w^3 + z^3 + w sage: p = 53 sage: Sh = RiemannSurface(f, prec=p, integration_method='heuristic') sage: Sr = RiemannSurface(f, prec=p, integration_method='rigorous') @@ -426,6 +583,22 @@ class RiemannSurface(): sage: ct2/ct1 # random 1.2429363969691192 + Note that for the above curve, the branch points are evenly distributed, and + hence the implicit assumptions in the heuristic method are more sensible, + meaning that a higher precision is required to see the heuristic method + being significantly slower than the rigorous method. For a worse conditioned + curve, this effect is more pronounced:: + + sage: q = 1 / 10 + sage: f = y^2 - (x^2 - 2*x + 1 + q^2) * (x^2 + 2*x + 1 + q^2) + sage: p = 500 + sage: Sh = RiemannSurface(f, prec=p, integration_method='heuristic') + sage: Sr = RiemannSurface(f, prec=p, integration_method='rigorous') + sage: nodes.cache.clear() + sage: Rh = Sh.riemann_matrix() # long time (8 seconds) + sage: nodes.cache.clear() + sage: Rr = Sr.riemann_matrix() # long time (1 seconds) + This disparity in timings can get increasingly worse, and testing has shown that even for random quadrics the heuristic method can be as bad as 30 times slower. @@ -450,7 +623,15 @@ class RiemannSurface(): sage: tau.algdep(6).degree() == 2 True """ - def __init__(self, f, prec=53, certification=True, differentials=None, integration_method="rigorous"): + + def __init__( + self, + f, + prec=53, + certification=True, + differentials=None, + integration_method="rigorous" + ): r""" TESTS:: @@ -462,18 +643,21 @@ def __init__(self, f, prec=53, certification=True, differentials=None, integrati # Initializations. self._prec = prec self._certification = certification + if not (integration_method == "heuristic" or integration_method == "rigorous"): + raise ValueError("invalid integration method") self._integration_method = integration_method self._R = f.parent() if len(self._R.gens()) != 2: - raise ValueError('only bivariate polynomials supported.') + raise ValueError('only bivariate polynomials supported') if f.degree() <= 1: - raise ValueError('equation must be of degree at least 2.') + raise ValueError('equation must be of degree at least 2') z, w = self._R.gen(0), self._R.gen(1) self._CC = ComplexField(self._prec) self._RR = RealField(self._prec) self._CCz = PolynomialRing(self._CC, [self._R.gen(0)]) self._CCw = PolynomialRing(self._CC, [self._R.gen(1)]) self._RRz = PolynomialRing(self._RR, [self._R.gen(0)]) + self._curve = None self.f = f if differentials is not None: self._differentials = [self._R(a) for a in differentials] @@ -487,7 +671,9 @@ def __init__(self, f, prec=53, certification=True, differentials=None, integrati self._differentials = None self.genus = self._R.ideal(self.f).genus() if self.genus < 0: - raise ValueError("Singular reports negative genus. Specify differentials manually.") + raise ValueError( + "Singular reports negative genus. Specify differentials manually." + ) self.degree = self.f.degree(w) self._dfdw = self.f.derivative(w) self._dfdz = self.f.derivative(z) @@ -495,24 +681,73 @@ def __init__(self, f, prec=53, certification=True, differentials=None, integrati # Coefficients of the polynomial for use in homotopy continuation. self._a0 = self._CCz(self.f.coefficient({w: self.degree})(self._CCz.gen(), 0)) self._a0roots = self._a0.roots(multiplicities=False) - self._aks = [self._CCz(self.f.coefficient({w: self.degree - k - 1}) - (self._CCz.gen(), 0)) for k in range(self.degree)] + self._aks = [ + self._CCz(self.f.coefficient({w: self.degree - k - 1})(self._CCz.gen(), 0)) + for k in range(self.degree) + ] # Compute the branch locus. Takes the square-free part of the discriminant # because of numerical issues. self.branch_locus = [] - for x in self._discriminant.factor(): - self.branch_locus += self._CCz(x[0](self._CCz.gen(), 0)).roots(multiplicities=False) + existing_factors = [x[0] for x in self._discriminant.factor()] + for fac in existing_factors: + self.branch_locus += self._CCz(fac(self._CCz.gen(), 0)).roots( + multiplicities=False + ) + self._f_branch_locus = self.branch_locus + self._cohomology_basis_bounding_data = self._bounding_data( + self.cohomology_basis(), exact=True + ) + RBzg, bounding_data_list = self._cohomology_basis_bounding_data + minpoly_list = [bd[2] for bd in bounding_data_list] + # We now want to calculate the additional branchpoints associated to + # the differentials. + discriminants = [RBzg(self._discriminant(*RBzg.gens()))] + for minpoly in minpoly_list: + F = RBzg(minpoly) + dF = F.derivative(RBzg.gen(1)) + discriminants += [F.resultant(dF, RBzg.gen(1))] + combined_discriminant = lcm(discriminants)(*self._R.gens()) + self._differentials_branch_locus = [] + for x in combined_discriminant.factor(): + if not x[0] in existing_factors: + self._differentials_branch_locus += self._CCz( + x[0](self._CCz.gen(), 0) + ).roots(multiplicities=False) + # We add these branchpoints to the existing. + # self.branch_locus = self.branch_locus+self._differentials_branch_locus + # We now want to also check whether Infinity is a branch point of any + # of the differentials. + # This will be useful when calculating the Abel-Jacobi map. + minpoly_list = [ + reparameterize_differential_minpoly(mp, Infinity) for mp in minpoly_list + ] + discriminants = [] + for minpoly in minpoly_list: + F = RBzg(minpoly) + dF = F.derivative(RBzg.gen(1)) + discriminants += [F.resultant(dF, RBzg.gen(1))] + discriminant_about_infinity = RBzg(lcm(discriminants)) + if discriminant_about_infinity(0, 0) == 0: + self._differentials_branch_locus.append(self._CC(Infinity)) + # Voronoi diagram and the important points associated with it - self.voronoi_diagram = Voronoi(voronoi_ghost(self.branch_locus, - CC=self._CC)) - self._vertices = [self._CC(x0, y0) - for x0, y0 in self.voronoi_diagram.vertices] + self.voronoi_diagram = Voronoi(voronoi_ghost(self.branch_locus, CC=self._CC)) + self._vertices = [self._CC(x0, y0) for x0, y0 in self.voronoi_diagram.vertices] self._wvalues = [self.w_values(z0) for z0 in self._vertices] + # We arbitrarily, but sensibly, set the basepoint to be the rightmost vertex + self._basepoint = ( + self._vertices.index(sorted(self._vertices, key=lambda z: z.real())[-1]), + 0, + ) self._Sn = SymmetricGroup(range(self.degree)) self._L = {} + self._integral_dict = {} self._fastcall_f = fast_callable(f, domain=self._CC) self._fastcall_dfdw = fast_callable(self._dfdw, domain=self._CC) self._fastcall_dfdz = fast_callable(self._dfdz, domain=self._CC) + self._fastcall_cohomology_basis = [ + fast_callable(h, domain=self._CC) for h in self.cohomology_basis() + ] def __repr__(self): r""" @@ -526,7 +761,10 @@ def __repr__(self): sage: RiemannSurface(f) Riemann surface defined by polynomial f = -z^4 + w^2 + 1 = 0, with 53 bits of precision """ - s = 'Riemann surface defined by polynomial f = %s = 0, with %s bits of precision' % (self.f, self._prec) + s = ( + "Riemann surface defined by polynomial f = %s = 0, with %s bits of precision" + % (self.f, self._prec) + ) return s def w_values(self, z0): @@ -552,8 +790,14 @@ def w_values(self, z0): sage: S.w_values(0) # abs tol 1e-14 [-1.00000000000000*I, 1.00000000000000*I] + + Note that typically the method returns a list of length ``self.degree``, + but that at ramification points, this may no longer be true:: + + sage: S.w_values(1) # abs tol 1e-14 + [0.000000000000000] """ - return self.f(z0,self._CCw.gen(0)).roots(multiplicities=False) + return self.f(z0, self._CCw.gen(0)).roots(multiplicities=False) @cached_method def downstairs_edges(self): @@ -587,23 +831,26 @@ def downstairs_edges(self): """ # Because of how we constructed the Voronoi diagram, the first n points # correspond to the branch locus points. - # The regions of these points are all of the edges which don't go off + # The regions of these points are all of the edges which do not go off # to infinity, which are exactly the ones we want. n = len(self.branch_locus) - desired_edges = [self.voronoi_diagram.regions[self.voronoi_diagram.point_region[i]] for i in range(n)] + desired_edges = [ + self.voronoi_diagram.regions[self.voronoi_diagram.point_region[i]] + for i in range(n) + ] # First construct the edges as a set because the regions will overlap - # and we don't want to have two of the same edge. + # and we do not want to have two of the same edge. edges1 = set() for c in desired_edges: - for j in range(len(c)-1): - edges1.add(frozenset((c[j],c[j+1]))) - edges1.add(frozenset((c[0],c[-1]))) + for j in range(len(c) - 1): + edges1.add(frozenset((c[j], c[j + 1]))) + edges1.add(frozenset((c[0], c[-1]))) # Then make it into a list and sort it. # The sorting is important - it will make computing the monodromy group # MUCH easier. # We orient all the edges so that we go from lower to higher # numbered vertex for the continuation. - edges = [(i0,i1) if (i0 < i1) else (i1,i0) for (i0,i1) in edges1] + edges = [(i0, i1) if (i0 < i1) else (i1, i0) for (i0, i1) in edges1] edges.sort() return edges @@ -612,7 +859,7 @@ def downstairs_graph(self): Return the Voronoi decomposition as a planar graph. The result of this routine can be useful to interpret the labelling of - the vertices. + the vertices. See also :meth:`upstairs_graph`. OUTPUT: @@ -626,22 +873,48 @@ def downstairs_graph(self): sage: S = RiemannSurface(f) sage: S.downstairs_graph() Graph on 11 vertices + """ + G = Graph(self.downstairs_edges()) + G.set_pos(dict(enumerate(list(v) for v in self._vertices))) + return G + + @cached_method + def upstairs_graph(self): + r""" + Return the graph of the upstairs edges. + + This method can be useful for generating paths in the surface between points labelled + by upstairs vertices, and verifying that a homology basis is likely computed correctly. + See also :meth:`downstairs_graph`. + + OUTPUT: + + The homotopy-continued Voronoi decomposition as a graph, with appropriate 3D embedding. - Similarly one can form the graph of the upstairs edges, which is - visually rather less attractive but can be instructive to verify that a - homology basis is likely correctly computed.:: + EXAMPLES:: - sage: G = Graph(S.upstairs_edges()); G + sage: R. = QQ[] + sage: S = Curve(w^2-z^4+1).riemann_surface() + sage: G = S.upstairs_graph(); G Graph on 22 vertices - sage: G.is_planar() - False sage: G.genus() 1 sage: G.is_connected() True """ - G = Graph(self.downstairs_edges()) - G.set_pos(dict(enumerate(list(v) for v in self._vertices))) + G = Graph(self.upstairs_edges(), immutable=True) + G.set_pos( + { + (i, j): [ + self._vertices[i].real(), + self._vertices[i].imag(), + self.w_values(self._vertices[i])[j].imag(), + ] + for i in range(len(self._vertices)) + for j in range(self.degree) + }, + dim=3, + ) return G def _compute_delta(self, z1, epsilon, wvalues=None): @@ -697,28 +970,44 @@ def _compute_delta(self, z1, epsilon, wvalues=None): if wvalues is None: wvalues = self.w_values(z1) # For computation of rho. Need the branch locus + roots of a0. - badpoints = self.branch_locus + self._a0roots + badpoints = self._f_branch_locus + self._a0roots rho = min(abs(z1 - z) for z in badpoints) / 2 - Y = max(abs(self._fastcall_dfdz(z1, wi)/self._fastcall_dfdw(z1, wi)) - for wi in wvalues) + Y = max( + abs(self._fastcall_dfdz(z1, wi) / self._fastcall_dfdw(z1, wi)) + for wi in wvalues + ) # compute M - upperbounds = [sum(ak[k] * (abs(z1) + rho)**k - for k in range(ak.degree())) - for ak in self._aks] + upperbounds = [ + sum(ak[k] * (abs(z1) + rho)**k for k in range(ak.degree())) + for ak in self._aks + ] upperbounds.reverse() # If a0 is a constant polynomial, it is obviously bounded below. if not self._a0roots: lowerbound = self._CC(self._a0) / 2 else: - lowerbound = self._a0[self._a0.degree()]*prod(abs((zk - z1) - rho) for zk in self._a0roots) / 2 - M = 2 * max((upperbounds[k]/lowerbound).abs().nth_root(k+1) - for k in range(self.degree-1)) - return rho*(((rho*Y - epsilon)**2 + 4*epsilon*M).sqrt() - (rho*Y + epsilon))/(2*M - 2*rho*Y) + lowerbound = ( + self._a0[self._a0.degree()] + * prod(abs((zk - z1) - rho) for zk in self._a0roots) + / 2 + ) + M = 2 * max( + (upperbounds[k] / lowerbound).abs().nth_root(k + 1) + for k in range(self.degree - 1) + ) + return ( + rho + * ( + ((rho * Y - epsilon)**2 + 4 * epsilon * M).sqrt() + - (rho * Y + epsilon) + ) + / (2 * M - 2 * rho * Y) + ) else: # Instead, we just compute the minimum distance between branch # points and the point in question. - return min(abs(b - z1) for b in self.branch_locus) / 2 + return min(abs(b - z1) for b in self._f_branch_locus) / 2 def homotopy_continuation(self, edge): r""" @@ -727,15 +1016,18 @@ def homotopy_continuation(self, edge): INPUT: - - ``edge`` -- a tuple of integers indicating an edge of the Voronoi - diagram + - ``edge`` -- a tuple ``(z_start, z_end)`` indicating the straight line + over which to perform the homotopy continutation OUTPUT: - A list of complex numbers corresponding to the points which are reached - when traversing along the direction of the edge. The ordering of these - points indicates how they have been permuted due to the weaving of the - curve. + A list containing the initialised continuation data. Each entry in the + list contains: the `t` values that entry corresponds to, a list of + complex numbers corresponding to the points which are reached when + continued along the edge when traversing along the direction of the + edge, and a value ``epsilon`` giving the minimumdistance between the + fibre values divided by 3. The ordering of these points indicates how + they have been permuted due to the weaving of the curve. EXAMPLES: @@ -749,48 +1041,54 @@ def homotopy_continuation(self, edge): sage: S = RiemannSurface(f) sage: edge1 = sorted(S.edge_permutations())[0] sage: sigma = S.edge_permutations()[edge1] - sage: continued_values = S.homotopy_continuation(edge1) + sage: edge = [S._vertices[i] for i in edge1] + sage: continued_values = S.homotopy_continuation(edge)[-1][1] sage: stored_values = S.w_values(S._vertices[edge1[1]]) - sage: all( abs(continued_values[i]-stored_values[sigma(i)]) < 1e-8 for i in range(3)) + sage: all(abs(continued_values[i]-stored_values[sigma(i)]) < 1e-8 for i in range(3)) True """ - i0, i1 = edge + z_start, z_end = edge + z_start = self._CC(z_start) + z_end = self._CC(z_end) ZERO = self._RR.zero() ONE = self._RR.one() datastorage = [] - z_start = self._CC(self._vertices[i0]) - z_end = self._CC(self._vertices[i1]) path_length = abs(z_end - z_start) def path(t): return z_start * (1 - t) + z_end * t + # Primary procedure. T = ZERO currw = self.w_values(path(T)) n = len(currw) - epsilon = min([abs(currw[i] - currw[j]) for i in range(1,n) for j in range(i)])/3 - datastorage += [(T,currw,epsilon)] + epsilon = ( + min([abs(currw[i] - currw[j]) for i in range(1, n) for j in range(i)]) / 3 + ) + datastorage += [(T, currw, epsilon)] while T < ONE: - delta = self._compute_delta(path(T), epsilon, wvalues=currw)/path_length + delta = self._compute_delta(path(T), epsilon, wvalues=currw) / path_length # Move along the path by delta. T += delta # If T exceeds 1, just set it to 1 and compute. if T > ONE: - delta -= (T-ONE) + delta -= T - ONE T = ONE while True: try: - neww = self._determine_new_w(path(T),currw,epsilon) + neww = self._determine_new_w(path(T), currw, epsilon) except ConvergenceError: delta /= 2 T -= delta else: break currw = neww - epsilon = min([abs(currw[i] - currw[j]) for i in range(1,n) for j in range(i)])/3 - datastorage += [(T,currw,epsilon)] - self._L[edge] = datastorage - return currw + epsilon = ( + min([abs(currw[i] - currw[j]) for i in range(1, n) for j in range(i)]) + / 3 + ) + datastorage += [(T, currw, epsilon)] + return datastorage def _determine_new_w(self, z0, oldw, epsilon): r""" @@ -828,7 +1126,7 @@ def _determine_new_w(self, z0, oldw, epsilon): sage: z0 = S._vertices[0] sage: epsilon = 0.1 sage: oldw = S.w_values(z0) - sage: neww = S._determine_new_w(z0,oldw,epsilon); neww #abs tol 0.00000001 + sage: neww = S._determine_new_w(z0, oldw, epsilon); neww #abs tol 0.00000001 [-0.934613146929672 + 2.01088055918363*I, 0.934613146929672 - 2.01088055918363*I] @@ -845,13 +1143,18 @@ def _determine_new_w(self, z0, oldw, epsilon): sage: g = z^3*w + w^3 + z sage: T = RiemannSurface(g) - sage: z0 = T._vertices[2]*(0.9) - T._vertices[15]*(0.1) + sage: z0 = T._vertices[2]*(0.9) + 0.3*I sage: epsilon = 0.5 sage: oldw = T.w_values(T._vertices[2]) - sage: T._determine_new_w(z0,oldw,epsilon) - [-0.562337685361648 + 0.151166007149998*I, - 0.640201585779414 - 1.48567225836436*I, - -0.0778639004177661 + 1.33450625121437*I] + sage: T._determine_new_w(z0, oldw, epsilon) + Traceback (most recent call last): + ... + ConvergenceError: Newton iteration escaped neighbourhood + + .. NOTE:: + + Algorithmically, this method is nearly identical to :meth:`_newton_iteration`, + but this method takes a list of `w` values. """ # Tools of Newton iteration. F = self._fastcall_f @@ -875,8 +1178,11 @@ def _determine_new_w(self, z0, oldw, epsilon): Nnew_delta = new_delta.norm() # If we found the root exactly, or if delta only affects half the digits and # stops getting smaller, we decide that we have converged. - if (new_delta == 0) or (Nnew_delta >= Ndelta and - Ndelta.sign_mantissa_exponent()[2]+prec < wi.norm().sign_mantissa_exponent()[2]): + if (new_delta == 0) or ( + Nnew_delta >= Ndelta + and Ndelta.sign_mantissa_exponent()[2] + prec + < wi.norm().sign_mantissa_exponent()[2] + ): neww.append(wi) break delta = new_delta @@ -884,7 +1190,9 @@ def _determine_new_w(self, z0, oldw, epsilon): wi -= delta # If we run 100 iterations without a result, terminate. else: - raise ConvergenceError("Newton iteration fails to converge after %s iterations" % j) + raise ConvergenceError( + "Newton iteration fails to converge after %s iterations" % j + ) return neww def _newton_iteration(self, z0, oldw, epsilon): @@ -932,11 +1240,13 @@ def _newton_iteration(self, z0, oldw, epsilon): sage: g = z^3*w + w^3 + z sage: T = RiemannSurface(g) - sage: z0 = T._vertices[2]*(0.9) - T._vertices[15]*(0.1) + sage: z0 = T._vertices[2]*(0.9) + 0.3*I sage: epsilon = 0.5 - sage: oldw = T.w_values(T._vertices[2])[0] + sage: oldw = T.w_values(T._vertices[2])[1] sage: T._newton_iteration(z0, oldw, epsilon) - -0.562337685361648 + 0.151166007149998*I + Traceback (most recent call last): + ... + ConvergenceError: Newton iteration escaped neighbourhood """ F = self._fastcall_f dF = self._fastcall_dfdw @@ -955,12 +1265,15 @@ def _newton_iteration(self, z0, oldw, epsilon): Nnew_delta = new_delta.norm() # If we found the root exactly, or if delta only affects half the digits and # stops getting smaller, we decide that we have converged. - if (new_delta == 0) or (Nnew_delta>=Ndelta and - Ndelta.sign_mantissa_exponent()[2]+prec < neww.norm().sign_mantissa_exponent()[2]): + if (new_delta == 0) or ( + Nnew_delta >= Ndelta + and Ndelta.sign_mantissa_exponent()[2] + prec + < neww.norm().sign_mantissa_exponent()[2] + ): return neww delta = new_delta Ndelta = Nnew_delta - neww-=delta + neww -= delta raise ConvergenceError("Newton iteration fails to converge") @cached_method @@ -992,10 +1305,20 @@ def upstairs_edges(self): # Lifts each edge individually. for e in self.downstairs_edges(): i0, i1 = e + d_edge = (self._vertices[i0], self._vertices[i1]) # Epsilon for checking w-value later. - epsilon = min([abs(self._wvalues[i1][i] - self._wvalues[i1][n-j-1]) for i in range(n) for j in range(n-i-1)])/3 + val = self._wvalues[i1] + epsilon = ( + min( + abs(val[i] - val[n - j - 1]) + for i in range(n) + for j in range(n - i - 1) + ) + / 3 + ) # Homotopy continuation along e. - homotopycont = self.homotopy_continuation(e) + self._L[e] = self.homotopy_continuation(d_edge) + homotopycont = self._L[e][-1][1] for i in range(len(homotopycont)): # Checks over the w-values of the next point to check which it is. for j in range(len(self._wvalues[i1])): @@ -1026,27 +1349,30 @@ def _edge_permutation(self, edge): sage: f = z^3*w + w^3 + z sage: S = RiemannSurface(f) - Compute the edge permutation of (1,2) on the Voronoi diagram:: + Compute the edge permutation of (1, 2) on the Voronoi diagram:: - sage: S._edge_permutation((1,2)) + sage: S._edge_permutation((1, 2)) (0,2,1) - This indicates that while traversing along the direction of `(5,16)`, + This indicates that while traversing along the direction of `(2, 9)`, the 2nd and 3rd layers of the Riemann surface are interchanging. """ if edge in self.downstairs_edges(): # find all upstairs edges that are lifts of the given # downstairs edge and store the corresponding indices at # start and end that label the branches upstairs. - L = [(j0, j1) for ((i0, j0), (i1, j1)) in self.upstairs_edges() - if edge == (i0, i1)] + L = [ + (j0, j1) + for ((i0, j0), (i1, j1)) in self.upstairs_edges() + if edge == (i0, i1) + ] # we should be finding exactly "degree" of these assert len(L) == self.degree # and as a corollary of how we construct them, the indices # at the start should be in order assert all(a == b[0] for a, b in enumerate(L)) return self._Sn([j1 for j0, j1 in L]) - raise ValueError('edge not in Voronoi diagram') + raise ValueError("edge not in Voronoi diagram") @cached_method def edge_permutations(self) -> dict: @@ -1142,15 +1468,19 @@ def monodromy_group(self): n = len(self.branch_locus) G = Graph(self.downstairs_edges()) # we get all the regions - loops = [self.voronoi_diagram.regions[i][:] - for i in self.voronoi_diagram.point_region] + loops = [ + self.voronoi_diagram.regions[i][:] + for i in self.voronoi_diagram.point_region + ] # and construct their Voronoi centers as complex numbers - centers = self.branch_locus + [self._CC(x, y) for x, y in self.voronoi_diagram.points[n:]] + centers = self.branch_locus + [ + self._CC(x, y) for x, y in self.voronoi_diagram.points[n:] + ] for center, loop in zip(centers, loops): if -1 in loop: # for loops involving infinity we take the finite part of the path i = loop.index(-1) - loop[:] = loop[i+1:]+loop[:i] + loop[:] = loop[i + 1 :] + loop[:i] else: # and for finite ones we close the paths loop.append(loop[0]) @@ -1165,7 +1495,7 @@ def monodromy_group(self): # infinity. There should be a unique way of doing so. inf_loops = loops[n:] inf_path = inf_loops.pop() - while (inf_loops): + while inf_loops: inf_path += (inf_loops.pop())[1:] assert inf_path[0] == inf_path[-1] @@ -1178,10 +1508,11 @@ def monodromy_group(self): SG = self._Sn for c in loops: to_loop = G.shortest_path(P0, c[0]) - to_loop_perm = SG.prod(edge_perms[(to_loop[i], to_loop[i + 1])] - for i in range(len(to_loop) - 1)) - c_perm = SG.prod(edge_perms[(c[i], c[i + 1])] - for i in range(len(c) - 1)) + to_loop_perm = SG.prod( + edge_perms[(to_loop[i], to_loop[i + 1])] + for i in range(len(to_loop) - 1) + ) + c_perm = SG.prod(edge_perms[(c[i], c[i + 1])] for i in range(len(c) - 1)) monodromy_gens.append(to_loop_perm * c_perm * ~to_loop_perm) return monodromy_gens @@ -1235,8 +1566,7 @@ def homology_basis(self): if self.genus == 0: return [] - edgesu = self.upstairs_edges() - cycles = Graph(edgesu).cycle_basis() + cycles = self.upstairs_graph().cycle_basis() # Computing the Gram matrix. cn = len(cycles) # Forming a list of lists of zeroes. @@ -1288,10 +1618,10 @@ def direction(center, neighbour): # score will be appropriately complemented at one of the # next vertices. - a_in = cycles[i][i0-1][0] - a_out = cycles[i][(i0+1) % len(cycles[i])][0] - b_in = cycles[j][i1-1][0] - b_out = cycles[j][(i1+1) % len(cycles[j])][0] + a_in = cycles[i][i0 - 1][0] + a_out = cycles[i][(i0 + 1) % len(cycles[i])][0] + b_in = cycles[j][i1 - 1][0] + b_out = cycles[j][(i1 + 1) % len(cycles[j])][0] # we can get the angles (and hence the rotation order) # by taking the arguments of the differences. @@ -1305,24 +1635,32 @@ def direction(center, neighbour): # problems occur with that. if (b_in != a_in) and (b_in != a_out): - if ((a_in_arg < b_in_arg < a_out_arg) + if ( + (a_in_arg < b_in_arg < a_out_arg) or (b_in_arg < a_out_arg < a_in_arg) - or (a_out_arg < a_in_arg < b_in_arg)): + or (a_out_arg < a_in_arg < b_in_arg) + ): intsum += 1 - elif ((a_out_arg < b_in_arg < a_in_arg) - or (b_in_arg < a_in_arg < a_out_arg) - or (a_in_arg < a_out_arg < b_in_arg)): + elif ( + (a_out_arg < b_in_arg < a_in_arg) + or (b_in_arg < a_in_arg < a_out_arg) + or (a_in_arg < a_out_arg < b_in_arg) + ): intsum -= 1 else: raise RuntimeError("impossible edge orientation") if (b_out != a_in) and (b_out != a_out): - if ((a_in_arg < b_out_arg < a_out_arg) + if ( + (a_in_arg < b_out_arg < a_out_arg) or (b_out_arg < a_out_arg < a_in_arg) - or (a_out_arg < a_in_arg < b_out_arg)): + or (a_out_arg < a_in_arg < b_out_arg) + ): intsum -= 1 - elif ((a_out_arg < b_out_arg < a_in_arg) - or (b_out_arg < a_in_arg < a_out_arg) - or (a_in_arg < a_out_arg < b_out_arg)): + elif ( + (a_out_arg < b_out_arg < a_in_arg) + or (b_out_arg < a_in_arg < a_out_arg) + or (a_in_arg < a_out_arg < b_out_arg) + ): intsum += 1 else: raise RuntimeError("impossible edge orientation") @@ -1349,24 +1687,29 @@ def direction(center, neighbour): if P[i][j] != 0: acycles[i] += [(P[i][j], [x for x in cycles[j]] + [cycles[j][0]])] if P[self.genus + i][j] != 0: - bcycles[i] += [(P[self.genus + i][j], [x for x in cycles[j]] + [cycles[j][0]])] + bcycles[i] += [ + (P[self.genus + i][j], [x for x in cycles[j]] + [cycles[j][0]]) + ] return acycles + bcycles - def make_zw_interpolator(self, upstairs_edge): + def make_zw_interpolator(self, upstairs_edge, initial_continuation=None): r""" - Given an upstairs edge for which continuation data has been stored, - return a function that computes `z(t),w(t)` , where `t` in `[0,1]` is a + Given a downstairs edge for which continuation data has been initialised, + return a function that computes `z(t), w(t)` , where `t` in `[0,1]` is a parametrization of the edge. INPUT: - - ``upstairs_edge`` -- a pair of integer tuples indicating an edge on - the upstairs graph of the surface + - ``upstairs_edge`` -- tuple ``((z_start, sb), (z_end,))`` giving the + start and end values of the base coordinate along the straight-line + path and the starting branch + - ``initial_continuation`` -- list (optional); output of + ``homotopy_continuation`` initialising the continuation data OUTPUT: - A tuple (g, d), where g is the function that computes the interpolation - along the edge and d is the difference of the z-values of the end and + A tuple ``(g, d)``, where ``g`` is the function that computes the interpolation + along the edge and ``d`` is the difference of the z-values of the end and start point. EXAMPLES:: @@ -1376,17 +1719,30 @@ def make_zw_interpolator(self, upstairs_edge): sage: f = w^2 - z^4 + 1 sage: S = RiemannSurface(f) sage: _ = S.homology_basis() - sage: g,d = S.make_zw_interpolator([(0,0),(1,0)]); + sage: u_edge = [(0, 0), (1, 0)] + sage: d_edge = tuple(u[0] for u in u_edge) + sage: u_edge = [(S._vertices[i], j) for i, j in u_edge] + sage: initial_continuation = S._L[d_edge] + sage: g, d = S.make_zw_interpolator(u_edge, initial_continuation) sage: all(f(*g(i*0.1)).abs() < 1e-13 for i in range(10)) True sage: abs((g(1)[0]-g(0)[0]) - d) < 1e-13 True + + .. NOTE:: + + The interpolator returned by this method can effectively hang if + either ``z_start`` or ``z_end`` are branchpoints. In these situations + it is better to take a different approach rather than continue to use + the interpolator. """ - eindex = tuple(u[0] for u in upstairs_edge) - i0, i1 = eindex - z_start = self._vertices[i0] - z_end = self._vertices[i1] - currL = self._L[eindex] + downstairs_edge = tuple(u[0] for u in upstairs_edge) + z_start, z_end = downstairs_edge + z_start = self._CC(z_start) + z_end = self._CC(z_end) + if initial_continuation is None: + initial_continuation = self.homotopy_continuation(downstairs_edge) + currL = initial_continuation windex = upstairs_edge[0][1] def w_interpolate(t): @@ -1402,8 +1758,8 @@ def w_interpolate(t): w1 = w1[windex] t2, w2, _ = currL[i + 1] w2 = w2[windex] - z0 = (1-t)*z_start+t*z_end - w0 = self._CC(((t2-t)*w1+(t-t1)*w2)/(t2-t1)) + z0 = (1 - t) * z_start + t * z_end + w0 = self._CC(((t2 - t) * w1 + (t - t1) * w2) / (t2 - t1)) try: desired_result = self._newton_iteration(z0, w0, epsilon) except ConvergenceError: @@ -1414,7 +1770,7 @@ def w_interpolate(t): tnew = t while True: tnew = (t1 + tnew) / 2 - znew = (1-tnew)*self._vertices[i0]+tnew*self._vertices[i1] + znew = (1 - tnew) * z_start + tnew * z_end try: neww1 = self._determine_new_w(znew, currL[i][1], epsilon) except ConvergenceError: @@ -1424,7 +1780,8 @@ def w_interpolate(t): break # once the loop has succeeded we insert our new value t1 = tnew - self._L[eindex].insert(i + 1, (t1, neww1, epsilon)) + currL.insert(i + 1, (t1, neww1, epsilon)) + return w_interpolate, (z_end - z_start) def simple_vector_line_integral(self, upstairs_edge, differentials): @@ -1433,8 +1790,10 @@ def simple_vector_line_integral(self, upstairs_edge, differentials): INPUT: - - ``upstairs_edge`` -- a pair of integer tuples corresponding to an edge - of the upstairs graph. + - ``upstairs_edge`` -- tuple. Either a pair of integer tuples + corresponding to an edge of the upstairs graph, or a tuple + ``((z_start, sb), (z_end, ))`` as in the input of + ``make_zw_interpolator``. - ``differentials`` -- a list of polynomials; a polynomial `g` represents the differential `g(z,w)/(df/dw) dz` where `f(z,w)=0` is @@ -1457,14 +1816,28 @@ def simple_vector_line_integral(self, upstairs_edge, differentials): sage: M = S.riemann_matrix() sage: differentials = S.cohomology_basis() - sage: S.simple_vector_line_integral([(0,0),(1,0)], differentials) # abs tol 0.00000001 + sage: S.simple_vector_line_integral([(0, 0), (1, 0)], differentials) #abs tol 0.00000001 (1.14590610929717e-16 - 0.352971844594760*I) .. NOTE:: - Uses data that ``homology_basis`` initializes. + Uses data that :meth:`homology_basis` initializes, and may give incorrect + values if :meth:`homology_basis` has not initialized them. In practice + it is more efficient to set ``differentials`` to a fast-callable version + of differentials to speed up execution. """ - w_of_t, Delta_z = self.make_zw_interpolator(upstairs_edge) + d_edge = tuple(u[0] for u in upstairs_edge) + # Using a try-catch here allows us to retain a certain amount of back compatibility + # for users. + try: + initial_continuation = self._L[d_edge] + upstairs_edge = ( + (self._vertices[d_edge[0]], upstairs_edge[0][1]), + (self._vertices[d_edge[1]],), + ) + except KeyError: + initial_continuation = self.homotopy_continuation(d_edge) + w_of_t, Delta_z = self.make_zw_interpolator(upstairs_edge, initial_continuation) V = VectorSpace(self._CC, len(differentials)) def integrand(t): @@ -1503,7 +1876,7 @@ def cohomology_basis(self, option=1): """ if self.genus == 0: self._differentials = [] - return self._differentials[0] + return self._differentials # [0] if self._differentials is None: # Computes differentials from the adjointIdeal using Singular # First we homogenize @@ -1516,30 +1889,35 @@ def cohomology_basis(self, option=1): # We load the relevant functionality into singularlib import sage.libs.singular.function_factory + sage.libs.singular.function_factory.lib("paraplanecurves.lib") adjointIdeal = sage.libs.singular.function.singular_function("adjointIdeal") libsing_options = sage.libs.singular.option.LibSingularVerboseOptions() # We compute the adjoint ideal (note we need to silence "redefine") - redef_save = libsing_options['redefine'] + redef_save = libsing_options["redefine"] try: - libsing_options['redefine'] = False + libsing_options["redefine"] = False J = adjointIdeal(fnew, option) finally: - libsing_options['redefine'] = redef_save + libsing_options["redefine"] = redef_save # We are interested in the (degree-3) subspace of the adjoint ideal. # We compute this by intersecting with (Z,W,U)^(degree-3). Then the # lowest degree generators are a basis of the relevant subspace. d = fnew.total_degree() - J2 = k.ideal(J).intersection(k.ideal([k.gen(0), k.gen(1), k.gen(2)])**(d - 3)) + J2 = k.ideal(J).intersection( + k.ideal([k.gen(0), k.gen(1), k.gen(2)])**(d - 3) + ) generators = [dehom(c) for c in J2.gens() if c.degree() == d - 3] if len(generators) != self.genus: - raise ValueError("computed regular differentials do not match stored genus") + raise ValueError( + "computed regular differentials do not match stored genus" + ) self._differentials = generators return self._differentials - def _bounding_data(self, differentials): + def _bounding_data(self, differentials, exact=False): r""" Compute the data required to bound a differential on a circle. @@ -1548,34 +1926,39 @@ def _bounding_data(self, differentials): INPUT: - - ``differentials`` -- list. A list of polynomials in ``self._R`` giving + - ``differentials`` -- list of polynomials in ``self._R`` giving the numerators of the differentials, as per the output of - :meth:`cohomology_basis`. + :meth:`cohomology_basis` + + - ``exact`` -- boolean (default: ``False``); whether to return the minimal + polynomials over the exact base ring, or over ``self._CC`` OUTPUT: - A tuple ``(CCzg, [(g, dgdz, F, a0_info), ...])`` where each element of + A tuple ``(Rzg, [(g, dgdz, F, a0_info), ...])`` where each element of the list corresponds to an element of ``differentials``. Introducing the notation ``RBzg = PolynomialRing(self._R, ['z','g'])`` and ``CCzg = PolynomialRing(self._CC, ['z','g'])``, we have that: - - ``g`` is the full rational function in ``self._R.fraction_field()`` - giving the differential, - - ``dgdz`` is the derivative of ``g`` with respect to ``self._R.gen(0)`` - , written in terms of ``self._R.gen(0)`` and ``g``, hence laying in - ``RBzg``, - - ``F`` is the minimal polynomial of ``g`` over ``self._R.gen(0)``, - laying in the polynomial ring ``CCzg``, - - ``a0_info`` is a tuple ``(lc, roots)`` where ``lc`` and ``roots`` are - the leading coefficient and roots of the polynomial in ``CCzg.gen(0)`` - that is the coefficient of the term of ``F`` of highest degree in - ``CCzg.gen(1)``. + - ``Rzg`` is either ``RBzg`` or ``CCzg`` depending on the value of + ``exact``, + - ``g`` is the full rational function in ``self._R.fraction_field()`` + giving the differential, + - ``dgdz`` is the derivative of ``g`` with respect to ``self._R.gen(0)``, + written in terms of ``self._R.gen(0)`` and ``g``, hence laying in + ``RBzg``, + - ``F`` is the minimal polynomial of ``g`` over ``self._R.gen(0)``, + laying in the polynomial ring ``Rzg``, + - ``a0_info`` is a tuple ``(lc, roots)`` where ``lc`` and ``roots`` are + the leading coefficient and roots of the polynomial in ``CCzg.gen(0)`` + that is the coefficient of the term of ``F`` of highest degree in + ``CCzg.gen(1)``. EXAMPLES:: sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface sage: R. = QQ[] - sage: f = y^2-x^3+1 + sage: f = y^2 - x^3 + 1 sage: S = RiemannSurface(f) sage: differentials = S.cohomology_basis(); differentials [1] @@ -1591,32 +1974,49 @@ def _bounding_data(self, differentials): -0.500000000000000 - 0.866025403784439*I, -0.500000000000000 + 0.866025403784439*I]))]) + Note that ``self._bounding_data(self._cohomology_basis(), exact=True)`` + is stored in ``self._cohomology_basis_bounding_data``:: + + sage: S._cohomology_basis_bounding_data + (Multivariate Polynomial Ring in z, g over Rational Field, + [(1/(2*y), + (-3*z^2*g)/(2*z^3 - 2), + z^3*g^2 - g^2 - 1/4, + (1.00000000000000, + [1.00000000000000, + -0.500000000000000 - 0.866025403784439*I, + -0.500000000000000 + 0.866025403784439*I]))]) """ # This copies previous work by NB, outputting the zipped list required # for a certified line integral. RB = self._R.base_ring() - P = PolynomialRing(RB, 'Z') + P = PolynomialRing(RB, "Z") k = P.fraction_field() - KP = PolynomialRing(k, 'W') # W->fraction field + KP = PolynomialRing(k, "W") # W->fraction field fZW = self.f(P.gen(0), KP.gen(0)) - L = k.extension(fZW, 'Wb') + L = k.extension(fZW, "Wb") dfdw_L = self._dfdw(P.gen(0), L.gen(0)) - integrand_list = [h/self._dfdw for h in differentials] + integrand_list = [h / self._dfdw for h in differentials] # minpoly_univ gives the minimal polynomial for h, in variable x, with - # coefficients given by polynomials in P (i.e. rational polynomials in Z). - minpoly_univ = [(h(P.gen(0), L.gen(0))/dfdw_L).minpoly().numerator() - for h in differentials] - RBzg = PolynomialRing(RB, ['z', 'g']) + # coefficients given by polynomials with coefficients in P (i.e. + # rational polynomials in Z). + minpoly_univ = [ + (h(P.gen(0), L.gen(0)) / dfdw_L).minpoly().numerator() + for h in differentials + ] + RBzg = PolynomialRing(RB, ["z", "g"]) # The following line changes the variables in these minimal polynomials # as Z -> z, x -> G, then evaluates at G = QQzg.gens(1) ( = g ) - RBzgG = PolynomialRing(RBzg, 'G') - minpoly_list = [RBzgG([c(RBzg.gen(0)) for c in list(h)])(RBzg.gen(1)) - for h in minpoly_univ] + RBzgG = PolynomialRing(RBzg, "G") + minpoly_list = [ + RBzgG([c(RBzg.gen(0)) for c in list(h)])(RBzg.gen(1)) for h in minpoly_univ + ] # h(z,g)=0 --> dg/dz = - dhdz/dhdg - dgdz_list = [-h.derivative(RBzg.gen(0))/h.derivative(RBzg.gen(1)) - for h in minpoly_list] + dgdz_list = [ + -h.derivative(RBzg.gen(0)) / h.derivative(RBzg.gen(1)) for h in minpoly_list + ] - CCzg = PolynomialRing(self._CC, ['z','g']) + CCzg = PolynomialRing(self._CC, ["z", "g"]) CCminpoly_list = [CCzg(h) for h in minpoly_list] a0_list = [P(h.leading_coefficient()) for h in minpoly_univ] @@ -1624,11 +2024,22 @@ def _bounding_data(self, differentials): # is embedded into CC, it has characteristic 0, and so we know the # irreducible factors are all separable, i.e. the roots have multiplicity # one. - a0_info = [(self._CC(a0.leading_coefficient()), - flatten([self._CCz(F).roots(multiplicities=False)*m - for F, m in a0.factor()])) - for a0 in a0_list] - return CCzg, list(zip(integrand_list, dgdz_list, CCminpoly_list, a0_info)) + a0_info = [ + ( + self._CC(a0.leading_coefficient()), + flatten( + [ + self._CCz(F).roots(multiplicities=False) * m + for F, m in a0.factor() + ] + ), + ) + for a0 in a0_list + ] + if exact: + return RBzg, list(zip(integrand_list, dgdz_list, minpoly_list, a0_info)) + else: + return CCzg, list(zip(integrand_list, dgdz_list, CCminpoly_list, a0_info)) def rigorous_line_integral(self, upstairs_edge, differentials, bounding_data): r""" @@ -1643,8 +2054,10 @@ def rigorous_line_integral(self, upstairs_edge, differentials, bounding_data): INPUT: - - ``upstairs_edge`` -- a pair of integer tuples corresponding to an edge - of the upstairs graph. + - ``upstairs_edge`` -- tuple. Either a pair of integer tuples + corresponding to an edge of the upstairs graph, or a tuple + ``((z_start, sb), (z_end, ))`` as in the input of + ``make_zw_interpolator``. - ``differentials`` -- a list of polynomials; a polynomial `g` represents the differential `g(z,w)/(df/dw) dz` where `f(z,w)=0` is @@ -1677,9 +2090,10 @@ def rigorous_line_integral(self, upstairs_edge, differentials, bounding_data): .. NOTE:: - Uses data that ``homology_basis`` initializes. + Uses data that ``homology_basis`` initializes, and may give incorrect + values if :meth:`homology_basis` has not initialized them. - Note also that the data of the differentials is contained within + Note also that the data of the differentials is contained within ``bounding_data``. It is, however, still advantageous to have this be a separate argument, as it lets the user supply a fast-callable version of the differentials, to significantly speed up execution @@ -1711,21 +2125,34 @@ def rigorous_line_integral(self, upstairs_edge, differentials, bounding_data): # data at all corresponds to the differentials given. The onus is then # on the design of other functions which use it. - # CCzg is required to be known as we need to know the ring which the minpolys lie in. + # CCzg is required to be known as we need to know the ring which the minpolys + # lie in. CCzg, bounding_data_list = bounding_data - i0, _ = upstairs_edge[0] - i1, _ = upstairs_edge[1] - z0 = self._vertices[i0] - z1 = self._vertices[i1] - zwt, z1_minus_z0 = self.make_zw_interpolator(upstairs_edge) + d_edge = tuple(u[0] for u in upstairs_edge) + # Using a try-catch here allows us to retain a certain amount of back + # compatibility for users. + try: + initial_continuation = self._L[d_edge] + upstairs_edge = ( + (self._vertices[d_edge[0]], upstairs_edge[0][1]), + (self._vertices[d_edge[1]],), + ) + except KeyError: + initial_continuation = self.homotopy_continuation(d_edge) + + zwt, z1_minus_z0 = self.make_zw_interpolator( + upstairs_edge, initial_continuation + ) + z0 = zwt(0)[0] + z1 = zwt(1)[0] # list of (centre, radius) pairs that still need to be processed - ball_stack = [(self._RR(1/2), self._RR(1/2), 0)] - alpha = self._RR(912/1000) + ball_stack = [(self._RR(1 / 2), self._RR(1 / 2), 0)] + alpha = self._RR(912 / 1000) # alpha set manually for scaling purposes. Basic benchmarking shows # that ~0.9 is a sensible value. - E_global = self._RR(2)**(-self._prec+3) + E_global = self._RR(2)**(-self._prec + 3) # Output will iteratively store the output of the integral. V = VectorSpace(self._CC, len(differentials)) @@ -1750,45 +2177,54 @@ def rigorous_line_integral(self, upstairs_edge, differentials, bounding_data): # possible to cover it entirely in a ball which encompasses an appropriate # ellipse. def local_N(ct, rt): - cz = (1-ct)*z0+ct*z1 # This is the central z-value of our ball. - distances = [(cz-b).abs() for b in self.branch_locus] + cz = (1 - ct) * z0 + ct * z1 # This is the central z-value of our ball. + distances = [(cz - b).abs() for b in self.branch_locus] rho_z = min(distances) - rho_t = rho_z/(z1_minus_z0).abs() - rho_t = alpha*rho_t+(1-alpha)*rt # sqrt(rho_t*rt) could also work - rho_z = rho_t*(z1-z0).abs() - delta_z = (alpha*rho_t+(1-alpha)*rt)*(z1_minus_z0).abs() - expr = rho_t/rt+((rho_t/rt)**2-1).sqrt() # Note this is really exp(arcosh(rho_t/rt)) + rho_t = rho_z / (z1_minus_z0).abs() + rho_t = alpha * rho_t + (1 - alpha) * rt # sqrt(rho_t*rt) could also work + rho_z = rho_t * (z1 - z0).abs() + delta_z = (alpha * rho_t + (1 - alpha) * rt) * (z1_minus_z0).abs() + expr = ( + rho_t / rt + ((rho_t / rt)**2 - 1).sqrt() + ) # Note this is really exp(arcosh(rho_t/rt)) Ni = 3 cw = zwt(ct)[1] - for g, dgdz, minpoly,(a0lc,a0roots) in bounding_data_list: - z_1 = a0lc.abs()*prod((cz-r).abs()-rho_z for r in a0roots) + for g, dgdz, minpoly, (a0lc, a0roots) in bounding_data_list: + z_1 = a0lc.abs() * prod((cz - r).abs() - rho_z for r in a0roots) n = minpoly.degree(CCzg.gen(1)) - ai_new = [(minpoly.coefficient({CCzg.gen(1):i}))(z=cz+self._CCz.gen(0)) - for i in range(n)] - ai_pos = [self._RRz([c.abs() for c in h.list()]) - for h in ai_new] - m = [a(rho_z)/z_1 for a in ai_pos] + ai_new = [ + (minpoly.coefficient({CCzg.gen(1): i}))(z=cz + self._CCz.gen(0)) + for i in range(n) + ] + ai_pos = [self._RRz([c.abs() for c in h.list()]) for h in ai_new] + m = [a(rho_z) / z_1 for a in ai_pos] l = len(m) - M_tilde = 2*max((m[i].abs())**(1/self._RR(l-i)) - for i in range(l)) - cg = g(cz,cw) - cdgdz = dgdz(cz,cg) - Delta = delta_z*cdgdz.abs() + (delta_z**2)*M_tilde/(rho_z*(rho_z-delta_z)) + M_tilde = 2 * max( + (m[i].abs())**(1 / self._RR(l - i)) for i in range(l) + ) + cg = g(cz, cw) + cdgdz = dgdz(cz, cg) + Delta = delta_z * cdgdz.abs() + (delta_z**2) * M_tilde / ( + rho_z * (rho_z - delta_z) + ) M = Delta - N_required = ((M*(self._RR.pi()+64/(15*(expr**2-1)))/E_global).log()/(2*expr.log())).ceil() + N_required = ( + (M * (self._RR.pi() + 64 / (15 * (expr**2 - 1))) / E_global).log() + / (2 * expr.log()) + ).ceil() Ni = max(Ni, N_required) return Ni while ball_stack: ct, rt, lN = ball_stack.pop() - ncts = [ct-rt/2, ct+rt/2] - nrt = rt/2 + ncts = [ct - rt / 2, ct + rt / 2] + nrt = rt / 2 if not lN: - cz = (1-ct)*z0+ct*z1 - distances = [(cz-b).abs() for b in self.branch_locus] + cz = (1 - ct) * z0 + ct * z1 + distances = [(cz - b).abs() for b in self.branch_locus] rho_z = min(distances) - rho_t = rho_z/(z1_minus_z0).abs() + rho_t = rho_z / (z1_minus_z0).abs() if rho_t <= rt: ball_stack.append((ncts[0], nrt, 0)) @@ -1804,20 +2240,20 @@ def local_N(ct, rt): ball_stack.append((ncts[1], nrt, nNs[1])) continue - if lN % 2 and not lN==3: - lN += 1 + if lN % 2 and not lN == 3: + lN += 1 - ct_minus_rt = ct-rt - two_rt = 2*rt + ct_minus_rt = ct - rt + two_rt = 2 * rt def integrand(t): zt, wt = zwt(ct_minus_rt + t * two_rt) dfdwt = self._fastcall_dfdw(zt, wt) return V([h(zt, wt) / dfdwt for h in differentials]) - output += two_rt*integrate_vector_N(integrand, self._prec, lN) + output += two_rt * integrate_vector_N(integrand, self._prec, lN) - return output*z1_minus_z0 + return output * z1_minus_z0 def matrix_of_integral_values(self, differentials, integration_method="heuristic"): r""" @@ -1854,6 +2290,13 @@ def matrix_of_integral_values(self, differentials, integration_method="heuristic sage: (m[0,0]/m[0,1]).algdep(3).degree() # curve is CM, so the period is quadratic 2 + .. NOTE:: + + If ``differentials is self.cohomology_basis()``, the calculations + of the integrals along the edges are written to `self._integral_dict``. + This is as this data will be required when computing the Abel-Jacobi + map, and so it is helpful to have is stored rather than recomputing. + """ cycles = self.homology_basis() @@ -1870,11 +2313,16 @@ def normalize_pairs(L): else: R.append((L[i + 1], L[i])) return R + occurring_edges = set() - occurring_edges.update(*[normalize_pairs(p[1]) for h in cycles - for p in h]) + occurring_edges.update(*[normalize_pairs(p[1]) for h in cycles for p in h]) - fcd = [fast_callable(omega, domain=self._CC) for omega in differentials] + if differentials is self.cohomology_basis(): + fcd = self._fastcall_cohomology_basis + integral_dict = self._integral_dict + else: + fcd = [fast_callable(omega, domain=self._CC) for omega in differentials] + integral_dict = dict() if integration_method == "heuristic": line_int = lambda edge: self.simple_vector_line_integral(edge, fcd) @@ -1882,7 +2330,7 @@ def normalize_pairs(L): bd = self._bounding_data(differentials) line_int = lambda edge: self.rigorous_line_integral(edge, fcd, bd) else: - raise ValueError("Invalid integration method") + raise ValueError("invalid integration method") integral_dict = {edge: line_int(edge) for edge in occurring_edges} @@ -1958,7 +2406,7 @@ def riemann_matrix(self): sage: S = RiemannSurface(f, prec=60) sage: M = S.riemann_matrix() - The Klein quartic has a Riemann matrix with values is a quadratic + The Klein quartic has a Riemann matrix with values in a quadratic field:: sage: x = polygen(QQ) @@ -1967,8 +2415,11 @@ def riemann_matrix(self): True """ PeriodMatrix = self.period_matrix() - Am = PeriodMatrix[0:self.genus,0:self.genus] - RM = numerical_inverse(Am)*PeriodMatrix[0:self.genus,self.genus:2*self.genus] + Am = PeriodMatrix[0 : self.genus, 0 : self.genus] + RM = ( + numerical_inverse(Am) + * PeriodMatrix[0 : self.genus, self.genus : 2 * self.genus] + ) return RM def plot_paths(self): @@ -1990,6 +2441,7 @@ def plot_paths(self): Graphics object consisting of 2 graphics primitives """ from sage.plot.point import point2d + P = [] # trigger the computation of the homology basis, so that self._L is present @@ -2001,6 +2453,7 @@ def plot_paths(self): def path(t): return (1 - t) * z0 + t * z1 + T = self._L[e] P += [path(t[0]) for t in T] return point2d(P, size=1) + point2d(self.branch_locus, color="red") @@ -2026,6 +2479,7 @@ def plot_paths3d(self, thickness=0.01): """ from sage.plot.graphics import Graphics from sage.plot.plot3d.shapes2 import point3d, line3d + P = Graphics() # trigger the computation of the homology basis, so that @@ -2037,15 +2491,24 @@ def plot_paths3d(self, thickness=0.01): z1 = self._vertices[e[1]] def path(t): - z = (1-t)*z0+t*z1 - return (z.real_part(),z.imag_part()) + z = (1 - t) * z0 + t * z1 + return (z.real_part(), z.imag_part()) + T = self._L[e] color = "blue" for i in range(self.degree): - P += line3d([path(t[0])+(t[1][i].imag_part(),) for t in T],color=color,thickness=thickness) - for z,ws in zip(self._vertices,self._wvalues): + P += line3d( + [path(t[0]) + (t[1][i].imag_part(),) for t in T], + color=color, + thickness=thickness, + ) + for z, ws in zip(self._vertices, self._wvalues): for w in ws: - P += point3d([z.real_part(),z.imag_part(),w.imag_part()],color="purple", size=20) + P += point3d( + [z.real_part(), z.imag_part(), w.imag_part()], + color="purple", + size=20, + ) return P def endomorphism_basis(self, b=None, r=None): @@ -2089,7 +2552,7 @@ def endomorphism_basis(self, b=None, r=None): """ M = self.riemann_matrix() - return integer_matrix_relations(M,M,b,r) + return integer_matrix_relations(M, M, b, r) def homomorphism_basis(self, other, b=None, r=None): r""" @@ -2128,7 +2591,7 @@ def homomorphism_basis(self, other, b=None, r=None): """ M1 = self.riemann_matrix() M2 = other.riemann_matrix() - return integer_matrix_relations(M2,M1,b,r) + return integer_matrix_relations(M2, M1, b, r) def tangent_representation_numerical(self, Rs, other=None): r""" @@ -2241,7 +2704,7 @@ def algebraize_element(alpha): rt = tup[0] if (alpha - CC(rt)).abs() < epscomp: return rt - raise AssertionError('No close root found while algebraizing') + raise AssertionError("No close root found while algebraizing") def algebraize_matrices(Ts): nr = Ts[0].nrows() @@ -2252,7 +2715,7 @@ def algebraize_matrices(Ts): L = eltsAlg[0].parent() TsAlgL = [] for i in range(len(Ts)): - TAlgL = [eltsAlg[j] for j in range(i*nr*nc, (i + 1)*nr*nc)] + TAlgL = [eltsAlg[j] for j in range(i * nr * nc, (i + 1) * nr * nc)] TsAlgL.append(Matrix(L, nr, nc, TAlgL)) return TsAlgL @@ -2283,13 +2746,17 @@ def rosati_involution(self, R): sage: S.rosati_involution(S.rosati_involution(Rs[1])) == Rs[1] True """ + def standard_symplectic_matrix(n): one = Matrix.identity(n) zero = Matrix.zero(n) return Matrix.block([[zero, -one], [one, zero]]) + g = self.genus - if not(R.nrows() == 2 * g == R.ncols()): - raise AssertionError("Matrix is not the homology representation of an endomorphism") + if not (R.nrows() == 2 * g == R.ncols()): + raise AssertionError( + "Matrix is not the homology representation of an endomorphism" + ) J = standard_symplectic_matrix(g) return -J * R.transpose() * J @@ -2347,7 +2814,7 @@ def symplectic_isomorphisms(self, other=None, hom_basis=None, b=None, r=None): Rs = self.homomorphism_basis(other=other, b=b, r=r) r = len(Rs) g = self.genus - A = PolynomialRing(QQ, r, 'x') + A = PolynomialRing(QQ, r, "x") gensA = A.gens() # Use that the trace is positive definite; we could also put this as an # extra condition when determining the endomorphism basis to speed up @@ -2355,10 +2822,14 @@ def symplectic_isomorphisms(self, other=None, hom_basis=None, b=None, r=None): R = sum(gensA[i] * Rs[i].change_ring(A) for i in range(r)) tr = (R * self.rosati_involution(R)).trace() # Condition tr = 2 g creates ellipsoid - M = Matrix(ZZ, r, r, [tr.derivative(gen1).derivative(gen2) - for gen1 in gensA for gen2 in gensA]) - vs = M.__pari__().qfminim(4*g)[2].sage().transpose() - vs = [v for v in vs if v * M * v == 4*g] + M = Matrix( + ZZ, + r, + r, + [tr.derivative(gen1).derivative(gen2) for gen1 in gensA for gen2 in gensA], + ) + vs = M.__pari__().qfminim(4 * g)[2].sage().transpose() + vs = [v for v in vs if v * M * v == 4 * g] vs += [-v for v in vs] RsIso = [] for v in vs: @@ -2422,6 +2893,1021 @@ def __add__(self, other): """ return RiemannSurfaceSum([self, other]) + def _integrate_differentials_iteratively( + self, upstairs_edge, cutoff_individually=False, raise_errors=True, prec=None + ): + r""" + Integrate the cohomology basis along a straight line edge. + + The cohomology basis is integrated along a straight line using a version + of the double exponential quadrature. This method of integrating the + cohomology basis is especially useful when integrating out to infinity, + or near roots of ``self._dfdw``. In order to aid with convergence of the + method, two main modification to a standard integrator are made, most + importantly of which is the truncation of the integral near branch points, + where the first term in the Puiseux series of the integrands are used to + approximately bound the integral. The ``cutoff_individually`` parameter + allows the user to set whether that truncation is uniform over all the + integrands, which improves the complexity of the algorithm, but loses + the ability to gain benefits where integrands vanish to a high order at + the branchpoint. + + INPUT: + + - ``upstairs_edge`` -- tuple. A tuple of complex numbers of the form + ``((z_start, w_start), z_end)`` specifying the path to integrate + along, where ``z_start`` may be infinite, in which case ``w_start`` + must be an integer specifying the branch. + + - ``cutoff_individually`` -- boolean (default: False). Whether to truncate + the integrand uniformly or not. If ``None``, then no truncation is + applied. + + - ``raise_errors`` -- boolean (default: True). By default the code uses + convergence errors to ensure any answers returned are accurate. This + can be turned off to return answers faster that are not necessarily + correct. + + - ``prec`` -- integer (default: ``self._prec``). The precision to try + and achieve, defined as `2^{-\text{prec}+3}`. + + OUTPUT: + + Tuple ``(I, gs)`` where ``I`` is the vector of integrals, and ``gs`` are + the values of the differentials at ``z_end``. + + EXAMPLES: + + We know that for the surface given by `w^2-z^4-1` a cohomology basis is + given by `\frac{dz}{2w}`. One can verify analytically that + `\int_0^1 frac{dt}{\sqrt{1-t^4}}=\frac{\sqrt{\pi}\Gamma(5/4)}{\Gamma(3/4)}`, + and we check this with the integrator, being careful with signs:: + + sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface + sage: R. = QQ[] + sage: S = RiemannSurface(w^2+z^4-1, prec=100) + sage: branch = 0 + sage: eps = S._RR(2)**(-S._prec) + sage: z_start = 1-eps + sage: z_end = 0 + sage: w_start = S.w_values(z_start)[0] + sage: s = sign(w_start) + sage: u_edge = ((z_start, w_start), z_end) + sage: J, _ = S._integrate_differentials_iteratively(u_edge) + sage: bool(J[0]+s*S._RR(sqrt(pi)*gamma(5/4)/gamma(3/4)/2)<1e-10) + True + + .. NOTE:: + + The cutoff methodology is calculating the first term in the Puiseux + series of the differentials about z_start. In future it may be + desirable to extend this further and use the truncated Puiseux series + entirely to integrate the differentials. + """ + (z_start, w_start), z_end = upstairs_edge + z_start = self._CC(z_start) + z_end = self._CC(z_end) + + if z_end == self._CC(Infinity): + raise NotImplementedError + + _, bounding_data_list = self._cohomology_basis_bounding_data + mp_list = [bd[2] for bd in bounding_data_list] + + # Parameterise so zbar=0 corresponds to z=z_start + mp_list = [reparameterize_differential_minpoly(mp, z_start) for mp in mp_list] + + # Depending on whether we have reparameterized about infinity or not, + # we initialise some values we will need in the calculation, inclduing + # the function `initalize', which at a given value of zbar, calculates + # the starting value for the i-th differential so it can be iterated + # from via homotopy continuation. + if z_start == self._CC(Infinity): + CCzg = PolynomialRing(self._CC, ["zbar", "gbar"]) + mp_list = [CCzg(mp) for mp in mp_list] + J = 1 / z_end + endscale = -(z_end**(-2)) + + def initialise(z, i): + DF = ComplexField(2 * self._prec) + DFw = PolynomialRing(DF, "wbar") + z = DF(z) + R = DF(z**(-1)) + wR = DFw(self.f(R, DFw.gen(0))).roots(multiplicities=False)[w_start] + newg = -(R**2) * self.cohomology_basis()[i](R, wR) / self._dfdw(R, wR) + err = mp_list[i](z, newg).abs() + if err > tau: + rs = mp_list[i](z, DFw.gen(0)).roots(multiplicities=False) + sb = find_closest_element(newg, rs) + newg = rs[sb] + return newg + + else: + CCzg = mp_list[0].parent() + J = z_end - z_start + endscale = 1 + + def initialise(z, i): + newg = self.cohomology_basis()[i](z_start, w_start) / self._dfdw( + z_start, w_start + ) + err = mp_list[i](z, newg).abs() + if err > tau: + rs = mp_list[i](z, self._CCw.gen(0)).roots(multiplicities=False) + sb = find_closest_element(newg, rs) + newg = rs[sb] + return newg + + # As multiple calls of the minimal polynomial and it's derivative will + # be required for the homotopy continuaiton, we create fast-callable + # versions of these. + fc_mp_list = [fast_callable(mp, domain=self._CC) for mp in mp_list] + fc_dmp_list = [ + fast_callable(mp.derivative(CCzg.gen(1)), domain=self._CC) for mp in mp_list + ] + + if prec is None: + prec = self._prec + # tau here is playing the role of the desired error. + tau = self._RR(2)**(-prec + 3) + one = self._RR(1) + la = self._RR.pi() / 2 + + # Cutoffs are used to allow us to not have to integrate as close into + # a singularity as we might otherwise have to, by knowing that we can + # truncate the integration interval and only introduce a finite error + # that can be bounded by knowledge of the asymptotics of the integrands, + # which we have from their minimal polynomials. This is really a + # precursor to what would be ideal to implement eventually, namely + # a method that uses Puiseux series to integrate into singularities. + # We allow for cutoffs to be tailored to each integrand, or we take a + # uniform value. + if cutoff_individually is None: + cutoffs = [0] + cutoff_individually = False + else: + cutoffs = [] + A = PolynomialRing(self._CC, "xyz") + aes = [] + for mp in mp_list: + d = mp.dict() + mp = sum( + [ + d[k] * CCzg.gen(0)**k[0] * CCzg.gen(1)**k[1] + for k in d.keys() + if d[k].abs() > tau + ] + ) + cst = min([iz for (iz, ig) in d.keys() if ig == 0]) + a = QQ(max([(cst - iz) / ig for (iz, ig) in d.keys() if ig > 0])) + sum_coeffs = sum( + [ + d[k] * A.gen(0)**k[1] + for k in d.keys() + if ((k[1] == 0 and k[0] == cst) or k[1] * a + k[0] - cst == 0) + ] + ) + G = max([r.abs() for r in sum_coeffs.roots(multiplicities=False)]) + cutoffs.append(((a + 1) * tau / G)**(1 / self._CC(a + 1)) / J.abs()) + aes.append(a) + cutoff_individually = bool( + not all(ai <= 0 for ai in aes) and cutoff_individually + ) + + # The `raise_errors' variable toggles what we do in the event that + # newton iteration hasn't converged to the desired precision in a + # fixed number of steps, here set to 100. + # If the default value of True is taken, then the failure to converge + # raises an error. If the value of False is taken, this failure to + # converge happens silently, thus allowing the user to get *an* + # answer out of the integration, but numerical imprecision is to be + # expected. As such, we set the maximum number of steps in the sequence + # of DE integrations to be lower in the latter case. + if raise_errors: + n_steps = self._prec - 1 + else: + n_steps = 15 + + V = VectorSpace(self._CC, self.genus) + h = one + Nh = (-lambert_w(-1, -tau / 2) / la).log().ceil() + h0 = Nh * h + + # Depending on how the cutoffs were defined, we now create the function + # which calculates the integrand we want to integrate via double- + # exponential methods. This will get the value at the next node by + # homotopy-continuing from the last node value. There is also a slight + # technical condition which implements the cutoffs. + if cutoff_individually: + z_fc_list = list(zip(fc_mp_list, fc_dmp_list)) + + def fv(hj, previous_estimate_and_validity): + u2 = la * hj.sinh() + t = 1 / (2 * u2.exp() * u2.cosh()) + z0 = J * t + outg = [] + valid = self.genus * [True] + previous_estimate, validity = previous_estimate_and_validity + for i in range(self.genus): + co = cutoffs[i] + pv = validity[i] + if t < co: + outg.append(0) + valid[i] = False + elif not pv: + outg.append(initialise(z0, i)) + else: + F, dF = z_fc_list[i] + oldg = previous_estimate[i] + delta = F(z0, oldg) / dF(z0, oldg) + Ndelta = delta.norm() + newg = oldg - delta + for j in range(100): + new_delta = F(z0, newg) / dF(z0, newg) + Nnew_delta = new_delta.norm() + if (new_delta == 0) or ( + Nnew_delta >= Ndelta + and (Ndelta.sign_mantissa_exponent()[2] + self._prec) + < newg.norm().sign_mantissa_exponent()[2] + ): + outg.append(newg) + break + delta = new_delta + Ndelta = Nnew_delta + newg -= delta + else: + if raise_errors: + raise ConvergenceError("Newton iteration fails to converge") + else: + outg.append(newg) + fj = V(outg) + u1 = la * hj.cosh() + w = u1 / (2 * u2.cosh()**2) + return (fj, valid), w * fj + + f0, v0 = fv(h0, (self.genus * [0], self.genus * [False])) + else: + cutoffs.append(1) + cutoff = min(cutoffs) + cutoff_z = J * cutoff + J -= cutoff_z + + def fv(hj, previous_estimate): + u2 = la * hj.sinh() + t = 1 / (2 * u2.exp() * u2.cosh()) + z0 = cutoff_z + J * t + outg = [] + for F, dF, oldg in zip(fc_mp_list, fc_dmp_list, previous_estimate): + delta = F(z0, oldg) / dF(z0, oldg) + Ndelta = delta.norm() + newg = oldg - delta + for j in range(100): + new_delta = F(z0, newg) / dF(z0, newg) + Nnew_delta = new_delta.norm() + if (new_delta == 0) or ( + Nnew_delta >= Ndelta + and (Ndelta.sign_mantissa_exponent()[2] + self._prec) + < newg.norm().sign_mantissa_exponent()[2] + ): + outg.append(newg) + break + delta = new_delta + Ndelta = Nnew_delta + newg -= delta + else: + if raise_errors: + raise ConvergenceError("Newton iteration fails to converge") + else: + outg.append(newg) + fj = V(outg) + u1 = la * hj.cosh() + w = u1 / (2 * u2.cosh()**2) + return fj, w * fj + + u1, u2 = (la * h0.cosh(), la * h0.sinh()) + y, w = (1 / (2 * u2.exp() * u2.cosh()), u1 / (2 * u2.cosh() ** 2)) + z0 = cutoff_z + J * y + f0 = [initialise(z0, i) for i in range(self.genus)] + f0 = V(f0) + v0 = w * f0 + + D3_over_tau = v0.norm(Infinity) + D4 = D3_over_tau + results = [] + + # we now calculate the integral via double-exponential methods + # repeatedly halving the step size and then using a heuristic + # convergence check. The maximum number of steps allowed is + # currently set to make sure the step size does not fall below the + # resolution set by the binary precision used. + for k in range(n_steps): + hj = h0 + val = v0 + fj = f0 + for j in range(2 * Nh): + hj -= h + try: + fj, v = fv(hj, fj) + except ConvergenceError: + break + D3_over_tau = max(v.norm(Infinity), D3_over_tau) + val += v + if j == 2 * Nh - 1: + results.append(h * val) + D4 = max(D4, v.norm(Infinity)) + if len(results) > 2: + if results[-1] == results[-2] or results[2] == results[-3]: + D = tau + else: + D1 = (results[-1] - results[-2]).norm(Infinity) + D2 = (results[-1] - results[-3]).norm(Infinity) + D = min( + one, + max( + D1**(D1.log() / D2.log()), + D2**2, + tau * D3_over_tau, + D4, + tau, + ), + ) + + if D <= tau: + if cutoff_individually: + fj = fj[0] + return J * results[-1], endscale * fj + h /= 2 + Nh *= 2 + # Note that throughout this loop there is a return statement, intended + # to be activated when the sequence of integral approximations is + # deemed to have converged by the heuristic error. If this has no + # happened by the time we have gone through the process n_steps times, + # we have one final error handle. Again, this will throw an error if + # the raise_errors flag is true, but will just return the answer otherwise. + if raise_errors: + raise ConvergenceError("Newton iteration fails to converge") + + return (J * results[-1], endscale * fj) + + def _aj_based(self, P): + r""" + Return the Abel-Jacobi map to ``P`` from ``self._basepoint``. + + Computes a representative of the Abel-Jacobi map from ``self._basepoint`` + to ``P`` via a well-chosen vertex ``V``. The representative given will be + dependent on the path chosen. + + INPUT: + + - ``P`` -- tuple. A pair giving the endpoint of the integral, either in + the form ``(z, w)`` or ``(Infinity, branch)``, where in the latter case + we are using the convention that the `w` value over `\infty` is given by + the limit as ``z`` tends to `\infty` of ``self.w_values(z)[branch]``. + + OUTPUT: + + A vector of length ``self.genus``. + + EXAMPLES: + + As the output of ``_aj_based`` is difficult to interpret due to its path + dependency, we look at the output of :meth:`abel_jacobi`. We check for + two hyperelliptic curves that the Abel-Jacobi map between two branch + points is a 2-torsion point over the lattice. Note we must remember to + reduce over the period lattice, as results are path dependent:: + + sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface + sage: R. = QQ[] + sage: p = 100 + sage: S = RiemannSurface(y^2-x^3+1, prec=p) + sage: divisor = [(-1, (Infinity, 0)), (1, (1, 0))] + sage: AJ = S.abel_jacobi(divisor) + sage: AJx2 = [2*z for z in AJ] + sage: vector(AJx2).norm() # abs tol 1e-10 + 2.4286506478875809114000865640 + sage: bool(S.reduce_over_period_lattice(AJx2).norm() < 1e-10) + True + sage: S = RiemannSurface(y^2-x^4+1, prec=p) + sage: divisor = [(-1, (-1, 0)), (1, (1, 0))] + sage: AJ = S.abel_jacobi(divisor) + sage: AJx2 = [2*z for z in AJ] + sage: bool(S.reduce_over_period_lattice(AJx2).norm() < 1e-10) + True + + """ + ##### + fcd = self._fastcall_cohomology_basis + + if self._integration_method == "heuristic": + line_int = lambda edge: self.simple_vector_line_integral(edge, fcd) + else: + bd = self._cohomology_basis_bounding_data + line_int = lambda edge: self.rigorous_line_integral(edge, fcd, bd) + ##### + B = self._basepoint + zP, wP = P + + try: + Inf = bool(zP == zP.parent()(Infinity)) + except TypeError: + Inf = False + + if Inf: + zV = self._vertices[B[0]] + if zV == 0: + zV += 1 + upstairs_edge = (P, zV) + ci = bool(self._CC(Infinity) in self._differentials_branch_locus) + AJ, endgs = self._integrate_differentials_iteratively( + upstairs_edge, cutoff_individually=ci + ) + AJ = -AJ + g0e = endgs[0] + ws = self.w_values(zV) + g0s = [self.cohomology_basis()[0](zV, wi) / self._dfdw(zV, wi) for wi in ws] + W_index = find_closest_element(g0e, g0s) + if ( + g0e + - self.cohomology_basis()[0](zV, ws[W_index]) + / self._dfdw(zV, ws[W_index]) + ).abs() > 1e-10: + raise ConvergenceError( + "Integrand continuation failed to get representative values, higher precision required." + ) + V_index = B[0] + else: + zP = self._CC(zP) + wP = self._CC(wP) + V_index = find_closest_element(zP, self._vertices) + + if zP == self._vertices[V_index]: + W_index = find_closest_element(wP, self._wvalues[V_index]) + AJ = 0 + else: + b_index = find_closest_element(zP, self.branch_locus) + b = self.branch_locus[b_index] + # bl = self.branch_locus+self._differentials_branch_locus + # b_index = find_closest_element(zP, bl) + # b = bl[b_index] + + scale = max(b.abs() for b in self.branch_locus) + d1 = self._CC(1e-2) * scale + + # We choose the first vertex we want to go to. + # If the closest vertex is closer than the nearest branch point, just take that vertex + # otherwise we need something smarter. + delta = self._RR(2)**(-self._prec + 1) + if not ( + (zP - self._vertices[V_index]).abs() < (zP - b).abs() + or (zP - b).abs() <= delta + ): + region = self.voronoi_diagram.regions[ + self.voronoi_diagram.point_region[b_index] + ] + args = [ + (self._vertices[i] - zP).argument() - (b - zP).argument() + for i in region + ] + suitable_vertex_indices = [ + region[i] + for i in range(len(region)) + if args[i].abs() - self._RR.pi() / 2 >= -self._RR(1e-15) + ] + suitable_vertices = [ + self._vertices[i] for i in suitable_vertex_indices + ] + if suitable_vertices == []: + raise ValueError( + "There is no satisfactory choice of V for zP={}".format(zP) + ) + V_index = suitable_vertex_indices[ + find_closest_element(zP, suitable_vertices) + ] + ##### + zV = self._vertices[V_index] + + if (zP - b).abs() >= d1 or b in self._differentials_branch_locus: + wP_index = find_closest_element(wP, self.w_values(zP)) + d_edge = (zP, zV) + u_edge = ((zP, wP_index), (zV,)) + initial_continuation = self.homotopy_continuation(d_edge) + AJ = -line_int(u_edge) + + w_end = initial_continuation[-1][1][wP_index] + W_index = find_closest_element(w_end, self._wvalues[V_index]) + else: + zs = zP + ws = wP + + ##### + # Here we need a block of code to change the vertex if the path + # from zP to zV would go through a ramification point of the integrands + fl = [ + c + for c in self._differentials_branch_locus + if not c == self._CC(Infinity) + ] + ts = [ + ((c - zP) * (zV - zP).conjugate()).real() + / (zP - zV).norm()**2 + for c in fl + ] + ds = [ + (fl[i] - zP - ts[i] * (zV - zP)).abs() + for i in range(len(ts)) + if (ts[i] >= 0 and ts[i] <= 1) + ] + while len(ds) >= 1 and min(ds) < delta: + V_index = suitable_vertex_indices.pop() + zV = self._vertices[V_index] + ts = [ + ((c - zP) * (zV - zP).conjugate()).real() + / (zP - zV).norm()**2 + for c in fl + ] + ds = [ + (fl[i] - zP - ts[i] * (zV - zP)).abs() + for i in range(len(ts)) + if (ts[i] >= 0 and ts[i] <= 1) + ] + ##### + + while self._dfdw(zs, ws).abs() == 0: + zs = zs + delta * (zV - zs) / (zV - zs).abs() / 2 + ws_list = self.w_values(zs) + wP_index = find_closest_element(ws, ws_list) + ws = ws_list[wP_index] + upstairs_edge = ((zs, ws), zV) + AJ, endgs = self._integrate_differentials_iteratively( + upstairs_edge, cutoff_individually=False + ) + AJ = -AJ + g0e = endgs[0] + + ws = self.w_values(zV) + g0s = [ + self.cohomology_basis()[0](zV, wi) / self._dfdw(zV, wi) + for wi in ws + ] + W_index = find_closest_element(g0e, g0s) + if ( + g0e + - self.cohomology_basis()[0](zV, ws[W_index]) + / self._dfdw(zV, ws[W_index]) + ).abs() > 1e-10: + raise ConvergenceError( + "Integrand continuation failed to get representative values, higher precision required." + ) + + uV_index = (V_index, W_index) + ##### + G = self.upstairs_graph() + path = G.shortest_path(B, uV_index) + edges = [(path[i], path[i + 1]) for i in range(len(path) - 1)] + ##### + for e in edges: + if e[1][0] > e[0][0]: + s = 1 + else: + s = -1 + e = tuple(reversed(e)) + try: + AJ += s * self._integral_dict[e] + except KeyError: + Ie = line_int(e) + self._integral_dict[e] = Ie + AJ += s * Ie + return AJ + + def abel_jacobi(self, divisor, verbose=False): + r""" + Return the Abel-Jacobi map of ``divisor``. + + Return a representative of the Abel-Jacobi map of a divisor with basepoint + ``self._basepoint``. + + INPUT: + + - ``divisor`` -- list. A list with each entry a tuple of the form ``(v, P)``, + where ``v`` is the valuation of the divisor at point ``P``, ``P`` as per + the input to :meth:`_aj_based`. + + - ``verbose`` -- logical (default: False). Whether to report the progress + of the computation, in terms of how many elements of the list ``divisor`` + have been completed. + + OUTPUT: + + A vector of length ``self.genus``. + + EXAMPLES: + + We can test that the Abel-Jacobi map between two branchpoints of a + superelliptic curve of degree `p` is a `p`-torsion point in the Jacobian:: + + sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface + sage: R. = QQ[] + sage: p = 4 + sage: S = RiemannSurface(y^p-x^4+1, prec=100) + sage: divisor = [(-1, (-1, 0)), (1, (1, 0))] + sage: AJ = S.abel_jacobi(divisor) # long time (15 seconds) + sage: AJxp = [p*z for z in AJ] # long time + sage: bool(S.reduce_over_period_lattice(AJxp).norm()<1e-7) # long time + True + """ + if isinstance(divisor, FunctionFieldDivisor): + divisor = self.divisor_to_divisor_list(divisor) + ans = 0 + n = len(divisor) + for i in range(n): + v, p = divisor[i] + if verbose: + print("starting computation for p = {}".format(p)) + ans += v * self._aj_based(p) + if verbose: + print( + "Done, {}% complete".format(numerical_approx(100 * (i + 1) / n, 11)) + ) + return ans + + def reduce_over_period_lattice( + self, vector, method="ip", b=None, r=None, normalised=False + ): + r""" + Reduce a vector over the period lattice. + + Given a vector of length ``self.genus``, this method returns a vector + in the same orbit of the period lattice that is short. There are two + possible methods, ``'svp'`` which returns a certified shortest vector, + but can be much slower for higher genus curves, and ``'ip'``, which is + faster but not guaranteed to return the shortest vector. In general the + latter will perform well when the lattice basis vectors are of similar + size. + + INPUT: + + - ``vector`` -- vector. A vector of length ``self.genus`` to reduce over + the lattice. + + - ``method`` -- string (default: ``'ip'``). String specifying the method + to use to reduce the vector. THe options are ``'ip'`` and ``'svp'``. + + - ``b`` -- integer (default provided): as for + :meth:`homomorphism_basis`, and used in its invocation if + (re)calculating said basis. + + - ``r`` -- integer (default: ``b/4``). as for + :meth:`homomorphism_basis`, and used in its invocation if + (re)calculating said basis. + + - ``normalised`` -- logical (default: ``False``). Whether to use the + period matrix with the differentials normalised s.t. the `A`-matrix + is the identity. + + OUTPUT: + + Complex vector of length ``self.genus`` in the same orbit as ``vector`` + in the lattice. + + EXAMPLES: + + We can check that the lattice basis vectors themselves are reduced to + zero:: + + sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface + sage: R. = QQ[] + sage: S = RiemannSurface(y^2-x^5+1) + sage: epsilon = S._RR(2)^(-S._prec+1) + sage: for vector in S.period_matrix().columns(): + ....: print(bool(S.reduce_over_period_lattice(vector).norm() 2**(self._prec - 4): + raise ValueError("insufficient precision for b=%s" % b) + + def C2Z(v): + vR = [(S * z.real_part()).round() for z in v] + vR += [(S * z.imag_part()).round() for z in v] + return vR + + M = Matrix( + ZZ, 2 * self.genus, 2 * self.genus, [C2Z(c) for c in PM.columns()] + ) + u = C2Z(vector) + L = IntegerLattice(M) + u = VR(u) - VR(L.closest_vector(u)) + reduced = VC( + [self._CC(u[i] + I * u[i + self.genus]) / S for i in range(self.genus)] + ) + + elif method == "ip": + + def C2R(v): + return VR([z.real_part() for z in v] + [z.imag_part() for z in v]) + + u = C2R(vector) + basis_vecs = [C2R(c) for c in PM.columns()] + M = Matrix([[ei.dot_product(ej) for ei in basis_vecs] for ej in basis_vecs]) + v_dot_e = VR([u.dot_product(e) for e in basis_vecs]) + coeffs = M.solve_right(v_dot_e) + u -= sum([t.round() * e for t, e in zip(coeffs, basis_vecs)]) + reduced = VC( + [self._CC(u[i] + I * u[i + self.genus]) for i in range(self.genus)] + ) + else: + raise ValueError("Must give a valid method.") + + return reduced + + def curve(self): + r""" + Return the curve from which this Riemann surface is obtained. + + Riemann surfaces explicitly obtained from a curve return that same object. + For others, the curve is constructed and cached, so that an identical curve is + returned upon subsequent calls. + + OUTPUT: + + Curve from which Riemann surface is obtained. + + EXAMPLES:: + + sage: R. = QQ[] + sage: C = Curve( y^3+x^3-1) + sage: S = C.riemann_surface() + sage: S.curve() is C + True + """ + if self._curve is None: + self._curve = Curve(self.f) + return self._curve + + def places_at_branch_locus(self): + r""" + Return the places above the branch locus. + + Return a list of the of places above the branch locus. This must be + done over the base ring, and so the places are given in terms of the + factors of the discriminant. Currently, this method only works when + ``self._R.base_ring() == QQ`` as for other rings, the function field + for ``Curve(self.f)`` is not implemented. To go from these divisors to + a divisor list, see :meth:`divisor_to_divisor_list`. + + OUTPUT: + + List of places of the functions field ``Curve(self.f).function_field()``. + + EXAMPLES:: + + sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface + sage: R. = QQ[] + sage: S = RiemannSurface(25*(x^4+y^4+1) - 34*(x^2*y^2+x^2+y^2)) + sage: S.places_at_branch_locus() + [Place (x - 2, (x - 2)*y, y^2 - 17/5, y^3 - 17/5*y), + Place (x + 2, (x + 2)*y, y^2 - 17/5, y^3 - 17/5*y), + Place (x - 1/2, (x - 1/2)*y, y^2 - 17/20, y^3 - 17/20*y), + Place (x + 1/2, (x + 1/2)*y, y^2 - 17/20, y^3 - 17/20*y), + Place (x^4 - 34/25*x^2 + 1, y, y^2, y^3), + Place (x^4 - 34/25*x^2 + 1, (x^4 - 34/25*x^2 + 1)*y, y^2 - 34/25*x^2 - 34/25, y^3 + (-34/25*x^2 - 34/25)*y)] + """ + BP = [] + K = self._R.base_ring() + if K is not QQ: + raise NotImplementedError + C = self.curve() + KC = C.function_field() + g0, g1 = self._R.gens() + Kb = FunctionField(K, str(g0)) + MO = Kb.maximal_order() + BP = [] + for x in self._discriminant.factor(): + fac = x[0](g0, 0) + p0 = MO.ideal(fac).place() + BP += KC.places_above(p0) + return BP + + def strong_approximation(self, divisor, S): + r""" + Apply the method of strong approximation to a divisor. + + As described in [Neu2018]_, apply the method of strong approximation to + ``divisor`` with list of places to avoid ``S``. Currently, this method + only works when ``self._R.base_ring() == QQ`` as for other rings, the function + field for ``Curve(self.f)`` is not implemented. + + INPUT: + + - ``divisor`` -- an element of ``Curve(self.f).function_field().divisor_group()`` + + - ``S`` -- list of places to avoid + + OUTPUT: + + A tuple ``(D, B)``, where ``D`` is a new divisor, linearly equivalent + to ``divisor``, but not intersecting ``S``, and ``B`` is a list of tuples + ``(v, b)`` where ``b`` are the functions giving the linear equivalence, + added with multiplicity ``v``. + + EXAMPLES:: + + sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface + sage: R. = QQ[] + sage: S = RiemannSurface(y^2-x^3+1) + sage: avoid = Curve(S.f).places_at_infinity() + sage: D = 1*avoid[0] + sage: S.strong_approximation(D, avoid) + (- Place (x - 2, (x - 2)*y) + + Place (x - 1, y) + + Place (x^2 + x + 1, y), + [(1, (1/(x - 2))*y)]) + """ + # One would standardly expect to run this with + # S = Curve(self.f).places_at_infinity() + # or + # S = Curve(self.f).places_at_infinity()+self.places_at_branch_locus() + # + # To avoid current implementation issues with going between divisors + # and divisor lists, we implement a method that handles only divisors + K = self._R.base_ring() + if not K == QQ: + raise NotImplementedError + C = self.curve() + KC = C.function_field() + g0, g1 = self._R.gens() + Kb = FunctionField(K, str(g0)) + MO = Kb.maximal_order() + + D_base = -sum(S) + + rr = self._vertices[self._basepoint[0]].real() + rr = rr.ceil() + Fac = g0 - K(rr) + p0 = MO.ideal(Fac).place() + q0 = KC.places_above(p0)[0] + + new_divisor = divisor + B = [] + for p in divisor.support(): + if p in S: + v = divisor.valuation(p) + i = S.index(p) + Q = S[i] + D = D_base + Q + if not D: + ios = self.genus + else: + ios = len(D.basis_differential_space()) + while ios > 0: + D += q0 + ios = len(D.basis_differential_space()) + LD = D.function_space() + V = LD[0] + a = LD[1] + b = 0 + for s in S: + LDps = (D + s).function_space() + Vps = LDps[0] + ebd = [LDps[2](a(g)) for g in V.gens()] + U = Vps.span(ebd) + Quot = Vps.quotient(U) + bs = LDps[1](Quot.lift(Quot.basis()[0])) + b += bs + B.append((v, b)) + new_divisor += v * b.divisor() + return new_divisor, B + + def divisor_to_divisor_list(self, divisor, eps=None): + r""" + Turn a divisor into a list for :meth:`abel_jacobi`. + + Given ``divisor`` in ``Curve(self.f).function_field().divisor_group()``, + consisting of places above finite points in the base, return an equivalent + divisor list suitable for input into :meth:`abel_jacboi`. + + INPUT: + + - ``divisor`` -- an element of ``Curve(self.f).function_field().divisor_group()`` + - ``eps`` -- real number (optional); tolerance used to determine whether a complex + number is close enough to a root of a polynomial + + OUTPUT: + + A list with elements of the form ``(v, (z, w))`` representing the finite places. + + EXAMPLES:: + + sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface + sage: R. = QQ[] + sage: S = RiemannSurface(y^2-x^3+1) + sage: D = sum(S.places_at_branch_locus()) + sage: S.divisor_to_divisor_list(D) + [(1, (1.00000000000000, 0.000000000000000)), + (1, (-0.500000000000000 - 0.866025403784439*I, 0.000000000000000)), + (1, (-0.500000000000000 + 0.866025403784439*I, 0.000000000000000))] + + .. TODO:: + + Currently this method can only handle places above finite points in + the base. It would be useful to extend this to allow for places at + infinity. + """ + # If this error bound is too restrictive, this method might fail and + # not return. One might want to change the way this error is handled. + if not eps: + eps = self._RR(2)**(-self._prec + 3) + dl = [] + + PZ = PolynomialRing(self._R.base(), "z").fraction_field() + RF = PolynomialRing(PZ, "w") + + for d in divisor.support(): + if d.is_infinite_place(): + raise NotImplementedError( + "Conversion of infinite places not implemented yet." + ) + v = divisor.valuation(d) + gs = d._prime.gens() + + g0 = self._R(gs[0]) + gis = [ + sum([PZ(gi.list()[i]) * RF.gen()**i for i in range(len(gi.list()))]) + for gi in gs[1:] + ] + + rs = self._CCz(g0).roots() + rys = [] + + for r, m in rs: + ys = [] + for gi in gis: + # This test is a bit clunky, it surely can be made more efficient. + if len(ys): + ers = min([gi(y, r).abs() for y in ys]) + else: + ers = 1 + + if not ers <= eps: + poly = self._CCw(gi(self._CCw.gen(0), r)) + if poly == 0: + nys = [] + else: + nys = poly.roots() + ys.extend(ny[0] for ny in nys) + rys.extend((v * m * n, (r, y)) for y, n in nys) + + if rys: + dl.extend(rys) + else: + for r, m in rs: + ys = self._CCw(self.f(r, self._CCw.gen(0))).roots() + dl.extend([(v * m * n, (r, y)) for y, n in ys]) + if not sum([v[0] for v in dl]) == divisor.degree(): + raise ValueError( + "numerical instability, list of wrong degree, returning list {}".format( + dl + ) + ) + return dl + def integer_matrix_relations(M1, M2, b=None, r=None): r""" @@ -2467,44 +3953,52 @@ def integer_matrix_relations(M1, M2, b=None, r=None): sage: [((m[:,:2]^(-1)*m)[:,2:]-M2).norm() < 1e-13 for m in M1t] [True, True] """ - if not(M1.is_square() and M2.is_square()): + if not (M1.is_square() and M2.is_square()): raise ValueError("matrices need to be square") - prec = min(M1.base_ring().precision(),M2.base_ring().precision()) - H = max(max(abs(m.real_part()) for m in M1.list() + M2.list()), - max(abs(m.imag_part()) for m in M1.list() + M2.list())) + prec = min(M1.base_ring().precision(), M2.base_ring().precision()) + H = max( + max(abs(m.real_part()) for m in M1.list() + M2.list()), + max(abs(m.imag_part()) for m in M1.list() + M2.list()), + ) if b is None: - b = prec-5-H.log2().floor() + b = prec - 5 - H.log2().floor() if r is None: - r = b//4 + r = b // 4 S = 2**b - if H*S > 2**(prec-4): + if H * S > 2**(prec - 4): raise ValueError("insufficient precision for b=%s" % b) g1 = M1.ncols() g2 = M2.ncols() - CC = M1.base_ring() if (M1.base_ring().precision() <= M2.base_ring().precision()) else M2.base_ring() - V = ["%s%s" % (n, i) for n in ["a","b","c","d"] for i in range(1,1+g1*g2)] + CC = ( + M1.base_ring() + if (M1.base_ring().precision() <= M2.base_ring().precision()) + else M2.base_ring() + ) + V = ["%s%s" % (n, i) for n in ["a", "b", "c", "d"] for i in range(1, 1 + g1 * g2)] R = PolynomialRing(CC, V) vars = R.gens() - A = Matrix(R, g1, g2, vars[:g1*g2]) - B = Matrix(R, g1, g2, vars[g1*g2:2*g1*g2]) - C = Matrix(R, g1, g2, vars[2*g1*g2:3*g1*g2]) - D = Matrix(R, g1, g2, vars[3*g1*g2:4*g1*g2]) - W = ((M1*A+B) - (M1*C+D)*M2).list() + A = Matrix(R, g1, g2, vars[: g1 * g2]) + B = Matrix(R, g1, g2, vars[g1 * g2 : 2 * g1 * g2]) + C = Matrix(R, g1, g2, vars[2 * g1 * g2 : 3 * g1 * g2]) + D = Matrix(R, g1, g2, vars[3 * g1 * g2 : 4 * g1 * g2]) + W = ((M1 * A + B) - (M1 * C + D) * M2).list() vars = R.gens() - mt = Matrix(ZZ,[[1 if i == j else 0 for j in range(4*g1*g2)] + - [(S*w.monomial_coefficient(vars[i]).real_part()).round() for w in W] + - [(S*w.monomial_coefficient(vars[i]).imag_part()).round() for w in W] for i in range(len(vars))]) + mt = Matrix(ZZ, [[1 if i == j else 0 for j in range(4 * g1 * g2)] + + [(S * w.monomial_coefficient(vi).real_part()).round() for w in W] + + [(S * w.monomial_coefficient(vi).imag_part()).round() for w in W] + for i, vi in enumerate(vars)]) # we compute an LLL-reduced basis of this lattice: mtL = mt.LLL() def vectomat(v): - A = Matrix(g1,g2,v[:g1*g2].list()) - B = Matrix(g1,g2,v[g1*g2:2*g1*g2].list()) - C = Matrix(g1,g2,v[2*g1*g2:3*g1*g2].list()) - D = Matrix(g1,g2,v[3*g1*g2:4*g1*g2].list()) + A = Matrix(g1, g2, v[: g1 * g2].list()) + B = Matrix(g1, g2, v[g1 * g2 : 2 * g1 * g2].list()) + C = Matrix(g1, g2, v[2 * g1 * g2 : 3 * g1 * g2].list()) + D = Matrix(g1, g2, v[3 * g1 * g2 : 4 * g1 * g2].list()) return D.augment(B).stack(C.augment(A)) + c = 2**r - return [vectomat(v) for v in mtL if all(a.abs() <= c for a in v[g1*g2:])] + return [vectomat(v) for v in mtL if all(a.abs() <= c for a in v[g1 * g2 :])] class RiemannSurfaceSum(RiemannSurface): @@ -2529,6 +4023,7 @@ class RiemannSurfaceSum(RiemannSurface): sage: len(SC.homomorphism_basis(S1+S2)) 2 """ + def __init__(self, L): r""" TESTS:: @@ -2551,13 +4046,13 @@ def __init__(self, L): g = s.genus PM = s.period_matrix() PM1 = PM[:g, :g] - PM2 = PM[:g, g:2*g] + PM2 = PM[:g, g : 2 * g] tau = s.riemann_matrix() for s in it: g = s.genus PM = s.period_matrix() PM1 = PM1.block_sum(PM[:g, :g]) - PM2 = PM2.block_sum(PM[:g, g:2*g]) + PM2 = PM2.block_sum(PM[:g, g : 2 * g]) tau = tau.block_sum(s.riemann_matrix()) self.PM = block_matrix([[PM1, PM2]], subdivide=False) self.tau = tau diff --git a/src/sage/schemes/toric/divisor_class.pyx b/src/sage/schemes/toric/divisor_class.pyx index dfbd518e951..1fde30da963 100644 --- a/src/sage/schemes/toric/divisor_class.pyx +++ b/src/sage/schemes/toric/divisor_class.pyx @@ -33,7 +33,7 @@ They behave much like ordinary vectors:: sage: D * E Traceback (most recent call last): ... - TypeError: cannot multiply two divisor classes! + TypeError: cannot multiply two divisor classes The only special method is :meth:`~ToricRationalDivisorClass.lift` to get a divisor representing a divisor class:: @@ -163,7 +163,7 @@ cdef class ToricRationalDivisorClass(Vector_rational_dense): sage: D * D Traceback (most recent call last): ... - TypeError: cannot multiply two divisor classes! + TypeError: cannot multiply two divisor classes We test standard behaviour:: @@ -223,13 +223,13 @@ cdef class ToricRationalDivisorClass(Vector_rational_dense): sage: c[0]._dot_product_(c[1]) Traceback (most recent call last): ... - TypeError: cannot multiply two divisor classes! + TypeError: cannot multiply two divisor classes sage: c[0] * c[1] # indirect doctest Traceback (most recent call last): ... - TypeError: cannot multiply two divisor classes! + TypeError: cannot multiply two divisor classes """ - raise TypeError("cannot multiply two divisor classes!") + raise TypeError("cannot multiply two divisor classes") def _latex_(self): r""" diff --git a/src/sage/schemes/toric/fano_variety.py b/src/sage/schemes/toric/fano_variety.py index be1f476e395..1bf2b79ad36 100644 --- a/src/sage/schemes/toric/fano_variety.py +++ b/src/sage/schemes/toric/fano_variety.py @@ -138,7 +138,8 @@ from sage.geometry.all import Cone, FaceFan, Fan, LatticePolytope from sage.misc.latex import latex from sage.misc.misc_c import prod -from sage.rings.all import (PolynomialRing, QQ) +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.rational_field import QQ from sage.rings.polynomial.multi_polynomial_ring import is_MPolynomialRing from sage.rings.polynomial.polynomial_ring import is_PolynomialRing diff --git a/src/sage/schemes/toric/homset.py b/src/sage/schemes/toric/homset.py index 52a0730ddee..b5c97e10f83 100644 --- a/src/sage/schemes/toric/homset.py +++ b/src/sage/schemes/toric/homset.py @@ -250,7 +250,7 @@ def _element_constructor_(self, x, check=True): return SchemeMorphism_polynomial_toric_variety(self, x, check=check) from sage.categories.map import Map - from sage.categories.all import Rings + from sage.categories.rings import Rings if isinstance(x, Map) and x.category_for().is_subcategory(Rings()): # x is a morphism of Rings assert x.domain() is self.codomain().coordinate_ring() diff --git a/src/sage/schemes/toric/weierstrass_covering.py b/src/sage/schemes/toric/weierstrass_covering.py index 7e141ebfbd9..fe3c45ed722 100644 --- a/src/sage/schemes/toric/weierstrass_covering.py +++ b/src/sage/schemes/toric/weierstrass_covering.py @@ -106,7 +106,7 @@ from sage.rings.integer_ring import ZZ from sage.modules.free_module_element import vector -from sage.rings.all import invariant_theory +from sage.rings.invariants.invariant_theory import invariant_theory from sage.schemes.toric.weierstrass import ( _partial_discriminant, _check_polynomial_P2, diff --git a/src/sage/sets/family.py b/src/sage/sets/family.py index 996eb106de6..c1bf734381c 100644 --- a/src/sage/sets/family.py +++ b/src/sage/sets/family.py @@ -38,6 +38,7 @@ from pprint import pformat, saferepr from collections.abc import Iterable +from sage.misc.abstract_method import abstract_method from sage.misc.cachefunc import cached_method from sage.structure.parent import Parent from sage.categories.enumerated_sets import EnumeratedSets @@ -325,7 +326,7 @@ def Family(indices, function=None, hidden_keys=[], hidden_function=None, lazy=Fa sage: f = Family({1:'a', 2:'b', 3:'c'}, lazy=True) Traceback (most recent call last): ... - ValueError: lazy keyword only makes sense together with function keyword ! + ValueError: lazy keyword only makes sense together with function keyword :: @@ -385,7 +386,7 @@ def Family(indices, function=None, hidden_keys=[], hidden_function=None, lazy=Fa "together with hidden_keys keyword !") if function is None: if lazy: - raise ValueError("lazy keyword only makes sense together with function keyword !") + raise ValueError("lazy keyword only makes sense together with function keyword") if isinstance(indices, dict): return FiniteFamily(indices) if isinstance(indices, (list, tuple) ): @@ -406,13 +407,14 @@ def Family(indices, function=None, hidden_keys=[], hidden_function=None, lazy=Fa return LazyFamily(indices, function, name) if lazy: - raise ValueError("lazy keyword is incompatible with hidden keys !") + raise ValueError("lazy keyword is incompatible with hidden keys") if hidden_function is None: hidden_function = function return FiniteFamilyWithHiddenKeys({i: function(i) for i in indices}, hidden_keys, hidden_function, keys=indices) + class AbstractFamily(Parent): """ The abstract class for family @@ -431,6 +433,45 @@ def hidden_keys(self): """ return [] + @abstract_method + def keys(self): + """ + Return the keys of the family. + + EXAMPLES:: + + sage: f = Family({3: 'a', 4: 'b', 7: 'd'}) + sage: sorted(f.keys()) + [3, 4, 7] + """ + + @abstract_method(optional=True) + def values(self): + """ + Return the elements (values) of this family. + + EXAMPLES:: + + sage: f = Family(["c", "a", "b"], lambda x: x + x) + sage: sorted(f.values()) + ['aa', 'bb', 'cc'] + """ + + def items(self): + """ + Return an iterator for key-value pairs. + + A key can only appear once, but if the function is not injective, values may + appear multiple times. + + EXAMPLES:: + + sage: f = Family([-2, -1, 0, 1, 2], abs) + sage: list(f.items()) + [(-2, 2), (-1, 1), (0, 0), (1, 1), (2, 2)] + """ + return zip(self.keys(), self.values()) + def zip(self, f, other, name=None): r""" Given two families with same index set `I` (and same hidden @@ -1346,7 +1387,8 @@ def __setstate__(self, state): class EnumeratedFamily(LazyFamily): r""" :class:`EnumeratedFamily` turns an enumerated set ``c`` into a family - indexed by the set `\{0,\dots, |c|-1\}`. + indexed by the set `\{0,\dots, |c|-1\}` (or ``NN`` if `|c|` is + countably infinite). Instances should be created via the :func:`Family` factory. See its documentation for examples and tests. @@ -1374,6 +1416,8 @@ def __init__(self, enumset): True sage: Family(Permutations()).keys() Non negative integers + sage: type(Family(NN)) + """ if enumset.cardinality() == Infinity: baseset = NonNegativeIntegers() diff --git a/src/sage/sets/image_set.py b/src/sage/sets/image_set.py index 2de224762ba..44a219a3f24 100644 --- a/src/sage/sets/image_set.py +++ b/src/sage/sets/image_set.py @@ -18,14 +18,16 @@ from typing import Iterator -from sage.structure.parent import Parent, is_Parent from sage.categories.map import is_Map from sage.categories.poor_man_map import PoorManMap from sage.categories.sets_cat import Sets from sage.categories.enumerated_sets import EnumeratedSets +from sage.misc.cachefunc import cached_method +from sage.rings.infinity import Infinity from sage.rings.integer import Integer from sage.modules.free_module import FreeModule from sage.structure.element import Expression +from sage.structure.parent import Parent, is_Parent from .set import Set_base, Set_add_sub_operators, Set_boolean_operators @@ -39,8 +41,33 @@ class ImageSubobject(Parent): .. MATH:: \{ f(x) | x \in X \} \subseteq Y. + + INPUT: + + - ``map`` -- a function + + - ``domain_subset`` -- the set `X`; optional if `f` has a domain + + - ``is_injective`` -- whether the ``map`` is injective: + - ``None`` (default): infer from ``map`` or default to ``False`` + - ``False``: do not assume that ``map`` is injective + - ``True``: ``map`` is known to be injective + - ``"check"``: raise an error when ``map`` is not injective + + - ``inverse`` -- a function (optional); a map from `f(X)` to `X` + + EXAMPLES:: + + sage: import itertools + sage: from sage.sets.image_set import ImageSubobject + sage: D = ZZ + sage: I = ImageSubobject(abs, ZZ, is_injective='check') + sage: list(itertools.islice(I, 10)) + Traceback (most recent call last): + ... + ValueError: The map from Integer Ring is not injective: 1 """ - def __init__(self, map, domain_subset, *, category=None, is_injective=None): + def __init__(self, map, domain_subset, *, category=None, is_injective=None, inverse=None): """ Initialize ``self``. @@ -99,9 +126,46 @@ def map(arg): Parent.__init__(self, category=category) self._map = map + self._inverse = inverse self._domain_subset = domain_subset self._is_injective = is_injective + def _element_constructor_(self, x): + """ + EXAMPLES:: + + sage: import itertools + sage: from sage.sets.image_set import ImageSubobject + sage: D = ZZ + sage: I = ImageSubobject(lambda x: 2 * x, ZZ, inverse=lambda x: x/2) + sage: I(8/2) + 4 + sage: _.parent() + Integer Ring + sage: I(10/2) + Traceback (most recent call last): + ... + ValueError: 5 is not in Image of Integer Ring by The map at ...> from Integer Ring + sage: 6 in I + True + sage: 7 in I + False + """ + # Same as ImageManifoldSubset.__contains__ + codomain = self._map.codomain() + if codomain is not None and x not in codomain: + raise ValueError(f"{x} is not in {self}") + if self._inverse is not None: + preimage = self._inverse(x) + if preimage not in self._domain_subset: + raise ValueError(f"{x} is not in {self}") + preimage = self._map.domain()(preimage) + y = self._map(preimage) + if y == x: + return y + raise ValueError(f"{x} is not in {self}") + raise NotImplementedError + def ambient(self): """ Return the ambient set of ``self``, which is the codomain of @@ -176,19 +240,53 @@ def _repr_(self) -> str: """ return f"Image of {self._domain_subset} by {self._map}" + @cached_method def cardinality(self) -> Integer: r""" Return the cardinality of ``self``. - EXAMPLES:: + EXAMPLES: + + Injective case (note that + :meth:`~sage.categories.enumerated_sets.EnumeratedSets.ParentMethods.map` + defaults to ``is_injective=True``): sage: R = Permutations(10).map(attrcall('reduced_word')) sage: R.cardinality() 3628800 + + sage: Evens = ZZ.map(lambda x: 2 * x) + sage: Evens.cardinality() + +Infinity + + Non-injective case:: + + sage: Z7 = Set(range(7)) + sage: from sage.sets.image_set import ImageSet + sage: Z4711 = ImageSet(lambda x: x**4 % 11, Z7, is_injective=False) + sage: Z4711.cardinality() + 6 + + sage: Squares = ImageSet(lambda x: x^2, ZZ, is_injective=False, + ....: category=Sets().Infinite()) + sage: Squares.cardinality() + +Infinity + + sage: Mod2 = ZZ.map(lambda x: x % 2, is_injective=False) + sage: Mod2.cardinality() + Traceback (most recent call last): + ... + NotImplementedError: cannot determine cardinality of a non-injective image of an infinite set """ - if self._is_injective: - return self._domain_subset.cardinality() - return super().cardinality() + domain_cardinality = self._domain_subset.cardinality() + if self._is_injective and self._is_injective != 'check': + return domain_cardinality + if self in Sets().Infinite(): + return Infinity + if domain_cardinality == Infinity: + raise NotImplementedError('cannot determine cardinality of a non-injective image of an infinite set') + # Fallback like EnumeratedSets.ParentMethods.__len__ + return Integer(len(list(iter(self)))) def __iter__(self) -> Iterator: r""" @@ -204,7 +302,7 @@ def __iter__(self) -> Iterator: sage: [next(it) for _ in range(5)] [0, 1, 2, 3, 4] """ - if self._is_injective: + if self._is_injective and self._is_injective != 'check': for x in self._domain_subset: yield self._map(x) else: @@ -212,6 +310,8 @@ def __iter__(self) -> Iterator: for x in self._domain_subset: y = self._map(x) if y in visited: + if self._is_injective == 'check': + raise ValueError(f'{self._map} is not injective: {y}') continue visited.add(y) yield y diff --git a/src/sage/sets/set.py b/src/sage/sets/set.py index 96e3b2ad7a4..b76e6b064ef 100644 --- a/src/sage/sets/set.py +++ b/src/sage/sets/set.py @@ -48,6 +48,7 @@ from sage.categories.sets_cat import Sets from sage.categories.enumerated_sets import EnumeratedSets +from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets import sage.rings.infinity @@ -85,7 +86,7 @@ def has_finite_length(obj): return True -def Set(X=None): +def Set(X=None, category=None): r""" Create the underlying set of ``X``. @@ -187,12 +188,12 @@ def Set(X=None): if X is None: X = [] elif isinstance(X, CategoryObject): - if isinstance(X, Set_generic): + if isinstance(X, Set_generic) and category is None: return X elif X in Sets().Finite(): - return Set_object_enumerated(X) + return Set_object_enumerated(X, category=category) else: - return Set_object(X) + return Set_object(X, category=category) if isinstance(X, Element) and not isinstance(X, Set_base): raise TypeError("Element has no defined underlying set") @@ -200,9 +201,9 @@ def Set(X=None): try: X = frozenset(X) except TypeError: - return Set_object(X) + return Set_object(X, category=category) else: - return Set_object_enumerated(X) + return Set_object_enumerated(X, category=category) class Set_base(): @@ -474,7 +475,7 @@ def __init__(self, X, category=None): sage: type(Set(QQ)) sage: Set(QQ).category() - Category of sets + Category of infinite sets TESTS:: @@ -492,6 +493,15 @@ def __init__(self, X, category=None): if category is None: category = Sets() + + if isinstance(X, CategoryObject): + if X in Sets().Finite(): + category = category.Finite() + elif X in Sets().Infinite(): + category = category.Infinite() + if X in Sets().Enumerated(): + category = category.Enumerated() + Parent.__init__(self, category=category) self.__object = X @@ -666,6 +676,9 @@ def cardinality(self): sage: Set(GF(5^2,'a')).cardinality() 25 """ + if self in Sets().Infinite(): + return sage.rings.infinity.infinity + if not self.is_finite(): return sage.rings.infinity.infinity @@ -680,7 +693,7 @@ def cardinality(self): except TypeError: pass - raise NotImplementedError("computation of cardinality of %s not yet implemented" % self.__object) + return super().cardinality() def is_empty(self): """ @@ -733,6 +746,10 @@ def is_finite(self): sage: Set([1,'a',ZZ]).is_finite() True """ + if self in Sets().Finite(): + return True + if self in Sets().Infinite(): + return False obj = self.__object try: is_finite = obj.is_finite @@ -830,7 +847,7 @@ class Set_object_enumerated(Set_object): """ A finite enumerated set. """ - def __init__(self, X): + def __init__(self, X, category=None): r""" Initialize ``self``. @@ -839,12 +856,12 @@ def __init__(self, X): sage: S = Set(GF(19)); S {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18} sage: S.category() - Category of finite sets + Category of finite enumerated sets sage: print(latex(S)) \left\{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18\right\} sage: TestSuite(S).run() """ - Set_object.__init__(self, X, category=Sets().Finite()) + Set_object.__init__(self, X, category=FiniteEnumeratedSets().or_subcategory(category)) def random_element(self): r""" @@ -1267,7 +1284,7 @@ def __classcall__(cls, X, Y, *args, **kwds): Y = Set(Y) return type.__call__(cls, X, Y, *args, **kwds) - def __init__(self, X, Y, op, latex_op): + def __init__(self, X, Y, op, latex_op, category=None): r""" Initialization. @@ -1284,7 +1301,7 @@ def __init__(self, X, Y, op, latex_op): self._Y = Y self._op = op self._latex_op = latex_op - Set_object.__init__(self, self) + Set_object.__init__(self, self, category=category) def _repr_(self): r""" @@ -1338,7 +1355,7 @@ class Set_object_union(Set_object_binary): """ A formal union of two sets. """ - def __init__(self, X, Y): + def __init__(self, X, Y, category=None): r""" Initialize ``self``. @@ -1348,13 +1365,23 @@ def __init__(self, X, Y): sage: T = Set(ZZ) sage: X = S.union(T); X Set-theoretic union of Set of elements of Vector space of dimension 2 over Rational Field and Set of elements of Integer Ring + sage: X.category() + Category of infinite sets sage: latex(X) \Bold{Q}^{2} \cup \Bold{Z} sage: TestSuite(X).run() """ - Set_object_binary.__init__(self, X, Y, "union", "\\cup") + if category is None: + category = Sets() + if all(S in Sets().Enumerated() for S in (X, Y)): + category = category.Enumerated() + if any(S in Sets().Infinite() for S in (X, Y)): + category = category.Infinite() + elif all(S in Sets().Finite() for S in (X, Y)): + category = category.Finite() + Set_object_binary.__init__(self, X, Y, "union", "\\cup", category=category) def is_finite(self): r""" @@ -1481,7 +1508,7 @@ class Set_object_intersection(Set_object_binary): """ Formal intersection of two sets. """ - def __init__(self, X, Y): + def __init__(self, X, Y, category=None): r""" Initialize ``self``. @@ -1491,15 +1518,32 @@ def __init__(self, X, Y): sage: T = Set(ZZ) sage: X = S.intersection(T); X Set-theoretic intersection of Set of elements of Vector space of dimension 2 over Rational Field and Set of elements of Integer Ring + sage: X.category() + Category of enumerated sets sage: latex(X) \Bold{Q}^{2} \cap \Bold{Z} sage: X = Set(IntegerRange(100)).intersection(Primes()) sage: X.is_finite() True + sage: X.cardinality() + 25 + sage: X.category() + Category of finite enumerated sets + sage: TestSuite(X).run() + + sage: X = Set(Primes(), category=Sets()).intersection(Set(IntegerRange(200))) + sage: X.cardinality() + 46 sage: TestSuite(X).run() """ - Set_object_binary.__init__(self, X, Y, "intersection", "\\cap") + if category is None: + category = Sets() + if any(S in Sets().Finite() for S in (X, Y)): + category = category.Finite() + if any(S in Sets().Enumerated() for S in (X, Y)): + category = category.Enumerated() + Set_object_binary.__init__(self, X, Y, "intersection", "\\cap", category=category) def is_finite(self): r""" @@ -1643,7 +1687,7 @@ class Set_object_difference(Set_object_binary): """ Formal difference of two sets. """ - def __init__(self, X, Y): + def __init__(self, X, Y, category=None): r""" Initialize ``self``. @@ -1653,12 +1697,22 @@ def __init__(self, X, Y): sage: T = Set(ZZ) sage: X = S.difference(T); X Set-theoretic difference of Set of elements of Rational Field and Set of elements of Integer Ring + sage: X.category() + Category of sets sage: latex(X) \Bold{Q} - \Bold{Z} sage: TestSuite(X).run() """ - Set_object_binary.__init__(self, X, Y, "difference", "-") + if category is None: + category = Sets() + if X in Sets().Enumerated(): + category = category.Enumerated() + if X in Sets().Finite(): + category = category.Finite() + elif X in Sets().Infinite() and Y in Sets().Finite(): + category = category.Infinite() + Set_object_binary.__init__(self, X, Y, "difference", "-", category=category) def is_finite(self): r""" @@ -1787,6 +1841,8 @@ def _sympy_(self): Set-theoretic difference of Set of elements of Rational Field and Set of elements of Integer Ring + sage: X.category() + Category of sets sage: X._sympy_() Complement(Rationals, Integers) @@ -1794,6 +1850,8 @@ def _sympy_(self): Set-theoretic difference of Set of elements of Integer Ring and Set of elements of Rational Field + sage: X.category() + Category of enumerated sets sage: X._sympy_() EmptySet """ @@ -1807,7 +1865,7 @@ class Set_object_symmetric_difference(Set_object_binary): """ Formal symmetric difference of two sets. """ - def __init__(self, X, Y): + def __init__(self, X, Y, category=None): r""" Initialize ``self``. @@ -1817,12 +1875,20 @@ def __init__(self, X, Y): sage: T = Set(ZZ) sage: X = S.symmetric_difference(T); X Set-theoretic symmetric difference of Set of elements of Rational Field and Set of elements of Integer Ring + sage: X.category() + Category of sets sage: latex(X) \Bold{Q} \bigtriangleup \Bold{Z} sage: TestSuite(X).run() """ - Set_object_binary.__init__(self, X, Y, "symmetric difference", "\\bigtriangleup") + if category is None: + category = Sets() + if all(S in Sets().Finite() for S in (X, Y)): + category = category.Finite() + if all(S in Sets().Enumerated() for S in (X, Y)): + category = category.Enumerated() + Set_object_binary.__init__(self, X, Y, "symmetric difference", "\\bigtriangleup", category=category) def is_finite(self): r""" diff --git a/src/sage/structure/coerce.pyx b/src/sage/structure/coerce.pyx index 6cf8ec78bae..227a62cb3ef 100644 --- a/src/sage/structure/coerce.pyx +++ b/src/sage/structure/coerce.pyx @@ -962,7 +962,7 @@ cdef class CoercionModel: all.append("Coercion on right operand via") all.append(y_mor) if res is not None and res is not y_mor.codomain(): - raise RuntimeError("BUG in coercion model: codomains not equal!", x_mor, y_mor) + raise RuntimeError("BUG in coercion model: codomains not equal", x_mor, y_mor) res = y_mor.codomain() all.append("Arithmetic performed after coercions.") if op is truediv and isinstance(res, Parent): diff --git a/src/sage/structure/element.pyx b/src/sage/structure/element.pyx index 62fd7a136cf..b5d83ef71b6 100644 --- a/src/sage/structure/element.pyx +++ b/src/sage/structure/element.pyx @@ -2615,7 +2615,10 @@ cdef class MultiplicativeGroupElement(MonoidElement): def __invert__(self): r""" - Return the inverse of ``self``. + Return the multiplicative inverse of ``self``. + + This may cause infinite recursion because of the default definition + of division using inversion in ``_div_``. """ return self._parent.one() / self @@ -2626,6 +2629,7 @@ def is_RingElement(x): """ return isinstance(x, RingElement) + cdef class RingElement(ModuleElement): cpdef _mul_(self, other): """ diff --git a/src/sage/structure/factory.pyx b/src/sage/structure/factory.pyx index 6ad42da8c2d..a4a13186973 100644 --- a/src/sage/structure/factory.pyx +++ b/src/sage/structure/factory.pyx @@ -467,7 +467,7 @@ cdef class UniqueFactory(SageObject): sage: test_factory.create_key_and_extra_args(1, 2, key=5) ((1, 2), {}) sage: GF.create_key_and_extra_args(3) - ((3, ('x',), None, 'modn', 3, 1, True, None, None, None), {}) + ((3, ('x',), None, 'modn', 3, 1, True, None, None, None, True, False), {}) """ return self.create_key(*args, **kwds), {} @@ -517,7 +517,7 @@ cdef class UniqueFactory(SageObject): method, but this was removed in :trac:`16934`:: sage: key, _ = GF.create_key_and_extra_args(27, 'k'); key - (27, ('k',), x^3 + 2*x + 1, 'givaro', 3, 3, True, None, 'poly', True) + (27, ('k',), x^3 + 2*x + 1, 'givaro', 3, 3, True, None, 'poly', True, True, True) sage: K = GF.create_object(0, key); K Finite Field in k of size 3^3 sage: GF.other_keys(key, K) diff --git a/src/sage/structure/parent.pyx b/src/sage/structure/parent.pyx index 36b1e45d223..585ec9e559e 100644 --- a/src/sage/structure/parent.pyx +++ b/src/sage/structure/parent.pyx @@ -2863,7 +2863,7 @@ cdef class Set_generic(Parent): TESTS:: sage: Set(QQ).category() - Category of sets + Category of infinite sets """ def object(self): diff --git a/src/sage/structure/support_view.py b/src/sage/structure/support_view.py new file mode 100644 index 00000000000..0212afb5414 --- /dev/null +++ b/src/sage/structure/support_view.py @@ -0,0 +1,177 @@ +r""" +Iterable of the keys of a Mapping associated with nonzero values +""" + +from collections.abc import MappingView, Sequence, Set + +from sage.misc.superseded import deprecation + + +class SupportView(MappingView, Sequence, Set): + r""" + Dynamic view of the set of keys of a dictionary that are associated with nonzero values + + It behaves like the objects returned by the :meth:`keys`, :meth:`values`, + :meth:`items` of a dictionary (or other :class:`collections.abc.Mapping` + classes). + + INPUT: + + - ``mapping`` -- a :class:`dict` or another :class:`collections.abc.Mapping`. + + - ``zero`` -- (optional) test for zeroness by comparing with this value. + + EXAMPLES:: + + sage: d = {'a': 47, 'b': 0, 'c': 11} + sage: from sage.structure.support_view import SupportView + sage: supp = SupportView(d); supp + SupportView({'a': 47, 'b': 0, 'c': 11}) + sage: 'a' in supp, 'b' in supp, 'z' in supp + (True, False, False) + sage: len(supp) + 2 + sage: list(supp) + ['a', 'c'] + sage: supp[0], supp[1] + ('a', 'c') + sage: supp[-1] + 'c' + sage: supp[:] + ('a', 'c') + + It reflects changes to the underlying dictionary:: + + sage: d['b'] = 815 + sage: len(supp) + 3 + """ + + def __init__(self, mapping, *, zero=None): + r""" + TESTS:: + + sage: from sage.structure.support_view import SupportView + sage: supp = SupportView({'a': 'b', 'c': ''}, zero='') + sage: len(supp) + 1 + """ + self._mapping = mapping + self._zero = zero + + def __len__(self): + r""" + TESTS:: + + sage: d = {'a': 47, 'b': 0, 'c': 11} + sage: from sage.structure.support_view import SupportView + sage: supp = SupportView(d); supp + SupportView({'a': 47, 'b': 0, 'c': 11}) + sage: len(supp) + 2 + """ + length = 0 + for key in self: + length += 1 + return length + + def __getitem__(self, index): + r""" + TESTS:: + + sage: d = {'a': 47, 'b': 0, 'c': 11} + sage: from sage.structure.support_view import SupportView + sage: supp = SupportView(d); supp + SupportView({'a': 47, 'b': 0, 'c': 11}) + sage: supp[2] + Traceback (most recent call last): + ... + IndexError + """ + if isinstance(index, slice): + return tuple(self)[index] + if index < 0: + return tuple(self)[index] + for i, key in enumerate(self): + if i == index: + return key + raise IndexError + + def __iter__(self): + r""" + TESTS:: + + sage: d = {'a': 47, 'b': 0, 'c': 11} + sage: from sage.structure.support_view import SupportView + sage: supp = SupportView(d); supp + SupportView({'a': 47, 'b': 0, 'c': 11}) + sage: iter(supp) + + """ + zero = self._zero + if zero is None: + for key, value in self._mapping.items(): + if value: + yield key + else: + for key, value in self._mapping.items(): + if value != zero: + yield key + + def __contains__(self, key): + r""" + TESTS:: + + sage: d = {'a': 47, 'b': 0, 'c': 11} + sage: from sage.structure.support_view import SupportView + sage: supp = SupportView(d); supp + SupportView({'a': 47, 'b': 0, 'c': 11}) + sage: 'a' in supp, 'b' in supp, 'z' in supp + (True, False, False) + """ + try: + value = self._mapping[key] + except KeyError: + return False + zero = self._zero + if zero is None: + return bool(value) + return value != zero + + def __eq__(self, other): + r""" + TESTS:: + + sage: d = {1: 17, 2: 0} + sage: from sage.structure.support_view import SupportView + sage: supp = SupportView(d); supp + SupportView({1: 17, 2: 0}) + sage: supp == [1] + doctest:warning... + DeprecationWarning: comparing a SupportView with a list is deprecated + See https://trac.sagemath.org/34509 for details. + True + """ + if isinstance(other, list): + deprecation(34509, 'comparing a SupportView with a list is deprecated') + return list(self) == other + return NotImplemented + + def __ne__(self, other): + r""" + TESTS:: + + sage: d = {1: 17, 2: 0} + sage: from sage.structure.support_view import SupportView + sage: supp = SupportView(d); supp + SupportView({1: 17, 2: 0}) + sage: supp != [1] + doctest:warning... + DeprecationWarning: comparing a SupportView with a list is deprecated + See https://trac.sagemath.org/34509 for details. + False + """ + if isinstance(other, list): + deprecation(34509, 'comparing a SupportView with a list is deprecated') + return list(self) != other + return NotImplemented diff --git a/src/sage/symbolic/callable.py b/src/sage/symbolic/callable.py index b1411260884..4a123ec0e33 100644 --- a/src/sage/symbolic/callable.py +++ b/src/sage/symbolic/callable.py @@ -144,7 +144,7 @@ def __init__(self, arguments): CallableSymbolicExpressionFunctor(x, y) """ self._arguments = arguments - from sage.categories.all import Rings + from sage.categories.rings import Rings self.rank = 3 ConstructionFunctor.__init__(self, Rings(), Rings()) diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index cc68b28bb90..7ee103f8555 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -376,6 +376,8 @@ More sanity tests:: from cysignals.signals cimport sig_on, sig_off from sage.ext.cplusplus cimport ccrepr, ccreadstr +from copy import copy + import operator import sage.rings.integer import sage.rings.rational @@ -2548,8 +2550,9 @@ cdef class Expression(Expression_abc): sage: SR(1.2).is_algebraic() False """ + from sage.rings.qqbar import QQbar try: - ex = sage.rings.all.QQbar(self) + ex = QQbar(self) except (TypeError, ValueError, NotImplementedError): return False return True @@ -3456,6 +3459,11 @@ cdef class Expression(Expression_abc): sage: val = pi - 2286635172367940241408/1029347477390786609545*sqrt(2) sage: bool(val>0) False + + Check that :trac:`34341` is fixed:: + + sage: bool(x^2 + 2*x + 1 != (x + 1)^2) + False """ if self.is_relational(): # constants are wrappers around Sage objects, compare directly @@ -3499,8 +3507,8 @@ cdef class Expression(Expression_abc): # Use interval fields to try and falsify the relation if not need_assumptions: - if pynac_result == relational_notimplemented and self.operator()==operator.ne: - return not (self.lhs()-self.rhs()).is_trivial_zero() + if pynac_result == relational_notimplemented and self.operator() == operator.ne: + return not (self.lhs()-self.rhs()).is_zero() res = self.test_relation() if res in (True, False): return res @@ -7793,7 +7801,7 @@ cdef class Expression(Expression_abc): if len(v) != 1: raise ValueError("self must be a polynomial in one variable but it is in the variables %s" % tuple([v])) f = self.polynomial(base_ring) - from sage.rings.all import PowerSeriesRing + from sage.rings.power_series_ring import PowerSeriesRing R = PowerSeriesRing(base_ring, names=f.parent().variable_names()) return R(f, f.degree()+1) @@ -11787,13 +11795,14 @@ cdef class Expression(Expression_abc): from sage.symbolic.operators import add_vararg as opadd, \ mul_vararg as opmul from sage.misc.misc_c import prod + from sage.symbolic.ring import SR def treat_term(op, term, args): - l = sage.all.copy(args) + l = copy(args) l.insert(0, term) return op(*l) - if self.parent() is not sage.all.SR: + if self.parent() is not SR: return self op = self.operator() @@ -11889,6 +11898,11 @@ cdef class Expression(Expression_abc): x + sqrt(x) sage: factor((x + sqrt(x))/(x - sqrt(x))) (x + sqrt(x))/(x - sqrt(x)) + + Check that :trac:`33640` is fixed:: + + sage: ((x + 1)^2 - 2*x - 1).factor() + x^2 """ from sage.calculus.calculus import symbolic_expression_from_maxima_string cdef GEx x @@ -13726,7 +13740,10 @@ cpdef new_Expression(parent, x): from sage.misc.misc_c 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.rings.integer_ring import ZZ + from sage.rings.qqbar import AA + from sage.rings.rational_field import QQ + from sage.rings.semirings.non_negative_integer_semiring import NN from sage.sets.real_set import RealSet if (x.is_finite() or x in (NN, ZZ, QQ, AA) or isinstance(x, RealSet)): diff --git a/src/sage/symbolic/expression_conversions.py b/src/sage/symbolic/expression_conversions.py index c5a8240cda0..3b11fb8651e 100644 --- a/src/sage/symbolic/expression_conversions.py +++ b/src/sage/symbolic/expression_conversions.py @@ -15,7 +15,9 @@ # https://www.gnu.org/licenses/ ############################################################################### -import operator as _operator +from operator import eq, ne, gt, lt, ge, le, mul, pow, neg, add, truediv +from functools import reduce + from sage.rings.rational_field import QQ from sage.symbolic.ring import SR from sage.structure.element import Expression @@ -23,7 +25,6 @@ from sage.symbolic.operators import arithmetic_operators, relation_operators, FDerivativeOperator, add_vararg, mul_vararg from sage.rings.number_field.number_field_element_quadratic import NumberFieldElement_gaussian from sage.rings.universal_cyclotomic_field import UniversalCyclotomicField -from functools import reduce class FakeExpression(): @@ -59,7 +60,7 @@ def __repr__(self): sage: FakeExpression([x, y], operator.truediv) FakeExpression([x, y], ) """ - return "FakeExpression(%r, %r)"%(self._operands, self._operator) + return "FakeExpression(%r, %r)" % (self._operands, self._operator) def pyobject(self): """ @@ -200,7 +201,7 @@ def __call__(self, ex=None): return self.symbol(ex) if operator in arithmetic_operators: - if getattr(self, 'use_fake_div', False) and (operator is _operator.mul or operator is mul_vararg): + if getattr(self, 'use_fake_div', False) and (operator is mul or operator is mul_vararg): div = self.get_fake_div(ex) return self.arithmetic(div, div.operator()) return self.arithmetic(ex, operator) @@ -238,7 +239,7 @@ def get_fake_div(self, ex): for arg in ex.operands(): ops = arg.operands() try: - if arg.operator() is _operator.pow and repr(ops[1]) == '-1': + if arg.operator() is pow and repr(ops[1]) == '-1': d.append(ops[0]) else: n.append(arg) @@ -250,22 +251,22 @@ def get_fake_div(self, ex): repr_n = [repr(_) for _ in n] if len(n) == 2 and "-1" in repr_n: a = n[0] if repr_n[1] == "-1" else n[1] - return FakeExpression([a], _operator.neg) + return FakeExpression([a], neg) else: return ex elif len_d == 1: d = d[0] else: - d = FakeExpression(d, _operator.mul) + d = FakeExpression(d, mul) if len(n) == 0: - return FakeExpression([SR.one(), d], _operator.truediv) + return FakeExpression([SR.one(), d], truediv) elif len(n) == 1: n = n[0] else: - n = FakeExpression(n, _operator.mul) + n = FakeExpression(n, mul) - return FakeExpression([n,d], _operator.truediv) + return FakeExpression([n, d], truediv) def pyobject(self, ex, obj): """ @@ -379,6 +380,7 @@ def composition(self, ex, operator): """ raise NotImplementedError("composition") + class InterfaceInit(Converter): def __init__(self, interface): """ @@ -395,7 +397,7 @@ def __init__(self, interface): '(%pi)+(exp((_SAGE_VAR_x)^(2)))+(2)' """ - self.name_init = "_%s_init_"%interface.name() + self.name_init = "_%s_init_" % interface.name() self.interface = interface self.relation_symbols = interface._relation_symbols() @@ -417,12 +419,11 @@ def symbol(self, ex): sage: g.symbol(x) 'sageVARx' """ - if self.interface.name()=='maxima': - return '_SAGE_VAR_'+repr(SR(ex)) - elif self.interface.name() == 'giac': + if self.interface.name() == 'maxima': + return '_SAGE_VAR_' + repr(SR(ex)) + if self.interface.name() == 'giac': return 'sageVAR' + repr(SR(ex)) - else: - return repr(SR(ex)) + return repr(SR(ex)) def pyobject(self, ex, obj): """ @@ -440,7 +441,7 @@ def pyobject(self, ex, obj): sage: ii.pyobject(pi, pi.pyobject()) 'Pi' """ - if (self.interface.name() in ['pari','gp'] and + if (self.interface.name() in ['pari', 'gp'] and isinstance(obj, NumberFieldElement_gaussian)): return repr(obj) try: @@ -460,8 +461,8 @@ def relation(self, ex, operator): sage: m.relation(x==3, operator.lt) '_SAGE_VAR_x < 3' """ - return "%s %s %s"%(self(ex.lhs()), self.relation_symbols[operator], - self(ex.rhs())) + return "%s %s %s" % (self(ex.lhs()), self.relation_symbols[operator], + self(ex.rhs())) def tuple(self, ex): """ @@ -575,8 +576,8 @@ def derivative(self, ex, operator): sage: (gamma_inc(x,x+1).diff(x)).simplify() -(x + 1)^(x - 1)*e^(-x - 1) + D[0](gamma)(x, x + 1) """ - #This code should probably be moved into the interface - #object in a nice way. + # This code should probably be moved into the interface + # object in a nice way. from sage.symbolic.ring import is_SymbolicVariable if self.name_init != "_maxima_init_": raise NotImplementedError @@ -591,20 +592,22 @@ def derivative(self, ex, operator): # 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))] + 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)] - subs = ["%s = %s"%(t._maxima_init_(),a._maxima_init_()) for t,a in zip(temp_args,args)] - outstr = "at(diff(%s, %s), [%s])"%(f._maxima_init_(), - ", ".join(params), - ", ".join(subs)) + params = ["%s, %s" % (temp_args[i]._maxima_init_(), params.count(i)) for i in set(params)] + subs = ["%s = %s" % (t._maxima_init_(), a._maxima_init_()) + for t, a in zip(temp_args, args)] + outstr = "at(diff(%s, %s), [%s])" % (f._maxima_init_(), + ", ".join(params), + ", ".join(subs)) else: f = operator.function()(*args) params = operator.parameter_set() - params = ["%s, %s"%(args[i]._maxima_init_(), params.count(i)) for i in set(params)] - outstr = "diff(%s, %s)"%(f._maxima_init_(), - ", ".join(params)) + params = ["%s, %s" % (args[i]._maxima_init_(), params.count(i)) + for i in set(params)] + outstr = "diff(%s, %s)" % (f._maxima_init_(), + ", ".join(params)) return outstr def arithmetic(self, ex, operator): @@ -617,7 +620,7 @@ def arithmetic(self, ex, operator): sage: m.arithmetic(x+2, sage.symbolic.operators.add_vararg) '(_SAGE_VAR_x)+(2)' """ - args = ["(%s)"%self(op) for op in ex.operands()] + args = ["(%s)" % self(op) for op in ex.operands()] return arithmetic_operators[operator].join(args) def composition(self, ex, operator): @@ -636,7 +639,7 @@ def composition(self, ex, operator): 'Sin[x]' """ ops = ex.operands() - #FIXME: consider stripping pyobjects() in ops + # FIXME: consider stripping pyobjects() in ops if hasattr(operator, self.name_init + "evaled_"): return getattr(operator, self.name_init + "evaled_")(*ops) else: @@ -646,7 +649,8 @@ def composition(self, ex, operator): except (TypeError, AttributeError): op = repr(operator) - return self.interface._function_call_string(op,ops,[]) + return self.interface._function_call_string(op, ops, []) + ######### # Sympy # @@ -780,9 +784,8 @@ def relation(self, ex, op): sage: s.relation(x > 0, operator.gt) x > 0 """ - from operator import eq, ne, gt, lt, ge, le from sympy import Eq, Ne, Gt, Lt, Ge, Le - ops = {eq : Eq, ne : Ne, gt : Gt, lt : Lt, ge : Ge, le : Le} + ops = {eq: Eq, ne: Ne, gt: Gt, lt: Lt, ge: Ge, le: Le} return ops.get(op)(self(ex.lhs()), self(ex.rhs()), evaluate=False) def composition(self, ex, operator): @@ -940,6 +943,7 @@ def derivative(self, ex, operator): sympy_converter = SympyConverter() + ########## # FriCAS # ########## @@ -1096,7 +1100,7 @@ def derivative(self, ex, operator): """ from sage.symbolic.ring import is_SymbolicVariable - args = ex.operands() # the arguments the derivative is evaluated at + args = ex.operands() # the arguments the derivative is evaluated at params = operator.parameter_set() params_set = set(params) mult = ",".join(str(params.count(i)) for i in params_set) @@ -1123,8 +1127,10 @@ def derivative(self, ex, operator): return outstr + fricas_converter = FriCASConverter() + ############# # Algebraic # ############# @@ -1199,28 +1205,28 @@ def arithmetic(self, ex, operator): # can change the value of a radical expression (by changing which # root is selected). try: - if operator is _operator.pow: - from sage.rings.all import Rational + if operator is pow: + from sage.rings.rational import Rational base, expt = ex.operands() base = self.field(base) expt = Rational(expt) return self.field(base**expt) else: if operator is add_vararg: - operator = _operator.add + operator = add elif operator is mul_vararg: - operator = _operator.mul + operator = mul return reduce(operator, map(self, ex.operands())) except TypeError: pass - if operator is _operator.pow: + if operator is pow: from sage.symbolic.constants import e, pi, I base, expt = ex.operands() - if base == e and expt / (pi*I) in QQ: + if base == e and expt / (pi * I) in QQ: return exp(expt)._algebraic_(self.field) - raise TypeError("unable to convert %r to %s"%(ex, self.field)) + raise TypeError("unable to convert %r to %s" % (ex, self.field)) def composition(self, ex, operator): """ @@ -1291,17 +1297,17 @@ def composition(self, ex, operator): if func_name == 'exp': if operand.is_trivial_zero(): return self.field.one() - if not (SR(-1).sqrt()*operand).is_real(): + if not (SR(-1).sqrt() * operand).is_real(): raise ValueError("unable to represent as an algebraic number") # Coerce (not convert, see #22571) arg to a rational arg = operand.imag()/(2*ex.parent().pi()) try: rat_arg = QQ.coerce(arg.pyobject()) except TypeError: - raise TypeError("unable to convert %r to %s"%(ex, self.field)) + raise TypeError("unable to convert %r to %s" % (ex, self.field)) res = zeta(rat_arg.denom())**rat_arg.numer() elif func_name in ['sin', 'cos', 'tan']: - exp_ia = exp(SR(-1).sqrt()*operand, hold=hold)._algebraic_(QQbar) + exp_ia = exp(SR(-1).sqrt() * operand, hold=hold)._algebraic_(QQbar) if func_name == 'sin': res = (exp_ia - ~exp_ia) / (2 * zeta(4)) elif func_name == 'cos': @@ -1322,13 +1328,14 @@ def composition(self, ex, operator): res = ~self.reciprocal_trig_functions[func_name](operand)._algebraic_(QQbar) else: res = func(operand._algebraic_(self.field)) - #We have to handle the case where we get the same symbolic - #expression back. For example, QQbar(zeta(7)). See - #ticket #12665. + # We have to handle the case where we get the same symbolic + # expression back. For example, QQbar(zeta(7)). See + # ticket #12665. if (res - ex).is_trivial_zero(): - raise TypeError("unable to convert %r to %s"%(ex, self.field)) + raise TypeError("unable to convert %r to %s" % (ex, self.field)) return self.field(res) + def algebraic(ex, field): """ Returns the symbolic expression *ex* as a element of the algebraic @@ -1372,6 +1379,7 @@ def algebraic(ex, field): """ return AlgebraicConverter(field)(ex) + ############## # Polynomial # ############## @@ -1424,7 +1432,7 @@ def __init__(self, ex, base_ring=None, ring=None): self.varnames = ring.variable_names_recursive() for v in ex.variables(): if repr(v) not in self.varnames and v not in base_ring: - raise TypeError("%s is not a variable of %s" %(v, ring)) + raise TypeError("%s is not a variable of %s" % (v, ring)) self.ring = ring self.base_ring = base_ring elif base_ring is not None: @@ -1456,10 +1464,10 @@ def symbol(self, ex): y """ try: - #The symbol is one of the polynomial generators + # The symbol is one of the polynomial generators return self.ring(repr(ex)) except TypeError: - #The symbol should go into the base ring + # The symbol should go into the base ring return self.base_ring(repr(ex)) def pyobject(self, ex, obj): @@ -1512,8 +1520,7 @@ def relation(self, ex, op): import operator if op == operator.eq: return self(ex.lhs()) - self(ex.rhs()) - else: - raise ValueError("Unable to represent as a polynomial") + raise ValueError("Unable to represent as a polynomial") def arithmetic(self, ex, operator): """ @@ -1541,17 +1548,18 @@ def arithmetic(self, ex, operator): """ if not any(repr(v) in self.varnames for v in ex.variables()): return self.base_ring(ex) - elif operator == _operator.pow: + elif operator == pow: from sage.rings.integer import Integer base, exp = ex.operands() return self(base)**Integer(exp) if operator == add_vararg: - operator = _operator.add + operator = add elif operator == mul_vararg: - operator = _operator.mul + operator = mul ops = [self(a) for a in ex.operands()] return reduce(operator, ops) + def polynomial(ex, base_ring=None, ring=None): """ Return a polynomial from the symbolic expression ``ex``. @@ -1636,7 +1644,7 @@ def __init__(self, ex, base_ring=None, ring=None): super().__init__(ex, base_ring, ring) if ring is None and base_ring is not None: - from sage.rings.all import LaurentPolynomialRing + from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing self.ring = LaurentPolynomialRing(self.base_ring, names=self.varnames) @@ -1739,7 +1747,7 @@ def relation(self, ex, operator): ... NotImplementedError """ - if operator is not _operator.eq: + if operator is not eq: raise NotImplementedError return self(ex.lhs() - ex.rhs()) @@ -1777,23 +1785,23 @@ def arithmetic(self, ex, operator): # exponent before the exponent gets (potentially) converted # to another type. operands = ex.operands() - if operator is _operator.pow: + if operator is pow: exponent = operands[1] if exponent == -1: - return self.etb.call(_operator.truediv, 1, operands[0]) + return self.etb.call(truediv, 1, operands[0]) elif exponent == 0.5: from sage.misc.functional import sqrt return self.etb.call(sqrt, operands[0]) elif exponent == -0.5: from sage.misc.functional import sqrt - return self.etb.call(_operator.truediv, 1, self.etb.call(sqrt, operands[0])) - elif operator is _operator.neg: + return self.etb.call(truediv, 1, self.etb.call(sqrt, operands[0])) + elif operator is neg: return self.etb.call(operator, operands[0]) if operator == add_vararg: - operator = _operator.add + operator = add elif operator == mul_vararg: - operator = _operator.mul - return reduce(lambda x,y: self.etb.call(operator, x,y), operands) + operator = mul + return reduce(lambda x, y: self.etb.call(operator, x, y), operands) def symbol(self, ex): r""" @@ -1846,6 +1854,7 @@ def tuple(self, ex): """ return ex.operands() + def fast_callable(ex, etb): """ Given an ExpressionTreeBuilder *etb*, return an Expression representing @@ -1867,6 +1876,7 @@ def fast_callable(ex, etb): """ return FastCallableConverter(ex, etb)() + class RingConverter(Converter): def __init__(self, R, subs_dict=None): """ @@ -1940,15 +1950,16 @@ def arithmetic(self, ex, operator): sage: R(a) 2*z^2 + z + 3 """ - if operator not in [_operator.pow, add_vararg, mul_vararg]: + if operator not in [pow, add_vararg, mul_vararg]: raise TypeError operands = ex.operands() - if operator is _operator.pow: - from sage.all import Integer, Rational + if operator is pow: + from sage.rings.integer import Integer + from sage.rings.rational import Rational base, expt = operands - if expt == Rational(((1,2))): + if expt == Rational(((1, 2))): from sage.misc.functional import sqrt return sqrt(self(base)) try: @@ -1960,9 +1971,9 @@ def arithmetic(self, ex, operator): return base ** expt if operator == add_vararg: - operator = _operator.add + operator = add elif operator == mul_vararg: - operator = _operator.mul + operator = mul return reduce(operator, map(self, operands)) def composition(self, ex, operator): @@ -1974,7 +1985,7 @@ def composition(self, ex, operator): sage: R(cos(2)) -0.4161468365471424? """ - res = operator(*[self(_) for _ in ex.operands()]) + res = operator(*[self(op) for op in ex.operands()]) if res.parent() is not self.ring: raise TypeError else: @@ -2094,6 +2105,7 @@ def tuple(self, ex): """ return ex.operands() + class SubstituteFunction(ExpressionTreeWalker): def __init__(self, ex, *args): """ @@ -2178,6 +2190,7 @@ def derivative(self, ex, operator): else: return operator(*[self(_) for _ in ex.operands()]) + class Exponentialize(ExpressionTreeWalker): # Implementation note: this code is executed once at first # reference in the code using it, therefore avoiding rebuilding @@ -2216,7 +2229,7 @@ def __init__(self, ex): expressions. EXAMPLES:: - + sage: from sage.symbolic.expression_conversions import Exponentialize sage: d = Exponentialize(sin(x)) sage: d(sin(x)) @@ -2268,7 +2281,7 @@ def __init__(self, ex, force=False): """ self.ex = ex self.force = force - + def composition(self, ex, op): """ Return the composition of ``self`` with ``ex`` by ``op``. @@ -2306,6 +2319,7 @@ def composition(self, ex, op): return cosh(arg) + sinh(arg) return exp(arg) + class HoldRemover(ExpressionTreeWalker): def __init__(self, ex, exclude=None): """ diff --git a/src/sage/symbolic/function.pyx b/src/sage/symbolic/function.pyx index 10393548c88..06e626c7748 100644 --- a/src/sage/symbolic/function.pyx +++ b/src/sage/symbolic/function.pyx @@ -1060,7 +1060,9 @@ cdef class BuiltinFunction(Function): return res p = res.parent() - from sage.rings.all import ZZ, RDF, CDF + from sage.rings.complex_double import CDF + from sage.rings.integer_ring import ZZ + from sage.rings.real_double import RDF if ZZ.has_coerce_map_from(p): return int(res) elif RDF.has_coerce_map_from(p): diff --git a/src/sage/symbolic/ginac/infinity.cpp b/src/sage/symbolic/ginac/infinity.cpp index cfb2161f2dd..3a2a7b8469e 100644 --- a/src/sage/symbolic/ginac/infinity.cpp +++ b/src/sage/symbolic/ginac/infinity.cpp @@ -76,7 +76,7 @@ infinity::infinity() infinity::infinity(const numeric & _direction) : basic(&infinity::tinfo_static) { - // Note: we cannot accept an arbirtary ex as argument + // Note: we cannot accept an arbitrary ex as argument // or we would take precedence over the copy constructor. set_direction(_direction); hashvalue = hash_from_dir(direction); diff --git a/src/sage/symbolic/ginac/mpoly-ginac.cpp b/src/sage/symbolic/ginac/mpoly-ginac.cpp index 4c84a5ddb2f..608e0f6f207 100644 --- a/src/sage/symbolic/ginac/mpoly-ginac.cpp +++ b/src/sage/symbolic/ginac/mpoly-ginac.cpp @@ -118,7 +118,7 @@ struct sym_desc { /** Maximum number of terms of leading coefficient of symbol in both polynomials */ size_t max_lcnops; - /** Commparison operator for sorting */ + /** Comparison operator for sorting */ bool operator<(const sym_desc &x) const { if (max_deg == x.max_deg) diff --git a/src/sage/symbolic/ginac/mul.cpp b/src/sage/symbolic/ginac/mul.cpp index 6a3a8321bd2..f7496cbb5e5 100644 --- a/src/sage/symbolic/ginac/mul.cpp +++ b/src/sage/symbolic/ginac/mul.cpp @@ -1459,7 +1459,7 @@ const epvector & mul::get_sorted_seq() const /** Member-wise expand the expairs representing this sequence. This must be * overridden from expairseq::expandchildren() and done iteratively in order - * to allow for early cancallations and thus safe memory. + * to allow for early cancellations and thus save memory. * * @see mul::expand() * @return pointer to epvector containing expanded representation or zero diff --git a/src/sage/symbolic/ginac/normal.cpp b/src/sage/symbolic/ginac/normal.cpp index 2d34d8f16f2..d2c9e7be637 100644 --- a/src/sage/symbolic/ginac/normal.cpp +++ b/src/sage/symbolic/ginac/normal.cpp @@ -633,7 +633,7 @@ ex add::normal(exmap & repl, exmap & rev_lookup, int level, unsigned options) co num_it++; den_it++; } - // Additiion of two fractions, taking advantage of the fact that + // Addition of two fractions, taking advantage of the fact that // the heuristic GCD algorithm computes the cofactors at no extra cost ex co_den1, co_den2; ex g = gcdpoly(den, next_den, &co_den1, &co_den2, false); @@ -848,7 +848,7 @@ ex ex::denom() const return e; } -/** Get numerator and denominator of an expression. If the expresison is not +/** Get numerator and denominator of an expression. If the expression is not * of the normal form "numerator/denominator", it is first converted to this * form and then a list [numerator, denominator] is returned. * @@ -1221,8 +1221,6 @@ bool factor(const ex& the_ex, ex& res_ex) den = normalized.op(1); ex res_den; bool dres = factorpoly(den, res_den); - if (not nres and not dres) - return false; if (not nres) res_ex = num; if (not dres) diff --git a/src/sage/symbolic/integration/integral.py b/src/sage/symbolic/integration/integral.py index a7eec1b72a5..f9914e438a1 100644 --- a/src/sage/symbolic/integration/integral.py +++ b/src/sage/symbolic/integration/integral.py @@ -321,7 +321,7 @@ def _tderivative_(self, f, x, a, b, diff_param=None): def _print_latex_(self, f, x, a, b): r""" - Convert this integral to LaTeX notation + Convert this integral to LaTeX notation. EXAMPLES:: @@ -345,7 +345,7 @@ def _print_latex_(self, f, x, a, b): def _sympy_(self, f, x, a, b): """ - Convert this integral to the equivalent SymPy object + Convert this integral to the equivalent SymPy object. The resulting SymPy integral can be evaluated using ``doit()``. @@ -1038,6 +1038,12 @@ def integrate(expression, v=None, a=None, b=None, algorithm=None, hold=False): sage: x,m = SR.var('x,m', domain='real') # long time sage: integrate(elliptic_e(x,m).diff(x), x) # long time elliptic_e(x, m) + + Check that :trac:`20467` is fixed:: + + sage: k = var('k') + sage: integral(sin(k*x)/x*erf(x^2), x, 0, oo, algorithm='maxima') + integrate(erf(x^2)*sin(k*x)/x, x, 0, +Infinity) """ expression, v, a, b = _normalize_integral_input(expression, v, a, b) if algorithm is not None: diff --git a/src/sage/symbolic/operators.py b/src/sage/symbolic/operators.py index 52351f0867b..a48b0e8e393 100644 --- a/src/sage/symbolic/operators.py +++ b/src/sage/symbolic/operators.py @@ -6,21 +6,22 @@ def add_vararg(first, *rest): r""" - Addition of a variable number of arguments. + Return the sum of all the arguments. INPUT: - - ``first``, ``rest`` -- arguments to add + - ``first``, ``*rest`` -- arguments to add - OUTPUT: sum of arguments + OUTPUT: sum of the arguments EXAMPLES:: sage: from sage.symbolic.operators import add_vararg - sage: add_vararg(1,2,3,4,5,6,7) + sage: add_vararg(1, 2, 3, 4, 5, 6, 7) 28 - sage: F = (1+x+x^2) - sage: bool(F.operator()(*F.operands()) == F) + sage: x = SR.var('x') + sage: s = 1 + x + x^2 # symbolic sum + sage: bool(s.operator()(*s.operands()) == s) True """ for r in rest: @@ -30,21 +31,22 @@ def add_vararg(first, *rest): def mul_vararg(first, *rest): r""" - Multiplication of a variable number of arguments. + Return the product of all the arguments. INPUT: - - ``args`` -- arguments to multiply + - ``first``, ``*rest`` -- arguments to multiply - OUTPUT: product of arguments + OUTPUT: product of the arguments EXAMPLES:: sage: from sage.symbolic.operators import mul_vararg - sage: mul_vararg(9,8,7,6,5,4) + sage: mul_vararg(9, 8, 7, 6, 5, 4) 60480 - sage: G = x*cos(x)*sin(x) - sage: bool(G.operator()(*G.operands())==G) + sage: x = SR.var('x') + sage: p = x * cos(x) * sin(x) # symbolic product + sage: bool(p.operator()(*p.operands()) == p) True """ for r in rest: @@ -69,13 +71,25 @@ def mul_vararg(first, *rest): operator.ge:'>='} class FDerivativeOperator(): + r""" + Function derivative operators. + + A function derivative operator represents a partial derivative + of a function with respect to some variables. + + The underlying data are the function, and the parameter set, + a list recording the indices of the variables with respect + to which the partial derivative is taken. + """ def __init__(self, function, parameter_set): - """ + r""" + Initialize this function derivative operator. + EXAMPLES:: sage: from sage.symbolic.operators import FDerivativeOperator sage: f = function('foo') - sage: op = FDerivativeOperator(f, [0,1]) + sage: op = FDerivativeOperator(f, [0, 1]) sage: loads(dumps(op)) D[0, 1](foo) """ @@ -83,16 +97,18 @@ def __init__(self, function, parameter_set): self._parameter_set = [int(_) for _ in parameter_set] def __call__(self, *args): - """ + r""" + Call this function derivative operator on these arguments. + EXAMPLES:: sage: from sage.symbolic.operators import FDerivativeOperator - sage: x,y = var('x,y') + sage: x, y = SR.var('x, y') sage: f = function('foo') - sage: op = FDerivativeOperator(f, [0,1]) - sage: op(x,y) + sage: op = FDerivativeOperator(f, [0, 1]) + sage: op(x, y) diff(foo(x, y), x, y) - sage: op(x,x^2) + sage: op(x, x^2) D[0, 1](foo)(x, x^2) TESTS: @@ -115,56 +131,65 @@ 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.temp_var(n=len(args)) - vars=[temp_args[i] for i in self._parameter_set] + 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] return self._f(*args).diff(*vars) def __repr__(self): - """ + r""" + Return the string representation of this function derivative operator. + EXAMPLES:: sage: from sage.symbolic.operators import FDerivativeOperator sage: f = function('foo') - sage: op = FDerivativeOperator(f, [0,1]); op + sage: op = FDerivativeOperator(f, [0, 1]); op D[0, 1](foo) """ - return "D[%s](%s)"%(", ".join(map(repr, self._parameter_set)), self._f) + return "D[%s](%s)" % (", ".join(map(repr, self._parameter_set)), self._f) def function(self): - """ + r""" + Return the function associated to this function derivative operator. + EXAMPLES:: sage: from sage.symbolic.operators import FDerivativeOperator sage: f = function('foo') - sage: op = FDerivativeOperator(f, [0,1]) + sage: op = FDerivativeOperator(f, [0, 1]) sage: op.function() foo """ return self._f def change_function(self, new): - """ - Returns a new FDerivativeOperator with the same parameter set - for a new function. + r""" + Return a new function derivative operator with the same + parameter set but for a new function. sage: from sage.symbolic.operators import FDerivativeOperator sage: f = function('foo') sage: b = function('bar') - sage: op = FDerivativeOperator(f, [0,1]) + sage: op = FDerivativeOperator(f, [0, 1]) sage: op.change_function(bar) D[0, 1](bar) """ return FDerivativeOperator(new, self._parameter_set) def parameter_set(self): - """ + r""" + Return the parameter set of this function derivative operator. + + This is the list of indices of variables with respect to which + the derivative is taken. + EXAMPLES:: sage: from sage.symbolic.operators import FDerivativeOperator sage: f = function('foo') - sage: op = FDerivativeOperator(f, [0,1]) + sage: op = FDerivativeOperator(f, [0, 1]) sage: op.parameter_set() [0, 1] """ diff --git a/src/sage/symbolic/relation.py b/src/sage/symbolic/relation.py index 037d22a89f2..6bd6469dd22 100644 --- a/src/sage/symbolic/relation.py +++ b/src/sage/symbolic/relation.py @@ -572,7 +572,7 @@ def string_to_list_of_solutions(s): sage: sage.symbolic.relation.string_to_list_of_solutions(s) [x == -1/2*a - 1/2*sqrt(a^2 - 4*b), x == -1/2*a + 1/2*sqrt(a^2 - 4*b)] """ - from sage.categories.all import Objects + from sage.categories.objects import Objects from sage.structure.sequence import Sequence from sage.calculus.calculus import symbolic_expression_from_maxima_string v = symbolic_expression_from_maxima_string(s, equals_sub=True) @@ -1570,7 +1570,9 @@ def solve_mod(eqns, modulus, solution_dict=False): """ - from sage.rings.all import Integer, Integers, crt_basis + from sage.rings.finite_rings.integer_mod_ring import Integers + from sage.rings.integer import Integer + from sage.rings.integer_ring import crt_basis from sage.structure.element import Expression from sage.misc.mrange import cartesian_product_iterator from sage.modules.free_module_element import vector @@ -1686,7 +1688,8 @@ def _solve_mod_prime_power(eqns, p, m, vars): 13241296179, 19473547571, 2263241296179] """ - from sage.rings.all import Integers, PolynomialRing + from sage.rings.finite_rings.integer_mod_ring import Integers + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.modules.free_module_element import vector from sage.misc.mrange import cartesian_product_iterator diff --git a/src/sage/symbolic/ring.pyx b/src/sage/symbolic/ring.pyx index bd0b89f9374..2036a7331d4 100644 --- a/src/sage/symbolic/ring.pyx +++ b/src/sage/symbolic/ring.pyx @@ -214,11 +214,9 @@ cdef class SymbolicRing(sage.rings.abc.SymbolicRing): from sage.rings.polynomial.polynomial_ring import is_PolynomialRing from sage.rings.polynomial.multi_polynomial_ring import is_MPolynomialRing from sage.rings.polynomial.laurent_polynomial_ring import is_LaurentPolynomialRing - - from sage.rings.all import (ComplexField, - RLF, CLF, - InfinityRing, - UnsignedInfinityRing) + from sage.rings.complex_mpfr import ComplexField + from sage.rings.infinity import InfinityRing, UnsignedInfinityRing + from sage.rings.real_lazy import RLF, CLF from sage.rings.finite_rings.finite_field_base import is_FiniteField from sage.interfaces.maxima import Maxima @@ -1225,7 +1223,7 @@ cdef class NumpyToSRMorphism(Morphism): from sage.rings.real_double import RDF self._intermediate_ring = RDF elif issubclass(numpy_type, numpy.complexfloating): - from sage.rings.all import CDF + from sage.rings.complex_double import CDF self._intermediate_ring = CDF else: raise TypeError("{} is not a numpy number type".format(numpy_type)) diff --git a/src/sage/symbolic/series_impl.pxi b/src/sage/symbolic/series_impl.pxi index cb68f8fe893..18c017066f6 100644 --- a/src/sage/symbolic/series_impl.pxi +++ b/src/sage/symbolic/series_impl.pxi @@ -271,6 +271,6 @@ cdef class SymbolicSeries(Expression): sage: g.parent() Power Series Ring in x over Symbolic Ring """ - from sage.rings.all import PowerSeriesRing + from sage.rings.power_series_ring import PowerSeriesRing R = PowerSeriesRing(base_ring, names=str(self.default_variable())) return R(self.list(), self.degree(self.default_variable())) diff --git a/src/sage/symbolic/subring.py b/src/sage/symbolic/subring.py index 0bf7a182587..e30e29a16cf 100644 --- a/src/sage/symbolic/subring.py +++ b/src/sage/symbolic/subring.py @@ -356,7 +356,7 @@ def _element_constructor_(self, x): TypeError: x is not contained in Symbolic Subring accepting the variable a """ expression = super()._element_constructor_(x) - assert(expression.parent() is self) + assert expression.parent() is self if not all(self.has_valid_variable(var) for var in expression.variables()): raise TypeError('%s is not contained in %s' % (x, self)) @@ -407,7 +407,9 @@ def _coerce_map_from_(self, P): # Workaround; can be deleted once #19231 is fixed return False - from sage.rings.all import RLF, CLF, AA, QQbar, InfinityRing + from sage.rings.infinity import InfinityRing + from sage.rings.qqbar import AA, QQbar + from sage.rings.real_lazy import RLF, CLF if isinstance(P, type): return SR._coerce_map_from_(P) diff --git a/src/sage/symbolic/units.py b/src/sage/symbolic/units.py index 9618739f6ba..9b5407786e1 100644 --- a/src/sage/symbolic/units.py +++ b/src/sage/symbolic/units.py @@ -1456,7 +1456,8 @@ def convert_temperature(expr, target): coeff = expr/expr_temp if target is not None: target_temp = target.variables()[0] - a = sage_eval(unitdict['temperature'][str(expr_temp)], locals = {'x':coeff}) + a = sage_eval(unitdict['temperature'][str(expr_temp)], + locals={'x': coeff}) if target is None or target_temp == units.temperature.kelvin: return a[0]*units.temperature.kelvin elif target_temp == units.temperature.celsius or target_temp == units.temperature.centigrade: diff --git a/src/sage/tensor/modules/comp.py b/src/sage/tensor/modules/comp.py index 04f14f782f0..62f9fd7d07f 100644 --- a/src/sage/tensor/modules/comp.py +++ b/src/sage/tensor/modules/comp.py @@ -525,15 +525,48 @@ def _repr_(self): sage: c._repr_() '2-indices components w.r.t. [1, 2, 3]' + sage: from sage.tensor.modules.comp import CompWithSym + sage: CompWithSym(ZZ, [1,2,3], 4, sym=(0,1)) + 4-indices components w.r.t. [1, 2, 3], + with symmetry on the index positions (0, 1) + sage: CompWithSym(ZZ, [1,2,3], 4, sym=(0,1), antisym=(2,3)) + 4-indices components w.r.t. [1, 2, 3], + with symmetry on the index positions (0, 1), + with antisymmetry on the index positions (2, 3) + + sage: from sage.tensor.modules.comp import CompFullySym + sage: CompFullySym(ZZ, (1,2,3), 4) + Fully symmetric 4-indices components w.r.t. (1, 2, 3) + + sage: from sage.tensor.modules.comp import CompFullyAntiSym + sage: CompFullyAntiSym(ZZ, (1,2,3), 4) + Fully antisymmetric 4-indices components w.r.t. (1, 2, 3) """ - description = str(self._nid) + prefix, suffix = self._repr_symmetry() + description = prefix + description += str(self._nid) if self._nid == 1: description += "-index" else: description += "-indices" description += " components w.r.t. " + str(self._frame) + description += suffix return description + def _repr_symmetry(self): + r""" + Return a prefix and a suffix string describing the symmetry of ``self``. + + EXAMPLES:: + + sage: from sage.tensor.modules.comp import Components + sage: c = Components(ZZ, [1,2,3], 2) + sage: c._repr_symmetry() + ('', '') + + """ + return "", "" + def _new_instance(self): r""" Creates a :class:`Components` instance of the same number of indices @@ -1023,6 +1056,40 @@ def _set_value_list(self, ind, format_type, val): for i in range(si, nsi): self._set_value_list(ind + [i], format_type, val[i-si]) + def items(self): + r""" + Return an iterable of ``(indices, value)`` elements. + + This may (but is not guaranteed to) suppress zero values. + + EXAMPLES:: + + sage: from sage.tensor.modules.comp import Components, CompWithSym + + sage: c = Components(ZZ, (ZZ^2).basis(), 3) + sage: c[0,1,0], c[1,0,1], c[1,1,1] = -2, 5, 3 + sage: list(c.items()) + [((0, 1, 0), -2), ((1, 0, 1), 5), ((1, 1, 1), 3)] + + sage: c = CompWithSym(ZZ, (ZZ^2).basis(), 3, sym=((1, 2))) + sage: c[0,1,0], c[1,0,1], c[1,1,1] = -2, 5, 3 + sage: list(c.items()) + [((0, 0, 1), -2), + ((0, 1, 0), -2), + ((1, 0, 1), 5), + ((1, 1, 0), 5), + ((1, 1, 1), 3)] + + """ + for ind in self.index_generator(): + val = self[ind] + if hasattr(val, 'is_trivial_zero'): + zero_value = val.is_trivial_zero() + else: + zero_value = val == 0 + if not zero_value: + yield ind, val + def display(self, symbol, latex_symbol=None, index_positions=None, index_labels=None, index_latex_labels=None, format_spec=None, only_nonzero=True, only_nonredundant=False): @@ -1773,12 +1840,12 @@ def __mul__(self, other): "same starting index") if isinstance(other, CompWithSym): sym = [] - if other._sym != []: + if other._sym: for s in other._sym: ns = tuple(s[i]+self._nid for i in range(len(s))) sym.append(ns) antisym = [] - if other._antisym != []: + if other._antisym: for s in other._antisym: ns = tuple(s[i]+self._nid for i in range(len(s))) antisym.append(ns) @@ -2982,7 +3049,6 @@ class CompWithSym(Components): ) sage: e + d == d + e True - """ def __init__(self, ring, frame, nb_indices, start_index=0, output_formatter=None, sym=None, antisym=None): @@ -2996,76 +3062,128 @@ def __init__(self, ring, frame, nb_indices, start_index=0, """ Components.__init__(self, ring, frame, nb_indices, start_index, output_formatter) - self._sym = [] - if sym is not None and sym != []: - if isinstance(sym[0], (int, Integer)): - # a single symmetry is provided as a tuple or a range object; - # it is converted to a 1-item list: - sym = [tuple(sym)] - for isym in sym: - if len(isym) < 2: - raise IndexError("at least two index positions must be " + - "provided to define a symmetry") - for i in isym: - if i<0 or i>self._nid-1: - raise IndexError("invalid index position: " + str(i) + - " not in [0," + str(self._nid-1) + "]") - self._sym.append(tuple(isym)) - self._antisym = [] - if antisym is not None and antisym != []: - if isinstance(antisym[0], (int, Integer)): - # a single antisymmetry is provided as a tuple or a range - # object; it is converted to a 1-item list: - antisym = [tuple(antisym)] - for isym in antisym: - if len(isym) < 2: - raise IndexError("at least two index positions must be " + - "provided to define an antisymmetry") - for i in isym: - if i<0 or i>self._nid-1: - raise IndexError("invalid index position: " + str(i) + - " not in [0," + str(self._nid-1) + "]") - self._antisym.append(tuple(isym)) + self._sym, self._antisym = self._canonicalize_sym_antisym( + nb_indices, sym, antisym) + + @staticmethod + def _canonicalize_sym_or_antisym(nb_indices, sym_or_antisym): + r""" + Bring ``sym`` or ``antisym`` to its canonical form. + + INPUT: + + - ``nb_indices`` -- number of integer indices labeling the components + + - ``sym_or_antisym`` -- (default: ``None``) a symmetry/antisymmetry + or an iterable of symmetries or an iterable of antisymmetries + among the tensor arguments: each symmetry/antisymmetry is described + by a tuple containing the positions of the involved arguments, with + the convention ``position = 0`` for the first argument. + + TESTS:: + + sage: from sage.tensor.modules.comp import CompWithSym + sage: CompWithSym._canonicalize_sym_or_antisym(3, [0, -1]) + Traceback (most recent call last): + ... + IndexError: invalid index position: -1 not in [0,2] + sage: CompWithSym._canonicalize_sym_or_antisym(3, [3, 1]) + Traceback (most recent call last): + ... + IndexError: invalid index position: 3 not in [0,2] + """ + if not sym_or_antisym: + return () + # Handle the case that sym_or_antisym is an iterator + sym_or_antisym = tuple(sym_or_antisym) + result_sym_or_antisym = [] + if isinstance(sym_or_antisym[0], (int, Integer)): + # a single symmetry is provided as a tuple or a range object; + # it is converted to a 1-item list: + sym_or_antisym = (tuple(sym_or_antisym),) + for isym in sym_or_antisym: + if len(isym) < 2: + # Drop trivial symmetry + continue + isym = tuple(sorted(isym)) + if isym[0] < 0: + raise IndexError("invalid index position: " + str(isym[0]) + + " not in [0," + str(nb_indices-1) + "]") + if isym[-1] > nb_indices - 1: + raise IndexError("invalid index position: " + str(isym[-1]) + + " not in [0," + str(nb_indices-1) + "]") + result_sym_or_antisym.append(isym) + # Canonicalize sort order, make tuples + return tuple(sorted(result_sym_or_antisym)) + + @staticmethod + def _canonicalize_sym_antisym(nb_indices, sym=None, antisym=None): + r""" + Bring ``sym`` and ``antisym`` into their canonical form. + + INPUT: + + - ``nb_indices`` -- number of integer indices labeling the components + + - ``sym`` -- (default: ``None``) a symmetry or an iterable of symmetries + among the tensor arguments: each symmetry is described by a tuple + containing the positions of the involved arguments, with the + convention ``position = 0`` for the first argument. For instance: + + * ``sym = (0,1)`` for a symmetry between the 1st and 2nd arguments + * ``sym = [(0,2), (1,3,4)]`` for a symmetry between the 1st and 3rd + arguments and a symmetry between the 2nd, 4th and 5th arguments. + + - ``antisym`` -- (default: ``None``) antisymmetry or iterable of + antisymmetries among the arguments, with the same convention + as for ``sym`` + + EXAMPLES:: + + sage: from sage.tensor.modules.comp import CompWithSym + sage: CompWithSym._canonicalize_sym_antisym(6, [(2, 1)]) + (((1, 2),), ()) + """ + if not sym and not antisym: + # fast path + return (), () + result_sym = CompWithSym._canonicalize_sym_or_antisym(nb_indices, sym) + result_antisym = CompWithSym._canonicalize_sym_or_antisym(nb_indices, antisym) # Final consistency check: index_list = [] - for isym in self._sym: - index_list += isym - for isym in self._antisym: - index_list += isym + for isym in result_sym: + index_list.extend(isym) + for isym in result_antisym: + index_list.extend(isym) if len(index_list) != len(set(index_list)): # There is a repeated index position: raise IndexError("incompatible lists of symmetries: the same " + - "index position appears more then once") + "index position appears more than once") + return result_sym, result_antisym - def _repr_(self): + def _repr_symmetry(self): r""" - Return a string representation of ``self``. + Return a prefix and a suffix string describing the symmetry of ``self``. EXAMPLES:: sage: from sage.tensor.modules.comp import CompWithSym - sage: CompWithSym(ZZ, [1,2,3], 4, sym=(0,1)) - 4-indices components w.r.t. [1, 2, 3], - with symmetry on the index positions (0, 1) - sage: CompWithSym(ZZ, [1,2,3], 4, sym=(0,1), antisym=(2,3)) - 4-indices components w.r.t. [1, 2, 3], - with symmetry on the index positions (0, 1), - with antisymmetry on the index positions (2, 3) - + sage: cp = CompWithSym(QQ, [1,2,3], 4, sym=(0,1)) + sage: cp._repr_symmetry() + ('', ', with symmetry on the index positions (0, 1)') + sage: cp = CompWithSym(QQ, [1,2,3], 4, sym=((0,1),), antisym=((2,3),)) + sage: cp._repr_symmetry() + ('', + ', with symmetry on the index positions (0, 1), with antisymmetry on the index positions (2, 3)') """ - description = str(self._nid) - if self._nid == 1: - description += "-index" - else: - description += "-indices" - description += " components w.r.t. " + str(self._frame) + description = "" for isym in self._sym: description += ", with symmetry on the index positions " + \ str(tuple(isym)) for isym in self._antisym: description += ", with antisymmetry on the index positions " + \ str(tuple(isym)) - return description + return "", description def _new_instance(self): r""" @@ -3367,9 +3485,9 @@ def swap_adjacent_indices(self, pos1, pos2, pos3): [[0, 7, 8], [-7, 0, 9], [-8, -9, 0]]] sage: c1 = c.swap_adjacent_indices(0,1,3) sage: c._antisym # c is antisymmetric with respect to the last pair of indices... - [(1, 2)] + ((1, 2),) sage: c1._antisym #...while c1 is antisymmetric with respect to the first pair of indices - [(0, 1)] + ((0, 1),) sage: c[0,1,2] 3 sage: c1[1,2,0] @@ -3390,6 +3508,8 @@ def swap_adjacent_indices(self, pos1, pos2, pos3): for s in self._antisym: new_s = [new_lpos.index(pos) for pos in s] result._antisym.append(tuple(sorted(new_s))) + result._sym, result._antisym = self._canonicalize_sym_antisym( + self._nid, result._sym, result._antisym) # The values: for ind, val in self._comp.items(): new_ind = ind[:pos1] + ind[pos2:pos3] + ind[pos1:pos2] + ind[pos3:] @@ -3520,7 +3640,7 @@ def paral_sum(a, b, local_list_ind): com = tuple(set(isym).intersection(set(osym))) if len(com) > 1: common_antisym.append(com) - if common_sym != [] or common_antisym != []: + if common_sym or common_antisym: result = CompWithSym(self._ring, self._frame, self._nid, self._sindex, self._output_formatter, common_sym, common_antisym) @@ -3628,11 +3748,11 @@ def __mul__(self, other): sym = list(self._sym) antisym = list(self._antisym) if isinstance(other, CompWithSym): - if other._sym != []: + if other._sym: for s in other._sym: ns = tuple(s[i]+self._nid for i in range(len(s))) sym.append(ns) - if other._antisym != []: + if other._antisym: for s in other._antisym: ns = tuple(s[i]+self._nid for i in range(len(s))) antisym.append(ns) @@ -3958,7 +4078,7 @@ def non_redundant_index_generator(self): si = self._sindex imax = self._dim - 1 + si ind = [si for k in range(self._nid)] - sym = self._sym.copy() # we may modify this in the following + sym = list(self._sym) # we may modify this in the following antisym = self._antisym for pos in range(self._nid): for isym in antisym: @@ -4134,7 +4254,7 @@ def symmetrize(self, *pos): (0, 0, 1) ], with symmetry on the index positions (0, 1), with symmetry on the index positions (2, 3) sage: a1._sym # a1 has two distinct symmetries: - [(0, 1), (2, 3)] + ((0, 1), (2, 3)) sage: a[0,1,2,0] == a[0,0,2,1] # a is symmetric w.r.t. positions 1 and 3 True sage: a1[0,1,2,0] == a1[0,0,2,1] # a1 is not @@ -4412,10 +4532,10 @@ def antisymmetrize(self, *pos): (1, 0, 0), (0, 1, 0), (0, 0, 1) - ], with antisymmetry on the index positions (1, 3), - with antisymmetry on the index positions (0, 2) + ], with antisymmetry on the index positions (0, 2), + with antisymmetry on the index positions (1, 3) sage: s._antisym # the antisymmetry (0,1,2) has been reduced to (0,2), since 1 is involved in the new antisymmetry (1,3): - [(1, 3), (0, 2)] + ((0, 2), (1, 3)) Partial antisymmetrization of 4-indices components with a symmetry on the first two indices:: @@ -4731,19 +4851,19 @@ def __init__(self, ring, frame, nb_indices, start_index=0, CompWithSym.__init__(self, ring, frame, nb_indices, start_index, output_formatter, sym=range(nb_indices)) - def _repr_(self): + def _repr_symmetry(self): r""" - Return a string representation of ``self``. + Return a prefix and a suffix string describing the symmetry of ``self``. EXAMPLES:: sage: from sage.tensor.modules.comp import CompFullySym - sage: CompFullySym(ZZ, (1,2,3), 4) - Fully symmetric 4-indices components w.r.t. (1, 2, 3) + sage: c = CompFullySym(ZZ, (1,2,3), 4) + sage: c._repr_symmetry() + ('Fully symmetric ', '') """ - return "Fully symmetric " + str(self._nid) + "-indices" + \ - " components w.r.t. " + str(self._frame) + return "Fully symmetric ", "" def _new_instance(self): r""" @@ -5190,19 +5310,19 @@ def __init__(self, ring, frame, nb_indices, start_index=0, CompWithSym.__init__(self, ring, frame, nb_indices, start_index, output_formatter, antisym=range(nb_indices)) - def _repr_(self): + def _repr_symmetry(self): r""" - Return a string representation of ``self``. + Return a prefix and a suffix string describing the symmetry of ``self``. EXAMPLES:: sage: from sage.tensor.modules.comp import CompFullyAntiSym - sage: CompFullyAntiSym(ZZ, (1,2,3), 4) - Fully antisymmetric 4-indices components w.r.t. (1, 2, 3) + sage: c = CompFullyAntiSym(ZZ, (1,2,3), 4) + sage: c._repr_symmetry() + ('Fully antisymmetric ', '') """ - return "Fully antisymmetric " + str(self._nid) + "-indices" + \ - " components w.r.t. " + str(self._frame) + return "Fully antisymmetric ", "" def _new_instance(self): r""" @@ -5446,7 +5566,7 @@ def interior_product(self, other): True """ - from sage.arith.all import factorial + from sage.arith.misc import factorial # Sanity checks: if not isinstance(other, CompFullyAntiSym): raise TypeError("{} is not a fully antisymmetric ".format(other) + diff --git a/src/sage/tensor/modules/ext_pow_free_module.py b/src/sage/tensor/modules/ext_pow_free_module.py index c68033fdf51..d9db1b5a05b 100644 --- a/src/sage/tensor/modules/ext_pow_free_module.py +++ b/src/sage/tensor/modules/ext_pow_free_module.py @@ -58,12 +58,12 @@ from sage.misc.cachefunc import cached_method from sage.rings.integer import Integer from sage.rings.integer_ring import ZZ -from sage.tensor.modules.finite_rank_free_module import FiniteRankFreeModule +from sage.tensor.modules.finite_rank_free_module import FiniteRankFreeModule_abstract from sage.tensor.modules.free_module_tensor import FreeModuleTensor from sage.tensor.modules.alternating_contr_tensor import AlternatingContrTensor from sage.tensor.modules.free_module_alt_form import FreeModuleAltForm -class ExtPowerFreeModule(FiniteRankFreeModule): +class ExtPowerFreeModule(FiniteRankFreeModule_abstract): r""" Exterior power of a free module of finite rank over a commutative ring. @@ -236,7 +236,7 @@ def __init__(self, fmodule, degree, name=None, latex_name=None): sage: TestSuite(A).run() """ - from sage.arith.all import binomial + from sage.arith.misc import binomial from sage.typeset.unicode_characters import unicode_bigwedge self._fmodule = fmodule self._degree = ZZ(degree) @@ -247,12 +247,29 @@ def __init__(self, fmodule, degree, name=None, latex_name=None): if latex_name is None and fmodule._latex_name is not None: latex_name = r'\Lambda^{' + str(degree) + r'}\left(' \ + fmodule._latex_name + r'\right)' - FiniteRankFreeModule.__init__(self, fmodule._ring, rank, - name=name, latex_name=latex_name, - start_index=fmodule._sindex, - output_formatter=fmodule._output_formatter) + super().__init__(fmodule._ring, rank, + name=name, latex_name=latex_name) fmodule._all_modules.add(self) + def construction(self): + r""" + Return the functorial construction of ``self``. + + This implementation just returns ``None``, as no functorial construction is implemented. + + TESTS:: + + sage: from sage.tensor.modules.ext_pow_free_module import ExtPowerFreeModule + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: A = ExtPowerFreeModule(M, 2) + sage: A.construction() is None + True + """ + # No construction until https://trac.sagemath.org/ticket/30242 + # makes this a quotient of TensorFreeModule + return None + #### Parent methods def _element_constructor_(self, comp=[], basis=None, name=None, @@ -423,11 +440,10 @@ def degree(self): """ return self._degree - #*********************************************************************** -class ExtPowerDualFreeModule(FiniteRankFreeModule): +class ExtPowerDualFreeModule(FiniteRankFreeModule_abstract): r""" Exterior power of the dual of a free module of finite rank over a commutative ring. @@ -560,21 +576,12 @@ class ExtPowerDualFreeModule(FiniteRankFreeModule): sage: latex(M.dual()) M^* - Since any tensor of type (0,1) is a linear form, there is a coercion map - from the set `T^{(0,1)}(M)` of such tensors to `M^*`:: - - sage: T01 = M.tensor_module(0,1) ; T01 - Free module of type-(0,1) tensors on the Rank-3 free module M over the - Integer Ring - sage: M.dual().has_coerce_map_from(T01) - True - - There is also a coercion map in the reverse direction:: + It also coincides with the module of type-`(0,1)` tensors:: - sage: T01.has_coerce_map_from(M.dual()) + sage: M.dual_exterior_power(1) is M.tensor_module(0,1) True - For a degree `p\geq 2`, the coercion holds only in the direction + For a degree `p\geq 2`, there is a coercion map `\Lambda^p(M^*)\rightarrow T^{(0,p)}(M)`:: sage: T02 = M.tensor_module(0,2) ; T02 @@ -585,24 +592,6 @@ class ExtPowerDualFreeModule(FiniteRankFreeModule): sage: A.has_coerce_map_from(T02) False - The coercion map `T^{(0,1)}(M) \rightarrow M^*` in action:: - - sage: b = T01([-2,1,4], basis=e, name='b') ; b - Type-(0,1) tensor b on the Rank-3 free module M over the Integer Ring - sage: b.display(e) - b = -2 e^0 + e^1 + 4 e^2 - sage: lb = M.dual()(b) ; lb - Linear form b on the Rank-3 free module M over the Integer Ring - sage: lb.display(e) - b = -2 e^0 + e^1 + 4 e^2 - - The coercion map `M^* \rightarrow T^{(0,1)}(M)` in action:: - - sage: tlb = T01(lb) ; tlb - Type-(0,1) tensor b on the Rank-3 free module M over the Integer Ring - sage: tlb == b - True - The coercion map `\Lambda^2(M^*)\rightarrow T^{(0,2)}(M)` in action:: sage: ta = T02(a) ; ta @@ -631,29 +620,40 @@ def __init__(self, fmodule, degree, name=None, latex_name=None): sage: TestSuite(A).run() """ - from sage.arith.all import binomial + from sage.arith.misc import binomial from sage.typeset.unicode_characters import unicode_bigwedge self._fmodule = fmodule self._degree = ZZ(degree) rank = binomial(fmodule._rank, degree) - if degree == 1: # case of the dual - if name is None and fmodule._name is not None: - name = fmodule._name + '*' - if latex_name is None and fmodule._latex_name is not None: - latex_name = fmodule._latex_name + r'^*' - else: - if name is None and fmodule._name is not None: - name = unicode_bigwedge + r'^{}('.format(degree) \ - + fmodule._name + '*)' - if latex_name is None and fmodule._latex_name is not None: - latex_name = r'\Lambda^{' + str(degree) + r'}\left(' \ - + fmodule._latex_name + r'^*\right)' - FiniteRankFreeModule.__init__(self, fmodule._ring, rank, name=name, - latex_name=latex_name, - start_index=fmodule._sindex, - output_formatter=fmodule._output_formatter) + if name is None and fmodule._name is not None: + name = unicode_bigwedge + r'^{}('.format(degree) \ + + fmodule._name + '*)' + if latex_name is None and fmodule._latex_name is not None: + latex_name = r'\Lambda^{' + str(degree) + r'}\left(' \ + + fmodule._latex_name + r'^*\right)' + super().__init__(fmodule._ring, rank, name=name, + latex_name=latex_name) fmodule._all_modules.add(self) + def construction(self): + r""" + Return the functorial construction of ``self``. + + This implementation just returns ``None``, as no functorial construction is implemented. + + TESTS:: + + sage: from sage.tensor.modules.ext_pow_free_module import ExtPowerDualFreeModule + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: A = ExtPowerDualFreeModule(M, 2) + sage: A.construction() is None + True + """ + # No construction until https://trac.sagemath.org/ticket/30242 + # makes this a quotient of TensorFreeModule + return None + #### Parent methods def _element_constructor_(self, comp=[], basis=None, name=None, @@ -665,13 +665,6 @@ def _element_constructor_(self, comp=[], basis=None, name=None, sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') - sage: A = M.dual_exterior_power(1) - sage: a = A._element_constructor_(0) ; a - Linear form zero on the Rank-3 free module M over the Integer Ring - sage: a = A._element_constructor_([2,0,-1], name='a') ; a - Linear form a on the Rank-3 free module M over the Integer Ring - sage: a.display() - a = 2 e^0 - e^2 sage: A = M.dual_exterior_power(2) sage: a = A._element_constructor_(0) ; a Alternating form zero of degree 2 on the Rank-3 free module M over @@ -714,11 +707,6 @@ def _an_element_(self): sage: M = FiniteRankFreeModule(QQ, 4, name='M') sage: e = M.basis('e') - sage: a = M.dual_exterior_power(1)._an_element_() ; a - Linear form on the 4-dimensional vector space M over the Rational - Field - sage: a.display() - 1/2 e^0 sage: a = M.dual_exterior_power(2)._an_element_() ; a Alternating form of degree 2 on the 4-dimensional vector space M over the Rational Field @@ -757,47 +745,6 @@ def _an_element_(self): resu.set_comp()[ind] = self._fmodule._ring.an_element() return resu - def _coerce_map_from_(self, other): - r""" - Determine whether coercion to ``self`` exists from other parent. - - EXAMPLES: - - Sets of type-`(0,1)` tensors coerce to ``self`` if the degree is 1:: - - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: L1 = M.dual_exterior_power(1) ; L1 - Dual of the Rank-3 free module M over the Integer Ring - sage: T01 = M.tensor_module(0,1) ; T01 - Free module of type-(0,1) tensors on the Rank-3 free module M over - the Integer Ring - sage: L1._coerce_map_from_(T01) - True - - Of course, coercions from other tensor types are meaningless:: - - sage: L1._coerce_map_from_(M.tensor_module(1,0)) - False - sage: L1._coerce_map_from_(M.tensor_module(0,2)) - False - - If the degree is larger than 1, there is no coercion:: - - sage: L2 = M.dual_exterior_power(2) ; L2 - 2nd exterior power of the dual of the Rank-3 free module M over - the Integer Ring - sage: L2._coerce_map_from_(M.tensor_module(0,2)) - False - - """ - from sage.tensor.modules.tensor_free_module import TensorFreeModule - if isinstance(other, TensorFreeModule): - # coercion of a type-(0,1) tensor to a linear form - if self._fmodule is other._fmodule and self._degree == 1 and \ - other.tensor_type() == (0,1): - return True - return False - #### End of parent methods @cached_method @@ -832,8 +779,6 @@ def _repr_(self): EXAMPLES:: sage: M = FiniteRankFreeModule(ZZ, 5, name='M') - sage: M.dual_exterior_power(1)._repr_() - 'Dual of the Rank-5 free module M over the Integer Ring' sage: M.dual_exterior_power(2)._repr_() '2nd exterior power of the dual of the Rank-5 free module M over the Integer Ring' sage: M.dual_exterior_power(3)._repr_() @@ -846,8 +791,6 @@ def _repr_(self): '21st exterior power of the dual of the Rank-5 free module M over the Integer Ring' """ - if self._degree == 1: - return "Dual of the {}".format(self._fmodule) description = "{}".format(self._degree.ordinal_str()) description += " exterior power of the dual of the {}".format( self._fmodule) diff --git a/src/sage/tensor/modules/finite_rank_free_module.py b/src/sage/tensor/modules/finite_rank_free_module.py index 091dc252ad5..8c007727799 100644 --- a/src/sage/tensor/modules/finite_rank_free_module.py +++ b/src/sage/tensor/modules/finite_rank_free_module.py @@ -38,10 +38,11 @@ class :class:`~sage.modules.free_module.FreeModule_generic` AUTHORS: - Eric Gourgoulhon, Michal Bejger (2014-2015): initial version -- Travis Scrimshaw (2016): category set to Modules(ring).FiniteDimensional() +- Travis Scrimshaw (2016): category set to ``Modules(ring).FiniteDimensional()`` (:trac:`20770`) - Michael Jung (2019): improve treatment of the zero element - Eric Gourgoulhon (2021): unicode symbols for tensor and exterior products +- Matthias Koeppe (2022): ``FiniteRankFreeModule_abstract``, symmetric powers REFERENCES: @@ -519,16 +520,19 @@ class :class:`~sage.modules.free_module.FreeModule_generic` [2, 0, -5] """ -#****************************************************************************** -# Copyright (C) 2015-2021 Eric Gourgoulhon -# Copyright (C) 2015 Michal Bejger -# Copyright (C) 2016 Travis Scrimshaw +# ****************************************************************************** +# Copyright (C) 2014-2021 Eric Gourgoulhon +# 2014-2016 Travis Scrimshaw +# 2015 Michal Bejger +# 2016 Frédéric Chapoton +# 2020 Michael Jung +# 2020-2022 Matthias Koeppe # # 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 __future__ import annotations from typing import Generator, Optional @@ -538,12 +542,535 @@ class :class:`~sage.modules.free_module.FreeModule_generic` from sage.categories.rings import Rings from sage.misc.cachefunc import cached_method from sage.rings.integer import Integer +from sage.sets.family import Family, TrivialFamily from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation +from sage.tensor.modules.free_module_alt_form import FreeModuleAltForm from sage.tensor.modules.free_module_element import FiniteRankFreeModuleElement +from sage.tensor.modules.free_module_tensor import FreeModuleTensor +class FiniteRankFreeModule_abstract(UniqueRepresentation, Parent): + r""" + Abstract base class for free modules of finite rank over a commutative ring. + """ + + def __init__( + self, + ring, + rank, + name=None, + latex_name=None, + category=None, + ambient=None, + ): + r""" + See :class:`FiniteRankFreeModule` for documentation and examples. + + TESTS:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: TestSuite(M).run() + sage: e = M.basis('e') + sage: TestSuite(M).run() + sage: f = M.basis('f') + sage: TestSuite(M).run() + + """ + # This duplicates the normalization done in __classcall_private__, + # but it is needed for various subclasses. + if ring not in Rings().Commutative(): + raise TypeError("the module base ring must be commutative") + category = Modules(ring).FiniteDimensional().or_subcategory(category) + Parent.__init__(self, base=ring, category=category) + self._ring = ring # same as self._base + if ambient is None: + self._ambient_module = self + else: + self._ambient_module = ambient + self._rank = rank + self._name = name + # This duplicates the normalization done in __classcall_private__, + # but it is needed for various subclasses. + if latex_name is None: + self._latex_name = self._name + else: + self._latex_name = latex_name + + def _latex_(self): + r""" + LaTeX representation of ``self``. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: M._latex_() + 'M' + sage: latex(M) + M + sage: M1 = FiniteRankFreeModule(ZZ, 3, name='M', latex_name=r'\mathcal{M}') + sage: M1._latex_() + '\\mathcal{M}' + sage: latex(M1) + \mathcal{M} + + """ + if self._latex_name is None: + return r'\mbox{' + str(self) + r'}' + else: + return self._latex_name + + def tensor_power(self, n): + r""" + Return the ``n``-fold tensor product of ``self``. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(QQ, 2) + sage: M.tensor_power(3) + Free module of type-(3,0) tensors on the 2-dimensional vector space over the Rational Field + sage: M.tensor_module(1,2).tensor_power(3) + Free module of type-(3,6) tensors on the 2-dimensional vector space over the Rational Field + """ + tensor_type = self.tensor_type() + return self.base_module().tensor_module(n * tensor_type[0], n * tensor_type[1]) + + def tensor_product(self, *others): + r""" + Return the tensor product of ``self`` and ``others``. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(QQ, 2) + sage: M.tensor_product(M) + Free module of type-(2,0) tensors on the 2-dimensional vector space over the Rational Field + sage: M.tensor_product(M.dual()) + Free module of type-(1,1) tensors on the 2-dimensional vector space over the Rational Field + sage: M.dual().tensor_product(M, M.dual()) + Free module of type-(1,2) tensors on the 2-dimensional vector space over the Rational Field + sage: M.tensor_product(M.tensor_module(1,2)) + Free module of type-(2,2) tensors on the 2-dimensional vector space over the Rational Field + sage: M.tensor_module(1,2).tensor_product(M) + Free module of type-(2,2) tensors on the 2-dimensional vector space over the Rational Field + sage: M.tensor_module(1,1).tensor_product(M.tensor_module(1,2)) + Free module of type-(2,3) tensors on the 2-dimensional vector space over the Rational Field + + sage: Sym2M = M.tensor_module(2, 0, sym=range(2)); Sym2M + Free module of fully symmetric type-(2,0) tensors on the 2-dimensional vector space over the Rational Field + sage: Sym01x23M = Sym2M.tensor_product(Sym2M); Sym01x23M + Free module of type-(4,0) tensors on the 2-dimensional vector space over the Rational Field, + with symmetry on the index positions (0, 1), with symmetry on the index positions (2, 3) + sage: Sym01x23M._index_maps + ((0, 1), (2, 3)) + + sage: N = M.tensor_module(3, 3, sym=[1, 2], antisym=[3, 4]); N + Free module of type-(3,3) tensors on the 2-dimensional vector space over the Rational Field, + with symmetry on the index positions (1, 2), + with antisymmetry on the index positions (3, 4) + sage: NxN = N.tensor_product(N); NxN + Free module of type-(6,6) tensors on the 2-dimensional vector space over the Rational Field, + with symmetry on the index positions (1, 2), with symmetry on the index positions (4, 5), + with antisymmetry on the index positions (6, 7), with antisymmetry on the index positions (9, 10) + sage: NxN._index_maps + ((0, 1, 2, 6, 7, 8), (3, 4, 5, 9, 10, 11)) + """ + from sage.modules.free_module_element import vector + from .comp import CompFullySym, CompFullyAntiSym, CompWithSym + + base_module = self.base_module() + if not all(module.base_module() == base_module for module in others): + raise NotImplementedError('all factors must be tensor modules over the same base module') + factors = [self] + list(others) + result_tensor_type = sum(vector(factor.tensor_type()) for factor in factors) + result_sym = [] + result_antisym = [] + # Keep track of reordering of the contravariant and covariant indices + # (compatible with FreeModuleTensor.__mul__) + index_maps = [] + running_indices = vector([0, result_tensor_type[0]]) + for factor in factors: + tensor_type = factor.tensor_type() + index_map = tuple(i + running_indices[0] for i in range(tensor_type[0])) + index_map += tuple(i + running_indices[1] for i in range(tensor_type[1])) + index_maps.append(index_map) + + if tensor_type[0] + tensor_type[1] > 1: + basis_sym = factor._basis_sym() + all_indices = tuple(range(tensor_type[0] + tensor_type[1])) + if isinstance(basis_sym, CompFullySym): + sym = [all_indices] + antisym = [] + elif isinstance(basis_sym, CompFullyAntiSym): + sym = [] + antisym = [all_indices] + elif isinstance(basis_sym, CompWithSym): + sym = basis_sym._sym + antisym = basis_sym._antisym + else: + sym = antisym = [] + + def map_isym(isym): + return tuple(index_map[i] for i in isym) + + result_sym.extend(tuple(index_map[i] for i in isym) for isym in sym) + result_antisym.extend(tuple(index_map[i] for i in isym) for isym in antisym) + + running_indices += vector(tensor_type) + + result = base_module.tensor_module(*result_tensor_type, + sym=result_sym, antisym=result_antisym) + result._index_maps = tuple(index_maps) + return result + + def rank(self) -> int: + r""" + Return the rank of the free module ``self``. + + Since the ring over which ``self`` is built is assumed to be + commutative (and hence has the invariant basis number property), the + rank is defined uniquely, as the cardinality of any basis of ``self``. + + EXAMPLES: + + Rank of free modules over `\ZZ`:: + + sage: M = FiniteRankFreeModule(ZZ, 3) + sage: M.rank() + 3 + sage: M.tensor_module(0,1).rank() + 3 + sage: M.tensor_module(0,2).rank() + 9 + sage: M.tensor_module(1,0).rank() + 3 + sage: M.tensor_module(1,1).rank() + 9 + sage: M.tensor_module(1,2).rank() + 27 + sage: M.tensor_module(2,2).rank() + 81 + + """ + return self._rank + + @cached_method + def zero(self): + r""" + Return the zero element of ``self``. + + EXAMPLES: + + Zero elements of free modules over `\ZZ`:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: M.zero() + Element zero of the Rank-3 free module M over the Integer Ring + sage: M.zero().parent() is M + True + sage: M.zero() is M(0) + True + sage: T = M.tensor_module(1,1) + sage: T.zero() + Type-(1,1) tensor zero on the Rank-3 free module M over the Integer Ring + sage: T.zero().parent() is T + True + sage: T.zero() is T(0) + True + + Components of the zero element with respect to some basis:: + + sage: e = M.basis('e') + sage: M.zero()[e,:] + [0, 0, 0] + sage: all(M.zero()[e,i] == M.base_ring().zero() for i in M.irange()) + True + sage: T.zero()[e,:] + [0 0 0] + [0 0 0] + [0 0 0] + sage: M.tensor_module(1,2).zero()[e,:] + [[[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]]] + + """ + resu = self._element_constructor_(name='zero', latex_name='0') + for basis in self._known_bases: + resu._add_comp_unsafe(basis) + # (since new components are initialized to zero) + resu._is_zero = True # This element is certainly zero + resu.set_immutable() + return resu + + def ambient_module(self): # compatible with sage.modules.free_module.FreeModule_generic + """ + Return the ambient module associated to this module. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: M.ambient_module() is M + True + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: Sym0123x45M = M.tensor_module(6, 0, sym=((0, 1, 2, 3), (4, 5))) + sage: T60M = M.tensor_module(6, 0) + sage: Sym0123x45M.ambient_module() is T60M + True + """ + return self._ambient_module + + ambient = ambient_module # compatible with sage.modules.with_basis.subquotient.SubmoduleWithBasis + + def is_submodule(self, other): + """ + Return ``True`` if ``self`` is a submodule of ``other``. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: N = FiniteRankFreeModule(ZZ, 4, name='N') + sage: M.is_submodule(M) + True + sage: M.is_submodule(N) + False + """ + return self == other or self.ambient_module() == other + + def isomorphism_with_fixed_basis(self, basis=None, codomain=None): + r""" + Construct the canonical isomorphism from the free module ``self`` + to a free module in which ``basis`` of ``self`` is mapped to the + distinguished basis of ``codomain``. + + INPUT: + + - ``basis`` -- (default: ``None``) the basis of ``self`` which + should be mapped to the distinguished basis on ``codomain``; + if ``None``, the default basis is assumed. + - ``codomain`` -- (default: ``None``) the codomain of the + isomorphism represented by a free module within the category + :class:`~sage.categories.modules_with_basis.ModulesWithBasis` with + the same rank and base ring as ``self``; if ``None`` a free module + represented by + :class:`~sage.combinat.free_module.CombinatorialFreeModule` is + constructed + + OUTPUT: + + - a module morphism represented by + :class:`~sage.modules.with_basis.morphism.ModuleMorphismFromFunction` + + EXAMPLES:: + + sage: V = FiniteRankFreeModule(QQ, 3, start_index=1); V + 3-dimensional vector space over the Rational Field + sage: basis = e = V.basis("e"); basis + Basis (e_1,e_2,e_3) on the 3-dimensional vector space over the + Rational Field + sage: phi_e = V.isomorphism_with_fixed_basis(basis); phi_e + Generic morphism: + From: 3-dimensional vector space over the Rational Field + To: Free module generated by {1, 2, 3} over Rational Field + sage: phi_e.codomain().category() + Category of finite dimensional vector spaces with basis over + Rational Field + sage: phi_e(e[1] + 2 * e[2]) + e[1] + 2*e[2] + + sage: abc = V.basis(['a', 'b', 'c'], symbol_dual=['d', 'e', 'f']); abc + Basis (a,b,c) on the 3-dimensional vector space over the Rational Field + sage: phi_abc = V.isomorphism_with_fixed_basis(abc); phi_abc + Generic morphism: + From: 3-dimensional vector space over the Rational Field + To: Free module generated by {1, 2, 3} over Rational Field + sage: phi_abc(abc[1] + 2 * abc[2]) + B[1] + 2*B[2] + + Providing a codomain:: + + sage: W = CombinatorialFreeModule(QQ, ['a', 'b', 'c']) + sage: phi_eW = V.isomorphism_with_fixed_basis(basis, codomain=W); phi_eW + Generic morphism: + From: 3-dimensional vector space over the Rational Field + To: Free module generated by {'a', 'b', 'c'} over Rational Field + sage: phi_eW(e[1] + 2 * e[2]) + B['a'] + 2*B['b'] + + Providing a :class:`~sage.modules.free_module.Module_free_ambient` as the codomain:: + + sage: W = QQ^3 + sage: phi_eW = V.isomorphism_with_fixed_basis(basis, codomain=W); phi_eW + Generic morphism: + From: 3-dimensional vector space over the Rational Field + To: Vector space of dimension 3 over Rational Field + sage: phi_eW(e[1] + 2 * e[2]) + (1, 2, 0) + + Sending (1,1)-tensors to matrices:: + + sage: T11 = V.tensor_module(1, 1); T11 + Free module of type-(1,1) tensors on the 3-dimensional vector space over the Rational Field + sage: e_T11 = T11.basis("e"); e_T11 + Standard basis on the + Free module of type-(1,1) tensors on the 3-dimensional vector space over the Rational Field + induced by Basis (e_1,e_2,e_3) on the 3-dimensional vector space over the Rational Field + sage: W = MatrixSpace(QQ, 3) + sage: phi_e_T11 = T11.isomorphism_with_fixed_basis(e_T11, codomain=W); phi_e_T11 + Generic morphism: + From: Free module of type-(1,1) tensors on the 3-dimensional vector space over the Rational Field + To: Full MatrixSpace of 3 by 3 dense matrices over Rational Field + sage: t = T11.an_element(); t.display() + 1/2 e_1⊗e^1 + sage: phi_e_T11(t) + [1/2 0 0] + [ 0 0 0] + [ 0 0 0] + + Sending symmetric bilinear forms to matrices (note that they are currently elements + of `T^{(0,2)}(M)`, not the symmetric power of `M`):: + + sage: T02 = V.tensor_module(0, 2); T02 + Free module of type-(0,2) tensors on the 3-dimensional vector space over the Rational Field + sage: e_T02 = T02.basis("e"); e_T02 + Standard basis on the + Free module of type-(0,2) tensors on the 3-dimensional vector space over the Rational Field + induced by Basis (e_1,e_2,e_3) on the 3-dimensional vector space over the Rational Field + sage: W = MatrixSpace(QQ, 3) + sage: phi_e_T02 = T02.isomorphism_with_fixed_basis(e_T02, codomain=W); phi_e_T02 + Generic morphism: + From: Free module of type-(0,2) tensors on the 3-dimensional vector space over the Rational Field + To: Full MatrixSpace of 3 by 3 dense matrices over Rational Field + + sage: a = V.sym_bilinear_form() + sage: a[1,1], a[1,2], a[1,3] = 1, 2, 3 + sage: a[2,2], a[2,3] = 4, 5 + sage: a[3,3] = 6 + sage: a.display() + e^1⊗e^1 + 2 e^1⊗e^2 + 3 e^1⊗e^3 + 2 e^2⊗e^1 + 4 e^2⊗e^2 + 5 e^2⊗e^3 + 3 e^3⊗e^1 + 5 e^3⊗e^2 + 6 e^3⊗e^3 + sage: phi_e_T02(a) + [1 2 3] + [2 4 5] + [3 5 6] -class FiniteRankFreeModule(UniqueRepresentation, Parent): + Same but explicitly in the subspace of symmetric bilinear forms:: + + sage: Sym2Vdual = V.dual_symmetric_power(2); Sym2Vdual + Free module of fully symmetric type-(0,2) tensors on the 3-dimensional vector space over the Rational Field + sage: Sym2Vdual.is_submodule(T02) + True + sage: Sym2Vdual.rank() + 6 + sage: e_Sym2Vdual = Sym2Vdual.basis("e"); e_Sym2Vdual + Standard basis on the + Free module of fully symmetric type-(0,2) tensors on the 3-dimensional vector space over the Rational Field + induced by Basis (e_1,e_2,e_3) on the 3-dimensional vector space over the Rational Field + sage: W_basis = [phi_e_T02(b) for b in e_Sym2Vdual]; W_basis + [ + [1 0 0] [0 1 0] [0 0 1] [0 0 0] [0 0 0] [0 0 0] + [0 0 0] [1 0 0] [0 0 0] [0 1 0] [0 0 1] [0 0 0] + [0 0 0], [0 0 0], [1 0 0], [0 0 0], [0 1 0], [0 0 1] + ] + sage: W = MatrixSpace(QQ, 3).submodule(W_basis); W + Free module generated by {0, 1, 2, 3, 4, 5} over Rational Field + sage: phi_e_Sym2Vdual = Sym2Vdual.isomorphism_with_fixed_basis(e_Sym2Vdual, codomain=W); phi_e_Sym2Vdual + Generic morphism: + From: Free module of fully symmetric type-(0,2) tensors on the 3-dimensional vector space over the Rational Field + To: Free module generated by {0, 1, 2, 3, 4, 5} over Rational Field + + Sending tensors to elements of the tensor square of :class:`CombinatorialFreeModule`:: + + sage: T20 = V.tensor_module(2, 0); T20 + Free module of type-(2,0) tensors on the 3-dimensional vector space over the Rational Field + sage: e_T20 = T02.basis("e"); e_T20 + Standard basis on the + Free module of type-(0,2) tensors on the 3-dimensional vector space over the Rational Field + induced by Basis (e_1,e_2,e_3) on the 3-dimensional vector space over the Rational Field + sage: W = CombinatorialFreeModule(QQ, [1, 2, 3]).tensor_square(); W + Free module generated by {1, 2, 3} over Rational Field # Free module generated by {1, 2, 3} over Rational Field + sage: phi_e_T20 = T20.isomorphism_with_fixed_basis(e_T20, codomain=W); phi_e_T20 + Generic morphism: + From: Free module of type-(2,0) tensors on the 3-dimensional vector space over the Rational Field + To: Free module generated by {1, 2, 3} over Rational Field # Free module generated by {1, 2, 3} over Rational Field + sage: t = T20.an_element(); t.display() + 1/2 e_1⊗e_1 + sage: phi_e_T20(t) + 1/2*B[1] # B[1] + + TESTS:: + + sage: V = FiniteRankFreeModule(QQ, 3); V + 3-dimensional vector space over the Rational Field + sage: e = V.basis("e") + sage: V.isomorphism_with_fixed_basis(e, codomain=QQ^42) + Traceback (most recent call last): + ... + ValueError: domain and codomain must have the same rank + sage: V.isomorphism_with_fixed_basis(e, codomain=RR^3) + Traceback (most recent call last): + ... + ValueError: domain and codomain must have the same base ring + """ + base_ring = self.base_ring() + if basis is None: + basis = self.default_basis() + if codomain is None: + from sage.combinat.free_module import CombinatorialFreeModule + if isinstance(basis._symbol, str): + prefix = basis._symbol + else: + prefix = None + codomain = CombinatorialFreeModule(base_ring, basis.keys(), + prefix=prefix) + else: + try: + codomain_rank = codomain.rank() + except AttributeError: + # https://trac.sagemath.org/ticket/34445: MatrixSpace does not have rank + codomain_rank = codomain.dimension() + if codomain_rank != self.rank(): + raise ValueError("domain and codomain must have the same rank") + if codomain.base_ring() != base_ring: + raise ValueError("domain and codomain must have the same " + "base ring") + + codomain_basis = Family(codomain.basis()) + if isinstance(codomain_basis, TrivialFamily): + # assume that codomain basis keys are to be ignored + key_pairs = enumerate(basis.keys()) + else: + # assume that the keys of the codomain should be used + key_pairs = zip(codomain_basis.keys(), basis.keys()) + # Need them several times, can't keep as generators + key_pairs = tuple(key_pairs) + + def _isomorphism(x): + r""" + Concrete isomorphism from ``self`` to ``codomain``. + """ + return codomain.sum(x[basis, domain_key] * codomain_basis[codomain_key] + for codomain_key, domain_key in key_pairs) + + return self.module_morphism(function=_isomorphism, codomain=codomain) + + def _test_isomorphism_with_fixed_basis(self, **options): + r""" + Test that the method ``isomorphism_with_fixed_basis`` works correctly. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: M._test_isomorphism_with_fixed_basis() + """ + tester = self._tester(**options) + try: + basis = self.basis('test') + except AttributeError: + return + morphism = self.isomorphism_with_fixed_basis(basis) + tester.assertEqual(morphism.codomain().rank(), self.rank()) + + +class FiniteRankFreeModule(FiniteRankFreeModule_abstract): r""" Free module of finite rank over a commutative ring. @@ -757,7 +1284,7 @@ class :class:`~sage.modules.module.Module`. @staticmethod def __classcall_private__(cls, ring, rank, name=None, latex_name=None, start_index=0, - output_formatter=None, category=None): + output_formatter=None, category=None, ambient=None): r""" Normalize init arguments for ``UniqueRepresentation`` @@ -780,7 +1307,7 @@ def __classcall_private__(cls, ring, rank, name=None, latex_name=None, start_ind if latex_name is None: latex_name = name return super(FiniteRankFreeModule, cls).__classcall__( - cls, ring, rank, name, latex_name, start_index, output_formatter, category) + cls, ring, rank, name, latex_name, start_index, output_formatter, category, ambient) def __init__( self, @@ -791,6 +1318,7 @@ def __init__( start_index: int = 0, output_formatter=None, category=None, + ambient=None, ): r""" See :class:`FiniteRankFreeModule` for documentation and examples. @@ -805,21 +1333,8 @@ def __init__( sage: TestSuite(M).run() """ - # This duplicates the normalization done in __classcall_private__, - # but it is needed for various subclasses. - if ring not in Rings().Commutative(): - raise TypeError("the module base ring must be commutative") - category = Modules(ring).FiniteDimensional().or_subcategory(category) - Parent.__init__(self, base=ring, category=category) - self._ring = ring # same as self._base - self._rank = rank - self._name = name - # This duplicates the normalization done in __classcall_private__, - # but it is needed for various subclasses. - if latex_name is None: - self._latex_name = self._name - else: - self._latex_name = latex_name + super().__init__(ring, rank, name=name, latex_name=latex_name, + category=category, ambient=ambient) self._sindex = start_index self._output_formatter = output_formatter # Dictionary of the tensor modules built on self @@ -845,6 +1360,39 @@ def __init__( self._general_linear_group = None # to be set by # self.general_linear_group() + def construction(self): + """ + The construction functor and base ring for self. + + EXAMPLES:: + + sage: FiniteRankFreeModule._clear_cache_() # for doctests only + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: M.construction() + (VectorFunctor, Integer Ring) + sage: N = FiniteRankFreeModule(ZZ, 3, name='N', start_index=17) + sage: N.construction() + (VectorFunctor, Integer Ring) + """ + # Try to take it from the category + c = super().construction() + if c is not None: + return c + # Implementation restrictions: + if self._output_formatter: + return None + from sage.categories.pushout import VectorFunctor + kwds = dict(is_sparse=False, + inner_product_matrix=None, + with_basis=None, + name_mapping={self.base_ring(): self._name} if self._name else None, + latex_name_mapping={self.base_ring(): self._latex_name} if self._latex_name else None) + if self._sindex: + return (VectorFunctor(basis_keys=list(self.irange()), **kwds), + self.base_ring()) + return (VectorFunctor(n=self.rank(), **kwds), + self.base_ring()) + #### Parent methods def _element_constructor_(self, comp=[], basis=None, name=None, @@ -955,7 +1503,7 @@ def _Hom_(self, other, category=None): from .free_module_homset import FreeModuleHomset return FreeModuleHomset(self, other) - def tensor_module(self, k, l): + def tensor_module(self, k, l, *, sym=None, antisym=None): r""" Return the free module of all tensors of type `(k, l)` defined on ``self``. @@ -966,6 +1514,18 @@ def tensor_module(self, k, l): type being `(k, l)` - ``l`` -- non-negative integer; the covariant rank, the tensor type being `(k, l)` + - ``sym`` -- (default: ``None``) a symmetry or a list of symmetries + among the tensor arguments: each symmetry is described by a tuple + containing the positions of the involved arguments, with the + convention ``position = 0`` for the first argument. For instance: + + * ``sym = (0,1)`` for a symmetry between the 1st and 2nd arguments + * ``sym = [(0,2), (1,3,4)]`` for a symmetry between the 1st and 3rd + arguments and a symmetry between the 2nd, 4th and 5th arguments. + + - ``antisym`` -- (default: ``None``) antisymmetry or list of + antisymmetries among the arguments, with the same convention + as for ``sym`` OUTPUT: @@ -990,26 +1550,129 @@ def tensor_module(self, k, l): sage: M.tensor_module(1,2) is T True - The base module is itself the module of all type-`(1,0)` tensors:: + The module of type-`(1,0)` tensors is the base module itself:: sage: M.tensor_module(1,0) is M True + while the module of type-`(0,1)` tensors is the dual of the base module:: + + sage: M.tensor_module(0, 1) is M.dual() + True + + By using the arguments ``sym`` and ``antisym``, submodules of a full tensor + module can be constructed:: + + sage: T = M.tensor_module(4, 4, sym=((0, 1)), antisym=((4, 5))); T + Free module of type-(4,4) tensors on the Rank-3 free module M over the Integer Ring, + with symmetry on the index positions (0, 1), + with antisymmetry on the index positions (4, 5) + sage: T._name + 'T^{2,3}(M)⊗T^{6,7}(M*)⊗Sym^{0,1}(M)⊗ASym^{4,5}(M*)' + sage: latex(T) + T^{\{2,3\}}(M) \otimes T^{\{6,7\}}(M^*) \otimes \mathrm{Sym}^{\{0,1\}}(M) \otimes \mathrm{ASym}^{\{4,5\}}(M^*) + See :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule` + and :class:`~sage.tensor.modules.tensor_free_module.TensorFreeSubmodule_sym` for more documentation. + TESTS:: + + sage: M = FiniteRankFreeModule(ZZ, 2) + sage: M.tensor_module(2, 0, sym=(0,1)) is M.symmetric_power(2) + True """ + from .comp import CompWithSym + + sym, antisym = CompWithSym._canonicalize_sym_antisym(k + l, sym, antisym) + if sym or antisym: + key = (k, l, sym, antisym) + else: + key = (k, l) try: - return self._tensor_modules[(k,l)] + return self._tensor_modules[key] except KeyError: - if (k, l) == (1, 0): + if key == (1, 0): T = self + elif key == (0, 1): + T = self.dual() + elif sym or antisym: + from sage.tensor.modules.tensor_free_submodule import TensorFreeSubmodule_sym + T = TensorFreeSubmodule_sym(self, (k, l), sym=sym, antisym=antisym) else: from sage.tensor.modules.tensor_free_module import TensorFreeModule - T = TensorFreeModule(self, (k,l)) - self._tensor_modules[(k,l)] = T + T = TensorFreeModule(self, (k, l)) + self._tensor_modules[key] = T return T + def symmetric_power(self, p): + r""" + Return the `p`-th symmetric power of ``self``. + + EXAMPLES: + + Symmetric powers of a free `\ZZ`-module of rank 3:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: M.symmetric_power(0) + Free module of type-(0,0) tensors on the Rank-3 free module M over the Integer Ring + sage: M.symmetric_power(1) # return the module itself + Rank-3 free module M over the Integer Ring + sage: M.symmetric_power(1) is M + True + sage: M.symmetric_power(2) + Free module of fully symmetric type-(2,0) tensors + on the Rank-3 free module M over the Integer Ring + sage: M.symmetric_power(2).an_element() + Type-(2,0) tensor on the Rank-3 free module M over the Integer Ring + sage: M.symmetric_power(2).an_element().display() + e_0⊗e_0 + sage: M.symmetric_power(3) + Free module of fully symmetric type-(3,0) tensors + on the Rank-3 free module M over the Integer Ring + sage: M.symmetric_power(3).an_element() + Type-(3,0) tensor on the Rank-3 free module M over the Integer Ring + sage: M.symmetric_power(3).an_element().display() + e_0⊗e_0⊗e_0 + """ + if p <= 1: + return self.tensor_module(p, 0) + return self.tensor_module(p, 0, sym=(tuple(range(p)),)) + + def dual_symmetric_power(self, p): + r""" + Return the `p`-th symmetric power of the dual of ``self``. + + EXAMPLES: + + Symmetric powers of the dual of a free `\ZZ`-module of rank 3:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: M.dual_symmetric_power(0) + Free module of type-(0,0) tensors on the Rank-3 free module M over the Integer Ring + sage: M.dual_symmetric_power(1) # return the dual module + Dual of the Rank-3 free module M over the Integer Ring + sage: M.dual_symmetric_power(2) + Free module of fully symmetric type-(0,2) tensors + on the Rank-3 free module M over the Integer Ring + sage: M.dual_symmetric_power(2).an_element() + Symmetric bilinear form on the Rank-3 free module M over the Integer Ring + sage: M.dual_symmetric_power(2).an_element().display() + e^0⊗e^0 + sage: M.dual_symmetric_power(3) + Free module of fully symmetric type-(0,3) tensors + on the Rank-3 free module M over the Integer Ring + sage: M.dual_symmetric_power(3).an_element() + Type-(0,3) tensor on the Rank-3 free module M over the Integer Ring + sage: M.dual_symmetric_power(3).an_element().display() + e^0⊗e^0⊗e^0 + """ + if p <= 1: + return self.tensor_module(0, p) + return self.tensor_module(0, p, sym=(tuple(range(p)),)) + def exterior_power(self, p): r""" Return the `p`-th exterior power of ``self``. @@ -1041,7 +1704,7 @@ def exterior_power(self, p): EXAMPLES: - Exterior powers of the dual of a free `\ZZ`-module of rank 3:: + Exterior powers of a free `\ZZ`-module of rank 3:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') @@ -1109,6 +1772,9 @@ def dual_exterior_power(self, p): OUTPUT: - for `p=0`, the base ring `R` + - for `p=1`, instance of + :class:`~sage.tensor.modules.finite_rank_free_module.FiniteRankDualFreeModule` + representing the dual `M^*` - for `p\geq 1`, instance of :class:`~sage.tensor.modules.ext_pow_free_module.ExtPowerDualFreeModule` representing the free module `\Lambda^p(M^*)` @@ -1147,6 +1813,8 @@ def dual_exterior_power(self, p): except KeyError: if p == 0: L = self._ring + elif p == 1: + L = FiniteRankDualFreeModule(self) else: from sage.tensor.modules.ext_pow_free_module import ExtPowerDualFreeModule L = ExtPowerDualFreeModule(self, p) @@ -1391,6 +2059,69 @@ def basis(self, symbol, latex_symbol=None, from_family=None, "linearly independent") return resu + def _test_basis(self, tester=None, **options): + r""" + Test that the ``basis`` method works correctly. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: M._test_basis(verbose=True) + + Running the test suite of self.basis('test') + running ._test_an_element() . . . pass + running ._test_cardinality() . . . pass + running ._test_category() . . . pass + running ._test_construction() . . . pass + running ._test_elements() . . . + Running the test suite of self.an_element() + running ._test_category() . . . pass + running ._test_eq() . . . pass + running ._test_new() . . . pass + running ._test_nonzero_equal() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_pickling() . . . pass + pass + running ._test_elements_eq_reflexive() . . . pass + running ._test_elements_eq_symmetric() . . . pass + running ._test_elements_eq_transitive() . . . pass + running ._test_elements_neq() . . . pass + running ._test_enumerated_set_contains() . . . pass + running ._test_enumerated_set_iter_cardinality() . . . pass + running ._test_enumerated_set_iter_list() . . . pass + running ._test_eq() . . . pass + running ._test_iter_len() . . . pass + running ._test_new() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_pickling() . . . pass + running ._test_some_elements() . . . pass + + """ + from sage.misc.sage_unittest import TestSuite + # The intention is to raise an exception only if this is + # run as a sub-testsuite of a larger testsuite. + # (from _test_elements) + is_sub_testsuite = (tester is not None) + tester = self._tester(tester=tester, **options) + try: + b = self.basis('test') + except NotImplementedError: + return + # Test uniqueness + b_again = self.basis('test') + tester.assertTrue(b is b_again) + # Test rank + tester.assertEqual(len(b), self.rank()) + indices = list(self.irange()) + tester.assertEqual(len(b), len(indices)) + # Test basis indexing + for index, element in zip(indices, b): + tester.assertTrue(element is b[index]) + # Run test suite of the basis object (similar to _test_elements) + tester.info("\n Running the test suite of self.basis('test')") + TestSuite(b).run(verbose=tester._verbose, prefix=tester._prefix + " ", + raise_on_failure=is_sub_testsuite) + def tensor(self, tensor_type, name=None, latex_name=None, sym=None, antisym=None): r""" @@ -1404,7 +2135,7 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to denote the tensor; if none is provided, the LaTeX symbol is set to ``name`` - - ``sym`` -- (default: ``None``) a symmetry or a list of symmetries + - ``sym`` -- (default: ``None``) a symmetry or an iterable of symmetries among the tensor arguments: each symmetry is described by a tuple containing the positions of the involved arguments, with the convention ``position = 0`` for the first argument. For instance: @@ -1413,7 +2144,7 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, * ``sym = [(0,2), (1,3,4)]`` for a symmetry between the 1st and 3rd arguments and a symmetry between the 2nd, 4th and 5th arguments. - - ``antisym`` -- (default: ``None``) antisymmetry or list of + - ``antisym`` -- (default: ``None``) antisymmetry or iterable of antisymmetries among the arguments, with the same convention as for ``sym`` @@ -1447,34 +2178,31 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, See :class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor` for more examples and documentation. + TESTS: + + Trivial symmetries in the list of symmetries or antisymmetries are silently + ignored:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: M.tensor((3,0), sym=[[1]]) + Type-(3,0) tensor on the Rank-3 free module M over the Integer Ring + sage: M.tensor((3,0), antisym=[[]]) + Type-(3,0) tensor on the Rank-3 free module M over the Integer Ring """ + from .comp import CompWithSym + sym, antisym = CompWithSym._canonicalize_sym_antisym( + tensor_type[0] + tensor_type[1], sym, antisym) # Special cases: if tensor_type == (1,0): return self.element_class(self, name=name, latex_name=latex_name) elif tensor_type == (0,1): return self.linear_form(name=name, latex_name=latex_name) elif tensor_type[0] == 0 and tensor_type[1] > 1 and antisym: - if isinstance(antisym[0], (int, Integer)): - # a single antisymmetry is provided as a tuple or a range - # object; it is converted to a 1-item list: - antisym = [tuple(antisym)] - if isinstance(antisym, list): - antisym0 = antisym[0] - else: - antisym0 = antisym - if len(antisym0) == tensor_type[1]: + if len(antisym[0]) == tensor_type[1]: return self.alternating_form(tensor_type[1], name=name, latex_name=latex_name) elif tensor_type[0] > 1 and tensor_type[1] == 0 and antisym: - if isinstance(antisym[0], (int, Integer)): - # a single antisymmetry is provided as a tuple or a range - # object; it is converted to a 1-item list: - antisym = [tuple(antisym)] - if isinstance(antisym, list): - antisym0 = antisym[0] - else: - antisym0 = antisym - if len(antisym0) == tensor_type[0]: + if len(antisym[0]) == tensor_type[0]: return self.alternating_contravariant_tensor(tensor_type[0], name=name, latex_name=latex_name) # Generic case: @@ -1948,113 +2676,10 @@ def sym_bilinear_form(self, name=None, latex_name=None): for more documentation. """ - return self.tensor_module(0,2).element_class(self, (0,2), name=name, - latex_name=latex_name, sym=(0,1)) - - #### End of methods to be redefined by derived classes #### - - def _latex_(self): - r""" - LaTeX representation of ``self``. - - EXAMPLES:: - - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: M._latex_() - 'M' - sage: latex(M) - M - sage: M1 = FiniteRankFreeModule(ZZ, 3, name='M', latex_name=r'\mathcal{M}') - sage: M1._latex_() - '\\mathcal{M}' - sage: latex(M1) - \mathcal{M} - - """ - if self._latex_name is None: - return r'\mbox{' + str(self) + r'}' - else: - return self._latex_name - - def rank(self) -> int: - r""" - Return the rank of the free module ``self``. - - Since the ring over which ``self`` is built is assumed to be - commutative (and hence has the invariant basis number property), the - rank is defined uniquely, as the cardinality of any basis of ``self``. - - EXAMPLES: - - Rank of free modules over `\ZZ`:: - - sage: M = FiniteRankFreeModule(ZZ, 3) - sage: M.rank() - 3 - sage: M.tensor_module(0,1).rank() - 3 - sage: M.tensor_module(0,2).rank() - 9 - sage: M.tensor_module(1,0).rank() - 3 - sage: M.tensor_module(1,1).rank() - 9 - sage: M.tensor_module(1,2).rank() - 27 - sage: M.tensor_module(2,2).rank() - 81 - - """ - return self._rank - - @cached_method - def zero(self): - r""" - Return the zero element of ``self``. - - EXAMPLES: - - Zero elements of free modules over `\ZZ`:: - - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: M.zero() - Element zero of the Rank-3 free module M over the Integer Ring - sage: M.zero().parent() is M - True - sage: M.zero() is M(0) - True - sage: T = M.tensor_module(1,1) - sage: T.zero() - Type-(1,1) tensor zero on the Rank-3 free module M over the Integer Ring - sage: T.zero().parent() is T - True - sage: T.zero() is T(0) - True - - Components of the zero element with respect to some basis:: - - sage: e = M.basis('e') - sage: M.zero()[e,:] - [0, 0, 0] - sage: all(M.zero()[e,i] == M.base_ring().zero() for i in M.irange()) - True - sage: T.zero()[e,:] - [0 0 0] - [0 0 0] - [0 0 0] - sage: M.tensor_module(1,2).zero()[e,:] - [[[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]]] - - """ - resu = self._element_constructor_(name='zero', latex_name='0') - for basis in self._known_bases: - resu._add_comp_unsafe(basis) - # (since new components are initialized to zero) - resu._is_zero = True # This element is certainly zero - resu.set_immutable() - return resu + return self.tensor_module(0,2).element_class(self, (0,2), name=name, + latex_name=latex_name, sym=(0,1)) + + #### End of methods to be redefined by derived classes #### def dual(self): r""" @@ -2072,7 +2697,8 @@ def dual(self): The dual is a free module of the same rank as M:: - sage: isinstance(M.dual(), FiniteRankFreeModule) + sage: from sage.tensor.modules.finite_rank_free_module import FiniteRankFreeModule_abstract + sage: isinstance(M.dual(), FiniteRankFreeModule_abstract) True sage: M.dual().rank() 3 @@ -2577,106 +3203,6 @@ def hom(self, codomain, matrix_rep, bases=None, name=None, return homset(matrix_rep, bases=bases, name=name, latex_name=latex_name) - def isomorphism_with_fixed_basis(self, basis, codomain=None): - r""" - Construct the canonical isomorphism from the free module ``self`` - to a free module in which ``basis`` of ``self`` is mapped to the - distinguished basis of ``codomain``. - - INPUT: - - - ``basis`` -- the basis of ``self`` which should be mapped to the - distinguished basis on ``codomain`` - - ``codomain`` -- (default: ``None``) the codomain of the - isomorphism represented by a free module within the category - :class:`~sage.categories.modules_with_basis.ModulesWithBasis` with - the same rank and base ring as ``self``; if ``None`` a free module - represented by - :class:`~sage.combinat.free_module.CombinatorialFreeModule` is - constructed - - OUTPUT: - - - a module morphism represented by - :class:`~sage.modules.with_basis.morphism.ModuleMorphismFromFunction` - - EXAMPLES:: - - sage: V = FiniteRankFreeModule(QQ, 3, start_index=1); V - 3-dimensional vector space over the Rational Field - sage: basis = e = V.basis("e"); basis - Basis (e_1,e_2,e_3) on the 3-dimensional vector space over the - Rational Field - sage: phi_e = V.isomorphism_with_fixed_basis(basis); phi_e - Generic morphism: - From: 3-dimensional vector space over the Rational Field - To: Free module generated by {1, 2, 3} over Rational Field - sage: phi_e.codomain().category() - Category of finite dimensional vector spaces with basis over - Rational Field - sage: phi_e(e[1] + 2 * e[2]) - e[1] + 2*e[2] - - sage: abc = V.basis(['a', 'b', 'c'], symbol_dual=['d', 'e', 'f']); abc - Basis (a,b,c) on the 3-dimensional vector space over the Rational Field - sage: phi_abc = V.isomorphism_with_fixed_basis(abc); phi_abc - Generic morphism: - From: 3-dimensional vector space over the Rational Field - To: Free module generated by {1, 2, 3} over Rational Field - sage: phi_abc(abc[1] + 2 * abc[2]) - B[1] + 2*B[2] - - Providing a codomain:: - - sage: W = CombinatorialFreeModule(QQ, ['a', 'b', 'c']) - sage: phi_eW = V.isomorphism_with_fixed_basis(basis, codomain=W); phi_eW - Generic morphism: - From: 3-dimensional vector space over the Rational Field - To: Free module generated by {'a', 'b', 'c'} over Rational Field - sage: phi_eW(e[1] + 2 * e[2]) - B['a'] + 2*B['b'] - - TESTS:: - - sage: V = FiniteRankFreeModule(QQ, 3); V - 3-dimensional vector space over the Rational Field - sage: e = V.basis("e") - sage: V.isomorphism_with_fixed_basis(e, codomain=QQ^42) - Traceback (most recent call last): - ... - ValueError: domain and codomain must have the same rank - sage: V.isomorphism_with_fixed_basis(e, codomain=RR^3) - Traceback (most recent call last): - ... - ValueError: domain and codomain must have the same base ring - """ - base_ring = self.base_ring() - if codomain is None: - from sage.combinat.free_module import CombinatorialFreeModule - if isinstance(basis._symbol, str): - prefix = basis._symbol - else: - prefix = None - codomain = CombinatorialFreeModule(base_ring, list(self.irange()), - prefix=prefix) - else: - if codomain.rank() != self.rank(): - raise ValueError("domain and codomain must have the same rank") - if codomain.base_ring() != base_ring: - raise ValueError("domain and codomain must have the same " - "base ring") - - codomain_basis = list(codomain.basis()) - - def _isomorphism(x): - r""" - Concrete isomorphism from ``self`` to ``codomain``. - """ - return codomain.sum(x[basis, i] * codomain_basis[i - self._sindex] - for i in self.irange()) - - return self.module_morphism(function=_isomorphism, codomain=codomain) - def endomorphism(self, matrix_rep, basis=None, name=None, latex_name=None): r""" Construct an endomorphism of the free module ``self``. @@ -2824,3 +3350,323 @@ def identity_map(self, name='Id', latex_name=None): latex_name = name self._identity_map.set_name(name=name, latex_name=latex_name) return self._identity_map + + def base_module(self): + r""" + Return the free module on which ``self`` is constructed, namely ``self`` itself. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: M.base_module() is M + True + + """ + return self + + def tensor_type(self): + r""" + Return the tensor type of ``self``, the pair `(1, 0)`. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3) + sage: M.tensor_type() + (1, 0) + + """ + return (1, 0) + + +class FiniteRankDualFreeModule(FiniteRankFreeModule_abstract): + r""" + Dual of a free module of finite rank over a commutative ring. + + Given a free module `M` of finite rank over a commutative ring `R`, + the *dual of* `M` is the set `M^*` of all linear forms on `M`, + i.e., linear maps + + .. MATH:: + + M \longrightarrow R + + This is a Sage *parent* class, whose *element* class is + :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleAltForm`. + + INPUT: + + - ``fmodule`` -- free module `M` of finite rank, as an instance of + :class:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule` + - ``name`` -- (default: ``None``) string; name given to `M^*` + - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to denote `M^*` + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: A = M.dual(); A + Dual of the Rank-3 free module M over the Integer Ring + + ``A`` is a module (actually a free module) over `\ZZ`:: + + sage: A.category() + Category of finite dimensional modules over Integer Ring + sage: A in Modules(ZZ) + True + sage: A.rank() + 3 + sage: A.base_ring() + Integer Ring + sage: A.base_module() + Rank-3 free module M over the Integer Ring + + ``A`` is a *parent* object, whose elements are linear forms, + represented by instances of the class + :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleAltForm`:: + + sage: a = A.an_element() ; a + Linear form on the Rank-3 free module M over the Integer Ring + sage: a.display() # expansion with respect to M's default basis (e) + e^0 + sage: from sage.tensor.modules.free_module_alt_form import FreeModuleAltForm + sage: isinstance(a, FreeModuleAltForm) + True + sage: a in A + True + sage: A.is_parent_of(a) + True + + Elements can be constructed from ``A``. In particular, 0 yields + the zero element of ``A``:: + + sage: A(0) + Linear form zero on the Rank-3 free module M over the Integer Ring + sage: A(0) is A.zero() + True + + while non-zero elements are constructed by providing their components in a + given basis:: + + sage: e + Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring + sage: comp = [0,3,-1] + sage: a = A(comp, basis=e, name='a') ; a + Linear form a on the Rank-3 free module M over the Integer Ring + sage: a.display(e) + a = 3 e^1 - e^2 + + An alternative is to construct the alternating form from an empty list of + components and to set the nonzero components afterwards:: + + sage: a = A([], name='a') + sage: a.set_comp(e)[0] = 3 + sage: a.set_comp(e)[1] = -1 + sage: a.set_comp(e)[2] = 4 + sage: a.display(e) + a = 3 e^0 - e^1 + 4 e^2 + + The dual is unique:: + + sage: A is M.dual() + True + + The exterior power `\Lambda^1(M^*)` is nothing but `M^*`:: + + sage: M.dual_exterior_power(1) is M.dual() + True + + It also coincides with the module of type-`(0,1)` tensors:: + + sage: M.dual_exterior_power(1) is M.tensor_module(0,1) + True + """ + + Element = FreeModuleAltForm + + def __init__(self, fmodule, name=None, latex_name=None): + r""" + TESTS:: + + sage: from sage.tensor.modules.finite_rank_free_module import FiniteRankDualFreeModule + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: A = FiniteRankDualFreeModule(M) ; A + Dual of the Rank-3 free module M over the Integer Ring + sage: TestSuite(A).run() + + """ + self._fmodule = fmodule + rank = fmodule._rank + if name is None and fmodule._name is not None: + name = fmodule._name + '*' + if latex_name is None and fmodule._latex_name is not None: + latex_name = fmodule._latex_name + r'^*' + super().__init__(fmodule._ring, rank, name=name, + latex_name=latex_name) + fmodule._all_modules.add(self) + + def construction(self): + r""" + Return the functorial construction of ``self``. + + This implementation just returns ``None``, as no functorial construction is implemented. + + TESTS:: + + sage: from sage.tensor.modules.ext_pow_free_module import ExtPowerDualFreeModule + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: A = M.dual() + sage: A.construction() is None + True + """ + # No construction until we extend VectorFunctor with a parameter 'dual' + return None + + #### Parent methods + + def _element_constructor_(self, comp=[], basis=None, name=None, + latex_name=None): + r""" + Construct a linear form. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: A = M.dual() + sage: a = A._element_constructor_(0) ; a + Linear form zero on the Rank-3 free module M over the Integer Ring + sage: a = A._element_constructor_([2,0,-1], name='a') ; a + Linear form a on the Rank-3 free module M over the Integer Ring + sage: a.display() + a = 2 e^0 - e^2 + """ + if isinstance(comp, (int, Integer)) and comp == 0: + return self.zero() + if isinstance(comp, FreeModuleTensor): + # coercion of a tensor of type (0,1) to a linear form + tensor = comp # for readability + if tensor.tensor_type() == (0,1) and self._degree == 1 and \ + tensor.base_module() is self._fmodule: + resu = self.element_class(self._fmodule, 1, name=tensor._name, + latex_name=tensor._latex_name) + for basis, comp in tensor._components.items(): + resu._components[basis] = comp.copy() + return resu + else: + raise TypeError("cannot coerce the {} ".format(tensor) + + "to an element of {}".format(self)) + # standard construction + resu = self.element_class(self._fmodule, 1, name=name, latex_name=latex_name) + if comp: + resu.set_comp(basis)[:] = comp + return resu + + def _an_element_(self): + r""" + Construct some (unnamed) alternating form. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(QQ, 4, name='M') + sage: e = M.basis('e') + sage: a = M.dual()._an_element_() ; a + Linear form on the 4-dimensional vector space M over the Rational + Field + sage: a.display() + 1/2 e^0 + + TESTS: + + When the base module has no default basis, a default + basis will be set for it:: + + sage: M2 = FiniteRankFreeModule(QQ, 4, name='M2') + sage: a = M2.dual()._an_element_(); a + Linear form on the 4-dimensional vector space M2 over the Rational Field + sage: a + a + Linear form on the 4-dimensional vector space M2 over the Rational Field + sage: M2.default_basis() + Basis (e_0,e_1,e_2,e_3) on the 4-dimensional vector space M2 over the Rational Field + + """ + resu = self.element_class(self._fmodule, 1) + # Make sure that the base module has a default basis + self._fmodule.an_element() + sindex = self._fmodule._sindex + ind = [sindex + i for i in range(resu._tensor_rank)] + resu.set_comp()[ind] = self._fmodule._ring.an_element() + return resu + + #### End of parent methods + + @cached_method + def zero(self): + r""" + Return the zero of ``self``. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: A = M.dual() + sage: A.zero() + Linear form zero on the Rank-3 free module M over the Integer Ring + sage: A(0) is A.zero() + True + + """ + resu = self._element_constructor_(name='zero', latex_name='0') + for basis in self._fmodule._known_bases: + resu._components[basis] = resu._new_comp(basis) + # (since new components are initialized to zero) + resu._is_zero = True # This element is certainly zero + resu.set_immutable() + return resu + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 5, name='M') + sage: M.dual_exterior_power(1)._repr_() + 'Dual of the Rank-5 free module M over the Integer Ring' + """ + return "Dual of the {}".format(self._fmodule) + + def base_module(self): + r""" + Return the free module on which ``self`` is constructed. + + OUTPUT: + + - instance of :class:`FiniteRankFreeModule` representing the free + module on which the dual is defined. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 5, name='M') + sage: A = M.dual() + sage: A.base_module() + Rank-5 free module M over the Integer Ring + sage: A.base_module() is M + True + + """ + return self._fmodule + + def tensor_type(self): + r""" + Return the tensor type of ``self``. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: M.dual().tensor_type() + (0, 1) + + """ + return (0, 1) diff --git a/src/sage/tensor/modules/free_module_basis.py b/src/sage/tensor/modules/free_module_basis.py index 7f90d56e9fc..7369c93263e 100644 --- a/src/sage/tensor/modules/free_module_basis.py +++ b/src/sage/tensor/modules/free_module_basis.py @@ -31,12 +31,44 @@ # http://www.gnu.org/licenses/ #****************************************************************************** +from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets +from sage.rings.integer_ring import ZZ +from sage.sets.family import AbstractFamily from sage.structure.unique_representation import UniqueRepresentation -from sage.structure.sage_object import SageObject -class Basis_abstract(UniqueRepresentation, SageObject): +class Basis_abstract(UniqueRepresentation, AbstractFamily): """ Abstract base class for (dual) bases of free modules. + + A basis is an :class:`~sage.sets.family.AbstractFamily`, hence like + :class:`collections.abc.Mapping` subclasses such as :class:`dict`, it is + an associative :class:`Container`, providing methods :meth:`keys`, + :meth:`values`, and :meth:`items`. Thus, ``e[i]`` returns the element + of the basis ``e`` indexed by the key ``i``. However, in contrast to + :class:`Mapping` subclasses, not the :meth:`keys` but the + :meth:`values` are considered the elements. + + EXAMPLES: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) + sage: e = M.basis('e'); e + Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring + sage: list(e) + [Element e_1 of the Rank-3 free module M over the Integer Ring, + Element e_2 of the Rank-3 free module M over the Integer Ring, + Element e_3 of the Rank-3 free module M over the Integer Ring] + sage: e.category() + Category of facade finite enumerated sets + sage: list(e.keys()) + [1, 2, 3] + sage: list(e.values()) + [Element e_1 of the Rank-3 free module M over the Integer Ring, + Element e_2 of the Rank-3 free module M over the Integer Ring, + Element e_3 of the Rank-3 free module M over the Integer Ring] + sage: list(e.items()) + [(1, Element e_1 of the Rank-3 free module M over the Integer Ring), + (2, Element e_2 of the Rank-3 free module M over the Integer Ring), + (3, Element e_3 of the Rank-3 free module M over the Integer Ring)] """ def __init__(self, fmodule, symbol, latex_symbol, indices, latex_indices): """ @@ -54,10 +86,59 @@ def __init__(self, fmodule, symbol, latex_symbol, indices, latex_indices): self._latex_symbol = latex_symbol self._indices = indices self._latex_indices = latex_indices + super().__init__(category=FiniteEnumeratedSets(), facade=fmodule) + + def keys(self): + """ + Return the keys (indices) of the family. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: list(e.keys()) + [0, 1, 2] + """ + return self._fmodule.irange() + + def values(self): + """ + Return the basis elements of ``self``. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: list(e.values()) + [Element e_0 of the Rank-3 free module M over the Integer Ring, + Element e_1 of the Rank-3 free module M over the Integer Ring, + Element e_2 of the Rank-3 free module M over the Integer Ring] + """ + return self._vec + + def _element_constructor_(self, x): + """ + Test whether ``x`` is an element of ``self``. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: e(e[1]) + Element e_1 of the Rank-3 free module M over the Integer Ring + sage: f = M.basis('f') + sage: e(f[1]) + Traceback (most recent call last): + ... + ValueError: no common basis for the comparison + """ + if x in self.values(): + return x + raise ValueError(f'{x} is not in {self}') def __iter__(self): r""" - Return the list of basis elements of ``self``. + Return an iterator for the basis elements of ``self``. EXAMPLES:: @@ -83,8 +164,7 @@ def __iter__(self): Element e_2 of the Rank-3 free module M1 over the Integer Ring, Element e_3 of the Rank-3 free module M1 over the Integer Ring] """ - for i in self._fmodule.irange(): - yield self[i] + yield from self.values() def _test_iter_len(self, **options): r""" @@ -125,6 +205,19 @@ def __len__(self): """ return self._fmodule._rank + def cardinality(self): + r""" + Return the basis length, i.e. the rank of the free module. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: e.cardinality() + 3 + """ + return ZZ(self._fmodule._rank) + def __getitem__(self, index): r""" Return the basis element corresponding to a given index. diff --git a/src/sage/tensor/modules/free_module_tensor.py b/src/sage/tensor/modules/free_module_tensor.py index bbcc1ecb2e0..2ffa00cc476 100644 --- a/src/sage/tensor/modules/free_module_tensor.py +++ b/src/sage/tensor/modules/free_module_tensor.py @@ -309,41 +309,8 @@ def __init__( # bases, with the bases as keys (initially empty) # Treatment of symmetry declarations: - self._sym = [] - if sym is not None and sym != []: - if isinstance(sym[0], (int, Integer)): - # a single symmetry is provided as a tuple -> 1-item list: - sym = [tuple(sym)] - for isym in sym: - if len(isym) > 1: - for i in isym: - if i<0 or i>self._tensor_rank-1: - raise IndexError("invalid position: " + str(i) + - " not in [0," + str(self._tensor_rank-1) + "]") - self._sym.append(tuple(isym)) - self._antisym = [] - if antisym is not None and antisym != []: - if isinstance(antisym[0], (int, Integer)): - # a single antisymmetry is provided as a tuple -> 1-item list: - antisym = [tuple(antisym)] - for isym in antisym: - if len(isym) > 1: - for i in isym: - if i<0 or i>self._tensor_rank-1: - raise IndexError("invalid position: " + str(i) + - " not in [0," + str(self._tensor_rank-1) + "]") - self._antisym.append(tuple(isym)) - - # Final consistency check: - index_list = [] - for isym in self._sym: - index_list += isym - for isym in self._antisym: - index_list += isym - if len(index_list) != len(set(index_list)): - # There is a repeated index position: - raise IndexError("incompatible lists of symmetries: the same " + - "position appears more than once") + self._sym, self._antisym = CompWithSym._canonicalize_sym_antisym( + self._tensor_rank, sym, antisym) # Initialization of derived quantities: FreeModuleTensor._init_derived(self) @@ -405,7 +372,7 @@ def _repr_(self): """ # Special cases - if self._tensor_type == (0,2) and self._sym == [(0,1)]: + if self._tensor_type == (0,2) and self._sym == ((0,1),): description = "Symmetric bilinear form " else: # Generic case @@ -563,13 +530,13 @@ def symmetries(self): elif len(self._sym) == 1: s = "symmetry: {}; ".format(self._sym[0]) else: - s = "symmetries: {}; ".format(self._sym) + s = "symmetries: {}; ".format(list(self._sym)) if len(self._antisym) == 0: a = "no antisymmetry" elif len(self._antisym) == 1: a = "antisymmetry: {}".format(self._antisym[0]) else: - a = "antisymmetries: {}".format(self._antisym) + a = "antisymmetries: {}".format(list(self._antisym)) print(s+a) #### End of simple accessors ##### @@ -1101,6 +1068,12 @@ class :class:`~sage.tensor.modules.comp.Components` fmodule = self._fmodule if basis is None: basis = fmodule._def_basis + try: + # Standard bases of tensor modules are keyed to the base module's basis, + # not to the TensorFreeSubmoduleBasis_comp instance. + basis = basis._base_module_basis + except AttributeError: + pass if basis not in self._components: # The components must be computed from # those in the basis from_basis diff --git a/src/sage/tensor/modules/tensor_free_module.py b/src/sage/tensor/modules/tensor_free_module.py index dbeb1f2be25..39174258ebb 100644 --- a/src/sage/tensor/modules/tensor_free_module.py +++ b/src/sage/tensor/modules/tensor_free_module.py @@ -59,15 +59,16 @@ #****************************************************************************** from sage.misc.cachefunc import cached_method -from sage.tensor.modules.finite_rank_free_module import FiniteRankFreeModule +from sage.tensor.modules.finite_rank_free_module import FiniteRankFreeModule_abstract from sage.tensor.modules.free_module_tensor import FreeModuleTensor from sage.tensor.modules.alternating_contr_tensor import AlternatingContrTensor from sage.tensor.modules.free_module_alt_form import FreeModuleAltForm from sage.tensor.modules.free_module_morphism import \ FiniteRankFreeModuleMorphism from sage.tensor.modules.free_module_automorphism import FreeModuleAutomorphism +from .tensor_free_submodule_basis import TensorFreeSubmoduleBasis_sym -class TensorFreeModule(FiniteRankFreeModule): +class TensorFreeModule(FiniteRankFreeModule_abstract): r""" Class for the free modules over a commutative ring `R` that are tensor products of a given free module `M` over `R` with itself and its @@ -237,39 +238,11 @@ class TensorFreeModule(FiniteRankFreeModule): sage: ta.symmetries() # the antisymmetry is of course preserved no symmetry; antisymmetry: (0, 1) - For the degree `p=1`, there is a coercion in both directions:: + For the degree `p=1`, we have the identity `\Lambda^1(M^*) = T^{(0,1)}(M) = M^*`:: - sage: L1 = M.dual_exterior_power(1) ; L1 - Dual of the Rank-3 free module M over the Integer Ring - sage: T01 = M.tensor_module(0,1) ; T01 - Free module of type-(0,1) tensors on the Rank-3 free module M over the - Integer Ring - sage: T01.has_coerce_map_from(L1) - True - sage: L1.has_coerce_map_from(T01) + sage: M.dual_exterior_power(1) is M.tensor_module(0,1) True - - The coercion map `\Lambda^1(M^*)\rightarrow T^{(0,1)}(M)` in action:: - - sage: a = M.linear_form('a') - sage: a[:] = -2, 4, 1 ; a.display(e) - a = -2 e^0 + 4 e^1 + e^2 - sage: a.parent() is L1 - True - sage: ta = T01(a) ; ta - Type-(0,1) tensor a on the Rank-3 free module M over the Integer Ring - sage: ta.display(e) - a = -2 e^0 + 4 e^1 + e^2 - - The coercion map `T^{(0,1)}(M) \rightarrow \Lambda^1(M^*)` in action:: - - sage: ta.parent() is T01 - True - sage: lta = L1(ta) ; lta - Linear form a on the Rank-3 free module M over the Integer Ring - sage: lta.display(e) - a = -2 e^0 + 4 e^1 + e^2 - sage: lta == a + sage: M.tensor_module(0,1) is M.dual() True There is a canonical identification between tensors of type `(1,1)` and @@ -387,12 +360,21 @@ def __init__(self, fmodule, tensor_type, name=None, latex_name=None): if latex_name is None and fmodule._latex_name is not None: latex_name = r'T^{' + str(self._tensor_type) + r'}\left(' + \ fmodule._latex_name + r'\right)' - FiniteRankFreeModule.__init__(self, fmodule._ring, rank, name=name, - latex_name=latex_name, - start_index=fmodule._sindex, - output_formatter=fmodule._output_formatter) + super().__init__(fmodule._ring, rank, name=name, latex_name=latex_name) fmodule._all_modules.add(self) + def construction(self): + r""" + TESTS:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: T = M.tensor_module(2, 3) + sage: T.construction() is None + True + """ + # No construction until https://trac.sagemath.org/ticket/31276 provides tensor_product methods + return None + #### Parent Methods def _element_constructor_(self, comp=[], basis=None, name=None, @@ -430,7 +412,8 @@ def _element_constructor_(self, comp=[], basis=None, name=None, self._fmodule is endo.domain(): resu = self.element_class(self._fmodule, (1,1), name=endo._name, - latex_name=endo._latex_name) + latex_name=endo._latex_name, + parent=self) for basis, mat in endo._matrices.items(): resu.add_comp(basis[0])[:] = mat else: @@ -452,7 +435,8 @@ def _element_constructor_(self, comp=[], basis=None, name=None, resu = self.element_class(self._fmodule, (p,0), name=tensor._name, latex_name=tensor._latex_name, - antisym=asym) + antisym=asym, + parent=self) for basis, comp in tensor._components.items(): resu._components[basis] = comp.copy() elif isinstance(comp, FreeModuleAltForm): @@ -469,7 +453,8 @@ def _element_constructor_(self, comp=[], basis=None, name=None, asym = range(p) resu = self.element_class(self._fmodule, (0,p), name=form._name, latex_name=form._latex_name, - antisym=asym) + antisym=asym, + parent=self) for basis, comp in form._components.items(): resu._components[basis] = comp.copy() elif isinstance(comp, FreeModuleAutomorphism): @@ -480,14 +465,27 @@ def _element_constructor_(self, comp=[], basis=None, name=None, raise TypeError("cannot coerce the {}".format(autom) + " to an element of {}".format(self)) resu = self.element_class(self._fmodule, (1,1), name=autom._name, - latex_name=autom._latex_name) + latex_name=autom._latex_name, + parent=self) for basis, comp in autom._components.items(): resu._components[basis] = comp.copy() + elif isinstance(comp, FreeModuleTensor): + tensor = comp + if self._tensor_type != tensor._tensor_type or \ + self._fmodule != tensor.base_module(): + raise TypeError("cannot coerce the {}".format(tensor) + + " to an element of {}".format(self)) + resu = self.element_class(self._fmodule, self._tensor_type, + name=name, latex_name=latex_name, + sym=sym, antisym=antisym, + parent=self) + for basis, comp in tensor._components.items(): + resu._components[basis] = comp.copy() else: # Standard construction: resu = self.element_class(self._fmodule, self._tensor_type, name=name, latex_name=latex_name, - sym=sym, antisym=antisym) + sym=sym, antisym=antisym, parent=self) if comp: resu.set_comp(basis)[:] = comp return resu @@ -538,8 +536,17 @@ def _an_element_(self): sage: M.tensor_module(2,3)._an_element_().display() 1/2 e_0⊗e_0⊗e^0⊗e^0⊗e^0 + TESTS:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: T60M = M.tensor_module(6, 0) + sage: Sym0123x45M = M.tensor_module(6, 0, sym=((0, 1, 2, 3), (4, 5))) + sage: t = Sym0123x45M._an_element_() + sage: t.parent() is Sym0123x45M + True """ - resu = self.element_class(self._fmodule, self._tensor_type) + resu = self([]) # Make sure that the base module has a default basis self._fmodule.an_element() sindex = self._fmodule._sindex @@ -562,7 +569,7 @@ def _coerce_map_from_(self, other): but not to tensor modules of other types:: - sage: M.tensor_module(0,1)._coerce_map_from_(End(M)) + sage: M.tensor_module(0,2)._coerce_map_from_(End(M)) False and not to type-`(1,1)` tensor modules defined on another free module:: @@ -588,8 +595,6 @@ def _coerce_map_from_(self, other): Coercion from alternating forms:: - sage: M.tensor_module(0,1)._coerce_map_from_(M.dual_exterior_power(1)) - True sage: M.tensor_module(0,2)._coerce_map_from_(M.dual_exterior_power(2)) True sage: M.tensor_module(0,2)._coerce_map_from_(M.dual_exterior_power(3)) @@ -597,6 +602,12 @@ def _coerce_map_from_(self, other): sage: M.tensor_module(0,2)._coerce_map_from_(N.dual_exterior_power(2)) False + Coercion from submodules:: + + sage: Sym01M = M.tensor_module(2, 0, sym=((0, 1))) + sage: M.tensor_module(2,0)._coerce_map_from_(Sym01M) + True + """ from .free_module_homset import FreeModuleHomset from .ext_pow_free_module import (ExtPowerFreeModule, @@ -622,6 +633,11 @@ def _coerce_map_from_(self, other): # Coercion of an automorphism to a type-(1,1) tensor: return self._tensor_type == (1,1) and \ self._fmodule is other.base_module() + try: + if other.is_submodule(self): + return True + except AttributeError: + pass return False #### End of parent methods @@ -636,9 +652,6 @@ def _repr_(self): sage: M.tensor_module(1,1) Free module of type-(1,1) tensors on the 2-dimensional vector space M over the Rational Field - sage: M.tensor_module(0,1) - Free module of type-(0,1) tensors on the 2-dimensional vector space - M over the Rational Field """ description = "Free module of type-({},{}) tensors on the {}".format( @@ -686,3 +699,97 @@ def tensor_type(self): """ return self._tensor_type + + @cached_method + def basis(self, symbol, latex_symbol=None, from_family=None, + indices=None, latex_indices=None, symbol_dual=None, + latex_symbol_dual=None): + r""" + Return the standard basis of ``self`` corresponding to a basis of the base module. + + INPUT: + + - ``symbol``, ``indices`` -- passed to the base module's method + :meth:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule.basis` + to select a basis of the :meth:`base_module` of ``self``, + or to create it. + + - other parameters -- passed to + :meth:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule.basis`; when + the basis does not exist yet, it will be created using these parameters. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: T = M.tensor_module(1,1) + sage: e_T = T.basis('e'); e_T + Standard basis on the + Free module of type-(1,1) tensors on the Rank-3 free module M over the Integer Ring + induced by Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring + sage: for a in e_T: a.display() + e_0⊗e^0 + e_0⊗e^1 + e_0⊗e^2 + e_1⊗e^0 + e_1⊗e^1 + e_1⊗e^2 + e_2⊗e^0 + e_2⊗e^1 + e_2⊗e^2 + + sage: Sym2M = M.tensor_module(2, 0, sym=range(2)) + sage: e_Sym2M = Sym2M.basis('e'); e_Sym2M + Standard basis on the + Free module of fully symmetric type-(2,0) tensors on the Rank-3 free module M over the Integer Ring + induced by Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring + sage: for a in e_Sym2M: a.display() + e_0⊗e_0 + e_0⊗e_1 + e_1⊗e_0 + e_0⊗e_2 + e_2⊗e_0 + e_1⊗e_1 + e_1⊗e_2 + e_2⊗e_1 + e_2⊗e_2 + + sage: M = FiniteRankFreeModule(ZZ, 2) + sage: e = M.basis('e') + sage: f = M.basis('f', from_family=(-e[1], e[0])) + sage: for b in f: b.display() + f_0 = -e_1 + f_1 = e_0 + sage: S = M.tensor_module(2, 0, sym=(0,1)) + sage: fS = S.basis('f') + sage: for b in fS: b.display() + e_1⊗e_1 + -e_0⊗e_1 - e_1⊗e_0 + e_0⊗e_0 + sage: for b in fS: b.display(f) + f_0⊗f_0 + f_0⊗f_1 + f_1⊗f_0 + f_1⊗f_1 + + """ + return TensorFreeSubmoduleBasis_sym(self, symbol=symbol, latex_symbol=latex_symbol, + indices=indices, latex_indices=latex_indices, + symbol_dual=symbol_dual, latex_symbol_dual=latex_symbol_dual) + + @cached_method + def _basis_sym(self): + r""" + Return an instance of :class:`~sage.tensor.modules.comp.Components`. + + This implementation returns an instance without symmetry. + + The subclass :class:`~sage.tensor.modules.tensor_free_submodule.TensorFreeSubmodule_sym` + overrides this method to encode the prescribed symmetry of the submodule. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: T = M.tensor_module(1,1) + sage: c = T._basis_sym(); c + 2-indices components w.r.t. (0, 1, 2) + + """ + frame = tuple(self.base_module().irange()) + tensor = self.ambient()() + return tensor._new_comp(frame) diff --git a/src/sage/tensor/modules/tensor_free_submodule.py b/src/sage/tensor/modules/tensor_free_submodule.py new file mode 100644 index 00000000000..5f6964f8546 --- /dev/null +++ b/src/sage/tensor/modules/tensor_free_submodule.py @@ -0,0 +1,523 @@ +r""" +Free submodules of tensor modules defined by monoterm symmetries + +AUTHORS: + +- Matthias Koeppe (2020-2022): initial version +""" + +# ****************************************************************************** +# Copyright (C) 2020-2022 Matthias Koeppe +# +# 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/ +# ****************************************************************************** + +import itertools + +from sage.misc.cachefunc import cached_method +from sage.misc.lazy_attribute import lazy_attribute +from sage.sets.disjoint_set import DisjointSet +from sage.typeset.unicode_characters import unicode_otimes + +from .comp import CompFullySym, CompFullyAntiSym, CompWithSym +from .tensor_free_module import TensorFreeModule +from .finite_rank_free_module import FiniteRankFreeModule_abstract + + +class TensorFreeSubmodule_sym(TensorFreeModule): + r""" + Class for free submodules of tensor products of free modules + that are defined by some monoterm symmetries. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: T60M = M.tensor_module(6, 0); T60M + Free module of type-(6,0) tensors on the Rank-3 free module M over the Integer Ring + sage: T60M._name + 'T^(6, 0)(M)' + sage: latex(T60M) + T^{(6, 0)}\left(M\right) + sage: T40Sym45M = M.tensor_module(6, 0, sym=((4, 5))); T40Sym45M + Free module of type-(6,0) tensors on the Rank-3 free module M over the Integer Ring, + with symmetry on the index positions (4, 5) + sage: T40Sym45M._name + 'T^{0,1,2,3}(M)⊗Sym^{4,5}(M)' + sage: latex(T40Sym45M) + T^{\{0,1,2,3\}}(M) \otimes \mathrm{Sym}^{\{4,5\}}(M) + sage: Sym0123x45M = M.tensor_module(6, 0, sym=((0, 1, 2, 3), (4, 5))); Sym0123x45M + Free module of type-(6,0) tensors on the Rank-3 free module M over the Integer Ring, + with symmetry on the index positions (0, 1, 2, 3), + with symmetry on the index positions (4, 5) + sage: Sym0123x45M._name + 'Sym^{0,1,2,3}(M)⊗Sym^{4,5}(M)' + sage: latex(Sym0123x45M) + \mathrm{Sym}^{\{0,1,2,3\}}(M) \otimes \mathrm{Sym}^{\{4,5\}}(M) + sage: Sym012x345M = M.tensor_module(6, 0, sym=((0, 1, 2), (3, 4, 5))); Sym012x345M + Free module of type-(6,0) tensors on the Rank-3 free module M over the Integer Ring, + with symmetry on the index positions (0, 1, 2), + with symmetry on the index positions (3, 4, 5) + sage: Sym012x345M._name + 'Sym^{0,1,2}(M)⊗Sym^{3,4,5}(M)' + sage: latex(Sym012x345M) + \mathrm{Sym}^{\{0,1,2\}}(M) \otimes \mathrm{Sym}^{\{3,4,5\}}(M) + sage: Sym012345M = M.tensor_module(6, 0, sym=((0, 1, 2, 3, 4, 5))); Sym012345M + Free module of fully symmetric type-(6,0) tensors + on the Rank-3 free module M over the Integer Ring + sage: Sym012345M._name + 'Sym^6(M)' + sage: latex(Sym012345M) + \mathrm{Sym}^6(M) + + Canonical injections from submodules are coercions:: + + sage: Sym0123x45M.has_coerce_map_from(Sym012345M) + True + sage: T60M.has_coerce_map_from(Sym0123x45M) + True + sage: t = e[0] * e[0] * e[0] * e[0] * e[0] * e[0] + sage: t.parent() + Free module of type-(6,0) tensors on the Rank-3 free module M over the Integer Ring + sage: Sym012345M(t) is t + False + + TESTS:: + + sage: T = M.tensor_module(4, 4, sym=((0, 1)), antisym=((4, 5))); T + Free module of type-(4,4) tensors on the Rank-3 free module M over the Integer Ring, + with symmetry on the index positions (0, 1), + with antisymmetry on the index positions (4, 5) + sage: T._name + 'T^{2,3}(M)⊗T^{6,7}(M*)⊗Sym^{0,1}(M)⊗ASym^{4,5}(M*)' + sage: latex(T) + T^{\{2,3\}}(M) \otimes T^{\{6,7\}}(M^*) \otimes \mathrm{Sym}^{\{0,1\}}(M) \otimes \mathrm{ASym}^{\{4,5\}}(M^*) + + """ + def __init__(self, fmodule, tensor_type, name=None, latex_name=None, + sym=None, antisym=None, *, category=None, ambient=None): + r""" + TESTS:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: Sym0123x45M = M.tensor_module(6, 0, sym=((0, 1, 2, 3), (4, 5))) + sage: TestSuite(Sym0123x45M).run() + """ + self._fmodule = fmodule + self._tensor_type = tuple(tensor_type) + if ambient is None: + ambient = fmodule.tensor_module(*tensor_type) + self._ambient_module = ambient + self._sym = sym + self._antisym = antisym + basis_sym = self._basis_sym() + rank = len(list(basis_sym.non_redundant_index_generator())) + + if name is None and fmodule._name is not None: + all_indices = tuple(range(tensor_type[0] + tensor_type[1])) + if isinstance(basis_sym, CompFullySym): + sym = [all_indices] + antisym = [] + elif isinstance(basis_sym, CompFullyAntiSym): + sym = [] + antisym = [all_indices] + elif isinstance(basis_sym, CompWithSym): + sym = basis_sym._sym + antisym = basis_sym._antisym + else: + sym = antisym = [] + nosym_0 = [i for i in range(tensor_type[0]) + if not any(i in s for s in sym) and not any(i in s for s in antisym)] + nosym_1 = [i for i in range(tensor_type[0], tensor_type[0] + tensor_type[1]) + if not any(i in s for s in sym) and not any(i in s for s in antisym)] + nosym = [s for s in [nosym_0, nosym_1] if s] + + def power_name(op, s, latex=False): + if s[0] < tensor_type[0]: + assert all(i < tensor_type[0] for i in s) + base = fmodule + full = tensor_type[0] + else: + assert all(i >= tensor_type[0] for i in s) + base = fmodule.dual() + full = tensor_type[1] + if len(s) == full: + superscript = str(full) + else: + superscript = ','.join(str(i) for i in s) + if latex: + superscript = r'\{' + superscript + r'\}' + else: + superscript = '{' + superscript + '}' + if latex: + if len(superscript) != 1: + superscript = '{' + superscript + '}' + if len(base._latex_name) > 3: + return op + '^' + superscript + r'\left(' + base._latex_name + r'\right)' + else: + return op + '^' + superscript + '(' + base._latex_name + ')' + else: + return op + '^' + superscript + '(' + base._name + ')' + + name = unicode_otimes.join(itertools.chain( + (power_name('T', s, latex=False) for s in nosym), + (power_name('Sym', s, latex=False) for s in sym), + (power_name('ASym', s, latex=False) for s in antisym))) + latex_name = r' \otimes '.join(itertools.chain( + (power_name('T', s, latex=True) for s in nosym), + (power_name(r'\mathrm{Sym}', s, latex=True) for s in sym), + (power_name(r'\mathrm{ASym}', s, latex=True) for s in antisym))) + + category = fmodule.category().TensorProducts().FiniteDimensional().Subobjects().or_subcategory(category) + # Skip TensorFreeModule.__init__ + FiniteRankFreeModule_abstract.__init__(self, fmodule._ring, rank, name=name, + latex_name=latex_name, + category=category, ambient=ambient) + + @cached_method + def _basis_sym(self): + r""" + Return an instance of :class:`~sage.tensor.modules.comp.Components`. + + In the current implementation of :class:`~sage.tensor.modules.tensor_free_submodule.TensorFreeSubmodule_sym`, + it encodes the prescribed symmetry of ``self``. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: Sym2M = M.tensor_module(2, 0, sym=range(2)); Sym2M + Free module of fully symmetric type-(2,0) tensors on the Rank-3 free module M over the Integer Ring + sage: c = Sym2M._basis_sym(); c + Fully symmetric 2-indices components w.r.t. (0, 1, 2) + + """ + frame = tuple(self.base_module().irange()) + # Need to call _element_constructor_ explicitly, or the passed arguments are dropped + tensor = self.ambient()._element_constructor_(sym=self._sym, antisym=self._antisym) + return tensor._new_comp(frame) + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: Sym2M = M.tensor_module(2, 0, sym=range(2)); Sym2M + Free module of fully symmetric type-(2,0) tensors + on the Rank-3 free module M over the Integer Ring + + """ + prefix, suffix = self._basis_sym()._repr_symmetry() + return "Free module of {}type-({},{}) tensors on the {}{}".format( + prefix.lower(), self._tensor_type[0], self._tensor_type[1], self._fmodule, suffix) + + def _is_symmetry_coarsening_of(self, coarser_comp, finer_comp): + r""" + Return whether ``coarser_comp`` has coarser symmetry than ``finer_comp``. + + INPUT: + + - ``coarser_comp``, ``finer_comp``: :class:`~sage.tensor.modules.comp.Components` + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: T60M = M.tensor_module(6, 0) + sage: Sym0123x45M = M.tensor_module(6, 0, sym=((0, 1, 2, 3), (4, 5))) + sage: ten0123x45M = Sym0123x45M.an_element(); ten0123x45M + Type-(6,0) tensor on the Rank-3 free module M over the Integer Ring + sage: ten0123x45M.parent() + Free module of type-(6,0) tensors on the Rank-3 free module M over the Integer Ring, + with symmetry on the index positions (0, 1, 2, 3), + with symmetry on the index positions (4, 5) + sage: com0123x45M = ten0123x45M._components[e]; com0123x45M + 6-indices components w.r.t. Basis (e_0,e_1,e_2) + on the Rank-3 free module M over the Integer Ring, + with symmetry on the index positions (0, 1, 2, 3), + with symmetry on the index positions (4, 5) + sage: Sym012x345M = M.tensor_module(6, 0, sym=((0, 1, 2), (3, 4, 5))) + sage: com012x345M = Sym012x345M.an_element()._components[e]; com012x345M + 6-indices components w.r.t. Basis (e_0,e_1,e_2) + on the Rank-3 free module M over the Integer Ring, + with symmetry on the index positions (0, 1, 2), + with symmetry on the index positions (3, 4, 5) + sage: Sym012345M = M.tensor_module(6, 0, sym=((0, 1, 2, 3, 4, 5))) + sage: com012345M = Sym012345M.an_element()._components[e]; com012345M + Fully symmetric 6-indices components w.r.t. Basis (e_0,e_1,e_2) + on the Rank-3 free module M over the Integer Ring + sage: Sym0123x45M._is_symmetry_coarsening_of(com0123x45M, com012x345M) + False + sage: Sym0123x45M._is_symmetry_coarsening_of(com012345M, com012x345M) + True + """ + self_tensor_type = self.tensor_type() + + def sym_antisym(comp): + if isinstance(comp, tuple): + sym, antisym = tuple + if sym is None: + sym = [] + if antisym is None: + antisym = [] + return sym, antisym + # Similar code is in Component.contract, should refactor. + try: + return comp._sym, comp._antisym + except AttributeError: + return [], [] + + def is_coarsening_of(self_sym_list, other_sym_list): + # Use the union-find data structure + S = DisjointSet(self_tensor_type[0] + self_tensor_type[1]) + for index_set in self_sym_list: + i = index_set[0] + for j in index_set[1:]: + S.union(i, j) + for index_set in other_sym_list: + i = S.find(index_set[0]) + for j in index_set[1:]: + if S.find(j) != i: + return False + return True + + finer_sym, finer_antisym = sym_antisym(finer_comp) + if not finer_sym and not finer_antisym: + return True + coarser_sym, coarser_antisym = sym_antisym(coarser_comp) + if not is_coarsening_of(coarser_sym, finer_sym): + return False + if not is_coarsening_of(coarser_antisym, finer_antisym): + return False + return True + + def _element_constructor_(self, comp=[], basis=None, name=None, + latex_name=None, sym=None, antisym=None): + r""" + TESTS:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: T60M = M.tensor_module(6, 0) + sage: Sym0123x45M = M.tensor_module(6, 0, sym=((0, 1, 2, 3), (4, 5))) + sage: Sym0123x45M(e[0]*e[0]*e[0]*e[0]*e[1]*e[2]) + Traceback (most recent call last): + ... + ValueError: this tensor does not have the symmetries of + Free module of type-(6,0) tensors on the Rank-3 free module M over the Integer Ring, + with symmetry on the index positions (0, 1, 2, 3), + with symmetry on the index positions (4, 5) + sage: t = Sym0123x45M(e[0]*e[0]*e[0]*e[0]*e[1]*e[2] + e[0]*e[0]*e[0]*e[0]*e[2]*e[1]); t.disp() + e_0⊗e_0⊗e_0⊗e_0⊗e_1⊗e_2 + e_0⊗e_0⊗e_0⊗e_0⊗e_2⊗e_1 + sage: t.parent()._name + 'Sym^{0,1,2,3}(M)⊗Sym^{4,5}(M)' + """ + if sym is not None or antisym is not None: + # Refuse to create a tensor with finer symmetries + # than those defining the subspace + if not self._is_symmetry_coarsening_of((sym, antisym), self._basis_sym()): + raise ValueError(f"cannot create a tensor with symmetries {sym=}, {antisym=} " + f"as an element of {self}") + + if sym is None: + sym = self._basis_sym()._sym + if antisym is None: + antisym = self._basis_sym()._antisym + + resu = super()._element_constructor_(comp=comp, + basis=basis, name=name, + latex_name=latex_name, + sym=sym, antisym=antisym) + if not resu._components: + # fast path for zero tensor + return resu + + try: + if self.reduce(resu): + raise ValueError(f"this tensor does not have the symmetries of {self}") + except TypeError: + # Averaging over the orbits of a tensor that does not have the required + # symmetries can lead to "TypeError: no conversion of this rational to integer" + raise ValueError(f"this tensor does not have the symmetries of {self}") + + return resu + + def is_submodule(self, other): + r""" + Return ``True`` if ``self`` is a submodule of ``other``. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: T60M = M.tensor_module(6, 0) + sage: Sym0123x45M = M.tensor_module(6, 0, sym=((0, 1, 2, 3), (4, 5))) + sage: Sym012x345M = M.tensor_module(6, 0, sym=((0, 1, 2), (3, 4, 5))) + sage: Sym012345M = M.tensor_module(6, 0, sym=((0, 1, 2, 3, 4, 5))) + sage: Sym012345M.is_submodule(Sym012345M) + True + sage: Sym012345M.is_submodule(Sym0123x45M) + True + sage: Sym0123x45M.is_submodule(Sym012345M) + False + sage: Sym012x345M.is_submodule(Sym0123x45M) + False + sage: all(S.is_submodule(T60M) for S in (Sym0123x45M, Sym012x345M, Sym012345M)) + True + + """ + if super().is_submodule(other): + return True + self_base_module = self.base_module() + self_tensor_type = self.tensor_type() + try: + other_base_module = other.base_module() + other_tensor_type = other.tensor_type() + except AttributeError: + return False + if self_base_module != other_base_module: + return False + if self_tensor_type != other_tensor_type: + return False + + other_comp = other._basis_sym() + return self._is_symmetry_coarsening_of(self._basis_sym(), other_comp) + + @lazy_attribute + def lift(self): + r""" + The lift (embedding) map from ``self`` to the ambient space. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: Sym0123x45M = M.tensor_module(6, 0, sym=((0, 1, 2, 3), (4, 5))) + sage: Sym0123x45M.lift + Generic morphism: + From: Free module of type-(6,0) tensors on the Rank-3 free module M over the Integer Ring, + with symmetry on the index positions (0, 1, 2, 3), + with symmetry on the index positions (4, 5) + To: Free module of type-(6,0) tensors on the Rank-3 free module M over the Integer Ring + """ + return self.module_morphism(function=lambda x: x, codomain=self.ambient()) + + @lazy_attribute + def reduce(self): + r""" + The reduce map. + + This map reduces elements of the ambient space modulo this + submodule. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(QQ, 3, name='M') + sage: e = M.basis('e') + sage: X = M.tensor_module(6, 0) + sage: Y = M.tensor_module(6, 0, sym=((0, 1, 2, 3), (4, 5))) + sage: Y.reduce + Generic endomorphism of + Free module of type-(6,0) tensors on the 3-dimensional vector space M over the Rational Field + sage: t = e[0]*e[0]*e[0]*e[0]*e[1]*e[2]; t.disp() + e_0⊗e_0⊗e_0⊗e_0⊗e_1⊗e_2 = e_0⊗e_0⊗e_0⊗e_0⊗e_1⊗e_2 + sage: r = Y.reduce(t); r + Type-(6,0) tensor on the 3-dimensional vector space M over the Rational Field + sage: r.disp() + 1/2 e_0⊗e_0⊗e_0⊗e_0⊗e_1⊗e_2 - 1/2 e_0⊗e_0⊗e_0⊗e_0⊗e_2⊗e_1 + sage: r.parent()._name + 'T^(6, 0)(M)' + + If the base ring is not a field, this may fail:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: X = M.tensor_module(6, 0) + sage: Y = M.tensor_module(6, 0, sym=((0, 1, 2, 3), (4, 5))) + sage: Y.reduce + Generic endomorphism of + Free module of type-(6,0) tensors on the Rank-3 free module M over the Integer Ring + sage: t = e[0]*e[0]*e[0]*e[0]*e[1]*e[2]; t.disp() + e_0⊗e_0⊗e_0⊗e_0⊗e_1⊗e_2 = e_0⊗e_0⊗e_0⊗e_0⊗e_1⊗e_2 + sage: Y.reduce(t) + Traceback (most recent call last): + ... + TypeError: no conversion of this rational to integer + + TESTS:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: X = M.tensor_module(6, 0) + sage: Y = M.tensor_module(6, 0, sym=((0, 1, 2, 3), (4, 5))) + sage: all(Y.reduce(u.lift()) == 0 for u in Y.basis('e')) + True + """ + sym = self._basis_sym()._sym + antisym = self._basis_sym()._antisym + + def _reduce_element(x): + if not x._components: + # zero tensor - methods symmetrize, antisymmetrize are broken + return x + # TODO: Implement a fast symmetry check, either as part of the symmetrize/antisymmetrize methods, + # or as a separate method + symmetrized = x + for s in sym: + symmetrized = symmetrized.symmetrize(*s) + for s in antisym: + symmetrized = symmetrized.antisymmetrize(*s) + return x - symmetrized + + return self.ambient().module_morphism(function=_reduce_element, codomain=self.ambient()) + + @lazy_attribute + def retract(self): + r""" + The retract map from the ambient space. + + This is a partial map, which gives an error for elements not in the subspace. + + Calling this map on elements of the ambient space is the same as calling the + element constructor of ``self``. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: X = M.tensor_module(6, 0) + sage: Y = M.tensor_module(6, 0, sym=((0, 1, 2, 3), (4, 5))) + sage: e_Y = Y.basis('e') + sage: Y.retract + Generic morphism: + From: Free module of type-(6,0) tensors on the Rank-3 free module M over the Integer Ring + To: Free module of type-(6,0) tensors on the Rank-3 free module M over the Integer Ring, + with symmetry on the index positions (0, 1, 2, 3), + with symmetry on the index positions (4, 5) + + sage: t = e[0]*e[0]*e[0]*e[0]*e[1]*e[2]; t.disp() + e_0⊗e_0⊗e_0⊗e_0⊗e_1⊗e_2 = e_0⊗e_0⊗e_0⊗e_0⊗e_1⊗e_2 + sage: Y.retract(t) + Traceback (most recent call last): + ... + ValueError: this tensor does not have the symmetries of + Free module of type-(6,0) tensors on the Rank-3 free module M over the Integer Ring, + with symmetry on the index positions (0, 1, 2, 3), + with symmetry on the index positions (4, 5) + sage: t = e[0]*e[0]*e[0]*e[0]*e[1]*e[2] + e[0]*e[0]*e[0]*e[0]*e[2]*e[1] + sage: y = Y.retract(t); y + Type-(6,0) tensor on the Rank-3 free module M over the Integer Ring + sage: y.disp() + e_0⊗e_0⊗e_0⊗e_0⊗e_1⊗e_2 + e_0⊗e_0⊗e_0⊗e_0⊗e_2⊗e_1 + sage: y.parent()._name + 'Sym^{0,1,2,3}(M)⊗Sym^{4,5}(M)' + + TESTS:: + + sage: all(Y.retract(u.lift()) == u for u in e_Y) + True + """ + return self.ambient().module_morphism(function=lambda x: self(x), codomain=self) diff --git a/src/sage/tensor/modules/tensor_free_submodule_basis.py b/src/sage/tensor/modules/tensor_free_submodule_basis.py new file mode 100644 index 00000000000..6c88b05af23 --- /dev/null +++ b/src/sage/tensor/modules/tensor_free_submodule_basis.py @@ -0,0 +1,141 @@ +r""" +Standard bases of free submodules of tensor modules defined by some monoterm symmetries + +AUTHORS: + +- Matthias Koeppe (2020-2022): initial version +""" + +# ****************************************************************************** +# Copyright (C) 2020-2022 Matthias Koeppe +# +# 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.tensor.modules.free_module_basis import Basis_abstract + + +class TensorFreeSubmoduleBasis_sym(Basis_abstract): + r""" + Standard basis of a free submodule of a tensor module with prescribed monoterm symmetries. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: T11 = M.tensor_module(1,1) + sage: e11 = T11.basis('e') + sage: for a in e11: a.display() + e_0⊗e^0 + e_0⊗e^1 + e_0⊗e^2 + e_1⊗e^0 + e_1⊗e^1 + e_1⊗e^2 + e_2⊗e^0 + e_2⊗e^1 + e_2⊗e^2 + + """ + + def __init__(self, tensor_module, symbol, latex_symbol=None, indices=None, + latex_indices=None, symbol_dual=None, latex_symbol_dual=None): + r""" + TESTS:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: T11 = M.tensor_module(1,1) + sage: e_T11 = T11.basis('e') + sage: TestSuite(e_T11).run() + """ + base_module = tensor_module.base_module() + base_module_basis = base_module.basis(symbol, latex_symbol, indices, + latex_indices, symbol_dual, latex_symbol_dual) + super().__init__(tensor_module, symbol, latex_symbol, indices, latex_indices) + self._base_module_basis = base_module_basis + self._comp = tensor_module._basis_sym() + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: T11 = M.tensor_module(1,1) + sage: e_T11 = T11.basis('e') + sage: e_T11 + Standard basis on the + Free module of type-(1,1) tensors on the Rank-3 free module M over the Integer Ring + induced by Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring + """ + return f"Standard basis on the {self._fmodule} induced by {self._base_module_basis}" + + def keys(self): + """ + Return an iterator for the keys (indices) of the family. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: T11 = M.tensor_module(1,1) + sage: e11 = T11.basis('e') + sage: list(e11.keys()) + [(0, 0), (0, 1), (0, 2), + (1, 0), (1, 1), (1, 2), + (2, 0), (2, 1), (2, 2)] + """ + yield from self._comp.non_redundant_index_generator() + + def values(self): + """ + Return an iterator for the elements of the family. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: T11 = M.tensor_module(1,1) + sage: e11 = T11.basis('e') + sage: [b.disp() for b in e11.values()] + [e_0⊗e^0, e_0⊗e^1, e_0⊗e^2, + e_1⊗e^0, e_1⊗e^1, e_1⊗e^2, + e_2⊗e^0, e_2⊗e^1, e_2⊗e^2] + """ + for ind in self.keys(): + yield self[ind] + + def __getitem__(self, index): + r""" + Return the basis element corresponding to a given index. + + INPUT: + + - ``index`` -- the index of the basis element + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: T11 = M.tensor_module(1,1) + sage: e11 = T11.basis('e') + sage: e11[1, 2].display() + e_1⊗e^2 + + sage: from sage.tensor.modules.tensor_free_submodule import TensorFreeSubmodule_sym + sage: Sym2M = TensorFreeSubmodule_sym(M, (2, 0), sym=range(2)); Sym2M + Free module of fully symmetric type-(2,0) tensors on the Rank-3 free module M over the Integer Ring + sage: eSym2M = Sym2M.basis('e') + sage: eSym2M[1, 1].display() + e_1⊗e_1 + sage: eSym2M[1, 2].display() + e_1⊗e_2 + e_2⊗e_1 + + """ + tensor_module = self._fmodule + base_module_basis = self._base_module_basis + element = tensor_module([]) + element.set_comp(base_module_basis)[index] = 1 + return element diff --git a/src/sage/tests/book_schilling_zabrocki_kschur_primer.py b/src/sage/tests/book_schilling_zabrocki_kschur_primer.py index fe79941c39e..b0d56792851 100644 --- a/src/sage/tests/book_schilling_zabrocki_kschur_primer.py +++ b/src/sage/tests/book_schilling_zabrocki_kschur_primer.py @@ -542,7 +542,7 @@ sage: la = Partition([3,2,1,1]) sage: la.k_atom(4) - [[[1, 1, 1], [2, 2], [3], [4]], [[1, 1, 1, 4], [2, 2], [3]]] + [[[1, 1, 1, 4], [2, 2], [3]], [[1, 1, 1], [2, 2], [3], [4]]] Sage example in ./kschurnotes/notes-mike-anne.tex, line 3639:: diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/README b/src/sage/tests/books/computational-mathematics-with-sagemath/README index c1c375296d5..99f566480c5 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/README +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/README @@ -1,6 +1,6 @@ This directory contains all the example code from the book "Computational Mathematics with SageMath" by Paul Zimmermann et al, -freely available from http://sagebook.gforge.inria.fr/english.html +freely available from https://www.sagemath.org/sagebook/english.html Each file corresponds to a chapter of the book. The directory "sol" contains the code for Annex A: answers to exercises diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/polynomes_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/polynomes_doctest.py index 452a116401d..951bfb2b5c6 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/polynomes_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/polynomes_doctest.py @@ -267,7 +267,7 @@ sage: A = Integers(101); R. = A[] sage: f6 = sum( (i+1)^2 * x^i for i in (0..5) ); f6 36*x^5 + 25*x^4 + 16*x^3 + 9*x^2 + 4*x + 1 - sage: num, den = f6.rational_reconstruct(x^6, 1, 3); num/den + sage: num, den = f6.rational_reconstruction(x^6, 1, 3); num/den (100*x + 100)/(x^3 + 98*x^2 + 3*x + 100) Sage example in ./polynomes.tex, line 1611:: @@ -283,7 +283,7 @@ Sage example in ./polynomes.tex, line 1677:: - sage: num, den = ZpZx(s).rational_reconstruct(ZpZx(x)^10,4,5) + sage: num, den = ZpZx(s).rational_reconstruction(ZpZx(x)^10,4,5) sage: num/den (1073741779*x^3 + 105*x)/(x^4 + 1073741744*x^2 + 105) @@ -304,7 +304,7 @@ sage: def mypade(pol, n, k): ....: x = ZpZx.gen(); - ....: n,d = ZpZx(pol).rational_reconstruct(x^n, k-1, n-k) + ....: n,d = ZpZx(pol).rational_reconstruction(x^n, k-1, n-k) ....: return Qx(list(map(lift_sym, n)))/Qx(list(map(lift_sym, d))) Sage example in ./polynomes.tex, line 1813:: diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/recequadiff_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/recequadiff_doctest.py index 1062f4f7e8c..f53f813d793 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/recequadiff_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/recequadiff_doctest.py @@ -382,7 +382,7 @@ sage: from sympy import rsolve_hyper sage: from sympy.abc import n sage: rsolve_hyper([-2,1],2**(n+2),n) - 2**n*C0 + 2**(n + 2)*(C0 + n/2) + 2**n*C0 + 2**(n + 1)*n """ diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/sol/polynomes_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/sol/polynomes_doctest.py index 15f60192153..2908f38254d 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/sol/polynomes_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/sol/polynomes_doctest.py @@ -91,7 +91,7 @@ Sage example in ./sol/polynomes.tex, line 428:: - sage: s.rational_reconstruct(mul(x-i for i in range(4)), 1, 2) + sage: s.rational_reconstruction(mul(x-i for i in range(4)), 1, 2) (15*x + 2, x^2 + 11*x + 15) Sage example in ./sol/polynomes.tex, line 454:: diff --git a/src/sage/tests/modular_group_cohomology.py b/src/sage/tests/modular_group_cohomology.py index 868d35e14be..be00146dc6f 100644 --- a/src/sage/tests/modular_group_cohomology.py +++ b/src/sage/tests/modular_group_cohomology.py @@ -52,7 +52,7 @@ sage: H.essential_ideal() # optional - p_group_cohomology a_1_0*a_1_1, a_1_1*a_3_1 - sage: ascii_art(H.bar_code('LowerCentralSeries')[2])# optional - p_group_cohomology + sage: ascii_art(H.bar_code('LowerCentralSeries')[2]) # known bug (possibly, the output might be correct) # optional - p_group_cohomology * *-* *-* diff --git a/src/sage/topology/simplicial_complex.py b/src/sage/topology/simplicial_complex.py index 6c000a8625b..736df96ef20 100644 --- a/src/sage/topology/simplicial_complex.py +++ b/src/sage/topology/simplicial_complex.py @@ -2785,6 +2785,35 @@ def remove_faces(self, faces, check=False): for f in faces: self.remove_face(f, check=check) + def is_subcomplex(self, other): + """ + Return ``True`` if this is a subcomplex of ``other``. + + :param other: another simplicial complex + + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: S1.is_subcomplex(S1) + True + sage: Empty = SimplicialComplex() + sage: Empty.is_subcomplex(S1) + True + sage: S1.is_subcomplex(Empty) + False + + sage: sorted(S1.facets()) + [(0, 1), (0, 2), (1, 2)] + sage: T = S1.product(S1) + sage: sorted(T.facets())[0] # typical facet in T + ('L0R0', 'L0R1', 'L1R1') + sage: S1.is_subcomplex(T) + False + sage: T._contractible_subcomplex().is_subcomplex(T) + True + """ + return all(f in other for f in self.facets()) + def connected_sum(self, other, is_mutable=True): """ The connected sum of this simplicial complex with another one. diff --git a/src/sage/topology/simplicial_complex_catalog.py b/src/sage/topology/simplicial_complex_catalog.py index 01ade2e3667..07a0de43b4a 100644 --- a/src/sage/topology/simplicial_complex_catalog.py +++ b/src/sage/topology/simplicial_complex_catalog.py @@ -27,13 +27,14 @@ - :meth:`~sage.topology.examples.ComplexProjectivePlane` - :meth:`~sage.topology.examples.DunceHat` - :meth:`~sage.topology.examples.FareyMap` +- :meth:`~sage.topology.examples.GenusSix` - :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.QuaternionicProjectivePlane` - :meth:`~sage.topology.examples.RandomComplex` - :meth:`~sage.topology.examples.RandomTwoSphere` - :meth:`~sage.topology.examples.RealProjectivePlane` @@ -66,9 +67,9 @@ from sage.topology.simplicial_complex_examples import (Sphere, Simplex, Torus, ProjectivePlane, - RealProjectivePlane, KleinBottle, FareyMap, SurfaceOfGenus, + RealProjectivePlane, KleinBottle, FareyMap, GenusSix, SurfaceOfGenus, MooreSpace, - ComplexProjectivePlane, PseudoQuaternionicProjectivePlane, + ComplexProjectivePlane, QuaternionicProjectivePlane, PoincareHomologyThreeSphere, RealProjectiveSpace, K3Surface, BarnetteSphere, BrucknerGrunbaumSphere, NotIConnectedGraphs, MatchingComplex, ChessboardComplex, RandomComplex, SumComplex, diff --git a/src/sage/topology/simplicial_complex_examples.py b/src/sage/topology/simplicial_complex_examples.py index e9068b7713d..a21391beab3 100644 --- a/src/sage/topology/simplicial_complex_examples.py +++ b/src/sage/topology/simplicial_complex_examples.py @@ -27,13 +27,14 @@ - :func:`ComplexProjectivePlane` - :func:`DunceHat` - :func:`FareyMap` +- :func:`GenusSix` - :func:`K3Surface` - :func:`KleinBottle` - :func:`MatchingComplex` - :func:`MooreSpace` - :func:`NotIConnectedGraphs` - :func:`PoincareHomologyThreeSphere` -- :func:`PseudoQuaternionicProjectivePlane` +- :func:`QuaternionicProjectivePlane` - :func:`RandomComplex` - :func:`RandomTwoSphere` - :func:`RealProjectivePlane` @@ -62,6 +63,14 @@ {0: 0, 1: C4, 2: 0} sage: simplicial_complexes.MatchingComplex(6).homology() {0: 0, 1: Z^16, 2: 0} + +TESTS:: + + sage: from sage.topology.simplicial_complex_examples import PseudoQuaternionicProjectivePlane + sage: H = PseudoQuaternionicProjectivePlane() + doctest:warning...: + DeprecationWarning: PseudoQuaternionicProjectivePlane is deprecated. Please use sage.topology.simplicial_complex_examples.QuaternionicProjectivePlane instead. + See https://trac.sagemath.org/34568 for details. """ from .simplicial_complex import SimplicialComplex @@ -74,6 +83,7 @@ from sage.misc.functional import is_even from sage.combinat.subset import Subsets import sage.misc.prandom as random +from sage.misc.superseded import deprecated_function_alias # Miscellaneous utility functions. @@ -242,9 +252,7 @@ def __classcall__(self, maximal_faces=None, name=None, **kwds): 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) + return super().__classcall__(self, maximal_faces, name=name, **kwds) def __init__(self, maximal_faces=None, name=None, **kwds): """ @@ -545,15 +553,14 @@ def ComplexProjectivePlane(): name='Minimal triangulation of the complex projective plane') -def PseudoQuaternionicProjectivePlane(): +def QuaternionicProjectivePlane(): 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 was proven to be a triangulation of the projective plane + `HP^2` over the ring of quaternions by Gorodkov in 2016 [Gor2016]_. This simplicial complex has the same homology as `HP^2`. Its automorphism group is isomorphic to the alternating group `A_5` @@ -561,12 +568,11 @@ def PseudoQuaternionicProjectivePlane(): 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. + returns the only one which has a transitive group of automorphisms. EXAMPLES:: - sage: HP2 = simplicial_complexes.PseudoQuaternionicProjectivePlane() ; HP2 + sage: HP2 = simplicial_complexes.QuaternionicProjectivePlane() ; HP2 Simplicial complex with 15 vertices and 490 facets sage: HP2.f_vector() [1, 15, 105, 455, 1365, 3003, 4515, 4230, 2205, 490] @@ -599,6 +605,10 @@ def PseudoQuaternionicProjectivePlane(): for tuple in start_list for g in PermutationGroup([P, S])]) + +PseudoQuaternionicProjectivePlane = deprecated_function_alias(34568, QuaternionicProjectivePlane) + + def PoincareHomologyThreeSphere(): """ A triangulation of the Poincaré homology 3-sphere. @@ -1615,3 +1625,43 @@ def normalise(pair): triangle = libgap.Set(triangle) triangles = libgap.Orbit(group, triangle, libgap.OnSets).sage() return SimplicialComplex(triangles) + + +def GenusSix(): + """ + Return a triangulated surface of genus 6. + + This is triangulated with 12 vertices, 66 edges and 44 faces. Each + vertex is neighbour to all other vertices. + + It appears as number `58` in the classification of Altshuler, + Bokowski and Schuchert in [ABS96]_, where it is the unique surface + with the largest symmetry group, of order 12. This article refers + for this surface to Ringel. + + EXAMPLES:: + + sage: S = simplicial_complexes.GenusSix() + sage: S.automorphism_group().cardinality() + 12 + sage: S.betti() + {0: 1, 1: 12, 2: 1} + sage: S.f_vector() + [1, 12, 66, 44] + + REFERENCES: + + .. [ABS96] Amos Altshule, Jürgen Bokowski and Peter Schuchert, + *Neighborly 2-Manifolds with 12 Vertices*, + Journal of Combinatorial Theory, Series A, 75, 148-162 (1996), + :doi:`10.1006/jcta.1996.0069` + """ + L = ["014", "018", "023", "027", "036", "049", + "056", "05b", "07a", "08a", "09b", + "125", "126", "137", "139", "147", "15a", + "16b", "18b", "19a", "23b", "248", + "24a", "258", "269", "279", "2ab", "345", + "34b", "35a", "367", "389", "38a", + "459", "46a", "46b", "478", "568", "579", + "57b", "67a", "689", "78b", "9ab"] + return SimplicialComplex([list(w) for w in L]) diff --git a/src/sage/typeset/character_art.py b/src/sage/typeset/character_art.py index 7ec212f0631..9681f0b8c53 100644 --- a/src/sage/typeset/character_art.py +++ b/src/sage/typeset/character_art.py @@ -24,8 +24,6 @@ # # https://www.gnu.org/licenses/ # ****************************************************************************** - -import os import sys from sage.structure.sage_object import SageObject diff --git a/src/sage/version.py b/src/sage/version.py index a6363c026c8..94cf8afef07 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.7.beta8' -date = '2022-08-07' -banner = 'SageMath version 9.7.beta8, Release Date: 2022-08-07' +version = '9.8.beta0' +date = '2022-09-25' +banner = 'SageMath version 9.8.beta0, Release Date: 2022-09-25' diff --git a/src/sage_docbuild/builders.py b/src/sage_docbuild/builders.py index c5be38600ad..fcb49cbb167 100644 --- a/src/sage_docbuild/builders.py +++ b/src/sage_docbuild/builders.py @@ -182,7 +182,7 @@ def f(self, *args, **kwds): return f -class DocBuilder(object): +class DocBuilder(): def __init__(self, name, lang='en'): """ INPUT: @@ -345,7 +345,7 @@ def build_other_doc(args): getattr(get_builder(document), name)(*args, **kwds) -class AllBuilder(object): +class AllBuilder(): """ A class used to build all of the documentation. """ @@ -863,10 +863,10 @@ def get_sphinx_environment(self): """ Return the Sphinx environment for this project. """ - class FakeConfig(object): + class FakeConfig(): values = tuple() - class FakeApp(object): + class FakeApp(): def __init__(self, dir): self.srcdir = dir self.config = FakeConfig() diff --git a/src/sage_docbuild/conf.py b/src/sage_docbuild/conf.py index 1e321ae14aa..245fce04aeb 100644 --- a/src/sage_docbuild/conf.py +++ b/src/sage_docbuild/conf.py @@ -260,15 +260,15 @@ def set_intersphinx_mappings(app, config): # The name of the Pygments (syntax highlighting) style to use. This # overrides a HTML theme's corresponding setting. - pygments_style = "tango" + pygments_style = "sphinx" pygments_dark_style = "monokai" # Add siderbar/home.html to the default sidebar. html_sidebars = { "**": [ + "sidebar/scroll-start.html", "sidebar/brand.html", "sidebar/search.html", - "sidebar/scroll-start.html", "sidebar/home.html", "sidebar/navigation.html", "sidebar/ethical-ads.html", @@ -501,6 +501,7 @@ def set_intersphinx_mappings(app, config): \DeclareUnicodeCharacter{2323}{\ensuremath{\smile}} % cup product \DeclareUnicodeCharacter{00B1}{\ensuremath{\pm}} \DeclareUnicodeCharacter{2A02}{\ensuremath{\bigotimes}} + \DeclareUnicodeCharacter{2295}{\ensuremath{\oplus}} \DeclareUnicodeCharacter{2297}{\ensuremath{\otimes}} \DeclareUnicodeCharacter{2A01}{\ensuremath{\oplus}} \DeclareUnicodeCharacter{00BD}{\ensuremath{\nicefrac{1}{2}}} diff --git a/src/sage_docbuild/ext/sage_autodoc.py b/src/sage_docbuild/ext/sage_autodoc.py index 39a198e88a6..5b97b272718 100644 --- a/src/sage_docbuild/ext/sage_autodoc.py +++ b/src/sage_docbuild/ext/sage_autodoc.py @@ -130,7 +130,7 @@ def formatargspec(function, args, varargs=None, varkw=None, defaults=None, annotations=annotations) -class AutodocReporter(object): +class AutodocReporter(): """ A reporter replacement that assigns the correct source name and line number to a system message, as recorded in a ViewList. @@ -246,9 +246,7 @@ def process(app, what_, name, obj, options, lines): return process - - -class Documenter(object): +class Documenter(): """ A Documenter knows how to autodocument a single object type. When registered with the AutoDirective, it will be used to document objects @@ -988,7 +986,7 @@ def resolve_name(self, modname, parents, path, base): return modname, parents + [base] -class DocstringSignatureMixin(object): +class DocstringSignatureMixin(): """ Mixin for FunctionDocumenter and MethodDocumenter to provide the feature of reading the signature from the docstring. @@ -1175,7 +1173,7 @@ def import_object(self): # __name__ of the nested class. For example, in # # class A(metaclass=NestedMetaclass): - # class B(object): + # class B(): # pass # # the class B get its name changed to 'A.B'. Such dots '.' in names diff --git a/src/tox.ini b/src/tox.ini index a26fee98803..b54938f74e3 100644 --- a/src/tox.ini +++ b/src/tox.ini @@ -90,6 +90,8 @@ deps = pycodestyle commands = pycodestyle {posargs:{toxinidir}/sage/} [testenv:pycodestyle-minimal] +## Note that the the pycodestyle linter provided by vscode checks against the +## same minimal conventions as defined below, see the file SAGE_ROOT/.vscode/settings.json. description = check against Sage's minimal style conventions # Check for the following issues: @@ -207,6 +209,7 @@ rst-roles = trac, wikipedia rst-directives = + attribute, automethod, autofunction, toctree, @@ -219,7 +222,9 @@ extend-ignore = # Ignore RST306 Unknown target name -- because of references to the global bibliography RST306 exclude = + # Avoid errors by exclude files with generated docstring portions such as {PLOT_OPTIONS_TABLE} sage/combinat/designs/database.py + sage/graphs/graph_plot.py sage/misc/sagedoc.py [pytest] diff --git a/tox.ini b/tox.ini index 918ce384f31..0106e693891 100644 --- a/tox.ini +++ b/tox.ini @@ -7,6 +7,12 @@ # # This will do a complete build of the Sage distribution in a Docker container, which will take a while. # +# To do an incremental build based on the latest build of a beta version on GitHub Actions: +# +# $ tox -e docker-fedora-31-standard-incremental +# +# This will download a large (multi-gigabyte) Docker image from GitHub Packages (ghcr.io). +# # Specific 'make' targets can be given as additional arguments after "--". # For example, to only run the configuration phase: # @@ -49,7 +55,7 @@ envlist = ### - standard # Install all known system packages equivalent to standard packages that have spkg-configure.m4 ### - maximal # Install all known system packages equivalent to standard/optional packages that have spkg-configure.m4 - docker-ubuntu-trusty-minimal, + docker-ubuntu-trusty-toolchain-gcc_9-minimal, docker-debian-bullseye-standard, docker-fedora-34-standard, docker-archlinux-latest-maximal, @@ -135,6 +141,7 @@ passenv = docker: EXTRA_DOCKER_TAGS # Use DOCKER_BUILDKIT=1 for new version - for which unfortunately we cannot save failed builds as an image docker: DOCKER_BUILDKIT + docker: BUILDKIT_INLINE_CACHE # Set for example to "with-system-packages configured with-targets-pre with-targets" # to tag intermediate images. docker: DOCKER_TARGETS @@ -142,6 +149,9 @@ passenv = docker: DOCKER_PUSH_REPOSITORY # If set, we symlink this file into {envdir}/.docker/; this can be used for providing credentials for pushing docker: DOCKER_CONFIG_FILE + docker-incremental: FROM_DOCKER_REPOSITORY + docker-incremental: FROM_DOCKER_TARGET + docker-incremental: FROM_DOCKER_TAG local: MAKE local: PREFIX local: SAGE_NUM_THREADS @@ -202,7 +212,6 @@ setenv = ubuntu-bionic: IGNORE_MISSING_SYSTEM_PACKAGES=yes ubuntu-focal: BASE_TAG=focal ubuntu-jammy: BASE_TAG=jammy - ubuntu-jammy: IGNORE_MISSING_SYSTEM_PACKAGES=yes ubuntu-kinetic: BASE_TAG=kinetic ubuntu-kinetic: IGNORE_MISSING_SYSTEM_PACKAGES=yes # @@ -219,7 +228,6 @@ setenv = debian-bullseye: BASE_TAG=bullseye debian-bullseye: IGNORE_MISSING_SYSTEM_PACKAGES=yes debian-bookworm: BASE_TAG=bookworm - debian-bookworm: IGNORE_MISSING_SYSTEM_PACKAGES=yes debian-sid: BASE_TAG=sid # # https://hub.docker.com/u/linuxmintd @@ -259,9 +267,9 @@ setenv = fedora-34: BASE_TAG=34 fedora-34: IGNORE_MISSING_SYSTEM_PACKAGES=no fedora-35: BASE_TAG=35 - fedora-35: IGNORE_MISSING_SYSTEM_PACKAGES=yes + fedora-35: IGNORE_MISSING_SYSTEM_PACKAGES=no fedora-36: BASE_TAG=36 - fedora-36: IGNORE_MISSING_SYSTEM_PACKAGES=yes + fedora-36: IGNORE_MISSING_SYSTEM_PACKAGES=no fedora-37: BASE_TAG=37 fedora-37: IGNORE_MISSING_SYSTEM_PACKAGES=yes # @@ -442,7 +450,9 @@ setenv = # # Resulting full image:tag name # - docker: FULL_BASE_IMAGE_AND_TAG={env:ARCH_IMAGE_PREFIX:}{env:BASE_IMAGE}{env:ARCH_IMAGE_SUFFIX:}:{env:ARCH_TAG_PREFIX:}{env:BASE_TAG}{env:ARCH_TAG_SUFFIX:} + docker: FULL_BASE_IMAGE_AND_TAG={env:ARCH_IMAGE_PREFIX:}{env:BASE_IMAGE}{env:ARCH_IMAGE_SUFFIX:}:{env:ARCH_TAG_PREFIX:}{env:BASE_TAG}{env:ARCH_TAG_SUFFIX:} + docker-incremental: FULL_BASE_IMAGE_AND_TAG={env:FROM_DOCKER_REPOSITORY:ghcr.io/sagemath/sage/}sage-$(echo {envname} | sed 's/-incremental//')-{env:FROM_DOCKER_TARGET:with-targets}:{env:FROM_DOCKER_TAG:dev} + docker-incremental: SKIP_SYSTEM_PKG_INSTALL=yes # docker-nobootstrap: BOOTSTRAP=./bootstrap -D ### @@ -513,6 +523,8 @@ setenv = # - toolchain # gcc_spkg: CONFIG_CONFIGURE_ARGS_2=--without-system-gcc + gcc_8: CONFIG_CONFIGURE_ARGS_2=--with-system-gcc=force CC=gcc-8 CXX=g++-8 FC=gfortran-8 + gcc_8: EXTRA_SAGE_PACKAGES_2=_gcc8 gcc_9: CONFIG_CONFIGURE_ARGS_2=--with-system-gcc=force CC=gcc-9 CXX=g++-9 FC=gfortran-9 gcc_9: EXTRA_SAGE_PACKAGES_2=_gcc9 gcc_10: CONFIG_CONFIGURE_ARGS_2=--with-system-gcc=force CC=gcc-10 CXX=g++-10 FC=gfortran-10 @@ -620,9 +632,10 @@ commands = docker-{arm64,armhf}: docker run --rm --privileged multiarch/qemu-user-static:register --reset docker: bash -c 'if [ x"{env:DOCKER_CONFIG_FILE:}" != x ]; then mkdir -p {envdir}/.docker && ln -sf $(realpath "{env:DOCKER_CONFIG_FILE:}") {envdir}/.docker/; fi' docker: bash -c 'for docker_target in {env:DOCKER_TARGETS:with-targets}; do \ - docker: BUILD_IMAGE={env:DOCKER_PUSH_REPOSITORY:}sage-{envname}-$docker_target; \ - docker: BUILD_TAG=$BUILD_IMAGE:$(git describe --dirty --always); \ - docker: TAG_ARGS=$(echo --tag $BUILD_TAG; for tag in {env:EXTRA_DOCKER_TAGS:}; do echo --tag $BUILD_IMAGE:$tag; done); \ + docker: BUILD_IMAGE_STEM=sage-$(echo {envname} | sed s/-incremental//); \ + docker: BUILD_IMAGE=$DOCKER_PUSH_REPOSITORY$BUILD_IMAGE_STEM-$docker_target; \ + docker: BUILD_TAG=$(git describe --dirty --always); \ + docker: TAG_ARGS=$(for tag in $BUILD_TAG {env:EXTRA_DOCKER_TAGS:}; do echo --tag $BUILD_IMAGE:$tag; done); \ docker: DOCKER_BUILDKIT={env:DOCKER_BUILDKIT:0} \ docker: docker build . -f {envdir}/Dockerfile \ docker: --target $docker_target \ @@ -636,11 +649,11 @@ commands = docker: --build-arg TARGETS_OPTIONAL="{env:TARGETS_OPTIONAL:ptest}" \ docker: {env:EXTRA_DOCKER_BUILD_ARGS:}; status=$?; \ docker: if [ $status != 0 ]; then \ - docker: BUILD_TAG="$BUILD_TAG-failed"; docker commit $(docker ps -l -q) $BUILD_TAG; PUSH_TAGS=$BUILD_TAG; \ + docker: BUILD_TAG="$BUILD_TAG-failed"; docker commit $(docker ps -l -q) $BUILD_IMAGE:$BUILD_TAG; PUSH_TAGS=$BUILD_IMAGE:$BUILD_TAG; \ docker: else \ - docker: PUSH_TAGS=$(echo $BUILD_TAG; for tag in {env:EXTRA_DOCKER_TAGS:}; do echo "$BUILD_IMAGE:$tag"; done); \ + docker: PUSH_TAGS=$(echo $BUILD_IMAGE:$BUILD_TAG; for tag in {env:EXTRA_DOCKER_TAGS:}; do echo "$BUILD_IMAGE:$tag"; done); \ docker: fi; \ - docker: echo $BUILD_TAG >> {envdir}/Dockertags; \ + docker: echo $BUILD_IMAGE:$BUILD_TAG >> {envdir}/Dockertags; \ docker: if [ x"{env:DOCKER_PUSH_REPOSITORY:}" != x ]; then \ docker: echo Pushing $PUSH_TAGS; \ docker: for tag in $PUSH_TAGS; do \