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/.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/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/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/.vscode/settings.json b/.vscode/settings.json index 10822524b25..c2193d76a35 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -33,5 +33,6 @@ "Conda", "sagemath", "Cython" - ] + ], + "editor.formatOnType": true } diff --git a/.zenodo.json b/.zenodo.json index d7b2439a788..999a424d8d2 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.rc1", - "version": "9.7.rc1", + "title": "sagemath/sage: 9.8.beta1", + "version": "9.8.beta1", "upload_type": "software", - "publication_date": "2022-09-07", + "publication_date": "2022-09-29", "creators": [ { "affiliation": "SageMath.org", @@ -15,7 +15,7 @@ "related_identifiers": [ { "scheme": "url", - "identifier": "https://github.com/sagemath/sage/tree/9.7.rc1", + "identifier": "https://github.com/sagemath/sage/tree/9.8.beta1", "relation": "isSupplementTo" }, { diff --git a/README.md b/README.md index 605ba1c514e..dc4139a8172 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ If your Mac uses the Apple Silicon (M1, arm64) architecture: https://brew.sh/ required because it provides a version of ``gfortran`` with necessary changes for this platform that are not in a released upstream version of GCC. (The ``gfortran`` package that comes with the Sage - distribution is not suitable for the M1.) + distribution is not suitable for the M1/M2.) If your Mac uses the Intel (x86_64) architecture: @@ -182,16 +182,22 @@ in the Installation Guide. 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. + See [build/pkgs/gcc/SPKG.rst](build/pkgs/gcc/SPKG.rst) and + [build/pkgs/gfortran/SPKG.rst](build/pkgs/gfortran/SPKG.rst) + for a discussion of suitable compilers. - Build tools: GNU `make`, GNU `m4`, `perl` (including ``ExtUtils::MakeMaker``), `ranlib`, `git`, `tar`, `bc`. + See [build/pkgs/_prereq/SPKG.rst](build/pkgs/_prereq/SPKG.rst) for + more details. - Python 3.4 or later, or Python 2.7, a full installation including `urllib`; but ideally version 3.8.x, 3.9.x, or 3.10.x, which will avoid having to build Sage's own copy of Python 3. + See [build/pkgs/python3/SPKG.rst](build/pkgs/python3/SPKG.rst) + for more details. We have collected lists of system packages that provide these build prerequisites. See, in the folder diff --git a/VERSION.txt b/VERSION.txt index affcafdf359..5a632a27b46 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -SageMath version 9.7.rc1, Release Date: 2022-09-07 +SageMath version 9.8.beta1, Release Date: 2022-09-29 diff --git a/bootstrap-conda b/bootstrap-conda index 0065d6ee3f6..9ba4b50ab6f 100755 --- a/bootstrap-conda +++ b/bootstrap-conda @@ -95,3 +95,26 @@ echo >&2 $0:$LINENO: generate conda environment files 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-dist-helpers b/build/bin/sage-dist-helpers index 339d06ce493..24769ebfffc 100644 --- a/build/bin/sage-dist-helpers +++ b/build/bin/sage-dist-helpers @@ -290,6 +290,8 @@ sdh_pip_install() { sdh_pip_editable_install() { echo "Installing $PKG_NAME (editable mode)" + # Until https://trac.sagemath.org/ticket/34209 switches us to PEP 660 editable wheels + export SETUPTOOLS_ENABLE_FEATURES=legacy-editable python3 -m pip install --verbose --no-deps --no-index --no-build-isolation --isolated --editable "$@" || \ sdh_die "Error installing $PKG_NAME" } 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 #============================================================================== @@ -403,7 +430,7 @@ define SET_SAGE_CHECK $(eval SAGE_CHECK_$(1) := $(2)) endef # Set defaults -$(foreach pkgname, $(NORMAL_PACKAGES),\ +$(foreach pkgname, $(NORMAL_PACKAGES) $(SCRIPT_PACKAGES),\ $(eval $(call SET_SAGE_CHECK,$(pkgname),$(SAGE_CHECK)))) # Parsing the SAGE_CHECK_PACKAGES variable: @@ -433,7 +460,7 @@ $(foreach clause, $(SAGE_CHECK_PACKAGES_sep), \ $(eval $(call SET_SAGE_CHECK,$(subst ?,,$(clause)),warn)), \ $(eval $(call SET_SAGE_CHECK,$(clause),yes))))) debug-check: - @echo $(foreach pkgname, $(NORMAL_PACKAGES), SAGE_CHECK_$(pkgname) = $(SAGE_CHECK_$(pkgname))) + @echo $(foreach pkgname, $(NORMAL_PACKAGES) $(SCRIPT_PACKAGES), SAGE_CHECK_$(pkgname) = $(SAGE_CHECK_$(pkgname))) #============================================================================== @@ -649,7 +676,7 @@ $(1)-$(4)-no-deps: . '$$(SAGE_ROOT)/build/bin/sage-build-env' && \ SAGE_SPKG_WHEELS=$$($(4))/var/lib/sage/wheels \ SAGE_INST_LOCAL=$$($(4)) \ - sage-logger -p '$$(SAGE_ROOT)/build/pkgs/$(1)/spkg-install' '$$(SAGE_LOGS)/$(1)-$(2).log' && \ + sage-logger -p 'SAGE_CHECK=$$(SAGE_CHECK_$(1)) $$(SAGE_ROOT)/build/pkgs/$(1)/spkg-install' '$$(SAGE_LOGS)/$(1)-$(2).log' && \ rm -f "$$($(4))/$(SPKG_INST_RELDIR)/$(1)-*" && \ touch "$$($(4))/$(SPKG_INST_RELDIR)/$(1)-$(2)"; \ else ( \ diff --git a/build/pkgs/_develop/distros/debian.txt b/build/pkgs/_develop/distros/debian.txt index 4598e83f3f5..5c3c17488e8 100644 --- a/build/pkgs/_develop/distros/debian.txt +++ b/build/pkgs/_develop/distros/debian.txt @@ -1,4 +1,3 @@ # Needed for devcontainer support in VS code gpgconf openssh-client -openssh 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/SPKG.rst b/build/pkgs/_prereq/SPKG.rst index a798c656ed0..2b64020a823 100644 --- a/build/pkgs/_prereq/SPKG.rst +++ b/build/pkgs/_prereq/SPKG.rst @@ -4,5 +4,49 @@ _prereq: Represents system packages required for installing SageMath from source Description ----------- -This script package represents the minimal requirements (system packages) +This dummy package represents the minimal requirements (system packages) for installing SageMath from source. + +In addition to standard :wikipedia:`POSIX ` utilities +and the :wikipedia:`bash ` shell, +the following standard command-line development tools must be installed on your +computer: + +- **make**: GNU make, version 3.80 or later. Version 3.82 or later is recommended. +- **m4**: GNU m4 1.4.2 or later (non-GNU or older versions might also work). +- **perl**: version 5.8.0 or later. +- **ar** and **ranlib**: can be obtained as part of GNU binutils. +- **tar**: GNU tar version 1.17 or later, or BSD tar (as provided on macOS). +- **python**: Python 3.4 or later, or Python 2.7. + (This range of versions is a minimal requirement for internal purposes of the SageMath + build system, which is referred to as ``sage-bootstrap-python``.) + +Other versions of these may work, but they are untested. + +On macOS, suitable versions of all of these tools are provided +by the Xcode Command Line Tools. To install them, open a terminal +window and run ``xcode-select --install``; then click "Install" in the +pop-up window. If the Xcode Command Line Tools are already installed, +you may want to check if they need to be updated by typing +``softwareupdate -l``. + +On Linux, ``ar`` and ``ranlib`` are in the `binutils +`_ package. The other +programs are usually located in packages with their respective names. + +On Redhat-derived systems not all perl components are installed by +default and you might have to install the ``perl-ExtUtils-MakeMaker`` +package. + +To check if you have the above prerequisites installed, for example ``perl``, +type:: + + $ command -v perl + +or:: + + $ which perl + +on the command line. If it gives an error (or returns nothing), then +either ``perl`` is not installed, or it is installed but not in your +:wikipedia:`PATH `. 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/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 11d18b32093..53ac5f0f4d1 100644 --- a/build/pkgs/configure/checksums.ini +++ b/build/pkgs/configure/checksums.ini @@ -1,4 +1,4 @@ tarball=configure-VERSION.tar.gz -sha1=17621baa3704964f752a02c3a1533a8adb59cd3d -md5=e1f4d86a1e996aeee5e5c072bf92f0a1 -cksum=3769097678 +sha1=73f9fe2ad9e2cfe224c7667a784144a1c520b9d8 +md5=8be336b502a66c5a410183be1eca2ca9 +cksum=42655200 diff --git a/build/pkgs/configure/package-version.txt b/build/pkgs/configure/package-version.txt index 60e9162dd80..ac53b0e4214 100644 --- a/build/pkgs/configure/package-version.txt +++ b/build/pkgs/configure/package-version.txt @@ -1 +1 @@ -2e5af062ddd382d2d0eb8070dd0c6e872b77b6bd +51d49e23f3d3014e301fefcf72bf7a2a3fe0c4e0 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/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/checksums.ini b/build/pkgs/distlib/checksums.ini index 9c739a93823..1d749b2f9a7 100644 --- a/build/pkgs/distlib/checksums.ini +++ b/build/pkgs/distlib/checksums.ini @@ -1,5 +1,5 @@ -tarball=distlib-VERSION.zip -sha1=e7927ebc964676c17d466ed6a345222c34167a85 -md5=c886b7d99b4085c5d960e7435dcbd397 -cksum=10374426 -upstream_url=https://pypi.io/packages/source/d/distlib/distlib-VERSION.zip +tarball=distlib-VERSION.tar.gz +sha1=3a86d49dc17320325004564d0dc86afa808624bc +md5=f60ba4e3f8e76c214d3d00b2227a16f7 +cksum=1543870863 +upstream_url=https://pypi.io/packages/source/d/distlib/distlib-VERSION.tar.gz 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/distlib/package-version.txt b/build/pkgs/distlib/package-version.txt index 42045acae20..449d7e73a96 100644 --- a/build/pkgs/distlib/package-version.txt +++ b/build/pkgs/distlib/package-version.txt @@ -1 +1 @@ -0.3.4 +0.3.6 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/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/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.rst b/build/pkgs/gcc/SPKG.rst index 1f5684b86b2..75feee2d6d8 100644 --- a/build/pkgs/gcc/SPKG.rst +++ b/build/pkgs/gcc/SPKG.rst @@ -1,10 +1,70 @@ -gcc: The GNU Compiler Collection, including the C, C++ and Fortran compiler -=========================================================================== +gcc: The GNU Compiler Collection or other suitable C and C++ compilers +====================================================================== Description ----------- -The GNU Compiler Collection, including the C, C++ and Fortran compiler. +This package represents the required C and C++ compilers. + +- GCC (GNU Compiler Collection) versions 8.x to 12.x are supported. + +- Clang (LLVM) is also supported. + +The required Fortran compiler is represented by the package ``gfortran``. + +You can pass the names of compilers to use to ``./configure`` using +the environment variables :envvar:`CC`, :envvar:`CXX`, and +:envvar:`FC`, for C, C++, and Fortran compilers, respectively. + +For example, if your C compiler is ``clang``, your C++ compiler is +``clang++``, and your Fortran compiler is ``flang``, then you would +need to run:: + + $ ./configure CC=clang CXX=clang++ FC=flang + +Vendor and versions of the C and C++ compilers should match. + +Users of older Linux distributions (in particular, ``ubuntu-xenial`` +or older, ``debian-stretch`` or older, ``linuxmint-18`` or older) +should upgrade their systems before attempting to install Sage from +source. Users of ``ubuntu-bionic``, ``linuxmint-19.x``, and +``opensuse-15.x`` can install a versioned ``gcc`` system package +and then use:: + + $ ./configure CC=gcc-8 CXX=g++-8 FC=gfortran-8 + +or similar. Users on ``ubuntu`` can also install a modern compiler +toolchain `using the ubuntu-toolchain-r ppa +`_. +On ``ubuntu-trusty``, also the package ``binutils-2.26`` is required; +after installing it, make it available using ``export +PATH="/usr/lib/binutils-2.26/bin:$PATH"``. Instead of upgrading their +distribution, users of ``centos-7`` can install a modern compiler +toolchain `using Redhat's devtoolset +`_. + +This package uses the non-standard default +``configure --with-system-gcc=force``, giving an error at ``configure`` +time when no suitable system compilers are configured. + +You can override this using ``./configure --without-system-gcc``. In +this case, Sage builds and installs the GNU Compiler Collection, +including the C, C++ and Fortran compiler. This is not recommended. +You will need suitable C and C++ compilers from which GCC can +bootstrap itself. There are some known problems with old assemblers, +in particular when building the ``ecm`` and ``fflas_ffpack`` +packages. You should ensure that your assembler understands all +instructions for your processor. On Linux, this means you need a +recent version of ``binutils`` (not provided by an SPKG); on macOS +you need a recent version of Xcode. + +(Installing the +``gfortran`` SPKG becomes a no-op in this case.) + +Building Sage from source on Apple Silicon (M1/M2) requires the use of +Apple's Command Line Tools, and those tools include a suitable +compiler. Sage's ``gcc`` SPKG is not suitable for M1/M2; building it +will likely fail. License ------- 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/gfortran/SPKG.rst b/build/pkgs/gfortran/SPKG.rst index 1bea5fae5fe..9559b98456d 100644 --- a/build/pkgs/gfortran/SPKG.rst +++ b/build/pkgs/gfortran/SPKG.rst @@ -4,8 +4,21 @@ gfortran: Fortran compiler from the GNU Compiler Collection Description ----------- -The GNU Compiler Collection, including the C, C++ and Fortran compiler. -This particular package is meant to only make gfortran available. +This package represents the required Fortran compiler. + +Officially we support ``gfortran`` from `GNU Compiler Collection (GCC) +`_. It has also been reported that using ``flang`` +(from LLVM) might work. + +You can pass the names of compilers to use to ``./configure`` using +the environment variables :envvar:`CC`, :envvar:`CXX`, and +:envvar:`FC`, for C, C++, and Fortran compilers, respectively. + +For example, if your C compiler is ``clang``, your C++ compiler is +``clang++``, and your Fortran compiler is ``flang``, then you would +need to run:: + + $ ./configure CC=clang CXX=clang++ FC=flang License ------- diff --git a/build/pkgs/hatchling/checksums.ini b/build/pkgs/hatchling/checksums.ini index ec1317175da..598cd79a2c7 100644 --- a/build/pkgs/hatchling/checksums.ini +++ b/build/pkgs/hatchling/checksums.ini @@ -1,5 +1,5 @@ tarball=hatchling-VERSION.tar.gz -sha1=63ae7f29657e4d069c716e098a9ac8114d2f29f9 -md5=e05f845d94f400c3085bbaab21adcdbe -cksum=3213522818 +sha1=8f102796a225fb18b0571a44308341c7211d5d94 +md5=c50eff4f711cee451037ec7eb780154a +cksum=3180958969 upstream_url=https://pypi.io/packages/source/h/hatchling/hatchling-VERSION.tar.gz 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/hatchling/package-version.txt b/build/pkgs/hatchling/package-version.txt index 3a3cd8cc8b0..81c871de46b 100644 --- a/build/pkgs/hatchling/package-version.txt +++ b/build/pkgs/hatchling/package-version.txt @@ -1 +1 @@ -1.3.1 +1.10.0 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_metadata/checksums.ini b/build/pkgs/importlib_metadata/checksums.ini index d5a9b1a2668..ea39c54a4a3 100644 --- a/build/pkgs/importlib_metadata/checksums.ini +++ b/build/pkgs/importlib_metadata/checksums.ini @@ -1,5 +1,5 @@ tarball=importlib_metadata-VERSION.tar.gz -sha1=7d15d8e06299a8f24e076600899aceee75ce8b0b -md5=a605ba6ec315bc1324fd6b7210fe7c12 -cksum=448954927 +sha1=ec68de1ec1800048de8656b9d211e22b7fe7c53e +md5=cfcf29185e13439c76d09c94bc8d81f4 +cksum=2134804316 upstream_url=https://pypi.io/packages/source/i/importlib_metadata/importlib_metadata-VERSION.tar.gz diff --git a/build/pkgs/importlib_metadata/package-version.txt b/build/pkgs/importlib_metadata/package-version.txt index 326ec6355f3..815588ef140 100644 --- a/build/pkgs/importlib_metadata/package-version.txt +++ b/build/pkgs/importlib_metadata/package-version.txt @@ -1 +1 @@ -4.8.2 +4.12.0 diff --git a/build/pkgs/importlib_resources/checksums.ini b/build/pkgs/importlib_resources/checksums.ini index f769a9f43b4..99a4cdf7908 100644 --- a/build/pkgs/importlib_resources/checksums.ini +++ b/build/pkgs/importlib_resources/checksums.ini @@ -1,5 +1,5 @@ tarball=importlib_resources-VERSION.tar.gz -sha1=d1f2742895a68f3f8d19dd7285df1687877fb15a -md5=5db738106ca7c05340495c36357986a2 -cksum=1338307365 +sha1=3b4b20fa0399e2fa21c7506be27a4b943495d3ad +md5=3b6d98270d40b2ba7af1f8d09188f0c2 +cksum=2401793228 upstream_url=https://pypi.io/packages/source/i/importlib_resources/importlib_resources-VERSION.tar.gz 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/importlib_resources/package-version.txt b/build/pkgs/importlib_resources/package-version.txt index ce7f2b425b5..b3d91f9cfc0 100644 --- a/build/pkgs/importlib_resources/package-version.txt +++ b/build/pkgs/importlib_resources/package-version.txt @@ -1 +1 @@ -5.2.2 +5.9.0 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_packaging/checksums.ini b/build/pkgs/jupyter_packaging/checksums.ini index eb449d9da6f..65b3bd148f6 100644 --- a/build/pkgs/jupyter_packaging/checksums.ini +++ b/build/pkgs/jupyter_packaging/checksums.ini @@ -1,5 +1,5 @@ tarball=jupyter_packaging-VERSION.tar.gz -sha1=c9b7dd75a70ad6a7c621588db410f07fba7e0fad -md5=d30e6fb387d46398a3ab26765b8fa74f -cksum=669146472 +sha1=092d249360aa56838a188decc4bcd09647fda4d9 +md5=9c6834023bd699bda5365ab7ed18bde2 +cksum=3308833189 upstream_url=https://pypi.io/packages/source/j/jupyter_packaging/jupyter_packaging-VERSION.tar.gz diff --git a/build/pkgs/jupyter_packaging/package-version.txt b/build/pkgs/jupyter_packaging/package-version.txt index 26acbf080be..aa22d3ce39b 100644 --- a/build/pkgs/jupyter_packaging/package-version.txt +++ b/build/pkgs/jupyter_packaging/package-version.txt @@ -1 +1 @@ -0.12.2 +0.12.3 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/msolve/SPKG.rst b/build/pkgs/msolve/SPKG.rst new file mode 100644 index 00000000000..00c1c417208 --- /dev/null +++ b/build/pkgs/msolve/SPKG.rst @@ -0,0 +1,21 @@ +msolve: Multivariate polynomial system solver +============================================= + +Description +----------- + +Open source C library implementing computer algebra algorithms for solving +polynomial systems (with rational coefficients or coefficients in a prime field). + +License +------- + +GPL v2+ + +Upstream Contact +---------------- + +https://github.com/algebraic-solving/msolve + +Upstream does not make source tarballs. +We make tarballs from the fork https://github.com/mkoeppe/msolve (branch 0.4.4+sage) diff --git a/build/pkgs/msolve/checksums.ini b/build/pkgs/msolve/checksums.ini new file mode 100644 index 00000000000..0b7558afd2b --- /dev/null +++ b/build/pkgs/msolve/checksums.ini @@ -0,0 +1,5 @@ +tarball=msolve-VERSION.tar.gz +sha1=5b227de8b222bfe8d143e1d7ea77ad71cd209dc8 +md5=2f34bd9ccb089688ae169201281108dc +cksum=941373315 +upstream_url=https://trac.sagemath.org/raw-attachment/ticket/31664/msolve-VERSION.tar.gz diff --git a/build/pkgs/msolve/dependencies b/build/pkgs/msolve/dependencies new file mode 100644 index 00000000000..4f96ff0a6c9 --- /dev/null +++ b/build/pkgs/msolve/dependencies @@ -0,0 +1,4 @@ +$(MP_LIBRARY) flint mpfr + +---------- +All lines of this file are ignored except the first. diff --git a/build/pkgs/msolve/package-version.txt b/build/pkgs/msolve/package-version.txt new file mode 100644 index 00000000000..fb78594e923 --- /dev/null +++ b/build/pkgs/msolve/package-version.txt @@ -0,0 +1 @@ +0.4.4+sage-2022-09-11 diff --git a/build/pkgs/msolve/spkg-install.in b/build/pkgs/msolve/spkg-install.in new file mode 100644 index 00000000000..2aaf0e99043 --- /dev/null +++ b/build/pkgs/msolve/spkg-install.in @@ -0,0 +1,4 @@ +cd src +sdh_configure +sdh_make +sdh_make_install diff --git a/build/pkgs/msolve/type b/build/pkgs/msolve/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/msolve/type @@ -0,0 +1 @@ +optional 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/numpy/checksums.ini b/build/pkgs/numpy/checksums.ini index c15b3d8db64..544f35fbfb8 100644 --- a/build/pkgs/numpy/checksums.ini +++ b/build/pkgs/numpy/checksums.ini @@ -1,5 +1,5 @@ -tarball=numpy-VERSION.zip -sha1=3ac08064b2ec8db8fe4870c2545c9d154f46bb30 -md5=b44849506fbb54cdef9dbb435b2b1987 -cksum=735479084 -upstream_url=https://pypi.io/packages/source/n/numpy/numpy-VERSION.zip +tarball=numpy-VERSION.tar.gz +sha1=570c995d7b155c7e4ac43bc46594172cedf1e4fa +md5=6efc60a3f6c1b74c849d53fbcc07807b +cksum=3973735135 +upstream_url=https://pypi.io/packages/source/n/numpy/numpy-VERSION.tar.gz diff --git a/build/pkgs/numpy/package-version.txt b/build/pkgs/numpy/package-version.txt index 2a0ba77cc5e..ac1df3fce34 100644 --- a/build/pkgs/numpy/package-version.txt +++ b/build/pkgs/numpy/package-version.txt @@ -1 +1 @@ -1.22.4 +1.23.3 diff --git a/build/pkgs/numpy/spkg-install.in b/build/pkgs/numpy/spkg-install.in index f66c6f56426..2b555d8b871 100644 --- a/build/pkgs/numpy/spkg-install.in +++ b/build/pkgs/numpy/spkg-install.in @@ -7,6 +7,14 @@ if [ `uname` = "Darwin" ]; then unset ATLAS unset BLAS unset LAPACK + # https://trac.sagemath.org/ticket/34110#comment:35 + # The fix for "reciprocal" (affected by a clang compiler bug) in + # https://github.com/numpy/numpy/pull/19926 relies on -ftrapping-math + # being used when Apple clang v12+ is used. + # But numpy.distutils.ccompiler only sets this flag when + # $CC contains the string "clang" -- missing the case CC=/usr/bin/gcc. + # So we set it here explicitly if the compiler supports the flag. + export CFLAGS="$(testcflags.sh $CFLAGS -ftrapping-math)" else export {ATLAS,PTATLAS,OPENBLAS,MKL,MKLROOT}=None export LDFLAGS="${LDFLAGS} -shared" @@ -27,15 +35,10 @@ fi export FFLAGS="$FFLAGS -fPIC" export FCFLAGS="$FCFLAGS -fPIC" -# Numpy 1.20.3 enables some intrinsics on machines without support with `-march=native`. -# We disable it until this is fixed. -export CFLAGS="$CFLAGS_NON_NATIVE" - -export NUMPY_FCONFIG="config_fc --noopt --noarch" if [ "$SAGE_FAT_BINARY" = "yes" ]; then export NUMPY_FCONFIG="--cpu-baseline=NONE" else - export NUMPY_FCONFIG + export NUMPY_FCONFIG="" fi # Trac #32423: Fix 32-bit builds on x86_64 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/checksums.ini b/build/pkgs/pathspec/checksums.ini index cb80c60c623..2db40ded285 100644 --- a/build/pkgs/pathspec/checksums.ini +++ b/build/pkgs/pathspec/checksums.ini @@ -1,5 +1,5 @@ tarball=pathspec-VERSION.tar.gz -sha1=afe51c21951f457f82a5810016d0b6752ffc487b -md5=9b6b70fa5ffc31e6f5700522880140c0 -cksum=3501694416 +sha1=ef0f4b07097506575ca8052256b56f137a7b170d +md5=6f4fde5e701d328a2846d206edb63aa9 +cksum=2376511942 upstream_url=https://pypi.io/packages/source/p/pathspec/pathspec-VERSION.tar.gz 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/pathspec/package-version.txt b/build/pkgs/pathspec/package-version.txt index ac39a106c48..571215736a6 100644 --- a/build/pkgs/pathspec/package-version.txt +++ b/build/pkgs/pathspec/package-version.txt @@ -1 +1 @@ -0.9.0 +0.10.1 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/checksums.ini b/build/pkgs/pip/checksums.ini index 35d4f6fde7b..a13af9996c7 100644 --- a/build/pkgs/pip/checksums.ini +++ b/build/pkgs/pip/checksums.ini @@ -1,5 +1,5 @@ tarball=pip-VERSION.tar.gz -sha1=be5b671f4868816c6ad2e09258c22bdb3fc82775 -md5=6ec06d38c3aed5d22bcbbbfbf7114d6a -cksum=3372144553 +sha1=ed6e6d191a686b4f989a1dbb2640737a1123f24f +md5=05bb8c0607721d171e9eecf22a8c5cc6 +cksum=4023376185 upstream_url=https://pypi.io/packages/source/p/pip/pip-VERSION.tar.gz diff --git a/build/pkgs/pip/package-version.txt b/build/pkgs/pip/package-version.txt index 30ed8778377..637c2a16439 100644 --- a/build/pkgs/pip/package-version.txt +++ b/build/pkgs/pip/package-version.txt @@ -1 +1 @@ -22.1.2 +22.2.2 diff --git a/build/pkgs/pkgconfig/dependencies b/build/pkgs/pkgconfig/dependencies index 79554fc438d..6dfe046e55e 100644 --- a/build/pkgs/pkgconfig/dependencies +++ b/build/pkgs/pkgconfig/dependencies @@ -1,4 +1,4 @@ -$(PYTHON) | $(PYTHON_TOOLCHAIN) pkgconf +$(PYTHON) | $(PYTHON_TOOLCHAIN) pkgconf poetry_core ---------- All lines of this file are ignored except the first. diff --git a/build/pkgs/pkgconfig/spkg-install.in b/build/pkgs/pkgconfig/spkg-install.in index 761190e309c..d14edc90bcd 100644 --- a/build/pkgs/pkgconfig/spkg-install.in +++ b/build/pkgs/pkgconfig/spkg-install.in @@ -1,10 +1,5 @@ cd src -# Make sure that modern pip uses the generated setup.py -# that is distributed with the PyPI tarball, -# so we do not have to have poetry. Trac #29803. -rm -f pyproject.toml - sdh_pip_install . if [ $? -ne 0 ]; then 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/primecount/distros/homebrew.txt b/build/pkgs/primecount/distros/homebrew.txt new file mode 100644 index 00000000000..f67843baa2b --- /dev/null +++ b/build/pkgs/primecount/distros/homebrew.txt @@ -0,0 +1 @@ +primecount 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/python3/SPKG.rst b/build/pkgs/python3/SPKG.rst index 10d444bd020..94a163de1f3 100644 --- a/build/pkgs/python3/SPKG.rst +++ b/build/pkgs/python3/SPKG.rst @@ -4,7 +4,21 @@ python3: The Python programming language Description ----------- -The Python programming language +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. + +Sage will accept versions 3.8.x to 3.10.x. + +You can also use ``--with-python=/path/to/python3_binary`` to tell Sage to use +``/path/to/python3_binary`` to set up the venv. Note that setting up the venv requires +a number of Python modules to be available within the Python in question. Currently, +as of Sage 9.7, these modules are as follows: ``sqlite3``, ``ctypes``, ``math``, +``hashlib``, ``crypt``, ``socket``, ``zlib``, ``distutils.core``, ``ssl`` - +they will be checked for by the ``configure`` script. + +Use the ``configure`` option ``--without-system-python3`` in case you want Python 3 +built from scratch. Upstream Contact 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 5fbaf1821bc..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=b54b4f1a0c7ce8a80ddb35d35b4e5d1552592793 -md5=9ce0139a294d1c738abc4eb75aab84cd -cksum=4113725847 +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 8225a4ba4fd..571215736a6 100644 --- a/build/pkgs/python_igraph/package-version.txt +++ b/build/pkgs/python_igraph/package-version.txt @@ -1 +1 @@ -0.9.11 +0.10.1 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/sage_conf/install-requires.txt b/build/pkgs/sage_conf/install-requires.txt index 806746abbe4..8e33acc92e6 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.7rc1 +sage-conf ~= 9.8b1 diff --git a/build/pkgs/sage_docbuild/install-requires.txt b/build/pkgs/sage_docbuild/install-requires.txt index 9a762283dae..a2cc79f66c9 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.7rc1 +sage-docbuild ~= 9.8b1 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 72ddc44f5ef..050098266b7 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.7rc1 +sage-setup ~= 9.8b1 diff --git a/build/pkgs/sage_sws2rst/install-requires.txt b/build/pkgs/sage_sws2rst/install-requires.txt index 0c11aa3cd0d..ca58fa64648 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.7rc1 +sage-sws2rst ~= 9.8b1 diff --git a/build/pkgs/sagelib/install-requires.txt b/build/pkgs/sagelib/install-requires.txt index 17a9b7fbd9e..82d6db622ed 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.7rc1 +sagelib ~= 9.8b1 diff --git a/build/pkgs/sagelib/spkg-install b/build/pkgs/sagelib/spkg-install index 8d91b16b3f0..91e4d8c1869 100755 --- a/build/pkgs/sagelib/spkg-install +++ b/build/pkgs/sagelib/spkg-install @@ -52,6 +52,8 @@ if [ "$SAGE_EDITABLE" = yes ]; then # and renamed the distribution to "sagemath-standard"). There is no clean way to uninstall # them, so we just use rm. (cd "$SITEPACKAGESDIR" && rm -rf sage sage_setup sage-[1-9]*.egg-info sage-[1-9]*.dist-info) + # Until https://trac.sagemath.org/ticket/34209 switches us to PEP 660 editable wheels + export SETUPTOOLS_ENABLE_FEATURES=legacy-editable time python3 -m pip install --verbose --no-deps --no-index --no-build-isolation --isolated --editable . || exit 1 else # Make sure that an installed old version of sagelib in which sage is an ordinary package diff --git a/build/pkgs/sagemath_categories/dependencies b/build/pkgs/sagemath_categories/dependencies deleted file mode 120000 index 55c209e6418..00000000000 --- a/build/pkgs/sagemath_categories/dependencies +++ /dev/null @@ -1 +0,0 @@ -../sagemath_objects/dependencies \ No newline at end of file diff --git a/build/pkgs/sagemath_categories/dependencies b/build/pkgs/sagemath_categories/dependencies new file mode 100644 index 00000000000..d8b6bdbd4a7 --- /dev/null +++ b/build/pkgs/sagemath_categories/dependencies @@ -0,0 +1 @@ +$(PYTHON) sagemath_objects | $(PYTHON_TOOLCHAIN) sagemath_environment sage_setup cython pkgconfig python_build diff --git a/build/pkgs/sagemath_categories/dependencies_check b/build/pkgs/sagemath_categories/dependencies_check new file mode 100644 index 00000000000..7d2fe6c3064 --- /dev/null +++ b/build/pkgs/sagemath_categories/dependencies_check @@ -0,0 +1 @@ +tox sagemath_repl diff --git a/build/pkgs/sagemath_categories/install-requires.txt b/build/pkgs/sagemath_categories/install-requires.txt index faa6593b049..f5c2a080bae 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.7rc1 +sagemath-categories ~= 9.8b1 diff --git a/build/pkgs/sagemath_environment/dependencies b/build/pkgs/sagemath_environment/dependencies deleted file mode 120000 index 55c209e6418..00000000000 --- a/build/pkgs/sagemath_environment/dependencies +++ /dev/null @@ -1 +0,0 @@ -../sagemath_objects/dependencies \ No newline at end of file diff --git a/build/pkgs/sagemath_environment/dependencies b/build/pkgs/sagemath_environment/dependencies new file mode 100644 index 00000000000..605611e7a21 --- /dev/null +++ b/build/pkgs/sagemath_environment/dependencies @@ -0,0 +1 @@ +$(PYTHON) | $(PYTHON_TOOLCHAIN) python_build diff --git a/build/pkgs/sagemath_environment/dependencies_check b/build/pkgs/sagemath_environment/dependencies_check new file mode 100644 index 00000000000..053148f8486 --- /dev/null +++ b/build/pkgs/sagemath_environment/dependencies_check @@ -0,0 +1 @@ +tox diff --git a/build/pkgs/sagemath_environment/install-requires.txt b/build/pkgs/sagemath_environment/install-requires.txt index 9dd9d149e08..5ac02d56d50 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.7rc1 +sagemath-environment ~= 9.8b1 diff --git a/build/pkgs/sagemath_objects/dependencies b/build/pkgs/sagemath_objects/dependencies index 217821d206b..807b8b17215 100644 --- a/build/pkgs/sagemath_objects/dependencies +++ b/build/pkgs/sagemath_objects/dependencies @@ -1,4 +1,3 @@ -FORCE $(PYTHON) cysignals gmpy2 ipython | $(PYTHON_TOOLCHAIN) sage_setup cython pkgconfig python_build +FORCE $(PYTHON) cysignals gmpy2 | $(PYTHON_TOOLCHAIN) sagemath_environment sage_setup cython pkgconfig python_build # FORCE: Always run the spkg-install script -# ipython - for the doctester diff --git a/build/pkgs/sagemath_objects/install-requires.txt b/build/pkgs/sagemath_objects/install-requires.txt index 7d5feaa7bb8..24f5473ae33 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.7rc1 +sagemath-objects ~= 9.8b1 diff --git a/build/pkgs/sagemath_objects/spkg-install b/build/pkgs/sagemath_objects/spkg-install index 5c2ab2350c4..6cc85e07e55 100755 --- a/build/pkgs/sagemath_objects/spkg-install +++ b/build/pkgs/sagemath_objects/spkg-install @@ -19,15 +19,11 @@ export PIP_FIND_LINKS="file://$SAGE_SPKG_WHEELS" # (Important because sagemath-objects uses MANIFEST.in for filtering.) # Do not install the wheel. DIST_DIR="$(mktemp -d)" -if ! python3 -m build --outdir "$DIST_DIR"/dist .; then - # This happens on Debian without python3-venv installed - "ensurepip" is missing - echo "Falling back to --no-isolation" - python3 -m build --no-isolation --outdir "$DIST_DIR"/dist . || sdh_die "Failure building sdist and wheel" -fi +python3 -m build --outdir "$DIST_DIR"/dist . || sdh_die "Failure building sdist and wheel" wheel=$(cd "$DIST_DIR" && sdh_store_wheel . && echo $wheel) ls -l "$wheel" if [ "$SAGE_CHECK" != no ]; then - tox -v -e sagepython-norequirements --installpkg "$wheel" + tox -r -v -e sagepython-sagewheels-nopypi-norequirements --installpkg $wheel fi diff --git a/build/pkgs/sagemath_repl/dependencies b/build/pkgs/sagemath_repl/dependencies deleted file mode 120000 index 55c209e6418..00000000000 --- a/build/pkgs/sagemath_repl/dependencies +++ /dev/null @@ -1 +0,0 @@ -../sagemath_objects/dependencies \ No newline at end of file diff --git a/build/pkgs/sagemath_repl/dependencies b/build/pkgs/sagemath_repl/dependencies new file mode 100644 index 00000000000..ebc253dac5b --- /dev/null +++ b/build/pkgs/sagemath_repl/dependencies @@ -0,0 +1 @@ +$(PYTHON) sagemath_objects sagemath_environment ipython ipywidgets | $(PYTHON_TOOLCHAIN) python_build diff --git a/build/pkgs/sagemath_repl/dependencies_check b/build/pkgs/sagemath_repl/dependencies_check new file mode 100644 index 00000000000..053148f8486 --- /dev/null +++ b/build/pkgs/sagemath_repl/dependencies_check @@ -0,0 +1 @@ +tox diff --git a/build/pkgs/sagemath_repl/install-requires.txt b/build/pkgs/sagemath_repl/install-requires.txt new file mode 100644 index 00000000000..9d91ae25d19 --- /dev/null +++ b/build/pkgs/sagemath_repl/install-requires.txt @@ -0,0 +1,2 @@ +# This file is updated on every release by the sage-update-version script +sagemath-repl ~= 9.8b1 diff --git a/build/pkgs/setuptools/SPKG.rst b/build/pkgs/setuptools/SPKG.rst index 8d510960f1d..a50e171a98d 100644 --- a/build/pkgs/setuptools/SPKG.rst +++ b/build/pkgs/setuptools/SPKG.rst @@ -4,26 +4,24 @@ setuptools: Build system for Python packages Description ----------- -setuptools is a collection of enhancements to the Python distutils (for -Python 2.6 and up) that allow you to more easily build and distribute -Python packages, especially ones that have dependencies on other -packages. +setuptools is the classical build system for Python packages, +a collection of enhancements to the Python distutils. -Website: http://pypi.python.org/pypi/setuptools/ +This package represents version 63.x of ``setuptools``. +Sage installs this version to provide the build system +for non-PEP 517 packages. In particular, Sage uses it +for building ``numpy``, whose build system ``numpy.distutils`` +is not compatible with newer versions of ``setuptools``, +see https://github.com/numpy/numpy/pull/22154 License ------- -PSF or ZPL. i.e Python Software Foundation License or Zope Public -License - +MIT License Upstream Contact ---------------- -- Phillip J. Eby (distutils-sig@python org) - -Dependencies ------------- +http://pypi.python.org/pypi/setuptools/ -- python +https://github.com/pypa/setuptools diff --git a/build/pkgs/setuptools/checksums.ini b/build/pkgs/setuptools/checksums.ini index 8a9faad33ca..d47099a8019 100644 --- a/build/pkgs/setuptools/checksums.ini +++ b/build/pkgs/setuptools/checksums.ini @@ -1,5 +1,5 @@ tarball=setuptools-VERSION.tar.gz -sha1=2a5a4ac384ace22dd10e3dac0a1b6af49e03c596 -md5=d72acb93671bde8e4ca0971866f9cdda -cksum=2262493785 +sha1=b14b8e2cf965fdb6870ebfccee50c751c056f757 +md5=02a0e4dc4fa13168904e8769a077aa26 +cksum=2734084418 upstream_url=https://pypi.io/packages/source/s/setuptools/setuptools-VERSION.tar.gz diff --git a/build/pkgs/setuptools/install-requires.txt b/build/pkgs/setuptools/install-requires.txt index 486c3c348ee..0810ca37277 100644 --- a/build/pkgs/setuptools/install-requires.txt +++ b/build/pkgs/setuptools/install-requires.txt @@ -1,2 +1 @@ -# Set this bound until https://trac.sagemath.org/ticket/34209 adds support for PEP660 editable builds -setuptools >=49.6.0,<64.0.0 +setuptools >=49.6.0 diff --git a/build/pkgs/setuptools/package-version.txt b/build/pkgs/setuptools/package-version.txt index 61f1f83a084..fe3c6688881 100644 --- a/build/pkgs/setuptools/package-version.txt +++ b/build/pkgs/setuptools/package-version.txt @@ -1 +1 @@ -63.2.0 +63.4.3 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/setuptools_wheel/SPKG.rst b/build/pkgs/setuptools_wheel/SPKG.rst index c78602a296a..b77a6679f8f 100644 --- a/build/pkgs/setuptools_wheel/SPKG.rst +++ b/build/pkgs/setuptools_wheel/SPKG.rst @@ -3,3 +3,6 @@ setuptools_wheel: Build the setuptools package as a wheel After installing setuptools and wheel, we build a wheel of setuptools to complete the set of wheels stored in our wheelhouse. + +This version of setuptools is suitable for PEP 517/518/660 builds, +but it is not suitable for building ``numpy``. diff --git a/build/pkgs/setuptools_wheel/checksums.ini b/build/pkgs/setuptools_wheel/checksums.ini deleted file mode 120000 index 4f64d3ce107..00000000000 --- a/build/pkgs/setuptools_wheel/checksums.ini +++ /dev/null @@ -1 +0,0 @@ -../setuptools/checksums.ini \ No newline at end of file diff --git a/build/pkgs/setuptools_wheel/checksums.ini b/build/pkgs/setuptools_wheel/checksums.ini new file mode 100644 index 00000000000..2da328e1726 --- /dev/null +++ b/build/pkgs/setuptools_wheel/checksums.ini @@ -0,0 +1,5 @@ +tarball=setuptools-VERSION.tar.gz +sha1=e5f9797d85db9bb2ce39b401c1b7aca38de616b5 +md5=ec88a6545351e72ca73fcec7a6bff6ad +cksum=1981709060 +upstream_url=https://pypi.io/packages/source/s/setuptools/setuptools-VERSION.tar.gz diff --git a/build/pkgs/setuptools_wheel/package-version.txt b/build/pkgs/setuptools_wheel/package-version.txt deleted file mode 120000 index 5268dbec8f6..00000000000 --- a/build/pkgs/setuptools_wheel/package-version.txt +++ /dev/null @@ -1 +0,0 @@ -../setuptools/package-version.txt \ No newline at end of file diff --git a/build/pkgs/setuptools_wheel/package-version.txt b/build/pkgs/setuptools_wheel/package-version.txt new file mode 100644 index 00000000000..ba0d20023a1 --- /dev/null +++ b/build/pkgs/setuptools_wheel/package-version.txt @@ -0,0 +1 @@ +65.4.0 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/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/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/terminado/checksums.ini b/build/pkgs/terminado/checksums.ini index f33efd4b5da..794b2cda764 100644 --- a/build/pkgs/terminado/checksums.ini +++ b/build/pkgs/terminado/checksums.ini @@ -1,5 +1,5 @@ tarball=terminado-VERSION.tar.gz -sha1=65f40480c1d8077b78dcffb7e0c909eae86998bf -md5=4871263f6aaed18e09603fa6785b4340 -cksum=2070178009 +sha1=b0ff75a4f024dc07c9a819c1a63d75908624a5d3 +md5=e3fe92b48b3885ffa19b9890ed41578f +cksum=1261401246 upstream_url=https://pypi.io/packages/source/t/terminado/terminado-VERSION.tar.gz diff --git a/build/pkgs/terminado/dependencies b/build/pkgs/terminado/dependencies index e44a0d91033..54ec1c7c229 100644 --- a/build/pkgs/terminado/dependencies +++ b/build/pkgs/terminado/dependencies @@ -1,4 +1,4 @@ -$(PYTHON) | $(PYTHON_TOOLCHAIN) ptyprocess tornado +$(PYTHON) ptyprocess tornado | $(PYTHON_TOOLCHAIN) hatchling ---------- All lines of this file are ignored except the first. diff --git a/build/pkgs/terminado/package-version.txt b/build/pkgs/terminado/package-version.txt index 34a83616bb5..a5510516948 100644 --- a/build/pkgs/terminado/package-version.txt +++ b/build/pkgs/terminado/package-version.txt @@ -1 +1 @@ -0.12.1 +0.15.0 diff --git a/build/pkgs/terminado/spkg-install.in b/build/pkgs/terminado/spkg-install.in index cb4ba894442..058b1344dc2 100644 --- a/build/pkgs/terminado/spkg-install.in +++ b/build/pkgs/terminado/spkg-install.in @@ -1,8 +1,3 @@ cd src -# Make sure that modern pip uses the generated setup.py -# that is distributed with the PyPI tarball, -# so we do not have to have flit. Trac #29803. -rm -f pyproject.toml - sdh_pip_install . 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/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/checksums.ini b/build/pkgs/tomlkit/checksums.ini index f2116d86933..f43c80b7196 100644 --- a/build/pkgs/tomlkit/checksums.ini +++ b/build/pkgs/tomlkit/checksums.ini @@ -1,5 +1,5 @@ tarball=tomlkit-VERSION.tar.gz -sha1=c618d9b0490fb626c053c691def29223dcfbd6cc -md5=c4edce886bc20ef8ecea49984cce2e69 -cksum=2455377393 +sha1=65f56e209410e4eee4b45d048e6b4dc0fcaad74a +md5=d0edd43143c7840deb88185685cea8dd +cksum=846256591 upstream_url=https://pypi.io/packages/source/t/tomlkit/tomlkit-VERSION.tar.gz 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/tomlkit/package-version.txt b/build/pkgs/tomlkit/package-version.txt index d9df1bbc0c7..35ad34429be 100644 --- a/build/pkgs/tomlkit/package-version.txt +++ b/build/pkgs/tomlkit/package-version.txt @@ -1 +1 @@ -0.11.0 +0.11.4 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..132ada17594 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): @@ -260,12 +266,20 @@ def fix_checksum(self, package_name): def create(self, package_name, version=None, tarball=None, pkg_type=None, upstream_url=None, description=None, license=None, upstream_contact=None, pypi=False, source='normal'): """ - Create a normal package + Create a package + + $ sage --package create foo --version 1.3 --tarball FoO-VERSION.tar.gz --type experimental + + $ sage --package create scikit_spatial --pypi --type optional + + $ sage --package create torch --pypi --source pip --type optional + + $ sage --package create jupyterlab_markup --pypi --source wheel --type optional """ if '-' in package_name: raise ValueError('package names must not contain dashes, use underscore instead') if pypi: - pypi_version = PyPiVersion(package_name) + pypi_version = PyPiVersion(package_name, source=source) if source == 'normal': if not tarball: # Guess the general format of the tarball name. @@ -275,6 +289,14 @@ def create(self, package_name, version=None, tarball=None, pkg_type=None, upstre # Use a URL from pypi.io instead of the specific URL received from the PyPI query # because it follows a simple pattern. upstream_url = 'https://pypi.io/packages/source/{0:1.1}/{0}/{1}'.format(package_name, tarball) + elif source == 'wheel': + if not tarball: + tarball = pypi_version.tarball.replace(pypi_version.version, 'VERSION') + if not tarball.endswith('-none-any.whl'): + raise ValueError('Only platform-independent wheels can be used for wheel packages, got {0}'.format(tarball)) + if not version: + version = pypi_version.version + upstream_url = 'https://pypi.io/packages/py3/{0:1.1}/{0}/{1}'.format(package_name, tarball) if not description: description = pypi_version.summary if not license: @@ -303,3 +325,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..aea51cb48f0 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') @@ -297,7 +312,7 @@ def make_parser(): 'package_name', default=None, type=str, help='Package name.') parser_create.add_argument( - '--source', type=str, default='normal', help='Package source (one of normal, script, pip)') + '--source', type=str, default='normal', help='Package source (one of normal, wheel, script, pip)') parser_create.add_argument( '--version', type=str, default=None, help='Package version') parser_create.add_argument( @@ -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/creator.py b/build/sage_bootstrap/creator.py index 3779707de1f..f7a6ab203dc 100644 --- a/build/sage_bootstrap/creator.py +++ b/build/sage_bootstrap/creator.py @@ -87,6 +87,8 @@ def set_python_data_and_scripts(self, pypi_package_name=None, source='normal'): If ``source`` is ``"normal"``, write the files ``spkg-install.in`` and ``install-requires.txt``. + If ``source`` is ``"wheel"``, write the file ``install-requires.txt``. + If ``source`` is ``"pip"``, write the file ``requirements.txt``. """ if pypi_package_name is None: @@ -99,10 +101,13 @@ def set_python_data_and_scripts(self, pypi_package_name=None, source='normal'): f.write('cd src\nsdh_pip_install .\n') with open(os.path.join(self.path, 'install-requires.txt'), 'w+') as f: f.write('{0}\n'.format(pypi_package_name)) + elif source == 'wheel': + with open(os.path.join(self.path, 'install-requires.txt'), 'w+') as f: + f.write('{0}\n'.format(pypi_package_name)) elif source == 'pip': with open(os.path.join(self.path, 'requirements.txt'), 'w+') as f: f.write('{0}\n'.format(pypi_package_name)) elif source == 'script': pass else: - raise ValueError('package source must be one of normal, script, or pip') + raise ValueError('package source must be one of normal, script, pip, or wheel') 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/build/sage_bootstrap/pypi.py b/build/sage_bootstrap/pypi.py index 24b050045f9..a11f3a95b46 100644 --- a/build/sage_bootstrap/pypi.py +++ b/build/sage_bootstrap/pypi.py @@ -34,11 +34,15 @@ class PyPiError(Exception): class PyPiVersion(object): - def __init__(self, package_name): + def __init__(self, package_name, source='normal'): self.name = package_name self.json = self._get_json() # Replace provided name with the canonical name self.name = self.json['info']['name'] + if source == 'wheel': + self.python_version = 'py3' + else: + self.python_version = 'source' def _get_json(self): response = urllib.urlopen(self.json_url) @@ -65,9 +69,9 @@ def url(self): Return the source url """ for download in self.json['urls']: - if download['python_version'] == 'source': + if download['python_version'] == self.python_version: return download['url'] - raise PyPiError('No source url for %s found', self.name) + raise PyPiError('No %s url for %s found', self.python_version, self.name) @property def tarball(self): @@ -75,9 +79,9 @@ def tarball(self): Return the source tarball name """ for download in self.json['urls']: - if download['python_version'] == 'source': + if download['python_version'] == self.python_version: return download['filename'] - raise PyPiError('No source url for %s found', self.name) + raise PyPiError('No %s url for %s found', self.python_version, self.name) @property def package_url(self): 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/pkgs/sage-conf/VERSION.txt b/pkgs/sage-conf/VERSION.txt index 37d25ab1334..737bfe2c1d2 100644 --- a/pkgs/sage-conf/VERSION.txt +++ b/pkgs/sage-conf/VERSION.txt @@ -1 +1 @@ -9.7.rc1 +9.8.beta1 diff --git a/pkgs/sage-conf_pypi/VERSION.txt b/pkgs/sage-conf_pypi/VERSION.txt index 37d25ab1334..737bfe2c1d2 100644 --- a/pkgs/sage-conf_pypi/VERSION.txt +++ b/pkgs/sage-conf_pypi/VERSION.txt @@ -1 +1 @@ -9.7.rc1 +9.8.beta1 diff --git a/pkgs/sage-docbuild/VERSION.txt b/pkgs/sage-docbuild/VERSION.txt index 37d25ab1334..737bfe2c1d2 100644 --- a/pkgs/sage-docbuild/VERSION.txt +++ b/pkgs/sage-docbuild/VERSION.txt @@ -1 +1 @@ -9.7.rc1 +9.8.beta1 diff --git a/pkgs/sage-setup/VERSION.txt b/pkgs/sage-setup/VERSION.txt index 37d25ab1334..737bfe2c1d2 100644 --- a/pkgs/sage-setup/VERSION.txt +++ b/pkgs/sage-setup/VERSION.txt @@ -1 +1 @@ -9.7.rc1 +9.8.beta1 diff --git a/pkgs/sage-sws2rst/VERSION.txt b/pkgs/sage-sws2rst/VERSION.txt index 37d25ab1334..737bfe2c1d2 100644 --- a/pkgs/sage-sws2rst/VERSION.txt +++ b/pkgs/sage-sws2rst/VERSION.txt @@ -1 +1 @@ -9.7.rc1 +9.8.beta1 diff --git a/pkgs/sagemath-categories/MANIFEST.in.m4 b/pkgs/sagemath-categories/MANIFEST.in.m4 index 3a9b1d9bff1..390c7e5759f 100644 --- a/pkgs/sagemath-categories/MANIFEST.in.m4 +++ b/pkgs/sagemath-categories/MANIFEST.in.m4 @@ -1,13 +1,36 @@ dnl MANIFEST.in is generated from this file by SAGE_ROOT/bootstrap via m4. +prune sage -dnl Include all from sagemath-objects (via m4 include) -include(`../sagemath_objects/src/MANIFEST.in') +include VERSION.txt -# Extra in sagemath-categories: global-include all__sagemath_categories.py graft sage/categories +# Exclude what is already shipped in sagemath-objects +exclude sage/categories/action.* +exclude sage/categories/algebra_functor.* +exclude sage/categories/basic.* +exclude sage/categories/cartesian_product.* +exclude sage/categories/category*.* +exclude sage/categories/covariant_functorial_construction.* +exclude sage/categories/functor.* +exclude sage/categories/homset.* +exclude sage/categories/homsets.* +exclude sage/categories/map.* +exclude sage/categories/morphism.* +exclude sage/categories/isomorphic_objects.* +exclude sage/categories/objects.* +exclude sage/categories/primer.* +exclude sage/categories/pushout.* +exclude sage/categories/quotients.* +exclude sage/categories/realizations.* +exclude sage/categories/sets_cat.* +exclude sage/categories/sets_with_partial_maps.* +exclude sage/categories/subobjects.* +exclude sage/categories/subquotients.* +exclude sage/categories/with_realizations.* +# Exclude to make it a namespace package exclude sage/categories/__init__.py -include sage/misc/prandom.* # dep of sage/rings/ring + include sage/rings/ideal.* include sage/rings/ring.* graft sage/typeset # dep of sage.categories.tensor @@ -17,10 +40,12 @@ graft sage/typeset # dep of sage.categories.tensor global-exclude *.c global-exclude *.cpp -include sage/cpython/debugimpl.c -include sage/misc/inherit_comparison_impl.c global-exclude __pycache__ global-exclude *.py[co] global-exclude *.bak global-exclude *.so +global-exclude *~ +prune .tox +prune build +prune dist diff --git a/pkgs/sagemath-categories/README.rst b/pkgs/sagemath-categories/README.rst index 81311e5a217..d1f90fea966 100644 --- a/pkgs/sagemath-categories/README.rst +++ b/pkgs/sagemath-categories/README.rst @@ -12,15 +12,27 @@ About SageMath https://www.sagemath.org -SageMath fully supports all major Linux distributions, recent versions of macOS, and Windows (using Cygwin or Windows Subsystem for Linux). +SageMath fully supports all major Linux distributions, recent versions of +macOS, and Windows (using Cygwin or Windows Subsystem for Linux). -The traditional and recommended way to install SageMath is from source via Sage-the-distribution (https://www.sagemath.org/download-source.html). Sage-the-distribution first builds a large number of open source packages from source (unless it finds suitable versions installed in the system) and then installs the Sage Library (sagelib, implemented in Python and Cython). +The traditional and recommended way to install SageMath is from source via +Sage-the-distribution (https://www.sagemath.org/download-source.html). +Sage-the-distribution first builds a large number of open source packages from +source (unless it finds suitable versions installed in the system) and then +installs the Sage Library (sagelib, implemented in Python and Cython). About this experimental pip-installable source distribution ----------------------------------------------------------- -This pip-installable source distribution `sagemath-categories` is an experimental distribution of a small part of the Sage Library. Use at your own risk. It provides a small subset of the modules of the Sage library ("sagelib", `sagemath-standard`). It is a superset of the `sagemath-objects` (providing Sage objects, the element/parent framework, categories, the coercion system and the related metaclasses), making various additional categories available without introducing dependencies on additional mathematical libraries. +This pip-installable source distribution `sagemath-categories` is an +experimental distribution of a small part of the Sage Library. Use at your own +risk. It provides a small subset of the modules of the Sage library +("sagelib", `sagemath-standard`). It is a superset of the `sagemath-objects` +(providing Sage objects, the element/parent framework, categories, the coercion +system and the related metaclasses), making various additional categories +available without introducing dependencies on additional mathematical +libraries. Dependencies diff --git a/pkgs/sagemath-categories/VERSION.txt b/pkgs/sagemath-categories/VERSION.txt index 37d25ab1334..737bfe2c1d2 100644 --- a/pkgs/sagemath-categories/VERSION.txt +++ b/pkgs/sagemath-categories/VERSION.txt @@ -1 +1 @@ -9.7.rc1 +9.8.beta1 diff --git a/pkgs/sagemath-categories/bin b/pkgs/sagemath-categories/bin deleted file mode 120000 index 1956438021b..00000000000 --- a/pkgs/sagemath-categories/bin +++ /dev/null @@ -1 +0,0 @@ -../sagemath-objects/bin \ No newline at end of file diff --git a/pkgs/sagemath-categories/pyproject.toml.m4 b/pkgs/sagemath-categories/pyproject.toml.m4 deleted file mode 120000 index b65c0df8a46..00000000000 --- a/pkgs/sagemath-categories/pyproject.toml.m4 +++ /dev/null @@ -1 +0,0 @@ -../sagemath-objects/pyproject.toml.m4 \ No newline at end of file diff --git a/pkgs/sagemath-categories/pyproject.toml.m4 b/pkgs/sagemath-categories/pyproject.toml.m4 new file mode 100644 index 00000000000..0afd7849de5 --- /dev/null +++ b/pkgs/sagemath-categories/pyproject.toml.m4 @@ -0,0 +1,14 @@ +[build-system] +# Minimum requirements for the build system to execute. +requires = [ + esyscmd(`sage-get-system-packages install-requires-toml \ + setuptools \ + wheel \ + sage_setup \ + sagemath_environment \ + sagemath_objects \ + cython \ + gmpy2 \ + cysignals \ + ')] +build-backend = "setuptools.build_meta" diff --git a/pkgs/sagemath-categories/sage_setup b/pkgs/sagemath-categories/sage_setup deleted file mode 120000 index 88b8133df49..00000000000 --- a/pkgs/sagemath-categories/sage_setup +++ /dev/null @@ -1 +0,0 @@ -../../src/sage_setup \ No newline at end of file diff --git a/pkgs/sagemath-categories/setup.cfg.m4 b/pkgs/sagemath-categories/setup.cfg.m4 index 4ba67f86fdb..744022da6eb 100644 --- a/pkgs/sagemath-categories/setup.cfg.m4 +++ b/pkgs/sagemath-categories/setup.cfg.m4 @@ -28,21 +28,8 @@ classifiers = python_requires = >=3.8, <3.11 install_requires = esyscmd(`sage-get-system-packages install-requires \ - cython \ - pkgconfig \ - ipython \ - gmpy2 \ - cysignals \ + sagemath_objects \ | sed "2,\$s/^/ /;"')dnl -scripts = - bin/sage - bin/sage-env - bin/sage-eval - bin/sage-fixdoctests - bin/sage-ipython - bin/sage-python - bin/sage-run - bin/sage-runtests - bin/sage-venv-config - bin/sage-version.sh +[options.extras_require] +test = sagemath-repl diff --git a/pkgs/sagemath-categories/tox.ini b/pkgs/sagemath-categories/tox.ini index 7ac63bae0ec..44ca511ac22 100644 --- a/pkgs/sagemath-categories/tox.ini +++ b/pkgs/sagemath-categories/tox.ini @@ -13,17 +13,31 @@ envlist = [testenv] deps = !norequirements: -rrequirements.txt + # tox 3.x does not handle extras when using --installpkg. https://github.com/tox-dev/tox/issues/1576 + sagemath-repl -setenv = - # Sage scripts such as sage-runtests like to use $HOME/.sage - HOME={envdir} +extras = test passenv = + # Variables set by .homebrew-build-env + CPATH + LIBRARY_PATH + PKG_CONFIG_PATH # Parallel build SAGE_NUM_THREADS SAGE_NUM_THREADS_PARALLEL - # SAGE_VENV only for referring to the basepython - sagepython: SAGE_VENV + # SAGE_VENV only for referring to the basepython or finding the wheels + sagepython, sagewheels: SAGE_VENV + # Location of the wheels + sagewheels: SAGE_SPKG_WHEELS + +setenv = + # Sage scripts such as sage-runtests like to use $HOME/.sage + HOME={envdir} + # We supply pip options by environment variables so that they + # apply both to the installation of the dependencies and of the package + sagewheels: PIP_FIND_LINKS=file://{env:SAGE_SPKG_WHEELS:{env:SAGE_VENV:{toxinidir}/../../../../venv}/var/lib/sage/wheels} + nopypi: PIP_NO_INDEX=true whitelist_externals = bash @@ -35,10 +49,19 @@ commands = # Test that importing sage.categories.all initializes categories {envpython} -c 'import sys; "" in sys.path and sys.path.remove(""); from sage.categories.all import *; SimplicialComplexes(); FunctionFields()' - bash -c 'cd bin && SAGE_SRC=$(python -c "from sage.env import SAGE_SRC; print(SAGE_SRC)") && sage-runtests --environment=sage.all__sagemath_categories --optional=sage $SAGE_SRC/sage/structure || echo "(lots of doctest failures are expected)"' + bash -c 'cd {temp_dir} && SAGE_SRC=$(python -c "from sage.env import SAGE_SRC; print(SAGE_SRC)") && sage-runtests --initial --environment=sage.all__sagemath_categories --optional=sage $SAGE_SRC/sage/structure || echo "(lots of doctest failures are expected)"' [testenv:sagepython] basepython = {env:SAGE_VENV}/bin/python3 +[testenv:sagepython-sagewheels-nopypi] +basepython = {env:SAGE_VENV}/bin/python3 + +[testenv:sagepython-sagewheels-nopypi-norequirements] +basepython = {env:SAGE_VENV}/bin/python3 + +[testenv:sagepython-sagewheels] +basepython = {env:SAGE_VENV}/bin/python3 + [testenv:sagepython-norequirements] basepython = {env:SAGE_VENV}/bin/python3 diff --git a/pkgs/sagemath-environment/README.rst b/pkgs/sagemath-environment/README.rst index f5c52660ca8..ba5905777c0 100644 --- a/pkgs/sagemath-environment/README.rst +++ b/pkgs/sagemath-environment/README.rst @@ -8,16 +8,27 @@ About SageMath "Creating a Viable Open Source Alternative to Magma, Maple, Mathematica, and MATLAB" - Copyright (C) 2005-2020 The Sage Development Team + Copyright (C) 2005-2022 The Sage Development Team https://www.sagemath.org -SageMath fully supports all major Linux distributions, recent versions of macOS, and Windows (using Cygwin or Windows Subsystem for Linux). +SageMath fully supports all major Linux distributions, recent versions of +macOS, and Windows (using Cygwin or Windows Subsystem for Linux). -The traditional and recommended way to install SageMath is from source via Sage-the-distribution (https://www.sagemath.org/download-source.html). Sage-the-distribution first builds a large number of open source packages from source (unless it finds suitable versions installed in the system) and then installs the Sage Library (sagelib, implemented in Python and Cython). +The traditional and recommended way to install SageMath is from source via +Sage-the-distribution (https://www.sagemath.org/download-source.html). +Sage-the-distribution first builds a large number of open source packages from +source (unless it finds suitable versions installed in the system) and then +installs the Sage Library (sagelib, implemented in Python and Cython). About this experimental pip-installable source distribution ----------------------------------------------------------- -This pip-installable source distribution `sagemath-environment` is an experimental distribution of a small part of the Sage Library. Use at your own risk. It provides a small, fundamental subset of the modules of the Sage library ("sagelib", `sagemath-standard`), providing the connection to the system and software environment. +This pip-installable source distribution `sagemath-environment` is an +experimental distribution of a small part of the Sage Library. Use at your own +risk. It provides a small, fundamental subset of the modules of the Sage +library ("sagelib", `sagemath-standard`), providing the connection to the +system and software environment. It also includes the `sage` script for +launching the Sage REPL and accessing various developer tools (see `sage +--help`). diff --git a/pkgs/sagemath-environment/VERSION.txt b/pkgs/sagemath-environment/VERSION.txt index 37d25ab1334..737bfe2c1d2 100644 --- a/pkgs/sagemath-environment/VERSION.txt +++ b/pkgs/sagemath-environment/VERSION.txt @@ -1 +1 @@ -9.7.rc1 +9.8.beta1 diff --git a/pkgs/sagemath-environment/tox.ini b/pkgs/sagemath-environment/tox.ini index 890ffdf0015..f4875e6228c 100644 --- a/pkgs/sagemath-environment/tox.ini +++ b/pkgs/sagemath-environment/tox.ini @@ -16,16 +16,22 @@ isolated_build = True deps = !norequirements: -rrequirements.txt -setenv = - # Sage scripts such as sage-runtests like to use $HOME/.sage - HOME={envdir} - passenv = # Parallel build SAGE_NUM_THREADS SAGE_NUM_THREADS_PARALLEL - # SAGE_VENV only for referring to the basepython - sagepython: SAGE_VENV + # SAGE_VENV only for referring to the basepython or finding the wheels + sagepython, sagewheels: SAGE_VENV + # Location of the wheels + sagewheels: SAGE_SPKG_WHEELS + +setenv = + # Sage scripts such as sage-runtests like to use $HOME/.sage + HOME={envdir} + # We supply pip options by environment variables so that they + # apply both to the installation of the dependencies and of the package + sagewheels: PIP_FIND_LINKS=file://{env:SAGE_SPKG_WHEELS:{env:SAGE_VENV:{toxinidir}/../../../../venv}/var/lib/sage/wheels} + nopypi: PIP_NO_INDEX=true whitelist_externals = bash @@ -37,5 +43,14 @@ commands = [testenv:sagepython] basepython = {env:SAGE_VENV}/bin/python3 +[testenv:sagepython-sagewheels-nopypi] +basepython = {env:SAGE_VENV}/bin/python3 + +[testenv:sagepython-sagewheels-nopypi-norequirements] +basepython = {env:SAGE_VENV}/bin/python3 + +[testenv:sagepython-sagewheels] +basepython = {env:SAGE_VENV}/bin/python3 + [testenv:sagepython-norequirements] basepython = {env:SAGE_VENV}/bin/python3 diff --git a/pkgs/sagemath-objects/MANIFEST.in b/pkgs/sagemath-objects/MANIFEST.in index d112d9b5c16..ffa9e9c7f10 100644 --- a/pkgs/sagemath-objects/MANIFEST.in +++ b/pkgs/sagemath-objects/MANIFEST.in @@ -1,15 +1,10 @@ prune sage graft sage/cpython -graft sage_setup # FIXME: Vendor it until we have made a new sage_setup release -include sage/env.py # FIXME: sage_setup must be changed so it does not depend on it -include sage/version.py # FIXME: likewise -include sage/misc/package_dir.p* # For sage_setup include VERSION.txt global-include all__sagemath_objects.py -graft sage/features graft sage/structure include sage/categories/action.* include sage/categories/algebra_functor.* @@ -67,6 +62,9 @@ include sage/misc/instancedoc.* # dep of sage/misc/lazy_import include sage/misc/persist.* include sage/misc/sage_unittest.* # dep of sage/misc/persist +include sage/misc/randstate.* # used in sage.doctest +include sage/misc/prandom.* # dep of sage/rings/ring + include sage/ext/stdsage.pxd include sage/sets/pythonclass.* include sage/arith/power.* @@ -82,28 +80,9 @@ graft sage/libs/gmp # sage/misc/latex -- this should really go to another package -## For doctesting -- this duplication will be removed in #28925 -global-include all__sagemath_environment.py -global-include all__sagemath_repl.py -include bin/sage -include bin/sage-env -include bin/sage-env-config -include bin/sage-python -include bin/sage-runtests -graft sage/doctest -include sage/misc/temporary_file.* -include sage/misc/randstate.* -include sage/misc/misc.* # walltime, cputime -# graft sage/features -include sage/misc/package.* -include sage/misc/sagedoc.py -include sage/misc/banner.py -include sage/misc/sage_input.py -include sage/misc/sage_eval.py -include sage/misc/viewer.py - -graft sage/repl -graft sage/server +## FIXME: Needed for doctesting +include sage/misc/misc.* # walltime, cputime used in sage.doctest + global-exclude *.c global-exclude *.cpp @@ -114,3 +93,7 @@ global-exclude __pycache__ global-exclude *.py[co] global-exclude *.bak global-exclude *.so +global-exclude *~ +prune .tox +prune build +prune dist diff --git a/pkgs/sagemath-objects/README.rst b/pkgs/sagemath-objects/README.rst index 8058f633654..9dc9cfd888f 100644 --- a/pkgs/sagemath-objects/README.rst +++ b/pkgs/sagemath-objects/README.rst @@ -12,15 +12,25 @@ About SageMath https://www.sagemath.org -SageMath fully supports all major Linux distributions, recent versions of macOS, and Windows (using Cygwin or Windows Subsystem for Linux). +SageMath fully supports all major Linux distributions, recent versions of +macOS, and Windows (using Cygwin or Windows Subsystem for Linux). -The traditional and recommended way to install SageMath is from source via Sage-the-distribution (https://www.sagemath.org/download-source.html). Sage-the-distribution first builds a large number of open source packages from source (unless it finds suitable versions installed in the system) and then installs the Sage Library (sagelib, implemented in Python and Cython). +The traditional and recommended way to install SageMath is from source via +Sage-the-distribution (https://www.sagemath.org/download-source.html). +Sage-the-distribution first builds a large number of open source packages from +source (unless it finds suitable versions installed in the system) and then +installs the Sage Library (sagelib, implemented in Python and Cython). About this experimental pip-installable source distribution ----------------------------------------------------------- -This pip-installable source distribution `sagemath-objects` is an experimental distribution of a small part of the Sage Library. Use at your own risk. It provides a small, fundamental subset of the modules of the Sage library ("sagelib", `sagemath-standard`), making Sage objects, the element/parent framework, categories, the coercion system and the related metaclasses available. +This pip-installable source distribution `sagemath-objects` is an experimental +distribution of a small part of the Sage Library. Use at your own risk. It +provides a small, fundamental subset of the modules of the Sage library +("sagelib", `sagemath-standard`), making Sage objects, the element/parent +framework, categories, the coercion system and the related metaclasses +available. Dependencies diff --git a/pkgs/sagemath-objects/VERSION.txt b/pkgs/sagemath-objects/VERSION.txt index 37d25ab1334..737bfe2c1d2 100644 --- a/pkgs/sagemath-objects/VERSION.txt +++ b/pkgs/sagemath-objects/VERSION.txt @@ -1 +1 @@ -9.7.rc1 +9.8.beta1 diff --git a/pkgs/sagemath-objects/bin/sage b/pkgs/sagemath-objects/bin/sage deleted file mode 120000 index 028392920ce..00000000000 --- a/pkgs/sagemath-objects/bin/sage +++ /dev/null @@ -1 +0,0 @@ -../../../src/bin/sage \ No newline at end of file diff --git a/pkgs/sagemath-objects/bin/sage-env b/pkgs/sagemath-objects/bin/sage-env deleted file mode 120000 index e35bf8a4000..00000000000 --- a/pkgs/sagemath-objects/bin/sage-env +++ /dev/null @@ -1 +0,0 @@ -../../../src/bin/sage-env \ No newline at end of file diff --git a/pkgs/sagemath-objects/bin/sage-eval b/pkgs/sagemath-objects/bin/sage-eval deleted file mode 120000 index ac096cd38f1..00000000000 --- a/pkgs/sagemath-objects/bin/sage-eval +++ /dev/null @@ -1 +0,0 @@ -../../../src/bin/sage-eval \ No newline at end of file diff --git a/pkgs/sagemath-objects/bin/sage-fixdoctests b/pkgs/sagemath-objects/bin/sage-fixdoctests deleted file mode 120000 index 3d394f77c44..00000000000 --- a/pkgs/sagemath-objects/bin/sage-fixdoctests +++ /dev/null @@ -1 +0,0 @@ -../../../src/bin/sage-fixdoctests \ No newline at end of file diff --git a/pkgs/sagemath-objects/bin/sage-ipython b/pkgs/sagemath-objects/bin/sage-ipython deleted file mode 120000 index 00b3d2b0c50..00000000000 --- a/pkgs/sagemath-objects/bin/sage-ipython +++ /dev/null @@ -1 +0,0 @@ -../../../src/bin/sage-ipython \ No newline at end of file diff --git a/pkgs/sagemath-objects/bin/sage-python b/pkgs/sagemath-objects/bin/sage-python deleted file mode 120000 index 26d8bde2c71..00000000000 --- a/pkgs/sagemath-objects/bin/sage-python +++ /dev/null @@ -1 +0,0 @@ -../../../src/bin/sage-python \ No newline at end of file diff --git a/pkgs/sagemath-objects/bin/sage-run b/pkgs/sagemath-objects/bin/sage-run deleted file mode 120000 index a2bce45f085..00000000000 --- a/pkgs/sagemath-objects/bin/sage-run +++ /dev/null @@ -1 +0,0 @@ -../../../src/bin/sage-run \ No newline at end of file diff --git a/pkgs/sagemath-objects/bin/sage-runtests b/pkgs/sagemath-objects/bin/sage-runtests deleted file mode 120000 index 8e3dbbaf100..00000000000 --- a/pkgs/sagemath-objects/bin/sage-runtests +++ /dev/null @@ -1 +0,0 @@ -../../../src/bin/sage-runtests \ No newline at end of file diff --git a/pkgs/sagemath-objects/bin/sage-venv-config b/pkgs/sagemath-objects/bin/sage-venv-config deleted file mode 120000 index d1e0d8ec19b..00000000000 --- a/pkgs/sagemath-objects/bin/sage-venv-config +++ /dev/null @@ -1 +0,0 @@ -../../../src/bin/sage-venv-config \ No newline at end of file diff --git a/pkgs/sagemath-objects/bin/sage-version.sh b/pkgs/sagemath-objects/bin/sage-version.sh deleted file mode 120000 index 46cfd0287a5..00000000000 --- a/pkgs/sagemath-objects/bin/sage-version.sh +++ /dev/null @@ -1 +0,0 @@ -../../../src/bin/sage-version.sh \ No newline at end of file diff --git a/pkgs/sagemath-objects/pyproject.toml.m4 b/pkgs/sagemath-objects/pyproject.toml.m4 index 0a0149b9e45..c13665be51d 100644 --- a/pkgs/sagemath-objects/pyproject.toml.m4 +++ b/pkgs/sagemath-objects/pyproject.toml.m4 @@ -5,6 +5,7 @@ requires = [ setuptools \ wheel \ sage_setup \ + sagemath_environment \ cython \ gmpy2 \ cysignals \ diff --git a/pkgs/sagemath-objects/sage_setup b/pkgs/sagemath-objects/sage_setup deleted file mode 120000 index 88b8133df49..00000000000 --- a/pkgs/sagemath-objects/sage_setup +++ /dev/null @@ -1 +0,0 @@ -../../src/sage_setup \ No newline at end of file diff --git a/pkgs/sagemath-objects/setup.cfg.m4 b/pkgs/sagemath-objects/setup.cfg.m4 index 00c65ca9f8a..cd5d4f72a9e 100644 --- a/pkgs/sagemath-objects/setup.cfg.m4 +++ b/pkgs/sagemath-objects/setup.cfg.m4 @@ -28,21 +28,22 @@ classifiers = python_requires = >=3.8, <3.11 install_requires = esyscmd(`sage-get-system-packages install-requires \ - cython \ - pkgconfig \ - ipython \ gmpy2 \ cysignals \ | sed "2,\$s/^/ /;"')dnl -scripts = - bin/sage - bin/sage-env - bin/sage-eval - bin/sage-fixdoctests - bin/sage-ipython - bin/sage-python - bin/sage-run - bin/sage-runtests - bin/sage-venv-config - bin/sage-version.sh +[options.extras_require] +# Currently we do not use the sage doctester to test sagemath-objects, +# so we do not list sagemath-repl here. +test = + + +[options.package_data] +sage.cpython = + pyx_visit.h + string_impl.h + cython_metaclass.h + python_debug.h + +sage.rings = + integer_fake.h diff --git a/pkgs/sagemath-objects/tox.ini b/pkgs/sagemath-objects/tox.ini index e03cbab1c3c..babc128ff0c 100644 --- a/pkgs/sagemath-objects/tox.ini +++ b/pkgs/sagemath-objects/tox.ini @@ -14,16 +14,28 @@ envlist = deps = !norequirements: -rrequirements.txt -setenv = - # Sage scripts such as sage-runtests like to use $HOME/.sage - HOME={envdir} +extras = test passenv = + # Variables set by .homebrew-build-env + CPATH + LIBRARY_PATH + PKG_CONFIG_PATH # Parallel build SAGE_NUM_THREADS SAGE_NUM_THREADS_PARALLEL - # SAGE_VENV only for referring to the basepython - sagepython: SAGE_VENV + # SAGE_VENV only for referring to the basepython or finding the wheels + sagepython, sagewheels: SAGE_VENV + # Location of the wheels + sagewheels: SAGE_SPKG_WHEELS + +setenv = + # Sage scripts such as sage-runtests like to use $HOME/.sage + HOME={envdir} + # We supply pip options by environment variables so that they + # apply both to the installation of the dependencies and of the package + sagewheels: PIP_FIND_LINKS=file://{env:SAGE_SPKG_WHEELS:{env:SAGE_VENV:{toxinidir}/../../../../venv}/var/lib/sage/wheels} + nopypi: PIP_NO_INDEX=true whitelist_externals = bash @@ -34,10 +46,19 @@ commands = {envpython} -c 'import sys; "" in sys.path and sys.path.remove(""); from sage.all__sagemath_objects import *' - #bash -c 'cd bin && SAGE_SRC=$(python -c "from sage.env import SAGE_SRC; print(SAGE_SRC)") && sage-runtests --environment=sage.all__sagemath_objects --optional=sage $SAGE_SRC/sage/structure || echo "(lots of doctest failures are expected)"' + #bash -c 'cd {temp_dir} && SAGE_SRC=$(python -c "from sage.env import SAGE_SRC; print(SAGE_SRC)") && sage-runtests --environment=sage.all__sagemath_objects --initial --optional=sage $SAGE_SRC/sage/structure || echo "(lots of doctest failures are expected)"' [testenv:sagepython] basepython = {env:SAGE_VENV}/bin/python3 +[testenv:sagepython-sagewheels-nopypi] +basepython = {env:SAGE_VENV}/bin/python3 + +[testenv:sagepython-sagewheels-nopypi-norequirements] +basepython = {env:SAGE_VENV}/bin/python3 + +[testenv:sagepython-sagewheels] +basepython = {env:SAGE_VENV}/bin/python3 + [testenv:sagepython-norequirements] basepython = {env:SAGE_VENV}/bin/python3 diff --git a/pkgs/sagemath-repl/MANIFEST.in b/pkgs/sagemath-repl/MANIFEST.in index 9bee69fd999..c54f63b15ee 100644 --- a/pkgs/sagemath-repl/MANIFEST.in +++ b/pkgs/sagemath-repl/MANIFEST.in @@ -11,4 +11,11 @@ include sage/misc/sage_eval.py include VERSION.txt +global-exclude __pycache__ global-exclude *.py[co] +global-exclude *.bak +global-exclude *.so +global-exclude *~ +prune .tox +prune build +prune dist diff --git a/pkgs/sagemath-repl/README.rst b/pkgs/sagemath-repl/README.rst index c240fbc1bac..3dde4aae5e5 100644 --- a/pkgs/sagemath-repl/README.rst +++ b/pkgs/sagemath-repl/README.rst @@ -8,16 +8,25 @@ About SageMath "Creating a Viable Open Source Alternative to Magma, Maple, Mathematica, and MATLAB" - Copyright (C) 2005-2020 The Sage Development Team + Copyright (C) 2005-2022 The Sage Development Team https://www.sagemath.org -SageMath fully supports all major Linux distributions, recent versions of macOS, and Windows (using Cygwin or Windows Subsystem for Linux). +SageMath fully supports all major Linux distributions, recent versions of +macOS, and Windows (using Cygwin or Windows Subsystem for Linux). -The traditional and recommended way to install SageMath is from source via Sage-the-distribution (https://www.sagemath.org/download-source.html). Sage-the-distribution first builds a large number of open source packages from source (unless it finds suitable versions installed in the system) and then installs the Sage Library (sagelib, implemented in Python and Cython). +The traditional and recommended way to install SageMath is from source via +Sage-the-distribution (https://www.sagemath.org/download-source.html). +Sage-the-distribution first builds a large number of open source packages from +source (unless it finds suitable versions installed in the system) and then +installs the Sage Library (sagelib, implemented in Python and Cython). About this experimental pip-installable source distribution ----------------------------------------------------------- -This pip-installable source distribution `sagemath-repl` is an experimental distribution of a small part of the Sage Library. Use at your own risk. It provides a small, fundamental subset of the modules of the Sage library ("sagelib", `sagemath-standard`), providing the IPython kernel, Sage preparser, and doctester. +This pip-installable source distribution `sagemath-repl` is an experimental +distribution of a small part of the Sage Library. Use at your own risk. It +provides a small, fundamental subset of the modules of the Sage library +("sagelib", `sagemath-standard`), providing the IPython kernel, Sage preparser, +and doctester. diff --git a/pkgs/sagemath-repl/VERSION.txt b/pkgs/sagemath-repl/VERSION.txt index 37d25ab1334..737bfe2c1d2 100644 --- a/pkgs/sagemath-repl/VERSION.txt +++ b/pkgs/sagemath-repl/VERSION.txt @@ -1 +1 @@ -9.7.rc1 +9.8.beta1 diff --git a/pkgs/sagemath-repl/setup.cfg.m4 b/pkgs/sagemath-repl/setup.cfg.m4 index f8757ee66cb..96515e21499 100644 --- a/pkgs/sagemath-repl/setup.cfg.m4 +++ b/pkgs/sagemath-repl/setup.cfg.m4 @@ -18,14 +18,14 @@ classifiers = Operating System :: POSIX Operating System :: MacOS :: MacOS X Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 Programming Language :: Python :: Implementation :: CPython Topic :: Scientific/Engineering :: Mathematics [options] -python_requires = >=3.7, <3.11 +python_requires = >=3.8, <3.11 install_requires = esyscmd(`sage-get-system-packages install-requires \ sagemath_objects \ diff --git a/pkgs/sagemath-repl/tox.ini b/pkgs/sagemath-repl/tox.ini index a7e321104cd..b564bcda707 100644 --- a/pkgs/sagemath-repl/tox.ini +++ b/pkgs/sagemath-repl/tox.ini @@ -16,16 +16,26 @@ isolated_build = True deps = !norequirements: -rrequirements.txt -setenv = - # Sage scripts such as sage-runtests like to use $HOME/.sage - HOME={envdir} - passenv = + # Variables set by .homebrew-build-env + CPATH + LIBRARY_PATH + PKG_CONFIG_PATH # Parallel build SAGE_NUM_THREADS SAGE_NUM_THREADS_PARALLEL - # SAGE_VENV only for referring to the basepython - sagepython: SAGE_VENV + # SAGE_VENV only for referring to the basepython or finding the wheels + sagepython, sagewheels: SAGE_VENV + # Location of the wheels + sagewheels: SAGE_SPKG_WHEELS + +setenv = + # Sage scripts such as sage-runtests like to use $HOME/.sage + HOME={envdir} + # We supply pip options by environment variables so that they + # apply both to the installation of the dependencies and of the package + sagewheels: PIP_FIND_LINKS=file://{env:SAGE_SPKG_WHEELS:{env:SAGE_VENV:{toxinidir}/../../../../venv}/var/lib/sage/wheels} + nopypi: PIP_NO_INDEX=true whitelist_externals = bash @@ -34,10 +44,19 @@ commands = # Beware of the treacherous non-src layout. "./sage/" shadows the installed sage package. {envpython} -c 'import sys; "" in sys.path and sys.path.remove(""); import sage.repl.all; import sage.doctest.all' - bash -c 'cd bin && SAGE_SRC=$({envpython} -c "from sage.env import SAGE_SRC; print(SAGE_SRC)") && sage-runtests --environment=sage.all__sagemath_repl --optional=sage $SAGE_SRC/sage/repl $SAGE_SRC/sage/doctest $SAGE_SRC/sage/misc/sage_input.py $SAGE_SRC/sage/misc/sage_eval.py || echo "(lots of doctest failures are expected)"' + bash -c 'cd bin && SAGE_SRC=$({envpython} -c "from sage.env import SAGE_SRC; print(SAGE_SRC)") && sage-runtests --environment=sage.all__sagemath_repl --initial --optional=sage $SAGE_SRC/sage/repl $SAGE_SRC/sage/doctest $SAGE_SRC/sage/misc/sage_input.py $SAGE_SRC/sage/misc/sage_eval.py || echo "(lots of doctest failures are expected)"' [testenv:sagepython] basepython = {env:SAGE_VENV}/bin/python3 +[testenv:sagepython-sagewheels-nopypi] +basepython = {env:SAGE_VENV}/bin/python3 + +[testenv:sagepython-sagewheels-nopypi-norequirements] +basepython = {env:SAGE_VENV}/bin/python3 + +[testenv:sagepython-sagewheels] +basepython = {env:SAGE_VENV}/bin/python3 + [testenv:sagepython-norequirements] basepython = {env:SAGE_VENV}/bin/python3 diff --git a/pkgs/sagemath-standard/tox.ini b/pkgs/sagemath-standard/tox.ini index 75e72ae32bd..f021a7d2b22 100644 --- a/pkgs/sagemath-standard/tox.ini +++ b/pkgs/sagemath-standard/tox.ini @@ -84,8 +84,7 @@ passenv = SAGE_NUM_THREADS # SAGE_VENV only for referring to the basepython or finding the wheels sagepython, sagewheels: SAGE_VENV - # Location of the wheels (needs to include a PEP 503 compliant - # Simple Repository index, i.e., a subdirectory "simple") + # Location of the wheels sagewheels: SAGE_SPKG_WHEELS setenv = diff --git a/src/VERSION.txt b/src/VERSION.txt index 37d25ab1334..737bfe2c1d2 100644 --- a/src/VERSION.txt +++ b/src/VERSION.txt @@ -1 +1 @@ -9.7.rc1 +9.8.beta1 diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index a18cc712d08..4f92b35ee39 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.rc1' -SAGE_RELEASE_DATE='2022-09-07' -SAGE_VERSION_BANNER='SageMath version 9.7.rc1, Release Date: 2022-09-07' +SAGE_VERSION='9.8.beta1' +SAGE_RELEASE_DATE='2022-09-29' +SAGE_VERSION_BANNER='SageMath version 9.8.beta1, Release Date: 2022-09-29' diff --git a/src/doc/en/developer/git_trac.rst b/src/doc/en/developer/git_trac.rst index 580c791ce60..25977e5ef23 100644 --- a/src/doc/en/developer/git_trac.rst +++ b/src/doc/en/developer/git_trac.rst @@ -17,6 +17,8 @@ perform every development task with just git and a web browser. Installing the Git-Trac Command =============================== +:: + [user@localhost]$ git clone https://github.com/sagemath/git-trac-command.git Cloning into 'git-trac-command'... [...] diff --git a/src/doc/en/developer/packaging.rst b/src/doc/en/developer/packaging.rst index 33ca56415f8..8f084b38ff6 100644 --- a/src/doc/en/developer/packaging.rst +++ b/src/doc/en/developer/packaging.rst @@ -1106,7 +1106,7 @@ set the ``upstream_url`` field in ``checksums.ini`` described above. For Python packages available from PyPI, you can use:: - [user@localhost]$ sage -package create scikit_spatial --pypi --type optional + [user@localhost]$ sage --package create scikit_spatial --pypi --type optional This automatically downloads the most recent version from PyPI and also obtains most of the necessary information by querying PyPI. @@ -1117,7 +1117,11 @@ in the file ``install-requires.txt``. To create a pip package rather than a normal package, you can use:: - [user@localhost]$ sage -package create scikit_spatial --pypi --source pip --type optional + [user@localhost]$ sage --package create scikit_spatial --pypi --source pip --type optional + +To create a wheel package rather than a normal package, you can use:: + + [user@localhost]$ sage --package create scikit_spatial --pypi --source wheel --type optional .. _section-manual-build: diff --git a/src/doc/en/developer/packaging_sage_library.rst b/src/doc/en/developer/packaging_sage_library.rst index 536e1da5913..c792b8ee68e 100644 --- a/src/doc/en/developer/packaging_sage_library.rst +++ b/src/doc/en/developer/packaging_sage_library.rst @@ -487,26 +487,49 @@ Hierarchy of distribution packages def node(label, pos): return text(label, (3*pos[0],2*pos[1]), background_color='pink', color='black') def edge(start, end, **kwds): - return arrow((3*start[0],2*start[1]+.5),(3*end[0],2*end[1]-.5), arrowsize=2, **kwds) + return arrow((3*start[0],2*start[1]),(3*end[0],2*end[1]-.28), arrowsize=2, **kwds) def extras_require(start, end): return edge(start, end, linestyle='dashed') g = Graphics() - g += (node("sage_conf", (0.5,0)) + extras_require((0.5,0),(0.5,1))) - g += (node("sagemath-objects", (1.5,0)) + edge((1.5,0),(1.5,1))) - g += (node("sagemath-environment", (0.5,1)) - + edge((0.5,1),(0,2)) + edge((0.5,1),(1,2)) + edge((0.5,1),(2,2))) - g += (node("sagemath-categories", (1.5,1)) + edge((1.5,1),(0,2)) + - edge((1.5,1),(1,2)) + edge((1.5,1),(2,2))) - g += (node("sagemath-graphs", (0,2)) + node("sagemath-polyhedra", (1,2)) + node("sagemath-singular", (2,2)) + - edge((0,2),(0,3)) + edge((0,2),(1,3)) + edge((1,2),(1,3)) + edge((2,2),(2,3))) - g += (node("sagemath-tdlib", (0,3)) + node("sagemath-standard-no-symbolics", (1,3)) + node("sagemath-symbolics", (2,3)) + - edge((1,3),(1,4)) + edge((2,3),(1,4))) + g += (extras_require((0.5,0),(0.5,1)) + node("sage_conf", (0.5,0))) + g += (edge((1.5,0),(0.75,2)) + edge((1.5,0),(1.5,1)) + + node("sagemath-objects", (1.5,0))) + g += (edge((0.5,1),(0,2)) + edge((0.5,1),(0.6,2)) + edge((0.5,1),(1.25,2)) + edge((0.5,1),(1.8,2)) + + node("sagemath-environment", (0.5,1))) + g += (edge((1.5,1),(0.2,2)) + edge((1.5,1),(1.41,2)) + edge((1.5,1),(2,2)) + + node("sagemath-categories", (1.5,1))) + g += (edge((0,2),(0,3)) + edge((0,2),(0.75,3)) + edge((0.67,2),(1,3)) + edge((1.33,2),(1.25,3)) + edge((2,2),(2,3)) + + node("sagemath-graphs", (0,2)) + node("sagemath-repl", (0.67,2)) + node("sagemath-polyhedra", (1.33,2)) + node("sagemath-singular", (2,2))) + g += (edge((1,3),(1,4)) + edge((2,3),(1.2,4)) + + node("sagemath-tdlib", (0,3)) + node("sagemath-standard-no-symbolics", (1,3)) + node("sagemath-symbolics", (2,3))) g += node("sagemath-standard", (1,4)) sphinx_plot(g, figsize=(8, 4), axes=False) Solid arrows indicate ``install_requires``, i.e., a declared runtime dependency. Dashed arrows indicate ``extras_require``, i.e., a declared optional runtime dependency. +Not shown in the diagram are build dependencies and optional dependencies for testing. + +- `sage_conf `_ is a configuration + module. It provides the configuration variable settings determined by the + ``configure`` script. + +- `sagemath-environment `_ + provides the connection to the system and software environment. It includes + :mod:`sage.env`, :mod:`sage.features`, :mod:`sage.misc.package_dir`, etc. + +- `sagemath-objects `_ + provides a small fundamental subset of the modules of the Sage library, + in particular all of :mod:`sage.structure`, a small portion of :mod:`sage.categories`, + and a portion of :mod:`sage.misc`. + +- `sagemath-categories `_ + provides a small subset of the modules of the Sage library, building upon sagemath-objects. + It provides all of :mod:`sage.categories` and a small portion of :mod:`sage.rings`. + +- `sagemath-repl `_ provides + the IPython kernel and Sage preparser (:mod:`sage.repl`), + the Sage doctester (:mod:`sage.doctest`), and some related modules from :mod:`sage.misc`. Testing distribution packages diff --git a/src/doc/en/installation/source.rst b/src/doc/en/installation/source.rst index 6e834b1ab5c..879c15f4847 100644 --- a/src/doc/en/installation/source.rst +++ b/src/doc/en/installation/source.rst @@ -38,12 +38,6 @@ will not need to read them. Prerequisites ------------- -General requirements -~~~~~~~~~~~~~~~~~~~~ - -This section details the technical prerequisites needed on all platforms. See -also the `System-specific requirements`_ below. - Disk space and memory ^^^^^^^^^^^^^^^^^^^^^ @@ -51,169 +45,76 @@ Your computer comes with at least 6 GB of free disk space. It is recommended to have at least 2 GB of RAM, but you might get away with less (be sure to have some swap space in this case). -Command-line tools -^^^^^^^^^^^^^^^^^^ - -In addition to standard :wikipedia:`POSIX ` utilities -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. - 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. -- **m4**: GNU m4 1.4.2 or later (non-GNU or older versions might also work). -- **perl**: version 5.8.0 or later. -- **ar** and **ranlib**: can be obtained as part of GNU binutils. -- **tar**: GNU tar version 1.17 or later, or BSD tar. -- **python**: Python 3.4 or later, or Python 2.7. - (This range of versions is a minimal requirement for internal purposes of the SageMath - build system, which is referred to as ``sage-bootstrap-python``.) - -Other versions of these may work, but they are untested. - - -Fortran and compiler suites -########################### - -Sage installation also needs a Fortran compiler. It is determined -automatically whether Sage's GCC package, or just its part containing -Fortran compiler ``gfortran`` needs to be installed. This can be -overwritten by running ``./configure`` with option -``--without-system-gcc``. - -Officially we support -gfortran from `GNU Compiler Collection (GCC) `_. -If C and C++ compilers also come from there (i.e., gcc and g++), their versions -should match. -Alternatively, one may use C and C++ compilers from -`Clang: a C language family frontend for LLVM `_, -and thus matching versions of -clang, clang++ , along with a recent gfortran. (Flang (or other LLVM-based -Fortran compilers) are not officially supported, however it is possible to -to build Sage using flang, with some extra efforts needed to set various flags; -this is work in progress at the moment (May 2019)). - -Therefore, if you plan on using your own GCC compilers, then make sure that -their versions match. - -To force using specific compilers, set environment variables ``CC``, -``CXX``, and ``FC`` (for C, C++, and Fortran compilers, respectively) -to the desired values, and run ``./configure``. For example, -``./configure CC=clang CXX=clang++ FC=gfortran`` will configure Sage -to be built with Clang C/C++ compilers and Fortran compiler -``gfortran``. - -Alternatively, Sage includes a GCC package, so that C, C++ and Fortran -compilers will be built when the build system detects that it is needed, -e.g., non-GCC compilers, or -versions of the GCC compilers known to miscompile some components of Sage, -or simply a missing Fortran compiler. -In any case, you always need at least a C/C++ compiler to build the GCC -package and its prerequisites before the compilers it provides can be used. - -Note that you can always override this behavior through the configure -options ``--without-system-gcc`` and ``--with-system-gcc``, see -:ref:`section_compilers`. - -There are some known problems with old assemblers, in particular when -building the ``ecm`` and ``fflas_ffpack`` packages. You should ensure -that your assembler understands all instructions for your -processor. On Linux, this means you need a recent version of -``binutils``; on macOS you need a recent version of Xcode. - -Python for venv -^^^^^^^^^^^^^^^ +Software prerequisites and recommended packages +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Sage depends on `a large number of software packages +<../reference/spkg/index.html>`_. Sage provides its own software +distribution providing most of these packages, so you do not have to +worry about having to download and install these packages yourself. -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 -built from scratch. - -Sage will accept versions 3.8.x to 3.10.x. - -You can also use ``--with-python=/path/to/python3_binary`` to tell Sage to use -``/path/to/python3_binary`` to set up the venv. Note that setting up venv requires -a number of Python modules to be available within the Python in question. Currently, -for Sage 9.6, these modules are as follows: ``sqlite3``, ``ctypes``, ``math``, -``hashlib``, ``crypt``, ``socket``, ``zlib``, ``distutils.core``, ``ssl`` - -they will be checked for by the ``configure`` script. - -Other notes -^^^^^^^^^^^ - -After extracting the Sage source tarball, the subdirectory :file:`upstream` -contains the source distributions for everything on which Sage depends. - -If cloned from a git repository, the upstream tarballs will be downloaded, -verified, and cached as part of the Sage installation process. -We emphasize that all of this software is included with Sage, so you do not -have to worry about trying to download and install any one of these packages -(such as Python, for example) yourself. - -When the Sage installation program is run, -it will check that you have each of the above-listed prerequisites, -and inform you of any that are missing, or have unsuitable versions. - -System-specific requirements -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -On macOS, there are various developer tools needed which may require -some registration on Apple's developer site; see -:ref:`section_macprereqs`. - -On Redhat-derived systems not all perl components are installed by -default and you might have to install the ``perl-ExtUtils-MakeMaker`` -package. - -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 extracted Sage from a source tarball, the subdirectory +:file:`upstream` contains the source distributions for all standard +packages on which Sage depends. If cloned from a git repository, the +upstream tarballs will be downloaded, verified, and cached as part of +the Sage installation process. + +However, there are minimal prerequisites for building Sage that +already must be installed on your system: + +- `Fundamental system packages required for installing from source + <../reference/spkg/_prereq.html>`_ + +- `C/C++ compilers <../reference/spkg/gcc.html>`_ + +If you have sufficient privileges (for example, on Linux you can +use ``sudo`` to become the ``root`` user), then you can install these packages +using the commands for your platform indicated in the pages linked above. 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 `. +do this for you. + +In addition to these minimal prerequisites, we strongly recommend to use system +installations of the following: -Installing prerequisites -~~~~~~~~~~~~~~~~~~~~~~~~ +- `Fortran compiler <../reference/spkg/gfortran.html>`_ -To check if you have the above prerequisites installed, for example ``perl``, -type:: +- `Python <../reference/spkg/python3.html>`_ - $ command -v perl +Sage developers will also need the `system packages required for +bootstrapping <../reference/spkg/_bootstrap.html>`_; they cannot be +installed by Sage. -or:: +When the ``./configure`` script runs, it will check for the presence of many +packages (including the above) and inform you of any that are +missing or have unsuitable versions. **Please read the messages that +``./configure`` prints:** It will inform you which additional system packages +you can install to avoid having to build them from source. This can save a lot of +time. - $ which perl +The following sections provide the commands to install a large +recommended set of packages on various systems, which will minimize +the time it takes to build Sage. This is intended as a convenient +shortcut, but of course you can choose to take a more fine-grained +approach. -on the command line. If it gives an error (or returns nothing), then -either ``perl`` is not installed, or it is installed but not in your -:wikipedia:`PATH `. .. _sec-installation-from-sources-linux-recommended-installation: -Debian/Ubuntu prerequisite installation -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Debian/Ubuntu package installation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -On Debian ("buster" or newer) or Ubuntu ("bionic" or newer): +On Debian ("buster" or newer) or Ubuntu ("bionic" or newer), we recommend that you +install: .. literalinclude:: debian.txt -If you wish to do Sage development, additionally install the following: +If you wish to do Sage development, we recommend that you additionally +install the following: .. literalinclude:: debian-develop.txt -For all users, we recommend the following: +For all users, we recommend that you install the following system packages, +which provide additional functionality and cannot be installed by Sage: .. literalinclude:: debian-recommended.txt @@ -223,16 +124,20 @@ install: .. literalinclude:: debian-optional.txt -Fedora/Redhat/CentOS prerequisite installation -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Fedora/Redhat/CentOS package installation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +On Fedora/Redhat/CentOS, we recommend that you install: .. literalinclude:: fedora.txt -If you wish to do Sage development, additionally install the following: +If you wish to do Sage development, we recommend that you additionally +install the following: .. literalinclude:: fedora-develop.txt -For all users, we recommend the following: +For all users, we recommend that you install the following system packages, +which provide additional functionality and cannot be installed by Sage: .. literalinclude:: fedora-recommended.txt @@ -242,16 +147,20 @@ install: .. literalinclude:: fedora-optional.txt -Arch Linux prerequisite installation -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Arch Linux package installation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +On ArchLinux, we recommend that you install: .. literalinclude:: arch.txt -If you wish to do Sage development, additionally install the following: +If you wish to do Sage development, we recommend that you additionally +install the following: .. literalinclude:: arch-develop.txt -For all users, we recommend the following: +For all users, we recommend that you install the following system packages, +which provide additional functionality and cannot be installed by Sage: .. literalinclude:: arch-recommended.txt @@ -261,16 +170,20 @@ install: .. literalinclude:: arch-optional.txt -OpenSUSE prerequisite installation -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +OpenSUSE package installation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +On OpenSUSE, we recommend that you install: .. literalinclude:: opensuse.txt -If you wish to do Sage development, additionally install the following: +If you wish to do Sage development, we recommend that you additionally +install the following: .. literalinclude:: opensuse-develop.txt -For all users, we recommend the following: +For all users, we recommend that you install the following system packages, +which provide additional functionality and cannot be installed by Sage: .. literalinclude:: opensuse-recommended.txt @@ -282,8 +195,8 @@ install: .. _section_macprereqs: -macOS prerequisite installation -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +macOS prerequisites +^^^^^^^^^^^^^^^^^^^ On macOS systems, you need a recent version of `Command Line Tools `_. @@ -312,25 +225,11 @@ a registration. to Command Line Tools. +macOS package installation +^^^^^^^^^^^^^^^^^^^^^^^^^^ -macOS recommended installation -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Although Sage can in theory build its own version of gfortran, this -can take a while, and the process fails on some recent versions of -OS X. So instead you can install your own copy. One advantage of this -is that you can install it once, and it will get used every time you -build Sage, rather than building gfortran every time. - -One way to do that is with the `Homebrew package manager -`_. Install Homebrew as their web page describes, and -then the command :: - - $ brew install gcc - -will install Homebrew's gcc package, which includes gfortran. Sage -will also use other Homebrew packages, if they are present. You can -install the following: +If you use the `Homebrew package manager +`_, you can install the following: .. literalinclude:: homebrew.txt @@ -344,11 +243,13 @@ Sage, run :: command like this to your shell profile if you want the settings to persist between shell sessions. -If you wish to do Sage development, additionally install the following: +If you wish to do Sage development, we recommend that you additionally +install the following: .. literalinclude:: homebrew-develop.txt -For all users, we recommend the following: +For all users, we recommend that you install the following system packages, +which provide additional functionality and cannot be installed by Sage: .. literalinclude:: homebrew-recommended.txt @@ -547,50 +448,6 @@ If you don't want conda to be used by sage, deactivate conda (for the current sh Then SageMath will be built either using the compilers provided by the operating system, or its own compilers. -Specific notes for ``make`` and ``tar`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -On macOS, the system-wide BSD ``tar`` supplied will build Sage, so there is no -need to install the GNU ``tar``. - -.. _section_compilers: - -Using alternative compilers -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Sage developers tend to use fairly recent versions of GCC. -Nonetheless, the Sage build process on Linux -should succeed with any reasonable C/C++ compiler; -(we do not recommend GCC older than version 5.1). -This is because Sage will build GCC first (if needed) and then use that newly -built GCC to compile Sage. - -If you don't want this and want to try building Sage with a different set of -compilers, -you need to pass Sage's ``./configure`` compiler names, via environment -variables ``CC``, ``CXX``, and ``FC``, for C, C++, and Fortran compilers, -respectively, e.g. if you C compiler is ``clang``, your C++ compiler is ``clang++``, -and your Fortran compiler is ``flang`` then you would need to run:: - - $ CC=clang CXX=clang++ FC=flang ./configure - -before running ``make``. It is recommended that you inspect the output of ``./configure`` -in order to check that Sage will not try to build GCC. Namely, there should be lines like:: - - gcc-7.2.0 will not be installed (configure check) - ... - gfortran-7.2.0 will not be installed (configure check) - -indicating that Sage will not attempt to build ``gcc/g++/gfortran``. - -If you are interested in working on support for commercial compilers from -`HP `_, -`IBM `_, -`Intel `_, -`Sun/Oracle `_, -etc, -please email the sage-devel mailing list at https://groups.google.com/group/sage-devel. - Additional software ------------------- @@ -702,7 +559,7 @@ General procedure serious consequences if you are logged in as root. Typing ``make`` performs the usual steps for each Sage's dependency, - but installs all the resulting files into the local build tree. + but installs all the resulting files into the installation prefix. Depending on the age and the architecture of your system, it can take from a few tens of minutes to several hours to build Sage from source. On really slow hardware, it can even take a few days to build Sage. @@ -867,9 +724,10 @@ General procedure Now typing ``sage`` within your terminal emulator should start Sage. #. Optional: - Install optional Sage packages and databases. - Type ``sage --optional`` to see a list of them (this requires an Internet - connection), or visit https://www.sagemath.org/packages/optional/. + Install optional Sage packages and databases. See `the list of optional packages + in the reference manual <../reference/spkg/index.html#optional-packages>`_ for + detailed information, or type ``sage --optional`` (this requires an Internet connection). + Then type ``sage -i `` to automatically download and install a given package. @@ -1331,4 +1189,4 @@ a single copy of Sage in a multi-user computer network. $ sudo chown -R root SAGE_LOCAL -**This page was last updated in May 2022 (Sage 9.7).** +**This page was last updated in September 2022 (Sage 9.8).** diff --git a/src/doc/en/reference/algebras/index.rst b/src/doc/en/reference/algebras/index.rst index d247256130b..237343faf32 100644 --- a/src/doc/en/reference/algebras/index.rst +++ b/src/doc/en/reference/algebras/index.rst @@ -99,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/discrete_geometry/index.rst b/src/doc/en/reference/discrete_geometry/index.rst index 0289ea570d9..e9e9255877e 100644 --- a/src/doc/en/reference/discrete_geometry/index.rst +++ b/src/doc/en/reference/discrete_geometry/index.rst @@ -112,6 +112,7 @@ Backends for Polyhedra sage/geometry/polyhedron/backend_cdd sage/geometry/polyhedron/backend_cdd_rdf sage/geometry/polyhedron/backend_field + sage/geometry/polyhedron/backend_number_field sage/geometry/polyhedron/backend_normaliz sage/geometry/polyhedron/backend_polymake sage/geometry/polyhedron/backend_ppl 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 1dbd6f036e7..e6a0e5c2000 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -2150,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** @@ -2662,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 @@ -3340,6 +3347,10 @@ REFERENCES: 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. @@ -4198,6 +4209,7 @@ REFERENCES: .. [MagmaHGM] *Hypergeometric motives* in Magma, http://magma.maths.usyd.edu.au/~watkins/papers/HGM-chapter.pdf + .. [Mar1980] Jacques Martinet, Petits discriminants des corps de nombres, Journ. Arithm. 1980, Cambridge Univ. Press, 1982, 151--193. @@ -4384,6 +4396,11 @@ REFERENCES: *Symmetric cyclotomic Hecke algebras* J. Algebra. **205** (1998) pp. 275-293. +.. [MM2008] Manel Maia and Miguel Méndez. + On the arithmetic product of combinatorial species. + Discrete Mathematics (2008), Volume 308, Issue 23, pp. 5407-5427, + :arxiv:`math/0503436v2`. + .. [MM2015] \J. Matherne and \G. Muller, *Computing upper cluster algebras*, Int. Math. Res. Not. IMRN, 2015, 3121-3149. 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/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/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/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/sage/algebras/catalog.py b/src/sage/algebras/catalog.py index da34c56e85d..a3c8aa161ed 100644 --- a/src/sage/algebras/catalog.py +++ b/src/sage/algebras/catalog.py @@ -60,6 +60,8 @@ - :class:`algebras.QSym ` - :class:`algebras.Partition ` - :class:`algebras.PlanarPartition ` +- :class:`algebras.qCommutingPolynomials + ` - :class:`algebras.QuantumGroup ` - :func:`algebras.Quaternion @@ -71,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 @@ -124,9 +126,11 @@ 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 + +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 0def7ae087b..be0db957cf6 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -666,7 +666,7 @@ def _basis_index_function(self, x): # 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()) + return FrozenBitset(x, capacity=Q.dim()) # slice the output of format in order to make conventions # of format and FrozenBitset agree. @@ -2984,7 +2984,7 @@ def groebner_basis(self, term_order=None, reduced=True): from sage.algebras.exterior_algebra_groebner import GroebnerStrategyDegLex as strategy else: raise ValueError("invalid term order") - if strategy == type(self._groebner_strategy): + if isinstance(self._groebner_strategy, strategy): if self._reduced or not reduced: return self._groebner_strategy.groebner_basis self._reduced = reduced @@ -2994,4 +2994,3 @@ def groebner_basis(self, term_order=None, reduced=True): self._groebner_strategy.compute_groebner(reduced=reduced) self._reduced = reduced return self._groebner_strategy.groebner_basis - diff --git a/src/sage/algebras/exterior_algebra_groebner.pyx b/src/sage/algebras/exterior_algebra_groebner.pyx index 98e338d3468..3d01aaf03a1 100644 --- a/src/sage/algebras/exterior_algebra_groebner.pyx +++ b/src/sage/algebras/exterior_algebra_groebner.pyx @@ -9,7 +9,7 @@ AUTHORS: - Trevor K. Karn, Travis Scrimshaw (July 2022): Initial implementation """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2022 Trevor K. Karn # (C) 2022 Travis Scrimshaw # @@ -17,8 +17,8 @@ AUTHORS: # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from cysignals.signals cimport sig_check from sage.libs.gmp.mpz cimport mpz_sizeinbase, mpz_setbit, mpz_tstbit, mpz_cmp_si, mpz_sgn @@ -137,7 +137,7 @@ cdef class GroebnerStrategy: 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): - """ + r""" Compute one half of the `S`-polynomial for ``f`` and ``g``. This computes: @@ -154,7 +154,7 @@ cdef class GroebnerStrategy: return ret cdef inline partial_S_poly_right(self, GBElement f, GBElement g): - """ + r""" Compute one half of the `S`-polynomial for ``f`` and ``g``. This computes: diff --git a/src/sage/algebras/finite_gca.py b/src/sage/algebras/finite_gca.py index 0a40e539438..5f21cdc8290 100644 --- a/src/sage/algebras/finite_gca.py +++ b/src/sage/algebras/finite_gca.py @@ -6,17 +6,16 @@ - Michael Jung (2021): initial version """ - -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2021 Michael Jung # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** - +# https://www.gnu.org/licenses/ +# **************************************************************************** +from __future__ import annotations from sage.combinat.free_module import CombinatorialFreeModule from sage.categories.algebras import Algebras from sage.misc.cachefunc import cached_method @@ -27,6 +26,7 @@ from sage.sets.condition_set import ConditionSet from sage.rings.integer_ring import ZZ + class FiniteGCAlgebra(CombinatorialFreeModule, Algebra): r""" Finite dimensional graded commutative algebras. @@ -484,16 +484,15 @@ def one_basis(self): n = len(self._degrees) return self._weighted_vectors([0 for _ in range(n)]) - def gens(self): + def gens(self) -> tuple: r""" - Return the generators of ``self`` as a list. + Return the generators of ``self`` as a tuple. EXAMPLES:: sage: A. = GradedCommutativeAlgebra(QQ, degrees=(4,8,2), max_degree=10) sage: A.gens() - [x, y, z] - + (x, y, z) """ n = len(self._degrees) zero = [0 for _ in range(n)] @@ -502,7 +501,7 @@ def gens(self): ind = list(zero) ind[k] = 1 indices.append(self._weighted_vectors(ind)) - return [self.monomial(ind) for ind in indices] + return tuple([self.monomial(ind) for ind in indices]) @cached_method def gen(self, i): 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 index 467313ce9b9..f3cdb5a6af8 100644 --- a/src/sage/algebras/hecke_algebras/cubic_hecke_algebra.py +++ b/src/sage/algebras/hecke_algebras/cubic_hecke_algebra.py @@ -1628,7 +1628,7 @@ def _tietze_to_finite_sub_basis_monomial(self, tietze_tup): def _create_matrix_list_for_one(self, representation_type): r""" Return the matrix list for the given representation type - for ``self.one()`. + for ``self.one()``. EXAMPLES:: @@ -1757,11 +1757,11 @@ def _test_ring_constructions(self, **options): raise RuntimeError('fatal: base ring embedding %s does not work' % bri) test_eleBgenEmb = self._tester(**options) - test_eleBgenEmb.assertTrue(eleBgenEmb == eleB) + test_eleBgenEmb.assertEqual(eleBgenEmb, eleB) test_eleEgenEmb = self._tester(**options) - test_eleEgenEmb.assertTrue(eleEgenEmb == eleE) + test_eleEgenEmb.assertEqual(eleEgenEmb, eleE) test_eleBembE = self._tester(**options) - test_eleBembE.assertTrue(eleBembE == eleB) + test_eleBembE.assertEqual(eleBembE, eleB) # -------------------------------------------------------------------------- # _test_matrix_constructions @@ -1806,7 +1806,7 @@ def check_matrix(representation_type): m12mult = m1*m2 m12mat = b12.matrix(representation_type=representation_type) test_matrix = self._tester(**options) - test_matrix.assertTrue(m12mult == m12mat) + test_matrix.assertEqual(m12mult, m12mat) from sage.combinat.root_system.reflection_group_real import is_chevie_available @@ -2366,8 +2366,8 @@ def _reduce_all_gen_powers(self, braid_tietze): @cached_method def _reduce_gen_power(self, k): r""" - Return the ``k``-th power on an arbitrary generator, - for example ``c0^k` . + Return the `k`-th power on an arbitrary generator, + for example `c_0^k`. INPUT: @@ -3535,4 +3535,3 @@ def char_function(ele): 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 index d30633cad55..1a21f5dcb9f 100644 --- a/src/sage/algebras/hecke_algebras/cubic_hecke_base_ring.py +++ b/src/sage/algebras/hecke_algebras/cubic_hecke_base_ring.py @@ -1477,7 +1477,7 @@ def specialize_links_gould(self): L = LaurentPolynomialRing(ZZ, 't0, t1') t0, t1 = L.gens() lu = t0 + t1 - 1 - lv = t0*t1 - t0 - t1 + lv = t0 * t1 - t0 - t1 lw = -t0 * t1 LL = L.localization((lu, lv)) u = LL(lu) @@ -1486,4 +1486,3 @@ def specialize_links_gould(self): 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 index 01c72e26098..b2d68fb2f6f 100644 --- a/src/sage/algebras/hecke_algebras/cubic_hecke_matrix_rep.py +++ b/src/sage/algebras/hecke_algebras/cubic_hecke_matrix_rep.py @@ -1079,4 +1079,3 @@ def some_elements(self): 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/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/n2_lie_conformal_algebra.py b/src/sage/algebras/lie_conformal_algebras/n2_lie_conformal_algebra.py index 5ee696bee17..23af0d3f7dd 100644 --- a/src/sage/algebras/lie_conformal_algebras/n2_lie_conformal_algebra.py +++ b/src/sage/algebras/lie_conformal_algebras/n2_lie_conformal_algebra.py @@ -19,18 +19,19 @@ - Reimundo Heluani (2020-06-03): Initial implementation. """ -#****************************************************************************** +# ***************************************************************************** # Copyright (C) 2020 Reimundo Heluani # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from .graded_lie_conformal_algebra import GradedLieConformalAlgebra + class N2LieConformalAlgebra(GradedLieConformalAlgebra): """ The N=2 super Lie conformal algebra. @@ -70,7 +71,7 @@ class N2LieConformalAlgebra(GradedLieConformalAlgebra): sage: G.bracket(G) {0: 2*L, 2: 2/3*C} """ - def __init__(self,R): + def __init__(self, R): """ Initialize self. @@ -79,24 +80,25 @@ def __init__(self,R): sage: V = lie_conformal_algebras.N2(QQ) sage: TestSuite(V).run() """ - n2dict =\ - {('L','L'):{0:{('L',1):1}, 1:{('L',0): 2}, - 3:{('C', 0):R(2).inverse_of_unit()}}, - ('L','G1'):{0:{('G1',1):1}, 1:{('G1',0):3*R(2).\ - inverse_of_unit()}}, - ('L','G2'):{0:{('G2',1):1}, 1:{('G2',0):3*R(2).\ - inverse_of_unit()}}, - ('G1','G2'): {0:{('L',0):1,('J',1):R(2).inverse_of_unit()}, - 1:{('J',0):1}, 2:{('C',0):R(3).inverse_of_unit()}}, - ('L','J'): {0:{('J',1):1},1:{('J',0):1}}, - ('J','J'): {1:{('C',0):R(3).inverse_of_unit()}}, - ('J','G1'): {0:{('G1',0):1}}, - ('J','G2'): {0:{('G2',0):-1}}} + n2dict = {('L', 'L'): {0: {('L', 1): 1}, + 1: {('L', 0): 2}, + 3: {('C', 0): R(2).inverse_of_unit()}}, + ('L', 'G1'): {0: {('G1', 1): 1}, + 1: {('G1', 0): 3 * R(2).inverse_of_unit()}}, + ('L', 'G2'): {0: {('G2', 1): 1}, + 1: {('G2', 0): 3 * R(2).inverse_of_unit()}}, + ('G1', 'G2'): {0: {('L', 0): 1, ('J', 1): R(2).inverse_of_unit()}, + 1: {('J', 0): 1}, + 2: {('C', 0): R(3).inverse_of_unit()}}, + ('L', 'J'): {0: {('J', 1): 1}, 1: {('J', 0): 1}}, + ('J', 'J'): {1: {('C', 0): R(3).inverse_of_unit()}}, + ('J', 'G1'): {0: {('G1', 0): 1}}, + ('J', 'G2'): {0: {('G2', 0): -1}}} from sage.rings.rational_field import QQ - weights = (2,1,QQ(3/2),QQ(3/2)) - parity = (0,0,1,1) - GradedLieConformalAlgebra.__init__(self,R,n2dict, - names=('L', 'J','G1','G2'), + weights = (2, 1, QQ(3) / 2, QQ(3) / 2) + parity = (0, 0, 1, 1) + GradedLieConformalAlgebra.__init__(self, R, n2dict, + names=('L', 'J', 'G1', 'G2'), central_elements=('C',), weights=weights, parity=parity) @@ -108,7 +110,5 @@ def _repr_(self): sage: R = lie_conformal_algebras.N2(QQbar); R The N=2 super Lie conformal algebra over Algebraic Field - """ - return "The N=2 super Lie conformal algebra over {}".\ - format(self.base_ring()) + return f"The N=2 super Lie conformal algebra over {self.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 7bd25496745..55fd2bdb641 100644 --- a/src/sage/algebras/orlik_terao.py +++ b/src/sage/algebras/orlik_terao.py @@ -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..d1aae987d61 --- /dev/null +++ b/src/sage/algebras/q_commuting_polynomials.py @@ -0,0 +1,350 @@ +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 f646b0c2a52..1e57682f255 100644 --- a/src/sage/algebras/quantum_clifford.py +++ b/src/sage/algebras/quantum_clifford.py @@ -948,8 +948,7 @@ def inverse(self): 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()}) + 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 3e12785e143..45a4d512c0a 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -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/yangian.py b/src/sage/algebras/yangian.py index b63648bbb95..55bb61fccf5 100644 --- a/src/sage/algebras/yangian.py +++ b/src/sage/algebras/yangian.py @@ -19,10 +19,9 @@ from sage.misc.cachefunc import cached_method from sage.misc.misc_c import prod -from sage.structure.unique_representation import UniqueRepresentation from sage.categories.hopf_algebras_with_basis import HopfAlgebrasWithBasis from sage.categories.graded_hopf_algebras_with_basis import GradedHopfAlgebrasWithBasis -from sage.rings.integer_ring import ZZ +from sage.categories.cartesian_product import cartesian_product from sage.rings.infinity import infinity from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.sets.family import Family @@ -31,157 +30,6 @@ from sage.combinat.free_module import CombinatorialFreeModule from sage.algebras.associated_graded import AssociatedGradedAlgebra -import itertools - - -class GeneratorIndexingSet(UniqueRepresentation): - """ - Helper class for the indexing set of the generators. - """ - def __init__(self, index_set, level=None): - """ - Initialize ``self``. - - TESTS:: - - sage: from sage.algebras.yangian import GeneratorIndexingSet - sage: I = GeneratorIndexingSet((1,2)) - """ - self._index_set = index_set - self._level = level - - def __repr__(self): - """ - Return a string representation of ``self``. - - TESTS:: - - sage: from sage.algebras.yangian import GeneratorIndexingSet - sage: GeneratorIndexingSet((1,2)) - Cartesian product of Positive integers, (1, 2), (1, 2) - sage: GeneratorIndexingSet((1,2), 4) - Cartesian product of (1, 2, 3, 4), (1, 2), (1, 2) - """ - if self._level is None: - L = PositiveIntegers() - else: - L = tuple(range(1, self._level + 1)) - return "Cartesian product of {L}, {I}, {I}".format(L=L, I=self._index_set) - - def an_element(self): - """ - Initialize ``self``. - - TESTS:: - - sage: from sage.algebras.yangian import GeneratorIndexingSet - sage: I = GeneratorIndexingSet((1,2)) - sage: I.an_element() - (3, 1, 1) - sage: I = GeneratorIndexingSet((1,2), 5) - sage: I.an_element() - (3, 1, 1) - sage: I = GeneratorIndexingSet((1,2), 1) - sage: I.an_element() - (1, 1, 1) - """ - if self._level is not None and self._level < 3: - return (1, self._index_set[0], self._index_set[0]) - return (3, self._index_set[0], self._index_set[0]) - - def cardinality(self): - """ - Return the cardinality of ``self``. - - TESTS:: - - sage: from sage.algebras.yangian import GeneratorIndexingSet - sage: I = GeneratorIndexingSet((1,2)) - sage: I.cardinality() - +Infinity - sage: I = GeneratorIndexingSet((1,2), level=3) - sage: I.cardinality() == 3 * 2 * 2 - True - """ - if self._level is not None: - return self._level * len(self._index_set)**2 - return infinity - - __len__ = cardinality - - def __call__(self, x): - """ - Call ``self``. - - TESTS:: - - sage: from sage.algebras.yangian import GeneratorIndexingSet - sage: I = GeneratorIndexingSet((1,2)) - sage: I([1, 2]) - (1, 2) - """ - return tuple(x) - - def __contains__(self, x): - """ - Check containment of ``x`` in ``self``. - - TESTS:: - - sage: from sage.algebras.yangian import GeneratorIndexingSet - sage: I = GeneratorIndexingSet((1,2)) - sage: (4, 1, 2) in I - True - sage: [4, 2, 1] in I - True - sage: (-1, 1, 1) in I - False - sage: (1, 3, 1) in I - False - - :: - - sage: I3 = GeneratorIndexingSet((1,2), 3) - sage: (1, 1, 2) in I3 - True - sage: (3, 1, 1) in I3 - True - sage: (4, 1, 1) in I3 - False - """ - return (isinstance(x, (tuple, list)) and len(x) == 3 - and x[0] in ZZ and x[0] > 0 - and (self._level is None or x[0] <= self._level) - and x[1] in self._index_set - and x[2] in self._index_set) - - def __iter__(self): - """ - Iterate over ``self``. - - TESTS:: - - sage: from sage.algebras.yangian import GeneratorIndexingSet - sage: I = GeneratorIndexingSet((1,2)) - sage: it = iter(I) - sage: [next(it) for dummy in range(5)] - [(1, 1, 1), (1, 1, 2), (1, 2, 1), (1, 2, 2), (2, 1, 1)] - - sage: I = GeneratorIndexingSet((1,2), 3) - sage: list(I) - [(1, 1, 1), (1, 1, 2), (1, 2, 1), (1, 2, 2), - (2, 1, 1), (2, 1, 2), (2, 2, 1), (2, 2, 2), - (3, 1, 1), (3, 1, 2), (3, 2, 1), (3, 2, 2)] - """ - I = self._index_set - if self._level is not None: - for x in itertools.product(range(1, self._level + 1), I, I): - yield x - return - for i in PositiveIntegers(): - for x in itertools.product(I, I): - yield (i, x[0], x[1]) - class Yangian(CombinatorialFreeModule): r""" @@ -392,7 +240,7 @@ def __init__(self, base_ring, n, variable_name, filtration): category = category.Connected() self._index_set = tuple(range(1, n + 1)) # The keys for the basis are tuples (l, i, j) - indices = GeneratorIndexingSet(self._index_set) + indices = cartesian_product([PositiveIntegers(), self._index_set, self._index_set]) # We note that the generators are non-commutative, but we always sort # them, so they are, in effect, indexed by the free abelian monoid basis_keys = IndexedFreeAbelianMonoid(indices, bracket=False, @@ -501,7 +349,7 @@ def _element_constructor_(self, x): True sage: Y6 = Yangian(QQ, 4, level=6, filtration='natural') sage: Y(Y6.an_element()) - t(1)[1,1]*t(1)[1,2]^2*t(1)[1,3]^3*t(3)[1,1] + t(1)[1,1]^2*t(1)[1,2]^2*t(1)[1,3]^3 + 2*t(1)[1,1] + 3*t(1)[1,2] + 1 """ if isinstance(x, CombinatorialFreeModule.Element): if isinstance(x.parent(), Yangian) and x.parent()._n <= self._n: @@ -543,8 +391,8 @@ def algebra_generators(self): sage: Y = Yangian(QQ, 4) sage: Y.algebra_generators() - Lazy family (generator(i))_{i in Cartesian product of - Positive integers, (1, 2, 3, 4), (1, 2, 3, 4)} + Lazy family (generator(i))_{i in The Cartesian product of + (Positive integers, {1, 2, 3, 4}, {1, 2, 3, 4})} """ return Family(self._indices._indices, self.gen, name="generator") @@ -816,7 +664,8 @@ def __init__(self, base_ring, n, level, variable_name, filtration): category = HopfAlgebrasWithBasis(base_ring).Filtered() self._index_set = tuple(range(1,n+1)) # The keys for the basis are tuples (l, i, j) - indices = GeneratorIndexingSet(self._index_set, level) + L = range(1, self._level + 1) + indices = cartesian_product([L, self._index_set, self._index_set]) # We note that the generators are non-commutative, but we always sort # them, so they are, in effect, indexed by the free abelian monoid basis_keys = IndexedFreeAbelianMonoid(indices, bracket=False, prefix=variable_name) @@ -1152,7 +1001,10 @@ def __init__(self, Y): EXAMPLES:: sage: grY = Yangian(QQ, 4).graded_algebra() - sage: TestSuite(grY).run() # long time + sage: g = grY.indices().gens() + sage: x = grY(g[1,1,1] * g[1,1,2]^2 * g[1,1,3]^3 * g[3,1,1]) + sage: elts = [grY(g[1,1,1]), grY(g[2,1,1]), x] + sage: TestSuite(grY).run(elements=elts) # long time """ if Y._filtration != 'loop': raise ValueError("the Yangian must have the loop filtration") @@ -1170,6 +1022,17 @@ def antipode_on_basis(self, m): -tbar(2)[1,1] sage: x = grY.an_element(); x + tbar(1)[1,1]*tbar(1)[1,2]^2*tbar(1)[1,3]^3*tbar(42)[1,1] + sage: grY.antipode_on_basis(x.leading_support()) + -tbar(1)[1,1]*tbar(1)[1,2]^2*tbar(1)[1,3]^3*tbar(42)[1,1] + - 2*tbar(1)[1,1]*tbar(1)[1,2]*tbar(1)[1,3]^3*tbar(42)[1,2] + - 3*tbar(1)[1,1]*tbar(1)[1,2]^2*tbar(1)[1,3]^2*tbar(42)[1,3] + + 5*tbar(1)[1,2]^2*tbar(1)[1,3]^3*tbar(42)[1,1] + + 10*tbar(1)[1,2]*tbar(1)[1,3]^3*tbar(42)[1,2] + + 15*tbar(1)[1,2]^2*tbar(1)[1,3]^2*tbar(42)[1,3] + + sage: g = grY.indices().gens() + sage: x = grY(g[1,1,1] * g[1,1,2]^2 * g[1,1,3]^3 * g[3,1,1]); x tbar(1)[1,1]*tbar(1)[1,2]^2*tbar(1)[1,3]^3*tbar(3)[1,1] sage: grY.antipode_on_basis(x.leading_support()) -tbar(1)[1,1]*tbar(1)[1,2]^2*tbar(1)[1,3]^3*tbar(3)[1,1] 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/arith/misc.py b/src/sage/arith/misc.py index e57076646f4..1bd4d4c6a1f 100644 --- a/src/sage/arith/misc.py +++ b/src/sage/arith/misc.py @@ -1,6 +1,10 @@ # -*- coding: utf-8 -*- r""" Miscellaneous arithmetic functions + +AUTHORS: + +- Kevin Stueve (2010-01-17): in ``is_prime(n)``, delegated calculation to ``n.is_prime()`` """ # **************************************************************************** @@ -471,25 +475,29 @@ def factorial(n, algorithm='gmp'): def is_prime(n): r""" - Return ``True`` if `n` is a prime number, and ``False`` otherwise. - - Use a provable primality test or a strong pseudo-primality test depending - on the global :mod:`arithmetic proof flag `. + Determine whether `n` is a prime element of its parent ring. INPUT: - - ``n`` - the object for which to determine primality + - ``n`` -- the object for which to determine primality + + Exceptional special cases: + + - For integers, determine whether `n` is a *positive* prime. + - For number fields except `\QQ`, determine whether `n` + is a prime element *of the maximal order*. + + ALGORITHM: + + For integers, this function uses a provable primality test + or a strong pseudo-primality test depending on the global + :mod:`arithmetic proof flag `. .. SEEALSO:: - :meth:`is_pseudoprime` - :meth:`sage.rings.integer.Integer.is_prime` - AUTHORS: - - - Kevin Stueve kstueve@uw.edu (2010-01-17): - delegated calculation to ``n.is_prime()`` - EXAMPLES:: sage: is_prime(389) @@ -505,18 +513,59 @@ def is_prime(n): sage: is_prime(-2) False + :: + sage: a = 2**2048 + 981 sage: is_prime(a) # not tested - takes ~ 1min sage: proof.arithmetic(False) sage: is_prime(a) # instantaneous! True sage: proof.arithmetic(True) + + TESTS: + + Make sure the warning from :trac:`25046` works as intended:: + + sage: is_prime(7/1) + doctest:warning + ... + UserWarning: Testing primality in Rational Field, which is a field, + hence the result will always be False. To test whether n is a prime + integer, use is_prime(ZZ(n)) or ZZ(n).is_prime(). Using n.is_prime() + instead will silence this warning. + False + sage: ZZ(7/1).is_prime() + True + sage: QQ(7/1).is_prime() + False + + However, number fields redefine ``.is_prime()`` in an incompatible fashion + (cf. :trac:`32340`) and we should not warn:: + + sage: K. = NumberField(x^2+1) + sage: is_prime(1+i) + True """ try: - return n.is_prime() + ret = n.is_prime() except (AttributeError, NotImplementedError): return ZZ(n).is_prime() + R = n.parent() + if R.is_field(): + # number fields redefine .is_prime(), see #32340 + from sage.rings.number_field.number_field import NumberField_generic + if not isinstance(R, NumberField_generic): + import warnings + s = f'Testing primality in {R}, which is a field, ' \ + 'hence the result will always be False. ' + if R is QQ: + s += 'To test whether n is a prime integer, use ' \ + 'is_prime(ZZ(n)) or ZZ(n).is_prime(). ' + s += 'Using n.is_prime() instead will silence this warning.' + warnings.warn(s) + + return ret def is_pseudoprime(n): r""" @@ -3224,10 +3273,10 @@ def crt(a, b, m=None, n=None): CRT = crt -def CRT_list(v, moduli): - r""" Given a list ``v`` of elements and a list of corresponding +def CRT_list(values, moduli): + r""" Given a list ``values`` of elements and a list of corresponding ``moduli``, find a single element that reduces to each element of - ``v`` modulo the corresponding moduli. + ``values`` modulo the corresponding moduli. .. SEEALSO:: @@ -3289,22 +3338,37 @@ def CRT_list(v, moduli): sage: from gmpy2 import mpz sage: CRT_list([mpz(2),mpz(3),mpz(2)], [mpz(3),mpz(5),mpz(7)]) 23 + + Make sure we are not mutating the input lists:: + + sage: xs = [1,2,3] + sage: ms = [5,7,9] + sage: CRT_list(xs, ms) + 156 + sage: xs + [1, 2, 3] + sage: ms + [5, 7, 9] """ - if not isinstance(v, list) or not isinstance(moduli, list): + if not isinstance(values, list) or not isinstance(moduli, list): raise ValueError("arguments to CRT_list should be lists") - if len(v) != len(moduli): + if len(values) != len(moduli): raise ValueError("arguments to CRT_list should be lists of the same length") - if not v: + if not values: return ZZ.zero() - if len(v) == 1: - return moduli[0].parent()(v[0]) - x = v[0] - m = moduli[0] + if len(values) == 1: + return moduli[0].parent()(values[0]) + + # The result is computed using a binary tree. In typical cases, + # this scales much better than folding the list from one side. from sage.arith.functions import lcm - for i in range(1, len(v)): - x = CRT(x, v[i], m, moduli[i]) - m = lcm(m, moduli[i]) - return x % m + while len(values) > 1: + vs, ms = values[::2], moduli[::2] + for i, (v, m) in enumerate(zip(values[1::2], moduli[1::2])): + vs[i] = CRT(vs[i], v, ms[i], m) + ms[i] = lcm(ms[i], m) + values, moduli = vs, ms + return values[0] % moduli[0] def CRT_basis(moduli): diff --git a/src/sage/arith/power.pyx b/src/sage/arith/power.pyx index 3c9219a5f11..2900d9f2a45 100644 --- a/src/sage/arith/power.pyx +++ b/src/sage/arith/power.pyx @@ -24,7 +24,7 @@ cpdef generic_power(a, n): """ Return `a^n`. - If `n` is negative, return `(1/a)^(-n)`. + If `n` is negative, return `(1/a)^{-n}`. INPUT: 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/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/categories/additive_magmas.py b/src/sage/categories/additive_magmas.py index e81dcf9d4b9..cf2fdf242b9 100644 --- a/src/sage/categories/additive_magmas.py +++ b/src/sage/categories/additive_magmas.py @@ -519,7 +519,7 @@ def algebra_generators(self): An example of a commutative semigroup: the free commutative semigroup generated by ('a', 'b', 'c', 'd') sage: A = S.algebra(QQ) sage: A.algebra_generators() - Finite family {0: B[a], 1: B[b], 2: B[c], 3: B[d]} + Family (B[a], B[b], B[c], B[d]) .. TODO:: diff --git a/src/sage/categories/additive_semigroups.py b/src/sage/categories/additive_semigroups.py index fde92f27896..0527867154b 100644 --- a/src/sage/categories/additive_semigroups.py +++ b/src/sage/categories/additive_semigroups.py @@ -153,7 +153,7 @@ def algebra_generators(self): An example of a commutative semigroup: the free commutative semigroup generated by ('a', 'b', 'c', 'd') sage: A = S.algebra(QQ) sage: A.algebra_generators() - Finite family {0: B[a], 1: B[b], 2: B[c], 3: B[d]} + Family (B[a], B[b], B[c], B[d]) """ return self.basis().keys().additive_semigroup_generators().map(self.monomial) diff --git a/src/sage/categories/commutative_algebras.py b/src/sage/categories/commutative_algebras.py index ec4037f9a84..cff24e1298a 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,31 @@ 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/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 ec7a5865398..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: @@ -1845,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): @@ -2057,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)} @@ -2579,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/groups.py b/src/sage/categories/groups.py index b921570a63c..ef681dbb07a 100644 --- a/src/sage/categories/groups.py +++ b/src/sage/categories/groups.py @@ -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/integral_domains.py b/src/sage/categories/integral_domains.py index 8cf87ffe94f..2d5d7730693 100644 --- a/src/sage/categories/integral_domains.py +++ b/src/sage/categories/integral_domains.py @@ -144,4 +144,3 @@ 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 aa96f2b5594..e065b7ef4f6 100644 --- a/src/sage/categories/loop_crystals.py +++ b/src/sage/categories/loop_crystals.py @@ -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/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 22f5a6d8f07..26b51ef4b87 100644 --- a/src/sage/categories/modules_with_basis.py +++ b/src/sage/categories/modules_with_basis.py @@ -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): """ diff --git a/src/sage/categories/monoids.py b/src/sage/categories/monoids.py index b630ee04ee5..dc548a72e66 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. @@ -525,18 +556,17 @@ def algebra_generators(self): sage: Z12.semigroup_generators() Family (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11) sage: Z12.algebra(QQ).algebra_generators() - Finite family {0: B[0], 1: B[1], 2: B[2], 3: B[3], 4: B[4], 5: B[5], - 6: B[6], 7: B[7], 8: B[8], 9: B[9], 10: B[10], 11: B[11]} + Family (B[0], B[1], B[2], B[3], B[4], B[5], B[6], B[7], B[8], B[9], B[10], B[11]) sage: GroupAlgebras(QQ).example(AlternatingGroup(10)).algebra_generators() - Finite family {0: (8,9,10), 1: (1,2,3,4,5,6,7,8,9)} + Family ((8,9,10), (1,2,3,4,5,6,7,8,9)) sage: A = DihedralGroup(3).algebra(QQ); A Algebra of Dihedral group of order 6 as a permutation group over Rational Field sage: A.algebra_generators() - Finite family {0: (1,2,3), 1: (1,3)} + Family ((1,2,3), (1,3)) """ monoid = self.basis().keys() try: @@ -639,3 +669,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/pushout.py b/src/sage/categories/pushout.py index ea4e3c22dcc..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 @@ -3679,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/rings.py b/src/sage/categories/rings.py index fc1df0de372..88ce6ef5bc0 100644 --- a/src/sage/categories/rings.py +++ b/src/sage/categories/rings.py @@ -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/semigroups.py b/src/sage/categories/semigroups.py index 007401e5474..d053b20e3c0 100644 --- a/src/sage/categories/semigroups.py +++ b/src/sage/categories/semigroups.py @@ -882,7 +882,7 @@ def algebra_generators(self): sage: M.semigroup_generators() Family ('a', 'b', 'c', 'd') sage: M.algebra(ZZ).algebra_generators() - Finite family {0: B['a'], 1: B['b'], 2: B['c'], 3: B['d']} + Family (B['a'], B['b'], B['c'], B['d']) """ return self.basis().keys().semigroup_generators().map(self.monomial) diff --git a/src/sage/categories/sets_cat.py b/src/sage/categories/sets_cat.py index d096a950773..13ea3b0338f 100644 --- a/src/sage/categories/sets_cat.py +++ b/src/sage/categories/sets_cat.py @@ -1616,7 +1616,7 @@ def algebra(self, base_ring, category=None, **kwds): of `S`, or ``None`` This returns the space of formal linear combinations of - elements of `G` with coefficients in `R`, endowed with + elements of `S` with coefficients in `K`, endowed with whatever structure can be induced from that of `S`. See the documentation of :mod:`sage.categories.algebra_functor` for details. 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/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 77d3885933a..874f278ee93 100644 --- a/src/sage/combinat/crystals/fast_crystals.py +++ b/src/sage/combinat/crystals/fast_crystals.py @@ -116,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): """ @@ -127,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 @@ -177,8 +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._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): 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 6ca78aed640..29ca6086c57 100644 --- a/src/sage/combinat/crystals/letters.pyx +++ b/src/sage/combinat/crystals/letters.pyx @@ -168,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 @@ -176,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': @@ -2522,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): """ @@ -2615,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..e747ab5ecca 100644 --- a/src/sage/combinat/crystals/littelmann_path.py +++ b/src/sage/combinat/crystals/littelmann_path.py @@ -9,7 +9,7 @@ - Travis Scrimshaw (2016): Implemented :class:`~sage.combinat.crystals.littelmann_path.InfinityCrystalOfLSPaths` """ -#**************************************************************************** +# *************************************************************************** # Copyright (C) 2012 Mark Shimozono # Anne Schilling # @@ -22,8 +22,8 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ -#**************************************************************************** +# https://www.gnu.org/licenses/ +# *************************************************************************** from sage.misc.cachefunc import cached_in_parent_method, cached_method from sage.structure.unique_representation import UniqueRepresentation @@ -147,10 +147,7 @@ def __classcall_private__(cls, starting_weight, cartan_type = None, starting_wei """ if cartan_type is not None: cartan_type, starting_weight = CartanType(starting_weight), cartan_type - if cartan_type.is_affine(): - extended = True - else: - extended = False + extended = cartan_type.is_affine() R = RootSystem(cartan_type) P = R.weight_space(extended = extended) @@ -333,7 +330,7 @@ def split_step(self, which_step, r): sage: b.split_step(0,1/3) (1/3*Lambda[1] + 1/3*Lambda[2], 2/3*Lambda[1] + 2/3*Lambda[2]) """ - assert 0 <= which_step and which_step <= len(self.value) + assert 0 <= which_step <= len(self.value) v = self.value[which_step] return self.parent()(self.value[:which_step] + (r*v,(1-r)*v) + self.value[which_step+1:]) @@ -476,7 +473,7 @@ def e(self, i, power=1, to_string_end=False, length_only=False): ix = len(data)-1 while ix >= 0 and data[ix][1] < M + p: - # get the index of the current step to be processed + # get the index of the current step to be processed j = data[ix][0] # find the i-height where the current step might need to be split if ix == 0: @@ -621,7 +618,7 @@ def _latex_(self): ##################################################################### -## Projected level-zero +# Projected level-zero class CrystalOfProjectedLevelZeroLSPaths(CrystalOfLSPaths): @@ -702,7 +699,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): @@ -1143,7 +1141,9 @@ def energy_function(self): Wd = WeylGroup(cartan_dual, prefix='s', implementation="permutation") G = Wd.quantum_bruhat_graph(J) Qd = RootSystem(cartan_dual).root_lattice() - dualize = lambda x: Qv.from_vector(x.to_vector()) + + def dualize(x): + return Qv.from_vector(x.to_vector()) L = [Wd.from_reduced_word(x.reduced_word()) for x in L] def stretch_short_root(a): @@ -1176,7 +1176,7 @@ def stretch_short_root(a): ##################################################################### -## B(\infty) +# B(\infty) class InfinityCrystalOfLSPaths(UniqueRepresentation, Parent): @@ -1208,7 +1208,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): """ @@ -1463,7 +1463,7 @@ def phi(self,i): ##################################################################### -## Helper functions +# Helper functions def positively_parallel_weights(v, w): 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/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 f11f9b81499..d6042d6facc 100644 --- a/src/sage/combinat/free_module.py +++ b/src/sage/combinat/free_module.py @@ -1183,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 @@ -1330,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:: @@ -1454,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) @@ -1494,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) @@ -1629,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 @@ -1703,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 @@ -1723,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)) @@ -1757,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) @@ -1787,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) @@ -1826,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..352ec520d4d 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,10 +454,60 @@ 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") + + + def trim(self): + """ + Remove trailing zeros from the integer vector. + EXAMPLES:: + + sage: IV = IntegerVectors() + sage: IV([5,3,5,1,0,0]).trim() + [5, 3, 5, 1] + sage: IV([5,0,5,1,0]).trim() + [5, 0, 5, 1] + sage: IV([4,3,3]).trim() + [4, 3, 3] + sage: IV([0,0,0]).trim() + [] + + sage: IV = IntegerVectors(k=4) + sage: v = IV([4,3,2,0]).trim(); v + [4, 3, 2] + sage: v.parent() + Integer vectors + """ + P = IntegerVectors() + v = list(self) + if all(i == 0 for i in v): + return P.element_class(P, [], check=False) + while not v[-1]: + v = v[:-1] + return P.element_class(P, v, check=False) class IntegerVectors(Parent, metaclass=ClasscallMetaclass): """ @@ -676,7 +727,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 +1066,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/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index 2d840e7416b..2046814a92e 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -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/matrices/dancing_links.pyx b/src/sage/combinat/matrices/dancing_links.pyx index 953b5c4e680..9b5ee82aa81 100644 --- a/src/sage/combinat/matrices/dancing_links.pyx +++ b/src/sage/combinat/matrices/dancing_links.pyx @@ -193,8 +193,8 @@ cdef class dancing_linksWrapper: r""" Reinitialization of the search algorithm - This recreates an empty `dancing_links` object and adds the rows to - the instance of dancing_links. + This recreates an empty ``dancing_links`` object and adds the rows to + the instance of ``dancing_links.`` EXAMPLES:: @@ -805,7 +805,7 @@ cdef class dancing_linksWrapper: INPUT: - ``ncpus`` -- integer (default: ``None``), maximal number of - subprocesses to use at the same time. If `ncpus>1` the dancing + subprocesses to use at the same time. If ``ncpus>1`` the dancing links problem is split into independent subproblems to allow parallel computation. If ``None``, it detects the number of effective CPUs in the system using @@ -968,8 +968,8 @@ cdef class dancing_linksWrapper: .. NOTE:: - When comparing the time taken by method `one_solution`, - have in mind that `one_solution_using_sat_solver` first + When comparing the time taken by method ``one_solution``, + have in mind that ``one_solution_using_sat_solver`` first creates the SAT solver instance from the dancing links solver. This copy of data may take many seconds depending on the size of the problem. @@ -1096,8 +1096,8 @@ cdef class dancing_linksWrapper: .. NOTE:: - When comparing the time taken by method `one_solution`, have in - mind that `one_solution_using_milp_solver` first creates (and + When comparing the time taken by method ``one_solution``, have in + mind that ``one_solution_using_milp_solver`` first creates (and caches) the MILP solver instance from the dancing links solver. This copy of data may take many seconds depending on the size of the problem. 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 590b0df65e5..f6cf8e4f364 100644 --- a/src/sage/combinat/partition.py +++ b/src/sage/combinat/partition.py @@ -1057,6 +1057,19 @@ def __truediv__(self, p): return SkewPartition([self[:], p]) + def stretch(self, k): + """ + Return the partition obtained by multiplying each part with the + given number. + + EXAMPLES:: + + sage: p = Partition([4,2,2,1,1]) + sage: p.stretch(3) + [12, 6, 6, 3, 3] + """ + return _Partitions([k * p for p in self]) + def power(self, k): r""" Return the cycle type of the `k`-th power of any permutation @@ -4749,6 +4762,117 @@ def add_horizontal_border_strip(self, k): res.append(_Partitions(tmp)) return res + def vertical_border_strip_cells(self, k): + """ + 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 + 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 + 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)): + for _ in range(iv[t]): + current_strip.append((j, tmp[j])) + j += 1 + 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): """ Return the partitions obtained from ``self`` by removing an diff --git a/src/sage/combinat/posets/hasse_diagram.py b/src/sage/combinat/posets/hasse_diagram.py index 515b762278c..d857092f9c7 100644 --- a/src/sage/combinat/posets/hasse_diagram.py +++ b/src/sage/combinat/posets/hasse_diagram.py @@ -3489,15 +3489,15 @@ def _glue_spectra(a_spec, b_spec, orientation): p = len(a_spec) q = len(b_spec) - for r in range(1, p+q+1): + for r in range(1, p + q + 1): new_a_spec.append(0) - for i in range(max(1, r-q), min(p, r) + 1): - k_val = binomial(r-1, i-1) * binomial(p+q-r, p-i) + for i in range(max(1, r - q), min(p, r) + 1): + k_val = binomial(r - 1, i - 1) * binomial(p + q - r, p - i) if orientation: - inner_sum = sum(b_spec[j-1] for j in range(r-i + 1, len(b_spec) + 1)) + inner_sum = sum(b_spec[j - 1] for j in range(r - i + 1, len(b_spec) + 1)) else: - inner_sum = sum(b_spec[j-1] for j in range(1, r-i + 1)) - new_a_spec[-1] = new_a_spec[-1] + (a_spec[i-1] * k_val * inner_sum) + inner_sum = sum(b_spec[j - 1] for j in range(1, r - i + 1)) + new_a_spec[-1] = new_a_spec[-1] + (a_spec[i - 1] * k_val * inner_sum) return new_a_spec diff --git a/src/sage/combinat/posets/lattices.py b/src/sage/combinat/posets/lattices.py index 00f50e68f19..dba3c15c549 100644 --- a/src/sage/combinat/posets/lattices.py +++ b/src/sage/combinat/posets/lattices.py @@ -1307,9 +1307,9 @@ def is_semidistributive(self): """ H = self._hasse_diagram # See trac #21528 for explanation. - return ( (H.in_degree_sequence().count(1) == + return ((H.in_degree_sequence().count(1) == H.out_degree_sequence().count(1)) and - self.is_meet_semidistributive() ) + self.is_meet_semidistributive()) def is_meet_semidistributive(self, certificate=False): r""" @@ -1693,11 +1693,11 @@ def is_cosectionally_complemented(self, certificate=False): H = self._hasse_diagram jn = H.join_matrix() n = H.order() - for e in range(n-2, -1, -1): + for e in range(n - 2, -1, -1): t = 0 for uc in H.neighbors_out(e): t = jn[t, uc] - if t == n-1: + if t == n - 1: break else: if certificate: @@ -1882,8 +1882,8 @@ def is_sectionally_complemented(self, certificate=False): H = self._hasse_diagram mt = H.meet_matrix() - n = H.order()-1 - for e in range(2, n+1): + n = H.order() - 1 + for e in range(2, n + 1): t = n for lc in H.neighbors_in(e): t = mt[t, lc] @@ -3085,9 +3085,9 @@ def vertical_decomposition(self, elements_only=False): if elements_only: return [self[e] for e in self._hasse_diagram.vertical_decomposition(return_list=True)] - elms = ( [0] + - self._hasse_diagram.vertical_decomposition(return_list=True) + - [self.cardinality() - 1] ) + elms = ([0] + + self._hasse_diagram.vertical_decomposition(return_list=True) + + [self.cardinality() - 1]) n = len(elms) result = [] for i in range(n - 1): @@ -4975,6 +4975,7 @@ def _log_2(n): return bits return bits + 1 + ############################################################################ FiniteMeetSemilattice._dual_class = FiniteJoinSemilattice diff --git a/src/sage/combinat/posets/moebius_algebra.py b/src/sage/combinat/posets/moebius_algebra.py index 9ed121e66c2..aada2725078 100644 --- a/src/sage/combinat/posets/moebius_algebra.py +++ b/src/sage/combinat/posets/moebius_algebra.py @@ -102,7 +102,7 @@ def __init__(self, R, L): TESTS:: - sage: L = posets.BooleanLattice(4) + sage: L = posets.BooleanLattice(3) sage: M = L.moebius_algebra(QQ) sage: TestSuite(M).run() """ @@ -632,7 +632,7 @@ def __init__(self, M, prefix='KL'): TESTS:: - sage: L = posets.BooleanLattice(4) + sage: L = posets.BooleanLattice(3) sage: M = L.quantum_moebius_algebra() sage: TestSuite(M.KL()).run() # long time """ diff --git a/src/sage/combinat/posets/poset_examples.py b/src/sage/combinat/posets/poset_examples.py index e415dc300f2..377fc569fde 100644 --- a/src/sage/combinat/posets/poset_examples.py +++ b/src/sage/combinat/posets/poset_examples.py @@ -219,7 +219,7 @@ def BooleanLattice(n, facade=None, use_subsets=False): from sage.sets.set import Set V = [Set(), Set([1])] return LatticePoset((V, [V]), facade=facade) - return LatticePoset(([0,1], [[0,1]]), facade=facade) + return LatticePoset(([0, 1], [[0, 1]]), facade=facade) if use_subsets: from sage.sets.set import Set @@ -288,7 +288,7 @@ def ChainPoset(n, facade=None): raise TypeError("number of elements must be an integer, not {0}".format(n)) if n < 0: raise ValueError("number of elements must be non-negative, not {0}".format(n)) - D = DiGraph([range(n), [[x,x+1] for x in range(n-1)]], + D = DiGraph([range(n), [[x, x + 1] for x in range(n - 1)]], format='vertices_and_edges') return FiniteLatticePoset(hasse_diagram=D, category=FiniteLatticePosets(), @@ -377,7 +377,7 @@ def PentagonPoset(facade=None): sage: posets.DiamondPoset(5).is_distributive() False """ - return LatticePoset([[1,2],[4],[3],[4],[]], facade=facade) + return LatticePoset([[1, 2], [4], [3], [4], []], facade=facade) @staticmethod def DiamondPoset(n, facade=None): @@ -404,10 +404,10 @@ def DiamondPoset(n, facade=None): raise TypeError("number of elements must be an integer, not {0}".format(n)) if n <= 2: raise ValueError("n must be an integer at least 3") - c = [[n-1] for x in range(n)] - c[0] = [x for x in range(1,n-1)] - c[n-1] = [] - D = DiGraph({v:c[v] for v in range(n)}, format='dict_of_lists') + c = [[n - 1] for x in range(n)] + c[0] = [x for x in range(1, n - 1)] + c[n - 1] = [] + D = DiGraph({v: c[v] for v in range(n)}, format='dict_of_lists') return FiniteLatticePoset(hasse_diagram=D, category=FiniteLatticePosets(), facade=facade) @@ -441,8 +441,8 @@ def Crown(n, facade=None): raise TypeError("number of elements must be an integer, not {0}".format(n)) if n < 2: raise ValueError("n must be an integer at least 2") - D = {i: [i+n, i+n+1] for i in range(n-1)} - D[n-1] = [n, n+n-1] + D = {i: [i + n, i + n + 1] for i in range(n - 1)} + D[n - 1] = [n, n + n - 1] return FinitePoset(hasse_diagram=DiGraph(D), category=FinitePosets(), facade=facade) @@ -485,7 +485,7 @@ def DivisorLattice(n, facade=None): if n <= 0: raise ValueError("n must be a positive integer") Div_n = divisors(n) - hasse = DiGraph([Div_n, lambda a, b: b%a==0 and is_prime(b//a)]) + hasse = DiGraph([Div_n, lambda a, b: b % a == 0 and is_prime(b // a)]) return FiniteLatticePoset(hasse, elements=Div_n, facade=facade, category=FiniteLatticePosets()) @@ -509,7 +509,8 @@ def IntegerCompositions(n): """ from sage.combinat.composition import Compositions C = Compositions(n) - return Poset((C, [[c,d] for c in C for d in C if d.is_finer(c)]), cover_relations=False) + return Poset((C, [[c, d] for c in C for d in C if d.is_finer(c)]), + cover_relations=False) @staticmethod def IntegerPartitions(n): @@ -535,19 +536,19 @@ def lower_covers(partition): of elements in the poset of integer partitions. """ lc = [] - for i in range(len(partition)-1): - for j in range(i+1,len(partition)): + for i in range(len(partition) - 1): + for j in range(i + 1, len(partition)): new_partition = partition[:] del new_partition[j] del new_partition[i] - new_partition.append(partition[i]+partition[j]) + new_partition.append(partition[i] + partition[j]) new_partition.sort(reverse=True) tup = tuple(new_partition) if tup not in lc: lc.append(tup) return lc from sage.combinat.partition import Partitions - H = DiGraph(dict([[tuple(p),lower_covers(p)] for p in Partitions(n)])) + H = DiGraph(dict([[tuple(p), lower_covers(p)] for p in Partitions(n)])) return Poset(H.reverse()) @staticmethod @@ -574,20 +575,20 @@ def lower_covers(partition): restricted poset of integer partitions. """ lc = [] - for i in range(len(partition)-1): - for j in range(i+1,len(partition)): + for i in range(len(partition) - 1): + for j in range(i + 1, len(partition)): if partition[i] != partition[j]: new_partition = partition[:] del new_partition[j] del new_partition[i] - new_partition.append(partition[i]+partition[j]) + new_partition.append(partition[i] + partition[j]) new_partition.sort(reverse=True) tup = tuple(new_partition) if tup not in lc: lc.append(tup) return lc from sage.combinat.partition import Partitions - H = DiGraph(dict([[tuple(p),lower_covers(p)] for p in Partitions(n)])) + H = DiGraph(dict([[tuple(p), lower_covers(p)] for p in Partitions(n)])) return Poset(H.reverse()) @staticmethod @@ -677,9 +678,8 @@ def PowerPoset(n): all_pos_n.add(P.relabel(list(r))) return MeetSemilattice((all_pos_n, - lambda A, B: all(B.is_lequal(x, y) for x,y in A.cover_relations_iterator()) - )) - + lambda A, B: all(B.is_lequal(x, y) + for x, y in A.cover_relations_iterator()))) @staticmethod def ProductOfChains(chain_lengths, facade=None): @@ -794,7 +794,7 @@ def RandomPoset(n, p): p = float(p) except Exception: raise TypeError("probability must be a real number, not {0}".format(p)) - if p < 0 or p> 1: + if p < 0 or p > 1: raise ValueError("probability must be between 0 and 1, not {0}".format(p)) D = DiGraph(loops=False, multiedges=False) @@ -904,7 +904,7 @@ def RandomLattice(n, p, properties=None): if n <= 3: return posets.ChainPoset(n) covers = _random_lattice(n, p) - covers_dict = {i:covers[i] for i in range(n)} + covers_dict = {i: covers[i] for i in range(n)} D = DiGraph(covers_dict) D.relabel([i-1 for i in Permutations(n).random_element()]) return LatticePoset(D, cover_relations=True) @@ -1173,9 +1173,11 @@ def SymmetricGroupWeakOrderPoset(n, labels="permutations", side="right"): Finite poset containing 24 elements """ if n < 10 and labels == "permutations": - element_labels = dict([[s,"".join(map(str,s))] for s in Permutations(n)]) + element_labels = dict([[s, "".join(map(str, s))] + for s in Permutations(n)]) if n < 10 and labels == "reduced_words": - element_labels = dict([[s,"".join(map(str,s.reduced_word_lexmin()))] for s in Permutations(n)]) + element_labels = dict([[s, "".join(map(str, s.reduced_word_lexmin()))] + for s in Permutations(n)]) if side == "left": def weak_covers(s): @@ -1193,7 +1195,8 @@ def weak_covers(s): """ return [v for v in s.bruhat_succ() if s.length() + (s.inverse().left_action_product(v)).length() == v.length()] - return Poset(dict([[s, weak_covers(s)] for s in Permutations(n)]),element_labels) + return Poset(dict([[s, weak_covers(s)] for s in Permutations(n)]), + element_labels) @staticmethod def TetrahedralPoset(n, *colors, **labels): @@ -1253,33 +1256,33 @@ def TetrahedralPoset(n, *colors, **labels): if c not in ('green', 'red', 'yellow', 'orange', 'silver', 'blue'): raise ValueError("color input must be from the following: 'green', 'red', 'yellow', 'orange', 'silver', and 'blue'") elem = [(i, j, k) for i in range(n) - for j in range(n-i) for k in range(n-i-j)] + for j in range(n - i) for k in range(n - i - j)] rels = [] elem_labels = {} if 'labels' in labels: if labels['labels'] == 'integers': labelcount = 0 - for (i,j,k) in elem: - elem_labels[(i,j,k)] = labelcount + for (i, j, k) in elem: + elem_labels[(i, j, k)] = labelcount labelcount += 1 for c in colors: - for (i,j,k) in elem: - if i+j+k < n-1: + for (i, j, k) in elem: + if i + j + k < n - 1: if c == 'green': - rels.append([(i,j,k),(i+1,j,k)]) + rels.append([(i, j, k), (i + 1, j, k)]) if c == 'red': - rels.append([(i,j,k),(i,j,k+1)]) + rels.append([(i, j, k), (i, j, k + 1)]) if c == 'yellow': - rels.append([(i,j,k),(i,j+1,k)]) - if j < n-1 and k > 0: + rels.append([(i, j, k), (i, j + 1, k)]) + if j < n - 1 and k > 0: if c == 'orange': - rels.append([(i,j,k),(i,j+1,k-1)]) - if i < n-1 and j > 0: + rels.append([(i, j, k), (i, j + 1, k - 1)]) + if i < n - 1 and j > 0: if c == 'silver': - rels.append([(i,j,k),(i+1,j-1,k)]) - if i < n-1 and k > 0: + rels.append([(i, j, k), (i + 1, j - 1, k)]) + if i < n - 1 and k > 0: if c == 'blue': - rels.append([(i,j,k),(i+1,j,k-1)]) + rels.append([(i, j, k), (i + 1, j, k - 1)]) return Poset([elem, rels], elem_labels) # shard intersection order @@ -1684,9 +1687,9 @@ def PermutationPattern(n): if n <= 0: raise ValueError("number of elements must be nonnegative, not {}".format(n)) elem = [] - for i in range(1, n+1): + for i in range(1, n + 1): elem += Permutations(i) - return Poset((elem, lambda a,b: b.has_pattern(a))) + return Poset((elem, lambda a, b: b.has_pattern(a))) @staticmethod def PermutationPatternInterval(bottom, top): @@ -1737,7 +1740,7 @@ def PermutationPatternInterval(bottom, top): level = 0 # Consider the top element to be level 0, and then go down from there. rel = [] # List of covering relations to be fed into poset constructor. while len(top) - len(bottom) >= level + 1: - elem.append([]) # Add a new empty level + elem.append([]) # Add a new empty level for upper in elem[level]: # Run through all permutations on current level # and find relations for which it is upper cover @@ -1746,17 +1749,17 @@ def PermutationPatternInterval(bottom, top): # Try and remove the ith element from the permutation lower = list(upper) j = lower.pop(i) - for k in range(len(top)-level-1): # Standardize result + for k in range(len(top)-level-1): # Standardize result if lower[k] > j: lower[k] = lower[k] - 1 lower_perm = P(lower) - if lower_perm.has_pattern(bottom): # Check to see if result is in interval + if lower_perm.has_pattern(bottom): # Check to see if result is in interval rel += [[lower_perm, upper_perm]] - if lower not in elem[level+1]: - elem[level+1].append(lower_perm) + if lower not in elem[level + 1]: + elem[level + 1].append(lower_perm) level += 1 elem = [item for sublist in elem for item in sublist] - return Poset((elem,rel)) + return Poset((elem, rel)) @staticmethod def PermutationPatternOccurrenceInterval(bottom, top, pos): @@ -1793,13 +1796,13 @@ def PermutationPatternOccurrenceInterval(bottom, top, pos): P = Permutations() top = P(top) bottom = P(bottom) - if not to_standard([top[z] for z in pos]) == list(bottom): # check input + if not to_standard([top[z] for z in pos]) == list(bottom): # check input raise ValueError("cannot find 'bottom' in 'top' given by 'pos'") elem = [[(top, pos)]] level = 0 rel = [] while len(top) - len(bottom) >= level + 1: - elem.append([]) # Add a new empty level + elem.append([]) # Add a new empty level for upper in elem[level]: for i in range(len(top)-level): # Try and remove the ith element from the permutation @@ -1816,11 +1819,11 @@ def PermutationPatternOccurrenceInterval(bottom, top, pos): lower_pos[f] = upper[1][f] - 1 rel += [[(P(lower_perm), tuple(lower_pos)), (P(upper[0]), upper[1])]] - if (P(lower_perm), tuple(lower_pos)) not in elem[level+1]: - elem[level+1].append((P(lower_perm), tuple(lower_pos))) + if (P(lower_perm), tuple(lower_pos)) not in elem[level + 1]: + elem[level + 1].append((P(lower_perm), tuple(lower_pos))) level += 1 elem = [item for sublist in elem for item in sublist] - return Poset([elem,rel]) + return Poset([elem, rel]) @staticmethod def RibbonPoset(n, descents): @@ -1838,7 +1841,9 @@ def RibbonPoset(n, descents): sage: sorted(R.cover_relations()) [[0, 1], [2, 1], [3, 2], [3, 4]] """ - return Mobile(DiGraph([list(range(n)), [(i+1, i) if i in descents else (i, i+1) for i in range(n-1) ]])) + return Mobile(DiGraph([list(range(n)), + [(i + 1, i) if i in descents else (i, i + 1) + for i in range(n - 1)]])) @staticmethod def MobilePoset(ribbon, hangers, anchor=None): @@ -1889,15 +1894,15 @@ def MobilePoset(ribbon, hangers, anchor=None): for r, hangs in hangers.items(): for i, h in enumerate(hangs): for v in h._elements: - elements.append((r,i,v)) + elements.append((r, i, v)) for cr in h.cover_relations(): cover_relations.append(((r, i, cr[0]), (r, i, cr[1]))) - cover_relations.append(((r,i,h.top()), r)) + cover_relations.append(((r, i, h.top()), r)) return Mobile(DiGraph([elements, cover_relations])) -## RANDOM LATTICES +# RANDOM LATTICES # Following are helper functions for random lattice generation. # There is no parameter checking, 0, 1, ..., n may or may not be a @@ -1939,8 +1944,8 @@ def _random_lattice(n, p): from sage.misc.functional import sqrt from sage.misc.prandom import random - n = n-1 - meets = [[None]*n for _ in range(n)] + n = n - 1 + meets = [[None] * n for _ in range(n)] meets[0][0] = 0 maxs = set([0]) lc_all = [[]] # No lower covers for the bottom element. @@ -2008,11 +2013,11 @@ def _random_dismantlable_lattice(n): """ from sage.misc.prandom import randint - D = DiGraph({0: [n-1]}) - for i in range(1, n-1): - a = randint(0, i//2) + D = DiGraph({0: [n - 1]}) + for i in range(1, n - 1): + a = randint(0, i // 2) b_ = list(D.depth_first_search(a)) - b = b_[randint(1, len(b_)-1)] + b = b_[randint(1, len(b_) - 1)] D.add_vertex(i) D.add_edge(a, i) D.add_edge(i, b) @@ -2053,19 +2058,19 @@ def _random_planar_lattice(n): """ from sage.misc.prandom import randint - G = DiGraph({0: [n-1]}) + G = DiGraph({0: [n - 1]}) while G.order() < n: - i = G.order()-1 - a = randint(0, i//2) + i = G.order() - 1 + a = randint(0, i // 2) b_ = list(G.depth_first_search(a)) - b = b_[randint(1, len(b_)-1)] + b = b_[randint(1, len(b_) - 1)] G1 = G.copy() G.add_vertex(i) G.add_edge(a, i) G.add_edge(i, b) G.delete_edge(a, b) G2 = G.copy() - G2.add_edge(n-1, 0) + G2.add_edge(n - 1, 0) if not G2.is_planar(): G = G1.copy() return G @@ -2102,7 +2107,7 @@ def _random_distributive_lattice(n): from sage.graphs.digraph_generators import digraphs if n < 4: - return digraphs.Path(n-1) + return digraphs.Path(n - 1) H = HasseDiagram({0: []}) while sum(1 for _ in H.antichains_iterator()) < n: @@ -2123,7 +2128,7 @@ def _random_distributive_lattice(n): for b in D.neighbors_out(to_delete): D.add_edge(a, b) D.delete_vertex(to_delete) - D.relabel({z:z-1 for z in range(to_delete + 1, D.order() + 1)}) + D.relabel({z: z - 1 for z in range(to_delete + 1, D.order() + 1)}) H = HasseDiagram(D) return D @@ -2177,4 +2182,5 @@ def _random_stone_lattice(n): return result + posets = Posets diff --git a/src/sage/combinat/posets/posets.py b/src/sage/combinat/posets/posets.py index 2c236e4a4ae..98f7a13b1df 100644 --- a/src/sage/combinat/posets/posets.py +++ b/src/sage/combinat/posets/posets.py @@ -286,6 +286,7 @@ from __future__ import annotations from collections import defaultdict from copy import copy, deepcopy +from typing import List from sage.misc.cachefunc import cached_method from sage.misc.lazy_attribute import lazy_attribute @@ -765,7 +766,8 @@ def Poset(data=None, element_labels=None, cover_relations=False, linear_extensio # Check for duplicate elements elif len(elements) != len(set(elements)): raise ValueError("the provided list of elements is not a linear " - "extension for the poset as it contains duplicate elements") + "extension for the poset as it contains " + "duplicate elements") else: elements = None return FinitePoset(D, elements=elements, category=category, facade=facade, key=key) @@ -2652,7 +2654,7 @@ def relations_number(self): # Maybe this should also be deprecated. intervals_number = relations_number - def linear_intervals_count(self) -> list[int]: + def linear_intervals_count(self) -> List[int]: """ Return the enumeration of linear intervals w.r.t. their cardinality. @@ -8106,7 +8108,7 @@ def is_eulerian(self, k=None, certificate=False): for rank_diff in range(2, k + 1, 2): for level in range(height - rank_diff): for i in levels[level]: - for j in levels[level+rank_diff]: + for j in levels[level + rank_diff]: if H.is_lequal(i, j) and M[i, j] != 1: if certificate: return (False, (self._vertex_to_element(i), @@ -8160,13 +8162,13 @@ def is_greedy(self, certificate=False): True """ H = self._hasse_diagram - N1 = H.order()-1 + N1 = H.order() - 1 it = H.greedy_linear_extensions_iterator() A = next(it) - A_jumps = sum(1 for i in range(N1) if H.has_edge(A[i], A[i+1])) + A_jumps = sum(1 for i in range(N1) if H.has_edge(A[i], A[i + 1])) for B in it: - B_jumps = sum(1 for i in range(N1) if H.has_edge(B[i], B[i+1])) + B_jumps = sum(1 for i in range(N1) if H.has_edge(B[i], B[i + 1])) if A_jumps != B_jumps: if certificate: if A_jumps > B_jumps: @@ -8452,13 +8454,15 @@ def p_partition_enumerator(self, tup, R, weights=None, check=False): # The simple case: ``weights == None``. F = QR.Fundamental() for lin in self.linear_extensions(facade=True): - descents = [i + 1 for i in range(n-1) if tupdict[lin[i]] > tupdict[lin[i+1]]] + descents = [i + 1 for i in range(n - 1) + if tupdict[lin[i]] > tupdict[lin[i + 1]]] res += F(Composition(from_subset=(descents, n))) return res for lin in self.linear_extensions(facade=True): M = QR.Monomial() lin_weights = Composition([weights.get(lin[i], 1) for i in range(n)]) - descents = [i + 1 for i in range(n-1) if tupdict[lin[i]] > tupdict[lin[i+1]]] + descents = [i + 1 for i in range(n - 1) + if tupdict[lin[i]] > tupdict[lin[i + 1]]] d_c = Composition(from_subset=(descents, n)) for comp in d_c.finer(): res += M[lin_weights.fatten(comp)] 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/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/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/powersum.py b/src/sage/combinat/sf/powersum.py index f7542d7929e..f7019407840 100644 --- a/src/sage/combinat/sf/powersum.py +++ b/src/sage/combinat/sf/powersum.py @@ -476,8 +476,8 @@ def frobenius(self, n): :meth:`~sage.combinat.sf.sfa.SymmetricFunctionAlgebra_generic_Element.plethysm` """ - dct = {Partition([n * i for i in lam]): coeff - for (lam, coeff) in self.monomial_coefficients().items()} + dct = {lam.stretch(n): coeff + for lam, coeff in self.monomial_coefficients().items()} return self.parent()._from_dict(dct) adams_operation = frobenius diff --git a/src/sage/combinat/sf/sfa.py b/src/sage/combinat/sf/sfa.py index afeaf70a3e0..83ee11bbe00 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -2601,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 @@ -3072,6 +3072,31 @@ def plethysm(self, x, include=None, exclude=None): sage: sum(s[mu](X)*s[mu.conjugate()](Y) for mu in P5) == sum(m[mu](X)*e[mu](Y) for mu in P5) True + Sage can also do the plethysm with an element in the completion:: + + sage: L = LazySymmetricFunctions(s) + sage: f = s[2,1] + sage: g = L(s[1]) / (1 - L(s[1])); g + s[1] + (s[1,1]+s[2]) + (s[1,1,1]+2*s[2,1]+s[3]) + + (s[1,1,1,1]+3*s[2,1,1]+2*s[2,2]+3*s[3,1]+s[4]) + + (s[1,1,1,1,1]+4*s[2,1,1,1]+5*s[2,2,1]+6*s[3,1,1]+5*s[3,2]+4*s[4,1]+s[5]) + + ... + O^8 + sage: fog = f(g) + sage: fog[:8] + [s[2, 1], + s[1, 1, 1, 1] + 3*s[2, 1, 1] + 2*s[2, 2] + 3*s[3, 1] + s[4], + 2*s[1, 1, 1, 1, 1] + 8*s[2, 1, 1, 1] + 10*s[2, 2, 1] + + 12*s[3, 1, 1] + 10*s[3, 2] + 8*s[4, 1] + 2*s[5], + 3*s[1, 1, 1, 1, 1, 1] + 17*s[2, 1, 1, 1, 1] + 30*s[2, 2, 1, 1] + + 16*s[2, 2, 2] + 33*s[3, 1, 1, 1] + 54*s[3, 2, 1] + 16*s[3, 3] + + 33*s[4, 1, 1] + 30*s[4, 2] + 17*s[5, 1] + 3*s[6], + 5*s[1, 1, 1, 1, 1, 1, 1] + 30*s[2, 1, 1, 1, 1, 1] + 70*s[2, 2, 1, 1, 1] + + 70*s[2, 2, 2, 1] + 75*s[3, 1, 1, 1, 1] + 175*s[3, 2, 1, 1] + + 105*s[3, 2, 2] + 105*s[3, 3, 1] + 100*s[4, 1, 1, 1] + 175*s[4, 2, 1] + + 70*s[4, 3] + 75*s[5, 1, 1] + 70*s[5, 2] + 30*s[6, 1] + 5*s[7]] + sage: parent(fog) + Lazy completion of Symmetric Functions over Rational Field in the Schur basis + .. SEEALSO:: :meth:`frobenius` @@ -3080,66 +3105,100 @@ def plethysm(self, x, include=None, exclude=None): sage: (1+p[2]).plethysm(p[2]) p[] + p[4] + + Check that degree one elements are treated in the correct way:: + + sage: R. = QQ[] + sage: p = SymmetricFunctions(R).p() + sage: f = a1*p[1] + a2*p[2] + a11*p[1,1] + sage: g = b1*p[1] + b21*p[2,1] + b111*p[1,1,1] + sage: r = f(g); r + a1*b1*p[1] + a11*b1^2*p[1, 1] + a1*b111*p[1, 1, 1] + + 2*a11*b1*b111*p[1, 1, 1, 1] + a11*b111^2*p[1, 1, 1, 1, 1, 1] + + a2*b1^2*p[2] + a1*b21*p[2, 1] + 2*a11*b1*b21*p[2, 1, 1] + + 2*a11*b21*b111*p[2, 1, 1, 1, 1] + a11*b21^2*p[2, 2, 1, 1] + + a2*b111^2*p[2, 2, 2] + a2*b21^2*p[4, 2] + sage: r - f(g, include=[]) + (a2*b1^2-a2*b1)*p[2] + (a2*b111^2-a2*b111)*p[2, 2, 2] + (a2*b21^2-a2*b21)*p[4, 2] + + Check that we can compute the plethysm with a constant:: + + sage: p[2,2,1](2) + 8*p[] + + sage: p[2,2,1](int(2)) + 8*p[] + + sage: p[2,2,1](a1) + a1^5*p[] + + sage: X = algebras.Shuffle(QQ, 'ab') + sage: Y = algebras.Shuffle(QQ, 'bc') + sage: T = tensor([X,Y]) + sage: s = SymmetricFunctions(T).s() + sage: s(2*T.one()) + (2*B[]#B[])*s[] + + .. TODO:: + + The implementation of plethysm in + :class:`sage.data_structures.stream.Stream_plethysm` seems + to be faster. This should be investigated. """ parent = self.parent() - R = parent.base_ring() - tHA = HopfAlgebrasWithBasis(R).TensorProducts() - tensorflag = tHA in x.parent().categories() - if not (is_SymmetricFunction(x) or tensorflag): - raise TypeError("only know how to compute plethysms " - "between symmetric functions or tensors " - "of symmetric functions") - p = parent.realization_of().power() - if self == parent.zero(): + if not self: return self - # Handle degree one elements - if include is not None and exclude is not None: - raise RuntimeError("include and exclude cannot both be specified") - - try: - degree_one = [R(g) for g in R.variable_names_recursive()] - except AttributeError: - try: - degree_one = R.gens() - except NotImplementedError: - degree_one = [] - - if include: - degree_one = [R(g) for g in include] - if exclude: - degree_one = [g for g in degree_one if g not in exclude] + R = parent.base_ring() + tHA = HopfAlgebrasWithBasis(R).TensorProducts() + from sage.structure.element import parent as get_parent + Px = get_parent(x) + tensorflag = Px in tHA + if not is_SymmetricFunction(x): + if Px is R: # Handle stuff that is directly in the base ring + x = parent(x) + elif (not tensorflag or any(not isinstance(factor, SymmetricFunctionAlgebra_generic) + for factor in Px._sets)): + from sage.rings.lazy_series import LazySymmetricFunction + if isinstance(x, LazySymmetricFunction): + from sage.rings.lazy_series_ring import LazySymmetricFunctions + L = LazySymmetricFunctions(parent) + return L(self)(x) + + # Try to coerce into a symmetric function + phi = parent.coerce_map_from(Px) + if phi is not None: + x = phi(x) + elif not tensorflag: + raise TypeError("only know how to compute plethysms " + "between symmetric functions or tensors " + "of symmetric functions") - degree_one = [g for g in degree_one if g != R.one()] + p = parent.realization_of().power() - def raise_c(n): - return lambda c: c.subs(**{str(g): g ** n for g in degree_one}) + degree_one = _variables_recursive(R, include=include, exclude=exclude) if tensorflag: - tparents = x.parent()._sets - return tensor([parent]*len(tparents))(sum(d*prod(sum(raise_c(r)(c) - * tensor([p[r].plethysm(base(la)) - for (base,la) in zip(tparents,trm)]) - for (trm,c) in x) - for r in mu) - for (mu, d) in p(self))) - - # Takes in n, and returns a function which takes in a partition and - # scales all of the parts of that partition by n - def scale_part(n): - return lambda m: m.__class__(m.parent(), [i * n for i in m]) - - # Takes n an symmetric function f, and an n and returns the + tparents = Px._sets + s = sum(d * prod(sum(_raise_variables(c, r, degree_one) + * tensor([p[r].plethysm(base(la)) + for base, la in zip(tparents, trm)]) + for trm, c in x) + for r in mu) + for mu, d in p(self)) + return tensor([parent]*len(tparents))(s) + + # Takes a symmetric function f, and an n and returns the # symmetric function with all of its basis partitions scaled # by n def pn_pleth(f, n): - return f.map_support(scale_part(n)) + return f.map_support(lambda mu: mu.stretch(n)) # Takes in a partition and applies p_x = p(x) def f(part): - return p.prod(pn_pleth(p_x.map_coefficients(raise_c(i)), i) + return p.prod(pn_pleth(p_x.map_coefficients(lambda c: _raise_variables(c, i, degree_one)), i) for i in part) return parent(p._apply_module_morphism(p(self), f, codomain=p)) @@ -4917,9 +4976,7 @@ def frobenius(self, n): # then convert back. parent = self.parent() m = parent.realization_of().monomial() - from sage.combinat.partition import Partition - dct = {Partition([n * i for i in lam]): coeff - for (lam, coeff) in m(self)} + dct = {lam.stretch(n): coeff for lam, coeff in m(self)} result_in_m_basis = m._from_dict(dct) return parent(result_in_m_basis) @@ -6125,3 +6182,82 @@ def _nonnegative_coefficients(x): return all(c >= 0 for c in x.coefficients(sparse=False)) else: return x >= 0 + +def _variables_recursive(R, include=None, exclude=None): + """ + Return all variables appearing in the ring ``R``. + + INPUT: + + - ``R`` -- a :class:`Ring` + - ``include``, ``exclude`` (optional, default ``None``) -- + iterables of variables in ``R`` + + OUTPUT: + + - If ``include`` is specified, only these variables are returned + as elements of ``R``. Otherwise, all variables in ``R`` + (recursively) with the exception of those in ``exclude`` are + returned. + + EXAMPLES:: + + sage: from sage.combinat.sf.sfa import _variables_recursive + sage: R. = QQ[] + sage: S. = R[] + sage: _variables_recursive(S) + [a, b, t] + + sage: _variables_recursive(S, exclude=[b]) + [a, t] + + sage: _variables_recursive(S, include=[b]) + [b] + + TESTS:: + + sage: _variables_recursive(R.fraction_field(), exclude=[b]) + [a] + + sage: _variables_recursive(S.fraction_field(), exclude=[b]) # known bug + [a, t] + """ + if include is not None and exclude is not None: + raise RuntimeError("include and exclude cannot both be specified") + + if include is not None: + degree_one = [R(g) for g in include] + else: + try: + degree_one = [R(g) for g in R.variable_names_recursive()] + except AttributeError: + try: + degree_one = R.gens() + except NotImplementedError: + degree_one = [] + if exclude is not None: + degree_one = [g for g in degree_one if g not in exclude] + + return [g for g in degree_one if g != R.one()] + +def _raise_variables(c, n, variables): + """ + Replace the given variables in the ring element ``c`` with their + ``n``-th power. + + INPUT: + + - ``c`` -- an element of a ring + - ``n`` -- the power to raise the given variables to + - ``variables`` -- the variables to raise + + EXAMPLES:: + + sage: from sage.combinat.sf.sfa import _raise_variables + sage: R. = QQ[] + sage: S. = R[] + sage: _raise_variables(2*a + 3*b*t, 2, [a, t]) + 3*b*t^2 + 2*a^2 + + """ + return c.subs(**{str(g): g ** n for g in variables}) 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 e349708c1ed..fc5a99a7a60 100644 --- a/src/sage/data_structures/stream.py +++ b/src/sage/data_structures/stream.py @@ -21,10 +21,10 @@ example, we can add two streams:: sage: from sage.data_structures.stream import * - sage: f = Stream_function(lambda n: n, QQ, True, 0) + sage: f = Stream_function(lambda n: n, True, 0) sage: [f[i] for i in range(10)] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - sage: g = Stream_function(lambda n: 1, QQ, True, 0) + sage: g = Stream_function(lambda n: 1, True, 0) sage: [g[i] for i in range(10)] [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] sage: h = Stream_add(f, g) @@ -52,7 +52,7 @@ Two streams can be composed:: - sage: g = Stream_function(lambda n: n, QQ, True, 1) + sage: g = Stream_function(lambda n: n, True, 1) sage: h = Stream_cauchy_compose(f, g) sage: [h[i] for i in range(10)] [0, 1, 4, 14, 46, 145, 444, 1331, 3926, 11434] @@ -71,7 +71,7 @@ Finally, we can apply an arbitrary functions to the elements of a stream:: - sage: h = Stream_map_coefficients(f, lambda n: n^2, QQ) + sage: h = Stream_map_coefficients(f, lambda n: n^2) sage: [h[i] for i in range(10)] [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] @@ -80,7 +80,6 @@ - Kwankyu Lee (2019-02-24): initial version - Tejasvi Chebrolu, Martin Rubey, Travis Scrimshaw (2021-08): refactored and expanded functionality - """ # **************************************************************************** @@ -98,7 +97,11 @@ from sage.rings.integer_ring import ZZ from sage.rings.infinity import infinity from sage.arith.misc import divisors +from sage.misc.misc_c import prod from sage.combinat.integer_vector_weighted import iterator_fast as wt_int_vec_iter +from sage.combinat.sf.sfa import _variables_recursive, _raise_variables +from sage.categories.hopf_algebras_with_basis import HopfAlgebrasWithBasis + class Stream(): """ @@ -168,6 +171,15 @@ class Stream_inexact(Stream): - ``sparse`` -- boolean; whether the implementation of the stream is sparse - ``approximate_order`` -- integer; a lower bound for the order of the stream + + .. TODO:: + + The ``approximate_order`` is currently only updated when + invoking :meth:`order`. It might make sense to update it + whenever the coefficient one larger than the current + ``approximate_order`` is computed, since in some methods this + will allow shortcuts. + """ def __init__(self, is_sparse, approximate_order): """ @@ -178,7 +190,7 @@ def __init__(self, is_sparse, approximate_order): sage: from sage.data_structures.stream import Stream_inexact sage: from sage.data_structures.stream import Stream_function - sage: g = Stream_function(lambda n: n, QQ, False, 0) + sage: g = Stream_function(lambda n: n, False, 0) sage: isinstance(g, Stream_inexact) True """ @@ -198,7 +210,7 @@ def is_nonzero(self): EXAMPLES:: sage: from sage.data_structures.stream import Stream_function - sage: CS = Stream_function(lambda n: 1/n, ZZ, False, 1) + sage: CS = Stream_function(lambda n: 1/n, False, 1) sage: CS.is_nonzero() False sage: CS[1] @@ -291,7 +303,7 @@ def __getitem__(self, n): EXAMPLES:: sage: from sage.data_structures.stream import Stream_function - sage: f = Stream_function(lambda n: n^2, QQ, True, 0) + sage: f = Stream_function(lambda n: n^2, True, 0) sage: f[3] 9 sage: f._cache @@ -301,7 +313,7 @@ def __getitem__(self, n): sage: f._cache {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81} - sage: f = Stream_function(lambda n: n^2, QQ, False, 0) + sage: f = Stream_function(lambda n: n^2, False, 0) sage: f[3] 9 sage: f._cache @@ -339,14 +351,14 @@ def iterate_coefficients(self): EXAMPLES:: sage: from sage.data_structures.stream import Stream_function, Stream_cauchy_compose - sage: f = Stream_function(lambda n: 1, ZZ, False, 1) - sage: g = Stream_function(lambda n: n^3, ZZ, False, 1) + sage: f = Stream_function(lambda n: 1, False, 1) + sage: g = Stream_function(lambda n: n^3, False, 1) sage: h = Stream_cauchy_compose(f, g) sage: n = h.iterate_coefficients() sage: [next(n) for i in range(10)] [1, 9, 44, 207, 991, 4752, 22769, 109089, 522676, 2504295] """ - n = self._approximate_order + n = self._approximate_order # TODO: shouldn't this be self._offset? while True: yield self.get_coefficient(n) n += 1 @@ -359,7 +371,7 @@ def order(self): EXAMPLES:: sage: from sage.data_structures.stream import Stream_function - sage: f = Stream_function(lambda n: n, QQ, True, 0) + sage: f = Stream_function(lambda n: n, True, 0) sage: f.order() 1 """ @@ -402,8 +414,8 @@ def __ne__(self, other): EXAMPLES:: sage: from sage.data_structures.stream import Stream_function - sage: f = Stream_function(lambda n: n, QQ, True, 0) - sage: g = Stream_function(lambda n: n^2, QQ, True, 0) + sage: f = Stream_function(lambda n: n, True, 0) + sage: g = Stream_function(lambda n: n^2, True, 0) sage: f != g False sage: f[1], g[1] @@ -421,8 +433,8 @@ def __ne__(self, other): Checking the dense implementation:: - sage: f = Stream_function(lambda n: n if n > 0 else 0, QQ, False, -3) - sage: g = Stream_function(lambda n: n^2, QQ, False, 0) + sage: f = Stream_function(lambda n: n if n > 0 else 0, False, -3) + sage: g = Stream_function(lambda n: n^2, False, 0) sage: f != g False sage: g != f @@ -438,8 +450,8 @@ def __ne__(self, other): sage: g != f True - sage: f = Stream_function(lambda n: n if n > 0 else 0, QQ, False, -3) - sage: g = Stream_function(lambda n: n^2, QQ, False, 0) + sage: f = Stream_function(lambda n: n if n > 0 else 0, False, -3) + sage: g = Stream_function(lambda n: n^2, False, 0) sage: _ = f[5], g[1] sage: f != g False @@ -451,8 +463,8 @@ def __ne__(self, other): sage: g != f True - sage: f = Stream_function(lambda n: n if n > 0 else 0, QQ, False, -3) - sage: g = Stream_function(lambda n: n^2, QQ, False, 0) + sage: f = Stream_function(lambda n: n if n > 0 else 0, False, -3) + sage: g = Stream_function(lambda n: n^2, False, 0) sage: _ = g[5], f[1] sage: f != g False @@ -471,7 +483,7 @@ def __ne__(self, other): for i in self._cache: if i in other._cache and other._cache[i] != self._cache[i]: return True - else: # they are dense + else: # they are dense # Make ``self`` have the smaller approximate order. if self._approximate_order > other._approximate_order: self, other = other, self @@ -491,6 +503,7 @@ def __ne__(self, other): return False + class Stream_exact(Stream): r""" A stream of eventually constant coefficients. @@ -533,7 +546,8 @@ def __init__(self, initial_coefficients, is_sparse, constant=None, degree=None, # We do not insist that the last entry of # initial_coefficients is different from constant in case - # comparisons can be expensive such as in the symbolic ring + # comparisons can be expensive such as in the symbolic ring, + # but we remove zeros for i, v in enumerate(initial_coefficients): if v: order += i @@ -686,7 +700,7 @@ def __ne__(self, other): return ``False``:: sage: from sage.data_structures.stream import Stream_function - sage: f = Stream_function(lambda n: 2 if n == 0 else 1, ZZ, False, 0) + sage: f = Stream_function(lambda n: 2 if n == 0 else 1, False, 0) sage: s == f False sage: s != f @@ -743,7 +757,6 @@ class Stream_function(Stream_inexact): - ``function`` -- a function that generates the coefficients of the stream - - ``ring`` -- the base ring - ``is_sparse`` -- boolean; specifies whether the stream is sparse - ``approximate_order`` -- integer; a lower bound for the order of the stream @@ -751,62 +764,34 @@ class Stream_function(Stream_inexact): EXAMPLES:: sage: from sage.data_structures.stream import Stream_function - sage: f = Stream_function(lambda n: n^2, ZZ, False, 1) + sage: f = Stream_function(lambda n: n^2, False, 1) sage: f[3] 9 sage: [f[i] for i in range(10)] [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] - """ - def __init__(self, function, ring, is_sparse, approximate_order): + sage: f = Stream_function(lambda n: 1, False, 0) + sage: n = f.iterate_coefficients() + sage: [next(n) for _ in range(10)] + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + + sage: f = Stream_function(lambda n: n, True, 0) + sage: f.get_coefficient(4) + 4 + """ + def __init__(self, function, is_sparse, approximate_order): """ Initialize. TESTS:: sage: from sage.data_structures.stream import Stream_function - sage: f = Stream_function(lambda n: 1, ZZ, False, 1) + sage: f = Stream_function(lambda n: 1, False, 1) sage: TestSuite(f).run(skip="_test_pickling") """ - self._function = function - self._ring = ring + self.get_coefficient = function super().__init__(is_sparse, approximate_order) - def get_coefficient(self, n): - """ - 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 - sage: f = Stream_function(lambda n: n, QQ, True, 0) - sage: f.get_coefficient(4) - 4 - """ - return self._ring(self._function(n)) - - def iterate_coefficients(self): - """ - A generator for the coefficients of ``self``. - - EXAMPLES:: - - sage: from sage.data_structures.stream import Stream_function - sage: f = Stream_function(lambda n: 1, QQ, False, 0) - sage: n = f.iterate_coefficients() - sage: [next(n) for _ in range(10)] - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - """ - n = self._offset - ring = self._ring - while True: - yield ring(self._function(n)) - n += 1 - class Stream_uninitialized(Stream_inexact): r""" @@ -897,7 +882,7 @@ class Stream_unary(Stream_inexact): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_function, Stream_cauchy_invert, Stream_lmul) - sage: f = Stream_function(lambda n: 2*n, ZZ, False, 1) + sage: f = Stream_function(lambda n: 2*n, False, 1) sage: g = Stream_cauchy_invert(f) sage: [g[i] for i in range(10)] [-1, 1/2, 0, 0, 0, 0, 0, 0, 0, 0] @@ -931,7 +916,7 @@ def __hash__(self): sage: from sage.data_structures.stream import Stream_unary sage: from sage.data_structures.stream import Stream_function - sage: M = Stream_unary(Stream_function(lambda n: 1, ZZ, False, 1), True, 0) + sage: M = Stream_unary(Stream_function(lambda n: 1, False, 1), True, 0) sage: hash(M) == hash(M) True """ @@ -948,8 +933,8 @@ def __eq__(self, other): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_function, Stream_rmul) - sage: f = Stream_function(lambda n: 2*n, ZZ, False, 1) - sage: g = Stream_function(lambda n: n, ZZ, False, 1) + sage: f = Stream_function(lambda n: 2*n, False, 1) + sage: g = Stream_function(lambda n: n, False, 1) sage: h = Stream_rmul(f, 2) sage: n = Stream_rmul(g, 2) sage: h == n @@ -974,8 +959,8 @@ class Stream_binary(Stream_inexact): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_function, Stream_add, Stream_sub) - sage: f = Stream_function(lambda n: 2*n, ZZ, True, 0) - sage: g = Stream_function(lambda n: n, ZZ, True, 1) + sage: f = Stream_function(lambda n: 2*n, True, 0) + sage: g = Stream_function(lambda n: n, True, 1) sage: h = Stream_add(f, g) sage: [h[i] for i in range(10)] [0, 3, 6, 9, 12, 15, 18, 21, 24, 27] @@ -1013,8 +998,8 @@ def __hash__(self): sage: from sage.data_structures.stream import Stream_binary sage: from sage.data_structures.stream import Stream_function - sage: M = Stream_function(lambda n: n, ZZ, True, 0) - sage: N = Stream_function(lambda n: -2*n, ZZ, True, 0) + sage: M = Stream_function(lambda n: n, True, 0) + sage: N = Stream_function(lambda n: -2*n, True, 0) sage: O = Stream_binary(M, N, True, 0) sage: hash(O) == hash(O) True @@ -1032,9 +1017,9 @@ def __eq__(self, other): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_function, Stream_cauchy_mul) - sage: f = Stream_function(lambda n: 2*n, ZZ, False, 1) - sage: g = Stream_function(lambda n: n, ZZ, False, 1) - sage: h = Stream_function(lambda n: 1, ZZ, False, 1) + sage: f = Stream_function(lambda n: 2*n, False, 1) + sage: g = Stream_function(lambda n: n, False, 1) + sage: h = Stream_function(lambda n: 1, False, 1) sage: t = Stream_cauchy_mul(f, g) sage: u = Stream_cauchy_mul(g, h) sage: v = Stream_cauchy_mul(h, f) @@ -1057,8 +1042,8 @@ class Stream_binaryCommutative(Stream_binary): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_function, Stream_add) - sage: f = Stream_function(lambda n: 2*n, ZZ, True, 0) - sage: g = Stream_function(lambda n: n, ZZ, True, 1) + sage: f = Stream_function(lambda n: 2*n, True, 0) + sage: g = Stream_function(lambda n: n, True, 1) sage: h = Stream_add(f, g) sage: [h[i] for i in range(10)] [0, 3, 6, 9, 12, 15, 18, 21, 24, 27] @@ -1075,8 +1060,8 @@ def __hash__(self): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_function, Stream_add) - sage: f = Stream_function(lambda n: 2*n, ZZ, True, 0) - sage: g = Stream_function(lambda n: n, ZZ, True, 1) + sage: f = Stream_function(lambda n: 2*n, True, 0) + sage: g = Stream_function(lambda n: n, True, 1) sage: h = Stream_add(f, g) sage: u = Stream_add(g, f) sage: hash(h) == hash(u) @@ -1095,8 +1080,8 @@ def __eq__(self, other): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_function, Stream_add) - sage: f = Stream_function(lambda n: 2*n, ZZ, True, 0) - sage: g = Stream_function(lambda n: n, ZZ, True, 1) + sage: f = Stream_function(lambda n: 2*n, True, 0) + sage: g = Stream_function(lambda n: n, True, 1) sage: h = Stream_add(f, g) sage: [h[i] for i in range(10)] [0, 3, 6, 9, 12, 15, 18, 21, 24, 27] @@ -1221,8 +1206,8 @@ class Stream_add(Stream_binaryCommutative): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_add, Stream_function) - sage: f = Stream_function(lambda n: n, ZZ, True, 0) - sage: g = Stream_function(lambda n: 1, ZZ, True, 0) + sage: f = Stream_function(lambda n: n, True, 0) + sage: g = Stream_function(lambda n: 1, True, 0) sage: h = Stream_add(f, g) sage: [h[i] for i in range(10)] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] @@ -1237,8 +1222,8 @@ def __init__(self, left, right): TESTS:: sage: from sage.data_structures.stream import (Stream_function, Stream_add) - sage: f = Stream_function(lambda n: 1, ZZ, True, 0) - sage: g = Stream_function(lambda n: n^2, ZZ, True, 0) + sage: f = Stream_function(lambda n: 1, True, 0) + sage: g = Stream_function(lambda n: n^2, True, 0) sage: h = Stream_add(f, g) """ if left._is_sparse != right._is_sparse: @@ -1258,8 +1243,8 @@ def get_coefficient(self, n): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_function, Stream_add) - sage: f = Stream_function(lambda n: n, ZZ, True, 0) - sage: g = Stream_function(lambda n: n^2, ZZ, True, 0) + sage: f = Stream_function(lambda n: n, True, 0) + sage: g = Stream_function(lambda n: n^2, True, 0) sage: h = Stream_add(f, g) sage: h.get_coefficient(5) 30 @@ -1281,8 +1266,8 @@ class Stream_sub(Stream_binary): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_sub, Stream_function) - sage: f = Stream_function(lambda n: n, ZZ, True, 0) - sage: g = Stream_function(lambda n: 1, ZZ, True, 0) + sage: f = Stream_function(lambda n: n, True, 0) + sage: g = Stream_function(lambda n: 1, True, 0) sage: h = Stream_sub(f, g) sage: [h[i] for i in range(10)] [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8] @@ -1298,8 +1283,8 @@ def __init__(self, left, right): TESTS:: sage: from sage.data_structures.stream import (Stream_function, Stream_sub) - sage: f = Stream_function(lambda n: 1, ZZ, True, 0) - sage: g = Stream_function(lambda n: n^2, ZZ, True, 0) + sage: f = Stream_function(lambda n: 1, True, 0) + sage: g = Stream_function(lambda n: n^2, True, 0) sage: h = Stream_sub(f, g) """ if left._is_sparse != right._is_sparse: @@ -1319,8 +1304,8 @@ def get_coefficient(self, n): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_function, Stream_sub) - sage: f = Stream_function(lambda n: n, ZZ, True, 0) - sage: g = Stream_function(lambda n: n^2, ZZ, True, 0) + sage: f = Stream_function(lambda n: n, True, 0) + sage: g = Stream_function(lambda n: n^2, True, 0) sage: h = Stream_sub(f, g) sage: h.get_coefficient(5) -20 @@ -1346,8 +1331,8 @@ class Stream_cauchy_mul(Stream_binary): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_cauchy_mul, Stream_function) - sage: f = Stream_function(lambda n: n, ZZ, True, 0) - sage: g = Stream_function(lambda n: 1, ZZ, True, 0) + sage: f = Stream_function(lambda n: n, True, 0) + sage: g = Stream_function(lambda n: 1, True, 0) sage: h = Stream_cauchy_mul(f, g) sage: [h[i] for i in range(10)] [0, 1, 3, 6, 10, 15, 21, 28, 36, 45] @@ -1362,8 +1347,8 @@ def __init__(self, left, right): TESTS:: sage: from sage.data_structures.stream import (Stream_function, Stream_cauchy_mul) - sage: f = Stream_function(lambda n: 1, ZZ, True, 0) - sage: g = Stream_function(lambda n: n^2, ZZ, True, 0) + sage: f = Stream_function(lambda n: 1, True, 0) + sage: g = Stream_function(lambda n: n^2, True, 0) sage: h = Stream_cauchy_mul(f, g) """ if left._is_sparse != right._is_sparse: @@ -1383,8 +1368,8 @@ def get_coefficient(self, n): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_function, Stream_cauchy_mul) - sage: f = Stream_function(lambda n: n, ZZ, True, 0) - sage: g = Stream_function(lambda n: n^2, ZZ, True, 0) + sage: f = Stream_function(lambda n: n, True, 0) + sage: g = Stream_function(lambda n: n^2, True, 0) sage: h = Stream_cauchy_mul(f, g) sage: h.get_coefficient(5) 50 @@ -1408,7 +1393,7 @@ def is_nonzero(self): sage: from sage.data_structures.stream import (Stream_function, ....: Stream_cauchy_mul, Stream_cauchy_invert) - sage: f = Stream_function(lambda n: n, ZZ, True, 1) + sage: f = Stream_function(lambda n: n, True, 1) sage: g = Stream_cauchy_mul(f, f) sage: g.is_nonzero() False @@ -1435,7 +1420,7 @@ class Stream_dirichlet_convolve(Stream_binary): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_dirichlet_convolve, Stream_function, Stream_exact) - sage: f = Stream_function(lambda n: n, ZZ, True, 1) + sage: f = Stream_function(lambda n: n, True, 1) sage: g = Stream_exact([0], True, constant=1) sage: h = Stream_dirichlet_convolve(f, g) sage: [h[i] for i in range(1, 10)] @@ -1449,10 +1434,10 @@ class Stream_dirichlet_convolve(Stream_binary): """ def __init__(self, left, right): """ - Initalize ``self``. + Initialize ``self``. sage: from sage.data_structures.stream import (Stream_dirichlet_convolve, Stream_function, Stream_exact) - sage: f = Stream_function(lambda n: n, ZZ, True, 1) + sage: f = Stream_function(lambda n: n, True, 1) sage: g = Stream_exact([1], True, constant=0) sage: Stream_dirichlet_convolve(f, g) Traceback (most recent call last): @@ -1484,7 +1469,7 @@ def get_coefficient(self, n): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_dirichlet_convolve, Stream_function, Stream_exact) - sage: f = Stream_function(lambda n: n, ZZ, True, 1) + sage: f = Stream_function(lambda n: n, True, 1) sage: g = Stream_exact([0], True, constant=1) sage: h = Stream_dirichlet_convolve(f, g) sage: h.get_coefficient(7) @@ -1513,7 +1498,7 @@ class Stream_dirichlet_invert(Stream_unary): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_dirichlet_invert, Stream_function) - sage: f = Stream_function(lambda n: 1, ZZ, True, 1) + sage: f = Stream_function(lambda n: 1, True, 1) sage: g = Stream_dirichlet_invert(f) sage: [g[i] for i in range(10)] [0, 1, -1, -1, 0, -1, 1, -1, 0, 0] @@ -1582,8 +1567,8 @@ class Stream_cauchy_compose(Stream_binary): EXAMPLES:: sage: from sage.data_structures.stream import Stream_cauchy_compose, Stream_function - sage: f = Stream_function(lambda n: n, ZZ, True, 1) - sage: g = Stream_function(lambda n: 1, ZZ, True, 1) + sage: f = Stream_function(lambda n: n, True, 1) + sage: g = Stream_function(lambda n: 1, True, 1) sage: h = Stream_cauchy_compose(f, g) sage: [h[i] for i in range(10)] [0, 1, 3, 8, 20, 48, 112, 256, 576, 1280] @@ -1598,23 +1583,21 @@ def __init__(self, f, g): TESTS:: sage: from sage.data_structures.stream import Stream_function, Stream_cauchy_compose - sage: f = Stream_function(lambda n: 1, ZZ, True, 1) - sage: g = Stream_function(lambda n: n^2, ZZ, True, 1) + sage: f = Stream_function(lambda n: 1, True, 1) + sage: g = Stream_function(lambda n: n^2, True, 1) sage: h = Stream_cauchy_compose(f, g) """ #assert g._approximate_order > 0 - self._fv = f._approximate_order - self._gv = g._approximate_order - if self._fv < 0: + if f._approximate_order < 0: ginv = Stream_cauchy_invert(g) # The constant part makes no contribution to the negative. # We need this for the case so self._neg_powers[0][n] => 0. self._neg_powers = [Stream_zero(f._is_sparse), ginv] - for i in range(1, -self._fv): + for i in range(1, -f._approximate_order): self._neg_powers.append(Stream_cauchy_mul(self._neg_powers[-1], ginv)) # Placeholder None to make this 1-based. self._pos_powers = [None, g] - val = self._fv * self._gv + val = f._approximate_order * g._approximate_order super().__init__(f, g, f._is_sparse, val) def get_coefficient(self, n): @@ -1628,23 +1611,26 @@ def get_coefficient(self, n): EXAMPLES:: sage: from sage.data_structures.stream import Stream_function, Stream_cauchy_compose - sage: f = Stream_function(lambda n: n, ZZ, True, 1) - sage: g = Stream_function(lambda n: n^2, ZZ, True, 1) + sage: f = Stream_function(lambda n: n, True, 1) + sage: g = Stream_function(lambda n: n^2, True, 1) sage: h = Stream_cauchy_compose(f, g) sage: h.get_coefficient(5) 527 sage: [h.get_coefficient(i) for i in range(10)] [0, 1, 6, 28, 124, 527, 2172, 8755, 34704, 135772] """ + fv = self._left._approximate_order + gv = self._right._approximate_order if n < 0: - return sum(self._left[i] * self._neg_powers[-i][n] for i in range(self._fv, n // self._gv + 1)) + return sum(self._left[i] * self._neg_powers[-i][n] + for i in range(fv, n // gv + 1)) # n > 0 - while len(self._pos_powers) <= n // self._gv: + while len(self._pos_powers) <= n // gv: self._pos_powers.append(Stream_cauchy_mul(self._pos_powers[-1], self._right)) - ret = sum(self._left[i] * self._neg_powers[-i][n] for i in range(self._fv, 0)) + ret = sum(self._left[i] * self._neg_powers[-i][n] for i in range(fv, 0)) if n == 0: ret += self._left[0] - return ret + sum(self._left[i] * self._pos_powers[i][n] for i in range(1, n // self._gv+1)) + return ret + sum(self._left[i] * self._pos_powers[i][n] for i in range(1, n // gv+1)) class Stream_plethysm(Stream_binary): @@ -1652,37 +1638,89 @@ class Stream_plethysm(Stream_binary): 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. + a ring of symmetric functions. INPUT: - ``f`` -- a :class:`Stream` - - ``g`` -- a :class:`Stream` with positive order - - ``p`` -- the powersum symmetric functions + - ``g`` -- a :class:`Stream` with positive order, unless ``f`` is + of :class:`Stream_exact`. + - ``p`` -- the ring of powersum symmetric functions containing ``g`` + - ``ring`` (optional, default ``None``) -- the ring the result + should be in, by default ``p`` + - ``include`` -- a list of variables to be treated as degree one + elements instead of the default degree one elements + - ``exclude`` -- a list of variables to be excluded from the + default degree one elements EXAMPLES:: - sage: from sage.data_structures.stream import Stream_function, Stream_plethysm + 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)] + sage: f = Stream_function(lambda n: s[n], True, 1) + sage: g = Stream_function(lambda n: s[[1]*n], True, 1) + sage: h = Stream_plethysm(f, g, p, s) + sage: [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)] + sage: u = Stream_plethysm(g, f, p, s) + sage: [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]] + + This class also handles the plethysm of an exact stream with a + stream of order `0`:: + + sage: from sage.data_structures.stream import Stream_exact + sage: f = Stream_exact([s[1]], True, order=1) + sage: g = Stream_function(lambda n: s[n], True, 0) + sage: r = Stream_plethysm(f, g, p, s) + sage: [r[n] for n in range(3)] + [s[], s[1], s[2]] + + TESTS: + + Check corner cases:: + + sage: f0 = Stream_exact([p([])], True) + sage: f1 = Stream_exact([p[1]], True, order=1) + sage: f2 = Stream_exact([p[2]], True, order=2 ) + sage: f11 = Stream_exact([p[1,1]], True, order=2 ) + sage: r = Stream_plethysm(f0, f1, p); [r[n] for n in range(3)] + [p[], 0, 0] + sage: r = Stream_plethysm(f0, f2, p); [r[n] for n in range(3)] + [p[], 0, 0] + sage: r = Stream_plethysm(f0, f11, p); [r[n] for n in range(3)] + [p[], 0, 0] + + Check that degree one elements are treated in the correct way:: + + sage: R. = QQ[]; p = SymmetricFunctions(R).p() + sage: f_s = a1*p[1] + a2*p[2] + a11*p[1,1] + sage: g_s = b1*p[1] + b21*p[2,1] + b111*p[1,1,1] + sage: r_s = f_s(g_s) + sage: f = Stream_exact([f_s.restrict_degree(k) for k in range(f_s.degree()+1)], True) + sage: g = Stream_exact([g_s.restrict_degree(k) for k in range(g_s.degree()+1)], True) + sage: r = Stream_plethysm(f, g, p) + sage: r_s == sum(r[n] for n in range(2*(r_s.degree()+1))) + True + + sage: r_s - f_s(g_s, include=[]) + (a2*b1^2-a2*b1)*p[2] + (a2*b111^2-a2*b111)*p[2, 2, 2] + (a2*b21^2-a2*b21)*p[4, 2] + + sage: r2 = Stream_plethysm(f, g, p, include=[]) + sage: r_s - sum(r2[n] for n in range(2*(r_s.degree()+1))) + (a2*b1^2-a2*b1)*p[2] + (a2*b111^2-a2*b111)*p[2, 2, 2] + (a2*b21^2-a2*b21)*p[4, 2] + """ - def __init__(self, f, g, p): + def __init__(self, f, g, p, ring=None, include=None, exclude=None): r""" Initialize ``self``. @@ -1691,15 +1729,32 @@ def __init__(self, f, g, p): 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: f = Stream_function(lambda n: s[n], True, 1) + sage: g = Stream_function(lambda n: s[n-1,1], True, 2) sage: h = Stream_plethysm(f, g, p) """ - #assert g._approximate_order > 0 - self._fv = f._approximate_order - self._gv = g._approximate_order + # assert g._approximate_order > 0 + val = f._approximate_order * g._approximate_order + if ring is None: + self._basis = p + else: + self._basis = ring self._p = p - val = self._fv * self._gv + g = Stream_map_coefficients(g, lambda x: p(x)) + self._powers = [g] # a cache for the powers of g + R = self._basis.base_ring() + self._degree_one = _variables_recursive(R, include=include, exclude=exclude) + if isinstance(f, Stream_exact) and not f._constant: + self._degree_f = f._degree + else: + self._degree_f = None + if HopfAlgebrasWithBasis(R).TensorProducts() in p.categories(): + self._tensor_power = len(p._sets) + p_f = p._sets[0] + f = Stream_map_coefficients(f, lambda x: p_f(x)) + else: + self._tensor_power = None + f = Stream_map_coefficients(f, lambda x: p(x)) super().__init__(f, g, f._is_sparse, val) def get_coefficient(self, n): @@ -1715,8 +1770,8 @@ def get_coefficient(self, n): 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: f = Stream_function(lambda n: s[n], True, 1) + sage: g = Stream_function(lambda n: s[[1]*n], 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] @@ -1728,51 +1783,127 @@ def get_coefficient(self, n): 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 + if not n: # special case of 0 + if self._right[0]: + assert self._degree_f is not None, "the plethysm with a lazy symmetric function of valuation 0 is defined only for symmetric functions of finite support" - def _compute_product(self, n, la, c): - """ + return sum((c * self.compute_product(n, la) + for k in range(self._left._approximate_order, self._degree_f) + if self._left[k] + for la, c in self._left[k]), + self._basis.zero()) + + res = sum((c * self.compute_product(n, la) + for k in range(self._left._approximate_order, n+1) + if self._left[k] + for la, c in self._left[k]), + self._basis.zero()) + return res + + def compute_product(self, n, la): + r""" 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: from sage.data_structures.stream import Stream_plethysm, Stream_exact, Stream_function, Stream_zero sage: s = SymmetricFunctions(QQ).s() sage: p = SymmetricFunctions(QQ).p() - sage: f = Stream_function(lambda n: s[n], s, True, 1) + sage: f = Stream_zero(True) # irrelevant for this test 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 + sage: A = h.compute_product(7, Partition([2, 1])); A 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) + sage: A == p[2, 1](s[2] + s[3]).homogeneous_component(7) + True + + sage: p2 = tensor([p, p]) + sage: f = Stream_zero(True) # irrelevant for this test + sage: g = Stream_function(lambda n: sum(tensor([p[k], p[n-k]]) for k in range(n+1)), True, 1) + sage: h = Stream_plethysm(f, g, p2) + sage: A = h.compute_product(7, Partition([2, 1])) + sage: B = p[2, 1](sum(g[n] for n in range(7))) + sage: B = p2.element_class(p2, {m: c for m, c in B if sum(mu.size() for mu in m) == 7}) + sage: A == B + True + + sage: f = Stream_zero(True) # irrelevant for this test + sage: g = Stream_function(lambda n: s[n], True, 0) + sage: h = Stream_plethysm(f, g, p) + sage: B = p[2, 2, 1](sum(s[i] for i in range(7))) + sage: all(h.compute_product(k, Partition([2, 2, 1])) == B.restrict_degree(k) for k in range(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 + # This is the approximate order of the result + rao = self._right._approximate_order + ret_approx_order = rao * sum(la) + ret = self._basis.zero() + if n < ret_approx_order: + return ret + + la_exp = la.to_exp() + wgt = [i for i, m in enumerate(la_exp, 1) if m] + exp = [m for m in la_exp if m] + # the docstring of wt_int_vec_iter, i.e., iterator_fast, + # states that the weights should be weakly decreasing + wgt.reverse() + exp.reverse() + for k in wt_int_vec_iter(n - ret_approx_order, wgt): + # TODO: it may make a big difference here if the + # approximate order would be updated. + # The test below is based on not removing the fixed block + #if any(d < self._right._approximate_order * m + # for m, d in zip(exp, k)): + # continue + ret += prod(self.stretched_power_restrict_degree(i, m, rao * m + d) + for i, m, d in zip(wgt, exp, k)) return ret + def stretched_power_restrict_degree(self, i, m, d): + r""" + Return the degree ``d*i`` part of ``p([i]*m)(g)``. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_plethysm, Stream_exact, Stream_function, Stream_zero + sage: s = SymmetricFunctions(QQ).s() + sage: p = SymmetricFunctions(QQ).p() + sage: f = Stream_zero(False) # irrelevant for this test + sage: g = Stream_exact([s[2], s[3]], False, 0, 4, 2) + sage: h = Stream_plethysm(f, g, p) + sage: A = h.stretched_power_restrict_degree(2, 3, 6) + sage: A == p[2,2,2](s[2] + s[3]).homogeneous_component(12) + True + + sage: p2 = tensor([p, p]) + sage: f = Stream_zero(True) # irrelevant for this test + sage: g = Stream_function(lambda n: sum(tensor([p[k], p[n-k]]) for k in range(n+1)), True, 1) + sage: h = Stream_plethysm(f, g, p2) + sage: A = h.stretched_power_restrict_degree(2, 3, 6) + sage: B = p[2,2,2](sum(g[n] for n in range(7))) + sage: B = p2.element_class(p2, {m: c for m, c in B if sum(mu.size() for mu in m) == 12}) + sage: A == B + True + """ + while len(self._powers) < m: + self._powers.append(Stream_cauchy_mul(self._powers[-1], self._powers[0])) + power_d = self._powers[m-1][d] + # we have to check power_d for zero because it might be an + # integer and not a symmetric function + if power_d: + if self._tensor_power is None: + terms = {mon.stretch(i): raised_c for mon, c in power_d + if (raised_c := _raise_variables(c, i, self._degree_one))} + else: + terms = {tuple((mu.stretch(i) for mu in mon)): raised_c + for mon, c in power_d + if (raised_c := _raise_variables(c, i, self._degree_one))} + return self._p.element_class(self._p, terms) + + return self._p.zero() + + ##################################################################### # Unary operations @@ -1787,7 +1918,7 @@ def __init__(self, series, scalar): TESTS:: sage: from sage.data_structures.stream import (Stream_rmul, Stream_function) - sage: f = Stream_function(lambda n: -1, ZZ, True, 0) + sage: f = Stream_function(lambda n: -1, True, 0) sage: g = Stream_rmul(f, 3) """ self._series = series @@ -1803,7 +1934,7 @@ def __hash__(self): sage: from sage.data_structures.stream import Stream_function sage: from sage.data_structures.stream import Stream_rmul - sage: a = Stream_function(lambda n: 2*n, ZZ, False, 1) + sage: a = Stream_function(lambda n: 2*n, False, 1) sage: f = Stream_rmul(a, 2) sage: hash(f) == hash(f) True @@ -1822,8 +1953,8 @@ def __eq__(self, other): sage: from sage.data_structures.stream import Stream_function sage: from sage.data_structures.stream import Stream_rmul, Stream_lmul - sage: a = Stream_function(lambda n: 2*n, ZZ, False, 1) - sage: b = Stream_function(lambda n: n, ZZ, False, 1) + sage: a = Stream_function(lambda n: 2*n, False, 1) + sage: b = Stream_function(lambda n: n, False, 1) sage: f = Stream_rmul(a, 2) sage: f == Stream_rmul(b, 2) False @@ -1845,7 +1976,7 @@ def is_nonzero(self): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_rmul, Stream_function) - sage: f = Stream_function(lambda n: n, ZZ, True, 1) + sage: f = Stream_function(lambda n: n, True, 1) sage: g = Stream_rmul(f, 2) sage: g.is_nonzero() False @@ -1874,7 +2005,7 @@ class Stream_rmul(Stream_scalar): sage: from sage.data_structures.stream import (Stream_rmul, Stream_function) sage: W = algebras.DifferentialWeyl(QQ, names=('x',)) sage: x, dx = W.gens() - sage: f = Stream_function(lambda n: x^n, W, True, 1) + sage: f = Stream_function(lambda n: x^n, True, 1) sage: g = Stream_rmul(f, dx) sage: [g[i] for i in range(5)] [0, x*dx + 1, x^2*dx + 2*x, x^3*dx + 3*x^2, x^4*dx + 4*x^3] @@ -1890,7 +2021,7 @@ def get_coefficient(self, n): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_rmul, Stream_function) - sage: f = Stream_function(lambda n: n, ZZ, True, 1) + sage: f = Stream_function(lambda n: n, True, 1) sage: g = Stream_rmul(f, 3) sage: g.get_coefficient(5) 15 @@ -1915,7 +2046,7 @@ class Stream_lmul(Stream_scalar): sage: from sage.data_structures.stream import (Stream_lmul, Stream_function) sage: W = algebras.DifferentialWeyl(QQ, names=('x',)) sage: x, dx = W.gens() - sage: f = Stream_function(lambda n: x^n, W, True, 1) + sage: f = Stream_function(lambda n: x^n, True, 1) sage: g = Stream_lmul(f, dx) sage: [g[i] for i in range(5)] [0, x*dx, x^2*dx, x^3*dx, x^4*dx] @@ -1931,7 +2062,7 @@ def get_coefficient(self, n): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_lmul, Stream_function) - sage: f = Stream_function(lambda n: n, ZZ, True, 1) + sage: f = Stream_function(lambda n: n, True, 1) sage: g = Stream_lmul(f, 3) sage: g.get_coefficient(5) 15 @@ -1952,7 +2083,7 @@ class Stream_neg(Stream_unary): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_neg, Stream_function) - sage: f = Stream_function(lambda n: 1, ZZ, True, 1) + sage: f = Stream_function(lambda n: 1, True, 1) sage: g = Stream_neg(f) sage: [g[i] for i in range(10)] [0, -1, -1, -1, -1, -1, -1, -1, -1, -1] @@ -1964,7 +2095,7 @@ def __init__(self, series): TESTS:: sage: from sage.data_structures.stream import (Stream_neg, Stream_function) - sage: f = Stream_function(lambda n: -1, ZZ, True, 0) + sage: f = Stream_function(lambda n: -1, True, 0) sage: g = Stream_neg(f) """ super().__init__(series, series._is_sparse, series._approximate_order) @@ -1980,7 +2111,7 @@ def get_coefficient(self, n): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_neg, Stream_function) - sage: f = Stream_function(lambda n: n, ZZ, True, 1) + sage: f = Stream_function(lambda n: n, True, 1) sage: g = Stream_neg(f) sage: g.get_coefficient(5) -5 @@ -1997,7 +2128,7 @@ def is_nonzero(self): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_neg, Stream_function) - sage: f = Stream_function(lambda n: n, ZZ, True, 1) + sage: f = Stream_function(lambda n: n, True, 1) sage: g = Stream_neg(f) sage: g.is_nonzero() False @@ -2010,6 +2141,7 @@ def is_nonzero(self): """ return self._series.is_nonzero() + class Stream_cauchy_invert(Stream_unary): """ Operator for multiplicative inverse of the stream. @@ -2021,7 +2153,7 @@ class Stream_cauchy_invert(Stream_unary): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_cauchy_invert, Stream_function) - sage: f = Stream_function(lambda n: 1, ZZ, True, 1) + sage: f = Stream_function(lambda n: 1, True, 1) sage: g = Stream_cauchy_invert(f) sage: [g[i] for i in range(10)] [-1, 0, 0, 0, 0, 0, 0, 0, 0, 0] @@ -2053,7 +2185,7 @@ def get_coefficient(self, n): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_cauchy_invert, Stream_function) - sage: f = Stream_function(lambda n: n, ZZ, True, 1) + sage: f = Stream_function(lambda n: n, True, 1) sage: g = Stream_cauchy_invert(f) sage: g.get_coefficient(5) 0 @@ -2077,7 +2209,7 @@ def iterate_coefficients(self): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_cauchy_invert, Stream_function) - sage: f = Stream_function(lambda n: n^2, ZZ, False, 1) + sage: f = Stream_function(lambda n: n^2, False, 1) sage: g = Stream_cauchy_invert(f) sage: n = g.iterate_coefficients() sage: [next(n) for i in range(10)] @@ -2112,45 +2244,45 @@ def is_nonzero(self): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_cauchy_invert, Stream_function) - sage: f = Stream_function(lambda n: n^2, ZZ, False, 1) + sage: f = Stream_function(lambda n: n^2, False, 1) sage: g = Stream_cauchy_invert(f) sage: g.is_nonzero() True """ return True + class Stream_map_coefficients(Stream_inexact): r""" - The stream with ``function`` applied to each nonzero - coefficient of ``series``. + The stream with ``function`` applied to each nonzero coefficient + of ``series``. INPUT: - ``series`` -- a :class:`Stream` - ``function`` -- a function that modifies the elements of the stream - - ``ring`` -- the base ring of the stream EXAMPLES:: sage: from sage.data_structures.stream import (Stream_map_coefficients, Stream_function) - sage: f = Stream_function(lambda n: 1, ZZ, True, 1) - sage: g = Stream_map_coefficients(f, lambda n: -n, ZZ) + sage: f = Stream_function(lambda n: 1, True, 1) + sage: g = Stream_map_coefficients(f, lambda n: -n) sage: [g[i] for i in range(10)] [0, -1, -1, -1, -1, -1, -1, -1, -1, -1] + """ - def __init__(self, series, function, ring): + def __init__(self, series, function): """ Initialize ``self``. TESTS:: sage: from sage.data_structures.stream import (Stream_map_coefficients, Stream_function) - sage: f = Stream_function(lambda n: -1, ZZ, True, 0) - sage: g = Stream_map_coefficients(f, lambda n: n + 1, ZZ) + sage: f = Stream_function(lambda n: -1, True, 0) + sage: g = Stream_map_coefficients(f, lambda n: n + 1) sage: TestSuite(g).run(skip="_test_pickling") """ self._function = function - self._ring = ring self._series = series super().__init__(series._is_sparse, series._approximate_order) @@ -2165,25 +2297,20 @@ def get_coefficient(self, n): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_map_coefficients, Stream_function) - sage: f = Stream_function(lambda n: n, ZZ, True, -1) - sage: g = Stream_map_coefficients(f, lambda n: n^2 + 1, ZZ) + sage: f = Stream_function(lambda n: n, True, -1) + sage: g = Stream_map_coefficients(f, lambda n: n^2 + 1) sage: g.get_coefficient(5) 26 sage: [g.get_coefficient(i) for i in range(-1, 10)] [2, 0, 2, 5, 10, 17, 26, 37, 50, 65, 82] sage: R. = ZZ[] - sage: f = Stream_function(lambda n: n, ZZ, True, -1) - sage: g = Stream_map_coefficients(f, lambda n: n.degree() + 1, R) + sage: f = Stream_function(lambda n: n, True, -1) + sage: g = Stream_map_coefficients(f, lambda n: R(n).degree() + 1) sage: [g.get_coefficient(i) for i in range(-1, 3)] [1, 0, 1, 1] - - sage: f = Stream_function(lambda n: n, ZZ, True, 0) - sage: g = Stream_map_coefficients(f, lambda n: 5, GF(3)) - sage: [g.get_coefficient(i) for i in range(10)] - [0, 5, 5, 0, 5, 5, 0, 5, 5, 0] """ - c = self._ring(self._series[n]) + c = self._series[n] if c: return self._function(c) return c @@ -2195,13 +2322,13 @@ def __hash__(self): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_map_coefficients, Stream_function) - sage: f = Stream_function(lambda n: -1, ZZ, True, 0) - sage: g = Stream_map_coefficients(f, lambda n: n + 1, ZZ) + sage: f = Stream_function(lambda n: -1, True, 0) + sage: g = Stream_map_coefficients(f, lambda n: n + 1) sage: hash(g) == hash(g) True """ # We don't hash the function as it might not be hashable. - return hash((type(self), self._series, self._ring)) + return hash((type(self), self._series)) def __eq__(self, other): """ @@ -2214,18 +2341,17 @@ def __eq__(self, other): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_map_coefficients, Stream_function) - sage: f = Stream_function(lambda n: -1, ZZ, True, 0) + sage: f = Stream_function(lambda n: -1, True, 0) sage: def plus_one(n): return n + 1 - sage: g = Stream_map_coefficients(f, plus_one, ZZ) + sage: g = Stream_map_coefficients(f, plus_one) sage: g == f False - sage: g == Stream_map_coefficients(f, plus_one, QQ) + sage: g == Stream_map_coefficients(f, lambda n: n + 1) False - sage: g == Stream_map_coefficients(f, plus_one, ZZ) - True """ return (isinstance(other, type(self)) and self._series == other._series - and self._ring == other._ring and self._function == other._function) + and self._function == other._function) + class Stream_shift(Stream_inexact): """ @@ -2246,7 +2372,7 @@ def __init__(self, series, shift): sage: from sage.data_structures.stream import Stream_exact sage: h = Stream_exact([1], False, constant=3) sage: M = Stream_shift(h, 2) - sage: TestSuite(M).run() + sage: TestSuite(M).run(skip="_test_pickling") """ self._series = series self._shift = shift @@ -2260,7 +2386,7 @@ def __getitem__(self, n): sage: from sage.data_structures.stream import Stream_shift sage: from sage.data_structures.stream import Stream_function - sage: F = Stream_function(lambda n: n, ZZ, False, 1) + sage: F = Stream_function(lambda n: n, False, 1) sage: M = Stream_shift(F, 2) sage: [F[i] for i in range(6)] [0, 1, 2, 3, 4, 5] @@ -2277,7 +2403,7 @@ def __hash__(self): sage: from sage.data_structures.stream import Stream_shift sage: from sage.data_structures.stream import Stream_function - sage: F = Stream_function(lambda n: n, ZZ, False, 1) + sage: F = Stream_function(lambda n: n, False, 1) sage: M = Stream_shift(F, 2) sage: hash(M) == hash(M) True @@ -2296,7 +2422,7 @@ def __eq__(self, other): sage: from sage.data_structures.stream import Stream_shift sage: from sage.data_structures.stream import Stream_function - sage: F = Stream_function(lambda n: 1, ZZ, False, 1) + sage: F = Stream_function(lambda n: 1, False, 1) sage: M2 = Stream_shift(F, 2) sage: M3 = Stream_shift(F, 3) sage: M2 == M3 @@ -2317,10 +2443,119 @@ def is_nonzero(self): EXAMPLES:: sage: from sage.data_structures.stream import (Stream_cauchy_invert, Stream_function) - sage: f = Stream_function(lambda n: n^2, ZZ, False, 1) + sage: f = Stream_function(lambda n: n^2, False, 1) sage: g = Stream_cauchy_invert(f) sage: g.is_nonzero() True """ return self._series.is_nonzero() +class Stream_derivative(Stream_inexact): + """ + Operator for taking derivatives of a stream. + + INPUT: + + - ``series`` -- a :class:`Stream` + - ``shift`` -- a positive integer + """ + def __init__(self, series, shift): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_exact, Stream_derivative + sage: f = Stream_exact([1,2,3], False) + sage: f2 = Stream_derivative(f, 2) + sage: TestSuite(f2).run() + """ + self._series = series + self._shift = shift + if 0 <= series._approximate_order <= shift: + aorder = 0 + else: + aorder = series._approximate_order - shift + super().__init__(series._is_sparse, aorder) + + def __getitem__(self, n): + """ + Return the ``n``-th coefficient of ``self``. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_function, Stream_derivative + sage: f = Stream_function(lambda n: 1/n if n else 0, True, -2) + sage: [f[i] for i in range(-5, 3)] + [0, 0, 0, -1/2, -1, 0, 1, 1/2] + sage: f2 = Stream_derivative(f, 2) + sage: [f2[i] for i in range(-5, 3)] + [0, -3, -2, 0, 0, 1, 2, 3] + + sage: f = Stream_function(lambda n: 1/n, True, 2) + sage: [f[i] for i in range(-1, 4)] + [0, 0, 0, 1/2, 1/3] + sage: f2 = Stream_derivative(f, 3) + sage: [f2[i] for i in range(-1, 4)] + [0, 2, 6, 12, 20] + """ + return (prod(n+k for k in range(1, self._shift + 1)) + * self._series[n + self._shift]) + + def __hash__(self): + """ + Return the hash of ``self``. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_function + sage: from sage.data_structures.stream import Stream_derivative + sage: a = Stream_function(lambda n: 2*n, False, 1) + sage: f = Stream_derivative(a, 1) + sage: g = Stream_derivative(a, 2) + sage: hash(f) == hash(f) + True + sage: hash(f) == hash(g) + False + + """ + return hash((type(self), self._series, self._shift)) + + def __eq__(self, other): + """ + Test equality. + + INPUT: + + - ``other`` -- a stream of coefficients + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_function + sage: from sage.data_structures.stream import Stream_derivative + sage: a = Stream_function(lambda n: 2*n, False, 1) + sage: f = Stream_derivative(a, 1) + sage: g = Stream_derivative(a, 2) + sage: f == g + False + sage: f == Stream_derivative(a, 1) + True + """ + return (isinstance(other, type(self)) and self._shift == other._shift + and self._series == other._series) + + def is_nonzero(self): + r""" + Return ``True`` if and only if this stream is known + to be nonzero. + + EXAMPLES:: + + sage: from sage.data_structures.stream import Stream_exact, Stream_derivative + sage: f = Stream_exact([1,2], False) + sage: Stream_derivative(f, 1).is_nonzero() + True + sage: Stream_derivative(f, 2).is_nonzero() # it might be nice if this gave False + True + """ + return self._series.is_nonzero() diff --git a/src/sage/databases/cubic_hecke_db.py b/src/sage/databases/cubic_hecke_db.py index fd0068f8793..99e96a9b816 100644 --- a/src/sage/databases/cubic_hecke_db.py +++ b/src/sage/databases/cubic_hecke_db.py @@ -1514,4 +1514,3 @@ def read_markov(bas_ele, variables, num_strands=4): 0, 1]} return data[num_strands][bas_ele] - diff --git a/src/sage/databases/findstat.py b/src/sage/databases/findstat.py index d21f1a250b3..70429a47ac0 100644 --- a/src/sage/databases/findstat.py +++ b/src/sage/databases/findstat.py @@ -4545,7 +4545,7 @@ def name(self, style="singular"): _SupportedFindStatCollection(lambda x: (lambda E, V: Graph([list(range(V)), lambda i,j: (i,j) in E or (j,i) in E], immutable=True))(*literal_eval(x)), - lambda X: str((sorted(X.edges(labels=False)), X.num_verts())), + lambda X: str((X.edges(labels=False, sort=True), X.num_verts())), lambda x: (g.copy(immutable=True) for g in graphs(x, copy=False)), lambda x: x.num_verts(), lambda x: isinstance(x, Graph)), @@ -4767,13 +4767,13 @@ def _element_constructor_(self, entry): sage: cc = FindStatCollection(graphs(3)); cc # optional -- internet a subset of Cc0020: Graphs - sage: cc.first_terms(lambda x: x.edges(labels=False)).list() # optional -- internet + sage: cc.first_terms(lambda x: x.edges(labels=False, sort=True)).list() # optional -- internet [(Graph on 3 vertices, []), (Graph on 3 vertices, [(0, 2)]), (Graph on 3 vertices, [(0, 2), (1, 2)]), (Graph on 3 vertices, [(0, 1), (0, 2), (1, 2)])] - sage: len(cc.first_terms(lambda x: x.edges(labels=False)).list()) # optional -- internet + sage: len(cc.first_terms(lambda x: x.edges(labels=False, sort=False)).list()) # optional -- internet 4 """ if isinstance(entry, self.Element): 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/dynamics/arithmetic_dynamics/projective_ds.py b/src/sage/dynamics/arithmetic_dynamics/projective_ds.py index cdfc11a9e52..f47d5d36249 100644 --- a/src/sage/dynamics/arithmetic_dynamics/projective_ds.py +++ b/src/sage/dynamics/arithmetic_dynamics/projective_ds.py @@ -1103,6 +1103,303 @@ def nth_iterate(self, P, n, **kwds): raise TypeError("must be a forward orbit") return self.orbit(P, [n,n+1], **kwds)[0] + def arakelov_zhang_pairing(self, g, **kwds): + r""" + Return an estimate of the Arakelov-Zhang pairing of the rational + maps ``self`` and ``g`` on `\mathbb{P}^1` over a number field. + + The Arakelov-Zhang pairing was introduced by Petsche, Szpiro, and + Tucker in 2012, which measures the dynamical closeness of two rational + maps. They prove inter alia that if one takes a sequence of small points + for one map (for example, preperiodic points for ``self``) and measure + their dynamical height with respect to the other map (say, ``g``), then + the values of the height will tend to the value of the Arakelov-Zhang pairing. + + The Arakelov-Zhang pairing involves mutual energy integrals between dynamical + measures, which are in the case of polynomials, the equilibrium measures + of the associated Julia sets at each place. As a result, these pairings + are very difficult to compute exactly via analytic methods. We use a + discrete approximation to these energy integrals. + + ALGORITHM: + + We select periodic points of order `n`, or ``n``-th preimages of a + specified starting value given by ``f_starting_point`` and ``g_starting_point``. + At the archimedean places and the places of bad reduction of the two maps, + we compute the discrete approximations to the energy integrals involved + using these points. + + INPUT: + + - ``g`` - a rational map of `\mathbb{P}^1` given as a projective morphism. + ``g`` and ``self`` should have the same field of definition. + + kwds: + + - ``n`` - (default: 5) a positive integer + Order of periodic points to use or preimages to take if starting points are specified. + + - ``f_starting_point`` - (optional, default: ``None``) value in the base number field or None. + If ``f_starting_point`` is None, we solve for points of period ``n`` for ``self``. + Otherwise, we take ``n``-th preimages of the point given by ``f_starting_point`` + under ``f`` on the affine line. + + - ``g_starting_point`` - (optional, default: ``None``) value in the base number field or None. + If ``g_starting_point`` is None, we solve for points of period ``n`` for ``g``. + Otherwise, we take ``n``-th preimages of the point given by ``g_starting_point`` + under ``g`` on the affine line. + + - ``check_primes_of_bad_reduction`` - (optional, default: ``False``) boolean. + Passed to the ``primes_of_bad_reduction`` function for ``self`` and ``g``. + + - ``prec`` - (optional, default: ``RealField`` default) + default precision for RealField values which are returned. + + - ``noise_multiplier`` - (default: 2) a real number. + Discriminant terms involved in the computation at the archimedean places + are often not needed, particularly if the capacity of the Julia sets is 1, + and introduce a lot of error. By a well-known result of Mahler (see + also M. Baker, ""A lower bound for averages of dynamical Green's + functions") such error (for a set of `N` points) is on the order of + `\log(N)/N` after our normalization. We check if the value of the + archimedean discriminant terms is within ``2*noise_multiplier`` of + `\log(N)/N`. If so, we discard it. In practice this greatly improves + the accuracy of the estimate of the pairing. If desired, + ``noise_multiplier`` can be set to 0, and no terms will be ignored. + + OUTPUT: + + - a real number estimating the Arakelov-Zhang pairing of the two rational maps. + + EXAMPLES:: + + sage: K. = CyclotomicField(3) + sage: P. = ProjectiveSpace(K, 1) + sage: f = DynamicalSystem_projective([x^2 + (2*k + 2)*y^2, y^2]) + sage: g = DynamicalSystem_projective([x^2, y^2]) + sage: pairingval = f.arakelov_zhang_pairing(g, n=5); pairingval + 0.409598197761958 + + :: + + sage: P. = ProjectiveSpace(QQ, 1) + sage: f = DynamicalSystem_projective([x^2 + 4*y^2, y^2]) + sage: g = DynamicalSystem_projective([x^2, y^2]) + sage: pairingval = f.arakelov_zhang_pairing(g, n=6); pairingval + 0.750178391443644 + sage: # Compare to the exact value: + sage: dynheight = f.canonical_height(P(0, 1)); dynheight + 0.75017839144364417318023000563 + sage: dynheight - pairingval + 0.000000000000000 + + Notice that if we set the noise_multiplier to 0, the accuracy is diminished:: + + sage: P. = ProjectiveSpace(QQ, 1) + sage: f = DynamicalSystem_projective([x^2 + 4*y^2, y^2]) + sage: g = DynamicalSystem_projective([x^2, y^2]) + sage: pairingval = f.arakelov_zhang_pairing(g, n=6, noise_multiplier=0) + sage: pairingval + 0.650660018921632 + sage: dynheight = f.canonical_height(P(0, 1)); dynheight + 0.75017839144364417318023000563 + sage: pairingval - dynheight + -0.0995183725220122 + + We compute the example of Prop. 18(d) from Petsche, Szpiro and Tucker:: + + sage: P. = ProjectiveSpace(QQ, 1) + sage: f = DynamicalSystem_projective([y^2 - (y - x)^2, y^2]) + sage: g = DynamicalSystem_projective([x^2, y^2]) + sage: f.arakelov_zhang_pairing(g) + 0.326954667248466 + sage: # Correct value should be = 0.323067... + sage: f.arakelov_zhang_pairing(g, n=9) + 0.323091061918965 + sage: _ - 0.323067 + 0.0000240619189654789 + + Also from Prop. 18 of Petsche, Szpiro and Tucker, includes places of bad reduction:: + + sage: R. = PolynomialRing(ZZ) + sage: K. = NumberField(z^3 - 11) + sage: P. = ProjectiveSpace(K,1) + sage: a = 7/(b - 1) + sage: f = DynamicalSystem_projective([a*y^2 - (a*y - x)^2, y^2]) + sage: g = DynamicalSystem_projective([x^2, y^2]) + sage: # If all archimedean absolute values of a have modulus > 2, + sage: # then the pairing should be h(a). + sage: f.arakelov_zhang_pairing(g, n=6) + 1.93846423207664 + sage: _ - a.global_height() + -0.00744591697867292 + """ + n = kwds.pop('n', 5) + f_starting_point = kwds.pop('f_starting_point', None) + g_starting_point = kwds.pop('g_starting_point', None) + check_primes_of_bad_reduction = kwds.pop('check_primes_of_bad_reduction', False) + prec = kwds.pop('prec', None) + noise_multiplier = kwds.pop('noise_multiplier', 2) + + f_domain = self.domain() + R = f_domain.base_ring() + g_domain = g.domain() + + if f_domain != g_domain: + raise TypeError("Implemented only for rational maps of the same projective line.") + + if n <= 0: + raise ValueError("Period must be a positive integer.") + + if not (is_ProjectiveSpace(f_domain) and is_ProjectiveSpace(g_domain)): + raise NotImplementedError("Not implemented for subschemes.") + + if f_domain.dimension_relative() > 1: + raise NotImplementedError("Only implemented for dimension 1.") + + if not self.is_endomorphism(): + raise TypeError("Self must be an endomorphism.") + + if R not in NumberFields() and R is not QQbar: + raise NotImplementedError("Only implemented for number fields.") + + f_iterate_map = self.nth_iterate_map(n) + f_iter_map_poly = f_iterate_map.defining_polynomials() + if f_starting_point is None: + f_poly_hom = f_iter_map_poly[0] * f_domain.gens()[1] - f_iter_map_poly[1] * f_domain.gens()[0] + else: + f_poly_hom = f_iter_map_poly[0] - f_starting_point * f_iter_map_poly[1] + + g_iterate_map = g.nth_iterate_map(n) + g_iter_map_poly = g_iterate_map.defining_polynomials() + if g_starting_point is None: + g_poly_hom = g_iter_map_poly[0] * g_domain.gens()[1] - g_iter_map_poly[1] * g_domain.gens()[0] + else: + g_poly_hom = g_iter_map_poly[0] - g_starting_point * g_iter_map_poly[1] + + f_poly = f_poly_hom([(f_domain.gens()[0]), 1]).univariate_polynomial().monic() + g_poly = g_poly_hom([(g_domain.gens()[0]), 1]).univariate_polynomial().monic() + + # If f_poly and g_poly are not square-free, make them square-free. + if not f_poly.is_squarefree(): + f_poly = f_poly.quo_rem(gcd(f_poly, f_poly.derivative()))[0] + if not g_poly.is_squarefree(): + g_poly = g_poly.quo_rem(gcd(g_poly, g_poly.derivative()))[0] + + if f_poly.degree() <= 2 or g_poly.degree() <= 2: + # f_point or g_point is exceptional + raise ValueError("One of the starting points is exceptional. \ + Please specify a non-exceptional initial point.") + + if gcd(f_poly, g_poly).degree() > 0: + if f_poly.degree() > g_poly.degree(): + f_poly = f_poly.quo_rem(gcd(f_poly, g_poly))[0] + else: + g_poly = g_poly.quo_rem(gcd(f_poly, g_poly))[0] + + if f_poly.degree() <= 2 or g_poly.degree() <= 2: + raise ValueError("After removing common factors, the n-th \ + iterates of 'self' and 'g' have too many \ + roots in common. Try another 'n' or starting \ + values.") + + # We want higher precision here temporarily, since resultants are + # usually very large. This is not to say that the computation is + # very accurate, merely that we want to keep track of potentially + # very large height integers/rationals. + old_prec = prec + if prec is None: + Real = RealField(512) + elif prec < 512: + prec = 512 + Real = RealField(prec) + + bad_primes = list(set(self.primes_of_bad_reduction(check=check_primes_of_bad_reduction)) + .union(g.primes_of_bad_reduction(check=check_primes_of_bad_reduction))) + + f_deg = f_poly.degree() + g_deg = g_poly.degree() + + f_disc = f_poly.discriminant() + g_disc = g_poly.discriminant() + + res = f_poly.resultant(g_poly) + + # The code below actually computes -( mu_f - mu_g, mu_f - mu_g ), + # so flip the sign at the end. + AZ_pairing = Real(0) + if R is QQ: + for p in bad_primes: + temp = (ZZ(1)/2) * (-f_disc.ord(p)) * Real(p).log() / (f_deg**2) + if abs(temp) > noise_multiplier * Real(f_deg).log() / Real(f_deg): + AZ_pairing += temp + + temp = (ZZ(1)/2) * (-g_disc.ord(p)) * Real(p).log() / (g_deg**2) + if abs(temp) > noise_multiplier * Real(g_deg).log() / Real(g_deg): + AZ_pairing += temp + + AZ_pairing -= (-res.ord(p)) * Real(p).log() / (f_deg * g_deg) + + temp = (ZZ(1)/2) * (Real(f_disc).abs().log()) / (f_deg**2) + if abs(temp) > noise_multiplier * Real(f_deg).log() / Real(f_deg): + AZ_pairing += temp + + temp = (ZZ(1)/2) * (Real(g_disc).abs().log()) / (g_deg**2) + if abs(temp) > noise_multiplier * Real(g_deg).log() / Real(g_deg): + AZ_pairing += temp + + AZ_pairing -= Real(res).abs().log() / (f_deg * g_deg) + + # For number fields + else: + K = self.base_ring() + d = K.absolute_degree() + + for v in bad_primes: + Nv = v.absolute_ramification_index() * v.residue_class_degree() / d + + temp = Nv * ((ZZ(1)/2) * K(f_disc).abs_non_arch(v, prec=prec).log() / (f_deg**2)) + if abs(temp) > noise_multiplier * Real(f_deg).log() / Real(f_deg): + AZ_pairing += temp + + temp = Nv * ((ZZ(1)/2) * K(g_disc).abs_non_arch(v, prec=prec).log() / (g_deg**2)) + if abs(temp) > noise_multiplier * Real(g_deg).log() / Real(g_deg): + AZ_pairing += temp + + AZ_pairing -= Nv * (K(res).abs_non_arch(v, prec=prec).log() / (f_deg * g_deg)) + + if f_disc.is_rational(): + f_disc = QQ(f_disc) + temp = (ZZ(1)/2) * (Real(f_disc).abs().log()) / (f_deg**2) + if abs(temp) > noise_multiplier * Real(f_deg).log() / Real(f_deg): + AZ_pairing += temp + else: + temp = (ZZ(1)/d) * (ZZ(1)/2) * (Real(K(f_disc).norm()).abs().log()) / (f_deg**2) + if abs(temp) > noise_multiplier * Real(f_deg).log() / Real(f_deg): + AZ_pairing += temp + + if g_disc.is_rational(): + g_disc = QQ(g_disc) + temp = (ZZ(1)/2) * (Real(g_disc).abs().log()) / (g_deg**2) + if abs(temp) > noise_multiplier * Real(g_deg).log() / Real(g_deg): + AZ_pairing += temp + else: + temp = (ZZ(1)/d) * (ZZ(1)/2) * (Real(K(g_disc).norm()).abs().log()) / (g_deg**2) + if abs(temp) > noise_multiplier * Real(g_deg).log() / Real(g_deg): + AZ_pairing += temp + + if res.is_rational(): + AZ_pairing -= (Real(res).abs().log()) / (f_deg * g_deg) + else: + AZ_pairing -= (ZZ(1)/d) * (Real(K(res).norm()).abs().log()) / (f_deg * g_deg) + + if old_prec is None: + Real = RealField() + else: + Real = RealField(old_prec) + + return Real(-AZ_pairing) + def degree_sequence(self, iterates=2): r""" Return sequence of degrees of normalized iterates starting with 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/join_feature.py b/src/sage/features/join_feature.py index b29241eb462..92e9851d635 100644 --- a/src/sage/features/join_feature.py +++ b/src/sage/features/join_feature.py @@ -9,6 +9,17 @@ class JoinFeature(Feature): r""" Join of several :class:`~sage.features.Feature` instances. + This creates a new feature as the union of the given features. Typically + these are executables of an SPKG. For an example, see + :class:`~sage.features.rubiks.Rubiks`. + + Furthermore, this can be the union of a single feature. This is used to map + the given feature to a more convenient name to be used in ``optional`` tags + of doctests. Thus you can equip a feature such as a + :class:`~sage.features.PythonModule` with a tag name that differs from the + systematic tag name. As an example for this use case, see + :class:`~sage.features.meataxe.Meataxe`. + EXAMPLES:: sage: from sage.features import Executable 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/airy.py b/src/sage/functions/airy.py index cf32fb8f1c8..eceb9e6eafc 100644 --- a/src/sage/functions/airy.py +++ b/src/sage/functions/airy.py @@ -1,5 +1,5 @@ r""" -Airy Functions +Airy functions This module implements Airy functions and their generalized derivatives. It supports symbolic functionality through Maxima and numeric evaluation through diff --git a/src/sage/functions/bessel.py b/src/sage/functions/bessel.py index de1401dde68..d6c5c4ed4bc 100644 --- a/src/sage/functions/bessel.py +++ b/src/sage/functions/bessel.py @@ -1,5 +1,5 @@ r""" -Bessel Functions +Bessel functions This module provides symbolic Bessel and Hankel functions, and their spherical versions. These functions use the `mpmath library`_ for numerical diff --git a/src/sage/functions/error.py b/src/sage/functions/error.py index 06f0b244736..ca665622f20 100644 --- a/src/sage/functions/error.py +++ b/src/sage/functions/error.py @@ -1,5 +1,5 @@ r""" -Error Functions +Error functions This module provides symbolic error functions. These functions use the `mpmath library` for numerical evaluation and Maxima, Pynac for diff --git a/src/sage/functions/exp_integral.py b/src/sage/functions/exp_integral.py index 7ef74d9ed49..3f193cf5f1b 100644 --- a/src/sage/functions/exp_integral.py +++ b/src/sage/functions/exp_integral.py @@ -1,5 +1,5 @@ r""" -Exponential Integrals +Exponential integrals AUTHORS: diff --git a/src/sage/functions/generalized.py b/src/sage/functions/generalized.py index a24268c9b6b..d912ea23719 100644 --- a/src/sage/functions/generalized.py +++ b/src/sage/functions/generalized.py @@ -1,5 +1,5 @@ r""" -Generalized Functions +Generalized functions Sage implements several generalized functions (also known as distributions) such as Dirac delta, Heaviside step functions. These diff --git a/src/sage/functions/hyperbolic.py b/src/sage/functions/hyperbolic.py index 4487a3b3641..9a362b8882f 100644 --- a/src/sage/functions/hyperbolic.py +++ b/src/sage/functions/hyperbolic.py @@ -1,5 +1,5 @@ r""" -Hyperbolic Functions +Hyperbolic functions The full set of hyperbolic and inverse hyperbolic functions is available: diff --git a/src/sage/functions/hypergeometric.py b/src/sage/functions/hypergeometric.py index 795a1ab7228..50c60b25638 100644 --- a/src/sage/functions/hypergeometric.py +++ b/src/sage/functions/hypergeometric.py @@ -1,5 +1,5 @@ r""" -Hypergeometric Functions +Hypergeometric functions This module implements manipulation of infinite hypergeometric series represented in standard parametric form (as `\,_pF_q` functions). diff --git a/src/sage/functions/jacobi.py b/src/sage/functions/jacobi.py index af67d857f27..6fe3b2ade89 100644 --- a/src/sage/functions/jacobi.py +++ b/src/sage/functions/jacobi.py @@ -1,5 +1,5 @@ r""" -Jacobi Elliptic Functions +Jacobi elliptic functions This module implements the 12 Jacobi elliptic functions, along with their inverses and the Jacobi amplitude function. diff --git a/src/sage/functions/log.py b/src/sage/functions/log.py index d322305b223..6f9133841a3 100644 --- a/src/sage/functions/log.py +++ b/src/sage/functions/log.py @@ -1,5 +1,5 @@ """ -Logarithmic Functions +Logarithmic functions AUTHORS: @@ -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/min_max.py b/src/sage/functions/min_max.py index 9b7d6d99f62..f783de0ba96 100644 --- a/src/sage/functions/min_max.py +++ b/src/sage/functions/min_max.py @@ -1,5 +1,5 @@ r""" -Symbolic Minimum and Maximum +Symbolic minimum and maximum Sage provides a symbolic maximum and minimum due to the fact that the Python builtin max and min are not able to deal with variables as users might expect. diff --git a/src/sage/functions/orthogonal_polys.py b/src/sage/functions/orthogonal_polys.py index a61f84649e9..7398c763971 100644 --- a/src/sage/functions/orthogonal_polys.py +++ b/src/sage/functions/orthogonal_polys.py @@ -1,5 +1,5 @@ r""" -Orthogonal Polynomials +Orthogonal polynomials Chebyshev polynomials --------------------- @@ -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/piecewise.py b/src/sage/functions/piecewise.py index 688db13a3b8..10d82e3709a 100644 --- a/src/sage/functions/piecewise.py +++ b/src/sage/functions/piecewise.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- r""" -Piecewise-defined Functions +Piecewise functions This module implement piecewise functions in a single variable. See :mod:`sage.sets.real_set` for more information about how to construct diff --git a/src/sage/functions/prime_pi.pyx b/src/sage/functions/prime_pi.pyx index 0a576734777..38c68a2d4b4 100644 --- a/src/sage/functions/prime_pi.pyx +++ b/src/sage/functions/prime_pi.pyx @@ -1,5 +1,13 @@ r""" -Counting Primes +Counting primes + +EXAMPLES:: + + sage: z = sage.functions.prime_pi.PrimePi() + sage: loads(dumps(z)) + prime_pi + sage: loads(dumps(z)) == z + True AUTHORS: @@ -12,14 +20,6 @@ AUTHORS: - Dima Pasechnik (2021): removed buggy cython code, replaced it with calls to primecount/primecountpy spkg - -EXAMPLES:: - - sage: z = sage.functions.prime_pi.PrimePi() - sage: loads(dumps(z)) - prime_pi - sage: loads(dumps(z)) == z - True """ # **************************************************************************** diff --git a/src/sage/functions/special.py b/src/sage/functions/special.py index 02596e49620..56f96f2ef53 100644 --- a/src/sage/functions/special.py +++ b/src/sage/functions/special.py @@ -1,20 +1,5 @@ r""" -Miscellaneous Special Functions - -AUTHORS: - -- David Joyner (2006-13-06): initial version - -- David Joyner (2006-30-10): bug fixes to pari wrappers of Bessel - functions, hypergeometric_U - -- William Stein (2008-02): Impose some sanity checks. - -- David Joyner (2008-04-23): addition of elliptic integrals - -- Eviatar Bach (2013): making elliptic integrals symbolic - -- Eric Gourgoulhon (2022): add Condon-Shortley phase to spherical harmonics +Miscellaneous special functions This module provides easy access to many of Maxima and PARI's special functions. @@ -104,6 +89,11 @@ and the complete ones are obtained by taking `\phi =\pi/2`. +.. WARNING:: + + SciPy's versions are poorly documented and seem less accurate than the + Maxima and PARI versions. Typically they are limited by hardware floats + precision. REFERENCES: @@ -118,16 +108,20 @@ AUTHORS: -- David Joyner and William Stein +- David Joyner (2006-13-06): initial version + +- David Joyner (2006-30-10): bug fixes to pari wrappers of Bessel + functions, hypergeometric_U + +- William Stein (2008-02): Impose some sanity checks. + +- David Joyner (2008-02-16): optional calls to scipy and replace all ``#random`` by ``...`` -Added 16-02-2008 (wdj): optional calls to scipy and replace all -'#random' by '...' (both at the request of William Stein) +- David Joyner (2008-04-23): addition of elliptic integrals -.. warning:: +- Eviatar Bach (2013): making elliptic integrals symbolic - SciPy's versions are poorly documented and seem less - accurate than the Maxima and PARI versions; typically they are limited - by hardware floats precision. +- Eric Gourgoulhon (2022): add Condon-Shortley phase to spherical harmonics """ # **************************************************************************** @@ -849,7 +843,7 @@ class EllipticF(BuiltinFunction): - :wikipedia:`Elliptic_integral#Incomplete_elliptic_integral_of_the_first_kind` """ def __init__(self): - """ + r""" EXAMPLES:: sage: loads(dumps(elliptic_f)) diff --git a/src/sage/functions/spike_function.py b/src/sage/functions/spike_function.py index 2118c1b2dcc..83fde81b0ca 100644 --- a/src/sage/functions/spike_function.py +++ b/src/sage/functions/spike_function.py @@ -1,5 +1,5 @@ r""" -Spike Functions +Spike functions AUTHORS: diff --git a/src/sage/functions/transcendental.py b/src/sage/functions/transcendental.py index a470e9ed67d..b76e3a1bbbe 100644 --- a/src/sage/functions/transcendental.py +++ b/src/sage/functions/transcendental.py @@ -1,5 +1,5 @@ """ -Number-Theoretic Functions +Number-theoretic functions """ # **************************************************************************** # Copyright (C) 2005 William Stein diff --git a/src/sage/functions/trig.py b/src/sage/functions/trig.py index fb97a5e8c58..16aeeae43ab 100644 --- a/src/sage/functions/trig.py +++ b/src/sage/functions/trig.py @@ -1,5 +1,5 @@ r""" -Trigonometric Functions +Trigonometric functions """ from sage.symbolic.function import GinacFunction import math 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/geometry/convex_set.py b/src/sage/geometry/convex_set.py index 695f118311b..65249cd2a4d 100644 --- a/src/sage/geometry/convex_set.py +++ b/src/sage/geometry/convex_set.py @@ -873,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/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/polyhedron/backend_field.py b/src/sage/geometry/polyhedron/backend_field.py index 6b921d23a68..93c46c66309 100644 --- a/src/sage/geometry/polyhedron/backend_field.py +++ b/src/sage/geometry/polyhedron/backend_field.py @@ -162,66 +162,76 @@ def _init_from_Vrepresentation_and_Hrepresentation(self, Vrep, Hrep): self._init_Hrepresentation(*Hrep) def _init_from_Vrepresentation(self, vertices, rays, lines, - minimize=True, verbose=False): + minimize=True, verbose=False, + internal_base_ring=None): """ Construct polyhedron from V-representation data. INPUT: - ``vertices`` -- list of points. Each point can be specified - as any iterable container of - :meth:`~sage.geometry.polyhedron.base.base_ring` elements. + as any iterable container of ``internal_base_ring`` elements. - ``rays`` -- list of rays. Each ray can be specified as any - iterable container of - :meth:`~sage.geometry.polyhedron.base.base_ring` elements. + iterable container of ``internal_base_ring`` elements. - - ``lines`` -- list of lines. Each line can be specified as - any iterable container of - :meth:`~sage.geometry.polyhedron.base.base_ring` elements. + - ``lines`` -- list of lines. Each line can be specified asinternal_base_ring + any iterable container of ``internal_base_ring`` elements. - ``verbose`` -- boolean (default: ``False``). Whether to print verbose output for debugging purposes. + - ``internal_base_ring`` -- the base ring of the generators' components. + Default is ``None``, in which case, it is set to + :meth:`~sage.geometry.polyhedron.base.base_ring`. + EXAMPLES:: sage: p = Polyhedron(ambient_dim=2, backend='field') sage: from sage.geometry.polyhedron.backend_field import Polyhedron_field sage: Polyhedron_field._init_from_Vrepresentation(p, [(0,0)], [], []) """ + if internal_base_ring is None: + internal_base_ring = self.base_ring() from sage.geometry.polyhedron.double_description_inhomogeneous import Hrep2Vrep, Vrep2Hrep - H = Vrep2Hrep(self.base_ring(), self.ambient_dim(), vertices, rays, lines) - V = Hrep2Vrep(self.base_ring(), self.ambient_dim(), + H = Vrep2Hrep(internal_base_ring, self.ambient_dim(), vertices, rays, lines) + V = Hrep2Vrep(internal_base_ring, self.ambient_dim(), H.inequalities, H.equations) self._init_Vrepresentation_backend(V) self._init_Hrepresentation_backend(H) - def _init_from_Hrepresentation(self, ieqs, eqns, minimize=True, verbose=False): + def _init_from_Hrepresentation(self, ieqs, eqns, + minimize=True, verbose=False, + internal_base_ring=None): """ Construct polyhedron from H-representation data. INPUT: - ``ieqs`` -- list of inequalities. Each line can be specified - as any iterable container of - :meth:`~sage.geometry.polyhedron.base.base_ring` elements. + as any iterable container of ``internal_base_ring`` elements. - ``eqns`` -- list of equalities. Each line can be specified - as any iterable container of - :meth:`~sage.geometry.polyhedron.base.base_ring` elements. + as any iterable container of ``internal_base_ring`` elements. - ``verbose`` -- boolean (default: ``False``). Whether to print verbose output for debugging purposes. + - ``internal_base_ring`` -- the base ring of the generators' components. + Default is ``None``, in which case, it is set to + :meth:`~sage.geometry.polyhedron.base.base_ring`. + TESTS:: sage: p = Polyhedron(ambient_dim=2, backend='field') sage: from sage.geometry.polyhedron.backend_field import Polyhedron_field sage: Polyhedron_field._init_from_Hrepresentation(p, [(1, 2, 3)], []) """ + if internal_base_ring is None: + internal_base_ring = self.base_ring() from sage.geometry.polyhedron.double_description_inhomogeneous import Hrep2Vrep, Vrep2Hrep - V = Hrep2Vrep(self.base_ring(), self.ambient_dim(), ieqs, eqns) - H = Vrep2Hrep(self.base_ring(), self.ambient_dim(), + V = Hrep2Vrep(internal_base_ring, self.ambient_dim(), ieqs, eqns) + H = Vrep2Hrep(internal_base_ring, self.ambient_dim(), V.vertices, V.rays, V.lines) self._init_Vrepresentation_backend(V) self._init_Hrepresentation_backend(H) diff --git a/src/sage/geometry/polyhedron/backend_normaliz.py b/src/sage/geometry/polyhedron/backend_normaliz.py index 86b89632a51..af0c2c459a7 100644 --- a/src/sage/geometry/polyhedron/backend_normaliz.py +++ b/src/sage/geometry/polyhedron/backend_normaliz.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ The Normaliz backend for polyhedral computations @@ -13,7 +12,15 @@ - Jean-Philippe Labbé (2019-04): Expose normaliz features and added functionalities """ # **************************************************************************** -# Copyright (C) 2016 Matthias Köppe +# Copyright (C) 2016-2022 Matthias Köppe +# 2016-2018 Travis Scrimshaw +# 2017 Jeroen Demeyer +# 2018-2020 Jean-Philippe Labbé +# 2019 Vincent Delecroix +# 2019-2021 Jonathan Kliem +# 2019-2021 Sophia Elia +# 2020 Frédéric Chapoton +# 2022 Yuan Zhou # # 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 @@ -30,39 +37,15 @@ 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 -from .base import Polyhedron_base from .base_QQ import Polyhedron_QQ from .base_ZZ import Polyhedron_ZZ - - -def _number_field_elements_from_algebraics_list_of_lists_of_lists(listss, **kwds): - r""" - Like `number_field_elements_from_algebraics`, but for a list of lists of lists. - - EXAMPLES:: - - sage: rt2 = AA(sqrt(2)); rt2 # optional - sage.rings.number_field - 1.414213562373095? - sage: rt3 = AA(sqrt(3)); rt3 # optional - sage.rings.number_field - 1.732050807568878? - sage: from sage.geometry.polyhedron.backend_normaliz import _number_field_elements_from_algebraics_list_of_lists_of_lists - sage: K, results, hom = _number_field_elements_from_algebraics_list_of_lists_of_lists([[[rt2], [1]], [[rt3]], [[1], []]]); results # optional - sage.rings.number_field - [[[-a^3 + 3*a], [1]], [[-a^2 + 2]], [[1], []]] - """ - from sage.rings.qqbar import number_field_elements_from_algebraics - numbers = [] - for lists in listss: - for list in lists: - numbers.extend(list) - K, K_numbers, hom = number_field_elements_from_algebraics(numbers, **kwds) - g = iter(K_numbers) - return K, [ [ [ next(g) for _ in list ] for list in lists ] for lists in listss ], hom +from .base_number_field import Polyhedron_base_number_field def _format_function_call(fn_name, *v, **k): @@ -82,7 +65,7 @@ def _format_function_call(fn_name, *v, **k): ######################################################################### -class Polyhedron_normaliz(Polyhedron_base): +class Polyhedron_normaliz(Polyhedron_base_number_field): """ Polyhedra with normaliz @@ -208,7 +191,7 @@ class Polyhedron_normaliz(Polyhedron_base): (A vertex at (2^(1/3)), A vertex at (sqrt(2))) """ - def __init__(self, parent, Vrep, Hrep, normaliz_cone=None, normaliz_data=None, normaliz_field=None, **kwds): + def __init__(self, parent, Vrep, Hrep, normaliz_cone=None, normaliz_data=None, internal_base_ring=None, **kwds): """ Initializes the polyhedron. @@ -230,16 +213,16 @@ def __init__(self, parent, Vrep, Hrep, normaliz_cone=None, normaliz_data=None, n if Hrep is not None or Vrep is not None or normaliz_data is not None: raise ValueError("only one of Vrep, Hrep, normaliz_cone, or normaliz_data can be different from None") Element.__init__(self, parent=parent) - self._init_from_normaliz_cone(normaliz_cone, normaliz_field) + self._init_from_normaliz_cone(normaliz_cone, internal_base_ring) elif normaliz_data: if Hrep is not None or Vrep is not None: raise ValueError("only one of Vrep, Hrep, normaliz_cone, or normaliz_data can be different from None") Element.__init__(self, parent=parent) - self._init_from_normaliz_data(normaliz_data, normaliz_field) + self._init_from_normaliz_data(normaliz_data, internal_base_ring) else: - if normaliz_field: - raise ValueError("if Vrep or Hrep are given, cannot provide normaliz_field") - Polyhedron_base.__init__(self, parent, Vrep, Hrep, **kwds) + if internal_base_ring: + raise ValueError("if Vrep or Hrep are given, cannot provide internal_base_ring") + Polyhedron_base_number_field.__init__(self, parent, Vrep, Hrep, **kwds) def _nmz_result(self, normaliz_cone, property): """ @@ -277,13 +260,13 @@ def rational_handler(list): def nfelem_handler(coords): # coords might be too short which is not accepted by Sage number field - v = list(coords) + [0] * (self._normaliz_field.degree() - len(coords)) - return self._normaliz_field(v) + v = list(coords) + [0] * (self._internal_base_ring.degree() - len(coords)) + return self._internal_base_ring(v) return NmzResult(normaliz_cone, property, RationalHandler=rational_handler, NumberfieldElementHandler=nfelem_handler) - def _init_from_normaliz_cone(self, normaliz_cone, normaliz_field): + def _init_from_normaliz_cone(self, normaliz_cone, internal_base_ring): """ Construct polyhedron from a PyNormaliz wrapper of a normaliz cone. @@ -293,9 +276,9 @@ def _init_from_normaliz_cone(self, normaliz_cone, normaliz_field): sage: from sage.geometry.polyhedron.backend_normaliz import Polyhedron_normaliz # optional - pynormaliz sage: Polyhedron_normaliz._init_from_Hrepresentation(p, [], []) # indirect doctest # optional - pynormaliz """ - if normaliz_field is None: - normaliz_field = QQ - self._normaliz_field = normaliz_field + if internal_base_ring is None: + internal_base_ring = QQ + self._internal_base_ring = internal_base_ring if normaliz_cone and self._nmz_result(normaliz_cone, "AffineDim") < 0: # Empty polyhedron. Special case because Normaliz defines the @@ -356,7 +339,7 @@ def _QQ_pair(x): # number field return [ _QQ_pair(c) for c in x.list() ] - def _init_from_normaliz_data(self, data, normaliz_field=None, verbose=False): + def _init_from_normaliz_data(self, data, internal_base_ring=None, verbose=False): """ Construct polyhedron from normaliz ``data`` (a dictionary). @@ -377,15 +360,15 @@ def _init_from_normaliz_data(self, data, normaliz_field=None, verbose=False): sage: from sage.geometry.polyhedron.parent import Polyhedra_normaliz # optional - pynormaliz sage: parent = Polyhedra_normaliz(AA, 2, 'normaliz') # optional - pynormaliz # optional - sage.rings.number_field sage: Polyhedron_normaliz(parent, None, None, normaliz_data=data, # indirect doctest, optional - pynormaliz # optional - sage.rings.number_field - ....: normaliz_field=QuadraticField(2)) + ....: internal_base_ring=QuadraticField(2)) A 2-dimensional polyhedron in AA^2 defined as the convex hull of 1 vertex and 2 rays sage: _.inequalities_list() # optional - pynormaliz # optional - sage.rings.number_field [[0, -1/2, 1], [0, 2, -1]] """ - if normaliz_field is None: - normaliz_field = QQ + if internal_base_ring is None: + internal_base_ring = QQ cone = self._cone_from_normaliz_data(data, verbose) - self._init_from_normaliz_cone(cone, normaliz_field) + self._init_from_normaliz_cone(cone, internal_base_ring) def _cone_from_normaliz_data(self, data, verbose=False): """ @@ -543,10 +526,9 @@ def vert_ray_line_NF(vertices, rays, lines): if lines is None: lines = [] - (nmz_vertices, nmz_rays, nmz_lines), normaliz_field \ - = self._compute_nmz_data_lists_and_field((vertices, rays, lines), - vert_ray_line_QQ, - vert_ray_line_NF) + (nmz_vertices, nmz_rays, nmz_lines), internal_base_ring \ + = self._compute_data_lists_and_internal_base_ring( + (vertices, rays, lines), vert_ray_line_QQ, vert_ray_line_NF) if not nmz_vertices and not nmz_rays and not nmz_lines: # Special case to avoid: @@ -557,10 +539,10 @@ def vert_ray_line_NF(vertices, rays, lines): data = {"vertices": nmz_vertices, "cone": nmz_rays, "subspace": nmz_lines} - number_field_data = self._number_field_triple(normaliz_field) + number_field_data = self._number_field_triple(internal_base_ring) if number_field_data: data["number_field"] = number_field_data - self._init_from_normaliz_data(data, normaliz_field=normaliz_field, verbose=verbose) + self._init_from_normaliz_data(data, internal_base_ring=internal_base_ring, verbose=verbose) def _init_from_Hrepresentation(self, ieqs, eqns, minimize=True, verbose=False): r""" @@ -642,10 +624,9 @@ def nmz_ieqs_eqns_QQ(ieqs, eqns): if eqns is None: eqns = [] - (nmz_ieqs, nmz_eqns), normaliz_field \ - = self._compute_nmz_data_lists_and_field((ieqs, eqns), - nmz_ieqs_eqns_QQ, - nmz_ieqs_eqns_NF) + (nmz_ieqs, nmz_eqns), internal_base_ring \ + = self._compute_data_lists_and_internal_base_ring( + (ieqs, eqns), nmz_ieqs_eqns_QQ, nmz_ieqs_eqns_NF) if not nmz_ieqs: # If normaliz gets an empty list of inequalities, it adds # nonnegativities. So let's add a tautological inequality to work @@ -653,10 +634,10 @@ def nmz_ieqs_eqns_QQ(ieqs, eqns): nmz_ieqs.append([0] * self.ambient_dim() + [0]) data = {"inhom_equations": nmz_eqns, "inhom_inequalities": nmz_ieqs} - number_field_data = self._number_field_triple(normaliz_field) + number_field_data = self._number_field_triple(internal_base_ring) if number_field_data: data["number_field"] = number_field_data - self._init_from_normaliz_data(data, normaliz_field=normaliz_field, verbose=verbose) + self._init_from_normaliz_data(data, internal_base_ring=internal_base_ring, verbose=verbose) def _cone_from_Vrepresentation_and_Hrepresentation(self, vertices, rays, lines, ieqs, eqns=None, verbose=False, homogeneous=False): r""" @@ -850,10 +831,10 @@ def rays_subspace_lattice_ieqs_NF(vertices, rays, lines, ieqs): return nmz_vertices + nmz_rays, nmz_lines, nmz_lattice, nmz_ieqs - (nmz_extreme_rays, nmz_subspace, nmz_lattice, nmz_ieqs), normaliz_field \ - = self._compute_nmz_data_lists_and_field((vertices, rays, lines, ieqs), - rays_subspace_lattice_ieqs_QQ, - rays_subspace_lattice_ieqs_NF) + (nmz_extreme_rays, nmz_subspace, nmz_lattice, nmz_ieqs), internal_base_ring \ + = self._compute_data_lists_and_internal_base_ring( + (vertices, rays, lines, ieqs), rays_subspace_lattice_ieqs_QQ, + rays_subspace_lattice_ieqs_NF) data = {"extreme_rays": nmz_extreme_rays, "maximal_subspace": nmz_subspace, @@ -864,7 +845,7 @@ def rays_subspace_lattice_ieqs_NF(vertices, rays, lines, ieqs): if not homogeneous: data["dehomogenization"] = [[0] * (ambient_dim - 1) + [1]] - number_field_data = self._number_field_triple(normaliz_field) + number_field_data = self._number_field_triple(internal_base_ring) if number_field_data: data["number_field"] = number_field_data return self._cone_from_normaliz_data(data, verbose=verbose) @@ -910,71 +891,6 @@ def _test_far_facet_condition(self, tester=None, **options): tester.assertEqual(self.n_inequalities() + 1, len(nmz_ieqs)) tester.assertTrue(any(ieq == [0] * self.ambient_dim() + [1] for ieq in nmz_ieqs)) - def _compute_nmz_data_lists_and_field(self, data_lists, convert_QQ, convert_NF): - r""" - Compute data lists in Normaliz format and the number field to use with Normaliz. - - EXAMPLES:: - - sage: p = Polyhedron(vertices=[(0,1/2),(2,0),(4,5/6)], # optional - pynormaliz - ....: base_ring=AA, backend='normaliz') - sage: def convert_QQ(ieqs, eqs): # optional - pynormaliz - ....: return [ [ 1000*x for x in ieq ] for ieq in ieqs], \ - ....: [ [ 1000*x for x in eq ] for eq in eqs] - sage: def convert_NF(ieqs, eqs): # optional - pynormaliz - ....: return ieqs, eqs - sage: p._compute_nmz_data_lists_and_field([[[1]], [[1/2]]], # optional - pynormaliz - ....: convert_QQ, convert_NF) - (([[1000]], [[500]]), Rational Field) - sage: p._compute_nmz_data_lists_and_field([[[AA(1)]], [[1/2]]], # optional - pynormaliz - ....: convert_QQ, convert_NF) - (([[1000]], [[500]]), Rational Field) - sage: p._compute_nmz_data_lists_and_field([[[AA(sqrt(2))]], [[1/2]]], # optional - pynormaliz # optional - sage.rings.number_field - ....: convert_QQ, convert_NF) - ([[[a]], [[1/2]]], - Number Field in a with defining polynomial y^2 - 2 with a = 1.414213562373095?) - - TESTS:: - - sage: K. = QuadraticField(-5) # optional - sage.rings.number_field - sage: p = Polyhedron(vertices=[(a,1/2),(2,0),(4,5/6)], # indirect doctest # optional - pynormaliz # optional - sage.rings.number_field - ....: base_ring=K, backend='normaliz') - Traceback (most recent call last): - ... - ValueError: invalid base ring: Number Field in a ... is not real embedded - - Checks that :trac:`30248` is fixed:: - - sage: q = Polyhedron(backend='normaliz', base_ring=AA, # indirect doctest # optional - pynormaliz # optional - sage.rings.number_field - ....: rays=[(0, 0, 1), (0, 1, -1), (1, 0, -1)]); q - A 3-dimensional polyhedron in AA^3 defined as the convex hull of 1 vertex and 3 rays - sage: -q # optional - pynormaliz # optional - sage.rings.number_field - A 3-dimensional polyhedron in AA^3 defined as the convex hull of 1 vertex and 3 rays - """ - from sage.categories.number_fields import NumberFields - from sage.rings.real_double import RDF - - if self.base_ring() in (QQ, ZZ): - normaliz_field = QQ - nmz_data_lists = convert_QQ(*data_lists) - else: - # Allows to re-iterate if K is QQ below when data_lists contain - # iterators: - data_lists = [tuple(_) for _ in data_lists] - nmz_data_lists = convert_NF(*data_lists) - if self.base_ring() in NumberFields(): - if not RDF.has_coerce_map_from(self.base_ring()): - raise ValueError("invalid base ring: {} is a number field that is not real embedded".format(self.base_ring())) - normaliz_field = self.base_ring() - else: - K, nmz_data_lists, hom = _number_field_elements_from_algebraics_list_of_lists_of_lists(nmz_data_lists, embedded=True) - normaliz_field = K - if K is QQ: - # Compute it with Normaliz, not QNormaliz - nmz_data_lists = convert_QQ(*[ [ [ QQ(x) for x in v ] for v in l] - for l in data_lists ]) - return nmz_data_lists, normaliz_field - def _init_Vrepresentation_from_normaliz(self): r""" Create the Vrepresentation objects from the normaliz polyhedron. @@ -1057,7 +973,7 @@ def _init_empty_polyhedron(self): self._normaliz_cone = None @classmethod - def _from_normaliz_cone(cls, parent, normaliz_cone, normaliz_field=None): + def _from_normaliz_cone(cls, parent, normaliz_cone, internal_base_ring=None): r""" Initializes a polyhedron from a PyNormaliz wrapper of a normaliz cone. @@ -1067,12 +983,12 @@ def _from_normaliz_cone(cls, parent, normaliz_cone, normaliz_field=None): ....: backend='normaliz') sage: PI = P.integral_hull() # indirect doctest; optional - pynormaliz """ - return cls(parent, None, None, normaliz_cone=normaliz_cone, normaliz_field=normaliz_field) + return cls(parent, None, None, normaliz_cone=normaliz_cone, internal_base_ring=internal_base_ring) @staticmethod - def _number_field_triple(normaliz_field): + def _number_field_triple(internal_base_ring): r""" - Construct the PyNormaliz triple that describes the number field ``normaliz_field``. + Construct the PyNormaliz triple that describes ``internal_base_ring``. TESTS:: @@ -1082,10 +998,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 + R = internal_base_ring 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) @@ -1262,7 +1179,7 @@ def __getstate__(self): A vertex at (0, 0, 1, 0), A vertex at (0, 1, 0, 0), A vertex at (1, 0, 0, 0)), - '_normaliz_field': Rational Field, + '_internal_base_ring': Rational Field, '_pickle_equations': [(-1, 1, 1, 1, 1)], '_pickle_inequalities': [(0, 0, 0, 0, 1), (0, 0, 0, 1, 0), @@ -1329,7 +1246,7 @@ def __setstate__(self, state): sage: P = polytopes.dodecahedron(backend='normaliz') # optional - pynormaliz # optional - sage.rings.number_field sage: P1 = loads(dumps(P)) # optional - pynormaliz # optional - sage.rings.number_field - sage: P2 = Polyhedron_normaliz(P1.parent(), None, None, P1._normaliz_cone, normaliz_field=P1._normaliz_field) # optional - pynormaliz # optional - sage.rings.number_field + sage: P2 = Polyhedron_normaliz(P1.parent(), None, None, P1._normaliz_cone, internal_base_ring=P1._internal_base_ring) # optional - pynormaliz # optional - sage.rings.number_field sage: P == P2 # optional - pynormaliz # optional - sage.rings.number_field True @@ -1536,7 +1453,7 @@ def _volume_normaliz(self, measure='euclidean'): if measure == 'euclidean': return self._nmz_result(cone, 'EuclideanVolume') elif measure == 'induced_lattice': - if self._normaliz_field in (ZZ, QQ): + if self._internal_base_ring in (ZZ, QQ): return self._nmz_result(cone, 'Volume') else: return self._nmz_result(cone, 'RenfVolume') @@ -2285,8 +2202,9 @@ class functions. sage: S = polytopes.simplex(3, backend = 'normaliz'); S # optional - pynormaliz A 3-dimensional polyhedron in ZZ^4 defined as the convex hull of 4 vertices - sage: G = S.restricted_automorphism_group(output = 'permutation'); G # optional - pynormaliz - Permutation Group with generators [(2,3), (1,2), (0,1)] + sage: G = S.restricted_automorphism_group(output = 'permutation'); # optional - pynormaliz + sage: G.is_isomorphic(SymmetricGroup(4)) # optional - pynormaliz + True sage: len(G) # optional - pynormaliz 24 sage: Hstar = S._Hstar_function_normaliz(G); Hstar # optional - pynormaliz @@ -2306,10 +2224,9 @@ class functions. sage: P = Polyhedron(vertices=[[0,0,1],[0,0,-1],[1,0,1],[-1,0,-1],[0,1,1], # optional - pynormaliz ....: [0,-1,-1],[1,1,1],[-1,-1,-1]],backend='normaliz') # optional - pynormaliz sage: K = P.restricted_automorphism_group(output = 'permutation') # optional - pynormaliz - sage: G = K.subgroup(gens = [K[6]]); G # optional - pynormaliz - Subgroup generated by [(0,2)(1,3)(4,6)(5,7)] of (Permutation Group with generators [(2,4)(3,5), (1,2)(5,6), (0,1)(2,3)(4,5)(6,7), (0,7)(1,3)(2,5)(4,6)]) + sage: G = K.subgroup(gens = [K([(0,2),(1,3),(4,6),(5,7)])]) # optional - pynormaliz sage: conj_reps = G.conjugacy_classes_representatives() # optional - pynormaliz - sage: Dict = P.permutations_to_matrices(conj_reps, acting_group = G) # optional - pynormaliz + sage: Dict = P.permutations_to_matrices(conj_reps, acting_group = G) # optional - pynormaliz sage: list(Dict.keys())[0] # optional - pynormaliz (0,2)(1,3)(4,6)(5,7) sage: list(Dict.values())[0] # optional - pynormaliz @@ -2336,8 +2253,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 +2393,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()) @@ -2513,13 +2433,15 @@ class functions of the acting group. A character `\rho` is effective if The `H^*` series of the two-dimensional permutahedron under the action of the symmetric group is effective:: - sage: p2 = polytopes.permutahedron(3, backend = 'normaliz') # optional - pynormaliz - sage: G = p2.restricted_automorphism_group(output='permutation') # optional - pynormaliz - sage: H = G.subgroup(gens=[G.gens()[1],G.gens()[2]]) # optional - pynormaliz - sage: H.order() # optional - pynormaliz - 6 - sage: [Hstar, Hlin] = [p2.Hstar_function(H), p2.Hstar_function(H, output = 'Hstar_as_lin_comb')] # optional - pynormaliz - sage: p2._is_effective_normaliz(Hstar,Hlin) # optional - pynormaliz + sage: p3 = polytopes.permutahedron(3, backend = 'normaliz') # optional - pynormaliz + sage: G = p3.restricted_automorphism_group(output='permutation') # optional - pynormaliz + sage: reflection12 = G([(0,2),(1,4),(3,5)]) # optional - pynormaliz + sage: reflection23 = G([(0,1),(2,3),(4,5)]) # optional - pynormaliz + sage: S3 = G.subgroup(gens=[reflection12, reflection23]) # optional - pynormaliz + sage: S3.is_isomorphic(SymmetricGroup(3)) # optional - pynormaliz + True + sage: [Hstar, Hlin] = [p3.Hstar_function(S3), p3.Hstar_function(S3, output = 'Hstar_as_lin_comb')] # optional - pynormaliz + sage: p3._is_effective_normaliz(Hstar,Hlin) # optional - pynormaliz True If the `H^*`-series is not polynomial, then it is not effective:: @@ -2527,7 +2449,7 @@ class functions of the acting group. A character `\rho` is effective if sage: P = Polyhedron(vertices=[[0,0,1],[0,0,-1],[1,0,1],[-1,0,-1],[0,1,1], # optional - pynormaliz ....: [0,-1,-1],[1,1,1],[-1,-1,-1]],backend='normaliz') # optional - pynormaliz sage: G = P.restricted_automorphism_group(output = 'permutation') # optional - pynormaliz - sage: H = G.subgroup(gens = [G[6]]) # optional - pynormaliz + sage: H = G.subgroup(gens = [G([(0,2),(1,3),(4,6),(5,7)])]) # optional - pynormaliz sage: Hstar = P.Hstar_function(H); Hstar # optional - pynormaliz (chi_0*t^4 + (3*chi_0 + 3*chi_1)*t^3 + (8*chi_0 + 2*chi_1)*t^2 + (3*chi_0 + 3*chi_1)*t + chi_0)/(t + 1) sage: Hstar_lin = P.Hstar_function(H, output = 'Hstar_as_lin_comb') # optional - pynormaliz diff --git a/src/sage/geometry/polyhedron/backend_number_field.py b/src/sage/geometry/polyhedron/backend_number_field.py new file mode 100644 index 00000000000..437550de3aa --- /dev/null +++ b/src/sage/geometry/polyhedron/backend_number_field.py @@ -0,0 +1,166 @@ +r""" +The Python backend, using number fields internally +""" + +# **************************************************************************** +# Copyright (C) 2016-2022 Matthias Köppe +# 2016-2018 Travis Scrimshaw +# 2017 Jeroen Demeyer +# 2018-2020 Jean-Philippe Labbé +# 2019 Vincent Delecroix +# 2019-2021 Jonathan Kliem +# 2019-2021 Sophia Elia +# 2020 Frédéric Chapoton +# 2022 Yuan Zhou +# +# 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 .backend_field import Polyhedron_field +from .base_number_field import Polyhedron_base_number_field + + +class Polyhedron_number_field(Polyhedron_field, Polyhedron_base_number_field): + r""" + Polyhedra whose data can be converted to number field elements + + All computations are done internally using a fixed real embedded number field, + which is determined automatically. + + INPUT: + + - ``Vrep`` -- a list ``[vertices, rays, lines]`` or ``None``. + + - ``Hrep`` -- a list ``[ieqs, eqns]`` or ``None``. + + EXAMPLES:: + + sage: P = Polyhedron(vertices=[[1], [sqrt(2)]], backend='number_field') # optional - sage.rings.number_field + sage: P # optional - sage.rings.number_field + A 1-dimensional polyhedron + in (Symbolic Ring)^1 defined as the convex hull of 2 vertices + sage: P.vertices() # optional - sage.rings.number_field + (A vertex at (1), A vertex at (sqrt(2))) + + sage: P = polytopes.icosahedron(exact=True, backend='number_field') # optional - sage.rings.number_field + sage: P # 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 + defined as the convex hull of 12 vertices + + sage: x = polygen(ZZ); P = Polyhedron( # optional - sage.rings.number_field + ....: vertices=[[sqrt(2)], [AA.polynomial_root(x^3-2, RIF(0,3))]], + ....: backend='number_field') + sage: P # optional - sage.rings.number_field + A 1-dimensional polyhedron + in (Symbolic Ring)^1 defined as the convex hull of 2 vertices + sage: P.vertices() # optional - sage.rings.number_field + (A vertex at (sqrt(2)), A vertex at (2^(1/3))) + + TESTS: + + Tests from :class:`~sage.geometry.polyhedron.backend_field.Polyhedron_field` -- + here the data are already either in a number field or in ``AA``:: + + sage: p = Polyhedron(vertices=[(0,0),(AA(2).sqrt(),0),(0,AA(3).sqrt())], # optional - sage.rings.number_field + ....: rays=[(1,1)], lines=[], backend='number_field', base_ring=AA) + sage: TestSuite(p).run() # optional - sage.rings.number_field + + sage: K. = QuadraticField(3) # optional - sage.rings.number_field + sage: p = Polyhedron([(0,0), (1,0), (1/2, sqrt3/2)], backend='number_field') # optional - sage.rings.number_field + sage: TestSuite(p).run() # optional - sage.rings.number_field + + sage: K. = NumberField(x^2-x-1, embedding=1.618) # optional - sage.rings.number_field + sage: P1 = Polyhedron([[0,1], [1,1], [1,-phi+1]], backend='number_field') # optional - sage.rings.number_field + sage: P2 = Polyhedron(ieqs=[[-1,-phi,0]], backend='number_field') # optional - sage.rings.number_field + sage: P1.intersection(P2) # optional - sage.rings.number_field + The empty polyhedron + in (Number Field in phi with defining polynomial x^2 - x - 1 + with phi = 1.618033988749895?)^2 + + sage: Polyhedron(lines=[[1]], backend='number_field') + A 1-dimensional polyhedron in QQ^1 defined as the convex hull of 1 vertex and 1 line + """ + + def _init_from_Vrepresentation(self, vertices, rays, lines, + minimize=True, verbose=False): + """ + Construct polyhedron from V-representation data. + + INPUT: + + - ``vertices`` -- list of points. Each point can be specified + as any iterable container of + :meth:`~sage.geometry.polyhedron.base.base_ring` elements. + + - ``rays`` -- list of rays. Each ray can be specified as any + iterable container of + :meth:`~sage.geometry.polyhedron.base.base_ring` elements. + + - ``lines`` -- list of lines. Each line can be specified as + any iterable container of + :meth:`~sage.geometry.polyhedron.base.base_ring` elements. + + - ``verbose`` -- boolean (default: ``False``). Whether to print + verbose output for debugging purposes. + + EXAMPLES:: + + sage: p = Polyhedron(ambient_dim=2, backend='number_field') + sage: from sage.geometry.polyhedron.backend_number_field import Polyhedron_number_field + sage: Polyhedron_number_field._init_from_Vrepresentation(p, [(0,0)], [], []) + + TESTS: + + Check that the coordinates of a vertex get simplified in the Symbolic Ring:: + + sage: p = Polyhedron(ambient_dim=2, base_ring=SR, backend='number_field') + sage: from sage.geometry.polyhedron.backend_number_field import Polyhedron_number_field + sage: Polyhedron_number_field._init_from_Vrepresentation(p, [(0,1/2),(sqrt(2),0),(4,5/6)], [], []); p + A 2-dimensional polyhedron in (Symbolic Ring)^2 defined as the convex hull of 3 vertices + sage: p.vertices()[0][0] + 0 + """ + (vertices, rays, lines), internal_base_ring \ + = self._compute_data_lists_and_internal_base_ring((vertices, rays, lines), + lambda *x: x, lambda *x: x) + self._internal_base_ring = internal_base_ring + super()._init_from_Vrepresentation(vertices, rays, lines, + minimize=minimize, verbose=verbose, + internal_base_ring=internal_base_ring) + + def _init_from_Hrepresentation(self, ieqs, eqns, minimize=True, verbose=False): + """ + Construct polyhedron from H-representation data. + + INPUT: + + - ``ieqs`` -- list of inequalities. Each line can be specified + as any iterable container of + :meth:`~sage.geometry.polyhedron.base.base_ring` elements. + + - ``eqns`` -- list of equalities. Each line can be specified + as any iterable container of + :meth:`~sage.geometry.polyhedron.base.base_ring` elements. + + - ``verbose`` -- boolean (default: ``False``). Whether to print + verbose output for debugging purposes. + + TESTS:: + + sage: p = Polyhedron(ambient_dim=2, backend='number_field') + sage: from sage.geometry.polyhedron.backend_number_field import Polyhedron_number_field + sage: Polyhedron_number_field._init_from_Hrepresentation(p, [(1, 2, 3)], []) + """ + (ieqs, eqns), internal_base_ring \ + = self._compute_data_lists_and_internal_base_ring((ieqs, eqns), + lambda *x: x, lambda *x: x) + self._internal_base_ring = internal_base_ring + super()._init_from_Hrepresentation(ieqs, eqns, + minimize=minimize, verbose=verbose, + internal_base_ring=internal_base_ring) diff --git a/src/sage/geometry/polyhedron/base.py b/src/sage/geometry/polyhedron/base.py index dbf8b4370b0..77a11c53d21 100644 --- a/src/sage/geometry/polyhedron/base.py +++ b/src/sage/geometry/polyhedron/base.py @@ -974,11 +974,12 @@ def permutations_to_matrices(self, conj_class_reps, acting_group=None, additiona sage: aut_square = square.restricted_automorphism_group(output = 'permutation') # optional - pynormaliz sage: conj_reps = aut_square.conjugacy_classes_representatives() # optional - pynormaliz sage: gens_dict = square.permutations_to_matrices(conj_reps); # optional - pynormaliz - sage: conj_reps[1],gens_dict[conj_reps[1]] # optional - pynormaliz + sage: rotation_180 = aut_square([(0,3),(1,2)]) # optional - pynormaliz + sage: rotation_180,gens_dict[rotation_180] # optional - pynormaliz ( - [0 1 0] - [1 0 0] - (1,2), [0 0 1] + [-1 0 0] + [ 0 -1 0] + (0,3)(1,2), [ 0 0 1] ) This example tests the functionality for additional elements:: @@ -986,8 +987,7 @@ def permutations_to_matrices(self, conj_class_reps, acting_group=None, additiona sage: C = polytopes.cross_polytope(2) sage: G = C.restricted_automorphism_group(output = 'permutation') sage: conj_reps = G.conjugacy_classes_representatives() - sage: add_elt = G[6]; add_elt - (0,2,3,1) + sage: add_elt = G([(0,2,3,1)]) sage: dict = C.permutations_to_matrices(conj_reps,additional_elts = [add_elt]) sage: dict[add_elt] [ 0 1 0] diff --git a/src/sage/geometry/polyhedron/base0.py b/src/sage/geometry/polyhedron/base0.py index cc2d8befced..7ed7e777374 100644 --- a/src/sage/geometry/polyhedron/base0.py +++ b/src/sage/geometry/polyhedron/base0.py @@ -1366,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') @@ -1431,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/base3.py b/src/sage/geometry/polyhedron/base3.py index 6b983a00395..32a335ce586 100644 --- a/src/sage/geometry/polyhedron/base3.py +++ b/src/sage/geometry/polyhedron/base3.py @@ -135,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] @@ -1870,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/base5.py b/src/sage/geometry/polyhedron/base5.py index f16bce682b9..7a77f9685a3 100644 --- a/src/sage/geometry/polyhedron/base5.py +++ b/src/sage/geometry/polyhedron/base5.py @@ -2440,7 +2440,7 @@ def _test_lawrence(self, tester=None, **options): if self.backend() == 'normaliz' and not self.base_ring() in (ZZ, QQ): # Speeds up the doctest for significantly. - self = self.change_ring(self._normaliz_field) + self = self.change_ring(self._internal_base_ring) if not self.is_compact(): with tester.assertRaises(NotImplementedError): diff --git a/src/sage/geometry/polyhedron/base6.py b/src/sage/geometry/polyhedron/base6.py index 3257c27fe0a..7a1f4882440 100644 --- a/src/sage/geometry/polyhedron/base6.py +++ b/src/sage/geometry/polyhedron/base6.py @@ -34,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 @@ -48,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)}, @@ -125,6 +127,7 @@ class Polyhedron_base6(Polyhedron_base5): %% %% \end{tikzpicture} + \end{document} sage: Q = polytopes.hypercube(4) sage: Polyhedron_base6.show(Q) @@ -473,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. @@ -494,10 +499,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:: @@ -535,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 @@ -556,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""" @@ -698,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: @@ -965,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: @@ -1174,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), @@ -1185,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:: @@ -1443,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 634edc2d7a0..db828e2eb0a 100644 --- a/src/sage/geometry/polyhedron/base7.py +++ b/src/sage/geometry/polyhedron/base7.py @@ -35,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): @@ -712,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 @@ -908,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_QQ.py b/src/sage/geometry/polyhedron/base_QQ.py index 95c4f4e2b1a..0efcb15f1a2 100644 --- a/src/sage/geometry/polyhedron/base_QQ.py +++ b/src/sage/geometry/polyhedron/base_QQ.py @@ -846,13 +846,9 @@ def fixed_subpolytope(self, vertex_permutation): You can obtain non-trivial examples:: - sage: fsp1 = Cube.fixed_subpolytope(reprs[8]);fsp1 # optional - pynormaliz - A 0-dimensional polyhedron in QQ^3 defined as the convex hull of 1 vertex - sage: fsp1.vertices() # optional - pynormaliz - (A vertex at (0, 0, 0),) - sage: fsp2 = Cube.fixed_subpolytope(reprs[3]);fsp2 # optional - pynormaliz + sage: fsp = Cube.fixed_subpolytope(AG([(0,1),(2,3),(4,5),(6,7)]));fsp # optional - pynormaliz A 2-dimensional polyhedron in QQ^3 defined as the convex hull of 4 vertices - sage: fsp2.vertices() # optional - pynormaliz + sage: fsp.vertices() # optional - pynormaliz (A vertex at (-1, -1, 0), A vertex at (-1, 1, 0), A vertex at (1, -1, 0), @@ -860,22 +856,17 @@ def fixed_subpolytope(self, vertex_permutation): The next example shows that fixed_subpolytope works for rational polytopes:: - sage: P = Polyhedron(vertices = [[0,0],[3/2,0],[3/2,3/2],[0,3/2]], backend ='normaliz') # optional - pynormaliz - sage: P.vertices() # optional - pynormaliz - (A vertex at (0, 0), - A vertex at (0, 3/2), - A vertex at (3/2, 0), - A vertex at (3/2, 3/2)) - sage: G = P.restricted_automorphism_group(output = 'permutation');G # optional - pynormaliz - Permutation Group with generators [(1,2), (0,1)(2,3), (0,3)] - sage: len(G) # optional - pynormaliz - 8 - sage: G[2] # optional - pynormaliz - (0,1)(2,3) - sage: fixed_set = P.fixed_subpolytope(G[2]); fixed_set # optional - pynormaliz - A 1-dimensional polyhedron in QQ^2 defined as the convex hull of 2 vertices - sage: fixed_set.vertices() # optional - pynormaliz - (A vertex at (0, 3/4), A vertex at (3/2, 3/4)) + sage: P = Polyhedron(vertices=[[0],[1/2]], backend='normaliz') # optional - pynormaliz + sage: P.vertices() # optional - pynormaliz + (A vertex at (0), A vertex at (1/2)) + sage: G = P.restricted_automorphism_group(output='permutation');G # optional - pynormaliz + Permutation Group with generators [(0,1)] + sage: len(G) # optional - pynormaliz + 2 + sage: fixed_set = P.fixed_subpolytope(G.gens()[0]); fixed_set # optional - pynormaliz + A 0-dimensional polyhedron in QQ^1 defined as the convex hull of 1 vertex + sage: fixed_set.vertices_list() # optional - pynormaliz + [[1/4]] """ if self.is_empty(): raise NotImplementedError('empty polyhedra are not supported') @@ -943,14 +934,10 @@ def fixed_subpolytopes(self, conj_class_reps): sage: aut_p = p.restricted_automorphism_group(output = 'permutation') # optional - pynormaliz sage: aut_p.order() # optional - pynormaliz 8 - sage: conj_list = aut_p.conjugacy_classes_representatives(); conj_list # optional - pynormaliz - [(), (1,2), (0,1)(2,3), (0,1,3,2), (0,3)(1,2)] - sage: p.fixed_subpolytopes(conj_list) # optional - pynormaliz - {(): A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 4 vertices, - (1,2): A 1-dimensional polyhedron in QQ^2 defined as the convex hull of 2 vertices, - (0,1)(2,3): A 1-dimensional polyhedron in QQ^2 defined as the convex hull of 2 vertices, - (0,1,3,2): A 0-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex, - (0,3)(1,2): A 0-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex} + sage: conj_list = aut_p.conjugacy_classes_representatives(); # optional - pynormaliz + sage: fixedpolytopes_dictionary = p.fixed_subpolytopes(conj_list) # optional - pynormaliz + sage: fixedpolytopes_dictionary[aut_p([(0,3),(1,2)])] # optional - pynormaliz + A 0-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex TESTS:: @@ -1025,10 +1012,9 @@ class functions. sage: S = polytopes.simplex(3, backend = 'normaliz'); S # optional - pynormaliz A 3-dimensional polyhedron in ZZ^4 defined as the convex hull of 4 vertices - sage: G = S.restricted_automorphism_group(output = 'permutation'); G # optional - pynormaliz - Permutation Group with generators [(2,3), (1,2), (0,1)] - sage: len(G) # optional - pynormaliz - 24 + sage: G = S.restricted_automorphism_group(output = 'permutation') # optional - pynormaliz + sage: G.is_isomorphic(SymmetricGroup(4)) # optional - pynormaliz + True sage: Hstar = S._Hstar_function_normaliz(G); Hstar # optional - pynormaliz chi_4 sage: G.character_table() # optional - pynormaliz @@ -1046,10 +1032,9 @@ class functions. sage: P = Polyhedron(vertices=[[0,0,1],[0,0,-1],[1,0,1],[-1,0,-1],[0,1,1], # optional - pynormaliz ....: [0,-1,-1],[1,1,1],[-1,-1,-1]],backend='normaliz') # optional - pynormaliz sage: K = P.restricted_automorphism_group(output = 'permutation') # optional - pynormaliz - sage: G = K.subgroup(gens = [K[6]]); G # optional - pynormaliz - Subgroup generated by [(0,2)(1,3)(4,6)(5,7)] of (Permutation Group with generators [(2,4)(3,5), (1,2)(5,6), (0,1)(2,3)(4,5)(6,7), (0,7)(1,3)(2,5)(4,6)]) + sage: G = K.subgroup(gens = [K([(0,2),(1,3),(4,6),(5,7)])]) # optional - pynormaliz sage: conj_reps = G.conjugacy_classes_representatives() # optional - pynormaliz - sage: Dict = P.permutations_to_matrices(conj_reps, acting_group = G) # optional - pynormaliz + sage: Dict = P.permutations_to_matrices(conj_reps, acting_group = G) # optional - pynormaliz sage: list(Dict.keys())[0] # optional - pynormaliz (0,2)(1,3)(4,6)(5,7) sage: list(Dict.values())[0] # optional - pynormaliz @@ -1166,13 +1151,15 @@ class functions of the acting group. A character `\rho` is effective if The `H^*` series of the two-dimensional permutahedron under the action of the symmetric group is effective:: - sage: p2 = polytopes.permutahedron(3, backend = 'normaliz') # optional - pynormaliz - sage: G = p2.restricted_automorphism_group(output='permutation') # optional - pynormaliz - sage: H = G.subgroup(gens=[G.gens()[1],G.gens()[2]]) # optional - pynormaliz - sage: H.order() # optional - pynormaliz - 6 - sage: [Hstar, Hlin] = [p2.Hstar_function(H), p2.Hstar_function(H, output = 'Hstar_as_lin_comb')] # optional - pynormaliz - sage: p2.is_effective(Hstar,Hlin) # optional - pynormaliz + sage: p3 = polytopes.permutahedron(3, backend = 'normaliz') # optional - pynormaliz + sage: G = p3.restricted_automorphism_group(output='permutation') # optional - pynormaliz + sage: reflection12 = G([(0,2),(1,4),(3,5)]) # optional - pynormaliz + sage: reflection23 = G([(0,1),(2,3),(4,5)]) # optional - pynormaliz + sage: S3 = G.subgroup(gens=[reflection12, reflection23]) # optional - pynormaliz + sage: S3.is_isomorphic(SymmetricGroup(3)) # optional - pynormaliz + True + sage: [Hstar, Hlin] = [p3.Hstar_function(S3), p3.Hstar_function(S3, output = 'Hstar_as_lin_comb')] # optional - pynormaliz + sage: p3.is_effective(Hstar,Hlin) # optional - pynormaliz True If the `H^*`-series is not polynomial, then it is not effective:: @@ -1180,7 +1167,7 @@ class functions of the acting group. A character `\rho` is effective if sage: P = Polyhedron(vertices=[[0,0,1],[0,0,-1],[1,0,1],[-1,0,-1],[0,1,1], # optional - pynormaliz ....: [0,-1,-1],[1,1,1],[-1,-1,-1]],backend='normaliz') # optional - pynormaliz sage: G = P.restricted_automorphism_group(output = 'permutation') # optional - pynormaliz - sage: H = G.subgroup(gens = [G[6]]) # optional - pynormaliz + sage: H = G.subgroup(gens = [G([(0,2),(1,3),(4,6),(5,7)])]) # optional - pynormaliz sage: Hstar = P.Hstar_function(H); Hstar # optional - pynormaliz (chi_0*t^4 + (3*chi_0 + 3*chi_1)*t^3 + (8*chi_0 + 2*chi_1)*t^2 + (3*chi_0 + 3*chi_1)*t + chi_0)/(t + 1) sage: Hstar_lin = P.Hstar_function(H, output = 'Hstar_as_lin_comb') # optional - pynormaliz 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/base_number_field.py b/src/sage/geometry/polyhedron/base_number_field.py new file mode 100644 index 00000000000..08040db3794 --- /dev/null +++ b/src/sage/geometry/polyhedron/base_number_field.py @@ -0,0 +1,118 @@ +r""" +Support for internal use of number fields in backends for polyhedral computations +""" + +# **************************************************************************** +# Copyright (C) 2016-2022 Matthias Köppe +# 2016-2018 Travis Scrimshaw +# 2017 Jeroen Demeyer +# 2018-2020 Jean-Philippe Labbé +# 2019 Vincent Delecroix +# 2019-2021 Jonathan Kliem +# 2019-2021 Sophia Elia +# 2020 Frédéric Chapoton +# 2022 Yuan Zhou +# +# 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.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ + +from .base import Polyhedron_base + + +def _number_field_elements_from_algebraics_list_of_lists_of_lists(listss, **kwds): + r""" + Like `number_field_elements_from_algebraics`, but for a list of lists of lists. + + EXAMPLES:: + + sage: rt2 = AA(sqrt(2)); rt2 # optional - sage.rings.number_field + 1.414213562373095? + sage: rt3 = AA(sqrt(3)); rt3 # optional - sage.rings.number_field + 1.732050807568878? + sage: from sage.geometry.polyhedron.base_number_field import _number_field_elements_from_algebraics_list_of_lists_of_lists + sage: K, results, hom = _number_field_elements_from_algebraics_list_of_lists_of_lists([[[rt2], [1]], [[rt3]], [[1], []]]); results # optional - sage.rings.number_field + [[[-a^3 + 3*a], [1]], [[-a^2 + 2]], [[1], []]] + """ + from sage.rings.qqbar import number_field_elements_from_algebraics + numbers = [] + for lists in listss: + for list in lists: + numbers.extend(list) + K, K_numbers, hom = number_field_elements_from_algebraics(numbers, **kwds) + g = iter(K_numbers) + return K, [ [ [ next(g) for _ in list ] for list in lists ] for lists in listss ], hom + + +class Polyhedron_base_number_field(Polyhedron_base): + + def _compute_data_lists_and_internal_base_ring(self, data_lists, convert_QQ, convert_NF): + r""" + Compute data lists in Normaliz or ``number_field`` backend format and the internal base ring of the data. + + EXAMPLES:: + + sage: p = Polyhedron(vertices=[(0,1/2),(2,0),(4,5/6)], # optional - pynormaliz + ....: base_ring=AA, backend='normaliz') + sage: def convert_QQ(ieqs, eqs): # optional - pynormaliz + ....: return [ [ 1000*x for x in ieq ] for ieq in ieqs], \ + ....: [ [ 1000*x for x in eq ] for eq in eqs] + sage: def convert_NF(ieqs, eqs): # optional - pynormaliz + ....: return ieqs, eqs + sage: p._compute_data_lists_and_internal_base_ring([[[1]], [[1/2]]], # optional - pynormaliz + ....: convert_QQ, convert_NF) + (([[1000]], [[500]]), Rational Field) + sage: p._compute_data_lists_and_internal_base_ring([[[AA(1)]], [[1/2]]], # optional - pynormaliz + ....: convert_QQ, convert_NF) + (([[1000]], [[500]]), Rational Field) + sage: p._compute_data_lists_and_internal_base_ring([[[AA(sqrt(2))]], [[1/2]]], # optional - pynormaliz # optional - sage.rings.number_field + ....: convert_QQ, convert_NF) + ([[[a]], [[1/2]]], + Number Field in a with defining polynomial y^2 - 2 with a = 1.414213562373095?) + + TESTS:: + + sage: K. = QuadraticField(-5) # optional - sage.rings.number_field + sage: p = Polyhedron(vertices=[(a,1/2),(2,0),(4,5/6)], # indirect doctest # optional - pynormaliz # optional - sage.rings.number_field + ....: base_ring=K, backend='normaliz') + Traceback (most recent call last): + ... + ValueError: invalid base ring: Number Field in a ... is not real embedded + + Checks that :trac:`30248` is fixed:: + + sage: q = Polyhedron(backend='normaliz', base_ring=AA, # indirect doctest # optional - pynormaliz # optional - sage.rings.number_field + ....: rays=[(0, 0, 1), (0, 1, -1), (1, 0, -1)]); q + A 3-dimensional polyhedron in AA^3 defined as the convex hull of 1 vertex and 3 rays + sage: -q # optional - pynormaliz # optional - sage.rings.number_field + A 3-dimensional polyhedron in AA^3 defined as the convex hull of 1 vertex and 3 rays + """ + from sage.categories.number_fields import NumberFields + from sage.rings.real_double import RDF + + if self.base_ring() in (QQ, ZZ): + internal_base_ring = QQ + internal_data_lists = convert_QQ(*data_lists) + else: + # Allows to re-iterate if K is QQ below when data_lists contain + # iterators: + data_lists = [tuple(_) for _ in data_lists] + internal_data_lists = convert_NF(*data_lists) + if self.base_ring() in NumberFields(): + if not RDF.has_coerce_map_from(self.base_ring()): + raise ValueError("invalid base ring: {} is a number field that is not real embedded".format(self.base_ring())) + internal_base_ring = self.base_ring() + else: + K, internal_data_lists, hom = _number_field_elements_from_algebraics_list_of_lists_of_lists(internal_data_lists, embedded=True) + internal_base_ring = K + if K is QQ: + # Compute it with Normaliz, not QNormaliz + internal_data_lists = convert_QQ(*[ [ [ QQ(x) for x in v ] for v in l] + for l in data_lists ]) + return internal_data_lists, internal_base_ring diff --git a/src/sage/geometry/polyhedron/face.py b/src/sage/geometry/polyhedron/face.py index d9dd5a6c4d6..421228813a9 100644 --- a/src/sage/geometry/polyhedron/face.py +++ b/src/sage/geometry/polyhedron/face.py @@ -1066,7 +1066,7 @@ def combinatorial_face_to_polyhedral_face(polyhedron, combinatorial_face): # Equations before inequalities in Hrep. H_indices = tuple(range(n_equations)) H_indices += tuple(x+n_equations for x in combinatorial_face.ambient_H_indices(add_equations=False)) - elif polyhedron.backend() in ('normaliz', 'cdd', 'field', 'polymake'): + elif polyhedron.backend() in ('normaliz', 'cdd', 'field', 'number_field', 'polymake'): # Equations after the inequalities in Hrep. n_ieqs = polyhedron.n_inequalities() H_indices = tuple(x for x in combinatorial_face.ambient_H_indices(add_equations=False)) diff --git a/src/sage/geometry/polyhedron/library.py b/src/sage/geometry/polyhedron/library.py index b71e1e29d2b..880eb4ee008 100644 --- a/src/sage/geometry/polyhedron/library.py +++ b/src/sage/geometry/polyhedron/library.py @@ -2575,7 +2575,6 @@ def tri(m): return parent([verts, [], []], [ieqs, eqns], Vrep_minimal=True, Hrep_minimal=True, pref_rep="Hrep") - def generalized_permutahedron(self, coxeter_type, point=None, exact=True, regular=False, backend=None): r""" Return the generalized permutahedron of type ``coxeter_type`` as the @@ -2638,38 +2637,19 @@ def generalized_permutahedron(self, coxeter_type, point=None, exact=True, regula A vertex at (1, 0), A vertex at (1, 1)) - Setting ``regular=True`` applies a linear transformation to get - isometric vertex figures and the result is inscribed. Even though there - are traces of small numbers, the internal computations are done using - an exact embedded NumberField:: + It works also with Coxeter types that lead to non-rational coordinates:: - sage: perm_a2_reg = polytopes.generalized_permutahedron(['A',2],regular=True) - sage: V = sorted(perm_a2_reg.vertices()); V # random - [A vertex at (-1, 0), - A vertex at (-1/2, -0.866025403784439?), - A vertex at (-1/2, 0.866025403784439?), - A vertex at (1/2, -0.866025403784439?), - A vertex at (1/2, 0.866025403784439?), - A vertex at (1.000000000000000?, 0.?e-18)] - sage: for v in V: - ....: for x in v: - ....: x.exactify() - sage: V - [A vertex at (-1, 0), - A vertex at (-1/2, -0.866025403784439?), - A vertex at (-1/2, 0.866025403784439?), - A vertex at (1/2, -0.866025403784439?), - A vertex at (1/2, 0.866025403784439?), - A vertex at (1, 0)] - sage: perm_a2_reg.is_inscribed() - True - sage: perm_a3_reg = polytopes.generalized_permutahedron(['A',3],regular=True) # long time - sage: perm_a3_reg.is_inscribed() # long time - True + sage: perm_b3 = polytopes.generalized_permutahedron(['B',3]); perm_b3 # long time # optional - sage.rings.number_field + A 3-dimensional polyhedron + in (Number Field in a with defining polynomial x^2 - 2 with a = 1.414213562373095?)^3 + defined as the convex hull of 48 vertices - The same is possible with vertices in ``RDF``:: + Setting ``regular=True`` applies a linear transformation to get + isometric vertex figures and the result is inscribed. This cannot be done using + rational coordinates. We first do the computations using floating point + approximations (``RDF``):: - sage: perm_a2_inexact = polytopes.generalized_permutahedron(['A',2],exact=False) + sage: perm_a2_inexact = polytopes.generalized_permutahedron(['A',2], exact=False) sage: sorted(perm_a2_inexact.vertices()) [A vertex at (-1.0, -1.0), A vertex at (-1.0, 0.0), @@ -2678,7 +2658,7 @@ def generalized_permutahedron(self, coxeter_type, point=None, exact=True, regula A vertex at (1.0, 0.0), A vertex at (1.0, 1.0)] - sage: perm_a2_inexact_reg = polytopes.generalized_permutahedron(['A',2],exact=False,regular=True) + sage: perm_a2_inexact_reg = polytopes.generalized_permutahedron(['A',2], exact=False, regular=True) sage: sorted(perm_a2_inexact_reg.vertices()) [A vertex at (-1.0, 0.0), A vertex at (-0.5, -0.8660254038), @@ -2687,29 +2667,77 @@ def generalized_permutahedron(self, coxeter_type, point=None, exact=True, regula A vertex at (0.5, 0.8660254038), A vertex at (1.0, 0.0)] - It works also with types with non-rational coordinates:: + We can do the same computation using exact arithmetic with the field ``AA``:: + + sage: perm_a2_reg = polytopes.generalized_permutahedron(['A',2], regular=True) # optional - sage.rings.number_field + sage: V = sorted(perm_a2_reg.vertices()); V # random # optional - sage.rings.number_field + [A vertex at (-1, 0), + A vertex at (-1/2, -0.866025403784439?), + A vertex at (-1/2, 0.866025403784439?), + A vertex at (1/2, -0.866025403784439?), + A vertex at (1/2, 0.866025403784439?), + A vertex at (1.000000000000000?, 0.?e-18)] + + Even though the numbers look like floating point approximations, the computation is + actually exact. We can clean up the display a bit using ``exactify``:: - sage: perm_b3 = polytopes.generalized_permutahedron(['B',3]); perm_b3 # long time - A 3-dimensional polyhedron in (Number Field in a with defining polynomial x^2 - 2 with a = 1.414213562373095?)^3 defined as the convex hull of 48 vertices + sage: for v in V: # optional - sage.rings.number_field + ....: for x in v: + ....: x.exactify() + sage: V # optional - sage.rings.number_field + [A vertex at (-1, 0), + A vertex at (-1/2, -0.866025403784439?), + A vertex at (-1/2, 0.866025403784439?), + A vertex at (1/2, -0.866025403784439?), + A vertex at (1/2, 0.866025403784439?), + A vertex at (1, 0)] + sage: perm_a2_reg.is_inscribed() # optional - sage.rings.number_field + True + + Larger examples take longer:: - sage: perm_b3_reg = polytopes.generalized_permutahedron(['B',3],regular=True); perm_b3_reg # not tested - long time (12sec on 64 bits). + sage: perm_a3_reg = polytopes.generalized_permutahedron(['A',3], regular=True); perm_a3_reg # long time # optional - sage.rings.number_field + A 3-dimensional polyhedron in AA^3 defined as the convex hull of 24 vertices + sage: perm_a3_reg.is_inscribed() # long time # optional - sage.rings.number_field + True + sage: perm_b3_reg = polytopes.generalized_permutahedron(['B',3], regular=True); perm_b3_reg # not tested - long time (12sec on 64 bits). A 3-dimensional polyhedron in AA^3 defined as the convex hull of 48 vertices - It is faster with the backend ``'normaliz'``:: + It is faster with the backend ``'number_field'``, which internally uses an embedded + number field instead of doing the computations directly with the base ring (``AA``):: - sage: perm_b3_reg_norm = polytopes.generalized_permutahedron(['B',3],regular=True,backend='normaliz') # optional - pynormaliz - sage: perm_b3_reg_norm # optional - pynormaliz + sage: perm_a3_reg_nf = polytopes.generalized_permutahedron( # optional - sage.rings.number_field + ....: ['A',3], regular=True, backend='number_field'); perm_a3_reg_nf + A 3-dimensional polyhedron in AA^3 defined as the convex hull of 24 vertices + sage: perm_a3_reg_nf.is_inscribed() # optional - sage.rings.number_field + True + sage: perm_b3_reg_nf = polytopes.generalized_permutahedron( # long time # optional - sage.rings.number_field + ....: ['B',3], regular=True, backend='number_field'); perm_b3_reg_nf A 3-dimensional polyhedron in AA^3 defined as the convex hull of 48 vertices - The backend ``'normaliz'`` allows further faster computation in the - non-rational case:: + It is even faster with the backend ``'normaliz'``:: + + sage: perm_a3_reg_norm = polytopes.generalized_permutahedron( # optional - pynormaliz + ....: ['A',3], regular=True, backend='normaliz'); perm_a3_reg_norm + A 3-dimensional polyhedron in AA^3 defined as the convex hull of 24 vertices + sage: perm_a3_reg_norm.is_inscribed() # optional - pynormaliz + True + sage: perm_b3_reg_norm = polytopes.generalized_permutahedron( # optional - pynormaliz + ....: ['B',3], regular=True, backend='normaliz'); perm_b3_reg_norm + A 3-dimensional polyhedron in AA^3 defined as the convex hull of 48 vertices - sage: perm_h3 = polytopes.generalized_permutahedron(['H',3],backend='normaliz') # optional - pynormaliz - sage: perm_h3 # optional - pynormaliz - 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 120 vertices - sage: perm_f4 = polytopes.generalized_permutahedron(['F',4],backend='normaliz') # optional - pynormaliz, long time - sage: perm_f4 # optional - pynormaliz, long time - A 4-dimensional polyhedron in (Number Field in a with defining polynomial x^2 - 2 with a = 1.414213562373095?)^4 defined as the convex hull of 1152 vertices + The speedups from using backend ``'normaliz'`` allow us to go even further:: + + sage: perm_h3 = polytopes.generalized_permutahedron(['H',3], backend='normaliz') # optional - pynormaliz + sage: perm_h3 # optional - pynormaliz + 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 120 vertices + sage: perm_f4 = polytopes.generalized_permutahedron(['F',4], backend='normaliz') # long time # optional - pynormaliz + sage: perm_f4 # long time # optional - pynormaliz + A 4-dimensional polyhedron + in (Number Field in a with defining polynomial x^2 - 2 with a = 1.414213562373095?)^4 + defined as the convex hull of 1152 vertices .. SEEALSO:: diff --git a/src/sage/geometry/polyhedron/parent.py b/src/sage/geometry/polyhedron/parent.py index 3eee9514560..24cf406df7e 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 - jupymake + 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 @@ -121,6 +121,10 @@ def Polyhedra(ambient_space_or_base_ring=None, ambient_dim=None, backend=None, * sage: SCR = SR.subring(no_variables=True) # optional - sage.symbolic sage: Polyhedra(SCR, 2, backend='normaliz') # optional - pynormaliz # optional - sage.symbolic Polyhedra in (Symbolic Constants Subring)^2 + + sage: Polyhedra(SCR, 2, backend='number_field') # optional - sage.symbolic + Polyhedra in (Symbolic Constants Subring)^2 + """ if ambient_space_or_base_ring is not None: if ambient_space_or_base_ring in Rings(): @@ -174,11 +178,14 @@ 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) + elif backend == 'number_field': + return Polyhedra_number_field(base_ring.fraction_field(), ambient_dim, backend) elif backend == 'field': if not base_ring.is_exact(): raise ValueError("the 'field' backend for polyhedron cannot be used with non-exact fields") @@ -264,13 +271,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 +502,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 +545,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 +628,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 +719,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 @@ -1137,13 +1164,14 @@ def _make_Line(self, polyhedron, data): return obj - from sage.geometry.polyhedron.backend_cdd import Polyhedron_QQ_cdd lazy_import('sage.geometry.polyhedron.backend_cdd_rdf', 'Polyhedron_RDF_cdd') from sage.geometry.polyhedron.backend_ppl import Polyhedron_ZZ_ppl, Polyhedron_QQ_ppl from sage.geometry.polyhedron.backend_normaliz import Polyhedron_normaliz, Polyhedron_ZZ_normaliz, Polyhedron_QQ_normaliz from sage.geometry.polyhedron.backend_polymake import Polyhedron_polymake from sage.geometry.polyhedron.backend_field import Polyhedron_field +from sage.geometry.polyhedron.backend_number_field import Polyhedron_number_field + class Polyhedra_ZZ_ppl(Polyhedra_base): Element = Polyhedron_ZZ_ppl @@ -1224,6 +1252,9 @@ class Polyhedra_polymake(Polyhedra_base): class Polyhedra_field(Polyhedra_base): Element = Polyhedron_field +class Polyhedra_number_field(Polyhedra_base): + Element = Polyhedron_number_field + @cached_function def does_backend_handle_base_ring(base_ring, backend): r""" @@ -1234,9 +1265,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 2c959b61195..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] 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/base/graph_backends.pyx b/src/sage/graphs/base/graph_backends.pyx index 9daf0702185..3ff36d5cee2 100644 --- a/src/sage/graphs/base/graph_backends.pyx +++ b/src/sage/graphs/base/graph_backends.pyx @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- r""" -Backends for Sage (di)graphs. +Backends for Sage (di)graphs This module implements :class:`GenericGraphBackend` (the base class for backends). diff --git a/src/sage/graphs/base/static_sparse_graph.pyx b/src/sage/graphs/base/static_sparse_graph.pyx index 23fe59aa8e3..6c4bc1b7edd 100644 --- a/src/sage/graphs/base/static_sparse_graph.pyx +++ b/src/sage/graphs/base/static_sparse_graph.pyx @@ -1,7 +1,7 @@ # cython: binding=True # distutils: language = c++ r""" -Static Sparse Graphs +Static sparse graphs What is the point ? ------------------- diff --git a/src/sage/graphs/chrompoly.pyx b/src/sage/graphs/chrompoly.pyx index d3afec0e278..441ba58643d 100644 --- a/src/sage/graphs/chrompoly.pyx +++ b/src/sage/graphs/chrompoly.pyx @@ -1,6 +1,6 @@ # cython: binding=True """ -Chromatic Polynomial +Chromatic polynomial AUTHORS: diff --git a/src/sage/graphs/generators/random.py b/src/sage/graphs/generators/random.py index 93cf637b8dd..1b0f4cd9d4d 100644 --- a/src/sage/graphs/generators/random.py +++ b/src/sage/graphs/generators/random.py @@ -805,19 +805,7 @@ def RandomHolmeKim(n, m, p, seed=None): may not be all linked to a new node on the first iteration like the BA model. - EXAMPLES: - - We check that a random graph on 8 nodes with 2 random edges per node and a - probability `p = 0.5` of forming triangles contains a triangle:: - - sage: G = graphs.RandomHolmeKim(8, 2, 0.5) - sage: G.order(), G.size() - (8, 12) - sage: C3 = graphs.CycleGraph(3) - sage: G.subgraph_search(C3) - Subgraph of (): Graph on 3 vertices - - :: + EXAMPLES:: sage: G = graphs.RandomHolmeKim(12, 3, .3) sage: G.show() # long time diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 0070705f781..23e66648fb2 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -12000,7 +12000,11 @@ def set_edge_label(self, u, v, l): EXAMPLES:: - sage: SD = DiGraph({1:[18,2], 2:[5,3], 3:[4,6], 4:[7,2], 5:[4], 6:[13,12], 7:[18,8,10], 8:[6,9,10], 9:[6], 10:[11,13], 11:[12], 12:[13], 13:[17,14], 14:[16,15], 15:[2], 16:[13], 17:[15,13], 18:[13]}, sparse=True) + sage: d = {1: [18, 2], 2: [5, 3], 3: [4, 6], 4: [7, 2], 5: [4], + ....: 6: [13, 12], 7: [18, 8, 10], 8: [6, 9, 10], 9: [6], + ....: 10: [11, 13], 11: [12], 12: [13], 13: [17, 14], + ....: 14: [16, 15], 15: [2], 16: [13], 17: [15, 13], 18: [13]} + sage: SD = DiGraph(d, sparse=True) sage: SD.set_edge_label(1, 18, 'discrete') sage: SD.set_edge_label(4, 7, 'discrete') sage: SD.set_edge_label(2, 5, 'h = 0') @@ -12013,7 +12017,11 @@ def set_edge_label(self, u, v, l): sage: SD.set_edge_label(13, 14, 'k = h') sage: SD.set_edge_label(17, 15, 'v_k finite') sage: SD.set_edge_label(14, 15, 'v_k m.c.r.') - sage: posn = {1:[ 3,-3], 2:[0,2], 3:[0, 13], 4:[3,9], 5:[3,3], 6:[16, 13], 7:[6,1], 8:[6,6], 9:[6,11], 10:[9,1], 11:[10,6], 12:[13,6], 13:[16,2], 14:[10,-6], 15:[0,-10], 16:[14,-6], 17:[16,-10], 18:[6,-4]} + sage: posn = {1: [3, -3], 2: [0, 2], 3: [0, 13], 4: [3, 9], + ....: 5: [3, 3], 6: [16, 13], 7: [6, 1], 8: [6, 6], + ....: 9: [6, 11], 10: [9, 1], 11: [10, 6], 12: [13, 6], + ....: 13: [16, 2], 14: [10, -6], 15: [0, -10], 16: [14, -6], + ....: 17: [16, -10], 18: [6, -4]} sage: SD.plot(pos=posn, vertex_size=400, vertex_colors={'#FFFFFF':list(range(1,19))}, edge_labels=True).show() # long time :: @@ -12066,7 +12074,8 @@ def set_edge_label(self, u, v, l): """ if self.allows_multiple_edges(): if len(self.edge_label(u, v)) > 1: - raise RuntimeError("cannot set edge label, since there are multiple edges from %s to %s"%(u,v)) + raise RuntimeError("cannot set edge label, since there are " + "multiple edges from %s to %s" % (u, v)) self._backend.set_edge_label(u, v, l, self._directed) def has_edge(self, u, v=None, label=None): @@ -12102,7 +12111,8 @@ def has_edge(self, u, v=None, label=None): label = None return self._backend.has_edge(u, v, label) - def edges(self, vertices=None, labels=True, sort=None, key=None, ignore_direction=False, sort_vertices=True): + def edges(self, vertices=None, labels=True, sort=None, key=None, + ignore_direction=False, sort_vertices=True): r""" Return a :class:`~EdgesView` of edges. @@ -12162,20 +12172,53 @@ def edges(self, vertices=None, labels=True, sort=None, key=None, ignore_directio EXAMPLES:: sage: graphs.DodecahedralGraph().edges(sort=True) - [(0, 1, None), (0, 10, None), (0, 19, None), (1, 2, None), (1, 8, None), (2, 3, None), (2, 6, None), (3, 4, None), (3, 19, None), (4, 5, None), (4, 17, None), (5, 6, None), (5, 15, None), (6, 7, None), (7, 8, None), (7, 14, None), (8, 9, None), (9, 10, None), (9, 13, None), (10, 11, None), (11, 12, None), (11, 18, None), (12, 13, None), (12, 16, None), (13, 14, None), (14, 15, None), (15, 16, None), (16, 17, None), (17, 18, None), (18, 19, None)] + [(0, 1, None), (0, 10, None), (0, 19, None), (1, 2, None), + (1, 8, None), (2, 3, None), (2, 6, None), (3, 4, None), + (3, 19, None), (4, 5, None), (4, 17, None), (5, 6, None), + (5, 15, None), (6, 7, None), (7, 8, None), (7, 14, None), + (8, 9, None), (9, 10, None), (9, 13, None), (10, 11, None), + (11, 12, None), (11, 18, None), (12, 13, None), (12, 16, None), + (13, 14, None), (14, 15, None), (15, 16, None), (16, 17, None), + (17, 18, None), (18, 19, None)] :: sage: graphs.DodecahedralGraph().edges(sort=True, labels=False) - [(0, 1), (0, 10), (0, 19), (1, 2), (1, 8), (2, 3), (2, 6), (3, 4), (3, 19), (4, 5), (4, 17), (5, 6), (5, 15), (6, 7), (7, 8), (7, 14), (8, 9), (9, 10), (9, 13), (10, 11), (11, 12), (11, 18), (12, 13), (12, 16), (13, 14), (14, 15), (15, 16), (16, 17), (17, 18), (18, 19)] + [(0, 1), (0, 10), (0, 19), (1, 2), (1, 8), (2, 3), (2, 6), (3, 4), + (3, 19), (4, 5), (4, 17), (5, 6), (5, 15), (6, 7), (7, 8), (7, 14), + (8, 9), (9, 10), (9, 13), (10, 11), (11, 12), (11, 18), (12, 13), + (12, 16), (13, 14), (14, 15), (15, 16), (16, 17), (17, 18), + (18, 19)] :: sage: D = graphs.DodecahedralGraph().to_directed() sage: D.edges(sort=True) - [(0, 1, None), (0, 10, None), (0, 19, None), (1, 0, None), (1, 2, None), (1, 8, None), (2, 1, None), (2, 3, None), (2, 6, None), (3, 2, None), (3, 4, None), (3, 19, None), (4, 3, None), (4, 5, None), (4, 17, None), (5, 4, None), (5, 6, None), (5, 15, None), (6, 2, None), (6, 5, None), (6, 7, None), (7, 6, None), (7, 8, None), (7, 14, None), (8, 1, None), (8, 7, None), (8, 9, None), (9, 8, None), (9, 10, None), (9, 13, None), (10, 0, None), (10, 9, None), (10, 11, None), (11, 10, None), (11, 12, None), (11, 18, None), (12, 11, None), (12, 13, None), (12, 16, None), (13, 9, None), (13, 12, None), (13, 14, None), (14, 7, None), (14, 13, None), (14, 15, None), (15, 5, None), (15, 14, None), (15, 16, None), (16, 12, None), (16, 15, None), (16, 17, None), (17, 4, None), (17, 16, None), (17, 18, None), (18, 11, None), (18, 17, None), (18, 19, None), (19, 0, None), (19, 3, None), (19, 18, None)] + [(0, 1, None), (0, 10, None), (0, 19, None), (1, 0, None), + (1, 2, None), (1, 8, None), (2, 1, None), (2, 3, None), + (2, 6, None), (3, 2, None), (3, 4, None), (3, 19, None), + (4, 3, None), (4, 5, None), (4, 17, None), (5, 4, None), + (5, 6, None), (5, 15, None), (6, 2, None), (6, 5, None), + (6, 7, None), (7, 6, None), (7, 8, None), (7, 14, None), + (8, 1, None), (8, 7, None), (8, 9, None), (9, 8, None), + (9, 10, None), (9, 13, None), (10, 0, None), (10, 9, None), + (10, 11, None), (11, 10, None), (11, 12, None), (11, 18, None), + (12, 11, None), (12, 13, None), (12, 16, None), (13, 9, None), + (13, 12, None), (13, 14, None), (14, 7, None), (14, 13, None), + (14, 15, None), (15, 5, None), (15, 14, None), (15, 16, None), + (16, 12, None), (16, 15, None), (16, 17, None), (17, 4, None), + (17, 16, None), (17, 18, None), (18, 11, None), (18, 17, None), + (18, 19, None), (19, 0, None), (19, 3, None), (19, 18, None)] sage: D.edges(sort=True, labels=False) - [(0, 1), (0, 10), (0, 19), (1, 0), (1, 2), (1, 8), (2, 1), (2, 3), (2, 6), (3, 2), (3, 4), (3, 19), (4, 3), (4, 5), (4, 17), (5, 4), (5, 6), (5, 15), (6, 2), (6, 5), (6, 7), (7, 6), (7, 8), (7, 14), (8, 1), (8, 7), (8, 9), (9, 8), (9, 10), (9, 13), (10, 0), (10, 9), (10, 11), (11, 10), (11, 12), (11, 18), (12, 11), (12, 13), (12, 16), (13, 9), (13, 12), (13, 14), (14, 7), (14, 13), (14, 15), (15, 5), (15, 14), (15, 16), (16, 12), (16, 15), (16, 17), (17, 4), (17, 16), (17, 18), (18, 11), (18, 17), (18, 19), (19, 0), (19, 3), (19, 18)] + [(0, 1), (0, 10), (0, 19), (1, 0), (1, 2), (1, 8), (2, 1), (2, 3), + (2, 6), (3, 2), (3, 4), (3, 19), (4, 3), (4, 5), (4, 17), (5, 4), + (5, 6), (5, 15), (6, 2), (6, 5), (6, 7), (7, 6), (7, 8), (7, 14), + (8, 1), (8, 7), (8, 9), (9, 8), (9, 10), (9, 13), (10, 0), (10, 9), + (10, 11), (11, 10), (11, 12), (11, 18), (12, 11), (12, 13), + (12, 16), (13, 9), (13, 12), (13, 14), (14, 7), (14, 13), (14, 15), + (15, 5), (15, 14), (15, 16), (16, 12), (16, 15), (16, 17), (17, 4), + (17, 16), (17, 18), (18, 11), (18, 17), (18, 19), (19, 0), (19, 3), + (19, 18)] The default is to sort the returned list in the default fashion, as in the above examples. This can be overridden by specifying a key @@ -12263,7 +12306,7 @@ def edges(self, vertices=None, labels=True, sort=None, key=None, ignore_directio vertices = [vertices] return EdgesView(self, vertices=vertices, labels=labels, sort=sort, key=key, - ignore_direction=ignore_direction, sort_vertices=sort_vertices) + ignore_direction=ignore_direction, sort_vertices=sort_vertices) def edge_boundary(self, vertices1, vertices2=None, labels=True, sort=False): r""" @@ -12322,19 +12365,19 @@ def edge_boundary(self, vertices1, vertices2=None, labels=True, sort=False): if vertices2 is not None: vertices2 = set(v for v in vertices2 if v in self) output = [e for e in self.outgoing_edge_iterator(vertices1, labels=labels) - if e[1] in vertices2] + if e[1] in vertices2] else: output = [e for e in self.outgoing_edge_iterator(vertices1, labels=labels) - if e[1] not in vertices1] + if e[1] not in vertices1] else: if vertices2 is not None: vertices2 = set(v for v in vertices2 if v in self) output = [e for e in self.edges(vertices=vertices1, labels=labels, sort=False) - if (e[0] in vertices1 and e[1] in vertices2) or - (e[1] in vertices1 and e[0] in vertices2)] + if (e[0] in vertices1 and e[1] in vertices2) or + (e[1] in vertices1 and e[0] in vertices2)] else: output = [e for e in self.edges(vertices=vertices1, labels=labels, sort=False) - if e[1] not in vertices1 or e[0] not in vertices1] + if e[1] not in vertices1 or e[0] not in vertices1] if sort: output.sort() return output @@ -12509,7 +12552,7 @@ def edge_label(self, u, v): sage: g.edge_label(2, 3) is None True """ - return self._backend.get_edge_label(u,v) + return self._backend.get_edge_label(u, v) def edge_labels(self): """ @@ -12611,7 +12654,7 @@ def remove_loops(self, vertices=None): if self.has_edge(v, v): self.delete_multiedge(v, v) - ### Modifications + # Modifications def clear(self): """ @@ -12646,7 +12689,7 @@ def clear(self): self.name('') self.delete_vertices(self.vertex_iterator()) - ### Degree functions + # Degree functions def degree(self, vertices=None, labels=False): """ @@ -12944,11 +12987,11 @@ def is_regular(self, k=None): return True - ### Substructures + # Substructures def subgraph(self, vertices=None, edges=None, inplace=False, - vertex_property=None, edge_property=None, algorithm=None, - immutable=None): + vertex_property=None, edge_property=None, algorithm=None, + immutable=None): r""" Return the subgraph containing the given vertices and edges. @@ -13227,7 +13270,7 @@ def _subgraph_by_adding(self, vertices=None, edges=None, edge_property=None, imm """ G = self.__class__(weighted=self._weighted, loops=self.allows_loops(), multiedges=self.allows_multiple_edges()) - G.name("Subgraph of (%s)"%self.name()) + G.name("Subgraph of (%s)" % self.name()) if edges is None and edge_property is None: self._backend.subgraph_given_vertices(G._backend, vertices) else: @@ -13254,7 +13297,7 @@ def _subgraph_by_adding(self, vertices=None, edges=None, edge_property=None, imm else: s_vertices = set(vertices) edges_to_keep = [e for e in self.edges(vertices=vertices, sort=False, sort_vertices=False) - if e[0] in s_vertices and e[1] in s_vertices] + if e[0] in s_vertices and e[1] in s_vertices] if edge_property is not None: edges_to_keep = [e for e in edges_to_keep if edge_property(e)] @@ -13409,7 +13452,7 @@ def _subgraph_by_deleting(self, vertices=None, edges=None, inplace=False, if vertices is not None: vertices = set(vertices) G.delete_vertices([v for v in G if v not in vertices]) - G.name("Subgraph of (%s)"%self.name()) + G.name("Subgraph of (%s)" % self.name()) else: G = self._subgraph_by_adding(vertices) @@ -13849,7 +13892,7 @@ def subgraph_search_iterator(self, G, induced=False, return_graphs=True): G._scream_if_not_simple() if self.is_directed() and not G.is_directed(): raise ValueError("cannot search for a graph in a digraph") - if not self.is_directed() and G.is_directed(): + if not self.is_directed() and G.is_directed(): raise ValueError("cannot search for a digraph in a graph") DoG = self.__class__ if not G.order(): @@ -14056,7 +14099,7 @@ def is_chordal(self, certificate=False, algorithm="B"): if algorithm == "A": - peo,t_peo = self.lex_BFS(tree=True) + peo, t_peo = self.lex_BFS(tree=True) peo.reverse() # Iteratively removing vertices and checking everything is fine. @@ -14100,7 +14143,7 @@ def is_chordal(self, certificate=False, algorithm="B"): elif algorithm == "B": - peo,t_peo = self.lex_BFS(reverse=True, tree=True) + peo, t_peo = self.lex_BFS(reverse=True, tree=True) # Remembering the (closed) neighborhoods of each vertex neighbors_subsets = {v: frozenset(self.neighbors(v) + [v]) for v in g} @@ -14145,7 +14188,6 @@ def is_chordal(self, certificate=False, algorithm="B"): else: return False - # Returning values # ---------------- @@ -14161,7 +14203,6 @@ def is_chordal(self, certificate=False, algorithm="B"): return (False, hole) - # 2- The graph is chordal if certificate: return True, peo @@ -14253,9 +14294,9 @@ def is_circulant(self, certificate=False): # The automorphism group, the translation between the vertices of self # and 1..n, and the orbits. ag, orbits = self.automorphism_group([list(self)], - order=False, - return_group=True, - orbits=True) + order=False, + return_group=True, + orbits=True) # Not transitive ? Not a circulant graph ! if len(orbits) != 1: @@ -14268,8 +14309,7 @@ def is_circulant(self, certificate=False): # If the automorphism is not the identity and has exactly one # cycle that contains all vertices. - if ((not cycles) or - len(cycles[0]) != self.order()): + if not cycles or len(cycles[0]) != self.order(): continue # From now on the graph is a circulant graph ! @@ -14370,7 +14410,10 @@ def is_interval(self, certificate=False): Test certificate on a larger graph by re-doing isomorphic graph:: - sage: g = Graph(':S__@_@A_@AB_@AC_@ACD_@ACDE_ACDEF_ACDEFG_ACDEGH_ACDEGHI_ACDEGHIJ_ACDEGIJK_ACDEGIJKL_ACDEGIJKLMaCEGIJKNaCEGIJKNaCGIJKNPaCIP', loops=False, multiedges=False) + sage: s6 = ':S__@_@A_@AB_@AC_@ACD_@ACDE_ACDEF_ACDEFG_ACDEGH_ACDEGHI' + sage: s6 += '_ACDEGHIJ_ACDEGIJK_ACDEGIJKL_ACDEGIJKLMaCEGIJKNaCEGIJK' + sage: s6 += 'NaCGIJKNPaCIP' + sage: g = Graph(s6, loops=False, multiedges=False) sage: d = g.is_interval(certificate=True)[1] sage: g2 = graphs.IntervalGraph(d.values()) sage: g2.is_isomorphic(g) @@ -14633,13 +14676,15 @@ def is_clique(self, vertices=None, directed_clique=False, induced=True, loops=Fa # We check that we have edges between all pairs of vertices v_to_int = {v: i for i, v in enumerate(self)} if G.is_directed() and not directed_clique: - R = lambda u,v: (u, v) if u <= v else (v, u) + def R(u, v): + return (u, v) if u <= v else (v, u) else: - R = lambda u,v:(u,v) + def R(u, v): + return (u, v) if loops: - edges = set(R(v_to_int[u], v_to_int[v]) for u,v in G.edge_iterator(labels=False)) + edges = set(R(v_to_int[u], v_to_int[v]) for u, v in G.edge_iterator(labels=False)) else: - edges = set(R(v_to_int[u], v_to_int[v]) for u,v in G.edge_iterator(labels=False) if u != v) + edges = set(R(v_to_int[u], v_to_int[v]) for u, v in G.edge_iterator(labels=False) if u != v) # If induced == True, we already know that G.size() == M, so # we only need to check that we have the right set of edges. @@ -14872,7 +14917,7 @@ def is_subgraph(self, other, induced=True, up_to_isomorphism=False): else: return self._backend.is_subgraph(other._backend, self) - ### Cluster + # Cluster def cluster_triangles(self, nbunch=None, implementation=None): r""" @@ -14938,7 +14983,7 @@ def cluster_triangles(self, nbunch=None, implementation=None): elif implementation == 'sparse_copy': from sage.graphs.base.static_sparse_graph import triangles_count - elif implementation =="dense_copy": + elif implementation == "dense_copy": from sage.graphs.base.static_dense_graph import triangles_count else: @@ -15131,8 +15176,7 @@ def clustering_coeff(self, raise ValueError("the implementation can only be 'networkx', " "'boost', 'sparse_copy', 'dense_copy' or None") - if ((self.is_directed() or weight) and - implementation != 'networkx'): + if (self.is_directed() or weight) and implementation != 'networkx': raise ValueError("this value of 'implementation' is invalid for directed/weighted graphs") if (implementation in ['sparse_copy', 'dense_copy'] and nodes is not None): @@ -15157,7 +15201,7 @@ def coeff_from_triangle_count(v, count): from sage.graphs.base.static_sparse_graph import triangles_count return {v: coeff_from_triangle_count(v, count) for v, count in triangles_count(self).items()} - elif implementation =="dense_copy": + elif implementation == "dense_copy": from sage.graphs.base.static_dense_graph import triangles_count return {v: coeff_from_triangle_count(v, count) for v, count in triangles_count(self).items()} @@ -15180,7 +15224,7 @@ def cluster_transitivity(self): import networkx return networkx.transitivity(self.networkx_graph()) - ### Distance + # Distance def distance(self, u, v, by_weight=False, weight_function=None, check_weight=True): """ @@ -15301,7 +15345,16 @@ def distance_all_pairs(self, by_weight=False, algorithm=None, sage: g = graphs.PetersenGraph() sage: print(g.distance_all_pairs()) - {0: {0: 0, 1: 1, 2: 2, 3: 2, 4: 1, 5: 1, 6: 2, 7: 2, 8: 2, 9: 2}, 1: {0: 1, 1: 0, 2: 1, 3: 2, 4: 2, 5: 2, 6: 1, 7: 2, 8: 2, 9: 2}, 2: {0: 2, 1: 1, 2: 0, 3: 1, 4: 2, 5: 2, 6: 2, 7: 1, 8: 2, 9: 2}, 3: {0: 2, 1: 2, 2: 1, 3: 0, 4: 1, 5: 2, 6: 2, 7: 2, 8: 1, 9: 2}, 4: {0: 1, 1: 2, 2: 2, 3: 1, 4: 0, 5: 2, 6: 2, 7: 2, 8: 2, 9: 1}, 5: {0: 1, 1: 2, 2: 2, 3: 2, 4: 2, 5: 0, 6: 2, 7: 1, 8: 1, 9: 2}, 6: {0: 2, 1: 1, 2: 2, 3: 2, 4: 2, 5: 2, 6: 0, 7: 2, 8: 1, 9: 1}, 7: {0: 2, 1: 2, 2: 1, 3: 2, 4: 2, 5: 1, 6: 2, 7: 0, 8: 2, 9: 1}, 8: {0: 2, 1: 2, 2: 2, 3: 1, 4: 2, 5: 1, 6: 1, 7: 2, 8: 0, 9: 2}, 9: {0: 2, 1: 2, 2: 2, 3: 2, 4: 1, 5: 2, 6: 1, 7: 1, 8: 2, 9: 0}} + {0: {0: 0, 1: 1, 2: 2, 3: 2, 4: 1, 5: 1, 6: 2, 7: 2, 8: 2, 9: 2}, + 1: {0: 1, 1: 0, 2: 1, 3: 2, 4: 2, 5: 2, 6: 1, 7: 2, 8: 2, 9: 2}, + 2: {0: 2, 1: 1, 2: 0, 3: 1, 4: 2, 5: 2, 6: 2, 7: 1, 8: 2, 9: 2}, + 3: {0: 2, 1: 2, 2: 1, 3: 0, 4: 1, 5: 2, 6: 2, 7: 2, 8: 1, 9: 2}, + 4: {0: 1, 1: 2, 2: 2, 3: 1, 4: 0, 5: 2, 6: 2, 7: 2, 8: 2, 9: 1}, + 5: {0: 1, 1: 2, 2: 2, 3: 2, 4: 2, 5: 0, 6: 2, 7: 1, 8: 1, 9: 2}, + 6: {0: 2, 1: 1, 2: 2, 3: 2, 4: 2, 5: 2, 6: 0, 7: 2, 8: 1, 9: 1}, + 7: {0: 2, 1: 2, 2: 1, 3: 2, 4: 2, 5: 1, 6: 2, 7: 0, 8: 2, 9: 1}, + 8: {0: 2, 1: 2, 2: 2, 3: 1, 4: 2, 5: 1, 6: 1, 7: 2, 8: 0, 9: 2}, + 9: {0: 2, 1: 2, 2: 2, 3: 2, 4: 1, 5: 2, 6: 1, 7: 1, 8: 2, 9: 0}} Testing on Random Graphs:: @@ -15634,7 +15687,7 @@ def _girth_bfs(self, odd=False, certificate=False): else: return best - ### Centrality + # Centrality def centrality_betweenness(self, k=None, normalized=True, weight=None, endpoints=False, seed=None, exact=False, @@ -15719,10 +15772,10 @@ def centrality_betweenness(self, k=None, normalized=True, weight=None, if algorithm == "NetworkX" and exact: raise ValueError("'exact' is not available with the NetworkX implementation") if (algorithm is None and - seed is None and - weight is None and - endpoints is False and - k is None): + seed is None and + weight is None and + endpoints is False and + k is None): algorithm = "Sage" elif algorithm is None: algorithm = "NetworkX" @@ -15741,7 +15794,6 @@ def centrality_betweenness(self, k=None, normalized=True, weight=None, else: raise ValueError("'algorithm' can be \"NetworkX\", \"Sage\" or None") - def centrality_closeness(self, vert=None, by_weight=False, algorithm=None, weight_function=None, check_weight=True): r""" @@ -15832,7 +15884,12 @@ def centrality_closeness(self, vert=None, by_weight=False, algorithm=None, Standard examples:: sage: (graphs.ChvatalGraph()).centrality_closeness() - {0: 0.61111111111111..., 1: 0.61111111111111..., 2: 0.61111111111111..., 3: 0.61111111111111..., 4: 0.61111111111111..., 5: 0.61111111111111..., 6: 0.61111111111111..., 7: 0.61111111111111..., 8: 0.61111111111111..., 9: 0.61111111111111..., 10: 0.61111111111111..., 11: 0.61111111111111...} + {0: 0.61111111111111..., 1: 0.61111111111111..., + 2: 0.61111111111111..., 3: 0.61111111111111..., + 4: 0.61111111111111..., 5: 0.61111111111111..., + 6: 0.61111111111111..., 7: 0.61111111111111..., + 8: 0.61111111111111..., 9: 0.61111111111111..., + 10: 0.61111111111111..., 11: 0.61111111111111...} sage: D = DiGraph({0:[1,2,3], 1:[2], 3:[0,1]}) sage: D.show(figsize=[2,2]) sage: D.centrality_closeness(vert=[0,1]) @@ -16276,7 +16333,12 @@ def shortest_path(self, u, v, by_weight=False, algorithm=None, [] [] [] - """ # TODO- multiple edges?? + + .. TODO:: + + Add options to return a path as a list of edges with or without edge + labels. This can be useful in (di)graphs with multiple edges. + """ if not self.has_vertex(u): raise ValueError("vertex '{}' is not in the (di)graph".format(u)) if not self.has_vertex(v): @@ -16693,7 +16755,12 @@ def shortest_paths(self, u, by_weight=False, algorithm=None, sage: D = graphs.DodecahedralGraph() sage: D.shortest_paths(0) - {0: [0], 1: [0, 1], 2: [0, 1, 2], 3: [0, 19, 3], 4: [0, 19, 3, 4], 5: [0, 1, 2, 6, 5], 6: [0, 1, 2, 6], 7: [0, 1, 8, 7], 8: [0, 1, 8], 9: [0, 10, 9], 10: [0, 10], 11: [0, 10, 11], 12: [0, 10, 11, 12], 13: [0, 10, 9, 13], 14: [0, 1, 8, 7, 14], 15: [0, 19, 18, 17, 16, 15], 16: [0, 19, 18, 17, 16], 17: [0, 19, 18, 17], 18: [0, 19, 18], 19: [0, 19]} + {0: [0], 1: [0, 1], 2: [0, 1, 2], 3: [0, 19, 3], 4: [0, 19, 3, 4], + 5: [0, 1, 2, 6, 5], 6: [0, 1, 2, 6], 7: [0, 1, 8, 7], 8: [0, 1, 8], + 9: [0, 10, 9], 10: [0, 10], 11: [0, 10, 11], 12: [0, 10, 11, 12], + 13: [0, 10, 9, 13], 14: [0, 1, 8, 7, 14], + 15: [0, 19, 18, 17, 16, 15], 16: [0, 19, 18, 17, 16], + 17: [0, 19, 18, 17], 18: [0, 19, 18], 19: [0, 19]} All these paths are obviously induced graphs:: @@ -16703,7 +16770,9 @@ def shortest_paths(self, u, by_weight=False, algorithm=None, :: sage: D.shortest_paths(0, cutoff=2) - {0: [0], 1: [0, 1], 2: [0, 1, 2], 3: [0, 19, 3], 8: [0, 1, 8], 9: [0, 10, 9], 10: [0, 10], 11: [0, 10, 11], 18: [0, 19, 18], 19: [0, 19]} + {0: [0], 1: [0, 1], 2: [0, 1, 2], 3: [0, 19, 3], 8: [0, 1, 8], + 9: [0, 10, 9], 10: [0, 10], 11: [0, 10, 11], 18: [0, 19, 18], + 19: [0, 19]} sage: G = Graph( { 0: {1: 1}, 1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {0: 2} }, sparse=True) sage: G.plot(edge_labels=True).show() # long time sage: G.shortest_paths(0, by_weight=True) @@ -16894,7 +16963,7 @@ def _path_length(self, path, by_weight=False, weight_function=None): weight_function=weight_function, check_weight=False) return sum(weight_function((u, v, self.edge_label(u, v))) - for u, v in zip(path[:-1], path[1:])) + for u, v in zip(path[:-1], path[1:])) else: return len(path) - 1 @@ -17116,17 +17185,33 @@ def shortest_path_all_pairs(self, by_weight=False, algorithm=None, sage: G.plot(edge_labels=True).show() # long time sage: dist, pred = G.shortest_path_all_pairs(by_weight = True) sage: dist - {0: {0: 0, 1: 1, 2: 2, 3: 3, 4: 2}, 1: {0: 1, 1: 0, 2: 1, 3: 2, 4: 3}, 2: {0: 2, 1: 1, 2: 0, 3: 1, 4: 3}, 3: {0: 3, 1: 2, 2: 1, 3: 0, 4: 2}, 4: {0: 2, 1: 3, 2: 3, 3: 2, 4: 0}} + {0: {0: 0, 1: 1, 2: 2, 3: 3, 4: 2}, + 1: {0: 1, 1: 0, 2: 1, 3: 2, 4: 3}, + 2: {0: 2, 1: 1, 2: 0, 3: 1, 4: 3}, + 3: {0: 3, 1: 2, 2: 1, 3: 0, 4: 2}, + 4: {0: 2, 1: 3, 2: 3, 3: 2, 4: 0}} sage: pred - {0: {0: None, 1: 0, 2: 1, 3: 2, 4: 0}, 1: {0: 1, 1: None, 2: 1, 3: 2, 4: 0}, 2: {0: 1, 1: 2, 2: None, 3: 2, 4: 3}, 3: {0: 1, 1: 2, 2: 3, 3: None, 4: 3}, 4: {0: 4, 1: 0, 2: 3, 3: 4, 4: None}} + {0: {0: None, 1: 0, 2: 1, 3: 2, 4: 0}, + 1: {0: 1, 1: None, 2: 1, 3: 2, 4: 0}, + 2: {0: 1, 1: 2, 2: None, 3: 2, 4: 3}, + 3: {0: 1, 1: 2, 2: 3, 3: None, 4: 3}, + 4: {0: 4, 1: 0, 2: 3, 3: 4, 4: None}} sage: pred[0] {0: None, 1: 0, 2: 1, 3: 2, 4: 0} sage: G = Graph( { 0: {1: {'weight':1}}, 1: {2: {'weight':1}}, 2: {3: {'weight':1}}, 3: {4: {'weight':2}}, 4: {0: {'weight':2}} }, sparse=True) sage: dist, pred = G.shortest_path_all_pairs(weight_function = lambda e:e[2]['weight']) sage: dist - {0: {0: 0, 1: 1, 2: 2, 3: 3, 4: 2}, 1: {0: 1, 1: 0, 2: 1, 3: 2, 4: 3}, 2: {0: 2, 1: 1, 2: 0, 3: 1, 4: 3}, 3: {0: 3, 1: 2, 2: 1, 3: 0, 4: 2}, 4: {0: 2, 1: 3, 2: 3, 3: 2, 4: 0}} + {0: {0: 0, 1: 1, 2: 2, 3: 3, 4: 2}, + 1: {0: 1, 1: 0, 2: 1, 3: 2, 4: 3}, + 2: {0: 2, 1: 1, 2: 0, 3: 1, 4: 3}, + 3: {0: 3, 1: 2, 2: 1, 3: 0, 4: 2}, + 4: {0: 2, 1: 3, 2: 3, 3: 2, 4: 0}} sage: pred - {0: {0: None, 1: 0, 2: 1, 3: 2, 4: 0}, 1: {0: 1, 1: None, 2: 1, 3: 2, 4: 0}, 2: {0: 1, 1: 2, 2: None, 3: 2, 4: 3}, 3: {0: 1, 1: 2, 2: 3, 3: None, 4: 3}, 4: {0: 4, 1: 0, 2: 3, 3: 4, 4: None}} + {0: {0: None, 1: 0, 2: 1, 3: 2, 4: 0}, + 1: {0: 1, 1: None, 2: 1, 3: 2, 4: 0}, + 2: {0: 1, 1: 2, 2: None, 3: 2, 4: 3}, + 3: {0: 1, 1: 2, 2: 3, 3: None, 4: 3}, + 4: {0: 4, 1: 0, 2: 3, 3: 4, 4: None}} So for example the shortest weighted path from `0` to `3` is obtained as follows. The predecessor of `3` is ``pred[0][3] == 2``, the predecessor @@ -17344,11 +17429,12 @@ def shortest_path_all_pairs(self, by_weight=False, algorithm=None, return_predecessors=True, unweighted=not by_weight) # and format the result - dist = {int_to_vertex[i]: {int_to_vertex[j]: dd[i, j] for j in range(n) if dd[i, j] != +Infinity} - for i in range(n)} + dist = {int_to_vertex[i]: {int_to_vertex[j]: dd[i, j] + for j in range(n) if dd[i, j] != +Infinity} + for i in range(n)} pred = {int_to_vertex[i]: {int_to_vertex[j]: (int_to_vertex[pp[i, j]] if i != j else None) - for j in range(n) if (i == j or pp[i, j] != -9999)} - for i in range(n)} + for j in range(n) if (i == j or pp[i, j] != -9999)} + for i in range(n)} return dist, pred elif algorithm == "Johnson_Boost": @@ -17367,9 +17453,9 @@ def shortest_path_all_pairs(self, by_weight=False, algorithm=None, dist = dict() pred = dict() for u in self: - paths=self.shortest_paths(u, by_weight=by_weight, - algorithm=algorithm, - weight_function=weight_function) + paths = self.shortest_paths(u, by_weight=by_weight, + algorithm=algorithm, + weight_function=weight_function) dist[u] = {v: self._path_length(p, by_weight=by_weight, weight_function=weight_function) for v, p in paths.items()} @@ -17605,7 +17691,7 @@ def wiener_index(self, by_weight=False, algorithm=None, G = networkx.Graph(list(self.edges(labels=False, sort=False))) G.add_nodes_from(self) total = sum(sum(networkx.single_source_dijkstra_path_length(G, u).values()) - for u in G) + for u in G) WI = total if self.is_directed() else (total / 2) else: @@ -17687,14 +17773,14 @@ def average_distance(self, by_weight=False, algorithm=None, """ if self.order() < 2: raise ValueError("average distance is not defined for empty or one-element graph") - WI = self.wiener_index(by_weight=by_weight, algorithm=algorithm, - weight_function=weight_function, check_weight=check_weight) + WI = self.wiener_index(by_weight=by_weight, algorithm=algorithm, + weight_function=weight_function, check_weight=check_weight) f = 1 if self.is_directed() else 2 if WI in ZZ: return QQ((f * WI, self.order() * (self.order() - 1))) return f * WI / (self.order() * (self.order() - 1)) - ### Searches + # Searches def breadth_first_search(self, start, ignore_direction=False, distance=None, neighbors=None, @@ -18038,7 +18124,7 @@ def depth_first_search(self, start, ignore_direction=False, if x not in seen: queue.append((w, x, d + 1)) - ### Constructors + # Constructors def add_clique(self, vertices, loops=False): """ @@ -18543,7 +18629,9 @@ def cartesian_product(self, other): sage: H = Graph([('a', 'b')]) sage: C1 = G.cartesian_product(H) sage: C1.edges(sort=True, labels=None) - [((0, 'a'), (0, 'b')), ((0, 'a'), (1, 'a')), ((0, 'b'), (1, 'b')), ((1, 'a'), (1, 'b')), ((1, 'a'), (2, 'a')), ((1, 'b'), (2, 'b')), ((2, 'a'), (2, 'b'))] + [((0, 'a'), (0, 'b')), ((0, 'a'), (1, 'a')), ((0, 'b'), (1, 'b')), + ((1, 'a'), (1, 'b')), ((1, 'a'), (2, 'a')), ((1, 'b'), (2, 'b')), + ((2, 'a'), (2, 'b'))] sage: C2 = H.cartesian_product(G) sage: C1.is_isomorphic(C2) True @@ -18562,7 +18650,16 @@ def cartesian_product(self, other): sage: B = digraphs.DeBruijn(['a', 'b'], 2) sage: Q = P.cartesian_product(B) sage: Q.edges(sort=True, labels=None) - [((0, 'aa'), (0, 'aa')), ((0, 'aa'), (0, 'ab')), ((0, 'aa'), (1, 'aa')), ((0, 'ab'), (0, 'ba')), ((0, 'ab'), (0, 'bb')), ((0, 'ab'), (1, 'ab')), ((0, 'ba'), (0, 'aa')), ((0, 'ba'), (0, 'ab')), ((0, 'ba'), (1, 'ba')), ((0, 'bb'), (0, 'ba')), ((0, 'bb'), (0, 'bb')), ((0, 'bb'), (1, 'bb')), ((1, 'aa'), (1, 'aa')), ((1, 'aa'), (1, 'ab')), ((1, 'ab'), (1, 'ba')), ((1, 'ab'), (1, 'bb')), ((1, 'ba'), (1, 'aa')), ((1, 'ba'), (1, 'ab')), ((1, 'bb'), (1, 'ba')), ((1, 'bb'), (1, 'bb'))] + [((0, 'aa'), (0, 'aa')), ((0, 'aa'), (0, 'ab')), + ((0, 'aa'), (1, 'aa')), ((0, 'ab'), (0, 'ba')), + ((0, 'ab'), (0, 'bb')), ((0, 'ab'), (1, 'ab')), + ((0, 'ba'), (0, 'aa')), ((0, 'ba'), (0, 'ab')), + ((0, 'ba'), (1, 'ba')), ((0, 'bb'), (0, 'ba')), + ((0, 'bb'), (0, 'bb')), ((0, 'bb'), (1, 'bb')), + ((1, 'aa'), (1, 'aa')), ((1, 'aa'), (1, 'ab')), + ((1, 'ab'), (1, 'ba')), ((1, 'ab'), (1, 'bb')), + ((1, 'ba'), (1, 'aa')), ((1, 'ba'), (1, 'ab')), + ((1, 'bb'), (1, 'ba')), ((1, 'bb'), (1, 'bb'))] sage: Q.strongly_connected_components_digraph().num_verts() 2 sage: V = Q.strongly_connected_component_containing_vertex((0, 'aa')) @@ -18709,7 +18806,10 @@ def lexicographic_product(self, other): sage: H = Graph([('a', 'b')]) sage: T = G.lexicographic_product(H) sage: T.edges(sort=True, labels=None) - [((0, 'a'), (0, 'b')), ((0, 'a'), (1, 'a')), ((0, 'a'), (1, 'b')), ((0, 'b'), (1, 'a')), ((0, 'b'), (1, 'b')), ((1, 'a'), (1, 'b')), ((1, 'a'), (2, 'a')), ((1, 'a'), (2, 'b')), ((1, 'b'), (2, 'a')), ((1, 'b'), (2, 'b')), ((2, 'a'), (2, 'b'))] + [((0, 'a'), (0, 'b')), ((0, 'a'), (1, 'a')), ((0, 'a'), (1, 'b')), + ((0, 'b'), (1, 'a')), ((0, 'b'), (1, 'b')), ((1, 'a'), (1, 'b')), + ((1, 'a'), (2, 'a')), ((1, 'a'), (2, 'b')), ((1, 'b'), (2, 'a')), + ((1, 'b'), (2, 'b')), ((2, 'a'), (2, 'b'))] sage: T.is_isomorphic(H.lexicographic_product(G)) False @@ -18719,7 +18819,10 @@ def lexicographic_product(self, other): sage: J = DiGraph([('a', 'b')]) sage: T = I.lexicographic_product(J) sage: T.edges(sort=True, labels=None) - [((0, 'a'), (0, 'b')), ((0, 'a'), (1, 'a')), ((0, 'a'), (1, 'b')), ((0, 'b'), (1, 'a')), ((0, 'b'), (1, 'b')), ((1, 'a'), (1, 'b')), ((1, 'a'), (2, 'a')), ((1, 'a'), (2, 'b')), ((1, 'b'), (2, 'a')), ((1, 'b'), (2, 'b')), ((2, 'a'), (2, 'b'))] + [((0, 'a'), (0, 'b')), ((0, 'a'), (1, 'a')), ((0, 'a'), (1, 'b')), + ((0, 'b'), (1, 'a')), ((0, 'b'), (1, 'b')), ((1, 'a'), (1, 'b')), + ((1, 'a'), (2, 'a')), ((1, 'a'), (2, 'b')), ((1, 'b'), (2, 'a')), + ((1, 'b'), (2, 'b')), ((2, 'a'), (2, 'b'))] sage: T.is_isomorphic(J.lexicographic_product(I)) False """ @@ -18861,7 +18964,11 @@ def disjunctive_product(self, other): sage: H = Graph([('a', 'b')]) sage: T = G.disjunctive_product(H) sage: T.edges(sort=True, labels=None) - [((0, 'a'), (0, 'b')), ((0, 'a'), (1, 'a')), ((0, 'a'), (1, 'b')), ((0, 'a'), (2, 'b')), ((0, 'b'), (1, 'a')), ((0, 'b'), (1, 'b')), ((0, 'b'), (2, 'a')), ((1, 'a'), (1, 'b')), ((1, 'a'), (2, 'a')), ((1, 'a'), (2, 'b')), ((1, 'b'), (2, 'a')), ((1, 'b'), (2, 'b')), ((2, 'a'), (2, 'b'))] + [((0, 'a'), (0, 'b')), ((0, 'a'), (1, 'a')), ((0, 'a'), (1, 'b')), + ((0, 'a'), (2, 'b')), ((0, 'b'), (1, 'a')), ((0, 'b'), (1, 'b')), + ((0, 'b'), (2, 'a')), ((1, 'a'), (1, 'b')), ((1, 'a'), (2, 'a')), + ((1, 'a'), (2, 'b')), ((1, 'b'), (2, 'a')), ((1, 'b'), (2, 'b')), + ((2, 'a'), (2, 'b'))] sage: T.is_isomorphic(H.disjunctive_product(G)) True @@ -18871,7 +18978,11 @@ def disjunctive_product(self, other): sage: J = DiGraph([('a', 'b')]) sage: T = I.disjunctive_product(J) sage: T.edges(sort=True, labels=None) - [((0, 'a'), (0, 'b')), ((0, 'a'), (1, 'a')), ((0, 'a'), (1, 'b')), ((0, 'a'), (2, 'b')), ((0, 'b'), (1, 'a')), ((0, 'b'), (1, 'b')), ((1, 'a'), (0, 'b')), ((1, 'a'), (1, 'b')), ((1, 'a'), (2, 'a')), ((1, 'a'), (2, 'b')), ((1, 'b'), (2, 'a')), ((1, 'b'), (2, 'b')), ((2, 'a'), (0, 'b')), ((2, 'a'), (1, 'b')), ((2, 'a'), (2, 'b'))] + [((0, 'a'), (0, 'b')), ((0, 'a'), (1, 'a')), ((0, 'a'), (1, 'b')), + ((0, 'a'), (2, 'b')), ((0, 'b'), (1, 'a')), ((0, 'b'), (1, 'b')), + ((1, 'a'), (0, 'b')), ((1, 'a'), (1, 'b')), ((1, 'a'), (2, 'a')), + ((1, 'a'), (2, 'b')), ((1, 'b'), (2, 'a')), ((1, 'b'), (2, 'b')), + ((2, 'a'), (0, 'b')), ((2, 'a'), (1, 'b')), ((2, 'a'), (2, 'b'))] sage: T.is_isomorphic(J.disjunctive_product(I)) True """ @@ -19047,8 +19158,7 @@ def is_transitively_reduced(self): return self.is_forest() - - ### Visualization + # Visualization def _color_by_label(self, format='hex', as_function=False, default_color="black"): """ @@ -19149,7 +19259,8 @@ def _color_by_label(self, format='hex', as_function=False, default_color="black" color_of_label = dict(zip(labels, colors)) color_of_label = color_of_label.__getitem__ elif isinstance(format, dict): - color_of_label = lambda label: format.get(label, default_color) + def color_of_label(label): + return format.get(label, default_color) else: # This assumes that ``format`` is already a function color_of_label = format @@ -19218,7 +19329,6 @@ def set_latex_options(self, **kwds): opts = self.latex_options() opts.set_options(**kwds) - def layout(self, layout=None, pos=None, dim=2, save_pos=False, **options): """ Return a layout for the vertices of this graph. @@ -19341,10 +19451,10 @@ def layout(self, layout=None, pos=None, dim=2, save_pos=False, **options): if pos is None: layout = 'default' - if hasattr(self, "layout_%s"%layout): - pos = getattr(self, "layout_%s"%layout)(dim=dim, **options) + if hasattr(self, "layout_%s" % layout): + pos = getattr(self, "layout_%s" % layout)(dim=dim, **options) elif layout is not None: - raise ValueError("unknown layout algorithm: %s"%layout) + raise ValueError("unknown layout algorithm: %s" % layout) if len(pos) < self.order(): pos = self.layout_extend_randomly(pos, dim=dim) @@ -19353,7 +19463,6 @@ def layout(self, layout=None, pos=None, dim=2, save_pos=False, **options): self.set_pos(pos, dim=dim) return pos - def layout_spring(self, by_component=True, **options): """ Return a spring layout for this graph. @@ -19490,11 +19599,11 @@ def layout_extend_randomly(self, pos, dim=2): sage: (xmin, ymin) == (0, 0) and (xmax, ymax) == (1, 1) True """ - assert dim == 2 # 3d not yet implemented + assert dim == 2 # 3d not yet implemented from sage.misc.randstate import current_randstate random = current_randstate().python_random().random - xmin, xmax,ymin, ymax = self._layout_bounding_box(pos) + xmin, xmax, ymin, ymax = self._layout_bounding_box(pos) dx = xmax - xmin dy = ymax - ymin @@ -19505,7 +19614,6 @@ def layout_extend_randomly(self, pos, dim=2): pos[v] = [xmin + dx * random(), ymin + dy * random()] return pos - def layout_circular(self, dim=2, center=(0, 0), radius=1, shift=0, angle=0, **options): r""" Return a circular layout for this graph @@ -19947,9 +20055,9 @@ def _layout_bounding_box(self, pos): ys = [pos[v][1] for v in pos] if not xs: xmin = -1 - xmax = 1 + xmax = 1 ymin = -1 - ymax = 1 + ymax = 1 else: xmin = min(xs) xmax = max(xs) @@ -20047,7 +20155,7 @@ def _circle_embedding(self, vertices, center=(0, 0), radius=1, shift=0, angle=0, pos = self._pos = {} from math import sin, cos, pi - for i,v in enumerate(vertices): + for i, v in enumerate(vertices): i += shift # We round cos and sin to avoid results like 1.2246467991473532e-16 # when asking for sin(pi) @@ -20365,7 +20473,11 @@ def plot(self, **options): :: - sage: D = DiGraph( { 0: [1, 10, 19], 1: [8, 2], 2: [3, 6], 3: [19, 4], 4: [17, 5], 5: [6, 15], 6: [7], 7: [8, 14], 8: [9], 9: [10, 13], 10: [11], 11: [12, 18], 12: [16, 13], 13: [14], 14: [15], 15: [16], 16: [17], 17: [18], 18: [19], 19: []} , sparse=True) + sage: D = DiGraph({0: [1, 10, 19], 1: [8, 2], 2: [3, 6], 3: [19, 4], + ....: 4: [17, 5], 5: [6, 15], 6: [7], 7: [8, 14], + ....: 8: [9], 9: [10, 13], 10: [11], 11: [12, 18], + ....: 12: [16, 13], 13: [14], 14: [15], 15: [16], + ....: 16: [17], 17: [18], 18: [19]}, sparse=True) sage: for u,v,l in D.edges(sort=False): ....: D.set_edge_label(u, v, '(' + str(u) + ',' + str(v) + ')') sage: D.plot(edge_labels=True, layout='circular').show() @@ -20559,7 +20671,7 @@ def show(self, method="matplotlib", **kwds): return from sage.misc.viewer import browser import os - os.system('%s %s 2>/dev/null 1>/dev/null &'% (browser(), filename)) + os.system('%s %s 2>/dev/null 1>/dev/null &' % (browser(), filename)) return from .graph_plot import graphplot_options diff --git a/src/sage/graphs/generic_graph_pyx.pyx b/src/sage/graphs/generic_graph_pyx.pyx index a2f98dd755c..022c02c3b06 100644 --- a/src/sage/graphs/generic_graph_pyx.pyx +++ b/src/sage/graphs/generic_graph_pyx.pyx @@ -120,30 +120,6 @@ def layout_split(layout_function, G, **options): return pos -def spring_layout_fast_split(G, **options): - """ - Graph each component of ``G`` separately, placing them adjacent to each - other. - - In ticket :trac:`29522` the function was modified so that it can - work with any layout method and renamed ``layout_split``. - Please use :func:`layout_split` from now on. - - TESTS:: - - sage: from sage.graphs.generic_graph_pyx import spring_layout_fast_split - sage: G = Graph(4) - sage: _ = spring_layout_fast_split(G) - doctest:...: DeprecationWarning: spring_layout_fast_split is deprecated, please use layout_split instead - See https://trac.sagemath.org/29522 for details. - - """ - from sage.misc.superseded import deprecation - deprecation(29522, ('spring_layout_fast_split is deprecated, please use ' - 'layout_split instead'), stacklevel=3) - return layout_split(spring_layout_fast, G, **options) - - def spring_layout_fast(G, iterations=50, int dim=2, vpos=None, bint rescale=True, bint height=False, by_component=False, **options): """ diff --git a/src/sage/graphs/graph_input.py b/src/sage/graphs/graph_input.py index 9b571953fed..34df7dd3c76 100644 --- a/src/sage/graphs/graph_input.py +++ b/src/sage/graphs/graph_input.py @@ -1,5 +1,5 @@ r""" -Functions for reading/building graphs/digraphs. +Functions for reading/building graphs/digraphs This module gathers functions needed to build a graph from any other data. diff --git a/src/sage/graphs/graph_plot.py b/src/sage/graphs/graph_plot.py index 16fd32608d8..5d646060add 100644 --- a/src/sage/graphs/graph_plot.py +++ b/src/sage/graphs/graph_plot.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- r""" -Graph Plotting +Graph plotting *(For LaTeX drawings of graphs, see the* :mod:`~sage.graphs.graph_latex` *module.)* diff --git a/src/sage/graphs/matchpoly.pyx b/src/sage/graphs/matchpoly.pyx index 444d99ebee3..4a8c24be8ca 100644 --- a/src/sage/graphs/matchpoly.pyx +++ b/src/sage/graphs/matchpoly.pyx @@ -1,6 +1,6 @@ # cython: binding=True """ -Matching Polynomial +Matching polynomial This module contains the following methods: diff --git a/src/sage/graphs/path_enumeration.pyx b/src/sage/graphs/path_enumeration.pyx index 2e3122b6134..0ef63dd0c17 100644 --- a/src/sage/graphs/path_enumeration.pyx +++ b/src/sage/graphs/path_enumeration.pyx @@ -2,7 +2,7 @@ # cython: binding=True # distutils: language = c++ r""" -Path Enumeration +Path enumeration This module is meant for all functions related to path enumeration in graphs. diff --git a/src/sage/graphs/schnyder.py b/src/sage/graphs/schnyder.py index a29bce8dd3c..5816d1c7952 100644 --- a/src/sage/graphs/schnyder.py +++ b/src/sage/graphs/schnyder.py @@ -1,5 +1,5 @@ """ -Schnyder's Algorithm for straight-line planar embeddings +Schnyder's algorithm for straight-line planar embeddings A module for computing the (x,y) coordinates for a straight-line planar embedding of any connected planar graph with at least three vertices. Uses diff --git a/src/sage/graphs/strongly_regular_db.pyx b/src/sage/graphs/strongly_regular_db.pyx index 027011ac677..1e01738662d 100644 --- a/src/sage/graphs/strongly_regular_db.pyx +++ b/src/sage/graphs/strongly_regular_db.pyx @@ -47,6 +47,7 @@ from libc.stdint cimport uint_fast32_t cdef dict _brouwer_database = None _small_srg_database = None + @cached_function def is_paley(int v, int k, int l, int mu): r""" @@ -72,12 +73,13 @@ def is_paley(int v, int k, int l, int mu): (13, 6, 2, 3) sage: t = is_paley(5,5,5,5); t """ - if (v%4 == 1 and is_prime_power(v) and - k == (v-1)//2 and - l == (v-5)//4 and - mu == (v-1)//4): + if (v % 4 == 1 and is_prime_power(v) and + k == (v - 1)//2 and + l == (v - 5)//4 and + mu == (v - 1)//4): from sage.graphs.generators.families import PaleyGraph - return (PaleyGraph,v) + return (PaleyGraph, v) + @cached_function def is_mathon_PC_srg(int v, int k, int l, int mu): @@ -119,21 +121,21 @@ def is_mathon_PC_srg(int v, int k, int l, int mu): sage: t = is_mathon_PC_srg(4*mu+1,2*mu,mu-1,mu); t """ cdef int t - if (v%4 == 1 and - k == (v-1)//2 and - l == (v-5)//4 and - mu == (v-1)//4): + if (v % 4 == 1 and + k == (v - 1)//2 and + l == (v - 5)//4 and + mu == (v - 1)//4): from sage.rings.integer_ring import ZZ K = ZZ['x'] x = K.gen() - rpoly = (w for w in (x*(4*x*(4*x-1)-1) - mu).roots() if w[0] > 0) + rpoly = (w for w in (x*(4*x*(4*x - 1) - 1) - mu).roots() if w[0] > 0) try: t = next(rpoly)[0] - if (is_prime_power(4*t-1) and - is_prime_power(4*t+1)): # extra assumption in TODO! + if (is_prime_power(4*t - 1) and + is_prime_power(4*t + 1)): # extra assumption in TODO! from sage.graphs.generators.families import \ MathonPseudocyclicStronglyRegularGraph - return (MathonPseudocyclicStronglyRegularGraph,t) + return (MathonPseudocyclicStronglyRegularGraph, t) except StopIteration: pass @@ -167,16 +169,19 @@ def is_muzychuk_S6(int v, int k, int l, int mu): """ cdef int n, d from sage.rings.integer_ring import ZZ - n_list = [n for n in range(l-1) if ZZ(n).is_prime_power()] + n_list = [n for n in range(l - 1) if ZZ(n).is_prime_power()] for n in n_list: d = 2 - while n**d * ((n**d-1)//(n-1)+1) <= v: - if v == n**d * ((n**d-1)//(n-1)+1) and k == n**(d-1)*(n**d-1)//(n-1) - 1\ - and l == mu - 2 and mu == n**(d-1) * (n**(d-1)-1) // (n-1): + while n**d * ((n**d - 1)//(n - 1) + 1) <= v: + if (v == n**d * ((n**d - 1)//(n - 1) + 1) and + k == n**(d - 1)*(n**d - 1)//(n - 1) - 1 and + l == mu - 2 and + mu == n**(d - 1) * (n**(d - 1) - 1)//(n - 1)): from sage.graphs.generators.families import MuzychukS6Graph return (MuzychukS6Graph, n, d) d += 1 + @cached_function def is_orthogonal_array_block_graph(int v, int k, int l, int mu): r""" @@ -232,19 +237,20 @@ def is_orthogonal_array_block_graph(int v, int k, int l, int mu): m, n = latin_squares_graph_parameters(v, k, l, mu) except Exception: return - if orthogonal_array(m,n,existence=True) is True: + if orthogonal_array(m, n, existence=True) is True: from sage.graphs.generators.intersection import OrthogonalArrayBlockGraph - return (lambda m,n : OrthogonalArrayBlockGraph(m, n), m,n) + return (lambda m, n: OrthogonalArrayBlockGraph(m, n), m, n) - elif n>2 and skew_hadamard_matrix(n+1, existence=True) is True: - if m==(n+1)/2: + elif n > 2 and skew_hadamard_matrix(n+1, existence=True) is True: + if m == (n + 1)/2: from sage.graphs.generators.families import SquaredSkewHadamardMatrixGraph as G - elif m==(n-1)//2: + elif m == (n - 1)//2: from sage.graphs.generators.families import PasechnikGraph as G else: return return (G, (n+1)//4) + @cached_function def is_johnson(int v, int k, int l, int mu): r""" @@ -276,10 +282,11 @@ def is_johnson(int v, int k, int l, int mu): # J(n,m) has parameters v = m(m – 1)/2, k = 2(m – 2), λ = m – 2, μ = 4. m = l + 2 if (mu == 4 and - k == 2*(m-2) and - v == m*(m-1)//2): + k == 2*(m - 2) and + v == m*(m - 1)//2): from sage.graphs.generators.families import JohnsonGraph - return (lambda m: JohnsonGraph(m,2), m) + return (lambda m: JohnsonGraph(m, 2), m) + @cached_function def is_steiner(int v, int k, int l, int mu): @@ -317,15 +324,16 @@ def is_steiner(int v, int k, int l, int mu): if mu <= 1 or not is_square(mu): return m = int(sqrt(mu)) - n = (k*(m-1))//m+m + n = (k*(m - 1))//m + m - if (v == (n*(n-1))/(m*(m-1)) and - k == m*(n-m)/(m-1) and - l == (m-1)**2 + (n-1)/(m-1)-2 and - balanced_incomplete_block_design(n,m,existence=True) is True): + if (v == (n*(n - 1))/(m*(m - 1)) and + k == m*(n - m)/(m - 1) and + l == (m - 1)**2 + (n - 1)/(m - 1) - 2 and + balanced_incomplete_block_design(n, m, existence=True) is True): from sage.graphs.generators.intersection import IntersectionGraph return (lambda n, m: IntersectionGraph([frozenset(b) for b in balanced_incomplete_block_design(n, m)]), n, m) + @cached_function def is_affine_polar(int v, int k, int l, int mu): r""" @@ -361,25 +369,25 @@ def is_affine_polar(int v, int k, int l, int mu): # # VO−(2e,q) has parameters v = q^(2e), k = (q^(e−1) - 1)(q^e + 1), λ = # q(q^(e−2) - 1)(q^(e−1) + 1) + q − 2, μ = q^(e−1)(q^(e−1) - 1) - if (not is_square(v) or - not is_prime_power(v)): + if not is_square(v) or not is_prime_power(v): return - prime,power = is_prime_power(v,get_data=True) - if power%2: + prime, power = is_prime_power(v, get_data=True) + if power % 2: return for e in divisors(power/2): q = prime**(power//(2*e)) assert v == q**(2*e) - if (k == (q**(e-1) + 1)*(q**e-1) and - l == q*(q**(e-2) + 1)*(q**(e-1)-1)+q-2 and - mu== q**(e-1)*(q**(e-1) + 1)): + if (k == (q**(e - 1) + 1)*(q**e - 1) and + l == q*(q**(e - 2) + 1)*(q**(e - 1) - 1) + q - 2 and + mu == q**(e - 1)*(q**(e - 1) + 1)): from sage.graphs.generators.classical_geometries import AffineOrthogonalPolarGraph - return (lambda d,q : AffineOrthogonalPolarGraph(d,q,sign='+'),2*e,q) - if (k == (q**(e-1) - 1)*(q**e+1) and - l == q*(q**(e-2)- 1)*(q**(e-1)+1)+q-2 and - mu== q**(e-1)*(q**(e-1) - 1)): + return (lambda d, q: AffineOrthogonalPolarGraph(d, q, sign='+'), 2*e, q) + if (k == (q**(e - 1) - 1)*(q**e + 1) and + l == q*(q**(e - 2) - 1)*(q**(e - 1) + 1) + q - 2 and + mu == q**(e - 1)*(q**(e - 1) - 1)): from sage.graphs.generators.classical_geometries import AffineOrthogonalPolarGraph - return (lambda d,q : AffineOrthogonalPolarGraph(d,q,sign='-'),2*e,q) + return (lambda d, q: AffineOrthogonalPolarGraph(d, q, sign='-'), 2*e, q) + @cached_function def is_orthogonal_polar(int v, int k, int l, int mu): @@ -427,35 +435,36 @@ def is_orthogonal_polar(int v, int k, int l, int mu): q_pow_m_minus_one = -s-1 if abs(s) > r else r+1 if is_prime_power(q_pow_m_minus_one): - prime,power = is_prime_power(q_pow_m_minus_one,get_data=True) + prime, power = is_prime_power(q_pow_m_minus_one, get_data=True) for d in divisors(power): q = prime**d - m = (power//d)+1 + m = (power//d) + 1 # O(2m+1,q) - if (v == (q**(2*m)-1)//(q-1) and - k == q*(q**(2*m-2)-1)//(q-1) and - l == q**2*(q**(2*m-4)-1)//(q-1) + q-1 and - mu== (q**(2*m-2)-1)//(q-1)): + if (v == (q**(2*m) - 1)//(q - 1) and + k == q*(q**(2*m - 2) - 1)//(q - 1) and + l == q**2*(q**(2*m - 4) - 1)//(q - 1) + q - 1 and + mu == (q**(2*m - 2) - 1)//(q - 1)): from sage.graphs.generators.classical_geometries import OrthogonalPolarGraph return (OrthogonalPolarGraph, 2*m+1, q, "") # O^+(2m,q) - if (v == (q**(2*m-1)-1)//(q-1) + q**(m-1) and - k == q*(q**(2*m-3)-1)//(q-1) + q**(m-1) and - k == q**(2*m-3) + l + 1 and - mu== k//q): + if (v == (q**(2*m - 1) - 1)//(q - 1) + q**(m - 1) and + k == q*(q**(2*m - 3) - 1)//(q - 1) + q**(m - 1) and + k == q**(2*m - 3) + l + 1 and + mu == k//q): from sage.graphs.generators.classical_geometries import OrthogonalPolarGraph return (OrthogonalPolarGraph, 2*m, q, "+") # O^+(2m+1,q) - if (v == (q**(2*m-1)-1)//(q-1) - q**(m-1) and - k == q*(q**(2*m-3)-1)//(q-1) - q**(m-1) and - k == q**(2*m-3) + l + 1 and - mu== k//q): + if (v == (q**(2*m - 1) - 1)//(q - 1) - q**(m - 1) and + k == q*(q**(2*m - 3) - 1)//(q - 1) - q**(m - 1) and + k == q**(2*m - 3) + l + 1 and + mu == k//q): from sage.graphs.generators.classical_geometries import OrthogonalPolarGraph return (OrthogonalPolarGraph, 2*m, q, "-") + @cached_function def is_goethals_seidel(int v, int k, int l, int mu): r""" @@ -520,18 +529,19 @@ def is_goethals_seidel(int v, int k, int l, int mu): # - the number of vertices v is equal to v_bibd*(r_bibd+1) # - the degree k of the graph is equal to k=(v+r_bibd-1)/2 - r_bibd = k - (v-1-k) - v_bibd = v//(r_bibd+1) - k_bibd = (v_bibd-1)//r_bibd + 1 if r_bibd>0 else -1 + r_bibd = k - (v - 1 - k) + v_bibd = v//(r_bibd + 1) + k_bibd = (v_bibd - 1)//r_bibd + 1 if r_bibd > 0 else -1 - if (v == v_bibd*(r_bibd+1) and - 2*k == v+r_bibd-1 and - 4*l == -2*v + 6*k -v_bibd -k_bibd and - hadamard_matrix(r_bibd+1, existence=True) is True and - balanced_incomplete_block_design(v_bibd, k_bibd, existence = True) is True): + if (v == v_bibd*(r_bibd + 1) and + 2*k == v + r_bibd - 1 and + 4*l == -2*v + 6*k - v_bibd - k_bibd and + hadamard_matrix(r_bibd + 1, existence=True) is True and + balanced_incomplete_block_design(v_bibd, k_bibd, existence=True) is True): 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 +597,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 +655,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 +819,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 +877,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 +921,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 +969,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 +1022,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 +1036,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 +1044,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 +1081,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 +1100,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 +1159,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 +1211,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 +1243,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 +1291,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,22 +1299,22 @@ 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) @@ -1641,7 +1659,7 @@ def is_switch_OA_srg(int v, int k, int l, int mu): cdef int n_2_p_1 = v 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 diff --git a/src/sage/graphs/traversals.pyx b/src/sage/graphs/traversals.pyx index 14374831be8..a16ac6d701e 100644 --- a/src/sage/graphs/traversals.pyx +++ b/src/sage/graphs/traversals.pyx @@ -2,7 +2,7 @@ # cython: binding=True # distutils: language = c++ r""" -Graph traversals. +Graph traversals **This module implements the following graph traversals** diff --git a/src/sage/groups/abelian_gps/dual_abelian_group.py b/src/sage/groups/abelian_gps/dual_abelian_group.py index 46e151e7fd2..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". 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 6abc64d782b..4866cdc4f85 100644 --- a/src/sage/groups/cubic_braid.py +++ b/src/sage/groups/cubic_braid.py @@ -218,12 +218,10 @@ 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) - ############################################################################## # # Class CubicBraidElement (for elements) @@ -354,9 +352,9 @@ def braid(self): 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. @@ -963,6 +961,7 @@ def _test_matrix_group(self, **options): - Construction of matrix group was faithful. - Coercion maps to and from matrix group exist and are inverse to each other. + EXAMPLES:: sage: CBG2 = CubicBraidGroup(2) @@ -1993,9 +1992,9 @@ def is_finite(self): """ 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): r""" Return a cubic braid group as subgroup of ``self`` on the first @@ -2029,19 +2028,17 @@ def cubic_braid_subgroup(self, nstrands=None): True """ if nstrands is None: - nstrands = self.strands() -1 + nstrands = self.strands() - 1 n = self.strands() nstrands = Integer(nstrands) if nstrands >= n or nstrands <= 0: - raise ValueError("nstrands must be positive and less than %s" %(self.strands())) - + raise ValueError("nstrands must be positive and less than %s" % (self.strands())) names = self.variable_names() - names_red = names[:nstrands-1] + 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/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/misc_gps/argument_groups.py b/src/sage/groups/misc_gps/argument_groups.py index a02305df78e..9fa0fe07974 100644 --- a/src/sage/groups/misc_gps/argument_groups.py +++ b/src/sage/groups/misc_gps/argument_groups.py @@ -31,15 +31,15 @@ Classes and Methods =================== """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2018 Daniel Krenn # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from sage.structure.element import MultiplicativeGroupElement from sage.structure.factory import UniqueFactory @@ -48,6 +48,7 @@ from sage.structure.unique_representation import UniqueRepresentation import sage.rings.abc + class AbstractArgument(MultiplicativeGroupElement): r""" An element of :class:`AbstractArgumentGroup`. This abstract class @@ -488,7 +489,7 @@ def _symbolic_(self, R=None): if R is None: R = SR - return exp(2*R('pi')*R('I') * self.exponent) + return exp(2 * R('pi') * R('I') * self.exponent) def _mul_(self, other): r""" @@ -609,7 +610,7 @@ def is_minus_one(self): False """ from sage.rings.rational_field import QQ - return self.exponent == QQ(1)/QQ(2) + return self.exponent == QQ((1, 2)) class UnitCircleGroup(AbstractArgumentGroup): @@ -750,7 +751,7 @@ def _element_constructor_(self, data, exponent=None, **kwds): exponent = 0 elif data == -1 or data == '-1': - exponent = QQ(1)/QQ(2) + exponent = QQ((1, 2)) else: try: @@ -762,7 +763,7 @@ def _element_constructor_(self, data, exponent=None, **kwds): if data.is_one(): exponent = 0 elif data.is_minus_one(): - exponent = QQ(1)/QQ(2) + exponent = QQ((1, 2)) elif isinstance(P, UnitCircleGroup): exponent = data.exponent @@ -961,11 +962,11 @@ def _repr_(self): from sage.rings.rational_field import QQ if self.exponent == 0: return '1' - if self.exponent == QQ(1)/QQ(2): + if self.exponent == QQ((1, 2)): return '-1' - if self.exponent == QQ(1)/QQ(4): + if self.exponent == QQ((1, 4)): return 'I' - if self.exponent == QQ(3)/QQ(4): + if self.exponent == QQ((3, 4)): return '-I' num = self.exponent_numerator() den = self.exponent_denominator() @@ -1008,8 +1009,7 @@ def __classcall__(cls, category=None): Category of commutative groups """ category = cls._determine_category_(category) - return super(AbstractArgumentGroup, cls).__classcall__( - cls, category) + return super(AbstractArgumentGroup, cls).__classcall__(cls, category) def __init__(self, category): r""" @@ -1143,7 +1143,7 @@ def _symbolic_(self, R=None): if R is None: R = SR - return exp(R('I')*arg(self._element_)) + return exp(R('I') * arg(self._element_)) def _mul_(self, other): r""" @@ -1624,9 +1624,9 @@ def __classcall__(cls, category=None): sage: from sage.groups.misc_gps.argument_groups import SignGroup sage: S = SignGroup() sage: S.category() # indirect doctest - Category of commutative groups + Category of finite commutative groups """ - category = cls._determine_category_(category) + category = cls._determine_category_(category).Finite() return super(AbstractArgumentGroup, cls).__classcall__( cls, category) 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/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 71e6c1fc94d..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. diff --git a/src/sage/groups/perm_gps/permgroup.py b/src/sage/groups/perm_gps/permgroup.py index 3a0c780d677..7723ec25260 100644 --- a/src/sage/groups/perm_gps/permgroup.py +++ b/src/sage/groups/perm_gps/permgroup.py @@ -135,7 +135,7 @@ # Distributed under the terms of the GNU General Public License (GPL) # https://www.gnu.org/licenses/ # **************************************************************************** - +from __future__ import annotations from functools import wraps from sage.misc.randstate import current_randstate @@ -492,7 +492,7 @@ def __init__(self, gens=None, gap_group=None, canonicalize=True, if isinstance(gap_group, LibGapElement): self._libgap = gap_group - #Handle the case where only the GAP group is specified. + # Handle the case where only the GAP group is specified. if gens is None: gens = [gen for gen in gap_group.GeneratorsOfGroup()] @@ -509,9 +509,9 @@ def __init__(self, gens=None, gap_group=None, canonicalize=True, # Fallback (not ideal: find a better solution?) domain = sorted(domain, key=str) - #Here we need to check if all of the points are integers - #to make the domain contain all integers up to the max. - #This is needed for backward compatibility + # Here we need to check if all of the points are integers + # to make the domain contain all integers up to the max. + # This is needed for backward compatibility if all(isinstance(p, (int, Integer)) for p in domain): domain = list(range(min([1] + domain), max([1] + domain)+1)) @@ -1237,9 +1237,11 @@ def elements(SGS): else: raise ValueError("the input algorithm (='%s') must be 'SGS', 'BFS' or 'DFS'" % algorithm) - def gens(self): + def gens(self) -> tuple: """ - Return tuple of generators of this group. These need not be + Return tuple of generators of this group. + + These need not be minimal, as they are the generators used in defining this group. EXAMPLES:: @@ -1270,14 +1272,14 @@ def gens(self): We make sure that the trivial group gets handled correctly:: sage: SymmetricGroup(1).gens() - [()] + ((),) """ return self._gens - def gens_small(self): """ For this group, returns a generating set which has few elements. + As neither irredundancy nor minimal length is proven, it is fast. EXAMPLES:: @@ -2795,7 +2797,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]): @@ -3173,7 +3175,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. @@ -3222,7 +3224,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 @@ -3258,7 +3260,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 @@ -3306,7 +3308,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 @@ -3358,8 +3360,7 @@ def character_table(self): sage: G = PermutationGroup([[(1,2),(3,4)], [(1,2,3)]]) sage: CT = gap(G).CharacterTable() - Type ``print(gap.eval("Display(%s)"%CT.name()))`` to display this - nicely. + Type ``CT.Display()`` to display this nicely. :: @@ -3374,8 +3375,7 @@ def character_table(self): [ 2 0 0 0 -2] sage: CT = gap(G).CharacterTable() - Again, type ``print(gap.eval("Display(%s)"%CT.name()))`` to display this - nicely. + Again, type ``CT.Display()`` to display this nicely. :: @@ -3630,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. @@ -3670,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. diff --git a/src/sage/groups/perm_gps/permgroup_element.pyx b/src/sage/groups/perm_gps/permgroup_element.pyx index 99cf04054cf..620ca2a71bd 100644 --- a/src/sage/groups/perm_gps/permgroup_element.pyx +++ b/src/sage/groups/perm_gps/permgroup_element.pyx @@ -88,7 +88,7 @@ We create element of a permutation group of large degree:: (1,30)(2,29)(3,28)(4,27)(5,26)(6,25)(7,24)(8,23)(9,22)(10,21)(11,20)(12,19)(13,18)(14,17)(15,16) """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2006 William Stein # Copyright (C) 2006 David Joyner # Copyright (C) 2019 Vincent Delecroix <20100.delecroix@gmail.com> @@ -97,8 +97,8 @@ We create element of a permutation group of large degree:: # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** import copy import random @@ -944,7 +944,7 @@ cdef class PermutationGroupElement(MultiplicativeGroupElement): sage: S = SymmetricGroup(['a', 'b']) sage: latex(S.gens()) - \left[(\text{\texttt{a}},\text{\texttt{b}})\right] + \left((\text{\texttt{a}},\text{\texttt{b}})\right) """ from sage.misc.latex import latex return "".join(("(" + ",".join(latex(x) for x in cycle) + ")") @@ -1849,14 +1849,14 @@ 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: diff --git a/src/sage/groups/perm_gps/permgroup_named.py b/src/sage/groups/perm_gps/permgroup_named.py index d5078f03b40..a3be9f974fe 100644 --- a/src/sage/groups/perm_gps/permgroup_named.py +++ b/src/sage/groups/perm_gps/permgroup_named.py @@ -272,8 +272,8 @@ def __init__(self, domain=None): gens = [tuple(self._domain)] if len(self._domain) > 2: gens.append(tuple(self._domain[:2])) - self._gens = [self.element_class(g, self, check=False) - for g in gens] + self._gens = tuple([self.element_class(g, self, check=False) + for g in gens]) def _gap_init_(self, gap=None): """ diff --git a/src/sage/groups/raag.py b/src/sage/groups/raag.py index 59b78c6d757..e8d626a32a4 100644 --- a/src/sage/groups/raag.py +++ b/src/sage/groups/raag.py @@ -857,4 +857,3 @@ class Element(CohomologyRAAGElement): """ An element in the cohomology ring of a right-angled Artin group. """ - diff --git a/src/sage/groups/semimonomial_transformations/semimonomial_transformation_group.py b/src/sage/groups/semimonomial_transformations/semimonomial_transformation_group.py index 6119a36d3ed..78c485bfc02 100644 --- a/src/sage/groups/semimonomial_transformations/semimonomial_transformation_group.py +++ b/src/sage/groups/semimonomial_transformations/semimonomial_transformation_group.py @@ -51,6 +51,7 @@ sage: TestSuite(S).run() sage: TestSuite(S.an_element()).run() """ +from __future__ import annotations from sage.rings.integer import Integer from sage.groups.group import FiniteGroup @@ -285,7 +286,7 @@ def __contains__(self, item) -> bool: return False return True - def gens(self): + def gens(self) -> tuple: r""" Return a tuple of generators of ``self``. @@ -293,11 +294,11 @@ def gens(self): sage: F. = GF(4) sage: SemimonomialTransformationGroup(F, 3).gens() - [((a, 1, 1); (), Ring endomorphism of Finite Field in a of size 2^2 + (((a, 1, 1); (), Ring endomorphism of Finite Field in a of size 2^2 Defn: a |--> a), ((1, 1, 1); (1,2,3), Ring endomorphism of Finite Field in a of size 2^2 Defn: a |--> a), ((1, 1, 1); (1,2), Ring endomorphism of Finite Field in a of size 2^2 Defn: a |--> a), ((1, 1, 1); (), Ring endomorphism of Finite Field in a of size 2^2 - Defn: a |--> a + 1)] + Defn: a |--> a + 1)) """ from sage.groups.perm_gps.permgroup_named import SymmetricGroup R = self.base_ring() @@ -306,7 +307,7 @@ def gens(self): l.append(self(perm=Permutation(g))) if R.is_field() and not R.is_prime_field(): l.append(self(autom=R.hom([R.primitive_element()**R.characteristic()]))) - return l + return tuple(l) def order(self) -> Integer: r""" 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 b879ac9c7eb..d88389e5c6d 100644 --- a/src/sage/homology/chain_complex.py +++ b/src/sage/homology/chain_complex.py @@ -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 index 4468585c4f6..424086d283e 100644 --- a/src/sage/homology/free_resolution.py +++ b/src/sage/homology/free_resolution.py @@ -6,7 +6,7 @@ .. MATH:: - 0 \xleftarrow{d_0} R^{n_1} \xleftarrow{d_1} R^{n_1} \xleftarrow{d_2} + 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 @@ -22,16 +22,15 @@ 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 = FreeResolution(I) + sage: r = I.free_resolution() sage: r S^1 <-- S^3 <-- S^2 <-- 0 :: - sage: from sage.homology.graded_resolution import GradedFreeResolution sage: S. = PolynomialRing(QQ) sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) - sage: r = GradedFreeResolution(I) + sage: r = I.graded_free_resolution() sage: r S(0) <-- S(-2)⊕S(-2)⊕S(-2) <-- S(-3)⊕S(-3) <-- 0 @@ -39,7 +38,7 @@ 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 = FreeResolution(I) + sage: r = I.free_resolution() sage: r S^1 <-- S^4 <-- S^4 <-- S^1 <-- 0 sage: len(r) @@ -53,11 +52,12 @@ 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 @@ -69,21 +69,248 @@ 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.matrix.matrix_mpolynomial_dense import Matrix_mpolynomial_dense +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 +from sage.modules.free_module import Module_free_ambient, FreeModule_generic from sage.rings.ideal import Ideal_generic -from sage.structure.sage_object import SageObject +from copy import copy -class FreeResolution_generic(SageObject): +class FreeResolution(SageObject, metaclass=ClasscallMetaclass): r""" - Generic base class of finite free resolutions. + A free resolution. - A subclass must provide a ``_maps`` attribute that contains a list of the - maps defining the 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 @@ -94,6 +321,9 @@ class FreeResolution_generic(SageObject): 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. @@ -115,25 +345,6 @@ class FreeResolution_generic(SageObject): [ y*z - x*w] [-y^2 + x*z] """ - def __init__(self, base_ring, name='F'): - """ - Initialize. - - INPUT: - - - ``base_ring`` -- a ring - - 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']) - """ - self._base_ring = base_ring - self._name = name - @lazy_attribute def _length(self): """ @@ -160,10 +371,9 @@ def _repr_(self): EXAMPLES:: - sage: from sage.homology.graded_resolution import GradedFreeResolution sage: S. = PolynomialRing(QQ) sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) - sage: r = GradedFreeResolution(I) + sage: r = I.graded_free_resolution() sage: r S(0) <-- S(-2)⊕S(-2)⊕S(-2) <-- S(-3)⊕S(-3) <-- 0 """ @@ -173,39 +383,6 @@ def _repr_(self): s += ' <-- 0' return s - 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 - def __len__(self): r""" Return the length of this resolution. @@ -214,10 +391,9 @@ def __len__(self): EXAMPLES:: - sage: from sage.homology.graded_resolution import GradedFreeResolution sage: S. = PolynomialRing(QQ) sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) - sage: r = GradedFreeResolution(I) + sage: r = I.graded_free_resolution() sage: r S(0) <-- S(-2)⊕S(-2)⊕S(-2) <-- S(-3)⊕S(-3) <-- 0 sage: len(r) @@ -235,10 +411,9 @@ def __getitem__(self, i): EXAMPLES:: - sage: from sage.homology.graded_resolution import GradedFreeResolution sage: S. = PolynomialRing(QQ) sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) - sage: r = GradedFreeResolution(I) + sage: r = I.graded_free_resolution() sage: r S(0) <-- S(-2)⊕S(-2)⊕S(-2) <-- S(-3)⊕S(-3) <-- 0 sage: r.target() @@ -248,7 +423,7 @@ def __getitem__(self, i): [-z^2 + y*w] [ y*z - x*w] [-y^2 + x*z] - """ + """ if i < 0: raise IndexError('invalid index') elif i > self._length: @@ -261,7 +436,7 @@ def __getitem__(self, i): def differential(self, i): r""" - Return the matrix representing the ``i``-th differential map. + Return the ``i``-th differential map. INPUT: @@ -269,10 +444,9 @@ def differential(self, i): EXAMPLES:: - sage: from sage.homology.graded_resolution import GradedFreeResolution sage: S. = PolynomialRing(QQ) sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) - sage: r = GradedFreeResolution(I) + 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) @@ -330,28 +504,6 @@ def differential(self, i): m = s.hom(self._maps[i - 1], t, side='right') return m - def target(self): - r""" - Return the codomain of the ``0``-th differential map. - - EXAMPLES:: - - sage: from sage.homology.graded_resolution import GradedFreeResolution - sage: S. = PolynomialRing(QQ) - sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) - sage: r = GradedFreeResolution(I) - 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() - def matrix(self, i): r""" Return the matrix representing the ``i``-th differential map. @@ -362,10 +514,9 @@ def matrix(self, i): EXAMPLES:: - sage: from sage.homology.graded_resolution import GradedFreeResolution sage: S. = PolynomialRing(QQ) sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) - sage: r = GradedFreeResolution(I) + 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) @@ -392,10 +543,9 @@ def chain_complex(self): EXAMPLES:: - sage: from sage.homology.graded_resolution import GradedFreeResolution sage: S. = PolynomialRing(QQ) sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) - sage: r = GradedFreeResolution(I) + sage: r = I.graded_free_resolution() sage: unicode_art(r.chain_complex()) ⎛-y x⎞ ⎜ z -y⎟ @@ -408,28 +558,209 @@ def chain_complex(self): 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. -class FreeResolution(FreeResolution_generic): + 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. + 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`` -- a string; name of the base ring + - ``name`` -- string (optional); name of the base ring - - ``algorithm`` -- Singular algorithm to compute a resolution of ``ideal`` + - ``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: + The available algorithms and the corresponding Singular commands + are shown below: ============= ============================ algorithm Singular commands @@ -494,9 +825,9 @@ class FreeResolution(FreeResolution_generic): [-y*z + x*w] [ z^2 - y*w] """ - def __init__(self, module, name='S', algorithm='heuristic'): - """ - Initialize. + def __init__(self, module, name='S', algorithm='heuristic', **kwds): + r""" + Initialize ``self``. TESTS:: @@ -514,48 +845,8 @@ def __init__(self, module, name='S', algorithm='heuristic'): sage: r = FreeResolution(M, name='S') sage: TestSuite(r).run(skip=['_test_pickling']) """ - if isinstance(module, Ideal_generic): - S = module.ring() - elif isinstance(module, Module_free_ambient): - S = module.base_ring() - elif isinstance(module, Matrix_mpolynomial_dense): - S = module.base_ring() - else: - raise TypeError('no ideal, module, or matrix') - - self._module = module self._algorithm = algorithm - super().__init__(S, name=name) - - def _m(self): - r""" - The defining module 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._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_mpolynomial_dense): - return self._module.transpose() + super().__init__(module, name=name, **kwds) @lazy_attribute def _maps(self): @@ -600,42 +891,3 @@ def _maps(self): r = minres(res(std(mod), 0)) return si2sa_resolution(r) - - @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] - """ - ideal = self._module - if isinstance(ideal, Ideal_generic): - S = ideal.ring() - M = FreeModule(S, 1) - N = M.submodule([vector([g]) for g in ideal.gens()]) - elif isinstance(ideal, Module_free_ambient): - S = ideal.base_ring() - M = ideal.ambient_module() - N = ideal - elif isinstance(ideal, Matrix_mpolynomial_dense): - S = ideal.base_ring() - N = ideal.row_space() - M = N.ambient_module() - Q = M.quotient(N) - return Q.coerce_map_from(M) - diff --git a/src/sage/homology/graded_resolution.py b/src/sage/homology/graded_resolution.py index a73aa7f5c9f..940d24d160d 100644 --- a/src/sage/homology/graded_resolution.py +++ b/src/sage/homology/graded_resolution.py @@ -7,17 +7,16 @@ EXAMPLES:: - sage: from sage.homology.graded_resolution import GradedFreeResolution sage: S. = PolynomialRing(QQ) sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) - sage: r = GradedFreeResolution(I, algorithm='minimal') + sage: r = I.graded_free_resolution(algorithm='minimal') sage: r S(0) <-- S(-2)⊕S(-2)⊕S(-2) <-- S(-3)⊕S(-3) <-- 0 - sage: GradedFreeResolution(I, algorithm='shreyer') + sage: I.graded_free_resolution(algorithm='shreyer') S(0) <-- S(-2)⊕S(-2)⊕S(-2) <-- S(-3)⊕S(-3) <-- 0 - sage: GradedFreeResolution(I, algorithm='standard') + sage: I.graded_free_resolution(algorithm='standard') S(0) <-- S(-2)⊕S(-2)⊕S(-2) <-- S(-3)⊕S(-3) <-- 0 - sage: GradedFreeResolution(I, algorithm='heuristic') + sage: I.graded_free_resolution(algorithm='heuristic') S(0) <-- S(-2)⊕S(-2)⊕S(-2) <-- S(-3)⊕S(-3) <-- 0 :: @@ -39,7 +38,7 @@ [ y -z w] [ x -y z] sage: m = d.image() - sage: GradedFreeResolution(m, shifts=(2,2,2)) + 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]_:: @@ -51,20 +50,21 @@ 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 = GradedFreeResolution(I, degrees=[(1,0), (1,1), (1,2), (1,3)]) + sage: r = I.graded_free_resolution(degrees=[(1,0), (1,1), (1,2), (1,3)]) sage: r - S(0) <-- S(-(2, 4))⊕S(-(2, 3))⊕S(-(2, 2)) <-- S(-(3, 5))⊕S(-(3, 4)) <-- 0 + 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 @@ -76,108 +76,63 @@ 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.matrix.matrix_mpolynomial_dense import Matrix_mpolynomial_dense +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 FreeResolution +from sage.homology.free_resolution import (FiniteFreeResolution, + FiniteFreeResolution_free_module, + FiniteFreeResolution_singular) -class GradedFreeResolution(FreeResolution): - """ - Graded free resolutions of ideals of multivariate polynomial rings. +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 - - ``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: from sage.homology.graded_resolution import GradedFreeResolution - sage: S. = PolynomialRing(QQ) - sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) - sage: r = GradedFreeResolution(I) - 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 = GradedFreeResolution(I) - 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'): - """ - Initialize. + def __init__(self, module, degrees=None, shifts=None, name='S', **kwds): + r""" + Initialize ``self``. TESTS:: - sage: from sage.homology.graded_resolution import GradedFreeResolution sage: S. = PolynomialRing(QQ) sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) - sage: r = GradedFreeResolution(I) + 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, algorithm=algorithm) + super().__init__(module, name=name, **kwds) nvars = self._base_ring.ngens() - if isinstance(self._module, Ideal_generic): - rank = 1 - elif isinstance(self._module, Module_free_ambient): - rank = self._m().nrows() - elif isinstance(self._module, Matrix_mpolynomial_dense): - rank = self._module.ncols() - if degrees is None: degrees = nvars * (1,) # standard grading @@ -193,6 +148,13 @@ def __init__(self, module, degrees=None, shifts=None, name='S', algorithm='heuri 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 @@ -200,89 +162,13 @@ def __init__(self, module, degrees=None, shifts=None, name='S', algorithm='heuri self._multigrade = multigrade self._zero_deg = zero_deg - @lazy_attribute - def _maps(self): - """ - The maps that define ``self``. - - This also sets the attribute ``_res_shifts``. - - TESTS:: - - sage: from sage.homology.graded_resolution import GradedFreeResolution - sage: S. = PolynomialRing(QQ) - sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) - sage: r = GradedFreeResolution(I) - 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 - def _repr_module(self, i): """ EXAMPLES:: - sage: from sage.homology.graded_resolution import GradedFreeResolution sage: S. = PolynomialRing(QQ) sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) - sage: r = GradedFreeResolution(I) + sage: r = I.graded_free_resolution() sage: r._repr_module(0) 'S(0)' sage: r._repr_module(1) @@ -291,39 +177,35 @@ def _repr_module(self, i): '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): - m = '0' + return '0' + + if i == 0: + shifts = self._shifts else: - if i == 0: - shifts = self._shifts - else: - shifts = self._res_shifts[i - 1] - - if len(shifts) > 0: - for j in range(len(shifts)): - shift = shifts[j] - if j == 0: - m = f'{self._name}' + \ - (f'(-{shift})' if shift != self._zero_deg else '(0)') - else: - m += f'\u2295{self._name}' + \ - (f'(-{shift})' if shift != self._zero_deg else '(0)') - else: - m = '0' - return m + 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: from sage.homology.graded_resolution import GradedFreeResolution sage: S. = PolynomialRing(QQ) sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) - sage: r = GradedFreeResolution(I) + sage: r = I.graded_free_resolution() sage: r.shifts(0) [0] sage: r.shifts(1) @@ -357,10 +239,9 @@ def betti(self, i, a=None): EXAMPLES:: - sage: from sage.homology.graded_resolution import GradedFreeResolution sage: S. = PolynomialRing(QQ) sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) - sage: r = GradedFreeResolution(I) + sage: r = I.graded_free_resolution() sage: r.betti(0) {0: 1} sage: r.betti(1) @@ -401,10 +282,9 @@ def K_polynomial(self, names=None): EXAMPLES:: - sage: from sage.homology.graded_resolution import GradedFreeResolution sage: S. = PolynomialRing(QQ) sage: I = S.ideal([y*w - z^2, -x*w + y*z, x*z - y^2]) - sage: r = GradedFreeResolution(I) + sage: r = I.graded_free_resolution() sage: r.K_polynomial() 2*t^3 - 3*t^2 + 1 """ @@ -431,3 +311,264 @@ def K_polynomial(self, names=None): 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/interfaces/axiom.py b/src/sage/interfaces/axiom.py index 499592bae6a..0bc5334b071 100644 --- a/src/sage/interfaces/axiom.py +++ b/src/sage/interfaces/axiom.py @@ -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 db2cf4a5d37..99287dec718 100644 --- a/src/sage/interfaces/ecm.py +++ b/src/sage/interfaces/ecm.py @@ -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 @@ -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/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/macaulay2.py b/src/sage/interfaces/macaulay2.py index 91e4da97ea0..14d217cff9e 100644 --- a/src/sage/interfaces/macaulay2.py +++ b/src/sage/interfaces/macaulay2.py @@ -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/magma.py b/src/sage/interfaces/magma.py index bd75a6a1850..e34e5a13aa3 100644 --- a/src/sage/interfaces/magma.py +++ b/src/sage/interfaces/magma.py @@ -197,7 +197,7 @@ magma.functions... """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2005 William Stein # # Distributed under the terms of the GNU General Public License (GPL) @@ -210,8 +210,8 @@ # The full text of the GPL is available at: # # http://www.gnu.org/licenses/ -#***************************************************************************** - +# **************************************************************************** +from __future__ import annotations import re import sys @@ -2133,14 +2133,14 @@ def gen(self, n): """ if n <= 0: raise IndexError("index must be positive since Magma indexes are 1-based") - return self.gens()[n-1] + return self.gens()[n - 1] - def gens(self): + def gens(self) -> tuple: """ - Return generators for self. + Return generators for ``self``. If self is named X in Magma, this function evaluates X.1, X.2, - etc., in Magma until an error occurs. It then returns a Sage list + etc., in Magma until an error occurs. It then returns a Sage tuple of the resulting X.i. Note - I don't think there is a Magma command that returns the list of valid X.i. There are numerous ad hoc functions for various classes but nothing systematic. This function @@ -2154,9 +2154,9 @@ def gens(self): EXAMPLES:: sage: magma("VectorSpace(RationalField(),3)").gens() # optional - magma - [(1 0 0), (0 1 0), (0 0 1)] + ((1 0 0), (0 1 0), (0 0 1)) sage: magma("AbelianGroup(EllipticCurve([1..5]))").gens() # optional - magma - [$.1] + ($.1,) """ try: return self._magma_gens @@ -2172,8 +2172,9 @@ def gens(self): except (RuntimeError, TypeError): break i += 1 - self._magma_gens = G - return G + tG = tuple(G) + self._magma_gens = tG + return tG def gen_names(self): """ diff --git a/src/sage/interfaces/mupad.py b/src/sage/interfaces/mupad.py index ada87034c0c..e4e8531e6c2 100644 --- a/src/sage/interfaces/mupad.py +++ b/src/sage/interfaces/mupad.py @@ -690,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 dd4663ab802..701b91b5afe 100644 --- a/src/sage/interfaces/mwrank.py +++ b/src/sage/interfaces/mwrank.py @@ -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 4923aec71da..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,12 +187,14 @@ 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', # We want the prompt sequence to be unique to avoid confusion with syntax error messages containing >>> prompt=r'octave\:\d+> ', # We don't want any pagination of output - command=command + " --no-line-editing --silent --eval 'PS2(PS1());more off' --persist", + 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, @@ -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/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 934b396e162..d4b10353e0c 100644 --- a/src/sage/interfaces/qepcad.py +++ b/src/sage/interfaces/qepcad.py @@ -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/scilab.py b/src/sage/interfaces/scilab.py index 692c6b0b3b4..8349739633c 100644 --- a/src/sage/interfaces/scilab.py +++ b/src/sage/interfaces/scilab.py @@ -561,4 +561,3 @@ def scilab_version(): 'scilab-...' """ return str(scilab('getversion()')).strip() - 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/lfunctions/pari.py b/src/sage/lfunctions/pari.py index d2b20f1891b..da783d28310 100644 --- a/src/sage/lfunctions/pari.py +++ b/src/sage/lfunctions/pari.py @@ -108,7 +108,7 @@ def __init__(self, conductor, gammaV, weight, eps, poles=[], if args or kwds: self.init_coeffs(*args, **kwds) - def init_coeffs(self, v, cutoff=None, w=1, *args, **kwds): + def init_coeffs(self, v, cutoff=None, w=1): """ Set the coefficients `a_n` of the `L`-series. @@ -126,7 +126,7 @@ def init_coeffs(self, v, cutoff=None, w=1, *args, **kwds): EXAMPLES:: sage: from sage.lfunctions.pari import lfun_generic, LFunction - sage: lf = lfun_generic(conductor=1, gammaV=[0,1], weight=12, eps=1) + sage: lf = lfun_generic(conductor=1, gammaV=[0, 1], weight=12, eps=1) sage: pari_coeffs = pari('k->vector(k,n,(5*sigma(n,3)+7*sigma(n,5))*n/12 - 35*sum(k=1,n-1,(6*k-4*(n-k))*sigma(k,3)*sigma(n-k,5)))') sage: lf.init_coeffs(pari_coeffs) @@ -144,7 +144,7 @@ def init_coeffs(self, v, cutoff=None, w=1, *args, **kwds): Illustrate that one can give a list of complex numbers for v (see :trac:`10937`):: - sage: l2 = lfun_generic(conductor=1, gammaV=[0,1], weight=12, eps=1) + sage: l2 = lfun_generic(conductor=1, gammaV=[0, 1], weight=12, eps=1) sage: l2.init_coeffs(list(delta_qexp(1000))[1:]) sage: L2 = LFunction(l2) sage: L2(14) @@ -155,20 +155,9 @@ def init_coeffs(self, v, cutoff=None, w=1, *args, **kwds): Verify that setting the `w` parameter does not raise an error (see :trac:`10937`):: - sage: L2 = lfun_generic(conductor=1, gammaV=[0,1], weight=12, eps=1) + sage: L2 = lfun_generic(conductor=1, gammaV=[0, 1], weight=12, eps=1) sage: L2.init_coeffs(list(delta_qexp(1000))[1:], w=[1..1000]) - - Additional arguments are ignored for compatibility with the old - Dokchitser script:: - - sage: L2.init_coeffs(list(delta_qexp(1000))[1:], foo="bar") - doctest:...: DeprecationWarning: additional arguments for initializing an lfun_generic are ignored - See https://trac.sagemath.org/26098 for details. """ - if args or kwds: - from sage.misc.superseded import deprecation - deprecation(26098, "additional arguments for initializing an lfun_generic are ignored") - v = pari(v) if v.type() not in ('t_CLOSURE', 't_VEC'): raise TypeError("v (coefficients) must be a list or a function") @@ -250,12 +239,12 @@ def lfun_character(chi): Check the values:: - sage: chi = DirichletGroup(24)([1,-1,-1]); chi + sage: chi = DirichletGroup(24)([1, -1, -1]); chi Dirichlet character modulo 24 of conductor 24 mapping 7 |--> 1, 13 |--> -1, 17 |--> -1 sage: Lchi = lfun_character(chi) sage: v = [0] + Lchi.lfunan(30).sage() - sage: all(v[i] == chi(i) for i in (7,13,17)) + sage: all(v[i] == chi(i) for i in (7, 13, 17)) True """ if not chi.is_primitive(): @@ -285,7 +274,7 @@ def lfun_elliptic_curve(E): Over number fields:: sage: K. = QuadraticField(2) - sage: E = EllipticCurve([1,a]) + sage: E = EllipticCurve([1, a]) sage: L = LFunction(lfun_elliptic_curve(E)) sage: L(3) 1.00412346717019 @@ -309,7 +298,7 @@ def lfun_number_field(K): sage: L(3) 1.20205690315959 - sage: K = NumberField(x**2-2, 'a') + sage: K = NumberField(x**2 - 2, 'a') sage: L = LFunction(lfun_number_field(K)) sage: L(3) 1.15202784126080 @@ -338,10 +327,10 @@ def lfun_eta_quotient(scalings, exponents): sage: L(1) 0.0374412812685155 - sage: lfun_eta_quotient([6],[4]) + sage: lfun_eta_quotient([6], [4]) [[Vecsmall([7]), [Vecsmall([6]), Vecsmall([4])]], 0, [0, 1], 2, 36, 1] - sage: lfun_eta_quotient([2,1,4], [5,-2,-2]) + sage: lfun_eta_quotient([2, 1, 4], [5, -2, -2]) Traceback (most recent call last): ... PariError: sorry, noncuspidal eta quotient is not yet implemented @@ -377,7 +366,7 @@ def lfun_quadratic_form(qf): EXAMPLES:: sage: from sage.lfunctions.pari import lfun_quadratic_form, LFunction - sage: Q = QuadraticForm(ZZ,2,[2,3,4]) + sage: Q = QuadraticForm(ZZ, 2, [2, 3, 4]) sage: L = LFunction(lfun_quadratic_form(Q)) sage: L(3) 0.377597233183583 @@ -409,7 +398,7 @@ def lfun_genus2(C): sage: L(3) 0.965946926261520 - sage: C = HyperellipticCurve(x^2+x, x^3+x^2+1) + sage: C = HyperellipticCurve(x^2 + x, x^3 + x^2 + 1) sage: L = LFunction(lfun_genus2(C)) sage: L(2) 0.364286342944359 @@ -445,11 +434,11 @@ class LFunction(SageObject): 0.000000000000000 sage: L.derivative(1) 0.305999773834052 - sage: L.derivative(1,2) + sage: L.derivative(1, 2) 0.373095594536324 sage: L.num_coeffs() 50 - sage: L.taylor_series(1,4) + sage: L.taylor_series(1, 4) 0.000000000000000 + 0.305999773834052*z + 0.186547797268162*z^2 - 0.136791463097188*z^3 + O(z^4) sage: L.check_functional_equation() # abs tol 4e-19 1.08420217248550e-19 @@ -463,9 +452,9 @@ class LFunction(SageObject): sage: L = E.lseries().dokchitser(algorithm="pari") sage: L.num_coeffs() 163 - sage: L.derivative(1,E.rank()) + sage: L.derivative(1, E.rank()) 1.51863300057685 - sage: L.taylor_series(1,4) + sage: L.taylor_series(1, 4) ...e-19 + (...e-19)*z + 0.759316500288427*z^2 - 0.430302337583362*z^3 + O(z^4) .. RUBRIC:: Number field @@ -481,7 +470,7 @@ class LFunction(SageObject): 348 sage: L(2) 1.10398438736918 - sage: L.taylor_series(2,3) + sage: L.taylor_series(2, 3) 1.10398438736918 - 0.215822638498759*z + 0.279836437522536*z^2 + O(z^3) .. RUBRIC:: Ramanujan `\Delta` L-function @@ -489,7 +478,7 @@ class LFunction(SageObject): The coefficients are given by Ramanujan's tau function:: sage: from sage.lfunctions.pari import lfun_generic, LFunction - sage: lf = lfun_generic(conductor=1, gammaV=[0,1], weight=12, eps=1) + sage: lf = lfun_generic(conductor=1, gammaV=[0, 1], weight=12, eps=1) sage: tau = pari('k->vector(k,n,(5*sigma(n,3)+7*sigma(n,5))*n/12 - 35*sum(k=1,n-1,(6*k-4*(n-k))*sigma(k,3)*sigma(n-k,5)))') sage: lf.init_coeffs(tau) sage: L = LFunction(lf) @@ -498,7 +487,7 @@ class LFunction(SageObject): sage: L(1) 0.0374412812685155 - sage: L.taylor_series(1,3) + sage: L.taylor_series(1, 3) 0.0374412812685155 + 0.0709221123619322*z + 0.0380744761270520*z^2 + O(z^3) """ def __init__(self, lfun, prec=None): @@ -608,7 +597,7 @@ def Lambda(self, s): sage: L = LFunction(lfun_number_field(QQ)) sage: L.Lambda(2) 0.523598775598299 - sage: L.Lambda(1-2) + sage: L.Lambda(1 - 2) 0.523598775598299 """ s = self._CCin(s) @@ -630,7 +619,7 @@ def hardy(self, t): TESTS:: - sage: L.hardy(.4+.3*I) + sage: L.hardy(.4 + .3*I) Traceback (most recent call last): ... PariError: incorrect type in lfunhardy (t_COMPLEX) @@ -694,7 +683,7 @@ def taylor_series(self, s, k=6, var='z'): sage: E = EllipticCurve('389a') sage: L = E.lseries().dokchitser(200,algorithm="pari") - sage: L.taylor_series(1,3) + sage: L.taylor_series(1, 3) 2...e-63 + (...e-63)*z + 0.75931650028842677023019260789472201907809751649492435158581*z^2 + O(z^3) Check that :trac:`25402` is fixed:: @@ -757,7 +746,7 @@ def __call__(self, s): sage: L = E.lseries().dokchitser(100, algorithm="pari") sage: L(1) 0.00000000000000000000000000000 - sage: L(1+I) + sage: L(1 + I) -1.3085436607849493358323930438 + 0.81298000036784359634835412129*I """ s = self._CC(s) diff --git a/src/sage/libs/eclib/mwrank.pyx b/src/sage/libs/eclib/mwrank.pyx index 7df61a54f12..119d4d49dd4 100644 --- a/src/sage/libs/eclib/mwrank.pyx +++ b/src/sage/libs/eclib/mwrank.pyx @@ -83,7 +83,7 @@ mwrank_set_precision(150) def get_precision(): """ - Returns the working floating point bit precision of mwrank, which is + Return the working floating point bit precision of mwrank, which is equal to the global NTL real number precision. OUTPUT: @@ -119,7 +119,7 @@ def set_precision(n): This change is global and affects *all* future calls of eclib functions by Sage. - .. note:: + .. NOTE:: The minimal value to which the precision may be set is 53. Lower values will be increased to 53. @@ -335,7 +335,7 @@ cdef class _Curvedata: # cython class wrapping eclib's Curvedata class return string_sigoff(Curvedata_repr(self.x))[:-1] def silverman_bound(self): - """ + r""" The Silverman height bound for this elliptic curve. OUTPUT: @@ -345,7 +345,7 @@ cdef class _Curvedata: # cython class wrapping eclib's Curvedata class + B`, where `h(P)` is the naive height and `\hat{h}(P)` the canonical height. - .. note:: + .. NOTE:: Since eclib can compute this to arbitrary precision, we could return a Sage real, but this is only a bound and in @@ -364,7 +364,7 @@ cdef class _Curvedata: # cython class wrapping eclib's Curvedata class return Curvedata_silverman_bound(self.x) def cps_bound(self): - """ + r""" The Cremona-Prickett-Siksek height bound for this elliptic curve. OUTPUT: @@ -374,12 +374,11 @@ cdef class _Curvedata: # cython class wrapping eclib's Curvedata class + B`, where `h(P)` is the naive height and `\hat{h}(P)` the canonical height. - .. note:: + .. NOTE:: - Since eclib can compute this to arbitrary precision, we + Since ``eclib`` can compute this to arbitrary precision, we could return a Sage real, but this is only a bound and in - the contexts in which it is used extra precision is - irrelevant. + the contexts in which it is used extra precision is irrelevant. EXAMPLES:: @@ -399,7 +398,7 @@ cdef class _Curvedata: # cython class wrapping eclib's Curvedata class return x def height_constant(self): - """ + r""" A height bound for this elliptic curve. OUTPUT: @@ -410,12 +409,11 @@ cdef class _Curvedata: # cython class wrapping eclib's Curvedata class canonical height. This is the minimum of the Silverman and Cremona_Prickett-Siksek height bounds. - .. note:: + .. NOTE:: - Since eclib can compute this to arbitrary precision, we + Since ``eclib`` can compute this to arbitrary precision, we could return a Sage real, but this is only a bound and in - the contexts in which it is used extra precision is - irrelevant. + the contexts in which it is used extra precision is irrelevant. EXAMPLES:: @@ -542,7 +540,7 @@ cdef class _mw: cdef int verb def __init__(self, _Curvedata curve, verb=False, pp=1, maxr=999): - """ + r""" Constructor for mw class. INPUT: @@ -704,10 +702,10 @@ cdef class _mw: .. NOTE:: - The eclib function which implements this only carries out - any saturation if the rank of the points increases upon - adding the new point. This is because it is assumed that - one saturates as ones goes along. + The eclib function which implements this only carries out + any saturation if the rank of the points increases upon + adding the new point. This is because it is assumed that + one saturates as ones goes along. EXAMPLES:: @@ -753,7 +751,7 @@ cdef class _mw: def getbasis(self): """ - Returns the current basis of the mw structure. + Return the current basis of the mw structure. OUTPUT: @@ -778,7 +776,7 @@ cdef class _mw: def regulator(self): """ - Returns the regulator of the current basis of the mw group. + Return the regulator of the current basis of the mw group. OUTPUT: @@ -810,7 +808,7 @@ cdef class _mw: def rank(self): """ - Returns the rank of the current basis of the mw group. + Return the rank of the current basis of the mw group. OUTPUT: @@ -908,7 +906,7 @@ cdef class _mw: return ok, index, unsat def search(self, h_lim, int moduli_option=0, int verb=0): - """ + r""" Search for points in the mw group. INPUT: @@ -926,17 +924,16 @@ cdef class _mw: .. NOTE:: - The effect of the search is also governed by the class - options, notably whether the points found are processed: - meaning that linear relations are found and saturation is - carried out, with the result that the list of generators - will always contain a `\ZZ`-span of the saturation of the - points found, modulo torsion. + The effect of the search is also governed by the class + options, notably whether the points found are processed: + meaning that linear relations are found and saturation is + carried out, with the result that the list of generators + will always contain a `\ZZ`-span of the saturation of the + points found, modulo torsion. OUTPUT: - None. The effect of the search is to update the list of - generators. + None. The effect of the search is to update the list of generators. EXAMPLES:: @@ -1069,7 +1066,7 @@ cdef class _two_descent: def getrank(self): """ - Returns the rank (after doing a 2-descent). + Return the rank (after doing a 2-descent). OUTPUT: @@ -1102,7 +1099,7 @@ cdef class _two_descent: def getrankbound(self): """ - Returns the rank upper bound (after doing a 2-descent). + Return the rank upper bound (after doing a 2-descent). OUTPUT: @@ -1135,7 +1132,7 @@ cdef class _two_descent: def getselmer(self): """ - Returns the 2-Selmer rank (after doing a 2-descent). + Return the 2-Selmer rank (after doing a 2-descent). OUTPUT: @@ -1167,7 +1164,7 @@ cdef class _two_descent: def ok(self): """ - Returns the success flag (after doing a 2-descent). + Return the success flag (after doing a 2-descent). OUTPUT: @@ -1196,7 +1193,7 @@ cdef class _two_descent: def getcertain(self): """ - Returns the certainty flag (after doing a 2-descent). + Return the certainty flag (after doing a 2-descent). OUTPUT: @@ -1273,8 +1270,8 @@ cdef class _two_descent: sig_off() def getbasis(self): - """ - Returns the basis of points found by doing a 2-descent. + r""" + Return the basis of points found by doing a 2-descent. If the success and certain flags are 1, this will be a `\ZZ/2\ZZ`-basis for `E(\QQ)/2E(\QQ)` (modulo torsion), @@ -1282,7 +1279,8 @@ cdef class _two_descent: .. NOTE:: - You must call ``saturate()`` first, or a RunTimeError will be raised. + You must call ``saturate()`` first, or a ``RunTimeError`` + will be raised. OUTPUT: @@ -1320,7 +1318,7 @@ cdef class _two_descent: def regulator(self): """ - Returns the regulator of the points found by doing a 2-descent. + Return the regulator of the points found by doing a 2-descent. OUTPUT: diff --git a/src/sage/libs/eclib/newforms.pyx b/src/sage/libs/eclib/newforms.pyx index d30ac0e3758..2d35716c4db 100644 --- a/src/sage/libs/eclib/newforms.pyx +++ b/src/sage/libs/eclib/newforms.pyx @@ -22,7 +22,7 @@ from sage.modular.all import Cusp cdef class ECModularSymbol: - """ + r""" Modular symbol associated with an elliptic curve, using John Cremona's newforms class. EXAMPLES:: 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/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/ntl/ntl_GF2.pyx b/src/sage/libs/ntl/ntl_GF2.pyx index c88034ff5c4..8af0d6caf9a 100644 --- a/src/sage/libs/ntl/ntl_GF2.pyx +++ b/src/sage/libs/ntl/ntl_GF2.pyx @@ -116,6 +116,8 @@ cdef class ntl_GF2(): def __mul__(self, other): """ + EXAMPLES:: + sage: o = ntl.GF2(1) sage: z = ntl.GF2(0) sage: o*o @@ -137,6 +139,8 @@ cdef class ntl_GF2(): def __truediv__(self, other): """ + EXAMPLES:: + sage: o = ntl.GF2(1) sage: z = ntl.GF2(0) sage: o/o @@ -159,6 +163,8 @@ cdef class ntl_GF2(): def __sub__(self, other): """ + EXAMPLES:: + sage: o = ntl.GF2(1) sage: z = ntl.GF2(0) sage: o-o @@ -180,6 +186,8 @@ cdef class ntl_GF2(): def __add__(self, other): """ + EXAMPLES:: + sage: o = ntl.GF2(1) sage: z = ntl.GF2(0) sage: o+o @@ -201,6 +209,8 @@ cdef class ntl_GF2(): def __neg__(ntl_GF2 self): """ + EXAMPLES:: + sage: o = ntl.GF2(1) sage: z = ntl.GF2(0) sage: -z @@ -214,6 +224,8 @@ cdef class ntl_GF2(): def __pow__(ntl_GF2 self, long e, ignored): """ + EXAMPLES:: + sage: o = ntl.GF2(1) sage: z = ntl.GF2(0) sage: z^2 diff --git a/src/sage/libs/ntl/ntl_GF2E.pyx b/src/sage/libs/ntl/ntl_GF2E.pyx index bc6a32ce0d3..54c7eef492a 100644 --- a/src/sage/libs/ntl/ntl_GF2E.pyx +++ b/src/sage/libs/ntl/ntl_GF2E.pyx @@ -5,7 +5,7 @@ # distutils: extra_link_args = NTL_LIBEXTRA # distutils: language = c++ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2005 William Stein # Copyright (C) 2007 Martin Albrecht # @@ -18,8 +18,8 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from sage.ext.cplusplus cimport ccrepr @@ -73,7 +73,7 @@ def ntl_GF2E_random(ntl_GF2EContext_class ctx): cdef class ntl_GF2E(): r""" - The \\class{GF2E} represents a finite extension field over GF(2) + The :class:`GF2E` represents a finite extension field over GF(2) using NTL. Elements are represented as polynomials over GF(2) modulo a modulus. @@ -164,6 +164,8 @@ cdef class ntl_GF2E(): def __reduce__(self): """ + EXAMPLES:: + sage: ctx = ntl.GF2EContext( ntl.GF2X([1,1,0,1,1,0,0,0,1]) ) sage: a = ntl.GF2E(ntl.ZZ_pX([1,1,3],2), ctx) sage: loads(dumps(a)) == a @@ -439,17 +441,20 @@ cdef class ntl_GF2E(): return l def _sage_(ntl_GF2E self, k=None): - """ - Returns a \class{FiniteFieldElement} representation - of this element. If a \class{FiniteField} k is provided - it is constructed in this field if possible. A \class{FiniteField} - will be constructed if none is provided. + r""" + Return a :class:`FiniteFieldElement` representation of this element. + + If a :class:`FiniteField` `k` is provided, it is constructed + in this field if possible. A :class:`FiniteField` will be + constructed if none is provided. INPUT: - k -- optional GF(2**deg) + + - `k` -- (optional) a field `\GF{2^d}` OUTPUT: - FiniteFieldElement over k + + :class:`FiniteFieldElement` over `k` EXAMPLES:: diff --git a/src/sage/libs/ntl/ntl_ZZ.pyx b/src/sage/libs/ntl/ntl_ZZ.pyx index 39a98da2101..6f9d15f780e 100644 --- a/src/sage/libs/ntl/ntl_ZZ.pyx +++ b/src/sage/libs/ntl/ntl_ZZ.pyx @@ -5,7 +5,7 @@ # distutils: extra_link_args = NTL_LIBEXTRA # distutils: language = c++ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2005 William Stein # # Distributed under the terms of the GNU General Public License (GPL) @@ -17,8 +17,8 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from cysignals.signals cimport sig_on, sig_off from sage.ext.cplusplus cimport ccrepr, ccreadstr @@ -176,6 +176,8 @@ cdef class ntl_ZZ(): def __mul__(self, other): """ + EXAMPLES:: + sage: n=ntl.ZZ(2983)*ntl.ZZ(2) sage: n 5966 @@ -192,6 +194,8 @@ cdef class ntl_ZZ(): def __sub__(self, other): """ + EXAMPLES:: + sage: n=ntl.ZZ(2983)-ntl.ZZ(2) sage: n 2981 @@ -208,6 +212,8 @@ cdef class ntl_ZZ(): def __add__(self, other): """ + EXAMPLES:: + sage: n=ntl.ZZ(2983)+ntl.ZZ(2) sage: n 2985 @@ -224,6 +230,8 @@ cdef class ntl_ZZ(): def __neg__(ntl_ZZ self): """ + EXAMPLES:: + sage: x = ntl.ZZ(38) sage: -x -38 @@ -236,6 +244,8 @@ cdef class ntl_ZZ(): def __pow__(ntl_ZZ self, long e, ignored): """ + EXAMPLES:: + sage: ntl.ZZ(23)^50 122008981252869411022491112993141891091036959856659100591281395343249 """ @@ -266,6 +276,7 @@ cdef class ntl_ZZ(): cdef int get_as_int(ntl_ZZ self): r""" Returns value as C int. + Return value is only valid if the result fits into an int. AUTHOR: David Harvey (2006-08-05) @@ -278,12 +289,14 @@ cdef class ntl_ZZ(): r""" This method exists solely for automated testing of get_as_int(). - sage: x = ntl.ZZ(42) - sage: i = x.get_as_int_doctest() - sage: i - 42 - sage: type(i) - <... 'int'> + EXAMPLES:: + + sage: x = ntl.ZZ(42) + sage: i = x.get_as_int_doctest() + sage: i + 42 + sage: type(i) + <... 'int'> """ return self.get_as_int() @@ -291,9 +304,11 @@ cdef class ntl_ZZ(): r""" Gets the value as a sage int. - sage: n=ntl.ZZ(2983) - sage: type(n._integer_()) - + EXAMPLES:: + + sage: n=ntl.ZZ(2983) + sage: type(n._integer_()) + AUTHOR: Joel B. Mohler """ @@ -332,10 +347,12 @@ cdef class ntl_ZZ(): r""" This method exists solely for automated testing of set_from_int(). - sage: x = ntl.ZZ() - sage: x.set_from_int_doctest(42) - sage: x - 42 + EXAMPLES:: + + sage: x = ntl.ZZ() + sage: x.set_from_int_doctest(42) + sage: x + 42 """ self.set_from_int(int(value)) diff --git a/src/sage/libs/ntl/ntl_ZZX.pyx b/src/sage/libs/ntl/ntl_ZZX.pyx index 8d38fcb7f8f..e369f7152e4 100644 --- a/src/sage/libs/ntl/ntl_ZZX.pyx +++ b/src/sage/libs/ntl/ntl_ZZX.pyx @@ -5,7 +5,7 @@ # distutils: extra_link_args = NTL_LIBEXTRA # distutils: language = c++ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2005 William Stein # # Distributed under the terms of the GNU General Public License (GPL) @@ -17,8 +17,8 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from cysignals.signals cimport sig_on, sig_off @@ -144,10 +144,12 @@ cdef class ntl_ZZX(): def __reduce__(self): """ - sage: from sage.libs.ntl.ntl_ZZX import ntl_ZZX - sage: f = ntl_ZZX([1,2,0,4]) - sage: loads(dumps(f)) == f - True + EXAMPLES:: + + sage: from sage.libs.ntl.ntl_ZZX import ntl_ZZX + sage: f = ntl_ZZX([1,2,0,4]) + sage: loads(dumps(f)) == f + True """ return unpickle_class_value, (ntl_ZZX, self.list()) @@ -183,6 +185,8 @@ cdef class ntl_ZZX(): def __setitem__(self, long i, a): """ + EXAMPLES:: + sage: n=ntl.ZZX([1,2,3]) sage: n [1 2 3] @@ -692,12 +696,14 @@ cdef class ntl_ZZX(): return (self*other).quo_rem(g)[0] def xgcd(self, ntl_ZZX other, proof=None): - """ - If self and other are coprime over the rationals, return r, s, - t such that r = s*self + t*other. Otherwise return 0. This - is \emph{not} the same as the \sage function on polynomials - over the integers, since here the return value r is always an - integer. + r""" + If ``self`` and ``other`` are coprime over the rationals, + return ``r, s, t`` such that ``r = s*self + t*other``. + Otherwise return 0. + + This is \emph{not} the same as the \sage function on + polynomials over the integers, since here the return value r + is always an integer. Here r is the resultant of a and b; if r != 0, then this function computes s and t such that: a*s + b*t = r; otherwise @@ -709,7 +715,6 @@ cdef class ntl_ZZX(): randomized strategy that errors with probability no more than `2^{-80}`. - EXAMPLES:: sage: f = ntl.ZZX([1,2,3]) * ntl.ZZX([4,5])**2 @@ -717,7 +722,8 @@ cdef class ntl_ZZX(): sage: f.xgcd(g) # nothing since they are not coprime (0, [], []) - In this example the input quadratic polynomials have a common root modulo 13. + In this example the input quadratic polynomials have a common root modulo 13:: + sage: f = ntl.ZZX([5,0,1]) sage: g = ntl.ZZX([18,0,1]) sage: f.xgcd(g) @@ -805,7 +811,8 @@ cdef class ntl_ZZX(): sage: f == g True - Though f and g are equal, they are not the same objects in memory: + Though f and g are equal, they are not the same objects in memory:: + sage: f is g False """ diff --git a/src/sage/libs/ntl/ntl_ZZ_p.pyx b/src/sage/libs/ntl/ntl_ZZ_p.pyx index 9b90383d50c..469d62f2812 100644 --- a/src/sage/libs/ntl/ntl_ZZ_p.pyx +++ b/src/sage/libs/ntl/ntl_ZZ_p.pyx @@ -166,9 +166,11 @@ cdef class ntl_ZZ_p(): def __reduce__(self): """ - sage: a = ntl.ZZ_p(4,7) - sage: loads(dumps(a)) == a - True + EXAMPLES:: + + sage: a = ntl.ZZ_p(4,7) + sage: loads(dumps(a)) == a + True """ return unpickle_class_args, (ntl_ZZ_p, (self.lift(), self.modulus_context())) diff --git a/src/sage/libs/ntl/ntl_ZZ_pE.pyx b/src/sage/libs/ntl/ntl_ZZ_pE.pyx index 97c658dffb0..9e2d06bb58e 100644 --- a/src/sage/libs/ntl/ntl_ZZ_pE.pyx +++ b/src/sage/libs/ntl/ntl_ZZ_pE.pyx @@ -166,9 +166,11 @@ cdef class ntl_ZZ_pE(): def __reduce__(self): """ - sage: a = ntl.ZZ_pE([4],ntl.ZZ_pX([1,1,1],ntl.ZZ(7))) - sage: loads(dumps(a)) == a - True + EXAMPLES:: + + sage: a = ntl.ZZ_pE([4],ntl.ZZ_pX([1,1,1],ntl.ZZ(7))) + sage: loads(dumps(a)) == a + True """ return make_ZZ_pE, (self.get_as_ZZ_pX(), self.get_modulus_context()) diff --git a/src/sage/libs/ntl/ntl_ZZ_pEContext.pyx b/src/sage/libs/ntl/ntl_ZZ_pEContext.pyx index bef581722bd..b6ff3c66b01 100644 --- a/src/sage/libs/ntl/ntl_ZZ_pEContext.pyx +++ b/src/sage/libs/ntl/ntl_ZZ_pEContext.pyx @@ -37,13 +37,15 @@ cdef class ntl_ZZ_pEContext_class(): """ EXAMPLES: - # You can construct contexts manually. + You can construct contexts manually:: + sage: c=ntl.ZZ_pEContext(ntl.ZZ_pX([4,1,6],25)) sage: n1=c.ZZ_pE([10,17,12]) sage: n1 [2 15] - # or You can construct contexts implicitly. + Or you can construct contexts implicitly:: + sage: n2=ntl.ZZ_pE(12, ntl.ZZ_pX([1,1,1],7)) sage: n2 [5] @@ -65,9 +67,11 @@ cdef class ntl_ZZ_pEContext_class(): def __reduce__(self): """ - sage: c=ntl.ZZ_pEContext(ntl.ZZ_pX([1,1,1],7)) - sage: loads(dumps(c)) is c - True + EXAMPLES:: + + sage: c=ntl.ZZ_pEContext(ntl.ZZ_pX([1,1,1],7)) + sage: loads(dumps(c)) is c + True """ return ntl_ZZ_pEContext, (self.f,) diff --git a/src/sage/libs/ntl/ntl_ZZ_pEX.pyx b/src/sage/libs/ntl/ntl_ZZ_pEX.pyx index f9d2e982343..d5f10218a77 100644 --- a/src/sage/libs/ntl/ntl_ZZ_pEX.pyx +++ b/src/sage/libs/ntl/ntl_ZZ_pEX.pyx @@ -5,14 +5,15 @@ # distutils: extra_link_args = NTL_LIBEXTRA # distutils: language = c++ -""" +r""" Wrapper for NTL's polynomials over finite ring extensions of `\Z / p\Z.` AUTHORS: - -- David Roe (2007-10-10) + +- David Roe (2007-10-10) """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2007 William Stein # David Roe # @@ -25,8 +26,8 @@ AUTHORS: # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from cysignals.signals cimport sig_on, sig_off from sage.ext.cplusplus cimport ccrepr diff --git a/src/sage/libs/ntl/ntl_ZZ_pX.pyx b/src/sage/libs/ntl/ntl_ZZ_pX.pyx index 95d77f727fa..c2fff88cb0f 100644 --- a/src/sage/libs/ntl/ntl_ZZ_pX.pyx +++ b/src/sage/libs/ntl/ntl_ZZ_pX.pyx @@ -207,11 +207,13 @@ cdef class ntl_ZZ_pX(): def __setitem__(self, long i, a): r""" - sage: c = ntl.ZZ_pContext(23) - sage: x = ntl.ZZ_pX([2, 3, 4], c) - sage: x[1] = 5 - sage: x - [2 5 4] + EXAMPLES:: + + sage: c = ntl.ZZ_pContext(23) + sage: x = ntl.ZZ_pX([2, 3, 4], c) + sage: x[1] = 5 + sage: x + [2 5 4] """ if i < 0: raise IndexError("index (i=%s) must be >= 0" % i) diff --git a/src/sage/libs/ntl/ntl_lzz_pContext.pyx b/src/sage/libs/ntl/ntl_lzz_pContext.pyx index 64301157702..80ff9ac51bc 100644 --- a/src/sage/libs/ntl/ntl_lzz_pContext.pyx +++ b/src/sage/libs/ntl/ntl_lzz_pContext.pyx @@ -60,9 +60,11 @@ cdef class ntl_zz_pContext_class(): def __reduce__(self): """ - sage: c=ntl.zz_pContext(13) - sage: loads(dumps(c)) is c - True + EXAMPLES:: + + sage: c=ntl.zz_pContext(13) + sage: loads(dumps(c)) is c + True """ return ntl_zz_pContext, (self.p,) diff --git a/src/sage/libs/ntl/ntl_mat_GF2.pyx b/src/sage/libs/ntl/ntl_mat_GF2.pyx index 833b7b9ea8e..ee90bf17fce 100644 --- a/src/sage/libs/ntl/ntl_mat_GF2.pyx +++ b/src/sage/libs/ntl/ntl_mat_GF2.pyx @@ -5,7 +5,7 @@ # distutils: extra_link_args = NTL_LIBEXTRA # distutils: language = c++ -""" +r""" Matrices over the `\GF{2}` via NTL This class is only provided to have a complete NTL interface and for @@ -13,11 +13,11 @@ comparison purposes. Sage's native matrices over `F_2` are much faster for many problems like matrix multiplication and Gaussian elimination. AUTHORS: - - Martin Albrecht - 2008-09: initial version + +- Martin Albrecht 2008-09: initial version """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2005 William Stein # Copyright (C) 2008 Martin Albrecht # @@ -30,8 +30,8 @@ AUTHORS: # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from cysignals.signals cimport sig_on, sig_off from sage.ext.cplusplus cimport ccrepr @@ -131,6 +131,8 @@ cdef class ntl_mat_GF2(): def __reduce__(self): """ + EXAMPLES:: + sage: A = random_matrix(GF(2),4,4) sage: B = ntl.mat_GF2(A) sage: loads(dumps(B)) == B # indirect doctest @@ -375,17 +377,18 @@ cdef class ntl_mat_GF2(): sig_off() return r - def gauss(self,ncols=-1): - """ - Performs unitary row operations so as to bring this matrix - into row echelon form (not reduced!). If the optional - argument \code{ncols} is supplied, stops when first ncols - columns are in echelon form. The return value is the rank (or - the rank of the first ncols columns). + def gauss(self, ncols=-1): + r""" + Perform unitary row operations so as to bring this matrix + into row echelon form (not reduced!). + + If the optional argument ``ncols`` is supplied, stops when + first ``ncols`` columns are in echelon form. The return value is + the rank (or the rank of the first ``ncols`` columns). INPUT: - ncols -- number of columns to process (default: all) + - ``ncols`` -- number of columns to process (default: all) EXAMPLES:: diff --git a/src/sage/libs/ntl/ntl_mat_GF2E.pyx b/src/sage/libs/ntl/ntl_mat_GF2E.pyx index e18d150cbda..574ff43c213 100644 --- a/src/sage/libs/ntl/ntl_mat_GF2E.pyx +++ b/src/sage/libs/ntl/ntl_mat_GF2E.pyx @@ -445,17 +445,18 @@ cdef class ntl_mat_GF2E(): sig_off() return r - def gauss(self,ncols=-1): - """ - Performs unitary row operations so as to bring this matrix - into row echelon form. If the optional argument \code{ncols} - is supplied, stops when first ncols columns are in echelon - form. The return value is the rank (or the rank of the first - ncols columns). + def gauss(self, ncols=-1): + r""" + Perform unitary row operations so as to bring this matrix + into row echelon form. + + If the optional argument ``ncols`` is supplied, stops when + first ``ncols`` columns are in echelon form. The return value + is the rank (or the rank of the first ``ncols`` columns). INPUT: - - ``ncols`` - number of columns to process (default: all) + - ``ncols`` -- number of columns to process (default: all) EXAMPLES:: diff --git a/src/sage/libs/ntl/ntl_mat_ZZ.pyx b/src/sage/libs/ntl/ntl_mat_ZZ.pyx index dac2ca79636..fb1769db352 100644 --- a/src/sage/libs/ntl/ntl_mat_ZZ.pyx +++ b/src/sage/libs/ntl/ntl_mat_ZZ.pyx @@ -73,7 +73,7 @@ cdef class ntl_mat_ZZ(): The \class{mat_ZZ} class implements arithmetic with matrices over `\Z`. """ def __init__(self, nrows=0, ncols=0, v=None): - """ + r""" The \class{mat_ZZ} class implements arithmetic with matrices over `\Z`. EXAMPLES:: @@ -319,6 +319,8 @@ cdef class ntl_mat_ZZ(): def __getitem__(self, ij): """ + EXAMPLES:: + sage: m = ntl.mat_ZZ(3, 2, range(6)) sage: m[0,0] ## indirect doctest 0 diff --git a/src/sage/libs/singular/polynomial.pyx b/src/sage/libs/singular/polynomial.pyx index e012da4573c..b2efc7dfbcb 100644 --- a/src/sage/libs/singular/polynomial.pyx +++ b/src/sage/libs/singular/polynomial.pyx @@ -5,16 +5,15 @@ AUTHOR: - Martin Albrecht (2009-07): refactoring """ - -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2009 Martin Albrecht # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from cysignals.signals cimport sig_on, sig_off @@ -22,7 +21,7 @@ cdef extern from *: # hack to get at cython macro int unlikely(int) import re -plusminus_pattern = re.compile("([^\(^])([\+\-])") +plusminus_pattern = re.compile(r"([^\(^])([\+\-])") parenthvar_pattern = re.compile(r"\(([a-zA-Z][a-zA-Z0-9]*)\)") from sage.cpython.string cimport bytes_to_str, str_to_bytes diff --git a/src/sage/libs/singular/singular.pyx b/src/sage/libs/singular/singular.pyx index 82d83e01212..d8ea7b07f3c 100644 --- a/src/sage/libs/singular/singular.pyx +++ b/src/sage/libs/singular/singular.pyx @@ -653,6 +653,24 @@ cpdef list si2sa_resolution(Resolution res): - ``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 @@ -754,6 +772,27 @@ cpdef tuple si2sa_resolution_graded(Resolution res, tuple degrees): - ``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 @@ -865,9 +904,9 @@ cdef number *sa2si_QQ(Rational r, ring *_ring): INPUT: - - ``r`` - a sage rational number + - ``r`` -- a sage rational number - - ``_ ring`` - a (pointer to) a singular ring, where the resul will live + - ``_ ring`` -- a (pointer to) a singular ring, where the resul will live OUTPUT: @@ -895,9 +934,9 @@ cdef number *sa2si_GFqGivaro(int quo, ring *_ring): INPUT: - - ``quo`` - a sage integer + - ``quo`` -- a sage integer - - ``_ ring`` - a (pointer to) a singular ring, where the resul will live + - ``_ ring`` -- a (pointer to) a singular ring, where the resul will live OUTPUT: @@ -963,9 +1002,9 @@ cdef number *sa2si_GFqNTLGF2E(FFgf2eE elem, ring *_ring): INPUT: - - ``elem`` - a sage element of a ntl_gf2e finite field + - ``elem`` -- a sage element of a ntl_gf2e finite field - - ``_ ring`` - a (pointer to) a singular ring, where the resul will live + - ``_ ring`` -- a (pointer to) a singular ring, where the resul will live OUTPUT: @@ -1028,9 +1067,9 @@ cdef number *sa2si_GFq_generic(object elem, ring *_ring): INPUT: - - ``elem`` - a sage element of a generic finite field + - ``elem`` -- a sage element of a generic finite field - - ``_ ring`` - a (pointer to) a singular ring, where the resul will live + - ``_ ring`` -- a (pointer to) a singular ring, where the resul will live OUTPUT: @@ -1094,15 +1133,14 @@ cdef number *sa2si_transext_QQ(object elem, ring *_ring): INPUT: - - ``elem`` - a sage element of a FractionField of polynomials over the rationals + - ``elem`` -- a sage element of a FractionField of polynomials over the rationals - - ``_ ring`` - a (pointer to) a singular ring, where the resul will live + - ``_ 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() @@ -1245,15 +1283,14 @@ cdef number *sa2si_transext_FF(object elem, ring *_ring): INPUT: - - ``elem`` - a sage element of a FractionField of polynomials over the rationals + - ``elem`` -- a sage element of a FractionField of polynomials over the rationals - - ``_ ring`` - a (pointer to) a singular ring, where the resul will live + - ``_ 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() @@ -1346,9 +1383,9 @@ cdef number *sa2si_NF(object elem, ring *_ring): INPUT: - - ``elem`` - a sage element of a NumberField + - ``elem`` -- a sage element of a NumberField - - ``_ ring`` - a (pointer to) a singular ring, where the resul will live + - ``_ ring`` -- a (pointer to) a singular ring, where the resul will live OUTPUT: @@ -1437,15 +1474,14 @@ cdef number *sa2si_ZZ(Integer d, ring *_ring): INPUT: - - ``elem`` - a sage Integer + - ``elem`` -- a sage Integer - - ``_ ring`` - a (pointer to) a singular ring, where the resul will live + - ``_ ring`` -- a (pointer to) a singular ring, where the resul will live OUTPUT: - A (pointer to) a singular number - TESTS:: sage: P. = ZZ[] @@ -1650,7 +1686,7 @@ cdef object si2sa_intvec(intvec *v): INPUT: - - ``v`` -- a (pointer to) a singular intvec + - ``v`` -- a (pointer to) singular intvec OUTPUT: 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/catalog.py b/src/sage/manifolds/catalog.py index 4e10184fd3e..62fc5710d6f 100644 --- a/src/sage/manifolds/catalog.py +++ b/src/sage/manifolds/catalog.py @@ -29,7 +29,7 @@ # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. -# http://www.gnu.org/licenses/ +# https://www.gnu.org/licenses/ # ***************************************************************************** # Lazy import from examples folders: diff --git a/src/sage/manifolds/differentiable/characteristic_cohomology_class.py b/src/sage/manifolds/differentiable/characteristic_cohomology_class.py index 69744a312a5..689760625da 100644 --- a/src/sage/manifolds/differentiable/characteristic_cohomology_class.py +++ b/src/sage/manifolds/differentiable/characteristic_cohomology_class.py @@ -271,14 +271,14 @@ """ -#****************************************************************************** +# ***************************************************************************** # Copyright (C) 2021 Michael Jung # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of # the License, or (at your option) any later version. # https://www.gnu.org/licenses/ -#****************************************************************************** +# ***************************************************************************** from sage.algebras.finite_gca import FiniteGCAlgebra from sage.combinat.free_module import IndexedFreeModuleElement @@ -693,10 +693,10 @@ class CharacteristicCohomologyClassRing(FiniteGCAlgebra): Algebra of characteristic cohomology classes of the Tangent bundle TM over the 8-dimensional differentiable manifold M sage: CR.gens() - [Characteristic cohomology class (p_1)(TM) of the Tangent bundle TM over + (Characteristic cohomology class (p_1)(TM) of the Tangent bundle TM over the 8-dimensional differentiable manifold M, Characteristic cohomology class (p_2)(TM) of the Tangent bundle TM - over the 8-dimensional differentiable manifold M] + over the 8-dimensional differentiable manifold M) The default base ring is `\QQ`:: @@ -712,12 +712,12 @@ class CharacteristicCohomologyClassRing(FiniteGCAlgebra): complex vector bundle E -> M of rank 2 over the base space 4-dimensional differentiable manifold M sage: CR_E.gens() - [Characteristic cohomology class (c_1)(E) of the Differentiable complex + (Characteristic cohomology class (c_1)(E) of the Differentiable complex vector bundle E -> M of rank 2 over the base space 4-dimensional differentiable manifold M, Characteristic cohomology class (c_2)(E) of the Differentiable complex vector bundle E -> M of rank 2 over the base space - 4-dimensional differentiable manifold M] + 4-dimensional differentiable manifold M) Characteristic cohomology class ring over an oriented manifold:: @@ -727,9 +727,9 @@ class CharacteristicCohomologyClassRing(FiniteGCAlgebra): True sage: CR = TS2.characteristic_cohomology_class_ring() sage: CR.gens() - [Characteristic cohomology class (e)(TS^2) of the Tangent bundle TS^2 + (Characteristic cohomology class (e)(TS^2) of the Tangent bundle TS^2 over the 2-sphere S^2 of radius 1 smoothly embedded in the Euclidean - space E^3] + space E^3,) """ Element = CharacteristicCohomologyClassRingElement @@ -1043,6 +1043,7 @@ def _repr_(self): repr = f'Algebra of characteristic cohomology classes of the {vbundle}' return repr + # ***************************************************************************** # ALGORITHMS # ***************************************************************************** @@ -1103,6 +1104,7 @@ def multiplicative_sequence(q, n=None): for p in Partitions(k)}) return Sym.e()(mon_pol) + def additive_sequence(q, k, n=None): r""" Turn the polynomial ``q`` into its additive sequence. @@ -1845,4 +1847,4 @@ def get_gen_pow(self, nab, i, n): return nab._domain._one_scalar_field # no computation necessary if i == 0: return fast_wedge_power(EulerAlgorithm().get(nab)[0], n) - return fast_wedge_power(PontryaginAlgorithm().get(nab)[i-1], n) + return fast_wedge_power(PontryaginAlgorithm().get(nab)[i - 1], n) diff --git a/src/sage/manifolds/differentiable/diff_form_module.py b/src/sage/manifolds/differentiable/diff_form_module.py index 0aa86ce31d5..37feeaa7650 100644 --- a/src/sage/manifolds/differentiable/diff_form_module.py +++ b/src/sage/manifolds/differentiable/diff_form_module.py @@ -12,11 +12,14 @@ (in practice, not parallelizable) differentiable manifold `M` - :class:`DiffFormFreeModule` for differential forms with values on a parallelizable manifold `M` + (the subclass :class:`VectorFieldDualFreeModule` implements the special + case of differential 1-forms on a parallelizable manifold `M`) AUTHORS: - Eric Gourgoulhon (2015): initial version - Travis Scrimshaw (2016): review tweaks +- Matthias Koeppe (2022): :class:`VectorFieldDualFreeModule` REFERENCES: @@ -25,8 +28,10 @@ """ # ***************************************************************************** -# Copyright (C) 2015 Eric Gourgoulhon -# Copyright (C) 2016 Travis Scrimshaw +# Copyright (C) 2015-2021 Eric Gourgoulhon +# 2016 Travis Scrimshaw +# 2020 Michael Jung +# 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 @@ -575,6 +580,8 @@ class DiffFormFreeModule(ExtPowerDualFreeModule): is not parallelizable, the class :class:`DiffFormModule` must be used instead. + For the special case of 1-forms, use the class :class:`VectorFieldDualFreeModule`. + INPUT: - ``vector_field_module`` -- free module `\mathfrak{X}(U,\Phi)` of vector @@ -661,18 +668,12 @@ class DiffFormFreeModule(ExtPowerDualFreeModule): sage: L1 is XM.dual() True - Since any tensor field of type `(0,1)` is a 1-form, there is a coercion - map from the set `T^{(0,1)}(M)` of such tensors to `\Omega^1(M)`:: + Since any tensor field of type `(0,1)` is a 1-form, it is also equal to + the set `T^{(0,1)}(M)` of such tensors to `\Omega^1(M)`:: sage: T01 = M.tensor_field_module((0,1)) ; T01 - Free module T^(0,1)(M) of type-(0,1) tensors fields on the - 3-dimensional differentiable manifold M - sage: L1.has_coerce_map_from(T01) - True - - There is also a coercion map in the reverse direction:: - - sage: T01.has_coerce_map_from(L1) + Free module Omega^1(M) of 1-forms on the 3-dimensional differentiable manifold M + sage: L1 is T01 True For a degree `p \geq 2`, the coercion holds only in the direction @@ -686,26 +687,6 @@ class DiffFormFreeModule(ExtPowerDualFreeModule): sage: A.has_coerce_map_from(T02) False - The coercion map `T^{(0,1)}(M) \rightarrow \Omega^1(M)` in action:: - - sage: b = T01([-x,2,3*y], name='b'); b - Tensor field b of type (0,1) on the 3-dimensional differentiable - manifold M - sage: b.display() - b = -x dx + 2 dy + 3*y dz - sage: lb = L1(b) ; lb - 1-form b on the 3-dimensional differentiable manifold M - sage: lb.display() - b = -x dx + 2 dy + 3*y dz - - The coercion map `\Omega^1(M) \rightarrow T^{(0,1)}(M)` in action:: - - sage: tlb = T01(lb); tlb - Tensor field b of type (0,1) on - the 3-dimensional differentiable manifold M - sage: tlb == b - True - The coercion map `\Omega^2(M) \rightarrow T^{(0,2)}(M)` in action:: sage: T02 = M.tensor_field_module((0,2)) ; T02 @@ -905,3 +886,167 @@ def _repr_(self): description += "along the {} mapped into the {}".format( self._domain, self._ambient_domain) return description + + +class VectorFieldDualFreeModule(DiffFormFreeModule): + r""" + Free module of differential 1-forms along a differentiable manifold `U` + with values on a parallelizable manifold `M`. + + Given a differentiable manifold `U` and a differentiable map + `\Phi:\; U \rightarrow M` to a parallelizable manifold `M` of dimension + `n`, the set `\Omega^1(U, \Phi)` of 1-forms along `U` with values on `M` + is a free module of rank `n` over `C^k(U)`, the commutative + algebra of differentiable scalar fields on `U` (see + :class:`~sage.manifolds.differentiable.scalarfield_algebra.DiffScalarFieldAlgebra`). + The standard case of 1-forms *on* a differentiable manifold `M` + corresponds to `U = M` and `\Phi = \mathrm{Id}_M`. Other common cases are + `\Phi` being an immersion and `\Phi` being a curve in `M` (`U` is then an + open interval of `\RR`). + + .. NOTE:: + + This class implements `\Omega^1(U, \Phi)` in the case where `M` is + parallelizable; `\Omega^1(U, \Phi)` is then a *free* module. If `M` + is not parallelizable, the class :class:`DiffFormModule` must be used + instead. + + INPUT: + + - ``vector_field_module`` -- free module `\mathfrak{X}(U,\Phi)` of vector + fields along `U` associated with the map `\Phi: U \rightarrow V` + + EXAMPLES: + + Free module of 1-forms on a parallelizable 3-dimensional manifold:: + + sage: M = Manifold(3, 'M') + sage: X. = M.chart() + sage: XM = M.vector_field_module() ; XM + Free module X(M) of vector fields on the 3-dimensional differentiable + manifold M + sage: A = M.diff_form_module(1) ; A + Free module Omega^1(M) of 1-forms on the 3-dimensional differentiable manifold M + sage: latex(A) + \Omega^{1}\left(M\right) + + ``A`` is nothing but the dual of ``XM`` (the free module of vector fields on `M`) + and thus also equal to the 1st exterior + power of the dual, i.e. we have `\Omega^{1}(M) = \Lambda^1(\mathfrak{X}(M)^*) + = \mathfrak{X}(M)^*` (See + :class:`~sage.tensor.modules.ext_pow_free_module.ExtPowerDualFreeModule`):: + + sage: A is XM.dual_exterior_power(1) + True + + `\Omega^{1}(M)` is a module over the algebra `C^k(M)` of (differentiable) + scalar fields on `M`:: + + sage: A.category() + Category of finite dimensional modules over Algebra of differentiable + scalar fields on the 3-dimensional differentiable manifold M + sage: CM = M.scalar_field_algebra() ; CM + Algebra of differentiable scalar fields on the 3-dimensional + differentiable manifold M + sage: A in Modules(CM) + True + sage: A.base_ring() + Algebra of differentiable scalar fields on + the 3-dimensional differentiable manifold M + sage: A.base_module() + Free module X(M) of vector fields on + the 3-dimensional differentiable manifold M + sage: A.base_module() is XM + True + sage: A.rank() + 3 + + Elements can be constructed from `A`. In particular, ``0`` yields + the zero element of `A`:: + + sage: A(0) + 1-form zero on the 3-dimensional differentiable manifold M + sage: A(0) is A.zero() + True + + while non-zero elements are constructed by providing their components + in a given vector frame:: + + sage: comp = [3*x,-z,4] + sage: a = A(comp, frame=X.frame(), name='a') ; a + 1-form a on the 3-dimensional differentiable manifold M + sage: a.display() + a = 3*x dx - z dy + 4 dz + + An alternative is to construct the 1-form from an empty list of + components and to set the nonzero nonredundant components afterwards:: + + sage: a = A([], name='a') + sage: a[0] = 3*x # component in the manifold's default frame + sage: a[1] = -z + sage: a[2] = 4 + sage: a.display() + a = 3*x dx - z dy + 4 dz + + Since any tensor field of type `(0,1)` is a 1-form, there is a coercion + map from the set `T^{(0,1)}(M)` of such tensors to `\Omega^1(M)`:: + + sage: T01 = M.tensor_field_module((0,1)) ; T01 + Free module Omega^1(M) of 1-forms on the 3-dimensional differentiable manifold M + sage: A.has_coerce_map_from(T01) + True + + There is also a coercion map in the reverse direction:: + + sage: T01.has_coerce_map_from(A) + True + + The coercion map `T^{(0,1)}(M) \rightarrow \Omega^1(M)` in action:: + + sage: b = T01([-x,2,3*y], name='b'); b + 1-form b on the 3-dimensional differentiable manifold M + sage: b.display() + b = -x dx + 2 dy + 3*y dz + sage: lb = A(b) ; lb + 1-form b on the 3-dimensional differentiable manifold M + sage: lb.display() + b = -x dx + 2 dy + 3*y dz + + The coercion map `\Omega^1(M) \rightarrow T^{(0,1)}(M)` in action:: + + sage: tlb = T01(lb); tlb + 1-form b on the 3-dimensional differentiable manifold M + sage: tlb == b + True + """ + + def __init__(self, vector_field_module): + r""" + Construct a free module of differential 1-forms. + + TESTS:: + + sage: M = Manifold(3, 'M') + sage: X. = M.chart() + sage: A = M.vector_field_module().dual(); A + Free module Omega^1(M) of 1-forms on the 3-dimensional differentiable manifold M + sage: TestSuite(A).run() + + """ + DiffFormFreeModule.__init__(self, vector_field_module, 1) + + def tensor_type(self): + r""" + Return the tensor type of ``self``. + + EXAMPLES:: + + sage: M = Manifold(3, 'M') + sage: X. = M.chart() + sage: A = M.vector_field_module().dual(); A + Free module Omega^1(M) of 1-forms on the 3-dimensional differentiable manifold M + sage: A.tensor_type() + (0, 1) + + """ + return (0, 1) diff --git a/src/sage/manifolds/differentiable/examples/euclidean.py b/src/sage/manifolds/differentiable/examples/euclidean.py index 8b3cb23531d..9528d5a5f8a 100644 --- a/src/sage/manifolds/differentiable/examples/euclidean.py +++ b/src/sage/manifolds/differentiable/examples/euclidean.py @@ -717,7 +717,7 @@ def __classcall_private__(cls, n=None, name=None, latex_name=None, start_index=start_index, unique_tag=unique_tag) - return super(cls, EuclideanSpace).__classcall__(cls, + return super().__classcall__(cls, n, name=name, latex_name=latex_name, coordinates=coordinates, symbols=symbols, metric_name=metric_name, diff --git a/src/sage/manifolds/differentiable/examples/real_line.py b/src/sage/manifolds/differentiable/examples/real_line.py index 35636dc0bd7..10a44c7e149 100644 --- a/src/sage/manifolds/differentiable/examples/real_line.py +++ b/src/sage/manifolds/differentiable/examples/real_line.py @@ -320,10 +320,10 @@ def __classcall_private__(cls, lower, upper, ambient_interval=None, coordinate = None names = None start_index = 0 - return super(cls, OpenInterval).__classcall__(cls, lower, upper, - ambient_interval=ambient_interval, name=name, - latex_name=latex_name, coordinate=coordinate, - names=names, start_index=start_index) + return super().__classcall__(cls, lower, upper, + ambient_interval=ambient_interval, name=name, + latex_name=latex_name, coordinate=coordinate, + names=names, start_index=start_index) def __init__(self, lower, upper, ambient_interval=None, name=None, latex_name=None, @@ -495,9 +495,9 @@ def _element_constructor_(self, coords=None, chart=None, name=None, """ if coords in SR: coords = (coords,) - return super(OpenInterval, self)._element_constructor_(coords=coords, - chart=chart, name=name, latex_name=latex_name, - check_coords=check_coords) + return super()._element_constructor_(coords=coords, + chart=chart, name=name, latex_name=latex_name, + check_coords=check_coords) def _Hom_(self, other, category=None): r""" @@ -879,10 +879,10 @@ def __classcall__(cls, name=unicode_mathbbR, latex_name=r'\Bold{R}', True """ - return super(cls, RealLine).__classcall__(cls, name=name, - latex_name=latex_name, - coordinate=coordinate, - names=names, start_index=start_index) + return super().__classcall__(cls, name=name, + latex_name=latex_name, + coordinate=coordinate, + names=names, start_index=start_index) def __init__(self, name=unicode_mathbbR, latex_name=r'\Bold{R}', coordinate=None, names=None, start_index=0): diff --git a/src/sage/manifolds/differentiable/examples/sphere.py b/src/sage/manifolds/differentiable/examples/sphere.py index 1183448251a..4ccc6f7440b 100644 --- a/src/sage/manifolds/differentiable/examples/sphere.py +++ b/src/sage/manifolds/differentiable/examples/sphere.py @@ -320,14 +320,14 @@ def __classcall_private__(cls, n=None, radius=1, ambient_space=None, from sage.misc.prandom import getrandbits from time import time if unique_tag is None: - unique_tag = getrandbits(128)*time() - - return super(cls, Sphere).__classcall__(cls, n, radius=radius, - ambient_space=ambient_space, - center=center, - name=name, latex_name=latex_name, - coordinates=coordinates, names=names, - unique_tag=unique_tag) + unique_tag = getrandbits(128) * time() + + return super().__classcall__(cls, n, radius=radius, + ambient_space=ambient_space, + center=center, + name=name, latex_name=latex_name, + coordinates=coordinates, names=names, + unique_tag=unique_tag) def __init__(self, n, radius=1, ambient_space=None, center=None, name=None, latex_name=None, coordinates='spherical', names=None, 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..d650d4eb712 100644 --- a/src/sage/manifolds/differentiable/manifold.py +++ b/src/sage/manifolds/differentiable/manifold.py @@ -1438,9 +1438,9 @@ def tensor_field_module(self, tensor_type, dest_map=None): Free module T^(2,1)(U) of type-(2,1) tensors fields on the Open subset U of the 3-dimensional differentiable manifold M sage: TU.category() - Category of finite dimensional modules over Algebra of - differentiable scalar fields on the Open subset U of the - 3-dimensional differentiable manifold M + Category of tensor products of finite dimensional modules + over Algebra of differentiable scalar fields + on the Open subset U of the 3-dimensional differentiable manifold M sage: TU.base_ring() Algebra of differentiable scalar fields on the Open subset U of the 3-dimensional differentiable manifold M @@ -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 f2e6478d07c..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 " + @@ -2299,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 " + @@ -2779,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 " + @@ -3017,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 36b68c7b5c5..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 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 d68264a472c..cbc73f9e520 100644 --- a/src/sage/manifolds/differentiable/tensorfield_module.py +++ b/src/sage/manifolds/differentiable/tensorfield_module.py @@ -652,8 +652,8 @@ class TensorFieldFreeModule(TensorFreeModule): `T^{(2,0)}(\RR^3)` is a module over the algebra `C^k(\RR^3)`:: sage: T20.category() - Category of finite dimensional modules over Algebra of differentiable - scalar fields on the 3-dimensional differentiable manifold R^3 + Category of tensor products of finite dimensional modules over + Algebra of differentiable scalar fields on the 3-dimensional differentiable manifold R^3 sage: T20.base_ring() is M.scalar_field_algebra() True diff --git a/src/sage/manifolds/differentiable/vectorfield_module.py b/src/sage/manifolds/differentiable/vectorfield_module.py index cf3f1dc687d..580eec814ab 100644 --- a/src/sage/manifolds/differentiable/vectorfield_module.py +++ b/src/sage/manifolds/differentiable/vectorfield_module.py @@ -28,9 +28,14 @@ """ # ****************************************************************************** -# Copyright (C) 2015 Eric Gourgoulhon -# Copyright (C) 2015 Michal Bejger -# Copyright (C) 2016 Travis Scrimshaw +# Copyright (C) 2015-2021 Eric Gourgoulhon +# 2015 Michal Bejger +# 2016 Travis Scrimshaw +# 2018 Florentin Jaffredo +# 2019 Hans Fotsing Tetsing +# 2020 Michael Jung +# 2020-2022 Matthias Koeppe +# 2021-2022 Tobias Diez # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of @@ -501,7 +506,7 @@ def destination_map(self): """ return self._dest_map - def tensor_module(self, k, l): + def tensor_module(self, k, l, *, sym=None, antisym=None): r""" Return the module of type-`(k,l)` tensors on ``self``. @@ -547,11 +552,16 @@ def tensor_module(self, k, l): for more examples and documentation. """ - from sage.manifolds.differentiable.tensorfield_module import \ + if sym or antisym: + raise NotImplementedError + try: + return self._tensor_modules[(k,l)] + except KeyError: + from sage.manifolds.differentiable.tensorfield_module import \ TensorFieldModule - if (k,l) not in self._tensor_modules: - self._tensor_modules[(k,l)] = TensorFieldModule(self, (k,l)) - return self._tensor_modules[(k,l)] + T = TensorFieldModule(self, (k,l)) + self._tensor_modules[(k,l)] = T + return T def exterior_power(self, p): r""" @@ -600,13 +610,17 @@ def exterior_power(self, p): for more examples and documentation. """ - from sage.manifolds.differentiable.multivector_module import \ + try: + return self._exterior_powers[p] + except KeyError: + if p == 0: + L = self._ring + else: + from sage.manifolds.differentiable.multivector_module import \ MultivectorModule - if p == 0: - return self._ring - if p not in self._exterior_powers: - self._exterior_powers[p] = MultivectorModule(self, p) - return self._exterior_powers[p] + L = MultivectorModule(self, p) + self._exterior_powers[p] = L + return L def dual_exterior_power(self, p): r""" @@ -654,13 +668,17 @@ def dual_exterior_power(self, p): for more examples and documentation. """ - from sage.manifolds.differentiable.diff_form_module import \ + try: + return self._dual_exterior_powers[p] + except KeyError: + if p == 0: + L = self._ring + else: + from sage.manifolds.differentiable.diff_form_module import \ DiffFormModule - if p == 0: - return self._ring - if p not in self._dual_exterior_powers: - self._dual_exterior_powers[p] = DiffFormModule(self, p) - return self._dual_exterior_powers[p] + L = DiffFormModule(self, p) + self._dual_exterior_powers[p] = L + return L def dual(self): r""" @@ -773,7 +791,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 +804,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 +820,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): @@ -1729,7 +1733,7 @@ def destination_map(self) -> DiffMap: """ return self._dest_map - 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``. @@ -1778,11 +1782,15 @@ def tensor_module(self, k, l): for more examples and documentation. """ + if sym or antisym: + raise NotImplementedError try: return self._tensor_modules[(k,l)] except KeyError: if (k, l) == (1, 0): T = self + elif (k, l) == (0, 1): + T = self.dual() else: from sage.manifolds.differentiable.tensorfield_module import \ TensorFieldFreeModule @@ -1882,8 +1890,7 @@ def dual_exterior_power(self, p): Free module Omega^2(M) of 2-forms on the 2-dimensional differentiable manifold M sage: XM.dual_exterior_power(1) - Free module Omega^1(M) of 1-forms on the 2-dimensional - differentiable manifold M + Free module Omega^1(M) of 1-forms on the 2-dimensional differentiable manifold M sage: XM.dual_exterior_power(1) is XM.dual() True sage: XM.dual_exterior_power(0) @@ -1903,6 +1910,10 @@ def dual_exterior_power(self, p): except KeyError: if p == 0: L = self._ring + elif p == 1: + from sage.manifolds.differentiable.diff_form_module import \ + VectorFieldDualFreeModule + L = VectorFieldDualFreeModule(self) else: from sage.manifolds.differentiable.diff_form_module import \ DiffFormFreeModule @@ -2024,7 +2035,7 @@ def basis(self, symbol=None, latex_symbol=None, from_frame=None, symbol_dual=symbol_dual, latex_symbol_dual=latex_symbol_dual) - def tensor(self, tensor_type, name=None, latex_name=None, sym=None, + def _tensor(self, tensor_type, name=None, latex_name=None, sym=None, antisym=None, specific_type=None): r""" Construct a tensor on ``self``. @@ -2088,6 +2099,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 +2112,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 +2128,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 6df9c54c0d1..8f8c26c0c61 100644 --- a/src/sage/manifolds/differentiable/vectorframe.py +++ b/src/sage/manifolds/differentiable/vectorframe.py @@ -452,10 +452,10 @@ def set_name(self, symbol, latex_symbol=None, indices=None, \left(M, \left(e^{\xi},e^{\zeta}\right)\right) """ - super(CoFrame, self).set_name(symbol, latex_symbol=latex_symbol, - indices=indices, - latex_indices=latex_indices, - index_position=index_position) + super().set_name(symbol, latex_symbol=latex_symbol, + indices=indices, + latex_indices=latex_indices, + index_position=index_position) if include_domain: # Redefinition of the name and the LaTeX name to include the domain self._name = "({}, {})".format(self._domain._name, self._name) @@ -672,12 +672,12 @@ def __classcall_private__(cls, vector_field_module, symbol, symbol_dual = tuple(symbol_dual) if isinstance(latex_symbol_dual, list): latex_symbol_dual = tuple(latex_symbol_dual) - return super(VectorFrame, cls).__classcall__(cls, vector_field_module, - symbol, latex_symbol=latex_symbol, - from_frame=from_frame, indices=indices, - latex_indices=latex_indices, - symbol_dual=symbol_dual, - latex_symbol_dual=latex_symbol_dual) + return super().__classcall__(cls, vector_field_module, + symbol, latex_symbol=latex_symbol, + from_frame=from_frame, indices=indices, + latex_indices=latex_indices, + symbol_dual=symbol_dual, + latex_symbol_dual=latex_symbol_dual) def __init__(self, vector_field_module, symbol, latex_symbol=None, from_frame=None, indices=None, latex_indices=None, @@ -1570,10 +1570,10 @@ def set_name(self, symbol, latex_symbol=None, indices=None, \left(M, \left(E_{\alpha},E_{\beta}\right)\right) """ - super(VectorFrame, self).set_name(symbol, latex_symbol=latex_symbol, - indices=indices, - latex_indices=latex_indices, - index_position=index_position) + super().set_name(symbol, latex_symbol=latex_symbol, + indices=indices, + latex_indices=latex_indices, + index_position=index_position) if include_domain: # Redefinition of the name and the LaTeX name to include the domain self._name = "({}, {})".format(self._domain._name, self._name) diff --git a/src/sage/manifolds/local_frame.py b/src/sage/manifolds/local_frame.py index 4d7db5c7d55..fffe607efda 100644 --- a/src/sage/manifolds/local_frame.py +++ b/src/sage/manifolds/local_frame.py @@ -162,20 +162,21 @@ """ -#****************************************************************************** +# ***************************************************************************** # Copyright (C) 2013-2018 Eric Gourgoulhon # Copyright (C) 2019 Michael Jung # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of # the License, or (at your option) any later version. -# http://www.gnu.org/licenses/ -#****************************************************************************** +# https://www.gnu.org/licenses/ +# ***************************************************************************** from sage.tensor.modules.free_module_basis import (FreeModuleBasis, FreeModuleCoBasis) from sage.tensor.modules.finite_rank_free_module import FiniteRankFreeModule + class LocalCoFrame(FreeModuleCoBasis): r""" Local coframe on a vector bundle. @@ -398,10 +399,10 @@ def set_name(self, symbol, latex_symbol=None, indices=None, \left(E|_{M}, \left(e^{\xi},e^{\zeta}\right)\right) """ - super(LocalCoFrame, self).set_name(symbol, latex_symbol=latex_symbol, - indices=indices, - latex_indices=latex_indices, - index_position=index_position) + super().set_name(symbol, latex_symbol=latex_symbol, + indices=indices, + latex_indices=latex_indices, + index_position=index_position) if include_domain: # Redefinition of the name and the LaTeX name to include the domain self._name = "({}|_{}, {})".format(self._vbundle._name, @@ -597,12 +598,12 @@ def __classcall_private__(cls, section_module, symbol, symbol_dual = tuple(symbol_dual) if isinstance(latex_symbol_dual, list): latex_symbol_dual = tuple(latex_symbol_dual) - return super(LocalFrame, cls).__classcall__(cls, section_module, - symbol, latex_symbol=latex_symbol, - indices=indices, - latex_indices=latex_indices, - symbol_dual=symbol_dual, - latex_symbol_dual=latex_symbol_dual) + return super().__classcall__(cls, section_module, + symbol, latex_symbol=latex_symbol, + indices=indices, + latex_indices=latex_indices, + symbol_dual=symbol_dual, + latex_symbol_dual=latex_symbol_dual) def __init__(self, section_module, symbol, latex_symbol=None, indices=None, latex_indices=None, symbol_dual=None, latex_symbol_dual=None): @@ -1239,10 +1240,10 @@ def set_name(self, symbol, latex_symbol=None, indices=None, \left(E|_{M}, \left(E_{\alpha},E_{\beta}\right)\right) """ - super(LocalFrame, self).set_name(symbol, latex_symbol=latex_symbol, - indices=indices, - latex_indices=latex_indices, - index_position=index_position) + super().set_name(symbol, latex_symbol=latex_symbol, + indices=indices, + latex_indices=latex_indices, + index_position=index_position) if include_domain: # Redefinition of the name and the LaTeX name to include the domain self._name = "({}|_{}, {})".format(self._vbundle._name, diff --git a/src/sage/manifolds/topological_submanifold.py b/src/sage/manifolds/topological_submanifold.py index 828b97703f5..314a6a16a1b 100644 --- a/src/sage/manifolds/topological_submanifold.py +++ b/src/sage/manifolds/topological_submanifold.py @@ -294,8 +294,7 @@ def open_subset(self, name, latex_name=None, coord_def={}, supersets=None): OUTPUT: - - the open subset, as an instance of - :class:`~sage.manifolds.manifold.topological_submanifold.TopologicalSubmanifold` + - the open subset, as an instance of :class:`TopologicalSubmanifold` EXAMPLES:: diff --git a/src/sage/manifolds/utilities.py b/src/sage/manifolds/utilities.py index 75a07519b0d..de83d63326f 100644 --- a/src/sage/manifolds/utilities.py +++ b/src/sage/manifolds/utilities.py @@ -204,7 +204,8 @@ def arithmetic(self, ex, operator): simpl = SR(1)/simpl return simpl # If operator is not a square root, we default to ExpressionTreeWalker: - return super(SimplifySqrtReal, self).arithmetic(ex, operator) + return super().arithmetic(ex, operator) + class SimplifyAbsTrig(ExpressionTreeWalker): r""" @@ -340,7 +341,7 @@ def composition(self, ex, operator): ex = -cos(x) return ex # If no pattern is found, we default to ExpressionTreeWalker: - return super(SimplifyAbsTrig, self).composition(ex, operator) + return super().composition(ex, operator) def simplify_sqrt_real(expr): diff --git a/src/sage/matrix/benchmark.py b/src/sage/matrix/benchmark.py index 86d09a63a49..811a1cbfc98 100644 --- a/src/sage/matrix/benchmark.py +++ b/src/sage/matrix/benchmark.py @@ -1229,12 +1229,10 @@ def nullspace_RDF(n=300, min=0, max=10, system='sage'): t := Cputime(); K := Kernel(A); s := Cputime(t); -"""%(n,min,max) +""" % (n, min, max) if verbose: print(code) magma.eval(code) return float(magma.eval('s')) else: - raise ValueError('unknown system "%s"'%system) - - + raise ValueError('unknown system "%s"' % system) 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/matrix_integer_dense_saturation.py b/src/sage/matrix/matrix_integer_dense_saturation.py index 9ac854ea2d1..01621f6844f 100644 --- a/src/sage/matrix/matrix_integer_dense_saturation.py +++ b/src/sage/matrix/matrix_integer_dense_saturation.py @@ -339,13 +339,11 @@ def index_in_saturation(A, proof=True): """ r = A.rank() if r == 0: - return ZZ(1) + return ZZ.one() if r < A.nrows(): A = A.hermite_form(proof=proof, include_zero_rows=False) if A.is_square(): return abs(A.determinant(proof=proof)) A = A.transpose() - A = A.hermite_form(proof=proof,include_zero_rows=False) + A = A.hermite_form(proof=proof, include_zero_rows=False) return abs(A.determinant(proof=proof)) - - diff --git a/src/sage/matrix/matrix_space.py b/src/sage/matrix/matrix_space.py index 26f058d3aeb..cfb8ce85931 100644 --- a/src/sage/matrix/matrix_space.py +++ b/src/sage/matrix/matrix_space.py @@ -49,7 +49,7 @@ from sage.misc.lazy_attribute import lazy_attribute from sage.misc.superseded import deprecated_function_alias - +from sage.misc.persist import register_unpickle_override from sage.categories.rings import Rings from sage.categories.fields import Fields from sage.categories.enumerated_sets import EnumeratedSets @@ -406,7 +406,6 @@ def get_matrix_class(R, nrows, ncols, sparse, implementation): return Matrix_generic_sparse - class MatrixSpace(UniqueRepresentation, Parent): """ The space of matrices of given size and base ring @@ -544,10 +543,10 @@ def __classcall__(cls, base_ring, nrows, ncols=None, sparse=False, implementatio sage: class MyMatrixSpace(MatrixSpace): ....: @staticmethod ....: def __classcall__(cls, base_ring, nrows, ncols=None, my_option=True, sparse=False, implementation=None): - ....: return super(MyMatrixSpace, cls).__classcall__(cls, base_ring, nrows, ncols=ncols, my_option=my_option, sparse=sparse, implementation=implementation) + ....: return super().__classcall__(cls, base_ring, nrows, ncols=ncols, my_option=my_option, sparse=sparse, implementation=implementation) ....: ....: def __init__(self, base_ring, nrows, ncols, sparse, implementation, my_option=True): - ....: super(MyMatrixSpace, self).__init__(base_ring, nrows, ncols, sparse, implementation) + ....: super().__init__(base_ring, nrows, ncols, sparse, implementation) ....: self._my_option = my_option sage: MS1 = MyMatrixSpace(ZZ, 2) @@ -558,7 +557,7 @@ def __classcall__(cls, base_ring, nrows, ncols=None, sparse=False, implementatio False """ if base_ring not in _Rings: - raise TypeError("base_ring (=%s) must be a ring"%base_ring) + raise TypeError("base_ring (=%s) must be a ring" % base_ring) nrows = int(nrows) if ncols is None: ncols = nrows @@ -574,8 +573,8 @@ def __classcall__(cls, base_ring, nrows, ncols=None, sparse=False, implementatio raise OverflowError("number of rows and columns may be at most %s" % sys.maxsize) matrix_cls = get_matrix_class(base_ring, nrows, ncols, sparse, implementation) - return super(MatrixSpace, cls).__classcall__( - cls, base_ring, nrows, ncols, sparse, matrix_cls, **kwds) + return super().__classcall__(cls, base_ring, nrows, + ncols, sparse, matrix_cls, **kwds) def __init__(self, base_ring, nrows, ncols, sparse, implementation): r""" @@ -1261,8 +1260,8 @@ def _repr_(self): s = "sparse" else: s = "dense" - s = "Full MatrixSpace of %s by %s %s matrices over %s"%( - self.__nrows, self.__ncols, s, self.base_ring()) + s = "Full MatrixSpace of %s by %s %s matrices over %s" % ( + self.__nrows, self.__ncols, s, self.base_ring()) if not self._has_default_implementation(): s += " (using {})".format(self.Element.__name__) @@ -1283,7 +1282,7 @@ def _repr_option(self, key): """ if key == 'element_ascii_art': return self.__nrows > 1 - return super(MatrixSpace, self)._repr_option(key) + return super()._repr_option(key) def _latex_(self): r""" @@ -1295,8 +1294,8 @@ def _latex_(self): sage: latex(MS3) \mathrm{Mat}_{6\times 6}(\Bold{Q}) """ - return "\\mathrm{Mat}_{%s\\times %s}(%s)"%(self.nrows(), self.ncols(), - latex.latex(self.base_ring())) + return "\\mathrm{Mat}_{%s\\times %s}(%s)" % (self.nrows(), self.ncols(), + latex.latex(self.base_ring())) def __len__(self): """ @@ -1504,14 +1503,14 @@ def __iter__(self): ... NotImplementedError: len() of an infinite set """ - #Make sure that we can iterate over the base ring + # Make sure that we can iterate over the base ring base_ring = self.base_ring() base_iter = iter(base_ring) - number_of_entries = (self.__nrows*self.__ncols) + number_of_entries = (self.__nrows * self.__ncols) - #If the number of entries is zero, then just - #yield the empty matrix in that case and return + # If the number of entries is zero, then just + # yield the empty matrix in that case and return if number_of_entries == 0: yield self(0) return @@ -1519,11 +1518,11 @@ def __iter__(self): import sage.combinat.integer_vector if not base_ring.is_finite(): - #When the base ring is not finite, then we should go - #through and yield the matrices by "weight", which is - #the total number of iterations that need to be done - #on the base ring to reach the matrix. - base_elements = [ next(base_iter) ] + # When the base ring is not finite, then we should go + # through and yield the matrices by "weight", which is + # the total number of iterations that need to be done + # on the base ring to reach the matrix. + base_elements = [next(base_iter)] weight = 0 while True: for iv in sage.combinat.integer_vector.IntegerVectors(weight, number_of_entries): @@ -1579,7 +1578,7 @@ def __getitem__(self, x): """ if isinstance(x, (integer.Integer, int)): return self.list()[x] - return super(MatrixSpace, self).__getitem__(x) + return super().__getitem__(x) def basis(self): """ @@ -1639,7 +1638,6 @@ def dims(self): """ return (self.__nrows, self.__ncols) - def submodule(self, gens, check=True, already_echelonized=False, unitriangular=False, support_order=None, category=None, *args, **opts): @@ -1733,6 +1731,7 @@ def submodule(self, gens, check=True, already_echelonized=False, category=category, *args, **opts) from sage.misc.cachefunc import cached_method + @cached_method def identity_matrix(self): """ @@ -1903,7 +1902,7 @@ def gen(self, n): r = n // self.__ncols c = n - (r * self.__ncols) z = self.zero_matrix().__copy__() - z[r,c] = 1 + z[r, c] = 1 return z @cached_method @@ -2208,10 +2207,10 @@ def random_element(self, density=None, *args, **kwds): """ Z = self.zero_matrix().__copy__() if density is None: - Z.randomize(density=float(1), nonzero=kwds.pop('nonzero', False), \ + Z.randomize(density=float(1), nonzero=kwds.pop('nonzero', False), *args, **kwds) else: - Z.randomize(density=density, nonzero=kwds.pop('nonzero', True), \ + Z.randomize(density=density, nonzero=kwds.pop('nonzero', True), *args, **kwds) return Z @@ -2328,10 +2327,8 @@ def _magma_init_(self, magma): """ K = magma(self.base_ring()) if self.__nrows == self.__ncols: - s = 'MatrixAlgebra(%s,%s)'%(K.name(), self.__nrows) - else: - s = 'RMatrixSpace(%s,%s,%s)'%(K.name(), self.__nrows, self.__ncols) - return s + return 'MatrixAlgebra(%s,%s)' % (K.name(), self.__nrows) + return 'RMatrixSpace(%s,%s,%s)' % (K.name(), self.__nrows, self.__ncols) def _polymake_init_(self): r""" @@ -2384,6 +2381,7 @@ def _random_nonzero_element(self, *args, **kwds): rand_matrix = self.random_element(*args, **kwds) return rand_matrix + def dict_to_list(entries, nrows, ncols): r""" Given a dictionary of coordinate tuples, return the list given by @@ -2468,7 +2466,7 @@ def _test_trivial_matrices_inverse(ring, sparse=True, implementation=None, check """ # Check that the empty 0x0 matrix is it's own inverse with det=1. ms00 = MatrixSpace(ring, 0, 0, sparse=sparse) - m00 = ms00(0) + m00 = ms00(0) assert(m00.determinant() == ring(1)) assert(m00.is_invertible()) assert(m00.inverse() == m00) @@ -2479,7 +2477,7 @@ def _test_trivial_matrices_inverse(ring, sparse=True, implementation=None, check # computing the determinant raise the proper exception. for ms0 in [MatrixSpace(ring, 0, 3, sparse=sparse), MatrixSpace(ring, 3, 0, sparse=sparse)]: - mn0 = ms0(0) + mn0 = ms0(0) assert(not mn0.is_invertible()) try: d = mn0.determinant() @@ -2499,22 +2497,22 @@ def _test_trivial_matrices_inverse(ring, sparse=True, implementation=None, check # Check that the null 1x1 matrix is not invertible and that det=0 ms1 = MatrixSpace(ring, 1, 1, sparse=sparse) - m0 = ms1(0) + m0 = ms1(0) assert(not m0.is_invertible()) assert(m0.determinant() == ring(0)) try: m0.inverse() res = False except (ZeroDivisionError, RuntimeError): - #FIXME: Make pynac throw a ZeroDivisionError on division by - #zero instead of a runtime Error + # FIXME: Make pynac throw a ZeroDivisionError on division by + # zero instead of a runtime Error res = True assert(res) if checkrank: assert(m0.rank() == 0) # Check that the identity 1x1 matrix is its own inverse with det=1 - m1 = ms1(1) + m1 = ms1(1) assert(m1.is_invertible()) assert(m1.determinant() == ring(1)) inv = m1.inverse() @@ -2529,10 +2527,13 @@ def _test_trivial_matrices_inverse(ring, sparse=True, implementation=None, check # Fix unpickling Matrix_modn_dense and Matrix_integer_2x2 lazy_import('sage.matrix.matrix_modn_dense_double', 'Matrix_modn_dense_double') lazy_import('sage.matrix.matrix_integer_dense', 'Matrix_integer_dense') -from sage.misc.persist import register_unpickle_override + + def _MatrixSpace_ZZ_2x2(): from sage.rings.integer_ring import ZZ - return MatrixSpace(ZZ,2) + return MatrixSpace(ZZ, 2) + + register_unpickle_override('sage.matrix.matrix_modn_dense', 'Matrix_modn_dense', Matrix_modn_dense_double) register_unpickle_override('sage.matrix.matrix_integer_2x2', diff --git a/src/sage/matrix/operation_table.py b/src/sage/matrix/operation_table.py index ff22a20d7c1..13f0a88822a 100644 --- a/src/sage/matrix/operation_table.py +++ b/src/sage/matrix/operation_table.py @@ -4,19 +4,19 @@ This module implements general operation tables, which are very matrix-like. """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2010 Rob Beezer # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** - +# https://www.gnu.org/licenses/ +# **************************************************************************** from sage.structure.sage_object import SageObject + class OperationTable(SageObject): r""" An object that represents a binary operation as a table. diff --git a/src/sage/matroids/basis_exchange_matroid.pyx b/src/sage/matroids/basis_exchange_matroid.pyx index 7b72cd245df..5679814d32b 100644 --- a/src/sage/matroids/basis_exchange_matroid.pyx +++ b/src/sage/matroids/basis_exchange_matroid.pyx @@ -2115,7 +2115,7 @@ cdef class BasisExchangeMatroid(Matroid): return EQ[0] cpdef _is_isomorphism(self, other, morphism): - """ + r""" Version of is_isomorphism() that does no type checking. INPUT: diff --git a/src/sage/matroids/basis_matroid.pyx b/src/sage/matroids/basis_matroid.pyx index b8fd1cdc342..ff70add1ce2 100644 --- a/src/sage/matroids/basis_matroid.pyx +++ b/src/sage/matroids/basis_matroid.pyx @@ -1280,8 +1280,9 @@ cdef long set_to_index(bitset_t S): s = bitset_next(S, s + 1) return index + cdef index_to_set(bitset_t S, long index, long k, long n): - """ + r""" Compute the k-subset of `\{0, ..., n-1\}` of rank index """ bitset_clear(S) diff --git a/src/sage/matroids/extension.pyx b/src/sage/matroids/extension.pyx index f4f9d44516b..0408e60f86e 100644 --- a/src/sage/matroids/extension.pyx +++ b/src/sage/matroids/extension.pyx @@ -248,7 +248,7 @@ cdef class LinearSubclassesIter: cdef class LinearSubclasses: - """ + r""" An iterable set of linear subclasses of a matroid. Enumerate linear subclasses of a given matroid. A *linear subclass* is a @@ -412,7 +412,7 @@ cdef class LinearSubclasses: cdef class MatroidExtensions(LinearSubclasses): - """ + r""" An iterable set of single-element extensions of a given matroid. INPUT: diff --git a/src/sage/matroids/linear_matroid.pyx b/src/sage/matroids/linear_matroid.pyx index 917017f6afe..0c9b0a1baff 100644 --- a/src/sage/matroids/linear_matroid.pyx +++ b/src/sage/matroids/linear_matroid.pyx @@ -761,7 +761,7 @@ cdef class LinearMatroid(BasisExchangeMatroid): return {e: R[self._idx[e]] for e in self.groundset()} cpdef LeanMatrix _reduced_representation(self, B=None): - """ + r""" Return a reduced representation of the matroid, i.e. a matrix `R` such that `[I\ \ R]` represents the matroid. @@ -800,7 +800,7 @@ cdef class LinearMatroid(BasisExchangeMatroid): # (field) isomorphism cpdef bint _is_field_isomorphism(self, LinearMatroid other, morphism): # not safe if self == other - """ + r""" Version of :meth:`` that does no type checking. @@ -966,7 +966,7 @@ cdef class LinearMatroid(BasisExchangeMatroid): return self._is_field_isomorphism(other, morphism) cpdef is_field_isomorphism(self, other, morphism): - """ + r""" Test if a provided morphism induces a bijection between represented matroids. @@ -1322,7 +1322,7 @@ cdef class LinearMatroid(BasisExchangeMatroid): return type(self)(reduced_matrix=M, groundset=rows + cols) cpdef dual(self): - """ + r""" Return the dual of the matroid. Let `M` be a matroid with ground set `E`. If `B` is the set of bases @@ -1354,7 +1354,7 @@ cdef class LinearMatroid(BasisExchangeMatroid): return type(self)(reduced_matrix=R, groundset=cols + rows) cpdef has_line_minor(self, k, hyperlines=None, certificate=False): - """ + r""" Test if the matroid has a `U_{2, k}`-minor. The matroid `U_{2, k}` is a matroid on `k` elements in which every @@ -3130,7 +3130,7 @@ cdef class BinaryMatroid(LinearMatroid): self._one = GF2_one cpdef base_ring(self): - """ + r""" Return the base ring of the matrix representing the matroid, in this case `\GF{2}`. @@ -3308,7 +3308,7 @@ cdef class BinaryMatroid(LinearMatroid): return self._A.copy() # Deprecated Sage matrix operation cpdef LeanMatrix _reduced_representation(self, B=None): - """ + r""" Return a reduced representation of the matroid, i.e. a matrix `R` such that `[I\ \ R]` represents the matroid. @@ -4197,7 +4197,7 @@ cdef class TernaryMatroid(LinearMatroid): self._two = GF3_minus_one cpdef base_ring(self): - """ + r""" Return the base ring of the matrix representing the matroid, in this case `\GF{3}`. @@ -4381,7 +4381,7 @@ cdef class TernaryMatroid(LinearMatroid): return self._A.copy() # Deprecated Sage matrix operation cpdef LeanMatrix _reduced_representation(self, B=None): - """ + r""" Return a reduced representation of the matroid, i.e. a matrix `R` such that `[I\ \ R]` represents the matroid. @@ -4563,7 +4563,7 @@ cdef class TernaryMatroid(LinearMatroid): return self._t_invariant cpdef bicycle_dimension(self): - """ + r""" Return the bicycle dimension of the ternary matroid. The bicycle dimension of a linear subspace `V` is @@ -5096,7 +5096,7 @@ cdef class QuaternaryMatroid(LinearMatroid): self._x_one = (self._A)._x_one cpdef base_ring(self): - """ + r""" Return the base ring of the matrix representing the matroid, in this case `\GF{4}`. @@ -5273,7 +5273,7 @@ cdef class QuaternaryMatroid(LinearMatroid): return self._A.copy() # Deprecated Sage matrix operation cpdef LeanMatrix _reduced_representation(self, B=None): - """ + r""" Return a reduced representation of the matroid, i.e. a matrix `R` such that `[I\ \ R]` represents the matroid. @@ -5413,7 +5413,7 @@ cdef class QuaternaryMatroid(LinearMatroid): return self._q_invariant cpdef bicycle_dimension(self): - """ + r""" Return the bicycle dimension of the quaternary matroid. The bicycle dimension of a linear subspace `V` is @@ -5810,7 +5810,7 @@ cdef class RegularMatroid(LinearMatroid): return P cpdef base_ring(self): - """ + r""" Return the base ring of the matrix representing the matroid, in this case `\ZZ`. @@ -6230,7 +6230,7 @@ cdef class RegularMatroid(LinearMatroid): return {e:idx[m[str(e)]] for e in self.groundset() if str(e) in m} cpdef has_line_minor(self, k, hyperlines=None, certificate=False): - """ + r""" Test if the matroid has a `U_{2, k}`-minor. The matroid `U_{2, k}` is a matroid on `k` elements in which every diff --git a/src/sage/matroids/matroid.pyx b/src/sage/matroids/matroid.pyx index 8263860cbec..7378d250e44 100644 --- a/src/sage/matroids/matroid.pyx +++ b/src/sage/matroids/matroid.pyx @@ -3150,7 +3150,7 @@ cdef class Matroid(SageObject): return Polyhedron(vertices) def independence_matroid_polytope(self): - """ + r""" Return the independence matroid polytope of ``self``. This is defined as the convex hull of the vertices @@ -3430,7 +3430,7 @@ cdef class Matroid(SageObject): return self._is_isomorphism(other, morphism) cpdef is_isomorphism(self, other, morphism): - """ + r""" Test if a provided morphism induces a matroid isomorphism. A *morphism* is a map from the groundset of ``self`` to the groundset @@ -3553,7 +3553,7 @@ cdef class Matroid(SageObject): return self._is_isomorphism(other, mf) cpdef _is_isomorphism(self, other, morphism): - """ + r""" Version of is_isomorphism() that does no type checking. INPUT: @@ -3942,7 +3942,7 @@ cdef class Matroid(SageObject): return self.delete(X) cpdef dual(self): - """ + r""" Return the dual of the matroid. Let `M` be a matroid with ground set `E`. If `B` is the set of bases @@ -4054,7 +4054,7 @@ cdef class Matroid(SageObject): return self._has_minor(N, certificate) cpdef has_line_minor(self, k, hyperlines=None, certificate=False): - """ + r""" Test if the matroid has a `U_{2, k}`-minor. The matroid `U_{2, k}` is a matroid on `k` elements in which every @@ -4125,7 +4125,7 @@ cdef class Matroid(SageObject): return self._has_line_minor(k, hyperlines, certificate) cpdef _has_line_minor(self, k, hyperlines, certificate=False): - """ + r""" Test if the matroid has a `U_{2, k}`-minor. Internal version that does no input checking. @@ -4313,7 +4313,7 @@ cdef class Matroid(SageObject): return self.dual().extension(element, subsets).dual() cpdef modular_cut(self, subsets): - """ + r""" Compute the modular cut generated by ``subsets``. A *modular cut* is a collection `C` of flats such that @@ -4403,7 +4403,7 @@ cdef class Matroid(SageObject): return final_list cpdef linear_subclasses(self, line_length=None, subsets=None): - """ + r""" Return an iterable set of linear subclasses of the matroid. A *linear subclass* is a set of hyperplanes (i.e. closed sets of rank @@ -4714,7 +4714,7 @@ cdef class Matroid(SageObject): return True cpdef is_cosimple(self): - """ + r""" Test if the matroid is cosimple. A matroid is *cosimple* if it contains no cocircuits of length 1 or 2. @@ -4791,7 +4791,7 @@ cdef class Matroid(SageObject): return components cpdef is_connected(self, certificate=False): - """ + r""" Test if the matroid is connected. A *separation* in a matroid is a partition `(X, Y)` of the @@ -7480,7 +7480,7 @@ cdef class Matroid(SageObject): return A cpdef tutte_polynomial(self, x=None, y=None): - """ + r""" Return the Tutte polynomial of the matroid. The *Tutte polynomial* of a matroid is the polynomial 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 5eed16a8565..8d2c2f080d5 100644 --- a/src/sage/misc/dev_tools.py +++ b/src/sage/misc/dev_tools.py @@ -525,12 +525,12 @@ def import_statements(*objects, **kwds): if kwds: raise TypeError("Unexpected '{}' argument".format(next(iter(kwds)))) - def expand_comma_separated_names(object): - if isinstance(object, str): - for w in object.strip('()').split(','): + def expand_comma_separated_names(obj): + if isinstance(obj, str): + for w in obj.strip('()').split(','): yield w.strip() else: - yield object + yield obj for obj in itertools.chain.from_iterable(expand_comma_separated_names(object) for object in objects): diff --git a/src/sage/misc/latex_standalone.py b/src/sage/misc/latex_standalone.py index 8ba1aa62211..cdfdd6b6cbb 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_import.pyx b/src/sage/misc/lazy_import.pyx index 35f09ba3063..2d4413cd1a3 100644 --- a/src/sage/misc/lazy_import.pyx +++ b/src/sage/misc/lazy_import.pyx @@ -68,7 +68,16 @@ from warnings import warn import inspect from . import sageinspect -from sage.features import FeatureNotPresentError + +# LazyImport.__repr__ uses try... except FeatureNotPresentError. +# This is defined in sage.features, provided by the distribution sagemath-environment. +try: + from sage.features import FeatureNotPresentError +except ImportError: + # If sage.features cannot be imported, then FeatureNotPresentError cannot + # be raised. In this case, use the empty tuple as the exception specification. + FeatureNotPresentError = () + cdef inline obj(x): if type(x) is LazyImport: @@ -252,6 +261,8 @@ cdef class LazyImport(): self._object = getattr(__import__(self._module, {}, {}, [self._name]), self._name) except ImportError as e: if self._feature: + # Avoid warnings from static type checkers by explicitly importing FeatureNotPresentError. + from sage.features import FeatureNotPresentError raise FeatureNotPresentError(self._feature, reason=f'Importing {self._name} failed: {e}') raise diff --git a/src/sage/misc/misc.py b/src/sage/misc/misc.py index c2c440daffb..bae968c42f3 100644 --- a/src/sage/misc/misc.py +++ b/src/sage/misc/misc.py @@ -548,7 +548,7 @@ def union(x, y=None): True """ from sage.misc.superseded import deprecation - deprecation(32096, "sage.misc.misc.union is deprecated, use 'list(set(x).union(y)' or a more suitable replacement") + deprecation(32096, "sage.misc.misc.union is deprecated, use 'list(set(x).union(y))' or a more suitable replacement") if y is None: return list(set(x)) return list(set(x).union(y)) diff --git a/src/sage/misc/sage_input.py b/src/sage/misc/sage_input.py index 24586170e4e..b49763dbf27 100644 --- a/src/sage/misc/sage_input.py +++ b/src/sage/misc/sage_input.py @@ -161,7 +161,7 @@ - Vincent Delecroix (2015-02): documentation formatting """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2008 Carl Witty # 2015 Vincent Delecroix <20100.delecroix@gmail.com> # @@ -169,8 +169,8 @@ # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** def sage_input(x, preparse=True, verify=False, allow_locals=False): @@ -240,7 +240,7 @@ def sage_input(x, preparse=True, verify=False, allow_locals=False): sage: sage_input((3, lambda x: x)) Traceback (most recent call last): ... - ValueError: Can't convert at 0x...> to sage_input form + ValueError: cannot convert at 0x...> to sage_input form But we can have :func:`sage_input` continue anyway, and return an input form for the rest of the expression, with ``allow_locals=True``.:: @@ -272,6 +272,7 @@ def sage_input(x, preparse=True, verify=False, allow_locals=False): return final_answer + class SageInputBuilder: r""" An instance of this class is passed to ``_sage_input_`` methods. @@ -427,7 +428,7 @@ def __call__(self, x, coerced=False): sage: sage_input(lambda x: x) Traceback (most recent call last): ... - ValueError: Can't convert at 0x...> to sage_input form + ValueError: cannot convert at 0x...> to sage_input form sage: sage_input(lambda x: x, allow_locals=True, verify=True) LOCALS: _sil1: at 0x...> @@ -519,7 +520,7 @@ def __call__(self, x, coerced=False): self._locals[loc_name] = x return SIE_literal_stringrep(self, loc_name) else: - raise ValueError("Can't convert {} to sage_input form".format(x)) + raise ValueError("cannot convert {} to sage_input form".format(x)) def preparse(self): r""" @@ -815,7 +816,7 @@ def dict(self, entries): """ if isinstance(entries, dict): entries = list(entries.items()) - entries = [(self(key),self(val)) for (key,val) in entries] + entries = [(self(key), self(val)) for (key, val) in entries] return SIE_dict(self, entries) def getattr(self, sie, attr): @@ -1077,7 +1078,7 @@ def prod(self, factors, simplify=False): neg = False break if isinstance(factor, SIE_literal_stringrep) and factor._sie_value == '1': - factors[i:i+1] = [] + factors[i:i + 1] = [] else: i += 1 if len(factors) == 0: @@ -1123,7 +1124,7 @@ def sum(self, terms, simplify=False): while i < len(terms): term = terms[i] if isinstance(term, SIE_literal_stringrep) and term._sie_value == '0': - terms[i:i+1] = [] + terms[i:i + 1] = [] else: i += 1 if len(terms) == 0: @@ -1174,6 +1175,7 @@ def result(self, e): else: return SageInputAnswer(sif._commands, sif.format(e, 0)) + # Python's precedence levels. Hand-transcribed from section 5.14 of # the Python 2 reference manual. In the Python 3 reference manual # this is section 6.16. @@ -1200,6 +1202,7 @@ def result(self, e): _prec_funcall = 40 _prec_atomic = 42 + class SageInputExpression(): r""" Subclasses of this class represent expressions for :func:`sage_input`. @@ -1687,6 +1690,7 @@ def _sie_format_statement(self, sif): result, prec = self._sie_format(sif) return result + class SIE_literal(SageInputExpression): r""" An abstract base class for ``literals`` (basically, values which @@ -1730,6 +1734,7 @@ def _sie_is_simple(self): # times in an expression, it might be better to do the replacement. return not self._sie_share + class SIE_literal_stringrep(SIE_literal): r""" Values in this class are leaves in a :func:`sage_input` expression @@ -1813,6 +1818,7 @@ def _sie_format(self, sif): """ return self._sie_value, _prec_atomic + class SIE_call(SageInputExpression): r""" This class represents a function-call node in a :func:`sage_input` @@ -2024,6 +2030,7 @@ def _sie_format(self, sif): key = sif.format(self._sie_key, 0) return '%s[%s]' % (coll, key), _prec_subscript + class SIE_getattr(SageInputExpression): r""" This class represents a getattr node in a :func:`sage_input` @@ -2222,6 +2229,7 @@ def _sie_format(self, sif): else: return '(%s)' % ', '.join(values), _prec_atomic + class SIE_dict(SageInputExpression): r""" This class represents a dict node in a :func:`sage_input` diff --git a/src/sage/misc/sageinspect.py b/src/sage/misc/sageinspect.py index be8f2527bde..fbca2defc20 100644 --- a/src/sage/misc/sageinspect.py +++ b/src/sage/misc/sageinspect.py @@ -1018,14 +1018,14 @@ def split_string(s, quot): if s[i] == '\\': escaped = not escaped continue - if not escaped and s[i:i+l] == quot: - return s[:i], s[i+l:] + if not escaped and s[i:i + l] == quot: + return s[:i], s[i + l:] escaped = False raise SyntaxError("EOF while scanning string literal") # 1. s is a triple-quoted string if s.startswith('"""'): a, b = split_string(s[3:], '"""') - return '"""'+a+'"""', b.strip() + return '"""' + a + '"""', b.strip() if s.startswith('r"""'): a, b = split_string(s[4:], '"""') return 'r"""'+a+'"""', b.strip() @@ -1313,7 +1313,7 @@ def _sage_getargspec_cython(source): name = None nb_stars = 0 else: - raise SyntaxError("varargs can't be defined twice") + raise SyntaxError("varargs cannot be defined twice") elif nb_stars == 2: if keywords is None: keywords = name @@ -1323,7 +1323,7 @@ def _sage_getargspec_cython(source): name = None nb_stars = 0 else: - raise SyntaxError("varargs can't be defined twice") + raise SyntaxError("varargs cannot be defined twice") else: raise SyntaxError("variable declaration comprises at most two '*'") else: diff --git a/src/sage/misc/table.py b/src/sage/misc/table.py index 8610f06df0d..cbd8b083a6b 100644 --- a/src/sage/misc/table.py +++ b/src/sage/misc/table.py @@ -207,7 +207,7 @@ class table(SageObject): sage: table(rows=[[1,2,3], [4,5,6]], columns=[[0,0,0], [0,0,1024]]) Traceback (most recent call last): ... - ValueError: Don't set both 'rows' and 'columns' when defining a table. + ValueError: do not set both 'rows' and 'columns' when defining a table sage: table(columns=[[0,0,0], [0,0,1024]]) 0 0 @@ -251,7 +251,7 @@ def __init__(self, rows=None, columns=None, header_row=False, """ # If both rows and columns are set, raise an error. if rows and columns: - raise ValueError("Don't set both 'rows' and 'columns' when defining a table.") + raise ValueError("do not set both 'rows' and 'columns' when defining a table") # If columns is set, use its transpose for rows. if columns: rows = list(zip(*columns)) @@ -268,7 +268,7 @@ def __init__(self, rows=None, columns=None, header_row=False, self._options['header_column'] = True elif header_column: self._options['header_column'] = True - rows = [(a,) + tuple(x) for (a,x) in zip(header_column, rows)] + rows = [(a,) + tuple(x) for (a, x) in zip(header_column, rows)] else: self._options['header_column'] = False @@ -442,7 +442,7 @@ def _repr_(self): if len(rows) == 0 or nc == 0: return "" - frame_line = "+" + "+".join("-" * (x+2) for x in self._widths()) + "+\n" + frame_line = "+" + "+".join("-" * (x + 2) for x in self._widths()) + "+\n" if self._options['header_column'] and self._options['frame']: frame_line = "+" + frame_line[1:].replace('+', '++', 1) @@ -503,7 +503,7 @@ def _str_table_row(self, row, header_row=False): """ frame = self._options['frame'] widths = self._widths() - frame_line = "+" + "+".join("-" * (x+2) for x in widths) + "+\n" + frame_line = "+" + "+".join("-" * (x + 2) for x in widths) + "+\n" align = self._options['align'] if align == 'right': @@ -607,16 +607,16 @@ def _latex_(self): # table header s = "\\begin{tabular}{" s += frame_char + align_char + frame_char + head_col_char - s += frame_char.join([align_char] * (nc-1)) + s += frame_char.join([align_char] * (nc - 1)) s += frame_char + "}" + frame_str + "\n" # first row s += " & ".join(LatexExpr(x) if isinstance(x, (str, LatexExpr)) - else '$' + latex(x).strip() + '$' for x in rows[0]) + else '$' + latex(x).strip() + '$' for x in rows[0]) s += " \\\\" + frame_str + head_row_str + "\n" # other rows for row in rows[1:]: s += " & ".join(LatexExpr(x) if isinstance(x, (str, LatexExpr)) - else '$' + latex(x).strip() + '$' for x in row) + else '$' + latex(x).strip() + '$' for x in row) s += " \\\\" + frame_str + "\n" s += "\\end{tabular}" return s @@ -724,7 +724,7 @@ def _html_(self): if rows: s.writelines([ # If the table has < 100 rows, don't truncate the output in the notebook - '
\n' if len(rows) <= 100 else '
' , + '
\n' if len(rows) <= 100 else '
', '\n'.format(frame), '\n', ]) @@ -813,7 +813,7 @@ def _html_table_row(self, file, row, header=False): # first entry of row entry = row[0] if isinstance(entry, Graphics): - file.write(first_column_tag % entry.show(linkmode = True)) + file.write(first_column_tag % entry.show(linkmode=True)) elif isinstance(entry, str): file.write(first_column_tag % math_parse(entry)) else: @@ -822,7 +822,7 @@ def _html_table_row(self, file, row, header=False): # other entries for column in range(1, len(row)): if isinstance(row[column], Graphics): - file.write(column_tag % row[column].show(linkmode = True)) + file.write(column_tag % row[column].show(linkmode=True)) elif isinstance(row[column], str): file.write(column_tag % math_parse(row[column])) else: diff --git a/src/sage/misc/weak_dict.pyx b/src/sage/misc/weak_dict.pyx index ac7bf3bbbd2..d52bedacfa4 100644 --- a/src/sage/misc/weak_dict.pyx +++ b/src/sage/misc/weak_dict.pyx @@ -129,6 +129,8 @@ from cpython.object cimport PyObject_Hash from cpython.ref cimport Py_INCREF, Py_XINCREF, Py_XDECREF from sage.cpython.dict_del_by_value cimport * +from sage.misc.superseded import deprecation + cdef extern from "Python.h": PyObject* Py_None # we need this redefinition because we want to be able to call @@ -369,7 +371,7 @@ cdef class WeakValueDictionary(dict): True """ - return WeakValueDictionary(self.iteritems()) + return WeakValueDictionary(self.items()) def __deepcopy__(self, memo): """ @@ -403,7 +405,7 @@ cdef class WeakValueDictionary(dict): """ out = WeakValueDictionary() - for k,v in self.iteritems(): + for k,v in self.items(): out[deepcopy(k, memo)] = v return out @@ -526,7 +528,7 @@ cdef class WeakValueDictionary(dict): sage: _ = gc.collect() sage: len(D) 1 - sage: D.items() + sage: list(D.items()) [(2, Integer Ring)] Check that :trac:`15956` has been fixed, i.e., a ``TypeError`` is @@ -620,7 +622,7 @@ cdef class WeakValueDictionary(dict): KeyError: 'popitem(): weak value dictionary is empty' """ - for k,v in self.iteritems(): + for k,v in self.items(): del self[k] return k, v raise KeyError('popitem(): weak value dictionary is empty') @@ -811,6 +813,24 @@ cdef class WeakValueDictionary(dict): return list(iter(self)) def itervalues(self): + """ + Deprecated. + + EXAMPLES:: + + sage: import sage.misc.weak_dict + sage: class Vals(): pass + sage: L = [Vals() for _ in range(10)] + sage: D = sage.misc.weak_dict.WeakValueDictionary(enumerate(L)) + sage: T = list(D.itervalues()) + doctest:warning...: + DeprecationWarning: use values instead + See https://trac.sagemath.org/34488 for details. + """ + deprecation(34488, "use values instead") + return self.values() + + def values(self): """ Iterate over the values of this dictionary. @@ -842,7 +862,7 @@ cdef class WeakValueDictionary(dict): sage: del D[2] sage: del L[5] - sage: for v in sorted(D.itervalues()): + sage: for v in sorted(D.values()): ....: print(v) <0> <1> @@ -866,7 +886,7 @@ cdef class WeakValueDictionary(dict): finally: self._exit_iter() - def values(self): + def values_list(self): """ Return the list of values. @@ -893,13 +913,28 @@ cdef class WeakValueDictionary(dict): sage: del D[2] sage: del L[5] - sage: sorted(D.values()) + sage: sorted(D.values_list()) [<0>, <1>, <3>, <4>, <6>, <7>, <8>, <9>] - """ - return list(self.itervalues()) + return list(self.values()) def iteritems(self): + """ + EXAMPLES:: + + sage: import sage.misc.weak_dict + sage: class Vals(): pass + sage: L = [Vals() for _ in range(10)] + sage: D = sage.misc.weak_dict.WeakValueDictionary(enumerate(L)) + sage: T = list(D.iteritems()) + doctest:warning...: + DeprecationWarning: use items instead + See https://trac.sagemath.org/34488 for details. + """ + deprecation(34488, "use items instead") + return self.items() + + def items(self): """ Iterate over the items of this dictionary. @@ -946,7 +981,7 @@ cdef class WeakValueDictionary(dict): sage: del D[Keys(2)] sage: del L[5] - sage: for k,v in sorted(D.iteritems()): + sage: for k,v in sorted(D.items()): ....: print("{} {}".format(k, v)) [0] <0> [1] <1> @@ -970,7 +1005,7 @@ cdef class WeakValueDictionary(dict): finally: self._exit_iter() - def items(self): + def items_list(self): """ The key-value pairs of this dictionary. @@ -1022,7 +1057,7 @@ cdef class WeakValueDictionary(dict): ([8], <8>), ([9], <9>)] """ - return list(self.iteritems()) + return list(self.items()) cdef int _enter_iter(self) except -1: """ diff --git a/src/sage/modular/abvar/cuspidal_subgroup.py b/src/sage/modular/abvar/cuspidal_subgroup.py index a2769d7eff7..136f6cf23e4 100644 --- a/src/sage/modular/abvar/cuspidal_subgroup.py +++ b/src/sage/modular/abvar/cuspidal_subgroup.py @@ -241,10 +241,11 @@ def lattice(self): try: return self.__lattice except AttributeError: - lattice = self._compute_lattice(rational_only = False) + lattice = self._compute_lattice(rational_only=False) self.__lattice = lattice return lattice + class RationalCuspSubgroup(CuspidalSubgroup_generic): """ EXAMPLES:: @@ -292,10 +293,11 @@ def lattice(self): try: return self.__lattice except AttributeError: - lattice = self._compute_lattice(rational_only = True) + lattice = self._compute_lattice(rational_only=True) self.__lattice = lattice return lattice + class RationalCuspidalSubgroup(CuspidalSubgroup_generic): """ EXAMPLES:: @@ -342,7 +344,7 @@ def lattice(self): try: return self.__lattice except AttributeError: - lattice = self._compute_lattice(rational_subgroup = True) + lattice = self._compute_lattice(rational_subgroup=True) self.__lattice = lattice return lattice diff --git a/src/sage/modular/abvar/finite_subgroup.py b/src/sage/modular/abvar/finite_subgroup.py index d2af5e29e00..3f4f0ffbd8f 100644 --- a/src/sage/modular/abvar/finite_subgroup.py +++ b/src/sage/modular/abvar/finite_subgroup.py @@ -461,7 +461,7 @@ def __mul__(self, right): """ lattice = self.lattice().scale(right) return FiniteSubgroup_lattice(self.abelian_variety(), lattice, - field_of_definition = self.field_of_definition()) + field_of_definition=self.field_of_definition()) def __rmul__(self, left): """ diff --git a/src/sage/modular/abvar/morphism.py b/src/sage/modular/abvar/morphism.py index d8676e7db00..bb31e19dbe0 100644 --- a/src/sage/modular/abvar/morphism.py +++ b/src/sage/modular/abvar/morphism.py @@ -546,7 +546,7 @@ def _image_of_finite_subgroup(self, G): B = G._relative_basis_matrix() * self.restrict_domain(G.abelian_variety()).matrix() * self.codomain().lattice().basis_matrix() lattice = B.row_module(ZZ) return self.codomain().finite_subgroup(lattice, - field_of_definition = G.field_of_definition()) + field_of_definition=G.field_of_definition()) def _image_of_abvar(self, A): """ diff --git a/src/sage/modular/arithgroup/arithgroup_generic.py b/src/sage/modular/arithgroup/arithgroup_generic.py index 7e07c33e551..b271e1b121f 100644 --- a/src/sage/modular/arithgroup/arithgroup_generic.py +++ b/src/sage/modular/arithgroup/arithgroup_generic.py @@ -744,7 +744,7 @@ def _find_cusps(self): so this should usually be overridden in subclasses; but it doesn't have to be. """ - i = Cusp([1,0]) + i = Cusp([1, 0]) L = [i] for a in self.coset_reps(): ai = i.apply([a.a(), a.b(), a.c(), a.d()]) @@ -757,11 +757,12 @@ def _find_cusps(self): L.append(ai) return L - def are_equivalent(self, x, y, trans = False): + def are_equivalent(self, x, y, trans=False): r""" - Test whether or not cusps x and y are equivalent modulo self. If self - has a reduce_cusp() method, use that; otherwise do a slow explicit - test. + Test whether or not cusps x and y are equivalent modulo self. + + If self has a reduce_cusp() method, use that; otherwise do a + slow explicit test. If trans = False, returns True or False. If trans = True, then return either False or an element of self mapping x onto y. diff --git a/src/sage/modular/hecke/submodule.py b/src/sage/modular/hecke/submodule.py index 572a8f73029..a6b5c96cbbc 100644 --- a/src/sage/modular/hecke/submodule.py +++ b/src/sage/modular/hecke/submodule.py @@ -27,7 +27,6 @@ from . import module - def is_HeckeSubmodule(x): r""" Return True if x is of type HeckeSubmodule. @@ -553,8 +552,8 @@ def dual_free_module(self, bound=None, anemic=True, use_star=True): # then we compute the dual on each eigenspace, then put them # together. if len(self.star_eigenvalues()) == 2: - V = self.plus_submodule(compute_dual = False).dual_free_module() + \ - self.minus_submodule(compute_dual = False).dual_free_module() + V = self.plus_submodule(compute_dual=False).dual_free_module() + \ + self.minus_submodule(compute_dual=False).dual_free_module() return V # At this point, we know that self is an eigenspace for star. diff --git a/src/sage/modular/local_comp/type_space.py b/src/sage/modular/local_comp/type_space.py index 87b3996dc04..2549e5519b0 100644 --- a/src/sage/modular/local_comp/type_space.py +++ b/src/sage/modular/local_comp/type_space.py @@ -33,7 +33,7 @@ @cached_function -def example_type_space(example_no = 0): +def example_type_space(example_no=0): r""" Quickly return an example of a type space. Used mainly to speed up doctesting. diff --git a/src/sage/modular/modform/ambient_eps.py b/src/sage/modular/modform/ambient_eps.py index 093f62daa0d..cf676af02ef 100644 --- a/src/sage/modular/modform/ambient_eps.py +++ b/src/sage/modular/modform/ambient_eps.py @@ -198,9 +198,9 @@ def change_ring(self, base_ring): """ if self.base_ring() == base_ring: return self - return ambient_R.ModularFormsAmbient_R(self, base_ring = base_ring) + return ambient_R.ModularFormsAmbient_R(self, base_ring=base_ring) - @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: rings.Integer(sign)) # convert sign to an Integer before looking this up in the cache def modular_symbols(self, sign=0): """ Return corresponding space of modular symbols with given sign. @@ -222,9 +222,9 @@ def modular_symbols(self, sign=0): """ sign = rings.Integer(sign) return modsym.ModularSymbols(self.character(), - weight = self.weight(), - sign = sign, - base_ring = self.base_ring()) + weight=self.weight(), + sign=sign, + base_ring=self.base_ring()) @cached_method def eisenstein_submodule(self): diff --git a/src/sage/modular/modform/constructor.py b/src/sage/modular/modform/constructor.py index e16b2e4f1df..dfb1dc1b53f 100644 --- a/src/sage/modular/modform/constructor.py +++ b/src/sage/modular/modform/constructor.py @@ -153,12 +153,13 @@ def ModularForms_clear_cache(): global _cache _cache = {} -def ModularForms(group = 1, - weight = 2, - base_ring = None, + +def ModularForms(group=1, + weight=2, + base_ring=None, eis_only=False, - use_cache = True, - prec = defaults.DEFAULT_PRECISION): + use_cache=True, + prec=defaults.DEFAULT_PRECISION): r""" Create an ambient space of modular forms. @@ -346,8 +347,8 @@ def ModularForms(group = 1, eps = eps.minimize_base_ring() if eps.is_trivial(): return ModularForms(eps.modulus(), weight, base_ring, - use_cache = use_cache, - prec = prec) + use_cache=use_cache, + prec=prec) M = ModularFormsAmbient_eps(eps, weight, eis_only=eis_only) if base_ring != eps.base_ring(): M = M.base_extend(base_ring) # ambient_R.ModularFormsAmbient_R(M, base_ring) @@ -360,11 +361,11 @@ def ModularForms(group = 1, return M -def CuspForms(group = 1, - weight = 2, - base_ring = None, - use_cache = True, - prec = defaults.DEFAULT_PRECISION): +def CuspForms(group=1, + weight=2, + base_ring=None, + use_cache=True, + prec=defaults.DEFAULT_PRECISION): """ Create a space of cuspidal modular forms. @@ -380,13 +381,13 @@ def CuspForms(group = 1, use_cache=use_cache, prec=prec).cuspidal_submodule() -def EisensteinForms(group = 1, - weight = 2, - base_ring = None, - use_cache = True, - prec = defaults.DEFAULT_PRECISION): +def EisensteinForms(group=1, + weight=2, + base_ring=None, + use_cache=True, + prec=defaults.DEFAULT_PRECISION): """ - Create a space of eisenstein modular forms. + Create a space of Eisenstein modular forms. See the documentation for the ModularForms command for a description of the input parameters. @@ -396,7 +397,7 @@ def EisensteinForms(group = 1, sage: EisensteinForms(11,2) Eisenstein subspace of dimension 1 of Modular Forms space of dimension 2 for Congruence Subgroup Gamma0(11) of weight 2 over Rational Field """ - if weight==1: + if weight == 1: return ModularForms(group, weight, base_ring, use_cache=use_cache, eis_only=True, prec=prec).eisenstein_submodule() else: @@ -404,7 +405,6 @@ def EisensteinForms(group = 1, use_cache=use_cache, prec=prec).eisenstein_submodule() - def Newforms(group, weight=2, base_ring=None, names=None): r""" Returns a list of the newforms of the given weight and level (or weight, diff --git a/src/sage/modular/modform/cuspidal_submodule.py b/src/sage/modular/modform/cuspidal_submodule.py index d40af39c52e..e2e43a01f96 100644 --- a/src/sage/modular/modform/cuspidal_submodule.py +++ b/src/sage/modular/modform/cuspidal_submodule.py @@ -236,7 +236,7 @@ def _compute_q_expansion_basis(self, prec=None): prec = Integer(prec) if self.dimension() == 0: return [] - M = self.modular_symbols(sign = 1) + M = self.modular_symbols(sign=1) return M.q_expansion_basis(prec) def _compute_hecke_matrix_prime(self, p): diff --git a/src/sage/modular/modform/eis_series.py b/src/sage/modular/modform/eis_series.py index 65d43a39fda..88499df94c5 100644 --- a/src/sage/modular/modform/eis_series.py +++ b/src/sage/modular/modform/eis_series.py @@ -26,7 +26,7 @@ from .eis_series_cython import eisenstein_series_poly, Ek_ZZ -def eisenstein_series_qexp(k, prec = 10, K=QQ, var='q', normalization='linear'): +def eisenstein_series_qexp(k, prec=10, K=QQ, var='q', normalization='linear'): r""" Return the `q`-expansion of the normalized weight `k` Eisenstein series on `\SL_2(\ZZ)` to precision prec in the ring `K`. Three normalizations @@ -420,28 +420,29 @@ def eisenstein_series_lseries(weight, prec=53, sage: L(2) -5.0235535164599797471968418348135050804419155747868718371029 """ - f = eisenstein_series_qexp(weight,prec) + f = eisenstein_series_qexp(weight, prec) from sage.lfunctions.all import Dokchitser j = weight - L = Dokchitser(conductor = 1, - gammaV = [0,1], - weight = j, - eps = (-1)**Integer(j/2), - poles = [j], + L = Dokchitser(conductor=1, + gammaV=[0, 1], + weight=j, + eps=(-1)**Integer(j // 2), + poles=[j], # Using a string for residues is a hack but it works well # since this will make PARI/GP compute sqrt(pi) with the # right precision. - residues = '[sqrt(Pi)*(%s)]'%((-1)**Integer(j/2)*bernoulli(j)/j), - prec = prec) + residues='[sqrt(Pi)*(%s)]'%((-1)**Integer(j/2)*bernoulli(j)/j), + prec=prec) s = 'coeff = %s;'%f.list() - L.init_coeffs('coeff[k+1]',pari_precode = s, + L.init_coeffs('coeff[k+1]',pari_precode=s, max_imaginary_part=max_imaginary_part, max_asymp_coeffs=max_asymp_coeffs) L.check_functional_equation() L.rename('L-series associated to the weight %s Eisenstein series %s on SL_2(Z)'%(j,f)) return L + def compute_eisenstein_params(character, k): r""" Compute and return a list of all parameters `(\chi,\psi,t)` that diff --git a/src/sage/modular/modform/hecke_operator_on_qexp.py b/src/sage/modular/modform/hecke_operator_on_qexp.py index 6a61cbb46f2..89de388f27f 100644 --- a/src/sage/modular/modform/hecke_operator_on_qexp.py +++ b/src/sage/modular/modform/hecke_operator_on_qexp.py @@ -1,16 +1,15 @@ """ Hecke Operators on `q`-expansions """ - -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2004-2006 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/ +# **************************************************************************** from sage.arith.misc import divisors, gcd from sage.matrix.constructor import matrix @@ -24,7 +23,7 @@ from sage.modular.dirichlet import DirichletGroup, is_DirichletCharacter from .element import is_ModularFormElement -def hecke_operator_on_qexp(f, n, k, eps = None, +def hecke_operator_on_qexp(f, n, k, eps=None, prec=None, check=True, _return_list=False): r""" Given the `q`-expansion `f` of a modular form with character @@ -149,8 +148,8 @@ def _hecke_operator_on_basis(B, V, n, k, eps): TB = [V.coordinate_vector(w) for w in TB] return matrix(V.base_ring(), len(B), len(B), TB, sparse=False) -def hecke_operator_on_basis(B, n, k, eps=None, - already_echelonized = False): + +def hecke_operator_on_basis(B, n, k, eps=None, already_echelonized=False): r""" Given a basis `B` of `q`-expansions for a space of modular forms with character `\varepsilon` to precision at least `\#B\cdot n+1`, @@ -232,8 +231,8 @@ def hecke_operator_on_basis(B, n, k, eps=None, raise TypeError("each element of B must be a power series") n = Integer(n) k = Integer(k) - prec = (f.prec()-1)//n + prec = (f.prec() - 1) // n A = R**prec V = A.span_of_basis([g.padded_list(prec) for g in B], - already_echelonized = already_echelonized) + already_echelonized=already_echelonized) return _hecke_operator_on_basis(B, V, n, k, eps) diff --git a/src/sage/modular/modform/space.py b/src/sage/modular/modform/space.py index 5dfd3f568d8..69206f0e023 100644 --- a/src/sage/modular/modform/space.py +++ b/src/sage/modular/modform/space.py @@ -1309,7 +1309,7 @@ def _compute_hecke_matrix_prime(self, p, prec=None): self.weight(), eps, already_echelonized=False) except ValueError: # Double the precision. - return self._compute_hecke_matrix_prime(p, prec = 2*prec+1) + return self._compute_hecke_matrix_prime(p, prec=2 * prec + 1) def _compute_hecke_matrix(self, n): """ diff --git a/src/sage/modular/modform/submodule.py b/src/sage/modular/modform/submodule.py index b1da8a92105..49b6c12b493 100644 --- a/src/sage/modular/modform/submodule.py +++ b/src/sage/modular/modform/submodule.py @@ -104,7 +104,8 @@ def _compute_q_expansion_basis(self, prec): O(q^5)] """ A = self.ambient_module() - return [A._q_expansion(element = f.element(), prec=prec) for f in self.basis()] + return [A._q_expansion(element=f.element(), prec=prec) + for f in self.basis()] # TODO diff --git a/src/sage/modular/modform_hecketriangle/hecke_triangle_groups.py b/src/sage/modular/modform_hecketriangle/hecke_triangle_groups.py index c1fc60f724c..fdd7e38b552 100644 --- a/src/sage/modular/modform_hecketriangle/hecke_triangle_groups.py +++ b/src/sage/modular/modform_hecketriangle/hecke_triangle_groups.py @@ -834,7 +834,7 @@ def emb_key(emb): return L if len(L) > 1: - L.sort(key = emb_key) + L.sort(key=emb_key) return L[-1] def _elliptic_conj_reps(self): diff --git a/src/sage/modular/modform_hecketriangle/series_constructor.py b/src/sage/modular/modform_hecketriangle/series_constructor.py index 3b698833b2b..881270c5bfa 100644 --- a/src/sage/modular/modform_hecketriangle/series_constructor.py +++ b/src/sage/modular/modform_hecketriangle/series_constructor.py @@ -33,8 +33,7 @@ from .hecke_triangle_groups import HeckeTriangleGroup - -class MFSeriesConstructor(SageObject,UniqueRepresentation): +class MFSeriesConstructor(SageObject, UniqueRepresentation): r""" Constructor for the Fourier expansion of some (specific, basic) modular forms. @@ -44,7 +43,7 @@ class MFSeriesConstructor(SageObject,UniqueRepresentation): """ @staticmethod - def __classcall__(cls, group = HeckeTriangleGroup(3), prec=ZZ(10)): + def __classcall__(cls, group=HeckeTriangleGroup(3), prec=ZZ(10)): r""" Return a (cached) instance with canonical parameters. diff --git a/src/sage/modular/modsym/ambient.py b/src/sage/modular/modsym/ambient.py index 6408405d520..4f9b23bfc28 100644 --- a/src/sage/modular/modsym/ambient.py +++ b/src/sage/modular/modsym/ambient.py @@ -3014,7 +3014,7 @@ def _compute_hecke_matrix_prime(self, p, rows=None): P1 = self.p1list() mod2term = self._mod2term R = self.manin_gens_to_basis() - W = R.new_matrix(nrows=len(B), ncols = R.nrows()) # the 0 with given number of rows and cols. + W = R.new_matrix(nrows=len(B), ncols=R.nrows()) # the 0 with given number of rows and cols. j = 0 tm = verbose("Matrix non-reduced", tm) for i in B: @@ -3588,11 +3588,11 @@ def __init__(self, eps, weight, sign, base_ring, custom_init=None, category=None """ level = eps.modulus() ModularSymbolsAmbient.__init__(self, - weight = weight, - group = arithgroup.Gamma1(level), - sign = sign, - base_ring = base_ring, - character = eps.change_ring(base_ring), + weight=weight, + group=arithgroup.Gamma1(level), + sign=sign, + base_ring=base_ring, + character=eps.change_ring(base_ring), custom_init=custom_init, category=category) diff --git a/src/sage/modular/modsym/boundary.py b/src/sage/modular/modsym/boundary.py index f8550d0f05f..fa24249edc8 100644 --- a/src/sage/modular/modsym/boundary.py +++ b/src/sage/modular/modsym/boundary.py @@ -274,17 +274,17 @@ def __neg__(self): sage: -x + x # indirect doctest 0 """ - return self*(-1) + return self * (-1) @richcmp_method class BoundarySpace(hecke.HeckeModule_generic): def __init__(self, - group = arithgroup.Gamma0(1), - weight = 2, - sign = 0, - base_ring = rings.QQ, - character = None): + group=arithgroup.Gamma0(1), + weight=2, + sign=0, + base_ring=rings.QQ, + character=None): """ Space of boundary symbols for a congruence subgroup of SL_2(Z). diff --git a/src/sage/modular/modsym/modsym.py b/src/sage/modular/modsym/modsym.py index a30f53a8a9e..0a87eece1d7 100644 --- a/src/sage/modular/modsym/modsym.py +++ b/src/sage/modular/modsym/modsym.py @@ -180,11 +180,11 @@ def ModularSymbols_clear_cache(): _cache = {} -def ModularSymbols(group = 1, - weight = 2, - sign = 0, - base_ring = None, - use_cache = True, +def ModularSymbols(group=1, + weight=2, + sign=0, + base_ring=None, + use_cache=True, custom_init=None): r""" Create an ambient space of modular symbols. diff --git a/src/sage/modular/modsym/subspace.py b/src/sage/modular/modsym/subspace.py index e27467a3816..c1f70471c0f 100644 --- a/src/sage/modular/modsym/subspace.py +++ b/src/sage/modular/modsym/subspace.py @@ -72,9 +72,10 @@ def __init__(self, ambient_hecke_module, submodule, """ self.__ambient_hecke_module = ambient_hecke_module A = ambient_hecke_module - sage.modular.modsym.space.ModularSymbolsSpace.__init__(self, A.group(), A.weight(), \ - A.character(), A.sign(), A.base_ring()) - hecke.HeckeSubmodule.__init__(self, A, submodule, dual_free_module = dual_free_module, check=check) + sage.modular.modsym.space.ModularSymbolsSpace.__init__(self, A.group(), + A.weight(), + A.character(), A.sign(), A.base_ring()) + hecke.HeckeSubmodule.__init__(self, A, submodule, dual_free_module=dual_free_module, check=check) def _repr_(self): """ diff --git a/src/sage/modular/multiple_zeta.py b/src/sage/modular/multiple_zeta.py index dcd3d681eac..6fd43f34560 100644 --- a/src/sage/modular/multiple_zeta.py +++ b/src/sage/modular/multiple_zeta.py @@ -170,8 +170,6 @@ from sage.structure.unique_representation import UniqueRepresentation from sage.structure.richcmp import op_EQ, op_NE from sage.structure.element import parent -from sage.algebras.free_zinbiel_algebra import FreeZinbielAlgebra -from sage.arith.misc import bernoulli from sage.categories.cartesian_product import cartesian_product from sage.categories.graded_algebras_with_basis import GradedAlgebrasWithBasis from sage.categories.rings import Rings @@ -189,12 +187,11 @@ from sage.misc.cachefunc import cached_function, cached_method from sage.misc.lazy_attribute import lazy_attribute from sage.misc.misc_c import prod -from sage.modules.free_module_element import vector +from sage.modular.multiple_zeta_F_algebra import F_algebra from sage.modules.free_module import VectorSpace 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.semirings.non_negative_integer_semiring import NN +from sage.sets.positive_integers import PositiveIntegers # multiplicative generators for weight <= 17 # using the following convention @@ -439,7 +436,7 @@ def __repr__(self): sage: MultizetaValues() Cached multiple zeta values at precision 1024 up to weight 8 """ - return "Cached multiple zeta values at precision %d up to weight %d" % (self.prec, self.max_weight) + return f"Cached multiple zeta values at precision {self.prec} up to weight {self.max_weight}" def reset(self, max_weight=8, prec=1024): r""" @@ -497,8 +494,7 @@ def pari_eval(self, index): if weight <= self.max_weight: index = pari.zetamultconvert(index, 2) return self._data[index - 1] - else: - return pari.zetamult(index, precision=self.prec) + return pari.zetamult(index, precision=self.prec) def __call__(self, index, prec=None, reverse=True): r""" @@ -543,97 +539,12 @@ def __call__(self, index, prec=None, reverse=True): index = pari.zetamultconvert(index, 2) value = self._data[index - 1] return value.sage().n(prec=prec) - else: - return pari.zetamult(index, precision=prec).sage().n(prec=prec) + return pari.zetamult(index, precision=prec).sage().n(prec=prec) Values = MultizetaValues() -def basis_f_odd_iterator(n): - """ - Return an iterator over compositions of ``n`` with parts in ``(3,5,7,...)`` - - INPUT: - - - ``n`` -- an integer - - EXAMPLES:: - - sage: from sage.modular.multiple_zeta import basis_f_odd_iterator - sage: [list(basis_f_odd_iterator(i)) for i in range(2,9)] - [[], [(3,)], [], [(5,)], [(3, 3)], [(7,)], [(5, 3), (3, 5)]] - sage: list(basis_f_odd_iterator(14)) - [(11, 3), - (5, 3, 3, 3), - (3, 5, 3, 3), - (3, 3, 5, 3), - (9, 5), - (3, 3, 3, 5), - (7, 7), - (5, 9), - (3, 11)] - """ - if n == 0: - yield tuple() - return - if n == 1: - return - if n % 2: - yield (n,) - for k in range(3, n, 2): - for start in basis_f_odd_iterator(n - k): - yield start + (k, ) - - -def basis_f_iterator(n): - """ - Return an iterator over decompositions of ``n`` using ``2,3,5,7,9,...``. - - The means that each term is made of a power of 2 and a composition - of the remaining integer with parts in ``(3,5,7,...)`` - - INPUT: - - - ``n`` -- an integer - - Each term is returned as a pair (integer, word) where - the integer is the exponent of 2. - - EXAMPLES:: - - sage: from sage.modular.multiple_zeta import basis_f_iterator - sage: [list(basis_f_iterator(i)) for i in range(2,9)] - [[(1, word: )], - [(0, word: f3)], - [(2, word: )], - [(0, word: f5), (1, word: f3)], - [(0, word: f3,f3), (3, word: )], - [(0, word: f7), (1, word: f5), (2, word: f3)], - [(0, word: f5,f3), (0, word: f3,f5), (1, word: f3,f3), (4, word: )]] - sage: list(basis_f_iterator(11)) - [(0, word: f11), - (0, word: f5,f3,f3), - (0, word: f3,f5,f3), - (0, word: f3,f3,f5), - (1, word: f9), - (1, word: f3,f3,f3), - (2, word: f7), - (3, word: f5), - (4, word: f3)] - - TESTS:: - - sage: list(basis_f_iterator(0)) - [(0, word: )] - """ - if n and n < 2: - return - for k in range(n // 2 + 1): - for start in basis_f_odd_iterator(n - 2 * k): - yield (k, Word(['f{}'.format(d) for d in start])) - - def extend_multiplicative_basis(B, n): """ Extend a multiplicative basis into a basis. @@ -749,9 +660,8 @@ def __init__(self, R): cat = GradedAlgebrasWithBasis(R).Commutative() if R in Domains(): cat = cat & Domains() - CombinatorialFreeModule.__init__(self, R, Words(NN, infinite=False), - prefix="Z", - category=cat) + W = Words(PositiveIntegers(), infinite=False) + CombinatorialFreeModule.__init__(self, R, W, prefix="Z", category=cat) def _repr_(self): r""" @@ -959,7 +869,7 @@ def phi(self): r""" Return the morphism ``phi``. - This sends multiple zeta values to the algebra :func:`F_ring`, + This sends multiple zeta values to the auxiliary F-algebra, which is a shuffle algebra in odd generators `f_3,f_5,f_7,\dots` over the polynomial ring in one variable `f_2`. @@ -975,12 +885,12 @@ def phi(self): sage: m = Multizeta(2,2) + 2*Multizeta(1,3); m 2*ζ(1,3) + ζ(2,2) sage: M.phi(m) - 1/2*f2^2*Z[] + 1/2*f2^2 sage: Z = Multizeta sage: B5 = [3*Z(1,4) + 2*Z(2,3) + Z(3,2), 3*Z(1,4) + Z(2,3)] sage: [M.phi(b) for b in B5] - [f2*Z[f3] - 1/2*Z[f5], 1/2*Z[f5]] + [-1/2*f5 + f2*f3, 1/2*f5] """ M_it = Multizetas_iterated(self.base_ring()) return M_it.phi * self.iterated @@ -1024,7 +934,7 @@ def _element_constructor_(self, x): if isinstance(x, list): x = tuple(x) return self._monomial(W(x, check=False)) - elif isinstance(parent(x), Multizetas_iterated): + if isinstance(parent(x), Multizetas_iterated): return x.composition() raise TypeError('invalid input for building a multizeta value') @@ -1129,7 +1039,7 @@ def basis_filtration(self, d, reverse=False): raise ValueError('d must be a non-negative integer') if d == 0: return [self([])] - elif d == 1: + if d == 1: return [] Values.reset(max_weight=d) @@ -1151,9 +1061,8 @@ def basis_filtration(self, d, reverse=False): v = self(c).phi_as_vector() if v in U: continue - else: - U = V.subspace(U.basis() + [v]) - basis.append(c) + U = V.subspace(U.basis() + [v]) + basis.append(c) k += 1 return [self(c) for c in basis] @@ -1201,10 +1110,10 @@ def single_valued(self): """ phi_im = self.phi() zin = phi_im.parent() - BR2 = zin.base_ring() - sv = zin.sum_of_terms((w, BR2(cf(0))) - for (a, b), cf in phi_im.coproduct() - for w in shuffle(a, b.reversal(), False)) + phi_no_f2 = phi_im.without_f2() + sv = zin.sum_of_terms(((0, w), cf) + for (a, b), cf in phi_no_f2.coproduct() + for w in shuffle(a[1], b[1].reversal(), False)) return rho_inverse(sv) def simplify(self): @@ -1331,7 +1240,7 @@ def _richcmp_(self, other, op): sage: (0*M()) == 0 True """ - if op != op_EQ and op != op_NE: + if op not in [op_EQ, op_NE]: raise TypeError('invalid comparison for multizetas') return self.iterated()._richcmp_(other.iterated(), op) @@ -1351,13 +1260,13 @@ def phi(self): """ Return the image of ``self`` by the morphism ``phi``. - This sends multiple zeta values to the algebra :func:`F_ring`. + This sends multiple zeta values to the auxiliary F-algebra. EXAMPLES:: sage: M = Multizetas(QQ) sage: M((1,2)).phi() - Z[f3] + f3 TESTS:: @@ -1366,7 +1275,7 @@ def phi(self): sage: M = Multizetas(A) sage: tst = u*M((1,2))+M((3,)) sage: tst.phi() - (u+1)*Z[f3] + (u+1)*f3 """ return self.parent().phi(self) @@ -1397,7 +1306,7 @@ def phi_as_vector(self): """ if not self.is_homogeneous(): raise ValueError('only defined for homogeneous elements') - return f_to_vector(self.parent().phi(self)) + return self.parent().phi(self).homogeneous_to_vector() def _numerical_approx_pari(self): r""" @@ -1463,8 +1372,7 @@ def numerical_approx(self, prec=None, digits=None, algorithm=None): if prec < Values.prec: s = sum(cf * Values(tuple(w)) for w, cf in self.monomial_coefficients().items()) return s.n(prec=prec) - else: - return sum(cf * Values(tuple(w), prec=prec) for w, cf in self.monomial_coefficients().items()) + return sum(cf * Values(tuple(w), prec=prec) for w, cf in self.monomial_coefficients().items()) class Multizetas_iterated(CombinatorialFreeModule): @@ -1514,9 +1422,10 @@ def _repr_(self): sage: from sage.modular.multiple_zeta import Multizetas_iterated sage: M = Multizetas_iterated(QQ); M - Algebra of motivic multiple zeta values as convergent iterated integrals over Rational Field + Algebra of motivic multiple zeta values + as convergent iterated integrals over Rational Field """ - return "Algebra of motivic multiple zeta values as convergent iterated integrals over {}".format(self.base_ring()) + return f"Algebra of motivic multiple zeta values as convergent iterated integrals over {self.base_ring()}" def _repr_term(self, m): """ @@ -1645,12 +1554,11 @@ def split_word(indices): w = Word(seq[indices[i]:indices[i + 1] + 1]) if len(w) == 2: # this factor is one continue - elif len(w) <= 4 or len(w) == 6 or w[0] == w[-1]: + if len(w) <= 4 or len(w) == 6 or w[0] == w[-1]: # vanishing factors return self.zero() - else: - value = M_all(w) - L *= value.regularise().simplify() + value = M_all(w) + L *= value.regularise().simplify() return L resu = self.tensor_square().zero() @@ -1837,7 +1745,7 @@ def phi_extended(self, w): OUTPUT: - an element in the algebra :func:`F_ring` + an element in the auxiliary F-algebra The coefficients are in the base ring. @@ -1846,52 +1754,50 @@ def phi_extended(self, w): sage: from sage.modular.multiple_zeta import Multizetas_iterated sage: M = Multizetas_iterated(QQ) sage: M.phi_extended((1,0)) - -f2*Z[] + -f2 sage: M.phi_extended((1,0,0)) - -Z[f3] + -f3 sage: M.phi_extended((1,1,0)) - Z[f3] + f3 sage: M.phi_extended((1,0,1,0,0)) - 3*f2*Z[f3] - 11/2*Z[f5] + -11/2*f5 + 3*f2*f3 More complicated examples:: sage: from sage.modular.multiple_zeta import composition_to_iterated sage: M.phi_extended(composition_to_iterated((4,3))) - 2/5*f2^2*Z[f3] + 10*f2*Z[f5] - 18*Z[f7] + -18*f7 + 10*f2*f5 + 2/5*f2^2*f3 sage: M.phi_extended(composition_to_iterated((3,4))) - -10*f2*Z[f5] + 17*Z[f7] + 17*f7 - 10*f2*f5 sage: M.phi_extended(composition_to_iterated((4,2))) - 10/21*f2^3*Z[] - 2*Z[f3,f3] + -2*f3f3 + 10/21*f2^3 sage: M.phi_extended(composition_to_iterated((3,5))) - -5*Z[f5,f3] + -5*f5f3 sage: M.phi_extended(composition_to_iterated((3,7))) - -6*Z[f5,f5] - 14*Z[f7,f3] + -6*f5f5 - 14*f7f3 sage: M.phi_extended(composition_to_iterated((3,3,2))) - -793/875*f2^4*Z[] - 4*f2*Z[f3,f3] + 9*Z[f3,f5] - 9/2*Z[f5,f3] + 9*f3f5 - 9/2*f5f3 - 4*f2*f3f3 - 793/875*f2^4 TESTS:: sage: M.phi_extended(tuple()) - Z[] + 1 """ # this is now hardcoded # prec = 1024 - f = F_ring_generator + F = F_algebra(self.base_ring()) + f = F.gen if not w: - F = F_ring(self.base_ring()) - empty = F.indices()([]) - return F.monomial(empty) + return F.one() N = len(w) compo = tuple(iterated_to_composition(w)) - BRf2 = PolynomialRing(self.base_ring(), 'f2') if compo in B_data[N]: # do not forget the sign result_QQ = (-1)**len(compo) * phi_on_multiplicative_basis(compo) - return result_QQ.base_extend(BRf2) + return result_QQ u = compute_u_on_basis(w) rho_inverse_u = rho_inverse(u) xi = self.composition_on_basis(w, QQ) @@ -1899,14 +1805,14 @@ def phi_extended(self, w): c_xi /= Multizeta(N)._numerical_approx_pari() c_xi = c_xi.bestappr().sage() # in QQ result_QQ = u + c_xi * f(N) - return result_QQ.base_extend(BRf2) + return result_QQ @lazy_attribute def phi(self): """ Return the morphism ``phi``. - This sends multiple zeta values to the algebra :func:`F_ring`. + This sends multiple zeta values to the auxiliary F-algebra. EXAMPLES:: @@ -1915,19 +1821,19 @@ def phi(self): sage: m = Multizeta(1,0,1,0) + 2*Multizeta(1,1,0,0); m 2*I(1100) + I(1010) sage: M.phi(m) - 1/2*f2^2*Z[] + 1/2*f2^2 sage: Z = Multizeta sage: B5 = [3*Z(1,4) + 2*Z(2,3) + Z(3,2), 3*Z(1,4) + Z(2,3)] sage: [M.phi(b.iterated()) for b in B5] - [f2*Z[f3] - 1/2*Z[f5], 1/2*Z[f5]] + [-1/2*f5 + f2*f3, 1/2*f5] sage: B6 = [6*Z(1,5) + 3*Z(2,4) + Z(3,3), ....: 6*Z(1,1,4) + 4*Z(1,2,3) + 2*Z(1,3,2) + 2*Z(2,1,3) + Z(2,2,2)] sage: [M.phi(b.iterated()) for b in B6] - [Z[f3,f3], 1/6*f2^3*Z[]] + [f3f3, 1/6*f2^3] """ - cod = F_ring(self.base_ring()) + cod = F_algebra(self.base_ring()) return self.module_morphism(self.phi_extended, codomain=cod) def _element_constructor_(self, x): @@ -1974,8 +1880,7 @@ def _element_constructor_(self, x): x = R(x) if x == 0: return self.element_class(self, {}) - else: - return self.from_base_ring_from_one_basis(x) + return self.from_base_ring_from_one_basis(x) class Element(CombinatorialFreeModule.Element): def simplify(self): @@ -2051,14 +1956,14 @@ def phi(self): """ Return the image of ``self`` by the morphism ``phi``. - This sends multiple zeta values to the algebra :func:`F_ring`. + This sends multiple zeta values to the auxiliary F-algebra. EXAMPLES:: sage: from sage.modular.multiple_zeta import Multizetas_iterated sage: M = Multizetas_iterated(QQ) sage: M((1,1,0)).phi() - Z[f3] + f3 """ return self.parent().phi(self) @@ -2121,7 +2026,7 @@ def _richcmp_(self, other, op): sage: a.iterated() == b.iterated() # not tested, long time 20s True """ - if op != op_EQ and op != op_NE: + if op not in [op_EQ, op_NE]: raise TypeError('invalid comparison for multizetas') return (self - other).is_zero() == (op == op_EQ) @@ -2452,100 +2357,6 @@ def regularise(self): # **************** procedures after F. Brown ************ - -def F_ring(basering, N=18): - r""" - Return the free Zinbiel algebra on many generators `f_3,f_5,\dots` - over the polynomial ring with generator `f_2`. - - For the moment, only with a finite number of variables. - - INPUT: - - - ``N`` -- an integer (default 18), upper bound for indices of generators - - EXAMPLES:: - - sage: from sage.modular.multiple_zeta import F_ring - sage: F_ring(QQ) - Free Zinbiel algebra on generators (Z[f3], Z[f5], Z[f7], Z[f9], ...) - over Univariate Polynomial Ring in f2 over Rational Field - """ - ring = PolynomialRing(basering, ['f2']) - return FreeZinbielAlgebra(ring, ['f{}'.format(k) - for k in range(3, N, 2)]) - - -def F_prod(a, b): - """ - Return the associative and commutative product of ``a`` and ``b``. - - INPUT: - - - ``a``, ``b`` -- two elements of the F ring - - OUTPUT: - - an element of the F ring - - EXAMPLES:: - - sage: from sage.modular.multiple_zeta import F_ring_generator, F_prod - sage: f2 = F_ring_generator(2) - sage: f3 = F_ring_generator(3) - sage: F_prod(f2,f2) - f2^2*Z[] - sage: F_prod(f2,f3) - f2*Z[f3] - sage: F_prod(f3,f3) - 2*Z[f3,f3] - sage: F_prod(3*f2+5*f3,6*f2+f3) - 18*f2^2*Z[] + 33*f2*Z[f3] + 10*Z[f3,f3] - """ - F = a.parent() - empty = F.indices()([]) - one = F.monomial(empty) - ct_a = a.coefficient(empty) - ct_b = b.coefficient(empty) - rem_a = a - ct_a * one - rem_b = b - ct_b * one - resu = ct_a * ct_b * one + ct_a * rem_b + ct_b * rem_a - return resu + rem_a * rem_b + rem_b * rem_a - - -def F_ring_generator(i): - r""" - Return the generator of the F ring over `\QQ`. - - INPUT: - - - ``i`` -- a nonnegative integer - - If ``i`` is odd, this returns a single generator `f_i` of the free - shuffle algebra. - - Otherwise, it returns an appropriate multiple of a power of `f_2`. - - EXAMPLES:: - - sage: from sage.modular.multiple_zeta import F_ring_generator - sage: [F_ring_generator(i) for i in range(2,8)] - [f2*Z[], Z[f3], 2/5*f2^2*Z[], Z[f5], 8/35*f2^3*Z[], Z[f7]] - """ - F = F_ring(QQ) - one = F.monomial(Word([])) - f2 = F.base_ring().gen() - if i == 2: - return f2 * one - # now i odd >= 3 - if i % 2: - return F.monomial(Word(['f{}'.format(i)])) - i = i // 2 - B = bernoulli(2 * i) * (-1)**(i - 1) - B *= ZZ(2)**(3 * i - 1) * ZZ(3)**i / ZZ(2 * i).factorial() - return B * f2**i * one - - def coeff_phi(w): """ Return the coefficient of `f_k` in the image by ``phi``. @@ -2577,8 +2388,8 @@ def coeff_phi(w): M = Multizetas_iterated(QQ) z = M.phi_extended(w) W = z.parent().basis().keys() - w = W(['f{}'.format(k)], check=False) - return z.coefficient(w).lc() # in QQ + w = W((0, [k])) + return z.coefficient(w) # in QQ def phi_on_multiplicative_basis(compo): @@ -2597,16 +2408,14 @@ def phi_on_multiplicative_basis(compo): sage: from sage.modular.multiple_zeta import phi_on_multiplicative_basis sage: phi_on_multiplicative_basis((2,)) - f2*Z[] + f2 sage: phi_on_multiplicative_basis((3,)) - Z[f3] + f3 """ - f = F_ring_generator - F = F_ring(QQ) - one = F.monomial(Word([])) + f = F_algebra(QQ).gen if tuple(compo) == (2,): - return f(2) * one + return f(2) if len(compo) == 1: n, = compo @@ -2633,18 +2442,14 @@ def phi_on_basis(L): sage: from sage.modular.multiple_zeta import phi_on_basis sage: phi_on_basis([(3,),(3,)]) - 2*Z[f3,f3] + 2*f3f3 sage: phi_on_basis([(2,),(2,)]) - f2^2*Z[] + f2^2 sage: phi_on_basis([(2,),(3,),(3,)]) - 2*f2*Z[f3,f3] + 2*f2*f3f3 """ - # beware that the default * is the half-shuffle ! - F = F_ring(QQ) - resu = F.monomial(Word([])) - for compo in L: - resu = F_prod(resu, phi_on_multiplicative_basis(compo)) - return resu + F = F_algebra(QQ) + return F.prod(phi_on_multiplicative_basis(compo) for compo in L) def D_on_compo(k, compo): @@ -2707,11 +2512,11 @@ def compute_u_on_compo(compo): sage: from sage.modular.multiple_zeta import compute_u_on_compo sage: compute_u_on_compo((2,4)) - 2*Z[f3,f3] + 2*f3f3 sage: compute_u_on_compo((2,3,2)) - -11/2*f2*Z[f5] + -11/2*f2*f5 sage: compute_u_on_compo((3,2,3,2)) - 11*f2*Z[f3,f5] - 75/4*Z[f3,f7] - 9*f2*Z[f5,f3] + 81/4*Z[f5,f5] + 75/8*Z[f7,f3] + -75/4*f3f7 + 81/4*f5f5 + 75/8*f7f3 + 11*f2*f3f5 - 9*f2*f5f3 """ it = composition_to_iterated(compo) return (-1)**len(compo) * compute_u_on_basis(it) @@ -2733,103 +2538,29 @@ def compute_u_on_basis(w): sage: from sage.modular.multiple_zeta import compute_u_on_basis sage: compute_u_on_basis((1,0,0,0,1,0)) - -2*Z[f3,f3] + -2*f3f3 sage: compute_u_on_basis((1,1,1,0,0)) - f2*Z[f3] + f2*f3 sage: compute_u_on_basis((1,0,0,1,0,0,0,0)) - -5*Z[f5,f3] + -5*f5f3 sage: compute_u_on_basis((1,0,1,0,0,1,0)) - 11/2*f2*Z[f5] + 11/2*f2*f5 sage: compute_u_on_basis((1,0,0,1,0,1,0,0,1,0)) - 11*f2*Z[f3,f5] - 75/4*Z[f3,f7] - 9*f2*Z[f5,f3] + 81/4*Z[f5,f5] - + 75/8*Z[f7,f3] + -75/4*f3f7 + 81/4*f5f5 + 75/8*f7f3 + 11*f2*f3f5 - 9*f2*f5f3 """ M = Multizetas_iterated(QQ) - F = F_ring(QQ) - f = F_ring_generator + F = F_algebra(QQ) N = len(w) xi_dict = {} for k in range(3, N, 2): xi_dict[k] = F.sum(cf * coeff_phi(ww[0]) * M.phi_extended(tuple(ww[1])) for ww, cf in M.D_on_basis(k, w)) - return F.sum(f(k) * xi_dict[k] for k in range(3, N, 2)) - - -def f_to_vector(elt): - """ - Convert an element of F ring to a vector. - - INPUT: - - an homogeneous element of :func:`F_ring` over some base ring - - OUTPUT: - - a vector with coefficients in the base ring - - .. SEEALSO:: :func:`vector_to_f` - - EXAMPLES:: - - sage: from sage.modular.multiple_zeta import F_ring, vector_to_f, f_to_vector - sage: F = F_ring(QQ) - sage: f2 = F.base_ring().gen() - sage: x = f2**4*F.monomial(Word([]))+f2*F.monomial(Word(['f3','f3'])) - sage: f_to_vector(x) - (0, 0, 1, 1) - sage: vector_to_f(_,8) - f2^4*Z[] + f2*Z[f3,f3] - - sage: x = F.monomial(Word(['f11'])); x - Z[f11] - sage: f_to_vector(x) - (1, 0, 0, 0, 0, 0, 0, 0, 0) - """ - F = elt.parent() - BR = F.base_ring().base_ring() - if not elt: - return vector(BR, []) - a, b = next(iter(elt)) - N = sum(int(x[1:]) for x in a) + 2 * b.degree() - W = F.basis().keys() - return vector(BR, [elt.coefficient(W(b, check=False)).lc() - for _, b in basis_f_iterator(N)]) - - -def vector_to_f(vec, N): - """ - Convert back a vector to an element of the F ring. - - INPUT: - - a vector with coefficients in some base ring - - OUTPUT: - - an homogeneous element of :func:`F_ring` over this base ring - - .. SEEALSO:: :func:`f_to_vector` - - EXAMPLES:: - - sage: from sage.modular.multiple_zeta import vector_to_f, f_to_vector - sage: vector_to_f((4,5),6) - 5*f2^3*Z[] + 4*Z[f3,f3] - sage: f_to_vector(_) - (4, 5) - """ - if isinstance(vec, (list, tuple)): - vec = vector(vec) - BR = vec.base_ring() - F = F_ring(BR) - f2 = F.base_ring().gen() - basis_F = (f2**k * F.monomial(b) - for k, b in basis_f_iterator(N)) - return sum(cf * bi for cf, bi in zip(vec, basis_F)) + return F.sum(F.half_product(F.gen(k), xi_dict[k]) + for k in range(3, N, 2)) @cached_function @@ -2859,7 +2590,7 @@ def rho_matrix_inverse(n): resu = [] for b in base: phi_b = phi_on_basis(b) - resu.append(f_to_vector(phi_b)) + resu.append(phi_b.homogeneous_to_vector()) dN = len(resu) return ~matrix(QQ, dN, dN, resu) @@ -2878,13 +2609,15 @@ def rho_inverse(elt): EXAMPLES:: - sage: from sage.modular.multiple_zeta import F_ring_generator, rho_inverse - sage: f = F_ring_generator + sage: from sage.modular.multiple_zeta import rho_inverse + sage: from sage.modular.multiple_zeta_F_algebra import F_algebra + sage: A = F_algebra(QQ) + sage: f = A.gen sage: rho_inverse(f(3)) ζ(3) sage: rho_inverse(f(9)) ζ(9) - sage: rho_inverse(f(5)*f(3)) + sage: rho_inverse(A("53")) -1/5*ζ(3,5) """ pa = elt.parent() @@ -2893,9 +2626,10 @@ def rho_inverse(elt): if elt == pa.zero(): return M_BR.zero() - a, b = next(iter(elt)) - N = sum(int(x[1:]) for x in a) + 2 * b.degree() + pw, _ = next(iter(elt)) + p, w = pw + N = 2 * p + sum(int(c) for c in w) - v = f_to_vector(elt) + v = elt.homogeneous_to_vector() w = v * rho_matrix_inverse(N) return sum(cf * b for cf, b in zip(w, M_BR.basis_data(BR, N))) diff --git a/src/sage/modular/multiple_zeta_F_algebra.py b/src/sage/modular/multiple_zeta_F_algebra.py new file mode 100644 index 00000000000..39e7dcad4e7 --- /dev/null +++ b/src/sage/modular/multiple_zeta_F_algebra.py @@ -0,0 +1,723 @@ +# -*- coding: utf-8 -*- +r""" +F-algebra for motivic multiple zeta values. + +This is a commutative algebra, defined as the tensor product of the +polynomial algebra over one generator `f_2` by the shuffle algebra in +infinitely many generators indexed by odd integers and denoted by +`f_3`, `f_5`, ... It serves as an auxiliary algebra in the study of +the ring of motivic multiple zeta values. + +Here we provide a basic direct implementation, endowed with the +motivic coproduct. + +AUTHORS: + +- Frédéric Chapoton (2022-09): Initial version + +""" +# **************************************************************************** +# Copyright (C) 2022 Frédéric Chapoton +# +# Distributed under the terms of the GNU General Public License (GPL) +# https://www.gnu.org/licenses/ +# **************************************************************************** +from __future__ import annotations +from typing import Iterator + +from sage.arith.misc import bernoulli +from sage.categories.rings import Rings +from sage.categories.bialgebras_with_basis import BialgebrasWithBasis +from sage.combinat.free_module import CombinatorialFreeModule +from sage.combinat.words.words import Words +from sage.combinat.words.finite_word import FiniteWord_class +from sage.misc.cachefunc import cached_method +from sage.misc.lazy_attribute import lazy_attribute +from sage.sets.integer_range import IntegerRange +from sage.rings.integer_ring import ZZ +from sage.sets.non_negative_integers import NonNegativeIntegers +from sage.rings.infinity import Infinity +from sage.modules.free_module_element import vector + + +# the indexing set: (integer power of f_2, word in 3, 5, 7,...) +W_Odds = Words(IntegerRange(3, Infinity, 2), infinite=False) + + +def str_to_index(x: str) -> tuple: + """ + Convert a string to an index. + + Every letter "2" contributes to the power of `f_2`. Other letters + define a word in `f_3`, `f_5`, ... + + Usually the letters "2" form a prefix of the input. + + EXAMPLES:: + + sage: from sage.modular.multiple_zeta_F_algebra import str_to_index + sage: str_to_index("22357") + (2, [3, 5, 7]) + """ + p = x.count("2") + w = [int(i) for i in x if i != '2'] + return (p, w) + + +def basis_f_odd_iterator(n) -> Iterator[tuple]: + """ + Return an iterator over compositions of ``n`` with parts in ``(3,5,7,...)`` + + This is used to index a basis. + + INPUT: + + - ``n`` -- an integer + + EXAMPLES:: + + sage: from sage.modular.multiple_zeta_F_algebra import basis_f_odd_iterator + sage: [list(basis_f_odd_iterator(i)) for i in range(2,9)] + [[], [(3,)], [], [(5,)], [(3, 3)], [(7,)], [(5, 3), (3, 5)]] + sage: list(basis_f_odd_iterator(14)) + [(11, 3), + (5, 3, 3, 3), + (3, 5, 3, 3), + (3, 3, 5, 3), + (9, 5), + (3, 3, 3, 5), + (7, 7), + (5, 9), + (3, 11)] + """ + if n == 0: + yield tuple() + return + if n == 1: + return + if n % 2: + yield (n,) + for k in range(3, n, 2): + for start in basis_f_odd_iterator(n - k): + yield start + (k, ) + + +def basis_f_iterator(n) -> Iterator[tuple]: + """ + Return an iterator over decompositions of ``n`` using ``2,3,5,7,9,...``. + + The means that each term is made of a power of 2 and a composition + of the remaining integer with parts in ``(3,5,7,...)`` + + This set is indexing a basis of the homogeneous component of weight ``n``. + + INPUT: + + - ``n`` -- an integer + + Each term is returned as a pair (integer, word) where + the integer is the exponent of 2. + + EXAMPLES:: + + sage: from sage.modular.multiple_zeta_F_algebra import basis_f_iterator + sage: [list(basis_f_iterator(i)) for i in range(2,9)] + [[(1, word: )], + [(0, word: 3)], + [(2, word: )], + [(0, word: 5), (1, word: 3)], + [(0, word: 33), (3, word: )], + [(0, word: 7), (1, word: 5), (2, word: 3)], + [(0, word: 53), (0, word: 35), (1, word: 33), (4, word: )]] + sage: list(basis_f_iterator(11)) + [(0, word: 11), + (0, word: 533), + (0, word: 353), + (0, word: 335), + (1, word: 9), + (1, word: 333), + (2, word: 7), + (3, word: 5), + (4, word: 3)] + + TESTS:: + + sage: list(basis_f_iterator(0)) + [(0, word: )] + """ + if n and n < 2: + return + for k in range(n // 2 + 1): + for start in basis_f_odd_iterator(n - 2 * k): + yield (k, W_Odds(start, check=False)) + + +def morphism_constructor(data: dict): + """ + Build a morphism from the F-algebra to some codomain. + + INPUT: + + a dictionary containing the images of `f_2`, `f_3`, `f_5`, `f_7`, ... + + OUTPUT: + + the unique morphism defined by the dictionary ``data`` + + The codomain must be a zinbiel algebra, namely have both a + commutative associative product ``*`` and a zinbiel product + available as ``half_product``. + + EXAMPLES:: + + sage: from sage.modular.multiple_zeta_F_algebra import F_algebra, morphism_constructor + sage: F = F_algebra(QQ) + sage: Z = Multizeta + sage: D = {2: Z(2), 3: Z(3)} + sage: rho = morphism_constructor(D) + sage: rho(F("2")) + ζ(2) + sage: rho(F("3")) + ζ(3) + sage: rho(F("33")) + 6*ζ(1,5) + 3*ζ(2,4) + ζ(3,3) + sage: rho(F("23")) + 6*ζ(1,4) + 3*ζ(2,3) + ζ(3,2) + """ + im_f2 = data[2] + codomain = im_f2.parent() + domain = F_algebra(codomain.base_ring()) + + def morphism_on_basis(pw): + p, w = pw + if not w: + return im_f2**p + v = im_f2**p * data[w[-1]] + for letter in w[-2::-1]: + v = codomain.half_product(data[letter], v) + return v + + morphism = domain._module_morphism(morphism_on_basis, codomain=codomain) + + return morphism + + +class F_algebra(CombinatorialFreeModule): + r""" + Auxiliary algebra for the study of motivic multiple zeta values. + + INPUT: + + - ``R`` -- ring + + EXAMPLES:: + + sage: from sage.modular.multiple_zeta_F_algebra import F_algebra + sage: F = F_algebra(QQ); F + F-ring over Rational Field + sage: F.base_ring() + Rational Field + sage: F.is_commutative() + True + sage: TestSuite(F).run() + + sage: f2 = F("2") + sage: f3 = F("3") + sage: f5 = F("5") + + sage: s = f2*f3+f5; s + f5 + f2*f3 + """ + def __init__(self, R): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: from sage.modular.multiple_zeta_F_algebra import F_algebra + sage: F = F_algebra(QQ); F + F-ring over Rational Field + + TESTS:: + + sage: F_algebra(24) + Traceback (most recent call last): + ... + TypeError: argument R must be a ring + """ + if R not in Rings(): + raise TypeError("argument R must be a ring") + Indices = NonNegativeIntegers().cartesian_product(W_Odds) + cat = BialgebrasWithBasis(R).Commutative().Graded() + CombinatorialFreeModule.__init__(self, R, Indices, + latex_prefix="", prefix='f', + category=cat) + + def _repr_term(self, pw) -> str: + """ + Return the custom representation of terms. + + Each monomial is written as a power of `f_2` times a word + in `f_3`, `f_5`, ... + + EXAMPLES:: + + sage: from sage.modular.multiple_zeta_F_algebra import F_algebra + sage: F = F_algebra(QQ) + sage: f2 = F.gen(2) + sage: f3 = F.gen(3) + sage: f5 = F.gen(5) + sage: f2*f3+f5+f2**2 + f5 + f2*f3 + f2^2 + """ + p, w = pw + if not p: + if not w: + return "1" + resu = "" + elif p == 1: + resu = "f2" + else: + resu = f"f2^{p}" + if p and w: + resu += "*" + return resu + "".join(f"f{i}" for i in w) + + def _repr_(self) -> str: + r""" + Text representation of this algebra. + + EXAMPLES:: + + sage: from sage.modular.multiple_zeta_F_algebra import F_algebra + sage: F = F_algebra(ZZ) + sage: F # indirect doctest + F-ring over Integer Ring + """ + return f"F-ring over {self.base_ring()}" + + @cached_method + def one_basis(self): + r""" + Return the pair (0, empty word), which index of `1` of this algebra. + + EXAMPLES:: + + sage: from sage.modular.multiple_zeta_F_algebra import F_algebra + sage: A = F_algebra(QQ) + sage: A.one_basis() + (0, word: ) + """ + return self.basis().keys()((0, [])) + + def product_on_basis(self, pw1, pw2): + r""" + Return the product of basis elements ``pw1`` and ``pw2``. + + INPUT: + + - ``pw1``, ``pw2`` -- basis elements + + EXAMPLES:: + + sage: from sage.modular.multiple_zeta_F_algebra import F_algebra + sage: A = F_algebra(QQ) + sage: W = A.basis().keys() + sage: A.product(A("23"), A("25")) + f2^2*f3f5 + f2^2*f5f3 + """ + p1, w1 = pw1 + p2, w2 = pw2 + p = p1 + p2 + return self.sum_of_monomials((p, u) for u in w1.shuffle(w2)) + + def half_product_on_basis(self, pw1, pw2): + r""" + Return the half product of basis elements ``pw1`` and ``pw2``. + + This is an extension of the zinbiel product of the shuffle algebra. + + INPUT: + + - ``pw1``, ``pw2`` -- Basis elements + + EXAMPLES:: + + sage: from sage.modular.multiple_zeta_F_algebra import F_algebra + sage: A = F_algebra(QQ) + sage: W = A.basis().keys() + sage: t = A.half_product(A("23"), A("25")); t + f2^2*f3f5 + + TESTS:: + + sage: [list(pw[1]) for pw, cf in t] + [[3, 5]] + """ + p1, w1 = pw1 + p2, w2 = pw2 + p = p1 + p2 + if not w1: + return self.basis()[(p, w2)] + letter = w1[:1] + return self.sum_of_monomials((p, letter + u) + for u in w1[1:].shuffle(w2)) + + @lazy_attribute + def half_product(self): + r""" + Return the `<` product. + + EXAMPLES:: + + sage: from sage.modular.multiple_zeta_F_algebra import F_algebra + sage: A = F_algebra(QQ) + sage: W = A.basis().keys() + sage: A.half_product(A("235"), A("227")) + f2^3*f3f5f7 + f2^3*f3f7f5 + """ + half = self.half_product_on_basis + return self._module_morphism(self._module_morphism(half, position=0, + codomain=self), + position=1) + + def gen(self, i): + r""" + Return the generator of the F ring over `\QQ`. + + INPUT: + + - ``i`` -- a nonnegative integer (at least 2) + + If ``i`` is odd, this returns a single generator `f_i` of the free + shuffle algebra. + + Otherwise, it returns an appropriate multiple of a power of `f_2`. + + EXAMPLES:: + + sage: from sage.modular.multiple_zeta_F_algebra import F_algebra + sage: A = F_algebra(QQ) + sage: [A.gen(i) for i in range(2,8)] + [f2, f3, 2/5*f2^2, f5, 8/35*f2^3, f7] + """ + f2 = self.monomial(self._indices((1, []))) + if i == 2: + return f2 + # now i odd >= 3 + if i % 2: + return self.monomial(self._indices((0, [i]))) + # now powers of f2 + i = i // 2 + B = bernoulli(2 * i) * (-1)**(i - 1) + B *= ZZ(2)**(3 * i - 1) * ZZ(3)**i / ZZ(2 * i).factorial() + return B * f2**i + + def an_element(self): + """ + Return a typical element. + + EXAMPLES:: + + sage: from sage.modular.multiple_zeta_F_algebra import F_algebra + sage: F = F_algebra(ZZ) + sage: F.an_element() + 3*f2*f3f5 + f2*f5f3 + """ + return self("253") + 3 * self("235") + + def some_elements(self): + """ + Return some typical elements. + + EXAMPLES:: + + sage: from sage.modular.multiple_zeta_F_algebra import F_algebra + sage: F = F_algebra(ZZ) + sage: F.some_elements() + [0, 1, f2, f3 + f5] + """ + return [self.zero(), self.one(), self.gen(2), + self.gen(3) + self.gen(5)] + + def coproduct_on_basis(self, pw): + r""" + Return the coproduct of the basis element indexed by the pair ``pw``. + + The coproduct is given by deconcatenation on the shuffle part, + and extended by the value + + .. MATH:: + + \Delta(f_2) = 1 \otimes f_2. + + INPUT: + + - ``pw`` -- an index + + EXAMPLES:: + + sage: from sage.modular.multiple_zeta_F_algebra import F_algebra + sage: F = F_algebra(QQ) + sage: W = F.basis().keys() + sage: F.coproduct_on_basis(W((1,[]))) + 1 # f2 + sage: F.coproduct_on_basis(W((0,[3]))) + 1 # f3 + f3 # 1 + sage: F.coproduct_on_basis(W((1,[3]))) + 1 # f2*f3 + f3 # f2 + sage: F.coproduct_on_basis(W((0,[3,5]))) + 1 # f3f5 + f3 # f5 + f3f5 # 1 + sage: F.coproduct_on_basis(W((0,[]))) + 1 # 1 + + TESTS:: + + sage: F = F_algebra(QQ) + sage: S = F.an_element(); S + 3*f2*f3f5 + f2*f5f3 + sage: F.coproduct(S) + 3*1 # f2*f3f5 + 1 # f2*f5f3 + 3*f3 # f2*f5 + 3*f3f5 # f2 + + f5 # f2*f3 + f5f3 # f2 + """ + p, w = pw + TS = self.tensor_square() + return TS.sum_of_monomials(((0, w[:i]), (p, w[i:])) + for i in range(len(w) + 1)) + + def degree_on_basis(self, pw): + """ + Return the degree of the element ``w``. + + This is the sum of the power of `f_2` and the indices in the word. + + EXAMPLES:: + + sage: from sage.modular.multiple_zeta_F_algebra import F_algebra + sage: A = F_algebra(QQ) + sage: [A.degree_on_basis(x.leading_support()) for x in A.some_elements() if x != 0] + [0, 1, 5] + """ + p, w = pw + return ZZ(p + sum(w)) + + def homogeneous_from_vector(self, vec, N): + """ + Convert back a vector to an element of the F-algebra. + + INPUT: + + - ``vec`` -- a vector with coefficients in some base ring + + - ``N`` -- integer, the homogeneous weight + + OUTPUT: + + an homogeneous element of :func:`F_ring` over this base ring + + .. SEEALSO:: :meth:`F_algebra.homogeneous_to_vector` + + EXAMPLES:: + + sage: from sage.modular.multiple_zeta_F_algebra import F_algebra + sage: F = F_algebra(QQ) + sage: F.homogeneous_from_vector((4,5),6) + 4*f3f3 + 5*f2^3 + sage: _.homogeneous_to_vector() + (4, 5) + """ + if isinstance(vec, (list, tuple)): + vec = vector(vec) + return self.sum(cf * self.monomial(bi) + for cf, bi in zip(vec, basis_f_iterator(N))) + + def _element_constructor_(self, x): + r""" + Convert ``x`` into ``self``. + + EXAMPLES:: + + sage: from sage.modular.multiple_zeta_F_algebra import F_algebra + sage: R = F_algebra(QQ) + sage: R("3") + f3 + sage: R("2") + f2 + sage: R("2235") + f2^2*f3f5 + """ + if isinstance(x, (str, FiniteWord_class)): + return self.monomial(self._indices(str_to_index(x))) + + P = x.parent() + if isinstance(P, F_algebra): + if P is self: + return x + if P is not self.base_ring(): + return self.element_class(self, x.monomial_coefficients()) + + R = self.base_ring() + # coercion via base ring + x = R(x) + if x == 0: + return self.element_class(self, {}) + return self.from_base_ring_from_one_basis(x) + + def _coerce_map_from_(self, R): + r""" + Return ``True`` if there is a coercion from ``R`` into ``self`` + and ``False`` otherwise. + + The things that coerce into ``self`` are + + - an ``F_algebra`` over a base with a coercion + map into ``self.base_ring()``. + + - Anything with a coercion into ``self.base_ring()``. + + EXAMPLES:: + + sage: from sage.modular.multiple_zeta_F_algebra import F_algebra + sage: F = F_algebra(GF(7)); F + F-ring over Finite Field of size 7 + + Elements of the algebra itself canonically coerce in:: + + sage: F.coerce(F("2")*F("3")) # indirect doctest + f2*f3 + + Elements of the integers coerce in, since there is a coerce map + from `\ZZ` to GF(7):: + + sage: F.coerce(1) # indirect doctest + 1 + + There is no coerce map from `\QQ` to `\GF{7}`:: + + sage: F.coerce(2/3) # indirect doctest + Traceback (most recent call last): + ... + TypeError: no canonical coercion from Rational Field + to F-ring over Finite Field of size 7 + + Elements of the base ring coerce in:: + + sage: F.coerce(GF(7)(5)) + 5 + + The algebra over `\ZZ` coerces in, since + `\ZZ` coerces to `\GF{7}`:: + + sage: G = F_algebra(ZZ) + sage: Gx,Gy = G.gen(2), G.gen(3) + sage: z = F.coerce(Gx**2 * Gy);z + f2^2*f3 + sage: z.parent() is F + True + + However, `\GF{7}` does not coerce to `\ZZ`, so the + algebra over `\GF{7}` does not coerce to the one over `\ZZ`:: + + sage: G.coerce(F("2")) + Traceback (most recent call last): + ... + TypeError: no canonical coercion from F-ring over Finite Field + of size 7 to F-ring over Integer Ring + + TESTS:: + + sage: F = F_algebra(ZZ) + sage: G = F_algebra(QQ) + + sage: F._coerce_map_from_(G) + False + sage: G._coerce_map_from_(F) + True + + sage: F._coerce_map_from_(QQ) + False + sage: G._coerce_map_from_(QQ) + True + + sage: F.has_coerce_map_from(PolynomialRing(ZZ, 3, 'x,y,z')) + False + """ + if isinstance(R, F_algebra): + return self.base_ring().has_coerce_map_from(R.base_ring()) + + return self.base_ring().has_coerce_map_from(R) + + class Element(CombinatorialFreeModule.Element): + def coefficient(self, w): + """ + Return the coefficient of the given index. + + EXAMPLES:: + + sage: from sage.modular.multiple_zeta_F_algebra import F_algebra + sage: F = F_algebra(QQ) + sage: S = F.an_element(); S + 3*f2*f3f5 + f2*f5f3 + sage: S.coefficient("235") + 3 + sage: S.coefficient((1,[5,3])) + 1 + """ + if isinstance(w, str): + w = str_to_index(w) + w = self.parent()._indices(w) + return super().coefficient(w) + + def homogeneous_to_vector(self): + """ + Convert an homogeneous element to a vector. + + This is using a fixed enumeration of the basis. + + OUTPUT: + + a vector with coefficients in the base ring + + .. SEEALSO:: :meth:`F_algebra.homogeneous_from_vector` + + EXAMPLES:: + + sage: from sage.modular.multiple_zeta_F_algebra import F_algebra + sage: F = F_algebra(QQ) + sage: f2 = F("2") + sage: x = f2**4 + 34 * F("233") + sage: x.homogeneous_to_vector() + (0, 0, 34, 1) + sage: x.coefficients() + [34, 1] + + TESTS:: + + sage: x = F.monomial(F._indices((0,[11]))); x + f11 + sage: x.homogeneous_to_vector() + (1, 0, 0, 0, 0, 0, 0, 0, 0) + """ + F = self.parent() + BR = F.base_ring() + if not self: + return vector(BR, []) + a, b = next(iter(self))[0] + N = 2 * a + sum(int(x) for x in b) + return vector(BR, [self.coefficient(b) + for b in basis_f_iterator(N)]) + + def without_f2(self): + """ + Remove all terms containing a power of `f_2`. + + EXAMPLES:: + + sage: from sage.modular.multiple_zeta_F_algebra import F_algebra + sage: F = F_algebra(QQ) + sage: t = 4*F("35")+F("27") + sage: t.without_f2() + 4*f3f5 + """ + F = self.parent() + return F._from_dict({(0, w): cf for (p, w), cf in self if not p}) diff --git a/src/sage/modular/overconvergent/genus0.py b/src/sage/modular/overconvergent/genus0.py index 0e30f40d23b..8f903f76b7c 100644 --- a/src/sage/modular/overconvergent/genus0.py +++ b/src/sage/modular/overconvergent/genus0.py @@ -202,11 +202,12 @@ __ocmfdict = {} + #################### # Factory function # #################### -def OverconvergentModularForms(prime, weight, radius, base_ring=QQ, prec = 20, char = None): +def OverconvergentModularForms(prime, weight, radius, base_ring=QQ, prec=20, char=None): r""" Create a space of overconvergent `p`-adic modular forms of level `\Gamma_0(p)`, over the given base ring. The base ring need not be a @@ -316,7 +317,7 @@ def __init__(self, prime, weight, radius, base_ring, prec, char): if isinstance(weight, WeightCharacter): self._wtchar = weight else: - self._wtchar = WeightSpace(prime, base_ring = char.base_ring())(weight, char, algebraic=True) + self._wtchar = WeightSpace(prime, base_ring=char.base_ring())(weight, char, algebraic=True) if not self._wtchar.is_even(): raise ValueError("Weight-character must be even") @@ -951,7 +952,7 @@ def _convert_to_basis(self, qexp): x = x - self._basis_cache[i] * answer[i] return answer + O(g**n) - def hecke_matrix(self, m, n, use_recurrence = False, exact_arith = False): + def hecke_matrix(self, m, n, use_recurrence=False, exact_arith=False): r""" Calculate the matrix of the `T_m` operator in the basis of this space, truncated to an `n \times n` matrix. Conventions are that operators act @@ -1052,12 +1053,14 @@ def slopes(self, n, use_recurrence=False): print("slopes are only defined for base field QQ or a p-adic field") return [-i for i in slopelist] - def eigenfunctions(self, n, F = None, exact_arith=True): + def eigenfunctions(self, n, F=None, exact_arith=True): """ - Calculate approximations to eigenfunctions of self. These are the - eigenfunctions of self.hecke_matrix(p, n), which are approximations to - the true eigenfunctions. Returns a list of - OverconvergentModularFormElement objects, in increasing order of slope. + Calculate approximations to eigenfunctions of self. + + These are the eigenfunctions of self.hecke_matrix(p, n), which + are approximations to the true eigenfunctions. Returns a list + of OverconvergentModularFormElement objects, in increasing + order of slope. INPUT: @@ -1201,7 +1204,7 @@ def recurrence_matrix(self, use_smithline=True): return self._cached_recurrence_matrix MM = OverconvergentModularForms(self.prime(), 0, 0, base_ring=QQ) - m = MM._discover_recurrence_matrix(use_smithline = True).base_extend(self.base_ring()) + m = MM._discover_recurrence_matrix(use_smithline=True).base_extend(self.base_ring()) r = diagonal_matrix([self._const**i for i in range(self.prime())]) self._cached_recurrence_matrix = (r**(-1)) * m * r @@ -1339,7 +1342,7 @@ def _add_(self, other): sage: f + f # indirect doctest 2-adic overconvergent modular form of weight-character 12 with q-expansion 2 - 131040/1414477*q ... """ - return OverconvergentModularFormElement(self.parent(), gexp = self.gexp() + other.gexp()) + return OverconvergentModularFormElement(self.parent(), gexp=self.gexp() + other.gexp()) def _lmul_(self, x): r""" @@ -1353,7 +1356,7 @@ def _lmul_(self, x): 2-adic overconvergent modular form of weight-character 12 with q-expansion 2 - 131040/1414477*q ... """ - return OverconvergentModularFormElement(self.parent(), gexp = x * self.gexp()) + return OverconvergentModularFormElement(self.parent(), gexp=x * self.gexp()) def _rmul_(self, x): r""" @@ -1367,7 +1370,7 @@ def _rmul_(self, x): 2-adic overconvergent modular form of weight-character 12 with q-expansion 3 - 196560/1414477*q ... """ - return OverconvergentModularFormElement(self.parent(), gexp = x * self.gexp()) + return OverconvergentModularFormElement(self.parent(), gexp=x * self.gexp()) def prec(self): r""" @@ -1636,7 +1639,7 @@ def governing_term(self, r): return i raise RuntimeError("Can't get here") - def valuation_plot(self, rmax = None): + def valuation_plot(self, rmax=None): r""" Draw a graph depicting the growth of the norm of this overconvergent modular form as it approaches the boundary of the overconvergent @@ -1650,8 +1653,8 @@ def valuation_plot(self, rmax = None): Graphics object consisting of 1 graphics primitive """ if rmax is None: - rmax = ZZ(self.prime())/ZZ(1 + self.prime()) - return plot(self.r_ord, (0, rmax) ) + rmax = ZZ(self.prime()) / ZZ(1 + self.prime()) + return plot(self.r_ord, (0, rmax)) def weight(self): r""" diff --git a/src/sage/modular/pollack_stevens/manin_map.py b/src/sage/modular/pollack_stevens/manin_map.py index 74de1b039f3..70465dade8f 100644 --- a/src/sage/modular/pollack_stevens/manin_map.py +++ b/src/sage/modular/pollack_stevens/manin_map.py @@ -762,7 +762,7 @@ def specialize(self, *args): return self.__class__(self._codomain.specialize(*args), self._manin, D, check=False) - def hecke(self, ell, algorithm = 'prep'): + def hecke(self, ell, algorithm='prep'): r""" Return the image of this Manin map under the Hecke operator `T_{\ell}`. 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/pollack_stevens/space.py b/src/sage/modular/pollack_stevens/space.py index 8bb41867770..99bff5e6890 100644 --- a/src/sage/modular/pollack_stevens/space.py +++ b/src/sage/modular/pollack_stevens/space.py @@ -851,7 +851,7 @@ def cusps_from_mat(g): return ac, bd -def ps_modsym_from_elliptic_curve(E, sign = 0, implementation='eclib'): +def ps_modsym_from_elliptic_curve(E, sign=0, implementation='eclib'): r""" Return the overconvergent modular symbol associated to an elliptic curve defined over the rationals. 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/free_module.py b/src/sage/modules/free_module.py index d508b22fcc7..47fb713d522 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -193,6 +193,7 @@ 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 @@ -868,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) @@ -1782,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. @@ -1799,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): """ @@ -3500,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. @@ -3511,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""" @@ -3594,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. @@ -3605,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): """ @@ -4180,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. @@ -4219,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. @@ -4240,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""" @@ -4994,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. @@ -5207,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. @@ -5243,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): """ @@ -5912,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. @@ -5922,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): """ @@ -6081,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. @@ -6114,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): """ @@ -6173,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. @@ -6191,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): """ @@ -6352,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. @@ -6382,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) @@ -6405,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) @@ -6698,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 @@ -7321,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. @@ -7336,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): """ @@ -7474,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. @@ -7490,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): """ @@ -7671,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. @@ -7688,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): """ @@ -8027,4 +8243,3 @@ def __richcmp__(self, other, op): True """ return self.obj._echelon_matrix_richcmp(other.obj, op) - diff --git a/src/sage/modules/free_module_element.pyx b/src/sage/modules/free_module_element.pyx index de627d5939a..67991cebd52 100644 --- a/src/sage/modules/free_module_element.pyx +++ b/src/sage/modules/free_module_element.pyx @@ -3743,7 +3743,7 @@ cdef class FreeModuleElement(Vector): # abstract base class from sage.misc.latex import latex vector_delimiters = latex.vector_delimiters() s = '\\left' + vector_delimiters[0] - s += ',\,'.join(latex(a) for a in self.list()) + s += r',\,'.join(latex(a) for a in self.list()) return s + '\\right' + vector_delimiters[1] def dense_vector(self): diff --git a/src/sage/modules/module_functors.py b/src/sage/modules/module_functors.py index 5e84c79008d..497120d02e8 100644 --- a/src/sage/modules/module_functors.py +++ b/src/sage/modules/module_functors.py @@ -75,7 +75,7 @@ class QuotientModuleFunctor(ConstructionFunctor): sage: Q2 = A2 / B2 sage: q3 = Q1.an_element() + Q2.an_element() """ - rank = 5 # ranking of functor, not rank of module + rank = 5 # ranking of functor, not rank of module def __init__(self, relations): """ @@ -187,4 +187,3 @@ def merge(self, other): """ if isinstance(other, QuotientModuleFunctor): return QuotientModuleFunctor(self._relations + other._relations) - 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/submodule.py b/src/sage/modules/submodule.py index f8078c4066e..4e0d6994f02 100644 --- a/src/sage/modules/submodule.py +++ b/src/sage/modules/submodule.py @@ -243,4 +243,3 @@ def ambient_module(self): True """ return self._ambient - 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/cell_module.py b/src/sage/modules/with_basis/cell_module.py index 5c6145d167e..a17882b4e9e 100644 --- a/src/sage/modules/with_basis/cell_module.py +++ b/src/sage/modules/with_basis/cell_module.py @@ -473,4 +473,3 @@ def _acted_upon_(self, scalar, self_on_left=False): # For backward compatibility _lmul_ = _acted_upon_ _rmul_ = _acted_upon_ - diff --git a/src/sage/modules/with_basis/indexed_element.pyx b/src/sage/modules/with_basis/indexed_element.pyx index cd1f17112f0..d5ccebde9ef 100644 --- a/src/sage/modules/with_basis/indexed_element.pyx +++ b/src/sage/modules/with_basis/indexed_element.pyx @@ -8,7 +8,7 @@ AUTHORS: - Travis Scrimshaw (29-08-2022): Implemented ``copy`` as the identity map. """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2017, 2022 Travis Scrimshaw # # This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ AUTHORS: # 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 @@ -25,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:: @@ -60,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:: @@ -80,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 @@ -93,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): """ @@ -129,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 @@ -148,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:: @@ -178,7 +199,7 @@ 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): @@ -257,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:: @@ -274,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]), @@ -361,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 = [] @@ -620,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): @@ -938,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 f180f442c19..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 @@ -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:: diff --git a/src/sage/modules/with_basis/morphism.py b/src/sage/modules/with_basis/morphism.py index b8fe98111b2..3cd841629a9 100644 --- a/src/sage/modules/with_basis/morphism.py +++ b/src/sage/modules/with_basis/morphism.py @@ -1551,6 +1551,7 @@ def pointwise_inverse_function(f): return f.pointwise_inverse() return PointwiseInverseFunction(f) + from sage.structure.sage_object import SageObject class PointwiseInverseFunction(SageObject): r""" @@ -1630,4 +1631,3 @@ def pointwise_inverse(self): True """ return self._pointwise_inverse - diff --git a/src/sage/monoids/free_abelian_monoid.py b/src/sage/monoids/free_abelian_monoid.py index d1a5dce30c1..54771b247e4 100644 --- a/src/sage/monoids/free_abelian_monoid.py +++ b/src/sage/monoids/free_abelian_monoid.py @@ -308,4 +308,3 @@ def cardinality(self): return ZZ.one() from sage.rings.infinity import infinity return infinity - diff --git a/src/sage/monoids/free_monoid_element.py b/src/sage/monoids/free_monoid_element.py index 465adf1dd1a..f23dba93b45 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 @@ -395,7 +408,6 @@ def to_list(self, indices=False): :meth:`to_word` """ if not indices: - return sum( ([i[0]] * i[1] for i in list(self)), []) + return sum(([i[0]] * i[1] for i in list(self)), []) gens = self.parent().gens() - return sum( ([gens.index(i[0])] * i[1] for i in list(self)), []) - + return sum(([gens.index(i[0])] * i[1] for i in list(self)), []) diff --git a/src/sage/monoids/indexed_free_monoid.py b/src/sage/monoids/indexed_free_monoid.py index cda2681c5be..58910533a9a 100644 --- a/src/sage/monoids/indexed_free_monoid.py +++ b/src/sage/monoids/indexed_free_monoid.py @@ -1002,7 +1002,6 @@ def gen(self, x): if x not in self._indices: raise IndexError("{} is not in the index set".format(x)) try: - return self.element_class(self, {self._indices(x):1}) - except (TypeError, NotImplementedError): # Backup (e.g., if it is a string) - return self.element_class(self, {x:1}) - + return self.element_class(self, {self._indices(x): 1}) + except (TypeError, NotImplementedError): # Backup (e.g., if it is a string) + return self.element_class(self, {x: 1}) diff --git a/src/sage/monoids/monoid.py b/src/sage/monoids/monoid.py index 4ba247e7d99..31e6c219f89 100644 --- a/src/sage/monoids/monoid.py +++ b/src/sage/monoids/monoid.py @@ -70,4 +70,3 @@ def monoid_generators(self): """ from sage.sets.family import Family return Family(self.gens()) - diff --git a/src/sage/numerical/linear_tensor.py b/src/sage/numerical/linear_tensor.py index bdf9529659a..a210ff52fd7 100644 --- a/src/sage/numerical/linear_tensor.py +++ b/src/sage/numerical/linear_tensor.py @@ -478,7 +478,4 @@ def _an_element_(self): (1.0, 0.0) + (5.0, 0.0)*x_2 + (7.0, 0.0)*x_5 """ m = self.free_module().an_element() - return self._element_constructor_({-1:m, 2:5*m, 5:7*m}) - - - + return self._element_constructor_({-1: m, 2: 5 * m, 5: 7 * m}) diff --git a/src/sage/numerical/linear_tensor_constraints.py b/src/sage/numerical/linear_tensor_constraints.py index ce09731cc6e..3da0cd4719a 100644 --- a/src/sage/numerical/linear_tensor_constraints.py +++ b/src/sage/numerical/linear_tensor_constraints.py @@ -446,7 +446,7 @@ def _element_constructor_(self, left, right, equality): def _an_element_(self): """ - Returns an element + Return an element. EXAMPLES:: @@ -457,4 +457,3 @@ def _an_element_(self): """ LT = self.linear_tensors() return LT.an_element() >= 0 - diff --git a/src/sage/plot/bar_chart.py b/src/sage/plot/bar_chart.py index 1f0d7d62cf7..34ed242af4c 100644 --- a/src/sage/plot/bar_chart.py +++ b/src/sage/plot/bar_chart.py @@ -1,5 +1,5 @@ """ -Bar Charts +Bar charts """ # ***************************************************************************** diff --git a/src/sage/plot/bezier_path.py b/src/sage/plot/bezier_path.py index 99290dc9a9d..27c95f10c0c 100644 --- a/src/sage/plot/bezier_path.py +++ b/src/sage/plot/bezier_path.py @@ -1,5 +1,5 @@ r""" -Bezier Paths +Bezier paths """ #***************************************************************************** # Copyright (C) 2006 Alex Clemesha , diff --git a/src/sage/plot/complex_plot.pyx b/src/sage/plot/complex_plot.pyx index 47617d9a353..6f0aeab87ae 100644 --- a/src/sage/plot/complex_plot.pyx +++ b/src/sage/plot/complex_plot.pyx @@ -1,5 +1,5 @@ """ -Complex Plots +Complex plots AUTHORS: diff --git a/src/sage/plot/contour_plot.py b/src/sage/plot/contour_plot.py index 3cf3d0f932d..c0cab456686 100644 --- a/src/sage/plot/contour_plot.py +++ b/src/sage/plot/contour_plot.py @@ -1,5 +1,5 @@ """ -Contour Plots +Contour plots """ # **************************************************************************** # Copyright (C) 2006 Alex Clemesha , diff --git a/src/sage/plot/density_plot.py b/src/sage/plot/density_plot.py index 741b5d9be00..155ac8d7a8e 100644 --- a/src/sage/plot/density_plot.py +++ b/src/sage/plot/density_plot.py @@ -1,5 +1,5 @@ """ -Density Plots +Density plots """ #***************************************************************************** diff --git a/src/sage/plot/line.py b/src/sage/plot/line.py index e4b3b4ddc82..691e3c31307 100644 --- a/src/sage/plot/line.py +++ b/src/sage/plot/line.py @@ -1,5 +1,5 @@ """ -Line Plots +Line plots """ # ***************************************************************************** diff --git a/src/sage/plot/matrix_plot.py b/src/sage/plot/matrix_plot.py index e15b007184e..f62d362ea11 100644 --- a/src/sage/plot/matrix_plot.py +++ b/src/sage/plot/matrix_plot.py @@ -1,5 +1,5 @@ """ -Matrix Plots +Matrix plots """ #***************************************************************************** # Copyright (C) 2006 Alex Clemesha , diff --git a/src/sage/plot/misc.py b/src/sage/plot/misc.py index a64457ef661..37a8000f9aa 100644 --- a/src/sage/plot/misc.py +++ b/src/sage/plot/misc.py @@ -1,4 +1,6 @@ -"Plotting utilities" +""" +Plotting utilities +""" # **************************************************************************** # Distributed under the terms of the GNU General Public License (GPL) diff --git a/src/sage/plot/plot.py b/src/sage/plot/plot.py index d41b4bad908..b36ee57227c 100644 --- a/src/sage/plot/plot.py +++ b/src/sage/plot/plot.py @@ -1,5 +1,5 @@ r""" -2D Plotting +2D plotting Sage provides extensive 2D plotting functionality. The underlying rendering is done using the matplotlib Python library. diff --git a/src/sage/plot/plot3d/base.pyx b/src/sage/plot/plot3d/base.pyx index ae1fd739499..77dabadc78e 100644 --- a/src/sage/plot/plot3d/base.pyx +++ b/src/sage/plot/plot3d/base.pyx @@ -1,5 +1,5 @@ r""" -Base Classes for 3D Graphics Objects and Plotting +Base classes for 3D graphics objects and plotting The most important facts about these classes are that you can simply add graphics objects @@ -9,7 +9,7 @@ choosing a viewer and setting various parameters for displaying the graphics. Most of the other methods of these classes are technical and -for special usage. +for special usage. AUTHORS: @@ -999,7 +999,7 @@ cdef class Graphics3d(SageObject): "camera_position","updir", # "look_at", # omit look_at. viewdir is sufficient for most purposes "viewdir") - + # The tachyon "aspectratio" parameter is outdated for normal users: # From the tachyon documentation: # "By using the aspect ratio parameter, one can produce images which look @@ -1085,7 +1085,7 @@ cdef class Graphics3d(SageObject): updir = _flip_orientation(updir) camera_position = _flip_orientation(camera_position) light_position = _flip_orientation(light_position) - + return """ begin_scene resolution {resolution_x:d} {resolution_y:d} @@ -1167,9 +1167,9 @@ end_scene""".format( def _tostring(s): r""" Converts vector information to a space-separated string. - + EXAMPLES:: - + sage: sage.plot.plot3d.base.Graphics3d._tostring((1.0,1.2,-1.3)) '1.00000000000000 1.20000000000000 -1.30000000000000' """ @@ -1682,7 +1682,7 @@ end_scene""".format( the viewpoint, with respect to the cube $[-1,1]\\times[-1,1]\\times[-1,1]$, into which the bounding box of the scene - is scaled and centered. + is scaled and centered. The default viewing direction is towards the origin. - ``viewdir`` (for tachyon) -- (default: None) three coordinates @@ -1713,7 +1713,7 @@ end_scene""".format( * ``'lowest'``: worst quality rendering, preview (and fastest). Sets tachyon command line flag ``-lowestshade``. - + - ``extra_opts`` (for tachyon) -- string (default: empty string); extra options that will be appended to the tachyon command line. diff --git a/src/sage/plot/plot3d/implicit_plot3d.py b/src/sage/plot/plot3d/implicit_plot3d.py index 2eb18f7d64d..aa05059878d 100644 --- a/src/sage/plot/plot3d/implicit_plot3d.py +++ b/src/sage/plot/plot3d/implicit_plot3d.py @@ -1,5 +1,5 @@ """ -Implicit Plots +Implicit plots """ from .implicit_surface import ImplicitSurface diff --git a/src/sage/plot/plot3d/implicit_surface.pyx b/src/sage/plot/plot3d/implicit_surface.pyx index aaa2db0c3a8..0a2d99cb5a7 100644 --- a/src/sage/plot/plot3d/implicit_surface.pyx +++ b/src/sage/plot/plot3d/implicit_surface.pyx @@ -1,5 +1,5 @@ r""" -Graphics 3D Object for Representing and Triangulating Isosurfaces. +Graphics 3D object for representing and triangulating isosurfaces AUTHORS: diff --git a/src/sage/plot/plot3d/index_face_set.pyx b/src/sage/plot/plot3d/index_face_set.pyx index 867529d59c5..642397e9ecf 100644 --- a/src/sage/plot/plot3d/index_face_set.pyx +++ b/src/sage/plot/plot3d/index_face_set.pyx @@ -1,5 +1,5 @@ """ -Indexed Face Sets +Indexed face sets Graphics3D object that consists of a list of polygons, also used for triangulations of other objects. diff --git a/src/sage/plot/plot3d/list_plot3d.py b/src/sage/plot/plot3d/list_plot3d.py index 417b9a3528a..c8179d036c5 100644 --- a/src/sage/plot/plot3d/list_plot3d.py +++ b/src/sage/plot/plot3d/list_plot3d.py @@ -1,5 +1,5 @@ """ -List Plots +List plots """ from sage.structure.element import is_Matrix diff --git a/src/sage/plot/plot3d/parametric_plot3d.py b/src/sage/plot/plot3d/parametric_plot3d.py index f0411e199fe..e16df517fb8 100644 --- a/src/sage/plot/plot3d/parametric_plot3d.py +++ b/src/sage/plot/plot3d/parametric_plot3d.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ -Parametric Plots +Parametric plots """ from .parametric_surface import ParametricSurface diff --git a/src/sage/plot/plot3d/parametric_surface.pyx b/src/sage/plot/plot3d/parametric_surface.pyx index ad6ea410e4d..68b388b587e 100644 --- a/src/sage/plot/plot3d/parametric_surface.pyx +++ b/src/sage/plot/plot3d/parametric_surface.pyx @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- r""" -Parametric Surface +Parametric surface Graphics 3D object for triangulating surfaces, and a base class for many other objects that can be represented by a 2D parametrization. diff --git a/src/sage/plot/plot3d/platonic.py b/src/sage/plot/plot3d/platonic.py index ef9a22aabdd..eee95d3290f 100644 --- a/src/sage/plot/plot3d/platonic.py +++ b/src/sage/plot/plot3d/platonic.py @@ -1,5 +1,5 @@ r""" -Platonic Solids +Platonic solids EXAMPLES: The five platonic solids in a row: diff --git a/src/sage/plot/plot3d/plot3d.py b/src/sage/plot/plot3d/plot3d.py index 44620a0edcb..a2bddd8a3a0 100644 --- a/src/sage/plot/plot3d/plot3d.py +++ b/src/sage/plot/plot3d/plot3d.py @@ -1,5 +1,5 @@ r""" -Plotting Functions +Plotting functions EXAMPLES:: diff --git a/src/sage/plot/plot3d/plot_field3d.py b/src/sage/plot/plot3d/plot_field3d.py index 4f1a28cc197..bdf39391d3e 100644 --- a/src/sage/plot/plot3d/plot_field3d.py +++ b/src/sage/plot/plot3d/plot_field3d.py @@ -1,5 +1,5 @@ """ -Plotting 3D Fields +Plotting 3D fields """ #***************************************************************************** # Copyright (C) 2009 Jason Grout diff --git a/src/sage/plot/plot3d/texture.py b/src/sage/plot/plot3d/texture.py index deb400773d7..c2aa4f652d1 100644 --- a/src/sage/plot/plot3d/texture.py +++ b/src/sage/plot/plot3d/texture.py @@ -1,5 +1,5 @@ r""" -Texture Support +Texture support This module provides texture/material support for 3D Graphics objects and plotting. This is a very rough common interface for diff --git a/src/sage/plot/plot3d/transform.pyx b/src/sage/plot/plot3d/transform.pyx index 7e91d30823b..b140a1c1c41 100644 --- a/src/sage/plot/plot3d/transform.pyx +++ b/src/sage/plot/plot3d/transform.pyx @@ -1,4 +1,6 @@ -"Transformations" +""" +Transformations +""" #***************************************************************************** # Copyright (C) 2007 Robert Bradshaw diff --git a/src/sage/plot/scatter_plot.py b/src/sage/plot/scatter_plot.py index 83e1836479b..c78b33a5258 100644 --- a/src/sage/plot/scatter_plot.py +++ b/src/sage/plot/scatter_plot.py @@ -1,5 +1,5 @@ """ -Scatter Plots +Scatter plots """ # ***************************************************************************** diff --git a/src/sage/plot/streamline_plot.py b/src/sage/plot/streamline_plot.py index 8e34bcc297b..bd9156bf000 100644 --- a/src/sage/plot/streamline_plot.py +++ b/src/sage/plot/streamline_plot.py @@ -1,5 +1,5 @@ """ -Streamline Plots +Streamline plots """ # **************************************************************************** # Copyright (C) 2006 Alex Clemesha , diff --git a/src/sage/quivers/homspace.py b/src/sage/quivers/homspace.py index 296ae15129c..cb9a36e3eed 100644 --- a/src/sage/quivers/homspace.py +++ b/src/sage/quivers/homspace.py @@ -17,6 +17,7 @@ # # https://www.gnu.org/licenses/ # **************************************************************************** +from __future__ import annotations from sage.categories.homset import Homset from sage.quivers.morphism import QuiverRepHom from sage.misc.cachefunc import cached_method @@ -53,8 +54,8 @@ class QuiverHomSpace(Homset): sage: H.dimension() 2 sage: H.gens() - [Homomorphism of representations of Multi-digraph on 2 vertices, - Homomorphism of representations of Multi-digraph on 2 vertices] + (Homomorphism of representations of Multi-digraph on 2 vertices, + Homomorphism of representations of Multi-digraph on 2 vertices) """ Element = QuiverRepHom @@ -499,25 +500,25 @@ def dimension(self): """ return self._space.dimension() - def gens(self): + def gens(self) -> tuple: """ - Return a list of generators of the hom space (as a `k`-vector + Return a tuple of generators of the hom space (as a `k`-vector space). OUTPUT: - - list of :class:`QuiverRepHom` objects, the generators + - tuple of :class:`QuiverRepHom` objects, the generators EXAMPLES:: sage: Q = DiGraph({1:{2:['a', 'b']}}).path_semigroup() sage: H = Q.S(QQ, 2).Hom(Q.P(QQ, 1)) sage: H.gens() - [Homomorphism of representations of Multi-digraph on 2 vertices, - Homomorphism of representations of Multi-digraph on 2 vertices] + (Homomorphism of representations of Multi-digraph on 2 vertices, + Homomorphism of representations of Multi-digraph on 2 vertices) """ - return [self.element_class(self._domain, self._codomain, f) - for f in self._space.gens()] + return tuple([self.element_class(self._domain, self._codomain, f) + for f in self._space.gens()]) def coordinates(self, hom): """ diff --git a/src/sage/quivers/representation.py b/src/sage/quivers/representation.py index 78ed8ce6d2a..859ff1b10b1 100644 --- a/src/sage/quivers/representation.py +++ b/src/sage/quivers/representation.py @@ -447,7 +447,7 @@ # # https://www.gnu.org/licenses/ # **************************************************************************** - +from __future__ import annotations from sage.structure.factory import UniqueFactory from sage.modules.module import Module from sage.structure.element import ModuleElement @@ -1915,9 +1915,9 @@ def support(self): return sup - def gens(self, names='v'): + def gens(self, names='v') -> tuple: """ - Return a list of generators of ``self`` as a `k`-module. + Return a tuple of generators of ``self`` as a `k`-module. INPUT: @@ -1928,7 +1928,7 @@ def gens(self, names='v'): OUTPUT: - - list of :class:`QuiverRepElement` objects, the linear generators + - tuple of :class:`QuiverRepElement` objects, the linear generators of the module (over the base ring) .. NOTE:: @@ -1942,14 +1942,14 @@ def gens(self, names='v'): sage: Q = DiGraph({1:{2:['a', 'b']}}).path_semigroup() sage: M = Q.P(QQ, 1) sage: M.gens() - [v_0, v_1, v_2] + (v_0, v_1, v_2) If a string is given then it is used as the name of each generator, with the index of the generator appended in order to differentiate them:: sage: M.gens('generator') - [generator_0, generator_1, generator_2] + (generator_0, generator_1, generator_2) If a list or other iterable variable is given then each generator is named using the appropriate entry. The length of the variable @@ -1960,14 +1960,14 @@ def gens(self, names='v'): ... TypeError: can only concatenate list (not "str") to list sage: M.gens(['x', 'y', 'z']) - [x, y, z] + (x, y, z) Strings are iterable, so if the length of the string is equal to the number of generators then the characters of the string will be used as the names:: sage: M.gens('xyz') - [x, y, z] + (x, y, z) """ # Use names as a list if and only if it is the correct length uselist = (len(names) == self.dimension()) @@ -1986,7 +1986,7 @@ def gens(self, names='v'): basis.append(self({v: m}, names + "_" + str(i))) i += 1 - return basis + return tuple(basis) def coordinates(self, vector): """ 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/element_givaro.pyx b/src/sage/rings/finite_rings/element_givaro.pyx index 025e9fe80ce..930eccbe57b 100644 --- a/src/sage/rings/finite_rings/element_givaro.pyx +++ b/src/sage/rings/finite_rings/element_givaro.pyx @@ -1,7 +1,7 @@ # distutils: libraries = givaro gmp m # distutils: language = c++ r""" -Givaro Field Elements +Givaro finite field elements Sage includes the Givaro finite field library, for highly optimized arithmetic in finite fields. diff --git a/src/sage/rings/finite_rings/element_ntl_gf2e.pyx b/src/sage/rings/finite_rings/element_ntl_gf2e.pyx index beda02aea78..a9c85582af7 100644 --- a/src/sage/rings/finite_rings/element_ntl_gf2e.pyx +++ b/src/sage/rings/finite_rings/element_ntl_gf2e.pyx @@ -5,7 +5,7 @@ # distutils: extra_link_args = NTL_LIBEXTRA # distutils: language = c++ r""" -Finite Fields of characteristic 2. +Finite field of characteristic 2 elements This implementation uses NTL's GF2E class to perform the arithmetic and is the standard implementation for ``GF(2^n)`` for ``n >= 16``. diff --git a/src/sage/rings/finite_rings/finite_field_base.pyx b/src/sage/rings/finite_rings/finite_field_base.pyx index ffad0442389..caeadff7ae2 100644 --- a/src/sage/rings/finite_rings/finite_field_base.pyx +++ b/src/sage/rings/finite_rings/finite_field_base.pyx @@ -1,5 +1,5 @@ """ -Base Classes for Finite Fields +Base class for finite fields TESTS:: diff --git a/src/sage/rings/finite_rings/finite_field_constructor.py b/src/sage/rings/finite_rings/finite_field_constructor.py index 685c385cf2e..e42b2bed2dd 100644 --- a/src/sage/rings/finite_rings/finite_field_constructor.py +++ b/src/sage/rings/finite_rings/finite_field_constructor.py @@ -1,5 +1,5 @@ r""" -Finite Fields +Finite fields Sage supports arithmetic in finite prime and extension fields. Several implementation for prime fields are implemented natively in diff --git a/src/sage/rings/finite_rings/finite_field_givaro.py b/src/sage/rings/finite_rings/finite_field_givaro.py index 6b34b7e66be..a70cfc0c717 100644 --- a/src/sage/rings/finite_rings/finite_field_givaro.py +++ b/src/sage/rings/finite_rings/finite_field_givaro.py @@ -1,5 +1,5 @@ """ -Givaro Finite Field +Givaro finite fields Finite fields that are implemented using Zech logs and the cardinality must be less than `2^{16}`. By default, Conway polynomials are diff --git a/src/sage/rings/finite_rings/finite_field_ntl_gf2e.py b/src/sage/rings/finite_rings/finite_field_ntl_gf2e.py index ee47bba4d1c..d2dd7f49613 100644 --- a/src/sage/rings/finite_rings/finite_field_ntl_gf2e.py +++ b/src/sage/rings/finite_rings/finite_field_ntl_gf2e.py @@ -1,5 +1,5 @@ """ -Finite Fields of Characteristic 2 +Finite fields of characteristic 2 """ #***************************************************************************** diff --git a/src/sage/rings/finite_rings/finite_field_prime_modn.py b/src/sage/rings/finite_rings/finite_field_prime_modn.py index 9129ecb56aa..f2a91186cd9 100644 --- a/src/sage/rings/finite_rings/finite_field_prime_modn.py +++ b/src/sage/rings/finite_rings/finite_field_prime_modn.py @@ -1,5 +1,5 @@ """ -Finite Prime Fields +Finite prime fields AUTHORS: diff --git a/src/sage/rings/finite_rings/hom_finite_field_givaro.pyx b/src/sage/rings/finite_rings/hom_finite_field_givaro.pyx index d79ea68fc25..ad88a240355 100644 --- a/src/sage/rings/finite_rings/hom_finite_field_givaro.pyx +++ b/src/sage/rings/finite_rings/hom_finite_field_givaro.pyx @@ -5,7 +5,7 @@ # distutils: extra_link_args = NTL_LIBEXTRA # distutils: language = c++ """ -Finite field morphisms using Givaro +Givaro finite field morphisms Special implementation for givaro finite fields of: diff --git a/src/sage/rings/finite_rings/homset.py b/src/sage/rings/finite_rings/homset.py index ca4bd31571b..0a7578a092e 100644 --- a/src/sage/rings/finite_rings/homset.py +++ b/src/sage/rings/finite_rings/homset.py @@ -1,5 +1,5 @@ """ -Homset for Finite Fields +Homset for finite fields This is the set of all field homomorphisms between two finite fields. diff --git a/src/sage/rings/ideal.py b/src/sage/rings/ideal.py index 8bb1de564f3..f1342415355 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): @@ -1808,11 +1847,8 @@ def FieldIdeal(R): Multivariate Polynomial Ring in x1, x2, x3, x4 over Finite Field in alpha of size 2^4 """ - q = R.base_ring().order() - import sage.rings.infinity if q is sage.rings.infinity.infinity: raise TypeError("Cannot construct field ideal for R.base_ring().order()==infinity") - - return R.ideal([x**q - x for x in R.gens() ]) + return R.ideal([x**q - x for x in R.gens()]) diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 9f4cd1b0f39..ab536e3c542 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -100,6 +100,50 @@ sage: hinv.valuation() -1 +TESTS:: + + sage: def check(L, z, verbose=False): + ....: # division + ....: lf = [0, L(0), 1, L(1), z, 1 + z, 2 + z + z^2] + ....: lg = [3, L(3), 1 + z, 2 + z + z^2] + ....: for f in lf: + ....: for g in lg: + ....: try: + ....: h = f / g + ....: if verbose: print("(%s) / (%s) = %s" % (f, g, h)) + ....: except Exception as e: + ....: print("%s in (%s) / (%s)" % (e, f, g)) + ....: # composition + ....: f = L(0) + ....: l = [(f, 0), (f, L(0)), (f, 2), (f, L(2)), (f, 2 + z + z^2), (f, 3/(1 - 2*z))] + ....: f = L(1) + ....: l.extend([(f, 0), (f, L(0)), (f, 2), (f, L(2)), (f, 2 + z + z^2), (f, 3/(1 - 2*z))]) + ....: f = 2 + z + z^2 + ....: l.extend([(f, 0), (f, L(0)), (f, 2), (f, L(2)), (f, 2 + z + z^2), (f, 3/(1 - 2*z))]) + ....: f = 3/(2 - 3*z) + ....: l.extend([(f, 0), (f, L(0)), (f, 3*z/(1 - 2*z))]) + ....: for f, g in l: + ....: try: + ....: h = f(g) + ....: if verbose: print("(%s)(%s) = %s" % (f, g, h)) + ....: except Exception as e: + ....: print("%s in (%s)(%s)" % (e, f, g)) + ....: # reversion + ....: l = [2 + 3*z, 3*z + 2*z^2, 3*z/(1 - 2*z - 3*z^2)] + ....: for f in l: + ....: try: + ....: h = f.revert() + ....: if verbose: print("(%s)^{(-1)} = %s" % (f, h)) + ....: except Exception as e: + ....: print("%s in (%s).revert()" % (e, f)) + + sage: L. = LazyLaurentSeriesRing(QQ) + sage: check(L, z) + sage: L. = LazyTaylorSeriesRing(QQ) + sage: check(L, z) + sage: p = SymmetricFunctions(QQ).p() + sage: L = LazySymmetricFunctions(p) + sage: check(L, L(p[1])) """ # **************************************************************************** @@ -117,11 +161,19 @@ 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.arith.functions import lcm +from sage.arith.misc import divisors, moebius +from sage.combinat.partition import Partition, Partitions +from sage.misc.misc_c import prod +from sage.misc.derivative import derivative_parse 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.categories.tensor import tensor from sage.data_structures.stream import ( Stream_add, Stream_cauchy_mul, @@ -137,11 +189,13 @@ Stream_uninitialized, Stream_shift, Stream_function, + Stream_derivative, Stream_dirichlet_convolve, Stream_dirichlet_invert, Stream_plethysm ) + class LazyModuleElement(Element): r""" A lazy sequence with a module structure given by term-wise @@ -253,7 +307,7 @@ def __getitem__(self, n): coefficient = __getitem__ - def map_coefficients(self, func, ring=None): + def map_coefficients(self, func): r""" Return the series with ``func`` applied to each nonzero coefficient of ``self``. @@ -323,13 +377,12 @@ def map_coefficients(self, func, ring=None): if not any(initial_coefficients) and not c: return P.zero() coeff_stream = Stream_exact(initial_coefficients, - self._coeff_stream._is_sparse, - order=coeff_stream._approximate_order, - degree=coeff_stream._degree, - constant=BR(c)) + self._coeff_stream._is_sparse, + order=coeff_stream._approximate_order, + degree=coeff_stream._degree, + constant=BR(c)) return P.element_class(P, coeff_stream) - R = P._internal_poly_ring.base_ring() - coeff_stream = Stream_map_coefficients(self._coeff_stream, func, R) + coeff_stream = Stream_map_coefficients(self._coeff_stream, func) return P.element_class(P, coeff_stream) def truncate(self, d): @@ -567,6 +620,10 @@ def __bool__(self): """ Test whether ``self`` is not zero. + An uninitialized series returns ``True`` as it is considered + as a formal variable, such as a generator of a polynomial + ring. + TESTS:: sage: L. = LazyLaurentSeriesRing(GF(2)) @@ -601,11 +658,41 @@ def __bool__(self): 1 sage: bool(M) True + + Uninitialized series:: + + sage: g = L(None, valuation=0) + sage: bool(g) + True + sage: g.define(0) + sage: bool(g) + False + + sage: g = L(None, valuation=0) + sage: bool(g) + True + sage: g.define(1 + z) + sage: bool(g) + True + + sage: g = L(None, valuation=0) + sage: bool(g) + True + sage: g.define(1 + z*g) + sage: bool(g) + True """ if isinstance(self._coeff_stream, Stream_zero): return False if isinstance(self._coeff_stream, Stream_exact): return True + if isinstance(self._coeff_stream, Stream_uninitialized): + if self._coeff_stream._target is None: + return True + if isinstance(self._coeff_stream._target, Stream_zero): + return False + if isinstance(self._coeff_stream._target, Stream_exact): + return True if self.parent()._sparse: cache = self._coeff_stream._cache if any(cache[a] for a in cache): @@ -623,7 +710,7 @@ def define(self, s): INPUT: - - ``s`` -- a Laurent polynomial + - ``s`` -- a lazy series EXAMPLES: @@ -754,6 +841,16 @@ def define(self, s): ... ValueError: series already defined + sage: e = L(None, valuation=0) + sage: e.define(1) + sage: e + 1 + + sage: e = L(None, valuation=0) + sage: e.define((1 + z).polynomial()) + sage: e + 1 + z + sage: D = LazyDirichletSeriesRing(QQ, "s") sage: L. = LazyLaurentSeriesRing(QQ) sage: e = L(lambda n: 1/factorial(n), 0) @@ -765,6 +862,15 @@ def define(self, s): """ if not isinstance(self._coeff_stream, Stream_uninitialized) or self._coeff_stream._target is not None: raise ValueError("series already defined") + + if not isinstance(s, LazyModuleElement): + s = self.parent()(s) + + # Special case when it has a trivial definition + if isinstance(s._coeff_stream, (Stream_zero, Stream_exact)): + self._coeff_stream = s._coeff_stream + return + self._coeff_stream._target = s._coeff_stream # an alias for compatibility with padics @@ -965,7 +1071,10 @@ def change_ring(self, ring): Lazy Taylor Series Ring in z over Rational Field """ P = self.parent() - Q = type(P)(ring, names=P.variable_names(), sparse=P._sparse) + if P._names is not None: + Q = type(P)(ring, names=P.variable_names(), sparse=P._sparse) + else: + Q = type(P)(ring, sparse=P._sparse) return Q.element_class(Q, self._coeff_stream) # === module structure === @@ -1773,7 +1882,7 @@ def f(n): n = ZZ(n) if n % 2: h = 4 ** ((n + 1) // 2) - return bernoulli(n + 1) * h * (h -1) / factorial(n + 1) + return bernoulli(n + 1) * h * (h - 1) / factorial(n + 1) return ZZ.zero() return P(f, valuation=1)(self) @@ -1993,7 +2102,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:: @@ -2008,13 +2118,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): @@ -2045,7 +2178,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): @@ -2160,6 +2293,11 @@ def _mul_(self, other): sage: t1 = t.approximate_series(44) sage: s1 * t1 - (s * t).approximate_series(42) O(z^42) + + Check products with exact series:: + + sage: L([1], constant=3)^2 + 1 + 6*z + 15*z^2 + 24*z^3 + 33*z^4 + 42*z^5 + 51*z^6 + O(z^7) """ P = self.parent() left = self._coeff_stream @@ -2168,9 +2306,15 @@ 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._internal_poly_ring.base_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 + and not left._constant): return other # self == 1 - if isinstance(right, Stream_exact) and right._initial_coefficients == (P._internal_poly_ring.base_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 + and not right._constant): return self # right == 1 # The product is exact if and only if both factors are exact @@ -2194,7 +2338,7 @@ def _mul_(self, other): # coefficients of q. if right._constant: d = right._degree - c = left._constant # this is zero + c = left._constant # this is zero # left._constant must be 0 and thus len(il) >= 1 for k in range(len(il)-1): c += il[k] * right._constant @@ -2202,14 +2346,14 @@ def _mul_(self, other): c += il[-1] * right._constant elif left._constant: d = left._degree - c = right._constant # this is zero + c = right._constant # this is zero # left._constant must be 0 and thus len(il) >= 1 for k in range(len(ir)-1): c += left._constant * ir[k] initial_coefficients[d - lv + k] += c c += left._constant * ir[-1] else: - c = left._constant # this is zero + c = left._constant # this is zero coeff_stream = Stream_exact(initial_coefficients, P._sparse, order=lv + rv, @@ -2368,7 +2512,7 @@ def __invert__(self): 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): + and not coeff_stream._constant): v = -coeff_stream.order() c = ~initial_coefficients[0] coeff_stream = Stream_exact((), @@ -2592,7 +2736,7 @@ def __call__(self, g, *, check=True): r""" Return the composition of ``self`` with ``g``. - Given two Laurent Series `f` and `g` over the same base ring, the + Given two Laurent 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`, @@ -2793,7 +2937,7 @@ def __call__(self, g, *, check=True): ZeroDivisionError: the valuation of the series must be nonnegative `g \neq 0` and `val(g) \leq 0` and `f` has infinitely many - non-zero coefficients`:: + non-zero coefficients:: sage: g = z^-1 + z^-2 sage: g.valuation() <= 0 @@ -2862,8 +3006,17 @@ def __call__(self, g, *, check=True): 3 sage: parent(three) Univariate Polynomial Ring in x over Rational Field + + Consistency check when `g` is an uninitialized series between a + polynomial `f` as both a polynomial and a lazy series:: + + sage: L. = LazyLaurentSeriesRing(QQ) + sage: f = 1 + z + sage: g = L(None, valuation=0) + sage: f(g) == f.polynomial()(g) + True """ - # f = self and compute f(g) + # 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(g)) @@ -2975,7 +3128,7 @@ def __call__(self, g, *, check=True): def coefficient(n): return sum(self[i] * (g**i)[n] for i in range(n+1)) R = P._internal_poly_ring.base_ring() - coeff_stream = Stream_function(coefficient, R, P._sparse, 1) + coeff_stream = Stream_function(coefficient, P._sparse, 1) return P.element_class(P, coeff_stream) coeff_stream = Stream_cauchy_compose(self._coeff_stream, g._coeff_stream) @@ -2987,35 +3140,43 @@ def revert(self): r""" Return the compositional inverse of ``self``. - Given a Laurent Series `f`. the compositional inverse is a - Laurent Series `g` over the same base ring, such that + Given a Laurent series `f`, the compositional inverse is a + Laurent series `g` over the same base ring, such that `(f \circ g)(z) = f(g(z)) = z`. The compositional inverse exists if and only if: - `val(f) = 1`, or - - `f = a + b z` with `a b \neq 0`, or + - `f = a + b z` with `a, b \neq 0`, or - `f = a/z` with `a \neq 0` EXAMPLES:: - sage: L. = LazyLaurentSeriesRing(ZZ) - sage: z.revert() - z + O(z^8) - sage: (1/z).revert() - z^-1 + sage: L. = LazyLaurentSeriesRing(QQ) + sage: (2*z).revert() + 1/2*z + sage: (2/z).revert() + 2*z^-1 sage: (z-z^2).revert() z + z^2 + 2*z^3 + 5*z^4 + 14*z^5 + 42*z^6 + 132*z^7 + O(z^8) + sage: s = L(degree=1, constant=-1) + sage: s.revert() + -z - z^2 - z^3 + O(z^4) + + sage: s = L(degree=1, constant=1) + sage: s.revert() + z - z^2 + z^3 - z^4 + z^5 - z^6 + z^7 + O(z^8) + TESTS:: sage: L. = LazyLaurentSeriesRing(QQ) - sage: s = L(lambda n: 1 if n == 1 else 0, valuation=1); s - z + O(z^8) + sage: s = L(lambda n: 2 if n == 1 else 0, valuation=1); s + 2*z + O(z^8) sage: s.revert() - z + O(z^8) + 1/2*z + O(z^8) sage: (2+3*z).revert() -2/3 + 1/3*z @@ -3027,6 +3188,22 @@ def revert(self): ... ValueError: cannot determine whether the compositional inverse exists + sage: s = L(lambda n: 1, valuation=-2); s + z^-2 + z^-1 + 1 + z + z^2 + z^3 + z^4 + O(z^5) + sage: s.revert() + Traceback (most recent call last): + ... + ValueError: compositional inverse does not exist + + sage: R. = QQ[] + sage: L. = LazyLaurentSeriesRing(R.fraction_field()) + sage: s = L([q], valuation=0, constant=t); s + q + t*z + t*z^2 + t*z^3 + O(z^4) + sage: s.revert() + Traceback (most recent call last): + ... + ValueError: compositional inverse does not exist + We look at some cases where the compositional inverse does not exist: `f = 0`:: @@ -3040,7 +3217,7 @@ def revert(self): ... ValueError: compositional inverse does not exist - `val(f) ! = 1` and `f(0) * f(1) = 0`:: + `val(f) != 1` and `f(0) * f(1) = 0`:: sage: (z^2).revert() Traceback (most recent call last): @@ -3051,30 +3228,187 @@ def revert(self): Traceback (most recent call last): ... ValueError: compositional inverse does not exist + + Reversion of exact series:: + + sage: f = L([2], valuation=-1, constant=2) + sage: f.revert() + Traceback (most recent call last): + ... + ValueError: compositional inverse does not exist + + sage: f = L([1, 2], valuation=0, constant=1) + sage: f.revert() + Traceback (most recent call last): + ... + ValueError: compositional inverse does not exist + + sage: f = L([-1, -1], valuation=1, constant=-1) + sage: f.revert() + -z - z^2 - z^3 - z^4 - z^5 + O(z^6) + + sage: f = L([-1, 0, -1], valuation=1, constant=-1) + sage: f.revert() + -z + z^3 - z^4 - 2*z^5 + 6*z^6 + z^7 + O(z^8) + + sage: f = L([-1], valuation=1, degree=3, constant=-1) + sage: f.revert() + -z + z^3 - z^4 - 2*z^5 + 6*z^6 + z^7 + O(z^8) """ P = self.parent() - if self.valuation() == 1: - z = P.gen() - g = P(None, valuation=1) - g.define(z/((self/z)(g))) - return g - if self.valuation() not in [-1, 0]: - raise ValueError("compositional inverse does not exist") coeff_stream = self._coeff_stream + if isinstance(coeff_stream, Stream_zero): + raise ValueError("compositional inverse does not exist") if isinstance(coeff_stream, Stream_exact): - if (coeff_stream.order() == 0 - and coeff_stream._degree == 2): - a = coeff_stream[0] - b = coeff_stream[1] - coeff_stream = Stream_exact((-a/b, 1/b), - coeff_stream._is_sparse, - order=0) - return P.element_class(P, coeff_stream) - if (coeff_stream.order() == -1 - and coeff_stream._degree == 0): - return self + if coeff_stream._constant: + if coeff_stream.order() == 1: + R = P.base_ring() + # we cannot assume that the last initial coefficient + # and the constant differ, see stream.Stream_exact + if (coeff_stream._degree == 1 + len(coeff_stream._initial_coefficients) + and coeff_stream._constant == -R.one() + and all(c == -R.one() for c in coeff_stream._initial_coefficients)): + # self = -z/(1-z); self.revert() = -z/(1-z) + return self + else: + raise ValueError("compositional inverse does not exist") + else: + if (coeff_stream.order() == -1 + and coeff_stream._degree == 0): + # self = a/z; self.revert() = a/z + return self + + if (coeff_stream.order() >= 0 + and coeff_stream._degree == 2): + # self = a + b*z; self.revert() = -a/b + 1/b * z + a = coeff_stream[0] + b = coeff_stream[1] + coeff_stream = Stream_exact((-a/b, 1/b), + coeff_stream._is_sparse, + order=0) + return P.element_class(P, coeff_stream) + + if coeff_stream.order() != 1: + raise ValueError("compositional inverse does not exist") + + if any(coeff_stream[i] for i in range(coeff_stream._approximate_order, -1)): + raise ValueError("compositional inverse does not exist") + + if coeff_stream[-1]: + if coeff_stream[0] or coeff_stream[1]: + raise ValueError("compositional inverse does not exist") + raise ValueError("cannot determine whether the compositional inverse exists") + + if not coeff_stream[1]: raise ValueError("compositional inverse does not exist") - raise ValueError("cannot determine whether the compositional inverse exists") + + if coeff_stream[0]: + raise ValueError("cannot determine whether the compositional inverse exists") + + z = P.gen() + g = P(None, valuation=1) + g.define(z / ((self / z)(g))) + return g + + compositional_inverse = revert + + def derivative(self, *args): + """ + Return the derivative of the Laurent series. + + Multiple variables and iteration counts may be supplied; see + the documentation of + :func:`sage.calculus.functional.derivative` function for + details. + + EXAMPLES:: + + sage: L. = LazyLaurentSeriesRing(ZZ) + sage: z.derivative() + 1 + sage: (1+z+z^2).derivative(3) + 0 + sage: (1/z).derivative() + -z^-2 + sage: (1/(1-z)).derivative(z) + 1 + 2*z + 3*z^2 + 4*z^3 + 5*z^4 + 6*z^5 + 7*z^6 + O(z^7) + + TESTS: + + Check the derivative of the logarithm: + + sage: L. = LazyLaurentSeriesRing(QQ) + sage: -log(1-z).derivative() + 1 + z + z^2 + z^3 + z^4 + z^5 + z^6 + O(z^7) + + Check that differentiation of 'exact' series with nonzero + constant works:: + + sage: L. = LazyLaurentSeriesRing(ZZ) + sage: f = L([1,2], valuation=-2, constant=1) + sage: f + z^-2 + 2*z^-1 + 1 + z + z^2 + O(z^3) + sage: f.derivative() + -2*z^-3 - 2*z^-2 + 1 + 2*z + 3*z^2 + 4*z^3 + O(z^4) + + Check that differentiation with respect to a variable other + than the series variable works:: + + sage: R. = QQ[] + sage: L. = LazyLaurentSeriesRing(R) + sage: (z*q).derivative() + q + + sage: (z*q).derivative(q) + z + + sage: (z*q).derivative(q, z) + 1 + + sage: f = 1/(1-q*z+z^2) + sage: f + 1 + q*z + (q^2 - 1)*z^2 + (q^3 - 2*q)*z^3 + (q^4 - 3*q^2 + 1)*z^4 + (q^5 - 4*q^3 + 3*q)*z^5 + (q^6 - 5*q^4 + 6*q^2 - 1)*z^6 + O(z^7) + sage: f.derivative(q)[3] + 3*q^2 - 2 + + """ + P = self.parent() + R = P._laurent_poly_ring + v = R.gen() + order = 0 + vars = [] + for x in derivative_parse(args): + if x is None or x == v: + order += 1 + else: + vars.append(x) + + coeff_stream = self._coeff_stream + if isinstance(coeff_stream, Stream_zero): + return self + if (isinstance(coeff_stream, Stream_exact) + and not coeff_stream._constant): + if coeff_stream._approximate_order >= 0 and coeff_stream._degree <= order: + return P.zero() + if vars: + coeffs = [prod(i-k for k in range(order)) * c.derivative(vars) + for i, c in enumerate(coeff_stream._initial_coefficients, + coeff_stream._approximate_order)] + else: + coeffs = [prod(i-k for k in range(order)) * c + for i, c in enumerate(coeff_stream._initial_coefficients, + coeff_stream._approximate_order)] + coeff_stream = Stream_exact(coeffs, + self._coeff_stream._is_sparse, + order=coeff_stream._approximate_order - order, + constant=coeff_stream._constant) + return P.element_class(P, coeff_stream) + + coeff_stream = Stream_derivative(self._coeff_stream, order) + if vars: + coeff_stream = Stream_map_coefficients(coeff_stream, + lambda c: c.derivative(vars)) + return P.element_class(P, coeff_stream) def approximate_series(self, prec, name=None): r""" @@ -3137,10 +3471,10 @@ def polynomial(self, degree=None, name=None): A Laurent polynomial if the valuation of the series is negative or a polynomial otherwise. - 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 or a Laurent polynomial, a - ``ValueError`` is raised. + If ``degree`` is not ``None``, the terms of the series of + degree greater than ``degree`` are first truncated. If + ``degree`` is ``None`` and the series is not a polynomial or + a Laurent polynomial, a ``ValueError`` is raised. EXAMPLES:: @@ -3181,6 +3515,7 @@ def polynomial(self, degree=None, name=None): sage: L.zero().polynomial() 0 + """ S = self.parent() @@ -3246,6 +3581,7 @@ 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. @@ -3274,12 +3610,21 @@ def __call__(self, *g, check=True): 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: + Given a Taylor series `f` of arity `n` and a tuple of Taylor + series `g = (g_1,\dots, g_n)` over the same base ring, the + composition `f \circ g` is defined if and only if for each + `1\leq k\leq n`: - - `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`. + - `g_i` is zero, or + - setting all variables except the `i`th in `f` to zero + yields a polynomial, or + - `val(g_i) > 0`. + + If `f` is a univariate 'exact' series, we can check whether + `f` is a actually a polynomial. However, if `f` is a + multivariate series, we have no way to test whether setting + all but one variable of `f` to zero yields a polynomial, + except if `f` itself is 'exact' and therefore a polynomial. INPUT: @@ -3391,8 +3736,50 @@ def __call__(self, *g, check=True): ... TypeError: no common canonical parent for objects with parents: ... + Consistency check when `g` is an uninitialized series between a + polynomial `f` as both a polynomial and a lazy series:: + + sage: L. = LazyTaylorSeriesRing(QQ) + sage: f = 1 - z + sage: g = L(None, valuation=1) + sage: f(g) == f.polynomial()(g) + True + + sage: g = L(None, valuation=1) + sage: g.define(z / (1 - g)) + sage: g + z + z^2 + 2*z^3 + 5*z^4 + 14*z^5 + 42*z^6 + 132*z^7 + O(z^8) + sage: gp = L(None, valuation=1) + sage: gp.define(z / f(gp)) + sage: gp + z + z^2 + 2*z^3 + 5*z^4 + 14*z^5 + 42*z^6 + 132*z^7 + O(z^8) + + Check that composing the zero series with anything yields zero:: + + sage: T. = LazyTaylorSeriesRing(QQ) + sage: M. = LazyTaylorSeriesRing(QQ) + sage: T(0)(1/(1-a), a+b) + 0 + + Check that composing `f` with zero series yields the constant term of `f`:: + + sage: T(3/(1-x-2*y))(0, 0) + 3 + + Check that we can compose a polynomial with anything:: + + sage: T(1-x-2*y + x*y^2)(1, 3) + 3 + + sage: T(1-x-2*y + x*y^2)(1 + a, 3) + 3 + 8*a + + sage: T(1-x-2*y + x*y^2)(1/(1-a), 3) + 3 + 8*a + 8*a^2 + 8*a^3 + 8*a^4 + 8*a^5 + 8*a^6 + O(a,b)^7 + """ - if len(g) != len(self.parent().variable_names()): + fP = parent(self) + if len(g) != fP._arity: raise ValueError("arity of must be equal to the number of arguments provided") # Find a good parent for the result @@ -3400,8 +3787,20 @@ def __call__(self, *g, check=True): 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: + # f = 0 + if isinstance(self._coeff_stream, Stream_zero): + return P.zero() + + # g = (0, ..., 0) + if all((not isinstance(h, LazyModuleElement) and not h) + or (isinstance(h, LazyModuleElement) + and isinstance(h._coeff_stream, Stream_zero)) + for h in g): + return P(self[0]) + + # f has finite length and f != 0 + if (isinstance(self._coeff_stream, Stream_exact) + and not self._coeff_stream._constant): # constant polynomial poly = self.polynomial() if poly.is_constant(): @@ -3416,7 +3815,6 @@ def __call__(self, *g, check=True): 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 @@ -3452,8 +3850,7 @@ def __call__(self, *g, check=True): 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 + # We now have that every element of g has a _coeff_stream sorder = self._coeff_stream._approximate_order if len(g) == 1: g0 = g[0] @@ -3461,7 +3858,7 @@ def __call__(self, *g, check=True): # 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) + coeff_stream = Stream_function(coefficient, P._sparse, 1) return P.element_class(P, coeff_stream) coeff_stream = Stream_cauchy_compose(self._coeff_stream, g0._coeff_stream) @@ -3476,101 +3873,331 @@ def coefficient(n): # 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) + coeff_stream = Stream_function(coefficient, 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:: + def revert(self): + r""" + Return the compositional inverse of ``self``. - 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' + Given a Taylor series `f`, the compositional inverse is a + Laurent series `g` over the same base ring, such that + `(f \circ g)(z) = f(g(z)) = z`. - 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 + The compositional inverse exists if and only if: - 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 = [] + - `val(f) = 1`, or - 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 = " + ") + - `f = a + b z` with `a, b \neq 0`, or - return poly + EXAMPLES:: - def polynomial(self, degree=None, names=None): - r""" - Return ``self`` as a polynomial if ``self`` is actually so. + sage: L. = LazyTaylorSeriesRing(QQ) + sage: (2*z).revert() + 1/2*z + sage: (z-z^2).revert() + z + z^2 + 2*z^3 + 5*z^4 + 14*z^5 + 42*z^6 + 132*z^7 + O(z^8) - INPUT: + sage: s = L(degree=1, constant=-1) + sage: s.revert() + -z - z^2 - z^3 + O(z^4) - - ``degree`` -- ``None`` or an integer - - ``names`` -- names of the variables; if it is ``None``, the name of - the variables of the series is used + sage: s = L(degree=1, constant=1) + sage: s.revert() + z - z^2 + z^3 - z^4 + z^5 - z^6 + z^7 + O(z^8) - OUTPUT: + TESTS:: - 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. + sage: L. = LazyTaylorSeriesRing(QQ) + sage: s = L(lambda n: 2 if n == 1 else 0, valuation=1); s + 2*z + O(z^8) + sage: s.revert() + 1/2*z + O(z^8) - EXAMPLES:: + sage: (2+3*z).revert() + -2/3 + 1/3*z - sage: L. = LazyTaylorSeriesRing(ZZ) - sage: f = x^2 + y*x - x + 2; f - 2 + (-x) + (x^2+x*y) + sage: s = L(lambda n: 2 if n == 0 else 3 if n == 1 else 0, valuation=0); s + 2 + 3*z + O(z^7) + sage: s.revert() + Traceback (most recent call last): + ... + ValueError: cannot determine whether the compositional inverse exists + + sage: R. = QQ[] + sage: L. = LazyTaylorSeriesRing(R.fraction_field()) + sage: s = L([q], valuation=0, constant=t); s + q + t*z + t*z^2 + t*z^3 + O(z^4) + sage: s.revert() + Traceback (most recent call last): + ... + ValueError: compositional inverse does not exist + + We look at some cases where the compositional inverse does not exist: + + `f = 0`:: + + sage: L(0).revert() + Traceback (most recent call last): + ... + ValueError: compositional inverse does not exist + sage: (z - z).revert() + Traceback (most recent call last): + ... + ValueError: compositional inverse does not exist + + `val(f) != 1` and `f(0) * f(1) = 0`:: + + sage: (z^2).revert() + Traceback (most recent call last): + ... + ValueError: compositional inverse does not exist + + sage: L(1).revert() + Traceback (most recent call last): + ... + ValueError: compositional inverse does not exist + + Reversion of exact series:: + + sage: f = L([1, 2], valuation=0, constant=1) + sage: f.revert() + Traceback (most recent call last): + ... + ValueError: compositional inverse does not exist + + sage: f = L([-1, -1], valuation=1, constant=-1) + sage: f.revert() + (-z) + (-z^2) + (-z^3) + (-z^4) + (-z^5) + O(z^6) + + sage: f = L([-1, 0, -1], valuation=1, constant=-1) + sage: f.revert() + (-z) + z^3 + (-z^4) + (-2*z^5) + 6*z^6 + z^7 + O(z^8) + + sage: f = L([-1], valuation=1, degree=3, constant=-1) + sage: f.revert() + (-z) + z^3 + (-z^4) + (-2*z^5) + 6*z^6 + z^7 + O(z^8) + """ + P = self.parent() + if P._arity != 1: + raise ValueError("arity must be equal to 1") + coeff_stream = self._coeff_stream + if isinstance(coeff_stream, Stream_zero): + raise ValueError("compositional inverse does not exist") + if isinstance(coeff_stream, Stream_exact): + if coeff_stream._constant: + if coeff_stream.order() == 1: + R = P.base_ring() + # we cannot assume that the last initial coefficient + # and the constant differ, see stream.Stream_exact + if (coeff_stream._degree == 1 + len(coeff_stream._initial_coefficients) + and coeff_stream._constant == -R.one() + and all(c == -R.one() for c in coeff_stream._initial_coefficients)): + # self = -z/(1-z); self.revert() = -z/(1-z) + return self + else: + raise ValueError("compositional inverse does not exist") + else: + if coeff_stream._degree == 2: + # self = a + b*z; self.revert() = -a/b + 1/b * z + a = coeff_stream[0] + b = coeff_stream[1] + coeff_stream = Stream_exact((-a/b, 1/b), + coeff_stream._is_sparse, + order=0) + return P.element_class(P, coeff_stream) + + if coeff_stream.order() != 1: + raise ValueError("compositional inverse does not exist") + + if not coeff_stream[1]: + raise ValueError("compositional inverse does not exist") + + if coeff_stream[0]: + raise ValueError("cannot determine whether the compositional inverse exists") + + z = P.gen() + g = P(None, valuation=1) + g.define(z / ((self / z)(g))) + return g + + compositional_inverse = revert + + def derivative(self, *args): + """ + Return the derivative of the Taylor series. + + Multiple variables and iteration counts may be supplied; see + the documentation of + :func:`sage.calculus.functional.derivative` function for + details. + + EXAMPLES:: + + sage: T. = LazyTaylorSeriesRing(ZZ) + sage: z.derivative() + 1 + sage: (1+z+z^2).derivative(3) + 0 + sage: (1/(1-z)).derivative() + 1 + 2*z + 3*z^2 + 4*z^3 + 5*z^4 + 6*z^5 + 7*z^6 + O(z^7) + + sage: R. = QQ[] + sage: L. = LazyTaylorSeriesRing(R) + sage: f = 1/(1-q*x+y); f + 1 + (q*x-y) + (q^2*x^2+(-2*q)*x*y+y^2) + (q^3*x^3+(-3*q^2)*x^2*y+3*q*x*y^2-y^3) + (q^4*x^4+(-4*q^3)*x^3*y+6*q^2*x^2*y^2+(-4*q)*x*y^3+y^4) + (q^5*x^5+(-5*q^4)*x^4*y+10*q^3*x^3*y^2+(-10*q^2)*x^2*y^3+5*q*x*y^4-y^5) + (q^6*x^6+(-6*q^5)*x^5*y+15*q^4*x^4*y^2+(-20*q^3)*x^3*y^3+15*q^2*x^2*y^4+(-6*q)*x*y^5+y^6) + O(x,y)^7 + sage: f.derivative(q) + x + (2*q*x^2+(-2)*x*y) + (3*q^2*x^3+(-6*q)*x^2*y+3*x*y^2) + (4*q^3*x^4+(-12*q^2)*x^3*y+12*q*x^2*y^2+(-4)*x*y^3) + (5*q^4*x^5+(-20*q^3)*x^4*y+30*q^2*x^3*y^2+(-20*q)*x^2*y^3+5*x*y^4) + (6*q^5*x^6+(-30*q^4)*x^5*y+60*q^3*x^4*y^2+(-60*q^2)*x^3*y^3+30*q*x^2*y^4+(-6)*x*y^5) + O(x,y)^7 + + """ + P = self.parent() + R = P._laurent_poly_ring + V = R.gens() + order = 0 + vars = [] + gen_vars = [] + for x in derivative_parse(args): + if x is None: + order += 1 + elif x in V: + gen_vars.append(x) + else: + vars.append(x) + + if P._arity > 1 and order: + raise ValueError("for multivariate series you have to specify the variable with respect to which the derivative should be taken") + else: + order += len(gen_vars) + + coeff_stream = self._coeff_stream + if isinstance(coeff_stream, Stream_zero): + return self + + if P._arity > 1: + v = gen_vars + vars + d = -len(gen_vars) + coeff_stream = Stream_map_coefficients(coeff_stream, + lambda c: R(c).derivative(v)) + coeff_stream = Stream_shift(coeff_stream, d) + return P.element_class(P, coeff_stream) + + if (isinstance(coeff_stream, Stream_exact) + and not coeff_stream._constant): + if coeff_stream._degree <= order: + return P.zero() + if vars: + coeffs = [prod(i-k for k in range(order)) * c.derivative(vars) + for i, c in enumerate(coeff_stream._initial_coefficients, + coeff_stream._approximate_order)] + else: + coeffs = [prod(i-k for k in range(order)) * c + for i, c in enumerate(coeff_stream._initial_coefficients, + coeff_stream._approximate_order)] + coeff_stream = Stream_exact(coeffs, + self._coeff_stream._is_sparse, + order=coeff_stream._approximate_order - order, + constant=coeff_stream._constant) + return P.element_class(P, coeff_stream) + + coeff_stream = Stream_derivative(self._coeff_stream, order) + if vars: + coeff_stream = Stream_map_coefficients(coeff_stream, + lambda c: c.derivative(vars)) + return P.element_class(P, coeff_stream) + + 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 first truncated. 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 @@ -3591,6 +4218,12 @@ def polynomial(self, degree=None, names=None): True sage: g3.polynomial(0) 1 + + sage: L. = LazyTaylorSeriesRing(ZZ) + sage: f = z-z^2 + sage: f.polynomial() + -z^2 + z + """ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing S = self.parent() @@ -3602,17 +4235,16 @@ def polynomial(self, degree=None, names=None): if degree is None: if (isinstance(self._coeff_stream, Stream_exact) - and not self._coeff_stream._constant): + 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]) + if S._arity == 1: + return R(self[0:m]) + return R.sum(self[0:m]) class LazyCompletionGradedAlgebraElement(LazyCauchyProductSeries): @@ -3705,22 +4337,37 @@ class LazySymmetricFunction(LazyCompletionGradedAlgebraElement): """ def __call__(self, *args, check=True): r""" - Return the composition of ``self`` with ``args``. + Return the composition of ``self`` with ``g``. 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`. + Given a lazy symmetric function `f` of arity `n` and a tuple + of lazy symmetric functions `g = (g_1,\dots, g_n)` over the + same base ring, the composition (or plethysm) `(f \circ g)` + is defined if and only if for each `1\leq k\leq n`: + + - `g_i = 0`, or + - setting all alphabets except the `i`th in `f` to zero + yields a symmetric function with only finitely many + non-zero coefficients, or + - `val(g) > 0`. + + If `f` is a univariate 'exact' lazy symmetric function, we + can check whether `f` has only finitely many non-zero + coefficients. However, if `f` has larger arity, we have no + way to test whether setting all but one alphabets of `f` to + zero yields a polynomial, except if `f` itself is 'exact' and + therefore a symmetric function with only finitely many + non-zero coefficients. INPUT: - - ``args`` -- other (lazy) symmetric functions + - ``g`` -- other (lazy) symmetric functions + + .. TODO:: + + allow specification of degree one elements EXAMPLES:: @@ -3753,9 +4400,27 @@ def __call__(self, *args, check=True): 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]] + sage: 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]] + The plethysm with a tensor product is also implemented:: + + sage: s = SymmetricFunctions(QQ).s() + sage: X = tensor([s[1],s[[]]]) + sage: Y = tensor([s[[]],s[1]]) + sage: S = LazySymmetricFunctions(s) + sage: S2 = LazySymmetricFunctions(tensor([s, s])) + sage: A = S(s[1,1,1]) + sage: B = S2(X+Y) + sage: A(B) + (s[]#s[1,1,1]+s[1]#s[1,1]+s[1,1]#s[1]+s[1,1,1]#s[]) + + sage: H = S(lambda n: s[n]) + sage: H(S2(X*Y)) + (s[]#s[]) + (s[1]#s[1]) + (s[1,1]#s[1,1]+s[2]#s[2]) + (s[1,1,1]#s[1,1,1]+s[2,1]#s[2,1]+s[3]#s[3]) + O^7 + sage: H(S2(X+Y)) + (s[]#s[]) + (s[]#s[1]+s[1]#s[]) + (s[]#s[2]+s[1]#s[1]+s[2]#s[]) + (s[]#s[3]+s[1]#s[2]+s[2]#s[1]+s[3]#s[]) + (s[]#s[4]+s[1]#s[3]+s[2]#s[2]+s[3]#s[1]+s[4]#s[]) + (s[]#s[5]+s[1]#s[4]+s[2]#s[3]+s[3]#s[2]+s[4]#s[1]+s[5]#s[]) + (s[]#s[6]+s[1]#s[5]+s[2]#s[4]+s[3]#s[3]+s[4]#s[2]+s[5]#s[1]+s[6]#s[]) + O^7 + TESTS:: sage: s = SymmetricFunctions(QQ).s() @@ -3767,8 +4432,7 @@ def __call__(self, *args, check=True): True sage: f = 1 / (1 - S(s[2])) sage: g = S(s[1]) / (1 - S(s[1])) - sage: h = f(g) - sage: h + sage: f(g) 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]) @@ -3780,30 +4444,64 @@ def __call__(self, *args, check=True): Traceback (most recent call last): ... ValueError: can only compose with a positive valuation series + + Check that composing the zero series with anything yields + zero in the correct parent:: + + sage: e = SymmetricFunctions(QQ).e() + sage: h = SymmetricFunctions(QQ).h() + sage: s = SymmetricFunctions(QQ).s() + sage: p = SymmetricFunctions(QQ).p() + sage: L = LazySymmetricFunctions(tensor([e, h])) + sage: r = (L(0)(s[1], p[1])); r + 0 + sage: r.parent() + Symmetric Functions over Rational Field in the Schur basis + + Check that composing `f` with zero series yields the constant term of `f`:: + + sage: f = 3*L(tensor([s[1], s[1]])) + sage: f(0, 0) + 0 + sage: (3+f)(0, 0) + 3 """ - if len(args) != self.parent()._arity: + fP = parent(self) + if len(args) != fP._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") + # 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 args]) + + # f = 0 if isinstance(self._coeff_stream, Stream_zero): - return self + return P.zero() + + # g = (0, ..., 0) + if all((not isinstance(h, LazyModuleElement) and not h) + or (isinstance(h, LazyModuleElement) + and isinstance(h._coeff_stream, Stream_zero)) + for h in args): + f = self[0] + # FIXME: TypeError: unable to convert 0 to a rational + if f: + return P(f.leading_coefficient()) + return P.zero() 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): - if isinstance(self._coeff_stream, Stream_exact) and not self._coeff_stream._constant: - f = self.symmetric_function() - if is_SymmetricFunction(g): + if not isinstance(g, LazySymmetricFunction): + f = self.symmetric_function() return f(g) - # g must be a LazySymmetricFunction - if isinstance(g._coeff_stream, Stream_exact) and not g._coeff_stream._constant: + + if (isinstance(g._coeff_stream, Stream_exact) + and not g._coeff_stream._constant): + f = self.symmetric_function() gs = g.symmetric_function() return P(f(gs)) @@ -3815,21 +4513,591 @@ def __call__(self, *args, check=True): P = LazySymmetricFunctions(R) g = P(g) - # self has (potentially) infinitely many terms - if check: + if check and not (isinstance(self._coeff_stream, Stream_exact) + and not self._coeff_stream._constant): 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) + if P._arity == 1: + ps = R.realization_of().p() + else: + ps = tensor([R._sets[0].realization_of().p()]*P._arity) + coeff_stream = Stream_plethysm(self._coeff_stream, g._coeff_stream, + ps, R) + return P.element_class(P, coeff_stream) + else: raise NotImplementedError("only implemented for arity 1") + plethysm = __call__ + + def revert(self): + r""" + Return the compositional inverse of ``self``. + + Given a symmetric function `f`, the compositional inverse is + a symmetric function `g` over the same base ring, such that + `f \circ g = p_1`. Thus, it is the inverse with respect to + plethystic substitution. + + The compositional inverse exists if and only if: + + - `val(f) = 1`, or + + - `f = a + b p_1` with `a, b \neq 0`. + + EXAMPLES:: + + sage: h = SymmetricFunctions(QQ).h() + sage: L = LazySymmetricFunctions(h) + sage: f = L(lambda n: h[n]) - 1 + sage: f(f.revert()) + h[1] + O^7 + + TESTS:: + + sage: f = L(lambda n: h[n]) - 1 - h[1] + sage: f.compositional_inverse() + Traceback (most recent call last): + ... + ValueError: compositional inverse does not exist + + sage: R. = QQ[] + sage: p = SymmetricFunctions(R.fraction_field()).p() + sage: L = LazySymmetricFunctions(p) + sage: f = L(a + b*p[1]) + sage: f.revert() + (((-a)/b)*p[]) + 1/b*p[1] + + sage: f = L(2*p[1]) + sage: f.revert() + 1/2*p[1] + + sage: f = L(2*p[1] + p[2] + p[1,1]) + sage: f.revert() + 1/2*p[1] + (-1/4*p[1,1]-1/2*p[2]) + (1/4*p[1,1,1]+1/2*p[2,1]) + (-5/16*p[1,1,1,1]-3/4*p[2,1,1]+1/2*p[4]) + (7/16*p[1,1,1,1,1]+5/4*p[2,1,1,1]+1/2*p[2,2,1]-1/2*p[4,1]) + (-21/32*p[1,1,1,1,1,1]-35/16*p[2,1,1,1,1]-3/2*p[2,2,1,1]-1/4*p[2,2,2]+3/4*p[4,1,1]) + (33/32*p[1,1,1,1,1,1,1]+63/16*p[2,1,1,1,1,1]+15/4*p[2,2,1,1,1]+3/4*p[2,2,2,1]-5/4*p[4,1,1,1]-p[4,2,1]) + O^8 + + ALGORITHM: + + Let `F` be a symmetric function with valuation `1`, i.e., + whose constant term vanishes and whose degree one term equals + `b p_1`. Then + + .. MATH:: + + (F - b p_1) \circ G = F \circ G - b p_1 \circ G = p_1 - b G, + + and therefore `G = (p_1 - (F - b p_1) \circ G) / b`, which + allows recursive computation of `G`. + + .. SEEALSO:: + + The compositional inverse `\Omega` of the symmetric + function `h_1 + h_2 + \dots` can be handled much more + efficiently using specialized methods. See + :func:`~sage.combinat.species.generating_series.LogarithmCycleIndexSeries` + + AUTHORS: + + - Andrew Gainer-Dewar + - Martin Rubey + + """ + P = self.parent() + if P._arity != 1: + raise ValueError("arity must be equal to 1") + coeff_stream = self._coeff_stream + if isinstance(coeff_stream, Stream_zero): + raise ValueError("compositional inverse does not exist") + R = P._laurent_poly_ring + if (isinstance(coeff_stream, Stream_exact) + and coeff_stream.order() >= 0 + and coeff_stream._degree == 2): + # self = a + b * p_1; self.revert() = -a/b + 1/b * p_1 + a = coeff_stream[0] + b = coeff_stream[1][Partition([1])] + X = R(Partition([1])) + coeff_stream = Stream_exact((-a/b, 1/b * X), + coeff_stream._is_sparse, + order=0) + return P.element_class(P, coeff_stream) + + if not coeff_stream[1]: + raise ValueError("compositional inverse does not exist") + + if coeff_stream[0]: + raise ValueError("cannot determine whether the compositional inverse exists") + + X = R(Partition([1])) + b = coeff_stream[1][Partition([1])] + g = P(None, valuation=1) + g.define(~b * X - (self - b * X)(g)) + return g + + plethystic_inverse = revert + + compositional_inverse = revert + + def derivative_with_respect_to_p1(self, n=1): + r""" + Return the symmetric function obtained by taking the + derivative of ``self`` with respect to the power-sum + symmetric function `p_1` when the expansion of ``self`` in + the power-sum basis is considered as a polynomial in `p_k`'s + (with `k \geq 1`). + + This is the same as skewing ``self`` by the first power-sum + symmetric function `p_1`. + + INPUT: + + - ``n`` -- (default: 1) nonnegative integer which determines + which power of the derivative is taken + + EXAMPLES: + + The species `E` of sets satisfies the relationship `E' = E`:: + + sage: h = SymmetricFunctions(QQ).h() + sage: T = LazySymmetricFunctions(h) + sage: E = T(lambda n: h[n]) + sage: E - E.derivative_with_respect_to_p1() + O^6 + + The species `C` of cyclic orderings and the species `L` of linear + orderings satisfy the relationship `C' = L`:: + + sage: p = SymmetricFunctions(QQ).p() + sage: C = T(lambda n: (sum(euler_phi(k)*p([k])**(n//k) for k in divisors(n))/n if n > 0 else 0)) + sage: L = T(lambda n: p([1]*n)) + sage: L - C.derivative_with_respect_to_p1() + O^6 + + TESTS:: + + sage: T = LazySymmetricFunctions(p) + sage: a = T(p([1,1,1])) + sage: a.derivative_with_respect_to_p1() + (3*p[1,1]) + O^9 + sage: a.derivative_with_respect_to_p1(1) + (3*p[1,1]) + O^9 + sage: a.derivative_with_respect_to_p1(2) + 6*p[1] + O^8 + sage: a.derivative_with_respect_to_p1(3) + 6*p[] + O^7 + """ + P = self.parent() + if P._arity != 1: + raise ValueError("arity must be equal to 1") + + coeff_stream = Stream_map_coefficients(self._coeff_stream, + lambda c: c.derivative_with_respect_to_p1(n)) + coeff_stream = Stream_shift(coeff_stream, -n) return P.element_class(P, coeff_stream) - plethysm = __call__ + def functorial_composition(self, *args): + r"""Return the functorial composition of ``self`` and ``g``. + + Let `X` be a finite set of cardinality `m`. For a group + action of the symmetric group `g: S_n \to S_X` and a + (possibly virtual) representation of the symmetric group on + `X`, `f: S_X \to GL(V)`, the functorial composition is the + (virtual) representation of the symmetric group `f \Box g: + S_n \to GL(V)` given by `\sigma \mapsto f(g(\sigma))`. + + This is more naturally phrased in the language of + combinatorial species. Let `F` and `G` be species, then + their functorial composition is the species `F \Box G` with + `(F \Box G) [A] = F[ G[A] ]`. In other words, an `(F \Box + G)`-structure on a set `A` of labels is an `F`-structure + whose labels are the set of all `G`-structures on `A`. + + The Frobenius character (or cycle index series) of `F \Box G` + can be computed as follows, see section 2.2 of [BLL]_): + + .. MATH:: + + \sum_{n \geq 0} \frac{1}{n!} \sum_{\sigma \in + \mathfrak{S}_{n}} \operatorname{fix} F[ (G[\sigma])_{1}, + (G[\sigma])_{2}, \ldots ] \, p_{1}^{\sigma_{1}} + p_{2}^{\sigma_{2}} \cdots. + + .. WARNING:: + + The operation `f \Box g` only makes sense when `g` + corresponds to a permutation representation, i.e., a + group action. + + EXAMPLES: + + The species `G` of simple graphs can be expressed in terms of + a functorial composition: `G = \mathfrak{p} \Box + \mathfrak{p}_{2}`, where `\mathfrak{p}` is the + :class:`~sage.combinat.species.subset_species.SubsetSpecies`.:: + + sage: R. = QQ[] + sage: h = SymmetricFunctions(R).h() + sage: m = SymmetricFunctions(R).m() + sage: L = LazySymmetricFunctions(m) + sage: P = L(lambda n: sum(q^k*h[n-k]*h[k] for k in range(n+1))) + sage: P2 = L(lambda n: h[2]*h[n-2], valuation=2) + sage: P.functorial_composition(P2)[:4] + [m[], + m[1], + (q+1)*m[1, 1] + (q+1)*m[2], + (q^3+3*q^2+3*q+1)*m[1, 1, 1] + (q^3+2*q^2+2*q+1)*m[2, 1] + (q^3+q^2+q+1)*m[3]] + + For example, there are:: + + sage: P.functorial_composition(P2)[4].coefficient([4])[3] + 3 + + unlabelled graphs on 4 vertices and 3 edges, and:: + + sage: P.functorial_composition(P2)[4].coefficient([2,2])[3] + 8 + + labellings of their vertices with two 1's and two 2's. + + The symmetric function `h_1 \sum_n h_n` is the neutral + element with respect to functorial composition:: + + sage: p = SymmetricFunctions(QQ).p() + sage: h = SymmetricFunctions(QQ).h() + sage: e = SymmetricFunctions(QQ).e() + sage: L = LazySymmetricFunctions(h) + sage: E = L(lambda n: h[n]) + sage: Ep = p[1]*E.derivative_with_respect_to_p1(); Ep + h[1] + (h[1,1]) + (h[2,1]) + (h[3,1]) + (h[4,1]) + (h[5,1]) + O^7 + sage: f = L(lambda n: h[n-n//2, n//2]) + sage: f - Ep.functorial_composition(f) + O^7 + + The functorial composition distributes over the sum:: + + sage: F1 = L(lambda n: h[n]) + sage: F2 = L(lambda n: e[n]) + sage: f1 = F1.functorial_composition(f) + sage: f2 = F2.functorial_composition(f) + sage: (F1 + F2).functorial_composition(f) - f1 - f2 + O^7 + + TESTS: + + Check a corner case:: + + sage: h = SymmetricFunctions(QQ).h() + sage: L = LazySymmetricFunctions(h) + sage: L(h[2,1]).functorial_composition(3*h[0]) + 3*h[] + O^7 + + Check an instance of a non-group action:: + + sage: s = SymmetricFunctions(QQ).s() + sage: p = SymmetricFunctions(QQ).p() + sage: L = LazySymmetricFunctions(p) + sage: f = L(lambda n: s[n]) + sage: g = 2*s[2, 1, 1] + s[2, 2] + 3*s[4] + sage: r = f.functorial_composition(g); r[4] + Traceback (most recent call last): + ... + ValueError: the argument is not the Frobenius character of a permutation representation + + """ + 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 len(args) == 1: + g = args[0] + P = g.parent() + 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) + + p = R.realization_of().p() + # TODO: does the following introduce a memory leak? + g = Stream_map_coefficients(g._coeff_stream, p) + f = Stream_map_coefficients(self._coeff_stream, p) + + def g_cycle_type(s, n): + # the cycle type of G[sigma] of any permutation sigma + # with cycle type s, which is a partition of n + if not n: + if g[0]: + return Partition([1]*ZZ(g[0].coefficient([]))) + return Partition([]) + res = [] + # in the species case, k is at most + # factorial(n) * g[n].coefficient([1]*n) + for k in range(1, lcm(s) + 1): + e = 0 + for d in divisors(k): + m = moebius(d) + if not m: + continue + u = s.power(k // d) + # it could be, that we never need to compute + # g[n], so we only do this here + g_u = g[n] + if g_u: + e += m * u.aut() * g_u.coefficient(u) + # e / k might not be an integer if g is not a + # group action, so it is good to check + res.extend([k] * ZZ(e / k)) + res.reverse() + return Partition(res) + + def coefficient(n): + terms = {} + t_size = None + for s in Partitions(n): + t = g_cycle_type(s, n) + if t_size is None: + t_size = sum(t) + f_t = f[t_size] + if not f_t: + break + elif t_size != sum(t): + raise ValueError("the argument is not the Frobenius character of a permutation representation") + + terms[s] = t.aut() * f_t.coefficient(t) / s.aut() + return R(p.element_class(p, terms)) + + coeff_stream = Stream_function(coefficient, P._sparse, 0) + return P.element_class(P, coeff_stream) + else: + raise NotImplementedError("only implemented for arity 1") + + def arithmetic_product(self, *args, check=True): + r""" + Return the arithmetic product of ``self`` with ``g``. + + The arithmetic product is a binary operation `\boxdot` on the + ring of symmetric functions which is bilinear in its two + arguments and satisfies + + .. MATH:: + + p_{\lambda} \boxdot p_{\mu} = \prod\limits_{i \geq 1, j \geq 1} + p_{\mathrm{lcm}(\lambda_i, \mu_j)}^{\mathrm{gcd}(\lambda_i, \mu_j)} + + for any two partitions `\lambda = (\lambda_1, \lambda_2, \lambda_3, + \dots )` and `\mu = (\mu_1, \mu_2, \mu_3, \dots )` (where `p_{\nu}` + denotes the power-sum symmetric function indexed by the partition + `\nu`, and `p_i` denotes the `i`-th power-sum symmetric function). + This is enough to define the arithmetic product if the base ring + is torsion-free as a `\ZZ`-module; for all other cases the + arithmetic product is uniquely determined by requiring it to be + functorial in the base ring. See + http://mathoverflow.net/questions/138148/ for a discussion of + this arithmetic product. + + .. WARNING:: + + The operation `f \boxdot g` was originally defined only + for symmetric functions `f` and `g` without constant + term. We extend this definition using the convention + that the least common multiple of any integer with `0` is + `0`. + + If `f` and `g` are two symmetric functions which are homogeneous + of degrees `a` and `b`, respectively, then `f \boxdot g` is + homogeneous of degree `ab`. + + The arithmetic product is commutative and associative and has + unity `e_1 = p_1 = h_1`. + + For species `M` and `N` such that `M[\varnothing] = + N[\varnothing] = \varnothing`, their arithmetic product is + the species `M \boxdot N` of "`M`-assemblies of cloned + `N`-structures". This operation is defined and several + examples are given in [MM2008]_. + + INPUT: + + - ``g`` -- a cycle index series having the same parent as ``self`` + + - ``check`` -- (default: ``True``) a Boolean which, when set + to ``False``, will cause input checks to be skipped + + OUTPUT: + + The arithmetic product of ``self`` with ``g``. + + .. SEEALSO:: + + :meth:`sage.combinat.sf.sfa.SymmetricFunctionAlgebra_generic_Element.arithmetic_product` + + EXAMPLES: + + For `C` the species of (oriented) cycles and `L_{+}` the + species of nonempty linear orders, `C \boxdot L_{+}` + corresponds to the species of "regular octopuses"; a `(C + \boxdot L_{+})`-structure is a cycle of some length, each of + whose elements is an ordered list of a length which is + consistent for all the lists in the structure. :: + + sage: R. = QQ[] + sage: p = SymmetricFunctions(R).p() + sage: m = SymmetricFunctions(R).m() + sage: L = LazySymmetricFunctions(m) + + sage: C = species.CycleSpecies().cycle_index_series() + sage: c = L(lambda n: C[n]) + sage: Lplus = L(lambda n: p([1]*n), valuation=1) + sage: r = c.arithmetic_product(Lplus); r + m[1] + (3*m[1,1]+2*m[2]) + + (8*m[1,1,1]+4*m[2,1]+2*m[3]) + + (42*m[1,1,1,1]+21*m[2,1,1]+12*m[2,2]+7*m[3,1]+3*m[4]) + + (144*m[1,1,1,1,1]+72*m[2,1,1,1]+36*m[2,2,1]+24*m[3,1,1]+12*m[3,2]+6*m[4,1]+2*m[5]) + + ... + + O^7 + + In particular, the number of regular octopuses is:: + + sage: [r[n].coefficient([1]*n) for n in range(8)] + [0, 1, 3, 8, 42, 144, 1440, 5760] + + It is shown in [MM2008]_ that the exponential generating + function for regular octopuses satisfies `(C \boxdot L_{+}) + (x) = \sum_{n \geq 1} \sigma (n) (n - 1)! \frac{x^{n}}{n!}` + (where `\sigma (n)` is the sum of the divisors of `n`). :: + + sage: [sum(divisors(i))*factorial(i-1) for i in range(1,8)] + [1, 3, 8, 42, 144, 1440, 5760] + + AUTHORS: + + - Andrew Gainer-Dewar (2013) + + REFERENCES: + + - [MM2008]_ + + TESTS: + + Check that the product with zero works:: + + sage: s = SymmetricFunctions(QQ).s() + sage: L = LazySymmetricFunctions(s) + sage: L(0).arithmetic_product(s[2]) + 0 + sage: L(s[2]).arithmetic_product(0) + 0 + + Check that the arithmetic product of symmetric functions of + finite support works:: + + sage: L(s([2])).arithmetic_product(s([1,1,1])) + s[2, 2, 1, 1] + s[3, 1, 1, 1] + s[3, 2, 1] + s[3, 3] + 2*s[4, 1, 1] + + sage: f = 1/(1-L(s[1])) + sage: f.arithmetic_product(s[1]) - f + O^7 + + Check that the arithmetic product of symmetric functions with + constant a term works as advertised:: + + sage: p = SymmetricFunctions(QQ).p() + sage: L = LazySymmetricFunctions(p) + sage: L(5).arithmetic_product(3*p[2,1]) + 15*p[] + + Check the arithmetic product of symmetric functions over a + finite field works:: + + sage: s = SymmetricFunctions(FiniteField(2)).s() + sage: L = LazySymmetricFunctions(s) + sage: L(s([2])).arithmetic_product(s([1,1,1])) + s[2, 2, 1, 1] + s[3, 1, 1, 1] + s[3, 2, 1] + s[3, 3] + + """ + 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 len(args) == 1: + g = args[0] + P = g.parent() + + # f = 0 or g = (0, ..., 0) + if (isinstance(self._coeff_stream, Stream_zero) + or (not isinstance(g, LazyModuleElement) and not g) + or (isinstance(g, LazyModuleElement) + and isinstance(g._coeff_stream, Stream_zero))): + return P.zero() + + if (isinstance(self._coeff_stream, Stream_exact) + and not self._coeff_stream._constant): + + if not isinstance(g, LazySymmetricFunction): + f = self.symmetric_function() + return f.arithmetic_product(g) + + if (isinstance(g._coeff_stream, Stream_exact) + and not g._coeff_stream._constant): + f = self.symmetric_function() + gs = g.symmetric_function() + return P(f.arithmetic_product(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) + + # compute the constant term in the case where not both f + # and g have finite support + # TODO: this should be done lazily if possible + c = R.zero() + if self[0]: + if (isinstance(g._coeff_stream, Stream_exact) + and not g._coeff_stream._constant): + gs = g.symmetric_function() + c += self[0].arithmetic_product(gs) + elif check: + raise ValueError("can only take the arithmetic product with a positive valuation series") + if g[0]: + if (isinstance(self._coeff_stream, Stream_exact) + and not self._coeff_stream._constant): + fs = self.symmetric_function() + c += fs.arithmetic_product(g[0]) + elif check: + raise ValueError("can only take the arithmetic product with a positive valuation series") + + p = R.realization_of().p() + # TODO: does the following introduce a memory leak? + g = Stream_map_coefficients(g._coeff_stream, p) + f = Stream_map_coefficients(self._coeff_stream, p) + + def coefficient(n): + if not n: + return c + index_set = ((d, n // d) for d in divisors(n)) + return sum(f[i].arithmetic_product(g[j]) + for i, j in index_set if f[i] and g[j]) + + coeff_stream = Stream_function(coefficient, P._sparse, 0) + return P.element_class(P, coeff_stream) + else: + raise NotImplementedError("only implemented for arity 1") def symmetric_function(self, degree=None): r""" @@ -3841,9 +5109,10 @@ def symmetric_function(self, degree=None): 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. + If ``degree`` is not ``None``, the terms of the series of + degree greater than ``degree`` are first truncated. If + ``degree`` is ``None`` and the series is not a polynomial + polynomial, a ``ValueError`` is raised. EXAMPLES:: @@ -3878,6 +5147,7 @@ def symmetric_function(self, degree=None): 0 sage: f4.symmetric_function(0) s[] + """ S = self.parent() R = S._laurent_poly_ring @@ -3887,7 +5157,7 @@ def symmetric_function(self, degree=None): if degree is None: if (isinstance(self._coeff_stream, Stream_exact) - and not self._coeff_stream._constant): + and not self._coeff_stream._constant): m = self._coeff_stream._degree else: raise ValueError("not a symmetric function") @@ -3896,6 +5166,7 @@ def symmetric_function(self, degree=None): return R.sum(self[:m]) + class LazyDirichletSeries(LazyModuleElement): r""" A Dirichlet series where the coefficients are computed lazily. @@ -4002,12 +5273,12 @@ def _mul_(self, other): and not left._constant and left._initial_coefficients == (P._internal_poly_ring.base_ring().one(),) and left.order() == 1): - return other # self == 1 + return other # self == 1 if (isinstance(right, Stream_exact) and not right._constant and right._initial_coefficients == (P._internal_poly_ring.base_ring().one(),) and right.order() == 1): - return self # other == 1 + return self # other == 1 coeff = Stream_dirichlet_convolve(left, right) # Performing exact arithmetic is slow because the series grow large # very quickly as we are multiplying the degree @@ -4139,7 +5410,7 @@ def coefficient(m): except ValueError: return ZZ.zero() R = P._internal_poly_ring.base_ring() - return P.element_class(P, Stream_function(coefficient, R, P._sparse, 1)) + return P.element_class(P, Stream_function(coefficient, P._sparse, 1)) def _format_series(self, formatter, format_strings=False): """ diff --git a/src/sage/rings/lazy_series_ring.py b/src/sage/rings/lazy_series_ring.py index 94a3c4ac465..1bfdffef6d8 100644 --- a/src/sage/rings/lazy_series_ring.py +++ b/src/sage/rings/lazy_series_ring.py @@ -65,6 +65,7 @@ Stream_uninitialized ) + class LazySeriesRing(UniqueRepresentation, Parent): """ Abstract base class for lazy series. @@ -430,7 +431,7 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, constant=No if degree is None: if constant is not None: raise ValueError("constant may only be specified if the degree is specified") - coeff_stream = Stream_function(x, self.base_ring(), self._sparse, valuation) + coeff_stream = Stream_function(lambda i: BR(x(i)), self._sparse, valuation) return self.element_class(self, coeff_stream) # degree is not None @@ -690,6 +691,7 @@ def is_exact(self): """ return self.base_ring().is_exact() + class LazyLaurentSeriesRing(LazySeriesRing): """ The ring of lazy Laurent series. @@ -806,11 +808,11 @@ class LazyLaurentSeriesRing(LazySeriesRing): sage: s 1 + z + 2*z^2 + 5*z^3 + 14*z^4 + 42*z^5 + 132*z^6 + O(z^7) - If we do not explcitly know the exact value of every coefficient, - then equality checking will depend on the computed coefficients. - If at a certain point we cannot prove two series are different - (which involves the coefficients we have computed), then we will - raise an error:: + If we do not explicitly know the exact value of every + coefficient, then equality checking will depend on the computed + coefficients. If at a certain point we cannot prove two series + are different (which involves the coefficients we have computed), + then we will raise an error:: sage: f = 1 / (z + z^2); f z^-1 - 1 + z - z^2 + z^3 - z^4 + z^5 + O(z^6) @@ -1116,6 +1118,7 @@ def _monomial(self, c, n): ###################################################################### + class LazyTaylorSeriesRing(LazySeriesRing): """ The ring of (possibly multivariate) lazy Taylor series. @@ -1149,7 +1152,8 @@ def __init__(self, base_ring, names, sparse=True, category=None): names = normalize_names(-1, names) self._sparse = sparse self._laurent_poly_ring = PolynomialRing(base_ring, names) - if len(names) == 1: + self._arity = len(names) + if self._arity == 1: self._internal_poly_ring = self._laurent_poly_ring else: coeff_ring = PolynomialRing(base_ring, names) @@ -1450,16 +1454,15 @@ def _element_constructor_(self, x=None, valuation=None, constant=None, degree=No 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) + coeff_stream = Stream_function(y, self._sparse, valuation) else: - coeff_stream = Stream_function(x, coeff_ring, self._sparse, valuation) + coeff_stream = Stream_function(x, self._sparse, valuation) return self.element_class(self, coeff_stream) raise ValueError(f"unable to convert {x} into a lazy Taylor series") @@ -1478,9 +1481,9 @@ def _an_element_(self): 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. @@ -1712,7 +1715,7 @@ def _element_constructor_(self, x=None, valuation=None, degree=None, check=True) d = f.degree() except (TypeError, ValueError, AttributeError): # FIXME: Fallback for symmetric functions in multiple variables - d = sum(p.size() for p in f.support()) + d = sum(sum(mu.size() for mu in p) for p in f.support()) p_dict[d] = p_dict.get(d, 0) + f v = min(p_dict) d = max(p_dict) @@ -1780,16 +1783,15 @@ def check_homogeneous_of_degree(f, d): 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) + coeff_stream = Stream_function(y, self._sparse, valuation) else: - coeff_stream = Stream_function(x, coeff_ring, self._sparse, valuation) + coeff_stream = Stream_function(x, self._sparse, valuation) return self.element_class(self, coeff_stream) raise ValueError(f"unable to convert {x} into a lazy completion element") @@ -2065,4 +2067,3 @@ def _monomial(self, c, n): return L(c) * L(n) ** -L(self.variable_name()) except (ValueError, TypeError): return '({})/{}^{}'.format(self.base_ring()(c), n, self.variable_name()) - 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 58463d570d4..cfb71b38abf 100644 --- a/src/sage/rings/number_field/number_field.py +++ b/src/sage/rings/number_field/number_field.py @@ -153,6 +153,11 @@ from collections import Counter from builtins import zip +from sage.categories.homset import Hom +from sage.categories.sets_cat import Sets +from sage.modules.free_module import VectorSpace +from sage.modules.free_module_element import vector +from sage.rings.real_mpfr import RR _NumberFields = NumberFields() @@ -6002,12 +6007,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): @@ -9297,6 +9311,78 @@ def minkowski_embedding(self, B=None, prec=None): return sage.matrix.all.matrix(d) + def logarithmic_embedding(self, prec=53): + r""" + Return the morphism of ``self`` under the logarithmic embedding + in the category Set. + + The logarithmic embedding is defined as a map from the number field ``self`` to `\RR^n`. + + It is defined under Definition 4.9.6 in [Coh1993]_. + + INPUT: + + - ``prec`` -- desired floating point precision. + + OUTPUT: + + - the morphism of ``self`` under the logarithmic embedding in the category Set. + + EXAMPLES:: + + sage: CF. = CyclotomicField(5) + sage: f = CF.logarithmic_embedding() + sage: f(0) + (-1, -1) + sage: f(7) + (3.89182029811063, 3.89182029811063) + + :: + + sage: K. = NumberField(x^3 + 5) + sage: f = K.logarithmic_embedding() + sage: f(0) + (-1, -1) + sage: f(7) + (1.94591014905531, 3.89182029811063) + + :: + + sage: F. = NumberField(x^4 - 8*x^2 + 3) + sage: f = F.logarithmic_embedding() + sage: f(0) + (-1, -1, -1, -1) + sage: f(7) + (1.94591014905531, 1.94591014905531, 1.94591014905531, 1.94591014905531) + """ + def closure_map(x, prec=53): + """ + The function closure of the logarithmic embedding. + """ + K = self + K_embeddings = K.places(prec) + r1, r2 = K.signature() + r = r1 + r2 - 1 + + from sage.rings.all import RealField + Reals = RealField(prec) + + if x == 0: + return vector([-1 for _ in range(r + 1)]) + + x_logs = [] + for i in range(r1): + sigma = K_embeddings[i] + x_logs.append(Reals(abs(sigma(x))).log()) + for i in range(r1, r + 1): + tau = K_embeddings[i] + x_logs.append(2 * Reals(abs(tau(x))).log()) + + return vector(x_logs) + + hom = Hom(self, VectorSpace(RR, len(closure_map(self(0), prec))), Sets()) + return hom(closure_map) + def places(self, all_complex=False, prec=None): r""" Return the collection of all infinite places of self. diff --git a/src/sage/rings/number_field/number_field_rel.py b/src/sage/rings/number_field/number_field_rel.py index d33980c4b10..9e80ef1e3c1 100644 --- a/src/sage/rings/number_field/number_field_rel.py +++ b/src/sage/rings/number_field/number_field_rel.py @@ -99,8 +99,12 @@ from sage.rings.number_field.morphism import RelativeNumberFieldHomomorphism_from_abs from sage.libs.pari.all import pari_gen -from sage.rings.rational_field import QQ -from sage.rings.integer_ring import ZZ +from sage.categories.homset import Hom +from sage.categories.sets_cat import Sets +from sage.modules.free_module import VectorSpace +from sage.modules.free_module_element import vector + +from sage.rings.all import RR, QQ, ZZ def is_RelativeNumberField(x): @@ -2062,6 +2066,75 @@ def automorphisms(self): check=False, universe=self.Hom(self)) return self.__automorphisms + def logarithmic_embedding(self, prec=53): + r""" + Return the morphism of ``self`` under the logarithmic embedding + in the category Set. + + The logarithmic embedding is defined as a map from the relative number field ``self`` to `\RR^n`. + + It is defined under Definition 4.9.6 in [Coh1993]_. + + INPUT: + + - ``prec`` -- desired floating point precision. + + OUTPUT: + + - the morphism of ``self`` under the logarithmic embedding in the category Set. + + EXAMPLES:: + + sage: K. = CyclotomicField(3) + sage: R. = K[] + sage: L. = K.extension(x^5 + 5) + sage: f = L.logarithmic_embedding() + sage: f(0) + (-1, -1, -1, -1, -1) + sage: f(5) + (3.21887582486820, 3.21887582486820, 3.21887582486820, + 3.21887582486820, 3.21887582486820) + + :: + + sage: K. = NumberField(x^2 + 1) + sage: t = K['t'].gen() + sage: L. = K.extension(t^4 - i) + sage: f = L.logarithmic_embedding() + sage: f(0) + (-1, -1, -1, -1, -1, -1, -1, -1) + sage: f(3) + (2.19722457733622, 2.19722457733622, 2.19722457733622, 2.19722457733622, + 2.19722457733622, 2.19722457733622, 2.19722457733622, 2.19722457733622) + """ + def closure_map(x, prec=53): + """ + The function closure of the logarithmic embedding. + """ + K = self + K_embeddings = K.places(prec) + r1, r2 = K.signature() + r = r1 + r2 - 1 + + from sage.rings.all import RealField + Reals = RealField(prec) + + if x == 0: + return vector([-1 for _ in range(r + 1)]) + + x_logs = [] + for i in range(r1): + sigma = K_embeddings[i] + x_logs.append(Reals(abs(sigma(x))).log()) + for i in range(r1, r + 1): + tau = K_embeddings[i] + x_logs.append(2 * Reals(abs(tau(x))).log()) + + return vector(x_logs) + + hom = Hom(self, VectorSpace(RR, len(closure_map(self(0), prec))), Sets()) + return hom(closure_map) + def places(self, all_complex=False, prec=None): """ Return the collection of all infinite places of self. 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 = InfinitePolynomialRing(ZZ) @@ -254,18 +254,24 @@ # # https://www.gnu.org/licenses/ # **************************************************************************** +import operator +import re +from functools import reduce from sage.rings.ring import CommutativeRing from sage.categories.rings import Rings from sage.structure.all import SageObject, parent from sage.structure.factory import UniqueFactory from sage.misc.cachefunc import cached_method -import operator -import re -from functools import reduce + +################################################### +# The Construction Functor + +from sage.categories.pushout import InfinitePolynomialFunctor + ############################################################### -## Ring Factory framework +# Ring Factory framework class InfinitePolynomialRingFactory(UniqueFactory): """ @@ -326,23 +332,21 @@ def create_key(self, R, names=('x',), order='lex', implementation='dense'): Traceback (most recent call last): ... ValueError: Infinite Polynomial Rings must have at least one generator - """ if isinstance(names, list): names = tuple(names) if not names: raise ValueError("Infinite Polynomial Rings must have at least one generator") - if len(names)>len(set(names)): - raise ValueError("The variable names must be distinct") - F = InfinitePolynomialFunctor(names,order,implementation) - while hasattr(R,'construction'): + if len(names) > len(set(names)): + raise ValueError("the variable names must be distinct") + F = InfinitePolynomialFunctor(names, order, implementation) + while hasattr(R, 'construction'): C = R.construction() if C is None: break - F = F*C[0] + F = F * C[0] R = C[1] - return (F,R) - + return (F, R) def create_object(self, version, key): """ @@ -352,36 +356,32 @@ def create_object(self, version, key): sage: InfinitePolynomialRing.create_object('1.0', InfinitePolynomialRing.create_key(ZZ, ('x3',))) Infinite polynomial ring in x3 over Integer Ring - """ - if len(key)>2: + if len(key) > 2: # We got an old pickle. By calling the ring constructor, it will automatically # be transformed into the new scheme return InfinitePolynomialRing(*key) # By now, we have different unique keys, based on construction functors - C,R = key + C, R = key from sage.categories.pushout import CompositeConstructionFunctor, InfinitePolynomialFunctor - if isinstance(C,CompositeConstructionFunctor): + if isinstance(C, CompositeConstructionFunctor): F = C.all[-1] - if len(C.all)>1: + if len(C.all) > 1: R = CompositeConstructionFunctor(*C.all[:-1])(R) else: F = C if not isinstance(F, InfinitePolynomialFunctor): - raise TypeError("We expected an InfinitePolynomialFunctor, not %s"%type(F)) - if F._imple=='sparse': + raise TypeError("we expected an InfinitePolynomialFunctor, not %s" % type(F)) + if F._imple == 'sparse': return InfinitePolynomialRing_sparse(R, F._gens, order=F._order) return InfinitePolynomialRing_dense(R, F._gens, order=F._order) -InfinitePolynomialRing = InfinitePolynomialRingFactory('InfinitePolynomialRing') -################################################### -## The Construction Functor +InfinitePolynomialRing = InfinitePolynomialRingFactory('InfinitePolynomialRing') -from sage.categories.pushout import InfinitePolynomialFunctor ############################################################## -## An auxiliary dictionary-like class that returns variables +# An auxiliary dictionary-like class that returns variables class InfiniteGenDict: """ @@ -421,9 +421,8 @@ def __init__(self, Gens): [InfiniteGenDict defined by ['a', 'b'], {'1': 1}] sage: D._D == loads(dumps(D._D)) # indirect doctest True - """ - self._D = dict(zip([(hasattr(X,'_name') and X._name) or repr(X) for X in Gens],Gens)) + self._D = dict(zip(((hasattr(X, '_name') and X._name) or repr(X) for X in Gens), Gens)) def __eq__(self, other): """ @@ -481,16 +480,16 @@ def __getitem__(self, k): sage: type(_) """ - if not isinstance(k, str): - raise KeyError("String expected") + raise KeyError("string expected") L = k.split('_') try: - if len(L)==2: + if len(L) == 2: return self._D[L[0]][int(L[1])] except Exception: pass - raise KeyError("%s is not a variable name"%k) + raise KeyError("%s is not a variable name" % k) + class GenDictWithBasering: """ @@ -513,10 +512,8 @@ class GenDictWithBasering: sage: sage_eval('3*a_3*b_5-1/2*a_7', D) -1/2*a_7 + 3*a_3*b_5 - """ - - def __init__(self,parent, start): + def __init__(self, parent, start): """ INPUT: @@ -547,14 +544,13 @@ def __init__(self,parent, start): KeyError: 'a' sage: D['a'] a - """ P = self._P = parent - if isinstance(start,list): + if isinstance(start, list): self._D = start return self._D = [start] - while hasattr(P,'base_ring') and (P.base_ring() is not P): + while hasattr(P, 'base_ring') and (P.base_ring() is not P): P = P.base_ring() D = P.gens_dict() if isinstance(D, GenDictWithBasering): @@ -562,6 +558,7 @@ def __init__(self,parent, start): break else: self._D.append(D) + def __next__(self): """ Return a dictionary that can be used to interprete strings in the base ring of ``self``. @@ -576,10 +573,9 @@ def __next__(self): GenDict of Univariate Polynomial Ring in t over Rational Field sage: sage_eval('t^2', next(D)) t^2 - """ - if len(self._D)<=1: - raise ValueError("No next term for %s available"%self) + if len(self._D) <= 1: + raise ValueError("no next term for %s available" % self) return GenDictWithBasering(self._P.base_ring(), self._D[1:]) next = __next__ @@ -593,7 +589,7 @@ def __repr__(self): sage: D GenDict of Infinite polynomial ring in a, b over Integer Ring """ - return "GenDict of "+repr(self._P) + return "GenDict of " + repr(self._P) def __getitem__(self, k): """ @@ -613,10 +609,11 @@ def __getitem__(self, k): return D[k] except KeyError: pass - raise KeyError("%s is not a variable name of %s or its iterated base rings"%(k,self._P)) + raise KeyError("%s is not a variable name of %s or its iterated base rings" % (k, self._P)) + ############################################################## -## The sparse implementation +# The sparse implementation class InfinitePolynomialRing_sparse(CommutativeRing): r""" @@ -697,20 +694,19 @@ def __init__(self, R, names, order): True sage: X.gen(1)[2]*Y.gen(0)[1] alpha_1*beta_2 - """ if not names: names = ['x'] for n in names: if not (isinstance(n, str) and n.isalnum() and (not n[0].isdigit())): - raise ValueError("generator names must be alpha-numeric strings not starting with a digit, but %s isn't"%n) + raise ValueError("generator names must be alpha-numeric strings not starting with a digit, but %s is not" % n) if len(names) != len(set(names)): raise ValueError("generator names must be pairwise different") self._names = tuple(names) if not isinstance(order, str): - raise TypeError("The monomial order must be given as a string") + raise TypeError("the monomial order must be given as a string") if R not in Rings().Commutative(): - raise TypeError("The given 'base ring' (= %s) must be a commutative ring" % R) + raise TypeError("the given 'base ring' (= %s) must be a commutative ring" % R) # now, the input is accepted if hasattr(R, '_underlying_ring'): @@ -722,7 +718,7 @@ def __init__(self, R, names, order): self._identify_variable = lambda x, y: (-self._names.index(x), int(y)) self._find_maxshift = re.compile('_([0-9]+)') # findall yields stringrep of the shifts self._find_variables = re.compile('[a-zA-Z0-9]+_[0-9]+') - self._find_varpowers = re.compile(r'([a-zA-Z0-9]+)_([0-9]+)\^?([0-9]*)') # findall yields triple "generator_name", "index", "exponent" + self._find_varpowers = re.compile(r'([a-zA-Z0-9]+)_([0-9]+)\^?([0-9]*)') # findall yields triple "generator_name", "index", "exponent" # Create some small underlying polynomial ring. # It is used to ensure that the parent of the underlying @@ -753,9 +749,8 @@ def __repr__(self): sage: X. = InfinitePolynomialRing(ZZ, order='deglex'); X Infinite polynomial ring in alpha, beta over Integer Ring - """ - return "Infinite polynomial ring in %s over %s"%(", ".join(self._names), self._base) + return "Infinite polynomial ring in %s over %s" % (", ".join(self._names), self._base) def _latex_(self): r""" @@ -794,10 +789,10 @@ def one(self): 1 """ from sage.rings.polynomial.infinite_polynomial_element import InfinitePolynomial - return InfinitePolynomial(self,self._base(1)) + return InfinitePolynomial(self, self._base(1)) ##################### - ## coercion + # coercion def construction(self): """ @@ -887,7 +882,7 @@ def _element_constructor_(self, x): sage: Y('1/3') Traceback (most recent call last): ... - ValueError: Can't convert 1/3 into an element of Infinite polynomial ring in x over Integer Ring + ValueError: cannot convert 1/3 into an element of Infinite polynomial ring in x over Integer Ring """ from sage.rings.polynomial.infinite_polynomial_element import InfinitePolynomial # In many cases, the easiest solution is to "simply" evaluate @@ -897,20 +892,20 @@ def _element_constructor_(self, x): try: x = sage_eval(x, self.gens_dict()) except Exception: - raise ValueError("Can't convert %s into an element of %s" % (x, self)) + raise ValueError("cannot convert %s into an element of %s" % (x, self)) P = parent(x) if P is self: return x elif self._base.has_coerce_map_from(P): return InfinitePolynomial(self, self._base(x)) else: - raise ValueError("Can't convert %s into an element of %s" % (x, self)) + raise ValueError("cannot convert %s into an element of %s" % (x, self)) if isinstance(parent(x), InfinitePolynomialRing_sparse): # the easy case - parent == self - is already past - if x.parent() is self._base: # another easy case - return InfinitePolynomial(self,x) - xmaxind = x.max_index() # save for later + if x.parent() is self._base: # another easy case + return InfinitePolynomial(self, x) + xmaxind = x.max_index() # save for later x = x._p else: xmaxind = -1 @@ -932,26 +927,26 @@ def _element_constructor_(self, x): # By now, we can assume that x has a parent, because # types like int have already been done in the previous step; # and also it is not an InfinitePolynomial. - # If it isn't a polynomial (duck typing: we need + # If it is not a polynomial (duck typing: we need # the variables attribute), we fall back to using strings - if not hasattr(x,'variables'): + if not hasattr(x, 'variables'): try: return sage_eval(repr(x), self.gens_dict()) except Exception: - raise ValueError("Can't convert %s into an element of %s" % (x, self)) + raise ValueError("cannot convert %s into an element of %s" % (x, self)) # direct conversion will only be used if the underlying polynomials are libsingular. from sage.rings.polynomial.multi_polynomial_libsingular import MPolynomial_libsingular, MPolynomialRing_libsingular # try interpretation in self._P, if we have a dense implementation - if hasattr(self,'_P'): + if hasattr(self, '_P'): if x.parent() is self._P: - return InfinitePolynomial(self,x) + return InfinitePolynomial(self, x) # It's a shame to use sage_eval. However, it's even more of a shame - # that MPolynomialRing_polydict doesn't work in complicated settings. + # that MPolynomialRing_polydict does not work in complicated settings. # So, if self._P is libsingular (and this will be the case in many # applications!), we do it "nicely". Otherwise, we have to use sage_eval. - if isinstance(x, MPolynomial_libsingular) and isinstance(self._P,MPolynomialRing_libsingular): - if xmaxind == -1: # Otherwise, x has been an InfinitePolynomial + if isinstance(x, MPolynomial_libsingular) and isinstance(self._P, MPolynomialRing_libsingular): + if xmaxind == -1: # Otherwise, x has been an InfinitePolynomial # We infer the correct variable shift. # Note: Since we are in the "libsingular" case, there are # no further "variables" hidden in the base ring of x.parent() @@ -963,7 +958,7 @@ def _element_constructor_(self, x): # This tests admissibility on the fly: VarList.sort(key=self.varname_key, reverse=True) except ValueError: - raise ValueError("Can't convert %s into an element of %s - variables aren't admissible"%(x,self)) + raise ValueError("cannot convert %s into an element of %s - variables are not admissible" % (x, self)) xmaxind = max([int(v.split('_')[1]) for v in VarList]) try: # Apparently, in libsingular, the polynomial conversion is not done by @@ -975,17 +970,17 @@ def _element_constructor_(self, x): if self._max < xmaxind: self.gen()[xmaxind] if self._P.ngens() == x.parent().ngens(): - self.gen()[self._max+1] + self.gen()[self._max + 1] # conversion to self._P will be done in InfinitePolynomial.__init__ return InfinitePolynomial(self, x) except (ValueError, TypeError, NameError): - raise ValueError("Can't convert %s (from %s, but variables %s) into an element of %s - no conversion into underlying polynomial ring %s"%(x,x.parent(),x.variables(),self,self._P)) + raise ValueError("cannot convert %s (from %s, but variables %s) into an element of %s - no conversion into underlying polynomial ring %s" % (x, x.parent(), x.variables(), self, self._P)) # By now, x or self._P are not libsingular. Since MPolynomialRing_polydict # is too buggy, we use string evaluation try: return sage_eval(repr(x), self.gens_dict()) except (ValueError, TypeError, NameError): - raise ValueError("Can't convert %s into an element of %s - no conversion into underlying polynomial ring"%(x,self)) + raise ValueError("cannot convert %s into an element of %s - no conversion into underlying polynomial ring" % (x, self)) # By now, we are in the sparse case. try: @@ -996,48 +991,48 @@ def _element_constructor_(self, x): # This tests admissibility on the fly: VarList.sort(key=self.varname_key, reverse=True) except ValueError: - raise ValueError("Can't convert %s into an element of %s - variables aren't admissible"%(x,self)) + raise ValueError("cannot convert %s into an element of %s - variables are not admissible" % (x, self)) - if len(VarList)==1: + if len(VarList) == 1: # univariate polynomial rings are crab. So, make up another variable. - if VarList[0]==self._names[0]+'_0': - VarList.append(self._names[0]+'_1') + if VarList[0] == self._names[0] + '_0': + VarList.append(self._names[0] + '_1') else: - VarList.append(self._names[0]+'_0') + VarList.append(self._names[0] + '_0') # We ensure that polynomial conversion is done by names; # the problem is that it is done by names if the number of variables coincides. - if len(VarList)==x.parent().ngens(): + if len(VarList) == x.parent().ngens(): BigList = x.parent().variable_names() ind = 2 - while self._names[0]+'_'+str(ind) in BigList: - ind+=1 - VarList.append(self._names[0]+'_'+str(ind)) + while self._names[0] + '_' + str(ind) in BigList: + ind += 1 + VarList.append(self._names[0] + '_' + str(ind)) try: VarList.sort(key=self.varname_key, reverse=True) except ValueError: - raise ValueError("Can't convert %s into an element of %s; the variables aren't admissible"%(x,self)) + raise ValueError("cannot convert %s into an element of %s; the variables are not admissible" % (x, self)) from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing R = PolynomialRing(self._base, VarList, order=self._order) - if isinstance(R, MPolynomialRing_libsingular) and isinstance(x,MPolynomial_libsingular): # everything else is so buggy that it's even not worth to try. + if isinstance(R, MPolynomialRing_libsingular) and isinstance(x, MPolynomial_libsingular): # everything else is so buggy that it's even not worth to try. try: # Problem: If there is only a partial overlap in the variables # of x.parent() and R, then R(x) raises an error (which, I think, # is a bug, since we talk here about conversion, not coercion). # Hence, for being on the safe side, we coerce into a pushout ring: - x = R(1)*x - return InfinitePolynomial(self,x) + x = R(1) * x + return InfinitePolynomial(self, x) except Exception: # OK, last resort, to be on the safe side try: return sage_eval(repr(x), self.gens_dict()) - except (ValueError,TypeError,NameError): - raise ValueError("Can't convert %s into an element of %s; conversion of the underlying polynomial failed"%(x,self)) + except (ValueError, TypeError, NameError): + raise ValueError("cannot convert %s into an element of %s; conversion of the underlying polynomial failed" % (x, self)) else: try: return sage_eval(repr(x), self.gens_dict()) - except (ValueError,TypeError,NameError): - raise ValueError("Can't convert %s into an element of %s"%(x,self)) + except (ValueError, TypeError, NameError): + raise ValueError("cannot convert %s into an element of %s" % (x, self)) def tensor_with_ring(self, R): """ @@ -1073,20 +1068,20 @@ def tensor_with_ring(self, R): True """ if not R.has_coerce_map_from(self._underlying_ring): - raise TypeError("We can't tensor with "+repr(R)) + raise TypeError("we cannot tensor with " + repr(R)) B = self.base_ring() - if hasattr(B,'tensor_with_ring'): + if hasattr(B, 'tensor_with_ring'): return InfinitePolynomialRing(B.tensor_with_ring(R), self._names, self._order, implementation='sparse') - if hasattr(B,'change_ring'): # e.g., polynomial rings + if hasattr(B, 'change_ring'): # e.g., polynomial rings return InfinitePolynomialRing(B.change_ring(R), self._names, self._order, implementation='sparse') # try to find the correct base ring in other ways: try: - o = B.one()*R.one() + o = B.one() * R.one() except Exception: - raise TypeError("We can't tensor with "+repr(R)) + raise TypeError("we cannot tensor with " + repr(R)) return InfinitePolynomialRing(o.parent(), self._names, self._order, implementation='sparse') - ## Basic Ring Properties + # Basic Ring Properties # -- some stuff that is useful for quotient rings etc. def is_noetherian(self): """ @@ -1230,7 +1225,6 @@ def gen(self, i=None): sage: XX = InfinitePolynomialRing(GF(5)) sage: XX.gen(0) is XX.gen() True - """ if i is not None and i > len(self._names): raise ValueError @@ -1449,48 +1443,49 @@ def __getitem__(self, i): alpha_1 """ if int(i) != i: - raise ValueError("The index (= %s) must be an integer" % i) + raise ValueError("the index (= %s) must be an integer" % i) i = int(i) if i < 0: - raise ValueError("The index (= %s) must be non-negative" % i) + raise ValueError("the index (= %s) must be non-negative" % i) P = self._parent from sage.rings.polynomial.infinite_polynomial_element import InfinitePolynomial_dense, InfinitePolynomial_sparse from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing OUT = self._output.get(i) - if hasattr(P,'_P'): + if hasattr(P, '_P'): if i <= P._max: - #return InfinitePolynomial_dense(P, P._P.gen(P._P.variable_names().index(self._name+'_'+str(i)))) + # return InfinitePolynomial_dense(P, P._P.gen(P._P.variable_names().index(self._name+'_'+str(i)))) if OUT is None: - self._output[i] = InfinitePolynomial_dense(P, P._P.gen(P._P.variable_names().index(self._name+'_'+str(i)))) + self._output[i] = InfinitePolynomial_dense(P, P._P.gen(P._P.variable_names().index(self._name + '_' + str(i)))) else: if OUT._p.parent() is not P._P: OUT._p = P._P(OUT._p) return self._output[i] - #Calculate all of the new names needed + # Calculate all of the new names needed try: - names = [ [name+'_'+str(j) for name in P._names] for j in range(i+1)] + names = [[name + '_' + str(j) for name in P._names] + for j in range(i + 1)] except OverflowError: - raise IndexError("Variable index is too big - consider using the sparse implementation") + raise IndexError("variable index is too big - consider using the sparse implementation") names = reduce(operator.add, names) names.sort(key=P.varname_key, reverse=True) - #Create the new polynomial ring - P._P = PolynomialRing(P.base_ring(), names, order = P._order) - ##Get the generators + # Create the new polynomial ring + P._P = PolynomialRing(P.base_ring(), names, order=P._order) + # Get the generators P._max = i - #return InfinitePolynomial_dense(P, P._P.gen(P._P.variable_names().index(self._name+'_'+str(i)))) - self._output[i] = InfinitePolynomial_dense(P, P._P.gen(P._P.variable_names().index(self._name+'_'+str(i)))) + # return InfinitePolynomial_dense(P, P._P.gen(P._P.variable_names().index(self._name+'_'+str(i)))) + self._output[i] = InfinitePolynomial_dense(P, P._P.gen(P._P.variable_names().index(self._name + '_' + str(i)))) return self._output[i] # Now, we are in the sparse implementation - if OUT is not None: # in the sparse implementation, this is ok + if OUT is not None: # in the sparse implementation, this is ok return OUT - if i==0: - names = [self._name+'_0',self._name+'_1'] + if i == 0: + names = [self._name + '_0', self._name + '_1'] else: - names = [self._name+'_0',self._name+'_'+str(i)] + names = [self._name + '_0', self._name + '_' + str(i)] names.sort(key=P.varname_key, reverse=True) Pol = PolynomialRing(P.base_ring(), names, order=P._order) - #return InfinitePolynomial_sparse(P, Pol.gen(names.index(self._name+'_'+str(i)))) - self._output[i] = InfinitePolynomial_sparse(P, Pol.gen(names.index(self._name+'_'+str(i)))) + # return InfinitePolynomial_sparse(P, Pol.gen(names.index(self._name+'_'+str(i)))) + self._output[i] = InfinitePolynomial_sparse(P, Pol.gen(names.index(self._name + '_' + str(i)))) return self._output[i] def _repr_(self): @@ -1500,9 +1495,8 @@ def _repr_(self): sage: X. = InfinitePolynomialRing(QQ) sage: x # indirect doctest x_* - """ - return self._name+'_*' + return self._name + '_*' def __str__(self): """ @@ -1511,12 +1505,12 @@ def __str__(self): sage: X. = InfinitePolynomialRing(QQ) sage: print(x) # indirect doctest Generator for the x's in Infinite polynomial ring in x, y over Rational Field - """ - return "Generator for the %s's in %s"%(self._name, self._parent) + return "Generator for the %s's in %s" % (self._name, self._parent) + ############################################################## -## The dense implementation +# The dense implementation class InfinitePolynomialRing_dense(InfinitePolynomialRing_sparse): """ @@ -1537,14 +1531,14 @@ def __init__(self, R, names, order): """ if not names: names = ['x'] - #Generate the initial polynomial ring + # Generate the initial polynomial ring self._max = 0 InfinitePolynomialRing_sparse.__init__(self, R, names, order) self._P = self._minP - #self._pgens = self._P.gens() + # self._pgens = self._P.gens() ##################### - ## Coercion + # Coercion def construction(self): """ @@ -1598,17 +1592,17 @@ def tensor_with_ring(self, R): """ if not R.has_coerce_map_from(self._underlying_ring): - raise TypeError("We can't tensor with "+repr(R)) + raise TypeError("we cannot tensor with " + repr(R)) B = self.base_ring() - if hasattr(B,'tensor_with_ring'): + if hasattr(B, 'tensor_with_ring'): return InfinitePolynomialRing(B.tensor_with_ring(R), self._names, self._order, implementation='dense') - if hasattr(B,'change_ring'): # e.g., polynomial rings + if hasattr(B, 'change_ring'): # e.g., polynomial rings return InfinitePolynomialRing(B.change_ring(R), self._names, self._order, implementation='dense') # try to find the correct base ring in other ways: try: - o = B.one()*R.one() + o = B.one() * R.one() except Exception: - raise TypeError("We can't tensor with "+repr(R)) + raise TypeError("we cannot tensor with " + repr(R)) return InfinitePolynomialRing(o.parent(), self._names, self._order, implementation='dense') def polynomial_ring(self): diff --git a/src/sage/rings/polynomial/msolve.py b/src/sage/rings/polynomial/msolve.py index b32f9f8505a..0be0c676758 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. +Note that the `optional package msolve <../spkg/msolve.html>`_ must be installed. .. 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,132 @@ 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 + + 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().absolute_filename(), "-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}") -def _variety(ideal, ring, proof): + 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=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 +205,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,28 +258,37 @@ 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: den_of_rt = den(rt) - point = [-p(rt)/den_of_rt for p in param] + point = [-p(rt) / den_of_rt for p in param] if len(param) != len(vars): point.append(rt) assert len(point) == len(vars) @@ -201,10 +296,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 +307,7 @@ 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..c2de7cbd075 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,14 @@ 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 `optional package msolve <../spkg/msolve.html>`_ + 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,15 +2518,35 @@ 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), uses triangular decomposition, via Singular if possible, falling back on a toy implementation otherwise. - - With ``algorithm`` = ``"msolve"``, calls the external program - `msolve `_ (if available in the system - program search path). Note that msolve uses heuristics and therefore + - With ``algorithm`` = ``"msolve"``, uses the + `optional package msolve <../spkg/msolve.html>`_. + Note that msolve uses heuristics and therefore requires setting the ``proof`` flag to ``False``. See :mod:`~sage.rings.polynomial.msolve` for more information. """ @@ -2453,7 +2554,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 +4089,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' + `optional package msolve <../spkg/msolve.html>`_ (degrevlex order, + prime fields) + 'magma:GroebnerBasis' Magma's ``Groebnerbasis`` command (if available) @@ -4111,6 +4216,14 @@ 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 the + `optional package msolve <../spkg/msolve.html>`_:: + + 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. diff --git a/src/sage/rings/polynomial/pbori/pbori.pyx b/src/sage/rings/polynomial/pbori/pbori.pyx index e369473c3c4..41850904621 100644 --- a/src/sage/rings/polynomial/pbori/pbori.pyx +++ b/src/sage/rings/polynomial/pbori/pbori.pyx @@ -328,8 +328,6 @@ cdef class BooleanPolynomialRing(MPolynomialRing_base): sage: S. = BooleanPolynomialRing(2, order='deglex') sage: P == S False - - """ def __init__(self, n=None, names=None, order='lex'): """ @@ -348,7 +346,7 @@ cdef class BooleanPolynomialRing(MPolynomialRing_base): cdef Py_ssize_t i, j, bstart, bsize if names is None: - raise TypeError("You must specify the names of the variables.") + raise TypeError("you must specify the names of the variables") if n is None: if isinstance(names, (tuple, list)): @@ -357,10 +355,10 @@ cdef class BooleanPolynomialRing(MPolynomialRing_base): try: n = int(n) except TypeError as msg: - raise TypeError("Number of variables must be an integer") + raise TypeError("number of variables must be an integer") if n < 1: - raise ValueError("Number of variables must be greater than 1.") + raise ValueError("number of variables must be greater than 1") self.pbind = sig_malloc(n*sizeof(Py_ssize_t)) @@ -369,13 +367,13 @@ cdef class BooleanPolynomialRing(MPolynomialRing_base): try: pb_order_code = order_mapping[order[0].name()] except KeyError: - raise ValueError("Only order keys " + + raise ValueError("only order keys " + ', '.join(order_mapping.keys()) + - " are supported.") + " are supported") if order.is_block_order(): if pb_order_code is pblp: - raise ValueError("Only deglex and degneglex are supported for block orders.") + raise ValueError("only deglex and degneglex are supported for block orders") elif pb_order_code is pbdlex: pb_order_code = pbblock_dlex elif pb_order_code is pbdp_asc: @@ -384,8 +382,8 @@ cdef class BooleanPolynomialRing(MPolynomialRing_base): pb_order_code = pbblock_dp for i in range(1, len(order.blocks())): if order[0].name() != order[i].name(): - raise ValueError("Each block must have the same order type " - "(deglex and degneglex) for block orders.") + raise ValueError("each block must have the same order type " + "(deglex and degneglex) for block orders") if pb_order_code is pbdp: for i in range(n): @@ -506,8 +504,7 @@ cdef class BooleanPolynomialRing(MPolynomialRing_base): INPUT: - - ``i`` - an integer or a boolean monomial in one - variable + - ``i`` -- an integer or a boolean monomial in one variable EXAMPLES:: @@ -530,10 +527,10 @@ cdef class BooleanPolynomialRing(MPolynomialRing_base): if len(i) == 1: i = i.index() else: - raise TypeError("Boolean monomials must be in one variable only.") + raise TypeError("boolean monomials must be in one variable only") cdef idx = int(i) if idx < 0 or idx >= self._pbring.nVariables(): - raise ValueError("Generator not defined.") + raise ValueError("generator not defined") return new_BP_from_PBVar(self, self._pbring.variable(self.pbind[idx])) def gens(self): @@ -551,7 +548,6 @@ cdef class BooleanPolynomialRing(MPolynomialRing_base): sage: P = BooleanPolynomialRing(10,'x') sage: P.gens() (x0, x1, x2, x3, x4, x5, x6, x7, x8, x9) - """ return tuple(new_BP_from_PBVar(self, self._pbring.variable(self.pbind[i])) @@ -559,14 +555,12 @@ cdef class BooleanPolynomialRing(MPolynomialRing_base): def change_ring(self, base_ring=None, names=None, order=None): """ - Return a new multivariate polynomial ring with base ring - ``base_ring``, variable names set to ``names``, and term - ordering given by ``order``. + Return a new multivariate polynomial ring with base ring ``base_ring``, + variable names set to ``names``, and term ordering given by ``order``. When ``base_ring`` is not specified, this function returns a ``BooleanPolynomialRing`` isomorphic to ``self``. Otherwise, - this returns a ``MPolynomialRing``. Each argument above is - optional. + this returns a ``MPolynomialRing``. Each argument above is optional. INPUT: @@ -590,7 +584,6 @@ cdef class BooleanPolynomialRing(MPolynomialRing_base): sage: T.term_order() Lexicographic term order """ - if names is None: names = self.variable_names() if order is None: @@ -1082,10 +1075,9 @@ cdef class BooleanPolynomialRing(MPolynomialRing_base): sage: R.remove_var(x,y,z) Traceback (most recent call last): ... - ValueError: impossible to use the original term order (most likely because it was a block order). Please specify the term order for the subring + ValueError: impossible to use the original term order (most likely because it was a block order); please specify the term order for the subring sage: R.remove_var(x,y,z, order='deglex') Boolean PolynomialRing in u, v - """ vars = list(self.variable_names()) for v in var: @@ -1096,7 +1088,7 @@ cdef class BooleanPolynomialRing(MPolynomialRing_base): try: return BooleanPolynomialRing(names=vars, order=self.term_order()) except ValueError: - raise ValueError("impossible to use the original term order (most likely because it was a block order). Please specify the term order for the subring") + raise ValueError("impossible to use the original term order (most likely because it was a block order); please specify the term order for the subring") else: return BooleanPolynomialRing(names=vars, order=order) @@ -1205,7 +1197,7 @@ cdef class BooleanPolynomialRing(MPolynomialRing_base): sage: P.random_element(degree=4) Traceback (most recent call last): ... - ValueError: Given degree should be less than or equal to number of variables (3) + ValueError: given degree should be less than or equal to number of variables (3) sage: f = P.random_element(degree=1, terms=5) sage: f.degree() <= 1 @@ -1251,7 +1243,7 @@ cdef class BooleanPolynomialRing(MPolynomialRing_base): degree = Integer(degree) if degree > nvars: - raise ValueError("Given degree should be less than or equal to number of variables (%s)" % nvars) + raise ValueError("given degree should be less than or equal to number of variables (%s)" % nvars) tot_terms = 0 monom_counts = [] @@ -1638,10 +1630,10 @@ cdef class BooleanPolynomialRing(MPolynomialRing_base): if len(i) == 1: i = i.index() else: - raise TypeError("Boolean monomials must be in one variable only.") + raise TypeError("boolean monomials must be in one variable only") i = int(i) if i < 0 or i >= self._pbring.nVariables(): - raise ValueError("Generator not defined.") + raise ValueError("generator not defined") return new_BM_from_PBVar(self._monom_monoid, self, self._pbring.variable(self.pbind[i])) @@ -1998,7 +1990,7 @@ class BooleanMonomialMonoid(UniqueRepresentation, Monoid_class): x50 """ if i < 0 or i >= self.ngens(): - raise ValueError("Generator not defined.") + raise ValueError("generator not defined") cdef PBVar newvar newvar = PBBooleVariable(i, (self._ring)._pbring) @@ -2382,11 +2374,11 @@ cdef class BooleanMonomial(MonoidElement): """ P = self.parent() if args and kwds: - raise ValueError("Using keywords and regular arguments not supported.") + raise ValueError("using keywords and regular arguments not supported") if args: - d = {} if len(args) > self._parent.ngens(): - raise ValueError("Number of arguments is greater than the number of variables of parent ring.") + raise ValueError("number of arguments is greater than the number of variables of parent ring") + d = {} for i in range(len(args)): d[i] = args[i] elif kwds: @@ -2522,7 +2514,7 @@ cdef class BooleanMonomial(MonoidElement): return self._pbmonom.deg() if x not in self._parent.gens(): - raise ValueError("x must be one of the generators of the parent.") + raise ValueError("x must be one of the generators of the parent") if self.reducible_by(x.lm()): return 1 @@ -3004,7 +2996,7 @@ cdef class BooleanPolynomial(MPolynomial): sage: a._repr_with_changed_varnames([1,'y','z']) Traceback (most recent call last): ... - TypeError: varnames has entries with wrong type. + TypeError: varnames has entries with wrong type :: @@ -3016,7 +3008,7 @@ cdef class BooleanPolynomial(MPolynomial): cdef int N = P._pbring.nVariables() if len(varnames) != N: - raise TypeError("len(varnames) doesn't equal self.parent().ngens()") + raise TypeError("len(varnames) is not equal to self.parent().ngens()") orig_varnames = P.variable_names() try: @@ -3025,7 +3017,7 @@ cdef class BooleanPolynomial(MPolynomial): except TypeError: for i in range(N): P._pbring.setVariableName(i, str_to_bytes(orig_varnames[i])) - raise TypeError("varnames has entries with wrong type.") + raise TypeError("varnames has entries with wrong type") s = ccrepr(self._pbpoly) for i in range(N): P._pbring.setVariableName(i, str_to_bytes(orig_varnames[i])) @@ -3256,7 +3248,7 @@ cdef class BooleanPolynomial(MPolynomial): sage: p^-1 Traceback (most recent call last): ... - NotImplementedError: Negative exponents for non constant boolean polynomials not implemented. + NotImplementedError: negative exponents for non constant boolean polynomials not implemented :: @@ -3278,7 +3270,7 @@ cdef class BooleanPolynomial(MPolynomial): elif self._pbpoly.isZero(): raise ZeroDivisionError else: - raise NotImplementedError("Negative exponents for non constant boolean polynomials not implemented.") + raise NotImplementedError("negative exponents for non constant boolean polynomials not implemented") def __neg__(BooleanPolynomial self): r""" @@ -3953,11 +3945,11 @@ cdef class BooleanPolynomial(MPolynomial): P = self._parent cdef int N = P.ngens() if args and kwds: - raise ValueError("Using keywords and regular arguments not supported.") + raise ValueError("using keywords and regular arguments not supported") if args: d = {} if len(args) != N: - raise ValueError("Number of arguments is different from the number of variables of parent ring.") + raise ValueError("number of arguments is different from the number of variables of parent ring") for i in range(N): arg = args[i] try: @@ -4565,7 +4557,7 @@ cdef class BooleanPolynomial(MPolynomial): L.append(tuple(l)) return tuple(L) else: - raise TypeError("Type '%s' of s not supported." % type(s)) + raise TypeError("type '%s' of s not supported" % type(s)) def spoly(self, BooleanPolynomial rhs): r""" @@ -4665,7 +4657,7 @@ cdef class BooleanPolynomial(MPolynomial): sage: a.reduce([None,None]) Traceback (most recent call last): ... - TypeError: argument must be a BooleanPolynomial. + TypeError: argument must be a BooleanPolynomial """ from sage.rings.polynomial.pbori.pbori import red_tail if not I: @@ -4674,7 +4666,7 @@ cdef class BooleanPolynomial(MPolynomial): I = I.gens() first = I[0] if first is None: - raise TypeError("argument must be a BooleanPolynomial.") + raise TypeError("argument must be a BooleanPolynomial") g = ReductionStrategy(first.ring()) g.opt_red_tail = True for p in I: @@ -4729,7 +4721,7 @@ cdef class PolynomialConstruct: # So, it is just a conversion. [Simon King] return (ring)._element_constructor_(x) - raise TypeError("Cannot generate Boolean polynomial from %s , %s" % + raise TypeError("cannot generate Boolean polynomial from %s , %s" % (type(x), type(ring))) @@ -4768,7 +4760,7 @@ cdef class MonomialConstruct: return result.lm() return result except Exception: - raise TypeError("Cannot convert to Boolean Monomial %s" % + raise TypeError("cannot convert to Boolean Monomial %s" % type(x)) cdef class VariableConstruct: @@ -4790,11 +4782,10 @@ cdef class VariableConstruct: """ if isinstance(arg, BooleanPolynomialRing): return arg.variable(0) - elif isinstance(ring, BooleanPolynomialRing): + if isinstance(ring, BooleanPolynomialRing): return (ring).variable(arg) - else: - raise TypeError("todo polynomial factory %s%s" % - (str(type(arg)), str(type(ring)))) + raise TypeError("todo polynomial factory %s%s" % + (str(type(arg)), str(type(ring)))) cdef class BooleanPolynomialIterator: @@ -4932,7 +4923,7 @@ class BooleanPolynomialIdeal(MPolynomialIdeal): - ``lazy`` - (default: ``True``) - ``invert`` - setting ``invert=True`` input and output get a - transformation ``x+1`` for each variable ``x``, which shouldn't + transformation ``x+1`` for each variable ``x``, which should not effect the calculated GB, but the algorithm. - ``other_ordering_first`` - possible values are ``False`` or @@ -5526,7 +5517,7 @@ cdef class BooleSet: elif isinstance(rhs, BooleanPolynomial): s = (rhs)._pbpoly.set() else: - raise TypeError("Argument 'rhs' has incorrect type (expected BooleSet or BooleanPolynomial, got %s)" % type(rhs)) + raise TypeError("argument 'rhs' has incorrect type (expected BooleSet or BooleanPolynomial, got %s)" % type(rhs)) return new_BS_from_PBSet(self._pbset.diff(s), self._ring) def union(self, rhs): @@ -5560,7 +5551,7 @@ cdef class BooleSet: elif isinstance(rhs, BooleanPolynomial): s = (rhs)._pbpoly.set() else: - raise TypeError("Argument 'rhs' has incorrect type (expected BooleSet or BooleanPolynomial, got %s)" % type(rhs)) + raise TypeError("argument 'rhs' has incorrect type (expected BooleSet or BooleanPolynomial, got %s)" % type(rhs)) return new_BS_from_PBSet(self._pbset.unite(s), self._ring) def change(self, ind): @@ -6175,7 +6166,7 @@ cdef class BooleanPolynomialVector: elif isinstance(el, BooleanMonomial): p = PBBoolePolynomial((el)._pbmonom) else: - raise TypeError("Argument 'el' has incorrect type (expected BooleanPolynomial or BooleanMonomial, got %s)" % type(el)) + raise TypeError("argument 'el' has incorrect type (expected BooleanPolynomial or BooleanMonomial, got %s)" % type(el)) self._vec.push_back(p) cdef inline BooleanPolynomialVector new_BPV_from_PBPolyVector( @@ -6255,12 +6246,12 @@ cdef class ReductionStrategy: sage: red.add_generator(None) Traceback (most recent call last): ... - TypeError: argument must be a BooleanPolynomial. + TypeError: argument must be a BooleanPolynomial """ if p is None: - raise TypeError("argument must be a BooleanPolynomial.") + raise TypeError("argument must be a BooleanPolynomial") if p._pbpoly.isZero(): - raise ValueError("zero generators not allowed.") + raise ValueError("zero generators not allowed") deref(self._strat).addGenerator(p._pbpoly) def nf(self, BooleanPolynomial p): @@ -6525,7 +6516,6 @@ cdef class FGLMStrategy: Traceback (most recent call last): ... RuntimeError... - """ cdef BooleanPolynomialRing _from_ring, _to_ring @@ -6598,7 +6588,7 @@ cdef class GroebnerStrategy: self._strat = make_shared[PBGBStrategy]((param)._pbring) self._parent = param else: - raise ValueError("Cannot generate GroebnerStrategy from %s." % + raise ValueError("cannot generate GroebnerStrategy from %s" % type(param)) self.reduction_strategy = ReductionStrategy(self._parent) @@ -6629,7 +6619,7 @@ cdef class GroebnerStrategy: [a + b, a + c] """ if p._pbpoly.isZero(): - raise ValueError("zero generators not allowed.") + raise ValueError("zero generators not allowed") deref(self._strat).addGeneratorDelayed(p._pbpoly) def add_generator(self, BooleanPolynomial p): @@ -6654,7 +6644,7 @@ cdef class GroebnerStrategy: ValueError: strategy already contains a polynomial with same lead """ if p._pbpoly.isZero(): - raise ValueError("zero generators not allowed.") + raise ValueError("zero generators not allowed") if deref(self._strat).generators.leadingTerms.owns(p._pbpoly.lead()): raise ValueError("strategy already contains a polynomial with same lead") deref(self._strat).generators.addGenerator(p._pbpoly) @@ -6689,7 +6679,7 @@ cdef class GroebnerStrategy: [a + c, b + c] """ if p._pbpoly.isZero(): - raise ValueError("zero generators not allowed.") + raise ValueError("zero generators not allowed") deref(self._strat).addAsYouWish(p._pbpoly) def implications(self, i): @@ -7207,7 +7197,7 @@ def zeros(pol, BooleSet s): elif isinstance(pol, BooleanMonomial): p = PBBoolePolynomial((pol)._pbmonom) else: - raise TypeError("Argument 'p' has incorrect type (expected BooleanPolynomial or BooleanMonomial, got %s)" % type(pol)) + raise TypeError("argument 'p' has incorrect type (expected BooleanPolynomial or BooleanMonomial, got %s)" % type(pol)) return new_BS_from_PBSet(pb_zeros(p, s._pbset), s._ring) @@ -7256,13 +7246,13 @@ def interpolate(zero, one): z = (zero)._pbpoly.set() ring = (zero)._parent else: - raise TypeError("Argument 'zero' has incorrect type (expected BooleSet or BooleanPolynomial, got %s)" % type(zero)) + raise TypeError("argument 'zero' has incorrect type (expected BooleSet or BooleanPolynomial, got %s)" % type(zero)) if isinstance(one, BooleSet): o = (one)._pbset elif isinstance(one, BooleanPolynomial): o = (one)._pbpoly.set() else: - raise TypeError("Argument 'one' has incorrect type (expected BooleSet or BooleanPolynomial, got %s)" % type(one)) + raise TypeError("argument 'one' has incorrect type (expected BooleSet or BooleanPolynomial, got %s)" % type(one)) return new_BP_from_PBPoly(ring, pb_interpolate(z, o)) @@ -7333,13 +7323,13 @@ def interpolate_smallest_lex(zero, one): elif isinstance(zero, BooleanPolynomial): z = (zero)._pbpoly.set() else: - raise TypeError("Argument 'zero' has incorrect type (expected BooleSet or BooleanPolynomial, got %s)" % type(zero)) + raise TypeError("argument 'zero' has incorrect type (expected BooleSet or BooleanPolynomial, got %s)" % type(zero)) if isinstance(one, BooleSet): o = (one)._pbset elif isinstance(one, BooleanPolynomial): o = (one)._pbpoly.set() else: - raise TypeError("Argument 'one' has incorrect type (expected BooleSet or BooleanPolynomial, got %s)" % type(one)) + raise TypeError("argument 'one' has incorrect type (expected BooleSet or BooleanPolynomial, got %s)" % type(one)) return new_BP_from_PBPoly(zero.ring(), pb_interpolate_smallest_lex(z, o)) @@ -7400,7 +7390,7 @@ def ll_red_nf_redsb(p, BooleSet reductors): t = PBBoolePolynomial((p)._pbmonom) parent = (p)._ring else: - raise TypeError("Argument 'p' has incorrect type (expected BooleSet or BooleanPolynomial, got %s)" % type(p)) + raise TypeError("argument 'p' has incorrect type (expected BooleSet or BooleanPolynomial, got %s)" % type(p)) res = pb_ll_red_nf(t, reductors._pbset) @@ -7513,7 +7503,7 @@ def if_then_else(root, a, b): sage: if_then_else(x5, f0, f1) Traceback (most recent call last): ... - IndexError: index of root must be less than the values of roots of the branches. + IndexError: index of root must be less than the values of roots of the branches """ cdef PBSet a_set, b_set cdef PBSet res @@ -7525,14 +7515,14 @@ def if_then_else(root, a, b): b_set = (b)._pbpoly.set() ring = (b)._parent else: - raise TypeError("Argument 'b' has incorrect type (expected BooleSet or BooleanPolynomial, got %s)" % type(b)) + raise TypeError("argument 'b' has incorrect type (expected BooleSet or BooleanPolynomial, got %s)" % type(b)) if isinstance(a, BooleSet): a_set = (a)._pbset elif isinstance(a, BooleanPolynomial): a_set = (a)._pbpoly.set() else: - raise TypeError("Argument 'a' has incorrect type (expected BooleSet or BooleanPolynomial, got %s)" % type(a)) + raise TypeError("argument 'a' has incorrect type (expected BooleSet or BooleanPolynomial, got %s)" % type(a)) try: root = int(root) @@ -7541,22 +7531,22 @@ def if_then_else(root, a, b): if len(root) == 1: root = root.lm() else: - raise TypeError("Only variables are acceptable as root.") + raise TypeError("only variables are acceptable as root") if isinstance(root, BooleanMonomial): if len(root) == 1: root = root.index() else: - raise TypeError("Only variables are acceptable as root.") + raise TypeError("only variables are acceptable as root") if not isinstance(root, int): - raise TypeError("Only variables are acceptable as root.") + raise TypeError("only variables are acceptable as root") cdef Py_ssize_t* pbind = ring.pbind root = ring.pbind[root] if (root >= a_set.navigation().value()) or (root >= b_set.navigation().value()): raise IndexError("index of root must be less than " - "the values of roots of the branches.") + "the values of roots of the branches") res = PBBooleSet(root, a_set.navigation(), b_set.navigation(), ring._pbring) @@ -7590,7 +7580,7 @@ def top_index(s): elif isinstance(s, BooleanPolynomial): idx = (s)._pbpoly.navigation().value() else: - raise TypeError("Argument 's' has incorrect type (expected BooleSet, BooleanMonomial or BooleanPolynomial, got %s)" % type(s)) + raise TypeError("argument 's' has incorrect type (expected BooleSet, BooleanMonomial or BooleanPolynomial, got %s)" % type(s)) return (s.ring()).pbind[idx] @@ -8010,19 +8000,16 @@ cdef class VariableFactory: a sage: VariableFactory(B)(0) a - """ if ring is None and self._ring is not None: return new_BM_from_PBVar(self._ring._monom_monoid, self._ring, self._factory(arg)) - elif isinstance(arg, BooleanPolynomialRing): + if isinstance(arg, BooleanPolynomialRing): return arg.variable(0) - elif isinstance(ring, BooleanPolynomialRing): + if isinstance(ring, BooleanPolynomialRing): return (ring).variable(arg) - else: - raise TypeError( - "Cannot convert (%s, %s) to Boolean Variable" % - (type(arg), type(ring))) + raise TypeError("cannot convert (%s, %s) to Boolean Variable" % + (type(arg), type(ring))) cdef class MonomialFactory: @@ -8097,7 +8084,7 @@ cdef class MonomialFactory: return result except Exception: raise TypeError( - "Cannot %s convert to Boolean Monomial" % type(arg)) + "cannot %s convert to Boolean Monomial" % type(arg)) cdef class PolynomialFactory: @@ -8174,5 +8161,5 @@ cdef class PolynomialFactory: return new_BP_from_PBPoly(self._ring, self._factory((arg)._pbmonom)) - raise TypeError("Cannot convert %s to BooleanPolynomial" % + raise TypeError("cannot convert %s to BooleanPolynomial" % type(arg)) diff --git a/src/sage/rings/polynomial/polynomial_element.pyx b/src/sage/rings/polynomial/polynomial_element.pyx index 4d5bd945ef0..69cb4e8bded 100644 --- a/src/sage/rings/polynomial/polynomial_element.pyx +++ b/src/sage/rings/polynomial/polynomial_element.pyx @@ -1,45 +1,50 @@ # coding: utf-8 """ -Univariate Polynomial Base Class +Univariate polynomial base class + +TESTS:: + + sage: R. = ZZ[] + sage: f = x^5 + 2*x^2 + (-1) + sage: f == loads(dumps(f)) + True + + sage: PolynomialRing(ZZ,'x').objgen() + (Univariate Polynomial Ring in x over Integer Ring, x) AUTHORS: -- William Stein: first version. +- William Stein: first version -- Martin Albrecht: Added singular coercion. +- Martin Albrecht: added singular coercion -- Robert Bradshaw: Move Polynomial_generic_dense to Cython. +- Robert Bradshaw: moved Polynomial_generic_dense to Cython -- Miguel Marco: Implemented resultant in the case where PARI fails. +- Miguel Marco: implemented resultant in the case where PARI fails -- Simon King: Use a faster way of conversion from the base ring. +- Simon King: used a faster way of conversion from the base ring -- Julian Rueth (2012-05-25,2014-05-09): Fixed is_squarefree() for imperfect - fields, fixed division without remainder over QQbar; added ``_cache_key`` - for polynomials with unhashable coefficients +- Kwankyu Lee (2013-06-02): enhanced :meth:`quo_rem` -- Simon King (2013-10): Implement copying of :class:`PolynomialBaseringInjection`. +- Julian Rueth (2012-05-25,2014-05-09): fixed is_squarefree() for imperfect + fields, fixed division without remainder over QQbar; added ``_cache_key`` + for polynomials with unhashable coefficients -- Kiran Kedlaya (2016-03): Added root counting. +- Simon King (2013-10): implemented copying of :class:`PolynomialBaseringInjection` -- Edgar Costa (2017-07): Added rational reconstruction. +- Bruno Grenet (2014-07-13): enhanced :meth:`quo_rem` -- Kiran Kedlaya (2017-09): Added reciprocal transform, trace polynomial. +- Kiran Kedlaya (2016-03): added root counting -- David Zureick-Brown (2017-09): Added is_weil_polynomial. +- Edgar Costa (2017-07): added rational reconstruction -- Sebastian Oehms (2018-10): made :meth:`roots` and :meth:`factor` work over more - cases of proper integral domains (see :trac:`26421`) +- Kiran Kedlaya (2017-09): added reciprocal transform, trace polynomial -TESTS:: +- David Zureick-Brown (2017-09): added is_weil_polynomial - sage: R. = ZZ[] - sage: f = x^5 + 2*x^2 + (-1) - sage: f == loads(dumps(f)) - True +- Sebastian Oehms (2018-10): made :meth:`roots` and :meth:`factor` work over more + cases of proper integral domains (see :trac:`26421`) - sage: PolynomialRing(ZZ,'x').objgen() - (Univariate Polynomial Ring in x over Integer Ring, x) """ # **************************************************************************** @@ -126,7 +131,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 +8930,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 +8955,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 +8966,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 +8978,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 +8998,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 +9007,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 +9019,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 +9030,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 +9041,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 +9081,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 +9140,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. @@ -11334,7 +11341,7 @@ cdef class Polynomial_generic_dense(Polynomial): sage: class BrokenRational(Rational): ....: def __bool__(self): ....: raise NotImplementedError("cannot check whether number is non-zero") - ....: + ....: sage: z = BrokenRational() sage: R. = QQ[] sage: from sage.rings.polynomial.polynomial_element import Polynomial_generic_dense @@ -11634,18 +11641,12 @@ cdef class Polynomial_generic_dense(Polynomial): Raises a ``ZerodivisionError`` if ``other`` is zero. Raises an ``ArithmeticError`` if the division is not exact. - AUTHORS: - - - Kwankyu Lee (2013-06-02) - - - Bruno Grenet (2014-07-13) - EXAMPLES:: sage: P. = QQ[] sage: R. = P[] - sage: f = R.random_element(10) - sage: g = y^5+R.random_element(4) + sage: f = y^10 + R.random_element(9) + sage: g = y^5 + R.random_element(4) sage: q,r = f.quo_rem(g) sage: f == q*g + r True 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..e397fffc6d8 100644 --- a/src/sage/rings/polynomial/polynomial_quotient_ring_element.py +++ b/src/sage/rings/polynomial/polynomial_quotient_ring_element.py @@ -714,3 +714,26 @@ 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 1e5bcc6e9a4..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): """ diff --git a/src/sage/rings/polynomial/skew_polynomial_finite_order.pxd b/src/sage/rings/polynomial/skew_polynomial_finite_order.pxd index ab22926414f..438773a39ef 100644 --- a/src/sage/rings/polynomial/skew_polynomial_finite_order.pxd +++ b/src/sage/rings/polynomial/skew_polynomial_finite_order.pxd @@ -2,6 +2,7 @@ from sage.rings.polynomial.skew_polynomial_element cimport SkewPolynomial_generi cdef class SkewPolynomial_finite_order_dense (SkewPolynomial_generic_dense): cdef _norm + cdef _charpoly cdef _optbound cdef _matphir_c(self) diff --git a/src/sage/rings/polynomial/skew_polynomial_finite_order.pyx b/src/sage/rings/polynomial/skew_polynomial_finite_order.pyx index e61f20e17f3..951fcd22c97 100644 --- a/src/sage/rings/polynomial/skew_polynomial_finite_order.pyx +++ b/src/sage/rings/polynomial/skew_polynomial_finite_order.pyx @@ -71,6 +71,7 @@ cdef class SkewPolynomial_finite_order_dense(SkewPolynomial_generic_dense): """ SkewPolynomial_generic_dense.__init__ (self, parent, x, check, construct, **kwds) self._norm = None + self._charpoly = None self._optbound = None @@ -87,7 +88,7 @@ cdef class SkewPolynomial_finite_order_dense(SkewPolynomial_generic_dense): cdef k = parent.base_ring() cdef RingElement zero = k(0) cdef RingElement one = k(1) - cdef list line, phir = [ ] + cdef list line, phir = [] if r < d: for i from 0 <= i < d-r: line = d * [zero] @@ -130,10 +131,6 @@ cdef class SkewPolynomial_finite_order_dense(SkewPolynomial_generic_dense): Return the matrix of the multiplication by ``self`` on `K[X,\sigma]` considered as a free module over `K[X^r]` (here `r` is the order of `\sigma`). - - .. WARNING:: - - Does not work if self is not monic. """ from sage.matrix.constructor import matrix cdef Py_ssize_t i, j, deb, k, r = self.parent()._order @@ -142,15 +139,15 @@ cdef class SkewPolynomial_finite_order_dense(SkewPolynomial_generic_dense): cdef RingElement minusone = base_ring(-1) cdef RingElement zero = base_ring(0) cdef Polk = PolynomialRing (base_ring, 'xr') - cdef list M = [ ] + cdef list M = [] cdef list l = self.list() - for j from 0 <= j < r: - for i from 0 <= i < r: + for j in range(r): + for i in range(r): if i < j: - pol = [ zero ] + pol = [zero] deb = i-j+r else: - pol = [ ] + pol = [] deb = i-j for k from deb <= k <= d by r: pol.append(l[k]) @@ -213,10 +210,14 @@ cdef class SkewPolynomial_finite_order_dense(SkewPolynomial_generic_dense): sage: b = S.random_element(degree=7) sage: a.reduced_trace() + b.reduced_trace() == (a+b).reduced_trace() True + + .. SEEALSO:: + + :meth:`reduced_norm`, :meth:`reduced_charpoly` """ order = self.parent()._order twisting_morphism = self.parent().twisting_morphism() - coeffs = [ ] + coeffs = [] for i in range(0, self.degree()+1, order): tr = c = self._coeffs[i] for _ in range(order-1): @@ -265,7 +266,7 @@ cdef class SkewPolynomial_finite_order_dense(SkewPolynomial_generic_dense): By default, the name of the central variable is usually ``z`` (see :meth:`~sage.rings.polynomial.skew_polynomial_ring.SkewPolynomialRing_finite_order.center` for more details about this). - However, the user can speciify a different variable name if desired:: + However, the user can specify a different variable name if desired:: sage: a.reduced_norm(var='u') u^3 + 4*u^2 + 4 @@ -312,6 +313,10 @@ cdef class SkewPolynomial_finite_order_dense(SkewPolynomial_generic_dense): polynomial of the left multiplication by `X` on the quotient `K[X,\sigma] / K[X,\sigma] P` (which is a `K`-vector space of dimension `d`). + + .. SEEALSO:: + + :meth:`reduced_trace`, :meth:`reduced_charpoly` """ if self._norm is None: if self.is_zero(): @@ -335,6 +340,84 @@ cdef class SkewPolynomial_finite_order_dense(SkewPolynomial_generic_dense): center = self.parent().center(name=var) return center(self._norm) + def reduced_charpoly(self, var=None): + r""" + Return the reduced characteristic polynomial of this + skew polynomial. + + INPUT: + + - ``var`` -- a string, a pair of strings or ``None`` + (default: ``None``); the variable names used for the + characteristic polynomial and the center + + .. NOTE:: + + The result is cached. + + EXAMPLES:: + + sage: k. = GF(5^3) + sage: Frob = k.frobenius_endomorphism() + sage: S. = k['u', Frob] + sage: a = u^3 + (2*t^2 + 3)*u^2 + (4*t^2 + t + 4)*u + 2*t^2 + 2 + sage: chi = a.reduced_charpoly() + sage: chi + x^3 + (2*z + 1)*x^2 + (3*z^2 + 4*z)*x + 4*z^3 + z^2 + 1 + + The reduced characteristic polynomial has coefficients in the center + of `S`, which is itself a univariate polynomial ring in the variable + `z = u^3` over `\GF{5}`. Hence it appears as a bivariate polynomial:: + + sage: chi.parent() + Univariate Polynomial Ring in x over Univariate Polynomial Ring in z over Finite Field of size 5 + + The constant coefficient of the reduced characteristic polynomial is + the reduced norm, up to a sign:: + + sage: chi[0] == -a.reduced_norm() + True + + Its coefficient of degree `\deg(a) - 1` is the opposite of the reduced + trace:: + + sage: chi[2] == -a.reduced_trace() + True + + By default, the name of the variable of the reduced characteristic + polynomial is ``x`` and the name of central variable is usually ``z`` + (see :meth:`~sage.rings.polynomial.skew_polynomial_ring.SkewPolynomialRing_finite_order.center` + for more details about this). + The user can speciify different names if desired:: + + sage: a.reduced_charpoly(var='T') # variable name for the caracteristic polynomial + T^3 + (2*z + 1)*T^2 + (3*z^2 + 4*z)*T + 4*z^3 + z^2 + 1 + + sage: a.reduced_charpoly(var=('T', 'c')) + T^3 + (2*c + 1)*T^2 + (3*c^2 + 4*c)*T + 4*c^3 + c^2 + 1 + + .. SEEALSO:: + + :meth:`reduced_trace`, :meth:`reduced_norm` + """ + if self._charpoly is None: + parent = self._parent + section = parent._embed_constants.section() + M = self._matmul_c() + chi = M.charpoly() + self._charpoly = [tuple(c.list()) for c in chi.list()] + if self._norm is not None: + self._norm = self._charpoly[-1] + varcenter = None + if var is None: + varcharpoly = 'x' + elif isinstance(var, (tuple, list)) and len(var) == 2: + (varcharpoly, varcenter) = var + else: + varcharpoly = var + center = self.parent().center(name=varcenter) + coeffs = [center(c) for c in self._charpoly] + return PolynomialRing(center, name=varcharpoly)(coeffs) def is_central(self): r""" @@ -486,7 +569,7 @@ cdef class SkewPolynomial_finite_order_dense(SkewPolynomial_generic_dense): except ValueError: bound = self._matphir_c().minimal_polynomial() section = self._parent._embed_constants.section() - self._optbound = [ section(x) for x in bound.list() ] + self._optbound = [section(x) for x in bound.list()] return center(self._optbound) diff --git a/src/sage/rings/power_series_poly.pyx b/src/sage/rings/power_series_poly.pyx index 41d32b85c3a..affd674afda 100644 --- a/src/sage/rings/power_series_poly.pyx +++ b/src/sage/rings/power_series_poly.pyx @@ -1119,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:: @@ -1172,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): 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/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/semirings/tropical_semiring.pyx b/src/sage/rings/semirings/tropical_semiring.pyx index 5ae1ea93bf7..17e25091f39 100644 --- a/src/sage/rings/semirings/tropical_semiring.pyx +++ b/src/sage/rings/semirings/tropical_semiring.pyx @@ -99,7 +99,7 @@ cdef class TropicalSemiringElement(Element): return repr(self._val) def _latex_(self): - """ + r""" Return a latex representation of ``self``. EXAMPLES:: @@ -135,7 +135,7 @@ cdef class TropicalSemiringElement(Element): # Comparisons cpdef _richcmp_(left, right, int op): - """ + r""" Return the standard comparison of ``left`` and ``right``. EXAMPLES:: @@ -259,7 +259,7 @@ cdef class TropicalSemiringElement(Element): return x def __neg__(self): - """ + r""" Return the additive inverse, which only exists for `\infty`. EXAMPLES:: @@ -610,7 +610,7 @@ class TropicalSemiring(Parent, UniqueRepresentation): @cached_method def zero(self): - """ + r""" Return the (tropical) additive identity element `+\infty`. EXAMPLES:: diff --git a/src/sage/rings/tate_algebra.py b/src/sage/rings/tate_algebra.py index 9097b2a4e07..701278b2eb1 100644 --- a/src/sage/rings/tate_algebra.py +++ b/src/sage/rings/tate_algebra.py @@ -1295,4 +1295,3 @@ def is_integral_domain(self, proof=True): True """ return True - diff --git a/src/sage/sat/solvers/cryptominisat.py b/src/sage/sat/solvers/cryptominisat.py index 6a090604438..eb12e9fbbb3 100644 --- a/src/sage/sat/solvers/cryptominisat.py +++ b/src/sage/sat/solvers/cryptominisat.py @@ -276,11 +276,9 @@ def clauses(self, filename=None): 1 2 -4 0 x1 2 3 0 x1 2 -5 0 - + """ if filename is None: return self._clauses - else: - from sage.sat.solvers.dimacs import DIMACS - DIMACS.render_dimacs(self._clauses, filename, self.nvars()) - + from sage.sat.solvers.dimacs import DIMACS + DIMACS.render_dimacs(self._clauses, filename, self.nvars()) diff --git a/src/sage/sat/solvers/dimacs.py b/src/sage/sat/solvers/dimacs.py index ca40b2d6fe7..cff59ac8205 100644 --- a/src/sage/sat/solvers/dimacs.py +++ b/src/sage/sat/solvers/dimacs.py @@ -622,6 +622,5 @@ def __call__(self, **kwds): if line.startswith("v"): full_line += line[2:] + ' ' s = map(int, full_line[:-3].strip().split(" ")) - s = (None,) + tuple(e>0 for e in s) + s = (None,) + tuple(e > 0 for e in s) return s - diff --git a/src/sage/sat/solvers/picosat.py b/src/sage/sat/solvers/picosat.py index f27c70a92bf..daeef620669 100644 --- a/src/sage/sat/solvers/picosat.py +++ b/src/sage/sat/solvers/picosat.py @@ -220,7 +220,5 @@ def clauses(self, filename=None): """ if filename is None: return self._clauses - else: - from sage.sat.solvers.dimacs import DIMACS - DIMACS.render_dimacs(self._clauses, filename, self.nvars()) - + from sage.sat.solvers.dimacs import DIMACS + DIMACS.render_dimacs(self._clauses, filename, self.nvars()) 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 bcdced80990..a329e927d51 100644 --- a/src/sage/schemes/curves/affine_curve.py +++ b/src/sage/schemes/curves/affine_curve.py @@ -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)]) @@ -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): diff --git a/src/sage/schemes/curves/projective_curve.py b/src/sage/schemes/curves/projective_curve.py index 61158a4fbb5..f9ff5944a90 100644 --- a/src/sage/schemes/curves/projective_curve.py +++ b/src/sage/schemes/curves/projective_curve.py @@ -703,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)]) @@ -2147,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/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_field.py b/src/sage/schemes/elliptic_curves/ell_field.py index 68b8375daee..6b64df4d075 100644 --- a/src/sage/schemes/elliptic_curves/ell_field.py +++ b/src/sage/schemes/elliptic_curves/ell_field.py @@ -1098,7 +1098,7 @@ def isogeny(self, kernel, codomain=None, degree=None, model=None, check=True, al kernel point of odd order `\geq 5`. This algorithm is selected using ``algorithm="velusqrt"``. - - Factored Isogenies (*experimental* --- see + - Factored Isogenies (see :mod:`~sage.schemes.elliptic_curves.hom_composite`): Given a list of points which generate a composite-order subgroup, decomposes the isogeny into prime-degree steps. @@ -1200,9 +1200,7 @@ def isogeny(self, kernel, codomain=None, degree=None, model=None, check=True, al sage: E = EllipticCurve(GF(2^32-5), [170246996, 2036646110]) sage: P = E.lift_x(2) - sage: E.isogeny(P, algorithm="factored") # experimental - doctest:warning - ... + sage: E.isogeny(P, algorithm="factored") Composite morphism of degree 1073721825 = 3^4*5^2*11*19*43*59: From: Elliptic Curve defined by y^2 = x^3 + 170246996*x + 2036646110 over Finite Field of size 4294967291 To: Elliptic Curve defined by y^2 = x^3 + 272790262*x + 1903695400 over Finite Field of size 4294967291 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 8d707e8e6b7..0ff9011fdd4 100644 --- a/src/sage/schemes/elliptic_curves/hom.py +++ b/src/sage/schemes/elliptic_curves/hom.py @@ -221,7 +221,6 @@ def degree(self): is the product of the degrees of the individual factors:: sage: from sage.schemes.elliptic_curves.hom_composite import EllipticCurveHom_composite - doctest:warning ... sage: E = EllipticCurve(GF(419), [1,0]) sage: P, = E.gens() sage: phi = EllipticCurveHom_composite(E, P+P) @@ -713,7 +712,7 @@ def compare_via_evaluation(left, right): 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) + 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) @@ -728,4 +727,3 @@ def compare_via_evaluation(left, right): 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 32b1fa9e0bb..6528d7bef5d 100644 --- a/src/sage/schemes/elliptic_curves/hom_composite.py +++ b/src/sage/schemes/elliptic_curves/hom_composite.py @@ -7,12 +7,6 @@ while exposing (close to) the same interface as "normal", unfactored elliptic-curve isogenies. -.. WARNING:: - - This module is currently considered experimental. - It may change in a future release without prior warning, or even - be removed altogether if things turn out to be unfixably broken. - EXAMPLES: The following example would take quite literally forever with the @@ -20,8 +14,6 @@ decomposing into prime steps is exponentially faster:: sage: from sage.schemes.elliptic_curves.hom_composite import EllipticCurveHom_composite - doctest:warning - ... sage: p = 3 * 2^143 - 1 sage: GF(p^2).inject_variables() Defining z2 @@ -90,9 +82,6 @@ from sage.schemes.elliptic_curves.ell_curve_isogeny import EllipticCurveIsogeny from sage.schemes.elliptic_curves.weierstrass_morphism import WeierstrassIsomorphism -from sage.misc.superseded import experimental_warning -experimental_warning(32744, 'EllipticCurveHom_composite is experimental code.') - #TODO: implement sparse strategies? (cf. the SIKE cryptosystem) def _eval_factored_isogeny(phis, P): @@ -789,7 +778,7 @@ def make_default(): This method exists only temporarily to make testing more convenient while :class:`EllipticCurveHom_composite` is - experimental. + not yet the default. EXAMPLES:: diff --git a/src/sage/schemes/elliptic_curves/hom_velusqrt.py b/src/sage/schemes/elliptic_curves/hom_velusqrt.py index 86ca9e51de0..043edcd7399 100644 --- a/src/sage/schemes/elliptic_curves/hom_velusqrt.py +++ b/src/sage/schemes/elliptic_curves/hom_velusqrt.py @@ -322,11 +322,14 @@ def prod_with_derivative(pairs): 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)) @@ -756,7 +759,7 @@ class EllipticCurveHom_velusqrt(EllipticCurveHom): INPUT: - ``E`` -- an elliptic curve over a finite field - - ``P`` -- a point on `E` of odd order `\geq 5` + - ``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` @@ -864,6 +867,26 @@ def __init__(self, E, P, *, codomain=None, model=None, Q=None): 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') @@ -884,8 +907,8 @@ def __init__(self, E, P, *, codomain=None, model=None, Q=None): self._P = self._pre_iso(P) self._degree = self._P.order() - if self._degree % 2 != 1 or self._degree < 5: - raise NotImplementedError('only implemented for odd degrees >= 5') + 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) @@ -1196,10 +1219,10 @@ def _random_example_for_testing(): True """ from sage.all import prime_range, choice, randrange, GF, gcd - p = choice(prime_range(3, 100)) - e = randrange(1,5) - F,t = GF((p,e),'t').objgen() 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: @@ -1208,11 +1231,11 @@ def _random_example_for_testing(): E.short_weierstrass_model() except ValueError: continue - if E.cardinality() < 5: + 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 5 <= d < 1000] + ds = [d for d in ds if 9 <= d < 1000] if ds: deg = choice(ds) break @@ -1224,4 +1247,3 @@ def _random_example_for_testing(): 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 34a3d38d65c..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() @@ -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 4824ee6bd1a..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: @@ -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. 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 33549debee1..a9ad45f6fb2 100644 --- a/src/sage/schemes/elliptic_curves/weierstrass_morphism.py +++ b/src/sage/schemes/elliptic_curves/weierstrass_morphism.py @@ -907,4 +907,3 @@ def scaling_factor(self): the tuple `(u,r,s,t)` defining the isomorphism. """ return self.u - diff --git a/src/sage/schemes/generic/scheme.py b/src/sage/schemes/generic/scheme.py index 3474d822f1e..e57f0e5b1e9 100644 --- a/src/sage/schemes/generic/scheme.py +++ b/src/sage/schemes/generic/scheme.py @@ -127,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): """ 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/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/plane_conics/con_field.py b/src/sage/schemes/plane_conics/con_field.py index 30be61e8154..05022c45113 100644 --- a/src/sage/schemes/plane_conics/con_field.py +++ b/src/sage/schemes/plane_conics/con_field.py @@ -353,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): @@ -382,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`. @@ -519,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: @@ -542,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. @@ -697,7 +697,7 @@ 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 self.Hom(Y)(x, check=False) return super().hom(x, Y) def is_diagonal(self): @@ -928,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""" @@ -995,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 @@ -1004,9 +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. @@ -1120,8 +1120,8 @@ 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!" % \ @@ -1147,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) diff --git a/src/sage/schemes/product_projective/space.py b/src/sage/schemes/product_projective/space.py index 31234b0ef11..7bd6ea88ec1 100644 --- a/src/sage/schemes/product_projective/space.py +++ b/src/sage/schemes/product_projective/space.py @@ -194,7 +194,7 @@ 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. @@ -291,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. @@ -890,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. 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_morphism.py b/src/sage/schemes/projective/projective_morphism.py index 7b0f8db98ae..86e54cbf1fe 100644 --- a/src/sage/schemes/projective/projective_morphism.py +++ b/src/sage/schemes/projective/projective_morphism.py @@ -89,7 +89,6 @@ from sage.categories.number_fields import NumberFields from sage.categories.homset import Hom, End from sage.categories.fields import Fields -from sage.homology.graded_resolution import GradedFreeResolution _NumberFields = NumberFields() _FiniteFields = FiniteFields() @@ -2724,7 +2723,7 @@ def projective_degrees(self): I = G.defining_ideal() # a bihomogeneous ideal degrees = xn*[vector([1,0])] + yn*[vector([0,1])] - res = GradedFreeResolution(I, degrees, algorithm='shreyer') + res = I.graded_free_resolution(degrees=degrees, algorithm='shreyer') kpoly = res.K_polynomial() L = kpoly.parent() 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 e80b88c1bed..87e02bc962b 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 @@ -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,8 +369,113 @@ 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:: + + 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(): @@ -379,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`` @@ -410,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') @@ -427,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. @@ -451,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:: @@ -463,6 +643,8 @@ 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: @@ -475,6 +657,7 @@ def __init__(self, f, prec=53, certification=True, differentials=None, integrati 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] @@ -488,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) @@ -496,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""" @@ -527,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): @@ -553,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): @@ -591,14 +834,17 @@ def downstairs_edges(self): # 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 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. @@ -613,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: @@ -627,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`. - 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.:: + OUTPUT: + + The homotopy-continued Voronoi decomposition as a graph, with appropriate 3D embedding. - sage: G = Graph(S.upstairs_edges()); G + EXAMPLES:: + + 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): @@ -698,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""" @@ -728,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: @@ -750,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""" @@ -829,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] @@ -846,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 @@ -876,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 @@ -885,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): @@ -933,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 @@ -956,8 +1265,11 @@ 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 @@ -993,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])): @@ -1027,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: @@ -1143,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]) @@ -1166,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] @@ -1179,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 @@ -1236,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. @@ -1289,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. @@ -1306,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") @@ -1350,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:: @@ -1377,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): @@ -1403,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: @@ -1415,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: @@ -1425,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): @@ -1434,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 @@ -1458,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): @@ -1504,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 @@ -1517,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. @@ -1549,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] @@ -1592,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] @@ -1625,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""" @@ -1644,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 @@ -1678,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 @@ -1712,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)) @@ -1751,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)) @@ -1806,19 +2241,19 @@ def local_N(ct, rt): continue if lN % 2 and not lN == 3: - lN += 1 + 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""" @@ -1855,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() @@ -1871,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) @@ -1968,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): @@ -1991,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 @@ -2002,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") @@ -2027,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 @@ -2038,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): @@ -2090,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""" @@ -2129,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""" @@ -2242,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() @@ -2253,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 @@ -2284,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") + raise AssertionError( + "Matrix is not the homology representation of an endomorphism" + ) J = standard_symplectic_matrix(g) return -J * R.transpose() * J @@ -2348,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 @@ -2356,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: @@ -2423,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""" @@ -2470,27 +3955,33 @@ def integer_matrix_relations(M1, M2, b=None, r=None): """ 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(vi).real_part()).round() for w in W] + @@ -2500,13 +3991,14 @@ def integer_matrix_relations(M1, M2, b=None, r=None): 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): @@ -2531,6 +4023,7 @@ class RiemannSurfaceSum(RiemannSurface): sage: len(SC.homomorphism_basis(S1+S2)) 2 """ + def __init__(self, L): r""" TESTS:: @@ -2553,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/sets/disjoint_union_enumerated_sets.py b/src/sage/sets/disjoint_union_enumerated_sets.py index 1820a79256f..7b95a9dc945 100644 --- a/src/sage/sets/disjoint_union_enumerated_sets.py +++ b/src/sage/sets/disjoint_union_enumerated_sets.py @@ -602,6 +602,4 @@ def Element(self): """ if not self._facade: return ElementWrapper - else: - return NotImplemented - + return NotImplemented diff --git a/src/sage/sets/family.py b/src/sage/sets/family.py index c1bf734381c..10c59a02490 100644 --- a/src/sage/sets/family.py +++ b/src/sage/sets/family.py @@ -1380,6 +1380,23 @@ def __setstate__(self, state): """ self.__init__(state['_enumeration']) + def map(self, f, name=None): + r""" + Return the family `( f(\mathtt{self}[i]) )_{i \in I}`, + where `I` is the index set of ``self``. + + The result is again a :class:`TrivialFamily`. + + EXAMPLES:: + + sage: from sage.sets.family import TrivialFamily + sage: f = TrivialFamily(['a', 'b', 'd']) + sage: g = f.map(lambda x: x + '1'); g + Family ('a1', 'b1', 'd1') + """ + # tuple([... for ...]) is faster than tuple(... for ...) + return Family(tuple([f(x) for x in self._enumeration]), name=name) + from sage.sets.non_negative_integers import NonNegativeIntegers from sage.rings.infinity import Infinity diff --git a/src/sage/sets/finite_set_maps.py b/src/sage/sets/finite_set_maps.py index fd4a3ed08ba..ce5029d8032 100644 --- a/src/sage/sets/finite_set_maps.py +++ b/src/sage/sets/finite_set_maps.py @@ -585,4 +585,3 @@ def __init__(self, domain, action, category=None): self._action = action Element = FiniteSetEndoMap_Set - 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/indexed_generators.py b/src/sage/structure/indexed_generators.py index da04658324f..4cf80c3522d 100644 --- a/src/sage/structure/indexed_generators.py +++ b/src/sage/structure/indexed_generators.py @@ -818,7 +818,7 @@ def standardize_names_index_set(names=None, index_set=None, ngens=None): index_set = tuple(names) from sage.sets.finite_enumerated_set import FiniteEnumeratedSet - if isinstance(index_set, dict): # dict of {name: index} -- not likely to be used + if isinstance(index_set, dict): # dict of {name: index} -- not likely to be used if names is not None: raise ValueError("cannot give index_set as a dict and names") names = normalize_names(-1, tuple(index_set.keys())) @@ -841,4 +841,3 @@ def standardize_names_index_set(names=None, index_set=None, ngens=None): " the number of generators") return (names, index_set) - diff --git a/src/sage/structure/proof/proof.py b/src/sage/structure/proof/proof.py index c667704db92..24532380e8a 100644 --- a/src/sage/structure/proof/proof.py +++ b/src/sage/structure/proof/proof.py @@ -253,4 +253,3 @@ def __exit__(self, *args): True """ _proof_prefs._require_proof[self._subsystem] = self._t_orig - 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/expression.pyx b/src/sage/symbolic/expression.pyx index 384c4a90c4f..7ee103f8555 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -11898,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 diff --git a/src/sage/symbolic/expression_conversions.py b/src/sage/symbolic/expression_conversions.py index e5766522196..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,7 +1205,7 @@ 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: + if operator is pow: from sage.rings.rational import Rational base, expt = ex.operands() base = self.field(base) @@ -1207,20 +1213,20 @@ def arithmetic(self, ex, operator): 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``. @@ -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,17 +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: + 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: @@ -1962,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): @@ -1976,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: @@ -2096,6 +2105,7 @@ def tuple(self, ex): """ return ex.operands() + class SubstituteFunction(ExpressionTreeWalker): def __init__(self, ex, *args): """ @@ -2180,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 @@ -2218,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)) @@ -2270,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``. @@ -2308,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/ginac/ex.h b/src/sage/symbolic/ginac/ex.h index 7d220d2d25d..6a164af270f 100644 --- a/src/sage/symbolic/ginac/ex.h +++ b/src/sage/symbolic/ginac/ex.h @@ -677,19 +677,19 @@ std::ostream & operator<<(std::ostream & os, const exset & e); std::ostream & operator<<(std::ostream & os, const exmap & e); /* Function objects for STL sort() etc. */ -struct ex_is_less : public std::binary_function { +struct ex_is_less { bool operator() (const ex &lh, const ex &rh) const { return lh.compare(rh) < 0; } }; -struct ex_is_equal : public std::binary_function { +struct ex_is_equal { bool operator() (const ex &lh, const ex &rh) const { return lh.is_equal(rh); } }; -struct op0_is_equal : public std::binary_function { +struct op0_is_equal { bool operator() (const ex &lh, const ex &rh) const { return lh.op(0).is_equal(rh.op(0)); } }; -struct ex_swap : public std::binary_function { +struct ex_swap { void operator() (ex &lh, ex &rh) const { lh.swap(rh); } }; diff --git a/src/sage/symbolic/ginac/expair.h b/src/sage/symbolic/ginac/expair.h index 75177f6e49a..38b34e18404 100644 --- a/src/sage/symbolic/ginac/expair.h +++ b/src/sage/symbolic/ginac/expair.h @@ -91,7 +91,7 @@ class expair }; /** Function object for insertion into third argument of STL's sort() etc. */ -struct expair_is_less : public std::binary_function { +struct expair_is_less { bool operator()(const expair &lh, const expair &rh) const { return lh.is_less(rh); } }; @@ -99,11 +99,11 @@ struct expair_is_less : public std::binary_function { * into third argument of STL's sort(). Note that this does not define a * strict weak ordering since for any symbol x we have neither 3*x<2*x or * 2*x<3*x. Handle with care! */ -struct expair_rest_is_less : public std::binary_function { +struct expair_rest_is_less { bool operator()(const expair &lh, const expair &rh) const { return (lh.rest.compare(rh.rest)<0); } }; -struct expair_swap : public std::binary_function { +struct expair_swap { void operator()(expair &lh, expair &rh) const { lh.swap(rh); } }; diff --git a/src/sage/symbolic/ginac/normal.cpp b/src/sage/symbolic/ginac/normal.cpp index 3fe3b8457f2..d2c9e7be637 100644 --- a/src/sage/symbolic/ginac/normal.cpp +++ b/src/sage/symbolic/ginac/normal.cpp @@ -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/ginac/order.h b/src/sage/symbolic/ginac/order.h index 63d5373ae90..7c65d6a7d69 100644 --- a/src/sage/symbolic/ginac/order.h +++ b/src/sage/symbolic/ginac/order.h @@ -35,7 +35,7 @@ namespace GiNaC { -class print_order : public std::binary_function { +class print_order { private: const tinfo_t& function_id() const; const tinfo_t& fderivative_id() const; @@ -96,9 +96,7 @@ class print_order_mul : public print_order { // We have to define the following class to sort held expressions // E.g. 3*x+2*x which does not get simplified to 5*x. -class print_order_pair : - public std::binary_function -{ +class print_order_pair { public: bool operator() (const expair &lh, const expair &rh) const; bool compare_degrees(const expair &lhex, const expair &rhex) const; diff --git a/src/sage/symbolic/ginac/ptr.h b/src/sage/symbolic/ginac/ptr.h index 7f3061cfe43..531e30ca869 100644 --- a/src/sage/symbolic/ginac/ptr.h +++ b/src/sage/symbolic/ginac/ptr.h @@ -158,8 +158,7 @@ namespace std { /** Specialization of std::less for ptr to enable ordering of ptr * objects (e.g. for the use as std::map keys). */ -template struct less< GiNaC::ptr > - : public binary_function, GiNaC::ptr, bool> { +template struct less< GiNaC::ptr > { bool operator()(const GiNaC::ptr &lhs, const GiNaC::ptr &rhs) const { return less()(lhs.p, rhs.p); 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/tensor/modules/comp.py b/src/sage/tensor/modules/comp.py index 23977e84f0d..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""" diff --git a/src/sage/tensor/modules/ext_pow_free_module.py b/src/sage/tensor/modules/ext_pow_free_module.py index a5938417f0f..d9db1b5a05b 100644 --- a/src/sage/tensor/modules/ext_pow_free_module.py +++ b/src/sage/tensor/modules/ext_pow_free_module.py @@ -253,6 +253,10 @@ def __init__(self, fmodule, degree, name=None, latex_name=None): 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 @@ -436,7 +440,6 @@ def degree(self): """ return self._degree - #*********************************************************************** @@ -573,21 +576,12 @@ class ExtPowerDualFreeModule(FiniteRankFreeModule_abstract): 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 @@ -598,24 +592,6 @@ class ExtPowerDualFreeModule(FiniteRankFreeModule_abstract): 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 @@ -649,24 +625,22 @@ def __init__(self, fmodule, degree, name=None, latex_name=None): 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)' + 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 @@ -691,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 @@ -740,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 @@ -783,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 @@ -858,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_() @@ -872,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 a49eda75404..61a44d022d4 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,10 +542,12 @@ 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""" @@ -555,6 +561,7 @@ def __init__( name=None, latex_name=None, category=None, + ambient=None, ): r""" See :class:`FiniteRankFreeModule` for documentation and examples. @@ -576,6 +583,10 @@ def __init__( 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__, @@ -632,6 +643,10 @@ def tensor_product(self, *others): 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) @@ -639,13 +654,94 @@ def tensor_product(self, *others): 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') - tensor_type = sum(vector(module.tensor_type()) for module in [self] + list(others)) - return base_module.tensor_module(*tensor_type) + 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 tensor(self, *args, **kwds): + # Until https://trac.sagemath.org/ticket/30373 is done, + # TensorProductFunctor._functor_name is "tensor", so here we delegate. + r""" + Return the tensor product of ``self`` and ``others``. + + This method is invoked when :class:`~sage.categories.tensor.TensorProductFunctor` + is applied to parents. + + It just delegates to :meth:`tensor_product`. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(QQ, 2); M + 2-dimensional vector space over the Rational Field + sage: M20 = M.tensor_module(2, 0); M20 + Free module of type-(2,0) tensors on the 2-dimensional vector space over the Rational Field + sage: tensor([M20, M20]) + Free module of type-(4,0) tensors on the 2-dimensional vector space over the Rational Field + """ + return self.tensor_product(*args, **kwds) def rank(self) -> int: r""" @@ -727,6 +823,274 @@ def zero(self): 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] + + 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""" @@ -942,7 +1306,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`` @@ -965,7 +1329,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, @@ -976,6 +1340,7 @@ def __init__( start_index: int = 0, output_formatter=None, category=None, + ambient=None, ): r""" See :class:`FiniteRankFreeModule` for documentation and examples. @@ -991,7 +1356,7 @@ def __init__( """ super().__init__(ring, rank, name=name, latex_name=latex_name, - category=category) + category=category, ambient=ambient) self._sindex = start_index self._output_formatter = output_formatter # Dictionary of the tensor modules built on self @@ -1160,7 +1525,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``. @@ -1171,6 +1536,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: @@ -1195,26 +1572,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``. @@ -1246,7 +1726,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') @@ -1314,6 +1794,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^*)` @@ -1352,6 +1835,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) @@ -1596,7 +2081,70 @@ def basis(self, symbol, latex_symbol=None, from_family=None, "linearly independent") return resu - def tensor(self, tensor_type, name=None, latex_name=None, sym=None, + 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""" Construct a tensor on the free module ``self``. @@ -1605,11 +2153,14 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, - ``tensor_type`` -- pair ``(k, l)`` with ``k`` being the contravariant rank and ``l`` the covariant rank + - ``name`` -- (default: ``None``) string; name given to the tensor + - ``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: @@ -1618,7 +2169,75 @@ 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`` + + OUTPUT: + + - instance of + :class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor` + representing the tensor defined on ``self`` with the provided + characteristics + + EXAMPLES: + + Tensors on a rank-3 free module:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: t = M._tensor((1,0), name='t') ; t + Element t of 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 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 len(antisym[0]) == tensor_type[0]: + return self.alternating_contravariant_tensor(tensor_type[0], + name=name, latex_name=latex_name) + # Generic case: + return self.tensor_module(*tensor_type).element_class(self, + tensor_type, name=name, latex_name=latex_name, + sym=sym, antisym=antisym) + + def tensor(self, *args, **kwds): + r""" + Construct a tensor on the free module ``self`` or a tensor product with other modules. + + If ``args`` consist of other parents, just delegate to :meth:`tensor_product`. + + Otherwise, construct a tensor from the following input. + + INPUT: + + - ``tensor_type`` -- pair ``(k, l)`` with ``k`` being the + contravariant rank and ``l`` the covariant rank + + - ``name`` -- (default: ``None``) string; name given to the tensor + + - ``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 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`` @@ -1652,40 +2271,23 @@ 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 """ - # 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]: - 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]: - return self.alternating_contravariant_tensor(tensor_type[0], - name=name, latex_name=latex_name) - # Generic case: - return self.tensor_module(*tensor_type).element_class(self, - tensor_type, name=name, latex_name=latex_name, - sym=sym, antisym=antisym) + # Until https://trac.sagemath.org/ticket/30373 is done, + # TensorProductFunctor._functor_name is "tensor", so this method + # also needs to double as the tensor product construction + if isinstance(args[0], Parent): + return self.tensor_product(*args, **kwds) + return self._tensor(*args, **kwds) def tensor_from_comp(self, tensor_type, comp, name=None, latex_name=None): r""" @@ -2680,106 +3282,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``. @@ -2953,3 +3455,297 @@ def tensor_type(self): """ 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_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 0566d4de98d..b04e8581148 100644 --- a/src/sage/tensor/modules/tensor_free_module.py +++ b/src/sage/tensor/modules/tensor_free_module.py @@ -58,6 +58,7 @@ # http://www.gnu.org/licenses/ #****************************************************************************** +from sage.categories.modules import Modules from sage.misc.cachefunc import cached_method from sage.tensor.modules.finite_rank_free_module import FiniteRankFreeModule_abstract from sage.tensor.modules.free_module_tensor import FreeModuleTensor @@ -66,6 +67,7 @@ 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_abstract): r""" @@ -125,7 +127,7 @@ class TensorFreeModule(FiniteRankFreeModule_abstract): ``T`` is a module (actually a free module) over `\ZZ`:: sage: T.category() - Category of finite dimensional modules over Integer Ring + Category of tensor products of finite dimensional modules over Integer Ring sage: T in Modules(ZZ) True sage: T.rank() @@ -237,39 +239,11 @@ class TensorFreeModule(FiniteRankFreeModule_abstract): 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) - 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 + sage: M.dual_exterior_power(1) is M.tensor_module(0,1) 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 @@ -363,7 +337,7 @@ class TensorFreeModule(FiniteRankFreeModule_abstract): Element = FreeModuleTensor - def __init__(self, fmodule, tensor_type, name=None, latex_name=None): + def __init__(self, fmodule, tensor_type, name=None, latex_name=None, category=None): r""" TESTS:: @@ -374,33 +348,47 @@ def __init__(self, fmodule, tensor_type, name=None, latex_name=None): """ self._fmodule = fmodule self._tensor_type = tuple(tensor_type) + ring = fmodule._ring rank = pow(fmodule._rank, tensor_type[0] + tensor_type[1]) if self._tensor_type == (0,1): # case of the dual + category = Modules(ring).FiniteDimensional().or_subcategory(category) 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: + category = Modules(ring).FiniteDimensional().TensorProducts().or_subcategory(category) if name is None and fmodule._name is not None: name = 'T^' + str(self._tensor_type) + '(' + fmodule._name + \ ')' 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)' - super().__init__(fmodule._ring, rank, name=name, latex_name=latex_name) + super().__init__(fmodule._ring, rank, name=name, latex_name=latex_name, category=category) fmodule._all_modules.add(self) - def construction(self): + def tensor_factors(self): r""" - TESTS:: + Return the tensor factors of this tensor module. + + EXAMPLES:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: T = M.tensor_module(2, 3) - sage: T.construction() is None - True + sage: T.tensor_factors() + [Rank-3 free module M over the Integer Ring, + Rank-3 free module M over the Integer Ring, + Dual of the Rank-3 free module M over the Integer Ring, + Dual of the Rank-3 free module M over the Integer Ring, + Dual of the Rank-3 free module M over the Integer Ring] """ - # No construction until https://trac.sagemath.org/ticket/31276 provides tensor_product methods - return None + if self._tensor_type == (0,1): # case of the dual + raise NotImplementedError + factors = [self._fmodule] * self._tensor_type[0] + dmodule = self._fmodule.dual() + if self._tensor_type[1]: + factors += [dmodule] * self._tensor_type[1] + return factors #### Parent Methods @@ -439,7 +427,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: @@ -461,7 +450,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): @@ -478,7 +468,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): @@ -489,14 +480,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 @@ -547,8 +551,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 @@ -571,7 +584,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:: @@ -597,8 +610,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)) @@ -606,6 +617,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, @@ -631,6 +648,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 @@ -645,9 +667,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( @@ -695,3 +714,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..ff4f739ac0d --- /dev/null +++ b/src/sage/tensor/modules/tensor_free_submodule.py @@ -0,0 +1,541 @@ +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) + + def construction(self): + # TODO: Define the symmetry group and its action (https://trac.sagemath.org/ticket/34495), + # return the construction functor for invariant subobjects. + r""" + Return the functorial construction of ``self``. + + This implementation just returns ``None``. + + 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: Sym2M.construction() is None + True + """ + return None + + @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/books/computational-mathematics-with-sagemath/calculus_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/calculus_doctest.py index 2e188829ed8..976b912de2a 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/calculus_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/calculus_doctest.py @@ -552,4 +552,3 @@ ) """ - diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/combinat_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/combinat_doctest.py index 9dc4f6e430a..d19ba51ec96 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/combinat_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/combinat_doctest.py @@ -1053,4 +1053,3 @@ 645490122795799841856164638490742749440 """ - diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/domaines_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/domaines_doctest.py index e08cf0fb5bb..94c50977d79 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/domaines_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/domaines_doctest.py @@ -442,4 +442,3 @@ (4) * (x + 2)^2 * (x^2 + 3) """ - diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/float_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/float_doctest.py index abc31568dd3..aa3eed32f3b 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/float_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/float_doctest.py @@ -476,4 +476,3 @@ 1.73205080756887729352744634151? """ - diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/graphique_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/graphique_doctest.py index 472b7167ac9..0caad449666 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/graphique_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/graphique_doctest.py @@ -260,4 +260,3 @@ Graphics3d Object """ - diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/graphtheory_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/graphtheory_doctest.py index 35d367d8de7..c1d8fa977e5 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/graphtheory_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/graphtheory_doctest.py @@ -417,4 +417,3 @@ sage: g.show(edge_colors=edge_coloring(g, hex_colors=True)) """ - diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/integration_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/integration_doctest.py index 518e958cad4..fcb293eb698 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/integration_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/integration_doctest.py @@ -289,4 +289,3 @@ mpf('2.7135204235459511323824699502438') """ - diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/linalg_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/linalg_doctest.py index 902b3c1aec2..5b99bdfa6ac 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/linalg_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/linalg_doctest.py @@ -458,4 +458,3 @@ True """ - diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/lp_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/lp_doctest.py index d4910d7b691..f3aa2201ac8 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/lp_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/lp_doctest.py @@ -233,4 +233,3 @@ ....: if p.get_values(B(u,v), convert=ZZ, tolerance=1e-3) == 1] ) """ - diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/mpoly_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/mpoly_doctest.py index f36b207d7e3..bef4a2b6c62 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/mpoly_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/mpoly_doctest.py @@ -559,4 +559,3 @@ 45 """ - diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/nonlinear_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/nonlinear_doctest.py index 909d7d3c746..3a7104637ec 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/nonlinear_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/nonlinear_doctest.py @@ -489,4 +489,3 @@ 1/2*pi - (e^(1/2*pi) - 10)*e^(-1/2*pi) """ - diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/numbertheory_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/numbertheory_doctest.py index 8a1bed213bc..46a4d4d2bec 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/numbertheory_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/numbertheory_doctest.py @@ -154,4 +154,3 @@ 17 """ - 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..f2f9530e9f4 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:: @@ -406,4 +406,3 @@ (x^562949953421312 + 1, 562949953421312*x^562949953421311) """ - diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/premierspas_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/premierspas_doctest.py index 7b8218b4899..fae01daa748 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/premierspas_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/premierspas_doctest.py @@ -179,4 +179,3 @@ bla """ - diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/programmation_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/programmation_doctest.py index dd2db19fd8a..3f036d5d362 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/programmation_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/programmation_doctest.py @@ -661,4 +661,3 @@ ....: return len(D) == len (Set(D.values())) """ - 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..50f936f8cba 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,6 @@ 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/calculus_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/sol/calculus_doctest.py index ca748ba2059..947f9f53a22 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/sol/calculus_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/sol/calculus_doctest.py @@ -263,4 +263,3 @@ True """ - diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/sol/combinat_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/sol/combinat_doctest.py index 4cd1f78259c..5f372f505b0 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/sol/combinat_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/sol/combinat_doctest.py @@ -216,4 +216,3 @@ [0, 1, 1, 2, 5, 14, 42, 132, 429] """ - diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/sol/domaines_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/sol/domaines_doctest.py index 03b4731f545..df3eb03d8fe 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/sol/domaines_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/sol/domaines_doctest.py @@ -58,4 +58,3 @@ """ - diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/sol/float_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/sol/float_doctest.py index 7e1d7f0c0e4..5d5d4686ec7 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/sol/float_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/sol/float_doctest.py @@ -140,4 +140,3 @@ [-1.0000000000000000 .. 1.0000000000000000] """ - diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/sol/integration_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/sol/integration_doctest.py index e11b6bad8d9..bb9550918bf 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/sol/integration_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/sol/integration_doctest.py @@ -55,4 +55,3 @@ [-0.285398163397448, -0.00524656673640445, -0.00125482109302663] """ - diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/sol/linalg_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/sol/linalg_doctest.py index 4e24775c753..7e164a3bffc 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/sol/linalg_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/sol/linalg_doctest.py @@ -55,4 +55,3 @@ False """ - diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/sol/linsolve_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/sol/linsolve_doctest.py index 839ad0d7ec7..e89d7c06fb5 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/sol/linsolve_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/sol/linsolve_doctest.py @@ -24,4 +24,3 @@ True """ - diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/sol/lp_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/sol/lp_doctest.py index 5219f6f6552..d60adc9dd8b 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/sol/lp_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/sol/lp_doctest.py @@ -46,4 +46,3 @@ True """ - diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/sol/mpoly_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/sol/mpoly_doctest.py index f056b349b3e..d4b92c1dfd6 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/sol/mpoly_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/sol/mpoly_doctest.py @@ -114,4 +114,3 @@ 1/16*u^2*v^2 - 3/8*u^2*v + 7/16*u^2 + 1/8*v^2 - 1/8*v - 1/8 """ - diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/sol/nonlinear_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/sol/nonlinear_doctest.py index afa1a637b7e..f99860f7b9c 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/sol/nonlinear_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/sol/nonlinear_doctest.py @@ -110,4 +110,3 @@ 1/2*pi - (e^(1/2*pi) - 10)*e^(-1/2*pi) """ - diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/sol/numbertheory_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/sol/numbertheory_doctest.py index ceae289f561..2dbd0b018e5 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/sol/numbertheory_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/sol/numbertheory_doctest.py @@ -166,4 +166,3 @@ + 4/5*s3^3*x3^15 - 9/32*s3^2*x3^16 + 1/17*s3*x3^17 """ - 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..f8cefd2f6e8 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:: @@ -106,4 +106,3 @@ + 21844/6081075*x^13 + O(x^15) """ - diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/sol/recequadiff_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/sol/recequadiff_doctest.py index 2dfe2109434..01d0e1bc143 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/sol/recequadiff_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/sol/recequadiff_doctest.py @@ -57,4 +57,3 @@ -sqrt(2*_C + 2*log(x))*x """ - diff --git a/src/sage/tests/combinatorial_hopf_algebras.py b/src/sage/tests/combinatorial_hopf_algebras.py index 83a732a170f..6ac40f7aad5 100644 --- a/src/sage/tests/combinatorial_hopf_algebras.py +++ b/src/sage/tests/combinatorial_hopf_algebras.py @@ -48,4 +48,3 @@ sage: all(go2(n) for n in range(6)) # not tested (needs more morphisms) True """ - diff --git a/src/sage/tests/functools_partial_src.py b/src/sage/tests/functools_partial_src.py index 01e4af0f574..1fb24e15b34 100644 --- a/src/sage/tests/functools_partial_src.py +++ b/src/sage/tests/functools_partial_src.py @@ -22,4 +22,3 @@ def base(x): return x test_func = partial(base, 6) - diff --git a/src/sage/tests/gosper-sum.py b/src/sage/tests/gosper-sum.py index 95266ac235c..abdb622b18c 100644 --- a/src/sage/tests/gosper-sum.py +++ b/src/sage/tests/gosper-sum.py @@ -214,4 +214,3 @@ sage: t.simplify_full().is_trivial_zero() False """ - diff --git a/src/sage/topology/simplicial_complex_catalog.py b/src/sage/topology/simplicial_complex_catalog.py index cbb84e0892e..07a0de43b4a 100644 --- a/src/sage/topology/simplicial_complex_catalog.py +++ b/src/sage/topology/simplicial_complex_catalog.py @@ -34,7 +34,7 @@ - :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` @@ -69,7 +69,7 @@ ProjectivePlane, 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 27d74458a65..a21391beab3 100644 --- a/src/sage/topology/simplicial_complex_examples.py +++ b/src/sage/topology/simplicial_complex_examples.py @@ -34,7 +34,7 @@ - :func:`MooreSpace` - :func:`NotIConnectedGraphs` - :func:`PoincareHomologyThreeSphere` -- :func:`PseudoQuaternionicProjectivePlane` +- :func:`QuaternionicProjectivePlane` - :func:`RandomComplex` - :func:`RandomTwoSphere` - :func:`RealProjectivePlane` @@ -63,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 @@ -75,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. @@ -544,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` @@ -560,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] @@ -598,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. 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/typeset/unicode_characters.py b/src/sage/typeset/unicode_characters.py index 4eacf326c43..c4aa7bdd5fb 100644 --- a/src/sage/typeset/unicode_characters.py +++ b/src/sage/typeset/unicode_characters.py @@ -99,5 +99,3 @@ unicode_mathbbR = '\u211D' # 'ℝ' unicode_mathbbC = '\u2102' # 'ℂ' - - diff --git a/src/sage/version.py b/src/sage/version.py index 4862a5603a6..96d65f23372 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.rc1' -date = '2022-09-07' -banner = 'SageMath version 9.7.rc1, Release Date: 2022-09-07' +version = '9.8.beta1' +date = '2022-09-29' +banner = 'SageMath version 9.8.beta1, Release Date: 2022-09-29' 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 \