diff --git a/.ci/create-changes-html.sh b/.ci/create-changes-html.sh index 40322ca86b9..2dc3ca61a41 100755 --- a/.ci/create-changes-html.sh +++ b/.ci/create-changes-html.sh @@ -51,7 +51,7 @@ diffParagraphs.forEach(paragraph => { EOF echo '' >> CHANGES.html echo '' >> CHANGES.html -(cd $DOC_REPOSITORY && git diff $BASE_DOC_COMMIT -- *.html) > diff.txt +(cd $DOC_REPOSITORY && git diff $BASE_DOC_COMMIT -- "*.html") > diff.txt python3 - << EOF import os, re, html with open('diff.txt', 'r') as f: diff --git a/.ci/docker-exec-script.sh b/.ci/docker-exec-script.sh new file mode 100755 index 00000000000..60bbd70aee7 --- /dev/null +++ b/.ci/docker-exec-script.sh @@ -0,0 +1,14 @@ +#!/bin/sh -x +if [ $# -lt 3 ]; then + echo >&2 "usage: docker-exec-script.sh CONTAINER WORKDIR [VAR=VALUE...] SCRIPT" + exit 1 +fi +CONTAINER=$1 +WORKDIR=$2 +shift 2 +(echo "cd \"$WORKDIR\""; + while [ $# -gt 1 ]; do + echo "export \"$1\"" + shift + done; + cat "$1") | docker exec -i $CONTAINER bash -ex diff --git a/build/bin/write-dockerfile.sh b/.ci/write-dockerfile.sh similarity index 79% rename from build/bin/write-dockerfile.sh rename to .ci/write-dockerfile.sh index 3c62d6082e4..0aa53414553 100755 --- a/build/bin/write-dockerfile.sh +++ b/.ci/write-dockerfile.sh @@ -27,13 +27,13 @@ for SPKG in $(sage-package list --has-file=spkg-configure.m4 $SAGE_PACKAGE_LIST_ CONFIGURE_ARGS+="--with-system-${SPKG}=${WITH_SYSTEM_SPKG} " fi done -echo "# Automatically generated by SAGE_ROOT/build/bin/write-dockerfile.sh" +echo "# Automatically generated by SAGE_ROOT/.ci/write-dockerfile.sh" echo "# the :comments: separate the generated file into sections" echo "# to simplify writing scripts that customize this file" ADD="ADD $__CHOWN" RUN=RUN cat < /dev/null; then \ + (yes | unminimize) || echo "(ignored)"; \ + rm -f "$(command -v unminimize)"; \ + fi EOF if [ -n "$DIST_UPGRADE" ]; then cat <> .gitignore && \ + ./.ci/retrofit-worktree.sh worktree-image /sage); \ + else \ + for a in local logs; do \ + if [ -d /sage/\$a ]; then mv /sage/\$a /new/; fi; \ + done; \ + rm -rf /sage; \ + mv /new /sage; \ + fi; \ + else \ + mv /new /sage; \ + fi WORKDIR /sage -$ADD Makefile VERSION.txt COPYING.txt condarc.yml README.md bootstrap bootstrap-conda configure.ac sage .homebrew-build-env tox.ini Pipfile.m4 ./ -$ADD config/config.rpath config/config.rpath -$ADD src/doc/bootstrap src/doc/bootstrap -$ADD src/bin src/bin -$ADD src/Pipfile.m4 src/pyproject.toml.m4 src/requirements.txt.m4 src/setup.cfg.m4 src/VERSION.txt src/ -$ADD m4 ./m4 -$ADD pkgs pkgs -$ADD build ./build -$ADD .upstream.d ./.upstream.d -ARG BOOTSTRAP=./bootstrap + +ARG BOOTSTRAP=${BOOTSTRAP-./bootstrap} $RUN sh -x -c "\${BOOTSTRAP}" $ENDRUN $THEN_SAVE_STATUS FROM bootstrapped as configured #:configuring: RUN $CHECK_STATUS_THEN mkdir -p logs/pkgs; rm -f config.log; ln -s logs/pkgs/config.log config.log -ARG EXTRA_CONFIGURE_ARGS="" +ARG EXTRA_CONFIGURE_ARGS="${CONFIGURE_ARGS}" EOF if [ ${WITH_SYSTEM_SPKG} = "force" ]; then cat <&1 | tee upstream/ci_fixes.log env: GH_TOKEN: ${{ github.token }} SAGE_CI_FIXES_FROM_REPOSITORIES: ${{ vars.SAGE_CI_FIXES_FROM_REPOSITORIES }} - - name: Store CI fixes in upstream artifact - run: | - if git format-patch --stdout test_base > ci_fixes.patch; then - cp ci_fixes.patch upstream/ - fi - - uses: actions/upload-artifact@v3 - with: - path: upstream - name: upstream - build: - runs-on: ubuntu-latest - container: ghcr.io/sagemath/sage/sage-${{ github.event.inputs.platform || 'ubuntu-focal-standard' }}-with-targets:${{ github.event.inputs.docker_tag || 'dev'}} - needs: [get_ci_fixes] - steps: - - name: Checkout - id: checkout - uses: actions/checkout@v4 + # Building - - name: Update system packages - id: prepare + - name: Generate Dockerfile + # From docker.yml run: | - export PATH="build/bin:$PATH" - eval $(sage-print-system-package-command auto update) - eval $(sage-print-system-package-command auto --spkg --yes --no-install-recommends install git) + tox -e ${{ env.TOX_ENV }} + cp .tox/${{ env.TOX_ENV }}/Dockerfile . + env: + # Only generate the Dockerfile, do not run 'docker build' here + DOCKER_TARGETS: "" - - name: Add prebuilt tree as a worktree - id: worktree - run: | - set -ex - git config --global --add safe.directory $(pwd) - .ci/retrofit-worktree.sh worktree-image /sage + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver-opts: network=host - - name: Download upstream artifact - uses: actions/download-artifact@v3 + - name: Build Docker image + id: image + uses: docker/build-push-action@v5 with: - path: upstream - name: upstream + # push and load may not be set together at the moment + # + # We are using "push" (to the local registry) because it was + # more reliable than "load", for which we observed random failure + # conditions in which the built image could not be found. + # + push: true + load: false + context: . + tags: ${{ env.BUILD_IMAGE }} + target: with-targets + cache-from: type=gha + cache-to: type=gha,mode=max + build-args: | + NUMPROC=6 + USE_MAKEFLAGS=-k V=0 SAGE_NUM_THREADS=4 --output-sync=recurse + TARGETS_PRE=build/make/Makefile + TARGETS=ci-build-with-fallback - - name: Apply CI fixes from sagemath/sage - # After applying the fixes, make sure all changes are marked as uncommitted changes. + - name: Start container run: | - if [ -r upstream/ci_fixes.patch ]; then - (cd worktree-image && git commit -q -m "current changes" --allow-empty -a && git am; git reset --quiet old; git add -N .) < upstream/ci_fixes.patch - fi + docker run --name BUILD -dit \ + --mount type=bind,src=$(pwd),dst=$(pwd) \ + --workdir $(pwd) \ + ${{ env.BUILD_IMAGE }} /bin/sh - - name: Incremental build - id: incremental + # Testing + + - name: Check that all modules can be imported run: | - # Now re-bootstrap and build. The build is incremental because we were careful with the timestamps. - ./bootstrap && make build - working-directory: ./worktree-image - env: - MAKE: make -j2 --output-sync=recurse - SAGE_NUM_THREADS: 2 + # Increase the length of the lines in the "short summary" + export COLUMNS=120 + # The following command checks that all modules can be imported. + # The output also includes a long list of modules together with the number of tests in each module. + # This can be ignored. + ./sage -python -m pip install pytest-xdist + ./sage -python -m pytest -c tox.ini -qq --doctest --collect-only || true + shell: sh .ci/docker-exec-script.sh BUILD /sage {0} - - name: Build modularized distributions - if: (success() || failure()) && steps.worktree.outcome == 'success' - run: make V=0 tox && make SAGE_CHECK=no pypi-wheels - working-directory: ./worktree-image - env: - MAKE: make -j2 --output-sync=recurse - SAGE_NUM_THREADS: 2 + - name: Test changed files (sage -t --new) + run: | + export MAKE="make -j2 --output-sync=recurse" SAGE_NUM_THREADS=4 + # We run tests with "sage -t --new"; this only tests the uncommitted changes. + ./sage -t --new -p4 + shell: sh .ci/docker-exec-script.sh BUILD /sage {0} - - name: Static code check with pyright - if: (success() || failure()) && steps.worktree.outcome == 'success' - uses: jakebailey/pyright-action@v1 - with: - version: 1.1.332 - # Many warnings issued by pyright are not yet helpful because there is not yet enough type information. - no-comments: true - working-directory: ./worktree-image - env: - # To avoid out of memory errors - NODE_OPTIONS: --max-old-space-size=8192 - - - name: Static code check with pyright (annotated) - if: (success() || failure()) && steps.worktree.outcome == 'success' - uses: jakebailey/pyright-action@v1 + test-mod: + runs-on: ubuntu-latest + needs: [test-new] + services: + # https://docs.docker.com/build/ci/github-actions/local-registry/ + registry: + image: registry:2 + ports: + - 5000:5000 + strategy: + fail-fast: false + matrix: + targets: + - sagemath_categories-check + steps: + - name: Maximize build disk space + uses: easimon/maximize-build-space@v10 with: - version: 1.1.332 - # Issue errors - no-comments: false - level: error - working-directory: ./worktree-image + # need space in /var for Docker images + root-reserve-mb: 30000 + remove-dotnet: true + remove-android: true + remove-haskell: true + remove-codeql: true + remove-docker-images: true + - name: Checkout + id: checkout + uses: actions/checkout@v4 + - name: Install test prerequisites + # From docker.yml + run: | + sudo DEBIAN_FRONTEND=noninteractive apt-get update + sudo DEBIAN_FRONTEND=noninteractive apt-get install tox + sudo apt-get clean + df -h + - name: Merge CI fixes from sagemath/sage + # From docker.yml + # This step needs to happen after the commit sha is put in DOCKER_TAG + # so that multi-stage builds can work correctly. + run: | + .ci/merge-fixes.sh env: - # To avoid out of memory errors - NODE_OPTIONS: --max-old-space-size=8192 + GH_TOKEN: ${{ github.token }} + + # Building - - name: Clean (fallback to non-incremental) - id: clean - if: (success() || failure()) && steps.worktree.outcome == 'success' && steps.incremental.outcome != 'success' + - name: Generate Dockerfile + # From docker.yml run: | - set -ex - ./bootstrap && make doc-clean doc-uninstall sagelib-clean && git clean -fx src/sage && ./config.status - working-directory: ./worktree-image + tox -e ${{ env.TOX_ENV }} + cp .tox/${{ env.TOX_ENV }}/Dockerfile . env: - MAKE: make -j2 - SAGE_NUM_THREADS: 2 + # Only generate the Dockerfile, do not run 'docker build' here + DOCKER_TARGETS: "" - - name: Build - # This step is needed because building the modularized distributions installs some optional packages, - # so the editable install of sagelib needs to build the corresponding optional extension modules. - id: build - if: (success() || failure()) && (steps.incremental.outcome == 'success' || steps.clean.outcome == 'success') + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver-opts: network=host + + - name: Build Docker image + id: image + uses: docker/build-push-action@v5 + with: + push: true + load: false + context: . + tags: ${{ env.BUILD_IMAGE }} + target: with-targets + cache-from: type=gha + cache-to: type=gha,mode=max + build-args: | + NUMPROC=6 + USE_MAKEFLAGS=-k V=0 SAGE_NUM_THREADS=4 --output-sync=recurse + TARGETS_PRE=build/make/Makefile + TARGETS=ci-build-with-fallback + + - name: Start container run: | - make build - working-directory: ./worktree-image - env: - MAKE: make -j2 --output-sync=recurse - SAGE_NUM_THREADS: 2 + docker run --name BUILD -dit \ + --mount type=bind,src=$(pwd),dst=$(pwd) \ + --workdir $(pwd) \ + ${{ env.BUILD_IMAGE }} /bin/sh # Testing - - name: Test changed files (sage -t --new) - if: (success() || failure()) && steps.build.outcome == 'success' + - name: Test modularized distributions run: | - # We run tests with "sage -t --new"; this only tests the uncommitted changes. - ./sage -t --new -p2 - working-directory: ./worktree-image - env: - MAKE: make -j2 --output-sync=recurse - SAGE_NUM_THREADS: 2 + export MAKE="make -j2 --output-sync=recurse" SAGE_NUM_THREADS=4 + make V=0 tox-ensure && make ${{ matrix.targets }} + shell: sh .ci/docker-exec-script.sh BUILD /sage {0} - - name: Test modularized distributions - if: (success() || failure()) && steps.build.outcome == 'success' - run: make V=0 tox && make pypi-wheels-check - working-directory: ./worktree-image + test-long: + runs-on: ubuntu-latest + needs: [test-new] + services: + # https://docs.docker.com/build/ci/github-actions/local-registry/ + registry: + image: registry:2 + ports: + - 5000:5000 + steps: + - name: Maximize build disk space + uses: easimon/maximize-build-space@v10 + with: + # need space in /var for Docker images + root-reserve-mb: 30000 + remove-dotnet: true + remove-android: true + remove-haskell: true + remove-codeql: true + remove-docker-images: true + - name: Checkout + id: checkout + uses: actions/checkout@v4 + - name: Install test prerequisites + # From docker.yml + run: | + sudo DEBIAN_FRONTEND=noninteractive apt-get update + sudo DEBIAN_FRONTEND=noninteractive apt-get install tox + sudo apt-get clean + df -h + - name: Merge CI fixes from sagemath/sage + # From docker.yml + # This step needs to happen after the commit sha is put in DOCKER_TAG + # so that multi-stage builds can work correctly. + run: | + .ci/merge-fixes.sh env: - MAKE: make -j2 --output-sync=recurse - SAGE_NUM_THREADS: 2 + GH_TOKEN: ${{ github.token }} - - name: Check that all modules can be imported + # Building + + - name: Generate Dockerfile + # From docker.yml run: | - # The following command checks that all modules can be imported. - # The output also includes a long list of modules together with the number of tests in each module. - # This can be ignored. - ../sage -python -m pip install pytest-xdist - ../sage -python -m pytest -c tox.ini -qq --doctest --collect-only || true - working-directory: ./worktree-image/src + tox -e ${{ env.TOX_ENV }} + cp .tox/${{ env.TOX_ENV }}/Dockerfile . env: - # Increase the length of the lines in the "short summary" - COLUMNS: 120 + # Only generate the Dockerfile, do not run 'docker build' here + DOCKER_TARGETS: "" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver-opts: network=host + + - name: Build Docker image + id: image + uses: docker/build-push-action@v5 + with: + push: true + load: false + context: . + tags: ${{ env.BUILD_IMAGE }} + target: with-targets + cache-from: type=gha + cache-to: type=gha,mode=max + build-args: | + NUMPROC=6 + USE_MAKEFLAGS=-k V=0 SAGE_NUM_THREADS=4 --output-sync=recurse + TARGETS_PRE=build/make/Makefile + TARGETS=ci-build-with-fallback - - name: Pytest - if: contains(github.ref, 'pytest') + - name: Start container + id: container run: | - ../sage -python -m pip install coverage pytest-xdist - ../sage -python -m coverage run -m pytest -c tox.ini --doctest || true - working-directory: ./worktree-image/src - env: - # Increase the length of the lines in the "short summary" - COLUMNS: 120 + docker run --name BUILD -dit \ + --mount type=bind,src=$(pwd),dst=$(pwd) \ + --workdir $(pwd) \ + ${{ env.BUILD_IMAGE }} /bin/sh + + # Testing - name: Test all files (sage -t --all --long) - if: (success() || failure()) && steps.build.outcome == 'success' run: | ./sage -python -m pip install coverage - ./sage -python -m coverage run --rcfile=src/tox.ini src/bin/sage-runtests --all --long -p2 --format github --random-seed=286735480429121101562228604801325644303 - working-directory: ./worktree-image + ./sage -python -m coverage run --rcfile=src/tox.ini src/bin/sage-runtests --all --long -p4 --format github --random-seed=286735480429121101562228604801325644303 + shell: sh .ci/docker-exec-script.sh BUILD /sage {0} - - name: Prepare coverage results - if: (success() || failure()) && steps.build.outcome == 'success' + - name: Copy coverage results + if: (success() || failure()) && steps.container.outcome == 'success' run: | ./sage -python -m coverage combine --rcfile=src/tox.ini ./sage -python -m coverage xml --rcfile=src/tox.ini mkdir -p coverage-report mv coverage.xml coverage-report/ - working-directory: ./worktree-image + shell: sh .ci/docker-exec-script.sh BUILD /sage {0} - name: Upload coverage to codecov - if: (success() || failure()) && steps.build.outcome == 'success' + if: (success() || failure()) && steps.container.outcome == 'success' uses: codecov/codecov-action@v3 with: - directory: ./worktree-image/coverage-report + directory: ./coverage-report diff --git a/.github/workflows/ci-conda.yml b/.github/workflows/ci-conda.yml index 1bb08637099..fd1c6442a79 100644 --- a/.github/workflows/ci-conda.yml +++ b/.github/workflows/ci-conda.yml @@ -35,12 +35,12 @@ jobs: # environment: [environment, environment-optional] conda-env: [environment] # On pull requests, only test two jobs: - # Ubuntu with Python 3.9, macOS with Python 3.10. - # Build & Test currently uses Python 3.11 (on ubuntu-focal). + # Ubuntu with Python 3.9, macOS with Python 3.11. + # Build & Test currently uses Python 3.10 (on ubuntu-jammy). # Together, they cover the supported minor Python versions. include: >- ${{ github.event_name == 'pull_request' - && fromJson('[{"os": "macos", "python": "3.10", "conda-env": "environment"}]') + && fromJson('[{"os": "macos", "python": "3.11", "conda-env": "environment"}]') || fromJson('[]') }} steps: @@ -54,7 +54,7 @@ jobs: SAGE_CI_FIXES_FROM_REPOSITORIES: ${{ vars.SAGE_CI_FIXES_FROM_REPOSITORIES }} - name: Cache conda packages - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/conda_pkgs_dir key: @@ -77,21 +77,16 @@ jobs: conda info conda list - - name: Configure + - name: Bootstrap shell: bash -l {0} continue-on-error: true run: | ./bootstrap - echo "::add-matcher::.github/workflows/configure-systempackage-problem-matcher.json" - ./configure --enable-build-as-root --with-python=$CONDA_PREFIX/bin/python --prefix=$CONDA_PREFIX --enable-system-site-packages $(for pkg in $(./sage -package list :standard: --has-file spkg-configure.m4 --has-file distros/conda.txt --exclude rpy2); do echo --with-system-$pkg=force; done) - echo "::remove-matcher owner=configure-system-package-warning::" - echo "::remove-matcher owner=configure-system-package-error::" - name: Build shell: bash -l {0} run: | # Use --no-deps and pip check below to verify that all necessary dependencies are installed via conda. - pip install --no-build-isolation --no-deps -v -v -e ./pkgs/sage-conf ./pkgs/sage-setup pip install --no-build-isolation --no-deps --config-settings editable_mode=compat -v -v -e ./src env: SAGE_NUM_THREADS: 2 diff --git a/.github/workflows/ci-linux-incremental.yml b/.github/workflows/ci-linux-incremental.yml index 82b14472c55..4861ef1d1d0 100644 --- a/.github/workflows/ci-linux-incremental.yml +++ b/.github/workflows/ci-linux-incremental.yml @@ -19,7 +19,12 @@ on: pull_request: paths: - 'build/pkgs/**' - - 'pkgs/**' + - '!build/pkgs/sage_conf/**' + - '!build/pkgs/sage_docbuild/**' + - '!build/pkgs/sage_setup/**' + - '!build/pkgs/sage_sws2rst/**' + - '!build/pkgs/sagelib/**' + - '!build/pkgs/sagemath_*/**' workflow_dispatch: concurrency: diff --git a/.github/workflows/ci-linux.yml b/.github/workflows/ci-linux.yml index 7e86f010b42..6860bd6e460 100644 --- a/.github/workflows/ci-linux.yml +++ b/.github/workflows/ci-linux.yml @@ -46,7 +46,7 @@ jobs: targets: build doc-html targets_optional: ptest tox_system_factors: >- - ["ubuntu-focal"] + ["ubuntu-jammy"] tox_packages_factors: >- ["standard"] docker_push_repository: ghcr.io/${{ github.repository }}/ @@ -111,18 +111,11 @@ jobs: ["ubuntu-jammy", "ubuntu-lunar", "ubuntu-mantic", - "debian-bullseye", "debian-bookworm", "debian-trixie", "debian-sid", - "linuxmint-21", "linuxmint-21.1", "linuxmint-21.2", - "fedora-33", - "fedora-34", - "fedora-35", - "fedora-36", - "fedora-37", "fedora-38", "fedora-39", "centos-stream-8-python3.9", @@ -131,11 +124,9 @@ jobs: "gentoo-python3.10", "gentoo-python3.11", "archlinux-latest", - "opensuse-15.4-gcc_11-python3.10", "opensuse-15.5-gcc_11-python3.11", "opensuse-tumbleweed-python3.10", - "opensuse-tumbleweed", - "debian-bullseye-i386"] + "opensuse-tumbleweed"] docker_push_repository: ghcr.io/${{ github.repository }}/ max_parallel: 8 diff --git a/.github/workflows/dist.yml b/.github/workflows/dist.yml index 5bf5729039c..8af0d7c2168 100644 --- a/.github/workflows/dist.yml +++ b/.github/workflows/dist.yml @@ -47,7 +47,7 @@ jobs: ./configure && make dist env: MAKE: make -j8 - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: success() || failure() with: path: | @@ -61,7 +61,7 @@ jobs: runs-on: ubuntu-latest if: (success() || failure()) && github.repository == 'sagemath/sage' && startsWith(github.ref, 'refs/tags/') && !contains(github.ref, 'beta') && !contains(github.ref, 'rc') steps: - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: release_dist - uses: softprops/action-gh-release@v1 @@ -91,7 +91,7 @@ jobs: make pypi-sdists V=0 (mkdir dist && mv upstream/sage*.tar.gz dist/) ls -l dist - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: path: "dist/*.tar.gz" name: dist @@ -103,6 +103,36 @@ jobs: verbose: true if: env.CAN_DEPLOY == 'true' + noarch_wheels_for_pypi: + + runs-on: ubuntu-latest + env: + CAN_DEPLOY: ${{ secrets.SAGEMATH_PYPI_API_TOKEN != '' }} + steps: + - uses: actions/checkout@v4 + - name: Install bootstrap prerequisites + run: | + sudo DEBIAN_FRONTEND=noninteractive apt-get update + sudo DEBIAN_FRONTEND=noninteractive apt-get install $(build/bin/sage-get-system-packages debian _bootstrap) + - name: make pypi-noarch-wheels + run: | + ./bootstrap + ./configure + make pypi-noarch-wheels V=0 + (mkdir dist && mv venv/var/lib/sage/wheels/sage*-none-any.whl dist/) + ls -l dist + - uses: actions/upload-artifact@v4 + with: + path: "dist/*.whl" + name: noarch-wheels + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.SAGEMATH_PYPI_API_TOKEN }} + skip-existing: true + verbose: true + if: env.CAN_DEPLOY == 'true' + build_wheels: name: Build wheels on ${{ matrix.os }}, arch ${{ matrix.arch }} runs-on: ${{ matrix.os }} @@ -116,8 +146,11 @@ jobs: - os: ubuntu-latest arch: i686 - os: macos-latest - arch: auto + arch: x86_64 + - os: macos-14 + arch: arm64 env: + CAN_DEPLOY: ${{ secrets.SAGEMATH_PYPI_API_TOKEN != '' }} # SPKGs to install as system packages SPKGS: _bootstrap _prereq # Non-Python packages to install as spkgs @@ -136,11 +169,19 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: dist path: dist + - uses: actions/setup-python@v5 + # As of 2024-02-03, the macOS M1 runners do not have preinstalled python or pipx. + # Installing pipx follows the approach of https://github.com/pypa/cibuildwheel/pull/1743 + id: python + with: + python-version: "3.8 - 3.12" + update-environment: false + - name: Build platform wheels # We build the wheels from the sdists so that MANIFEST filtering becomes effective. # But we must run cibuildwheel with the unpacked source directory, not a tarball, @@ -151,31 +192,20 @@ jobs: # This is unfortunately repeated for each of the packages that we build wheels for # because CIBW starts with a fresh container on each invocation. run: | + "${{ steps.python.outputs.python-path }}" -m pip install pipx export PATH=build/bin:$PATH export CIBW_BEFORE_ALL="( $(sage-print-system-package-command debian --yes --no-install-recommends install $(sage-get-system-packages debian $SPKGS)) || $(sage-print-system-package-command fedora --yes --no-install-recommends install $(sage-get-system-packages fedora $SPKGS | sed s/pkg-config/pkgconfig/)) || ( $(sage-print-system-package-command homebrew --yes --no-install-recommends install $(sage-get-system-packages homebrew $SPKGS)) || echo error ignored) ) && ./bootstrap && ./configure --enable-build-as-root && make -j4 V=0 $TARGETS_PRE" mkdir -p unpacked for pkg in sagemath-objects sagemath-categories; do (cd unpacked && tar xfz - ) < dist/$pkg*.tar.gz - pipx run cibuildwheel==2.16.2 unpacked/$pkg* + "${{ steps.python.outputs.python-path }}" -m pipx run cibuildwheel==2.17.0 unpacked/$pkg* done - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: - name: wheels + name: ${{ matrix.os }}-${{ matrix.arch }}-wheels path: ./wheelhouse/*.whl - upload_wheels: - needs: build_wheels - runs-on: ubuntu-latest - env: - CAN_DEPLOY: ${{ secrets.SAGEMATH_PYPI_API_TOKEN != '' }} - steps: - - - uses: actions/download-artifact@v3 - with: - name: wheels - path: wheelhouse - - uses: pypa/gh-action-pypi-publish@release/v1 with: user: __token__ diff --git a/.github/workflows/doc-build-pdf.yml b/.github/workflows/doc-build-pdf.yml index 6e6e8776062..983455cacb0 100644 --- a/.github/workflows/doc-build-pdf.yml +++ b/.github/workflows/doc-build-pdf.yml @@ -9,7 +9,7 @@ on: platform: description: 'Platform' required: true - default: 'ubuntu-focal-standard' + default: 'ubuntu-jammy-standard' docker_tag: description: 'Docker tag' required: true @@ -20,13 +20,44 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +env: + # Same as in build.yml + TOX_ENV: "docker-${{ github.event.inputs.platform || 'ubuntu-jammy-standard' }}-incremental" + BUILD_IMAGE: "localhost:5000/${{ github.repository }}/sage-${{ github.event.inputs.platform || 'ubuntu-jammy-standard' }}-with-targets:ci" + FROM_DOCKER_REPOSITORY: "ghcr.io/sagemath/sage/" + FROM_DOCKER_TARGET: "with-targets" + FROM_DOCKER_TAG: ${{ github.event.inputs.docker_tag || 'dev'}} + EXTRA_CONFIGURE_ARGS: --enable-fat-binary + jobs: - get_ci_fixes: + build-docs-pdf: runs-on: ubuntu-latest + services: + # https://docs.docker.com/build/ci/github-actions/local-registry/ + registry: + image: registry:2 + ports: + - 5000:5000 steps: + - name: Maximize build disk space + uses: easimon/maximize-build-space@v8 + with: + # need space in /var for Docker images + root-reserve-mb: 30000 + remove-dotnet: true + remove-android: true + remove-haskell: true + remove-codeql: true + remove-docker-images: true - name: Checkout - id: checkout uses: actions/checkout@v4 + - name: Install test prerequisites + # From docker.yml + run: | + sudo DEBIAN_FRONTEND=noninteractive apt-get update + sudo DEBIAN_FRONTEND=noninteractive apt-get install tox + sudo apt-get clean + df -h - name: Merge CI fixes from sagemath/sage run: | mkdir -p upstream @@ -34,82 +65,64 @@ jobs: env: GH_TOKEN: ${{ github.token }} SAGE_CI_FIXES_FROM_REPOSITORIES: ${{ vars.SAGE_CI_FIXES_FROM_REPOSITORIES }} - - name: Store CI fixes in upstream artifact - run: | - if git format-patch --stdout test_base > ci_fixes.patch; then - cp ci_fixes.patch upstream/ - fi - - uses: actions/upload-artifact@v3 - with: - path: upstream - name: upstream - build-docs-pdf: - runs-on: ubuntu-latest - container: ghcr.io/sagemath/sage/sage-${{ github.event.inputs.platform || 'ubuntu-focal-standard' }}-with-targets:${{ github.event.inputs.docker_tag || 'dev'}} - needs: [get_ci_fixes] - steps: - - name: Checkout - uses: actions/checkout@v4 + # Building - - name: Update system packages + - name: Generate Dockerfile + # From docker.yml run: | - export PATH="build/bin:$PATH" - eval $(sage-print-system-package-command auto update) - eval $(sage-print-system-package-command auto --yes --no-install-recommends install zip) - eval $(sage-print-system-package-command auto --spkg --yes --no-install-recommends install git texlive texlive_luatex free_fonts xindy) + tox -e ${{ env.TOX_ENV }} + cp .tox/${{ env.TOX_ENV }}/Dockerfile . + env: + # Only generate the Dockerfile, do not run 'docker build' here + DOCKER_TARGETS: "" - - name: Add prebuilt tree as a worktree - id: worktree - run: | - git config --global --add safe.directory $(pwd) - git config --global user.email "ci-sage@example.com" - git config --global user.name "Build & Test workflow" - .ci/retrofit-worktree.sh worktree-image /sage + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver-opts: network=host - - name: Download upstream artifact - uses: actions/download-artifact@v3 + - name: Build Docker image + id: image + uses: docker/build-push-action@v5 with: - path: upstream - name: upstream + # push and load may not be set together at the moment + push: true + load: false + context: . + tags: ${{ env.BUILD_IMAGE }} + target: with-targets + cache-from: type=gha + cache-to: type=gha,mode=max + build-args: | + NUMPROC=6 + USE_MAKEFLAGS=-k V=0 SAGE_NUM_THREADS=4 --output-sync=recurse + TARGETS_PRE=build/make/Makefile + TARGETS=ci-build-with-fallback - - name: Apply CI fixes from sagemath/sage - # After applying the fixes, make sure all changes are marked as uncommitted changes. + - name: Start container run: | - if [ -r upstream/ci_fixes.patch ]; then - (cd worktree-image && git commit -q -m "current changes" --allow-empty -a && git am; git reset --quiet old; git add -N .) < upstream/ci_fixes.patch - fi + docker run --name BUILD -dit \ + --mount type=bind,src=$(pwd),dst=$(pwd) \ + --workdir $(pwd) \ + ${{ env.BUILD_IMAGE }} /bin/sh - - name: Incremental build - id: incremental - run: | - # Now re-bootstrap and build. The build is incremental because we were careful with the timestamps. - ./bootstrap && make build - working-directory: ./worktree-image - env: - MAKE: make -j2 --output-sync=recurse - SAGE_NUM_THREADS: 2 + # Docs - - name: Build (fallback to non-incremental) - id: build - if: (success() || failure()) && steps.worktree.outcome == 'success' && steps.incremental.outcome != 'success' + - name: Update system packages run: | - set -ex - make sagelib-clean && git clean -fx src/sage && ./config.status && make build - working-directory: ./worktree-image - env: - MAKE: make -j2 --output-sync=recurse - SAGE_NUM_THREADS: 2 + export PATH="build/bin:$PATH" + eval $(sage-print-system-package-command auto update) + eval $(sage-print-system-package-command auto --yes --no-install-recommends install zip) + eval $(sage-print-system-package-command auto --spkg --yes --no-install-recommends install git texlive texlive_luatex free_fonts xindy) + shell: sh .ci/docker-exec-script.sh BUILD /sage {0} - name: Build docs (PDF) id: docbuild - if: (success() || failure()) && (steps.incremental.outcome == 'success' || steps.build.outcome == 'success') run: | + export MAKE="make -j5 --output-sync=recurse" SAGE_NUM_THREADS=5 make doc-clean doc-uninstall; make sagemath_doc_html-build-deps sagemath_doc_pdf-no-deps - working-directory: ./worktree-image - env: - MAKE: make -j2 --output-sync=recurse - SAGE_NUM_THREADS: 2 + shell: sh .ci/docker-exec-script.sh BUILD /sage {0} - name: Copy docs id: copy @@ -121,6 +134,7 @@ jobs: cp -r -L /sage/local/share/doc/sage/pdf ./docs # Zip everything for increased performance zip -r docs-pdf.zip docs + shell: sh .ci/docker-exec-script.sh BUILD /sage {0} - name: Upload docs if: (success() || failure()) && steps.copy.outcome == 'success' diff --git a/.github/workflows/doc-build.yml b/.github/workflows/doc-build.yml index 21e19e3859a..a9a1ddece13 100644 --- a/.github/workflows/doc-build.yml +++ b/.github/workflows/doc-build.yml @@ -9,19 +9,59 @@ on: - develop workflow_dispatch: # Allow to run manually + inputs: + platform: + description: 'Platform' + required: true + default: 'ubuntu-jammy-standard' + docker_tag: + description: 'Docker tag' + required: true + default: 'dev' concurrency: # Cancel previous runs of this workflow for the same branch group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +env: + # Same as in build.yml + TOX_ENV: "docker-${{ github.event.inputs.platform || 'ubuntu-jammy-standard' }}-incremental" + BUILD_IMAGE: "localhost:5000/${{ github.repository }}/sage-${{ github.event.inputs.platform || 'ubuntu-jammy-standard' }}-with-targets:ci" + FROM_DOCKER_REPOSITORY: "ghcr.io/sagemath/sage/" + FROM_DOCKER_TARGET: "with-targets" + FROM_DOCKER_TAG: ${{ github.event.inputs.docker_tag || 'dev'}} + EXTRA_CONFIGURE_ARGS: --enable-fat-binary + jobs: - get_ci_fixes: + build-docs: runs-on: ubuntu-latest + services: + # https://docs.docker.com/build/ci/github-actions/local-registry/ + registry: + image: registry:2 + ports: + - 5000:5000 steps: + - name: Maximize build disk space + uses: easimon/maximize-build-space@v8 + with: + # need space in /var for Docker images + root-reserve-mb: 30000 + remove-dotnet: true + remove-android: true + remove-haskell: true + remove-codeql: true + remove-docker-images: true - name: Checkout - id: checkout uses: actions/checkout@v4 + - name: Install test prerequisites + # From docker.yml + run: | + sudo DEBIAN_FRONTEND=noninteractive apt-get update + sudo DEBIAN_FRONTEND=noninteractive apt-get install tox + sudo apt-get clean + df -h - name: Merge CI fixes from sagemath/sage run: | mkdir -p upstream @@ -29,120 +69,101 @@ jobs: env: GH_TOKEN: ${{ github.token }} SAGE_CI_FIXES_FROM_REPOSITORIES: ${{ vars.SAGE_CI_FIXES_FROM_REPOSITORIES }} - - name: Store CI fixes in upstream artifact + + # Building + + - name: Generate Dockerfile + # From docker.yml run: | - if git format-patch --stdout test_base > ci_fixes.patch; then - cp ci_fixes.patch upstream/ - fi - - uses: actions/upload-artifact@v3 - with: - path: upstream - name: upstream + tox -e ${{ env.TOX_ENV }} + cp .tox/${{ env.TOX_ENV }}/Dockerfile . + env: + # Only generate the Dockerfile, do not run 'docker build' here + DOCKER_TARGETS: "" - build-docs: - runs-on: ubuntu-latest - container: ghcr.io/sagemath/sage/sage-ubuntu-focal-standard-with-targets:dev - needs: [get_ci_fixes] - steps: - - name: Checkout - uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver-opts: network=host - - name: Update system packages + - name: Build Docker image + id: image + uses: docker/build-push-action@v5 + with: + # push and load may not be set together at the moment + push: true + load: false + context: . + tags: ${{ env.BUILD_IMAGE }} + target: with-targets + cache-from: type=gha + cache-to: type=gha,mode=max + build-args: | + NUMPROC=6 + USE_MAKEFLAGS=-k V=0 SAGE_NUM_THREADS=4 --output-sync=recurse + TARGETS_PRE=build/make/Makefile + TARGETS=ci-build-with-fallback + + - name: Start container run: | - apt-get update && apt-get install -y git zip + docker run --name BUILD -dit \ + --mount type=bind,src=$(pwd),dst=$(pwd) \ + --workdir $(pwd) \ + ${{ env.BUILD_IMAGE }} /bin/sh + + # Docs - - name: Add prebuilt tree as a worktree + - name: Store old docs id: worktree run: | git config --global --add safe.directory $(pwd) git config --global user.email "ci-sage@example.com" - git config --global user.name "Build & Test workflow" - # mathjax path in old doc - mathjax_path_from=$(SAGE_USE_CDNS=no /sage/sage -python -c "from sage_docbuild.conf import mathjax_path; print(mathjax_path)") - .ci/retrofit-worktree.sh worktree-image /sage + git config --global user.name "Build documentation workflow" + # mathjax path in old doc (regex) + mathjax_path_from="[-./A-Za-z_]*/tex-chtml[.]js?v=[0-9a-f]*" # mathjax path in new doc - mathjax_path_to=$(SAGE_USE_CDNS=yes /sage/sage -python -c "from sage_docbuild.conf import mathjax_path; print(mathjax_path)") - new_version=$(cat src/VERSION.txt) + mathjax_path_to=$(docker exec -e SAGE_USE_CDNS=yes BUILD /sage/sage -python -c "from sage_docbuild.conf import mathjax_path; print(mathjax_path)") + new_version=$(docker exec BUILD cat src/VERSION.txt) + mkdir -p docs/ + docker cp BUILD:/sage/local/share/doc/sage/html docs/ # Wipe out chronic diffs between old doc and new doc - (cd /sage/local/share/doc/sage/html && \ + (cd docs && \ find . -name "*.html" | xargs sed -i -e '/class="sidebar-brand-text"/ s/Sage [0-9a-z.]* /Sage '"$new_version"' /' \ -e 's;'"$mathjax_path_from"';'"$mathjax_path_to"';' \ -e '\;; d') # Create git repo from old doc - DOC_DIR=/sage/local/share/doc/sage/html - (cd $DOC_DIR && git init && \ + (cd docs && \ + git init && \ (echo "*.svg binary"; echo "*.pdf binary") >> .gitattributes && \ (echo ".buildinfo"; echo '*.inv'; echo '.git*'; echo '*.svg'; echo '*.pdf'; echo '*.png'; echo 'searchindex.js') > .gitignore; \ git add -A && git commit --quiet -m "old") - - name: Download upstream artifact - uses: actions/download-artifact@v3 - with: - path: upstream - name: upstream - - - name: Apply CI fixes from sagemath/sage - # After applying the fixes, make sure all changes are marked as uncommitted changes. - run: | - if [ -r upstream/ci_fixes.patch ]; then - (cd worktree-image && git commit -q -m "current changes" --allow-empty -a && git am; git reset --quiet old; git add -N .) < upstream/ci_fixes.patch - fi - - - name: Incremental build - id: incremental - run: | - # Now re-bootstrap and build. The build is incremental because we were careful with the timestamps. - ./bootstrap && make sagemath_doc_html-build-deps - working-directory: ./worktree-image - env: - MAKE: make -j2 --output-sync=recurse - SAGE_NUM_THREADS: 2 - - - name: Build (fallback to non-incremental) - id: build - if: (success() || failure()) && steps.worktree.outcome == 'success' && steps.incremental.outcome != 'success' - run: | - set -ex - make sagelib-clean && git clean -fx src/sage && ./config.status && make sagemath_doc_html-build-deps - working-directory: ./worktree-image - env: - MAKE: make -j2 --output-sync=recurse - SAGE_NUM_THREADS: 2 - - name: Build docs id: docbuild - if: (success() || failure()) && (steps.incremental.outcome == 'success' || steps.build.outcome == 'success') # Always non-incremental because of the concern that # incremental docbuild may introduce broken links (inter-file references) though build succeeds run: | - set -ex - DOC_DIR=/sage/local/share/doc/sage/html - mv $DOC_DIR/.git /sage/.git-doc + export MAKE="make -j5 --output-sync=recurse" SAGE_NUM_THREADS=5 make doc-clean doc-uninstall - mkdir -p $DOC_DIR/ && mv /sage/.git-doc $DOC_DIR/.git export SAGE_USE_CDNS=yes ./config.status && make sagemath_doc_html-no-deps - working-directory: ./worktree-image - env: - MAKE: make -j2 --output-sync=recurse - SAGE_NUM_THREADS: 2 + shell: sh .ci/docker-exec-script.sh BUILD /sage {0} - name: Copy docs id: copy if: (success() || failure()) && steps.docbuild.outcome == 'success' run: | set -ex - DOC_DIR=/sage/local/share/doc/sage/html - (cd $DOC_DIR && git commit -a -m 'new') - ls -l /sage/venv/bin - PATH=/sage/venv/bin:$PATH .ci/create-changes-html.sh $(cd $DOC_DIR; git rev-parse HEAD^) $DOC_DIR - (cd $DOC_DIR && rm -rf .git) # We copy everything to a local folder + docker cp BUILD:/sage/local/share/doc/sage/html docs + docker cp BUILD:/sage/local/share/doc/sage/index.html docs + (cd docs && git commit -a -m 'new') + .ci/create-changes-html.sh $(cd docs && git rev-parse HEAD^) docs + (cd docs && rm -rf .git) + mv CHANGES.html docs # We also need to replace the symlinks because netlify is not following them - mkdir -p ./docs - mv CHANGES.html ./docs - cp -r -L $DOC_DIR ./docs - cp $DOC_DIR/../index.html ./docs + # CHECK IF STILL NEEDED + #cp -r -L $DOC_DIR ./docs # Zip everything for increased performance zip -r docs.zip docs @@ -157,7 +178,7 @@ jobs: id: buildlivedoc if: (success() || failure()) && steps.copy.outcome == 'success' && github.repository == 'sagemath/sage' && github.ref == 'refs/heads/develop' run: | - set -ex + export MAKE="make -j5 --output-sync=recurse" SAGE_NUM_THREADS=5 export PATH="build/bin:$PATH" eval $(sage-print-system-package-command auto update) eval $(sage-print-system-package-command auto --yes --no-install-recommends install zip) @@ -167,21 +188,18 @@ jobs: export SAGE_JUPYTER_SERVER=binder:sagemath/sage-binder-env/dev make doc-clean doc-uninstall ./config.status && make sagemath_doc_html-no-deps sagemath_doc_pdf-no-deps - working-directory: ./worktree-image - env: - MAKE: make -j2 --output-sync=recurse - SAGE_NUM_THREADS: 2 + shell: sh .ci/docker-exec-script.sh BUILD ./worktree-image {0} - name: Copy live doc id: copylivedoc if: (success() || failure()) && steps.buildlivedoc.outcome == 'success' run: | - set -ex mkdir -p ./livedoc cp -r -L /sage/local/share/doc/sage/html ./livedoc cp -r -L /sage/local/share/doc/sage/pdf ./livedoc cp /sage/local/share/doc/sage/index.html ./livedoc zip -r livedoc.zip livedoc + shell: sh .ci/docker-exec-script.sh BUILD . {0} - name: Upload live doc if: (success() || failure()) && steps.copylivedoc.outcome == 'success' diff --git a/.github/workflows/doc-publish.yml b/.github/workflows/doc-publish.yml index 361dafb22e0..f893f49c2ea 100644 --- a/.github/workflows/doc-publish.yml +++ b/.github/workflows/doc-publish.yml @@ -70,12 +70,13 @@ jobs: with: number: ${{ steps.source-run-info.outputs.pullRequestNumber }} header: preview-comment - recreate: true + recreate: false message: | [Documentation preview for this PR](${{ steps.deploy-netlify.outputs.NETLIFY_URL }}/html/en) (built with commit ${{ steps.source-run-info.outputs.sourceHeadSha }}; [changes](${{ steps.deploy-netlify.outputs.NETLIFY_URL }}/CHANGES.html)) is ready! :tada: + This preview will update shortly after each push to this PR. - name: Update deployment status PR check - uses: myrotvorets/set-commit-status-action@v2.0.0 + uses: myrotvorets/set-commit-status-action@v2.0.1 if: ${{ always() }} env: DEPLOY_SUCCESS: Successfully deployed preview. @@ -134,4 +135,3 @@ jobs: - name: Report deployment url run: | echo "::notice::The live documentation has been deployed - ${{ steps.deploy-netlify.outputs.NETLIFY_URL }}" - diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 18910ca50a6..52288013188 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -18,13 +18,13 @@ on: # 'tox -e update_docker_platforms' updates below default: >- [ - "ubuntu-trusty-toolchain-gcc_9", "ubuntu-xenial-toolchain-gcc_9", "ubuntu-bionic-gcc_8", "ubuntu-focal", "ubuntu-jammy", "ubuntu-lunar", "ubuntu-mantic", + "ubuntu-noble", "debian-buster-gcc_spkg", "debian-bullseye", "debian-bookworm", @@ -36,6 +36,7 @@ on: "linuxmint-21", "linuxmint-21.1", "linuxmint-21.2", + "linuxmint-21.3", "fedora-30", "fedora-31", "fedora-32", @@ -46,6 +47,7 @@ on: "fedora-37", "fedora-38", "fedora-39", + "fedora-40", "centos-7-devtoolset-gcc_11", "centos-stream-8-python3.9", "centos-stream-9-python3.9", @@ -53,9 +55,8 @@ on: "almalinux-9-python3.11", "gentoo-python3.10", "gentoo-python3.11", + "gentoo-python3.12", "archlinux-latest", - "opensuse-15.3-gcc_11-python3.9", - "opensuse-15.4-gcc_11-python3.10", "opensuse-15.5-gcc_11-python3.11", "opensuse-tumbleweed-python3.10", "opensuse-tumbleweed", @@ -182,8 +183,10 @@ jobs: sudo apt-get clean df -h - name: Update Sage packages from upstream artifact + # Handle both the old and new location of write-dockerfile.sh, + # because docker.yml is a reusable workflow. run: | - (export PATH=$(pwd)/build/bin:$PATH; (cd upstream && bash -x update-pkgs.sh) && sed -i.bak '/upstream/d' .dockerignore && echo "/:toolchain:/i ADD upstream upstream" | sed -i.bak -f - build/bin/write-dockerfile.sh && git diff) + (export PATH=$(pwd)/build/bin:$PATH; (cd upstream && bash -x update-pkgs.sh) && sed -i.bak '/upstream/d' .dockerignore; for a in build/bin/write-dockerfile.sh .ci/write-dockerfile.sh; do if [ -r $a ]; then echo "/:toolchain:/i ADD upstream upstream" | sed -i.bak -f - $a; fi; done; git diff) if: inputs.upstream_artifact - name: Try to login to ghcr.io @@ -244,7 +247,7 @@ jobs: # configuration on many platforms. run: | (sleep ${{ inputs.timeout }}; for id in $(docker ps -q); do docker exec $id find /proc -maxdepth 2 -name cmdline -exec bash -c "grep -l [m][a][k][e] {} | cut -d/ -f3 | xargs --no-run-if-empty kill" \;; done) & - set -o pipefail; EXTRA_DOCKER_BUILD_ARGS="--build-arg NUMPROC=4 --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::|;" + set -o pipefail; EXTRA_DOCKER_BUILD_ARGS="--build-arg NUMPROC=9 --build-arg USE_MAKEFLAGS=\"-k V=0 SAGE_NUM_THREADS=5\"" 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" diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml new file mode 100644 index 00000000000..7eb62c8a498 --- /dev/null +++ b/.github/workflows/pr-labeler.yml @@ -0,0 +1,39 @@ +# This action automatically labels Pull-Requests +# based on files edited and no of lines changed. +name: Size Labeler/Checker +on: + pull_request_target: + types: + - opened + - reopened + - synchronize + - ready_for_review + - review_requested + - edited +jobs: + label-changes: + if: vars.SMALL_THRESHOLD && vars.MODERATE_THRESHOLD && vars.LARGE_THRESHOLD && github.event.pull_request.draft == false + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Add labels based on size + run: | + git fetch origin $BASE_SHA + chmod a+x .github/workflows/set_labels_by_changes.sh + .github/workflows/set_labels_by_changes.sh + env: + BASE_SHA: ${{ github.base_ref }} + PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} + PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPOSITORY: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number}} + SMALL_THRESHOLD: ${{ vars.SMALL_THRESHOLD }} + MODERATE_THRESHOLD: ${{ vars.MODERATE_THRESHOLD }} + LARGE_THRESHOLD: ${{ vars.LARGE_THRESHOLD }} diff --git a/.github/workflows/pyright.yml b/.github/workflows/pyright.yml new file mode 100644 index 00000000000..cafe75db728 --- /dev/null +++ b/.github/workflows/pyright.yml @@ -0,0 +1,84 @@ +name: Static check with Pyright + +on: + pull_request: + merge_group: + push: + branches: + - master + - develop + workflow_dispatch: + # Allow to run manually + +concurrency: + # Cancel previous runs of this workflow for the same branch + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + pyright: + runs-on: ubuntu-latest + container: ghcr.io/sagemath/sage/sage-ubuntu-jammy-standard-with-targets:dev + steps: + - name: Checkout + id: checkout + uses: actions/checkout@v4 + + - name: Update system packages + id: prepare + run: | + export PATH="build/bin:$PATH" + eval $(sage-print-system-package-command auto update) + eval $(sage-print-system-package-command auto --spkg --yes --no-install-recommends install git) + + - name: Install GH CLI + uses: dev-hanz-ops/install-gh-cli-action@v0.1.0 + with: + gh-cli-version: 2.32.0 + + - name: Merge CI fixes from sagemath/sage + run: | + git config --global --add safe.directory "$GITHUB_WORKSPACE" + .ci/merge-fixes.sh + + - name: Add prebuilt tree as a worktree + id: worktree + run: | + set -ex + .ci/retrofit-worktree.sh worktree-image /sage + + - name: Incremental build (sagelib deps) + id: incremental + run: | + # Now re-bootstrap and build. The build is incremental because we were careful with the timestamps. + # pyright does not need a built sagelib; it only needs + # the libraries from which sagelib imports. + ./bootstrap && make sagelib-build-deps + working-directory: ./worktree-image + env: + MAKE: make -j2 --output-sync=recurse + SAGE_NUM_THREADS: 2 + + - name: Static code check with pyright + uses: jakebailey/pyright-action@v1 + with: + version: 1.1.332 + # Many warnings issued by pyright are not yet helpful because there is not yet enough type information. + no-comments: true + working-directory: ./worktree-image + env: + # To avoid out of memory errors + NODE_OPTIONS: --max-old-space-size=8192 + + - name: Static code check with pyright (annotated) + if: (success() || failure()) && steps.incremental.outcome == 'success' + uses: jakebailey/pyright-action@v1 + with: + version: 1.1.332 + # Issue errors + no-comments: false + level: error + working-directory: ./worktree-image + env: + # To avoid out of memory errors + NODE_OPTIONS: --max-old-space-size=8192 diff --git a/.github/workflows/set_labels_by_changes.sh b/.github/workflows/set_labels_by_changes.sh new file mode 100644 index 00000000000..a182d539339 --- /dev/null +++ b/.github/workflows/set_labels_by_changes.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash + +echo 'set_labels_by_changes.sh called with environment:' +echo "BASE SHA: $PR_BASE_SHA" +echo "HEAD SHA: $PR_HEAD_SHA" +echo "SMALL THRESHOLD $SMALL_THRESHOLD" +echo "MODERATE THERESHOLD: $MODERATE_THRESHOLD" +echo "LARGE THRESHOLD: $LARGE_THRESHOLD" + +# get all the changes made and changed files +CHANGES=$(git diff --ignore-all-space $PR_BASE_SHA $PR_HEAD_SHA) + +# ignore blank lines +CHANGES=$(echo "$CHANGES" | grep -vE '^[\+\-]\s*$') + +# ignore non necessary lines from git diff +CHANGES=$(echo "$CHANGES" | grep -E '^[+\-]' | grep -vE '^\+\+\+|^\-\-\-') + +# count total no of lines +CHANGES=$(echo "$CHANGES" | wc -l) + +echo "CHANGES MADE: $CHANGES" + +AUTH_HEADER="Authorization: Bearer $GITHUB_TOKEN" + +MINIMAL="v: minimal" +SMALL="v: small" +MODERATE="v: moderate" +LARGE="v: large" + +DELETE_LABELS=("$MINIMAL" "$SMALL" "$MODERATE" "$LARGE") + +if [ "$CHANGES" -gt "$LARGE_THRESHOLD" ]; then + SIZE_LABEL="$LARGE" +elif [ "$CHANGES" -gt "$MODERATE_THRESHOLD" ]; then + SIZE_LABEL="$MODERATE" +elif [ "$CHANGES" -gt "$SMALL_THRESHOLD" ]; then + SIZE_LABEL="$SMALL" +else + SIZE_LABEL="$MINIMAL" +fi + +DELETE_LABELS=("${DELETE_LABELS[@]//${SIZE_LABEL}/}") + +# API for adding labels on the Pull Request +API_URL="https://api.github.com/repos/$REPOSITORY/issues/$PR_NUMBER/labels" + +echo "Adding label: ${SIZE_LABEL[@]}" +for LABEL in "${SIZE_LABEL[@]}"; do + curl -X POST \ + -H "$AUTH_HEADER" \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + -d "{\"labels\":[\"$LABEL\"]}" \ + "$API_URL" >/dev/null +done + +echo "Deleting Labels:" + +for DELETE_LABEL in "${DELETE_LABELS[@]}"; do + ENCODED_LABEL=$(echo "$DELETE_LABEL" | sed 's/ /%20/g') + curl -X DELETE \ + -H "Accept: application/vnd.github+json" \ + -H "$AUTH_HEADER" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "$API_URL/$ENCODED_LABEL" >/dev/null +done diff --git a/.gitignore b/.gitignore index 43f58abcafe..41494785183 100644 --- a/.gitignore +++ b/.gitignore @@ -53,7 +53,6 @@ /src/setup.cfg /src/requirements.txt -/src/pyproject.toml /src/Pipfile /src/Pipfile.lock /Pipfile @@ -186,6 +185,19 @@ __pycache__/ build/temp.*/ build/bin/sage-build-env-config +# Generated files in build +build/pkgs/cypari/version_requirements.txt +build/pkgs/cysignals/version_requirements.txt +build/pkgs/cython/version_requirements.txt +build/pkgs/gmpy2/version_requirements.txt +build/pkgs/jupyter_core/version_requirements.txt +build/pkgs/memory_allocator/version_requirements.txt +build/pkgs/numpy/version_requirements.txt +build/pkgs/pkgconfig/version_requirements.txt +build/pkgs/pplpy/version_requirements.txt +build/pkgs/setuptools/version_requirements.txt +build/pkgs/wheel/version_requirements.txt + # Generated files in the top-level source trees /pkgs/*/build /pkgs/*/dist diff --git a/.gitpod.yml b/.gitpod.yml index 223e750bd63..6d7c9ec93df 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -11,8 +11,6 @@ tasks: && conda config --append envs_dirs $(pwd) && conda activate $(pwd)/venv && ./bootstrap - && ./configure --enable-build-as-root --with-python=$CONDA_PREFIX/bin/python --prefix=$CONDA_PREFIX - && pip install --no-build-isolation -v -v -e ./pkgs/sage-conf ./pkgs/sage-setup && pip install --no-build-isolation -v -v -e ./src # Activate conda environment, set up Trac remote # RestructuredText extension recommends python extension, although we have already installed it diff --git a/CITATION.cff b/CITATION.cff index 9e0beb8bcc1..3e6fd810876 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -4,8 +4,8 @@ title: SageMath abstract: SageMath is a free open-source mathematics software system. authors: - name: "The SageMath Developers" -version: 10.4.beta2 +version: 10.4.beta5 doi: 10.5281/zenodo.593563 -date-released: 2024-04-08 +date-released: 2024-05-02 repository-code: "https://github.com/sagemath/sage" url: "https://www.sagemath.org/" diff --git a/Makefile b/Makefile index 4dadbf823b8..4662dcd14b9 100644 --- a/Makefile +++ b/Makefile @@ -87,6 +87,14 @@ download: dist: build/make/Makefile ./sage --sdist +ci-build-with-fallback: + $(MAKE) build && $(MAKE) SAGE_CHECK=no pypi-wheels; \ + if [ $$? != 0 ]; then \ + echo "Incremental build failed, falling back"; \ + $(MAKE) doc-clean doc-uninstall sagelib-clean; \ + $(MAKE) build && $(MAKE) SAGE_CHECK=no pypi-wheels; \ + fi + ############################################################################### # Cleaning up ############################################################################### @@ -167,9 +175,19 @@ bootstrap-clean: rm -rf src/doc/en/reference/spkg/*.rst for a in environment environment-optional src/environment src/environment-dev src/environment-optional; do rm -f $$a.yml $$a-3.[89].yml $$a-3.1[0-9].yml; done rm -f src/Pipfile - rm -f src/pyproject.toml rm -f src/requirements.txt rm -f src/setup.cfg + rm -f build/pkgs/cypari/version_requirements.txt + rm -f build/pkgs/cysignals/version_requirements.txt + rm -f build/pkgs/cython/version_requirements.txt + rm -f build/pkgs/gmpy2/version_requirements.txt + rm -f build/pkgs/jupyter_core/version_requirements.txt + rm -f build/pkgs/memory_allocator/version_requirements.txt + rm -f build/pkgs/numpy/version_requirements.txt + rm -f build/pkgs/pkgconfig/version_requirements.txt + rm -f build/pkgs/pplpy/version_requirements.txt + rm -f build/pkgs/setuptools/version_requirements.txt + rm -f build/pkgs/wheel/version_requirements.txt # Remove absolutely everything which isn't part of the git repo maintainer-clean: distclean bootstrap-clean diff --git a/VERSION.txt b/VERSION.txt index 2a439e4ca37..ad931d54bc1 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -SageMath version 10.4.beta2, Release Date: 2024-04-08 +SageMath version 10.4.beta5, Release Date: 2024-05-02 diff --git a/bootstrap b/bootstrap index 14eb56c9981..eb5bcd732b6 100755 --- a/bootstrap +++ b/bootstrap @@ -35,6 +35,15 @@ CONFVERSION=$(cat $PKG/package-version.txt) bootstrap () { + for pkgname in cypari cysignals cython gmpy2 jupyter_core memory_allocator numpy pkgconfig pplpy setuptools wheel; do + # Write the version_requirements.txt files for dependencies declared in pyproject.toml + target=build/pkgs/${pkgname}/version_requirements.txt + if [ "${BOOTSTRAP_QUIET}" = "no" ]; then + echo "bootstrap:$LINENO: installing '"$target"'" + fi + echo "# Generated by SAGE_ROOT/bootstrap based on src/pyproject.toml; do not edit directly" > $target + sage-get-system-packages install-requires ${pkgname} >> $target + done for a in m4/sage_spkg_configures.m4 m4/sage_spkg_versions.m4 m4/sage_spkg_versions_toml.m4; do if [ "${BOOTSTRAP_QUIET}" = "no" ]; then echo "bootstrap:$LINENO: installing '"$a"'" @@ -233,7 +242,18 @@ save () { src/Pipfile \ src/pyproject.toml \ src/requirements.txt \ - src/setup.cfg + src/setup.cfg \ + build/pkgs/cypari/version_requirements.txt \ + build/pkgs/cysignals/version_requirements.txt \ + build/pkgs/cython/version_requirements.txt \ + build/pkgs/gmpy2/version_requirements.txt \ + build/pkgs/jupyter_core/version_requirements.txt \ + build/pkgs/memory_allocator/version_requirements.txt \ + build/pkgs/numpy/version_requirements.txt \ + build/pkgs/pkgconfig/version_requirements.txt \ + build/pkgs/pplpy/version_requirements.txt \ + build/pkgs/setuptools/version_requirements.txt \ + build/pkgs/wheel/version_requirements.txt # Update version echo "$NEWCONFVERSION" >$PKG/package-version.txt diff --git a/build/bin/sage-build-env-config.in b/build/bin/sage-build-env-config.in index c1eb4dfe101..183eba2e3dc 100644 --- a/build/bin/sage-build-env-config.in +++ b/build/bin/sage-build-env-config.in @@ -60,6 +60,8 @@ export SAGE_CONFIGURE_FFLAS_FFPACK="@SAGE_CONFIGURE_FFLAS_FFPACK@" export SAGE_HAVE_LIBJPEG="@SAGE_HAVE_LIBJPEG@" +export SAGE_FRICAS_LISP="@SAGE_FRICAS_LISP@" + export CONFIGURED_SAGE_EDITABLE="@SAGE_EDITABLE@" export CONFIGURED_SAGE_WHEELS="@SAGE_WHEELS@" diff --git a/build/bin/sage-dist-helpers b/build/bin/sage-dist-helpers index f8be31174ad..d7ef0a81a8b 100644 --- a/build/bin/sage-dist-helpers +++ b/build/bin/sage-dist-helpers @@ -69,11 +69,6 @@ # # Runs `pip uninstall` with the given arguments. If unsuccessful, it displays a warning. # -# - eval sdh_prefix_args PREFIX [...] -# -# Helper function for transforming build options so that they can be passed -# through "pip". -# # - sdh_cmake [...] # # Runs `cmake` in the current directory with the given arguments, as well as @@ -220,24 +215,17 @@ sdh_setup_bdist_wheel() { "$@" || sdh_die "Error building a wheel for $PKG_NAME" } -sdh_prefix_args () { - prefix="$1" - shift - while [ $# -gt 0 ]; do - # Quoted quotes because the result is to be run through eval - echo "$prefix" \"$1\" - shift - done -} - sdh_pip_install() { echo "Installing $PKG_NAME" mkdir -p dist rm -f dist/*.whl + export PIP_NO_INDEX=1 install_options="" build_options="" # pip has --no-build-isolation but no flag that turns the default back on... - build_isolation_option="--find-links=$SAGE_SPKG_WHEELS" + build_isolation_option="" + export PIP_FIND_LINKS="$SAGE_SPKG_WHEELS" + unset PIP_NO_BINARY while [ $# -gt 0 ]; do case "$1" in --build-isolation) @@ -245,17 +233,21 @@ sdh_pip_install() { # its build environment using the stored wheels. # We pass --find-links. # The SPKG needs to declare "setuptools" as a dependency. - build_isolation_option="--find-links=$SAGE_SPKG_WHEELS" + build_isolation_option="" + export PIP_FIND_LINKS="$SAGE_SPKG_WHEELS" + unset PIP_NO_BINARY ;; --no-build-isolation) # Use --no-binary, so that no wheels from caches are used. - build_isolation_option="--no-build-isolation --no-binary :all:" + unset PIP_FIND_LINKS + export PIP_NO_BINARY=:all: + build_isolation_option="--no-isolation --skip-dependency-check" ;; --no-deps) install_options="$install_options $1" ;; -C|--config-settings) - build_options="$build_options $1" + build_options="$build_options --config-setting" shift build_options="$build_options $1" ;; @@ -265,18 +257,20 @@ sdh_pip_install() { esac shift done - if python3 -m pip wheel --wheel-dir=dist --verbose --no-deps --no-index --isolated --ignore-requires-python $build_isolation_option $build_options "$@"; then + if python3 -m build --wheel --outdir=dist $build_isolation_option $build_options "$@"; then : # successful else case $build_isolation_option in - *--no-build-isolation*) + *--no-isolation*) sdh_die "Error building a wheel for $PKG_NAME" ;; *) - echo >&2 "Warning: building with \"python3 -m pip wheel --wheel-dir=dist --verbose --no-deps --no-index --isolated --ignore-requires-python $build_isolation_option\" failed." - build_isolation_option="--no-build-isolation --no-binary :all:" - echo >&2 "Retrying with \"python3 -m pip wheel --wheel-dir=dist --verbose --no-deps --no-index --isolated --ignore-requires-python $build_isolation_option\"." - if python3 -m pip wheel --wheel-dir=dist --verbose --no-deps --no-index --isolated --ignore-requires-python $build_isolation_option $build_options "$@"; then + echo >&2 "Warning: building with \"python3 -m build --wheel --outdir=dist $build_isolation_option $build_options $@\" failed." + unset PIP_FIND_LINKS + export PIP_NO_BINARY=:all: + build_isolation_option="--no-isolation --skip-dependency-check" + echo >&2 "Retrying with \"python3 -m build --wheel --outdir=dist $build_isolation_option $build_options $@\"." + if python3 -m build --wheel --outdir=dist $build_isolation_option $build_options "$@"; then echo >&2 "Warning: Wheel building needed to use \"$build_isolation_option\" to succeed. This means that a dependencies file in build/pkgs/ needs to be updated. Please report this to sage-devel@googlegroups.com, including the build log of this package." else sdh_die "Error building a wheel for $PKG_NAME" @@ -284,6 +278,9 @@ sdh_pip_install() { ;; esac fi + unset PIP_FIND_LINKS + unset PIP_NO_BINARY + unset PIP_NO_INDEX sdh_store_and_pip_install_wheel $install_options . } diff --git a/build/bin/sage-get-system-packages b/build/bin/sage-get-system-packages index 5e455c6daa3..0172155023d 100755 --- a/build/bin/sage-get-system-packages +++ b/build/bin/sage-get-system-packages @@ -14,22 +14,25 @@ fi case "$SYSTEM" in install-requires) - # Collect version_requirements.txt (falling back to requirements.txt) and output it in the format + # Collect from src/pyproject.toml or from version_requirements.txt (falling back to requirements.txt) and output it in the format # needed by setup.cfg [options] version_requirements= SYSTEM_PACKAGES_FILE_NAMES="version_requirements.txt requirements.txt" STRIP_COMMENTS="sed s/#.*//;/^[[:space:]]*$/d;" + FROM_PYPROJECT_TOML=1 COLLECT= ;; install-requires-toml) - # Collect version_requirements.txt (falling back to requirements.txt) and output it in the format + # Collect from src/pyproject.toml or from version_requirements.txt (falling back to requirements.txt) and output it in the format # needed by pyproject.toml [build-system] requires= SYSTEM_PACKAGES_FILE_NAMES="version_requirements.txt requirements.txt" STRIP_COMMENTS="sed s/#.*//;/^[[:space:]]*$/d;s/^/'/;s/$/',/;" + FROM_PYPROJECT_TOML=1 COLLECT= ;; pip) SYSTEM_PACKAGES_FILE_NAMES="requirements.txt version_requirements.txt" STRIP_COMMENTS='sed s/#.*//;s/[[:space:]]//g;' + FROM_PYPROJECT_TOML=1 COLLECT=echo ;; *) @@ -42,11 +45,24 @@ case "$SYSTEM" in fi SYSTEM_PACKAGES_FILE_NAMES="distros/$SYSTEM.txt" STRIP_COMMENTS="sed s/#.*//;s/\${PYTHON_MINOR}/${PYTHON_MINOR}/g" + FROM_PYPROJECT_TOML=0 COLLECT=echo ;; esac -for PKG_BASE in $SPKGS; do +for PKG_BASE in $SPKGS; do + if [ $FROM_PYPROJECT_TOML -eq 1 ]; then + if [ -f "$SAGE_ROOT/src/pyproject.toml" ]; then + # Extract from the "requires" block in src/pyproject.toml + # Packages are in the format "'sage-conf ~= 10.3b3'," + PACKAGE_INFO=$(sed -n '/requires *= *\[/,/^\]/s/^ *'\''\('$PKG_BASE'.*\)'\'',/\1/p' "$SAGE_ROOT/src/pyproject.toml") + if [ -n "$PACKAGE_INFO" ]; then + echo "$PACKAGE_INFO" | ${STRIP_COMMENTS} + continue + fi + fi + fi + case "$SYSTEM:$ENABLE_SYSTEM_SITE_PACKAGES" in install-requires*|pip*) # This is output for installation of packages into a Python environment. @@ -73,12 +89,12 @@ for PKG_BASE in $SPKGS; do for NAME in $SYSTEM_PACKAGES_FILE_NAMES; do SYSTEM_PACKAGES_FILE="$SAGE_ROOT"/build/pkgs/$PKG_BASE/$NAME if [ -f $SYSTEM_PACKAGES_FILE ]; then - if [ -z "$COLLECT" ]; then - ${STRIP_COMMENTS} $SYSTEM_PACKAGES_FILE - else - $COLLECT $(${STRIP_COMMENTS} $SYSTEM_PACKAGES_FILE) - fi - break + if [ -z "$COLLECT" ]; then + ${STRIP_COMMENTS} $SYSTEM_PACKAGES_FILE + else + $COLLECT $(${STRIP_COMMENTS} $SYSTEM_PACKAGES_FILE) + fi + break fi done done diff --git a/build/bin/sage-logger b/build/bin/sage-logger index 6945ba98182..1661e52f5f8 100755 --- a/build/bin/sage-logger +++ b/build/bin/sage-logger @@ -63,7 +63,7 @@ fi timefile="$logdir/$logname.time" rm -f "$timefile" -if /usr/bin/time -h -o /dev/null true; then +if /usr/bin/time -h -o /dev/null true 2>/dev/null; then TIME="/usr/bin/time -h -o $timefile" else TIME="" @@ -71,7 +71,7 @@ fi report_time () { - time=$(echo $(cat $timefile)) + time=$(echo $(cat $timefile 2>/dev/null)) case "$time" in *m*real*|*h*real*|*[1-9][0-9].*real*|*[1-9][0-9],*real*) # at least 10 seconds wall time diff --git a/build/make/Makefile.in b/build/make/Makefile.in index 28003ba0aff..49c0cc4ebcf 100644 --- a/build/make/Makefile.in +++ b/build/make/Makefile.in @@ -128,11 +128,14 @@ PIP_PACKAGES = @SAGE_PIP_PACKAGES@ # Packages that use the 'script' package build rules SCRIPT_PACKAGES = @SAGE_SCRIPT_PACKAGES@ -# Packages for which we build wheels for PyPI -PYPI_WHEEL_PACKAGES = \ +# Packages for which we build platform-independent wheels for PyPI +PYPI_NOARCH_WHEEL_PACKAGES = \ sage_sws2rst \ sage_setup \ sagemath_environment \ + +# Packages for which we build wheels for PyPI +PYPI_WHEEL_PACKAGES = $(PYPI_NOARCH_WHEEL_PACKAGES) \ sagemath_objects \ sagemath_repl \ sagemath_categories \ @@ -220,7 +223,7 @@ SAGE_I_TARGETS = sagelib doc # Tell make not to look for files with these names: .PHONY: all all-sage all-toolchain all-build all-sageruntime \ all-start build-start base toolchain toolchain-deps base-toolchain \ - pypi-sdists pypi-wheels wheels \ + pypi-sdists pypi-noarch-wheels pypi-wheels wheels \ sagelib \ doc doc-html doc-html-jsmath doc-html-mathjax doc-pdf \ doc-uninstall \ @@ -324,7 +327,7 @@ all-toolchain: base-toolchain # Shorthand for a list of packages sufficient for building and installing # typical Python packages from source. Wheel packages only need pip. -PYTHON_TOOLCHAIN = setuptools pip setuptools_scm wheel flit_core hatchling +PYTHON_TOOLCHAIN = setuptools pip setuptools_scm wheel flit_core hatchling python_build # Issue #32056: Avoid installed setuptools leaking into the build of python3 by uninstalling it. # It will have to be reinstalled anyway because of its dependency on $(PYTHON). @@ -453,6 +456,13 @@ pypi-sdists: $(PYPI_SDIST_PACKAGES:%=%-sdist) # Ensuring wheels are present, even for packages that may have been installed # as editable. Until we have better uninstallation of script packages, we # just remove the timestamps, which will lead to rebuilds of the packages. +pypi-noarch-wheels: + for a in $(PYPI_NOARCH_WHEEL_PACKAGES); do \ + rm -f $(SAGE_VENV)/var/lib/sage/installed/$$a-*; \ + done + $(MAKE_REC) SAGE_EDITABLE=no SAGE_WHEELS=yes $(PYPI_NOARCH_WHEEL_PACKAGES) + @echo "Built wheels are in venv/var/lib/sage/wheels/" + pypi-wheels: for a in $(PYPI_WHEEL_PACKAGES); do \ rm -f $(SAGE_VENV)/var/lib/sage/installed/$$a-*; \ diff --git a/build/pkgs/appnope/spkg-configure.m4 b/build/pkgs/appnope/spkg-configure.m4 index 059e63eb4fd..86ae64da07b 100644 --- a/build/pkgs/appnope/spkg-configure.m4 +++ b/build/pkgs/appnope/spkg-configure.m4 @@ -1,3 +1,7 @@ SAGE_SPKG_CONFIGURE([appnope], [ SAGE_PYTHON_PACKAGE_CHECK([appnope]) +], [dnl REQUIRED-CHECK + dnl Only required on macOS + sage_require_appnope=no + AS_CASE([$host], [*-*-darwin*], [sage_require_appnope=yes]) ]) diff --git a/build/pkgs/configure/checksums.ini b/build/pkgs/configure/checksums.ini index 2dd257e6307..ca72b9d2a44 100644 --- a/build/pkgs/configure/checksums.ini +++ b/build/pkgs/configure/checksums.ini @@ -1,4 +1,4 @@ tarball=configure-VERSION.tar.gz -sha1=a8f26f365284ca719e583e703c5b8eeb656b35ab -md5=612ba76c797d5fc92e272a59b1b7b2e6 -cksum=2444442853 +sha1=599182ff950764df31446f7bf8350499717dc76e +md5=06e0643f4948ad4b4c87d2530d026a1f +cksum=4286155814 diff --git a/build/pkgs/configure/package-version.txt b/build/pkgs/configure/package-version.txt index e6ac4c1dd9e..fc1538054bc 100644 --- a/build/pkgs/configure/package-version.txt +++ b/build/pkgs/configure/package-version.txt @@ -1 +1 @@ -6d187b153b5b5c3e29fe07d9efe925e34bf5baa5 +e77b3df44ade2f51f750de90ad255f772a933776 diff --git a/build/pkgs/cypari/checksums.ini b/build/pkgs/cypari/checksums.ini index 3f87b3fabb8..27a680da968 100644 --- a/build/pkgs/cypari/checksums.ini +++ b/build/pkgs/cypari/checksums.ini @@ -1,5 +1,5 @@ tarball=cypari2-VERSION.tar.gz -sha1=0af00b66e3f1b77e932ad9cade6e4e9dc6f21eaa -md5=34c919aedb0a470c8b4e4b0c0ec788e3 -cksum=2760761148 -upstream_url=https://github.com/sagemath/cypari2/releases/download/VERSION/cypari2-VERSION.tar.gz +sha1=4cb5fc43899852b7fc0c0175e610318c38f0caac +md5=e9940034c6707a3faeb416f207444e81 +cksum=361739172 +upstream_url=https://pypi.io/packages/source/c/cypari2/cypari2-VERSION.tar.gz diff --git a/build/pkgs/cypari/package-version.txt b/build/pkgs/cypari/package-version.txt index 7d2ed7c7020..cd57a8b95d6 100644 --- a/build/pkgs/cypari/package-version.txt +++ b/build/pkgs/cypari/package-version.txt @@ -1 +1 @@ -2.1.4 +2.1.5 diff --git a/build/pkgs/cypari/version_requirements.txt b/build/pkgs/cypari/version_requirements.txt deleted file mode 100644 index a10714cc59f..00000000000 --- a/build/pkgs/cypari/version_requirements.txt +++ /dev/null @@ -1 +0,0 @@ -cypari2 >=2.1.1 diff --git a/build/pkgs/cysignals/version_requirements.txt b/build/pkgs/cysignals/version_requirements.txt deleted file mode 100644 index ed9219d6ee5..00000000000 --- a/build/pkgs/cysignals/version_requirements.txt +++ /dev/null @@ -1 +0,0 @@ -cysignals >=1.10.2 diff --git a/build/pkgs/cython/version_requirements.txt b/build/pkgs/cython/version_requirements.txt deleted file mode 100644 index 74073ecb57f..00000000000 --- a/build/pkgs/cython/version_requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -cython >=3.0, != 3.0.3, <4.0 - -# Exclude 3.0.3 because of https://github.com/cython/cython/issues/5748 diff --git a/build/pkgs/debugpy/distros/gentoo.txt b/build/pkgs/debugpy/distros/gentoo.txt deleted file mode 100644 index 8b797ce715f..00000000000 --- a/build/pkgs/debugpy/distros/gentoo.txt +++ /dev/null @@ -1 +0,0 @@ -dev-python/debugpy diff --git a/build/pkgs/debugpy/spkg-configure.m4 b/build/pkgs/debugpy/spkg-configure.m4 index db605ff03c8..4ff24d41057 100644 --- a/build/pkgs/debugpy/spkg-configure.m4 +++ b/build/pkgs/debugpy/spkg-configure.m4 @@ -1 +1,14 @@ -SAGE_SPKG_CONFIGURE([debugpy], [SAGE_PYTHON_PACKAGE_CHECK([debugpy])]) +SAGE_SPKG_CONFIGURE([debugpy], [ + SAGE_PYTHON_PACKAGE_CHECK([debugpy]) +], [ + dnl REQUIRED-CHECK + dnl + dnl Skip debugpy if ipykernel from the system will be used. + dnl This allows downstream packagers to treat debugpy (a + dnl somewhat problematic package) as optional. + AC_REQUIRE([SAGE_SPKG_CONFIGURE_IPYKERNEL]) + sage_require_debugpy=yes + AS_VAR_IF([sage_spkg_install_ipykernel], [no], [ + sage_require_debugpy=no + ]) +]) diff --git a/build/pkgs/editables/checksums.ini b/build/pkgs/editables/checksums.ini index 52c7fa0b03b..a3628c1c005 100644 --- a/build/pkgs/editables/checksums.ini +++ b/build/pkgs/editables/checksums.ini @@ -1,5 +1,5 @@ -tarball=editables-VERSION.tar.gz -sha1=90efed858e78bf6276d1a5959ec6692e11a6bce9 -md5=520de8c3a9dc5dfb2b365d104541c9de -cksum=3074203672 -upstream_url=https://pypi.io/packages/source/e/editables/editables-VERSION.tar.gz +tarball=editables-VERSION-py3-none-any.whl +sha1=7aa90de86b05d6dc1a04c219b01ca7eab09de113 +md5=5de129d3a039b26b7f6798a4002acdf6 +cksum=24000838 +upstream_url=https://pypi.io/packages/py3/e/editables/editables-VERSION-py3-none-any.whl diff --git a/build/pkgs/editables/dependencies b/build/pkgs/editables/dependencies index e0e94942dba..644ad35f773 100644 --- a/build/pkgs/editables/dependencies +++ b/build/pkgs/editables/dependencies @@ -1,4 +1,4 @@ - | flit_core $(PYTHON) + | pip $(PYTHON) ---------- All lines of this file are ignored except the first. diff --git a/build/pkgs/editables/spkg-install.in b/build/pkgs/editables/spkg-install.in deleted file mode 100644 index 37ac1a53437..00000000000 --- a/build/pkgs/editables/spkg-install.in +++ /dev/null @@ -1,2 +0,0 @@ -cd src -sdh_pip_install . diff --git a/build/pkgs/exceptiongroup/spkg-configure.m4 b/build/pkgs/exceptiongroup/spkg-configure.m4 new file mode 100644 index 00000000000..22383688321 --- /dev/null +++ b/build/pkgs/exceptiongroup/spkg-configure.m4 @@ -0,0 +1,3 @@ +SAGE_SPKG_CONFIGURE([exceptiongroup],[ + SAGE_PYTHON_PACKAGE_CHECK([exceptiongroup]) +]) diff --git a/build/pkgs/exceptiongroup/version_requirements.txt b/build/pkgs/exceptiongroup/version_requirements.txt index 341183a94c9..2fffd03311e 100644 --- a/build/pkgs/exceptiongroup/version_requirements.txt +++ b/build/pkgs/exceptiongroup/version_requirements.txt @@ -1 +1 @@ -exceptiongroup +exceptiongroup; python_version<"3.11" diff --git a/build/pkgs/fricas/checksums.ini b/build/pkgs/fricas/checksums.ini index 965d2f17f01..a06136db04e 100644 --- a/build/pkgs/fricas/checksums.ini +++ b/build/pkgs/fricas/checksums.ini @@ -1,5 +1,5 @@ tarball=fricas-VERSION-full.tar.bz2 -sha1=6f2c1ae5eb71daab871d1814b26f596363c8e925 -md5=504b431c39e498527e6f9c739c973488 -cksum=2469663675 +sha1=2f1e1bbbad7e04a7114ffbd93eeedadc5db32272 +md5=d2ecd6f8c45cfc41c407b7d5f6eaae07 +cksum=1256005675 upstream_url=https://github.com/fricas/fricas/releases/download/VERSION/fricas-VERSION-full.tar.bz2 diff --git a/build/pkgs/fricas/package-version.txt b/build/pkgs/fricas/package-version.txt index 99e2eaffb13..0c00f610817 100644 --- a/build/pkgs/fricas/package-version.txt +++ b/build/pkgs/fricas/package-version.txt @@ -1 +1 @@ -1.3.8.p1 +1.3.10 diff --git a/build/pkgs/fricas/patches/extdecls.patch b/build/pkgs/fricas/patches/extdecls.patch deleted file mode 100644 index 848da9c02aa..00000000000 --- a/build/pkgs/fricas/patches/extdecls.patch +++ /dev/null @@ -1,9 +0,0 @@ -diff --git a/src/include/cfuns-c.H1 b/src/include/cfuns-c.H1 -index af23933a..8e458c6c 100644 ---- a/src/include/cfuns-c.H1 -+++ b/src/include/cfuns-c.H1 -@@ -1,2 +1,4 @@ - extern int directoryp(char *); - extern int writeablep(char *); -+extern int remove_directory(char *); -+extern int makedir(char *); diff --git a/build/pkgs/fricas/patches/macos_lisp.patch b/build/pkgs/fricas/patches/macos_lisp.patch deleted file mode 100644 index c1be0ac46fd..00000000000 --- a/build/pkgs/fricas/patches/macos_lisp.patch +++ /dev/null @@ -1,31 +0,0 @@ -diff --git a/src/lisp/Makefile.in b/src/lisp/Makefile.in -index 30f615096..f0b3f0bd4 100644 ---- a/src/lisp/Makefile.in -+++ b/src/lisp/Makefile.in -@@ -118,6 +118,8 @@ do_it.ecl: fricas-lisp.lisp fricas-package.lisp fricas-config.lisp \ - fricas-lisp.o primitives.o) ")))" \ - >> fricas-ecl.lisp - echo "(defvar *fricas-initial-lisp-forms* nil)" >> fricas-ecl.lisp -+ echo "(require :cmp)" -+ echo "(setf c::*user-cc-flags* (concatenate 'string c::*user-cc-flags* \" -I$(BASE)/$(fricas_src_srcdir)/include/ -I$(BASE)/$(fricas_src_srcdir)/../config/\"))" >> fricas-ecl.lisp - echo '(load "fricas-package.lisp")' \ - '(load "fricas-config.lisp")' \ - '(load "fricas-ecl.lisp")' \ -diff --git a/src/lisp/fricas-lisp.lisp b/src/lisp/fricas-lisp.lisp -index d6c7484df..f99e2e75b 100644 ---- a/src/lisp/fricas-lisp.lisp -+++ b/src/lisp/fricas-lisp.lisp -@@ -609,6 +609,13 @@ with this hack and will try to convince the GCL crowd to fix this. - #+(and :clisp :ffi) `(defun clisp-init-foreign-calls () ,@arguments) - ) - -+#+:ecl -+(ext:with-backend :c/c++ -+ (ffi:clines -+ "#include " -+ "#include " -+ "#include ")) -+ - (foreign-defs - - (fricas-foreign-call |writeablep| "writeablep" int diff --git a/build/pkgs/fricas/spkg-install.in b/build/pkgs/fricas/spkg-install.in index 847d88cba9d..dfae6f54edb 100644 --- a/build/pkgs/fricas/spkg-install.in +++ b/build/pkgs/fricas/spkg-install.in @@ -3,6 +3,6 @@ cd src # Use newer version of config.guess and config.sub (see Issue #23847) cp "$SAGE_ROOT"/config/config.* config -sdh_configure --with-lisp=ecl --enable-case-insensitive-file-system-check=no +sdh_configure --with-lisp="$SAGE_FRICAS_LISP" --enable-case-insensitive-file-system-check=no sdh_make sdh_make_install -j1 diff --git a/build/pkgs/gambit/SPKG.rst b/build/pkgs/gambit/SPKG.rst deleted file mode 100644 index f266379f6ff..00000000000 --- a/build/pkgs/gambit/SPKG.rst +++ /dev/null @@ -1,30 +0,0 @@ -gambit: Computations on finite, noncooperative games -==================================================== - -Description ------------ - -Gambit is a set of software tools for doing computation on finite, -noncooperative games. The Gambit Project was founded in the mid-1980s by -Richard McKelvey at the California Institute of Technology. - -License -------- - -GPL v2+ - - -Upstream Contact ----------------- - -- Website: http://www.gambit-project.org/ -- Mailing List: http://sourceforge.net/p/gambit/mailman/gambit-devel/ - -Dependencies ------------- - -- python -- cython -- setuptools -- IPython -- scipy diff --git a/build/pkgs/gambit/checksums.ini b/build/pkgs/gambit/checksums.ini deleted file mode 100644 index 132796d9573..00000000000 --- a/build/pkgs/gambit/checksums.ini +++ /dev/null @@ -1,4 +0,0 @@ -tarball=gambit-VERSION.tar.gz -sha1=603dd52e8c0c2881bc2fdc8523bd8cbd9106b36f -md5=db47a02f66644806dbd43f77dc41ebeb -cksum=2352708160 diff --git a/build/pkgs/gambit/distros/homebrew.txt b/build/pkgs/gambit/distros/homebrew.txt deleted file mode 100644 index c08942b85ca..00000000000 --- a/build/pkgs/gambit/distros/homebrew.txt +++ /dev/null @@ -1 +0,0 @@ -gambit diff --git a/build/pkgs/gambit/distros/repology.txt b/build/pkgs/gambit/distros/repology.txt deleted file mode 100644 index 748786b4f51..00000000000 --- a/build/pkgs/gambit/distros/repology.txt +++ /dev/null @@ -1 +0,0 @@ -gambit-game-theory diff --git a/build/pkgs/gambit/package-version.txt b/build/pkgs/gambit/package-version.txt deleted file mode 100644 index 1b67f294f5f..00000000000 --- a/build/pkgs/gambit/package-version.txt +++ /dev/null @@ -1 +0,0 @@ -15.1.1.p0 diff --git a/build/pkgs/gambit/patches/b91115633dbf6f64745fda2db7fbb83f918dd500.patch b/build/pkgs/gambit/patches/b91115633dbf6f64745fda2db7fbb83f918dd500.patch deleted file mode 100644 index f99d88daa7f..00000000000 --- a/build/pkgs/gambit/patches/b91115633dbf6f64745fda2db7fbb83f918dd500.patch +++ /dev/null @@ -1,24 +0,0 @@ -From b91115633dbf6f64745fda2db7fbb83f918dd500 Mon Sep 17 00:00:00 2001 -From: Ted Turocy -Date: Fri, 7 Jul 2017 12:56:56 +0100 -Subject: [PATCH] Const-correctness fix in shared_ptr.h - -This corrects the parameter to weak_ptr::swap(), which had been -incorrectly labeled as const. ---- - src/libgambit/shared_ptr.h | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/libgambit/shared_ptr.h b/src/libgambit/shared_ptr.h -index 959510e5..4379840e 100644 ---- a/src/libgambit/shared_ptr.h -+++ b/src/libgambit/shared_ptr.h -@@ -131,7 +131,7 @@ template class weak_ptr { - long use_count(void) const { return *m_count; } - bool expired(void) const { return *m_count == 0; } - -- void swap(const weak_ptr &other) // never throws -+ void swap(weak_ptr &other) // never throws - { - std::swap(m_ptr, other.m_ptr); - std::swap(m_count, other.m_count); diff --git a/build/pkgs/gambit/spkg-install.in b/build/pkgs/gambit/spkg-install.in deleted file mode 100644 index 61b6e43c0d4..00000000000 --- a/build/pkgs/gambit/spkg-install.in +++ /dev/null @@ -1,15 +0,0 @@ -cd src - -sdh_configure --disable-gui -sdh_make -sdh_make_install - - -cd src/python - -# Remove outdated source file (https://github.com/gambitproject/gambit/pull/232) -rm gambit/lib/libgambit.cpp - -# pip doesn't work (https://github.com/gambitproject/gambit/issues/207) -sdh_setup_bdist_wheel -sdh_store_and_pip_install_wheel . diff --git a/build/pkgs/gambit/type b/build/pkgs/gambit/type deleted file mode 100644 index 9839eb20815..00000000000 --- a/build/pkgs/gambit/type +++ /dev/null @@ -1 +0,0 @@ -experimental diff --git a/build/pkgs/gdb/dependencies b/build/pkgs/gdb/dependencies index cf6822bb92e..d6030116f99 100644 --- a/build/pkgs/gdb/dependencies +++ b/build/pkgs/gdb/dependencies @@ -1,4 +1,4 @@ -mpfr zlib ncurses xz | $(PYTHON) +mpfr zlib ncurses | $(PYTHON) xz ---------- All lines of this file are ignored except the first. diff --git a/build/pkgs/gmp/dependencies b/build/pkgs/gmp/dependencies index ce98901e90d..9738ee88e94 100644 --- a/build/pkgs/gmp/dependencies +++ b/build/pkgs/gmp/dependencies @@ -1,3 +1,3 @@ -xz +| xz # xz is only needed to unpack the tarball when sage-bootstrap-python is Python < 3.3 diff --git a/build/pkgs/gmpy2/version_requirements.txt b/build/pkgs/gmpy2/version_requirements.txt deleted file mode 100644 index 11116398187..00000000000 --- a/build/pkgs/gmpy2/version_requirements.txt +++ /dev/null @@ -1 +0,0 @@ -gmpy2 ~=2.1.b999 diff --git a/build/pkgs/hatch_fancy_pypi_readme/checksums.ini b/build/pkgs/hatch_fancy_pypi_readme/checksums.ini index 6da100dad5a..067609dd4fc 100644 --- a/build/pkgs/hatch_fancy_pypi_readme/checksums.ini +++ b/build/pkgs/hatch_fancy_pypi_readme/checksums.ini @@ -1,5 +1,5 @@ tarball=hatch_fancy_pypi_readme-VERSION-py3-none-any.whl -sha1=4076ea14577b3c711a8345498d8f91b1c8a13d09 -md5=d7acd13333f6c71dcbfa62420c7f257b -cksum=1527082323 +sha1=25cd6749c20a6803cbf1b6c4d29338c344a8f09c +md5=a38ee7191a80ebdbbf0f126f7dff7e46 +cksum=1509914432 upstream_url=https://pypi.io/packages/py3/h/hatch_fancy_pypi_readme/hatch_fancy_pypi_readme-VERSION-py3-none-any.whl diff --git a/build/pkgs/hatch_fancy_pypi_readme/package-version.txt b/build/pkgs/hatch_fancy_pypi_readme/package-version.txt index f8aed3e0b7a..7c974b0f495 100644 --- a/build/pkgs/hatch_fancy_pypi_readme/package-version.txt +++ b/build/pkgs/hatch_fancy_pypi_readme/package-version.txt @@ -1 +1 @@ -23.1.0 +24.1.0 diff --git a/build/pkgs/hatchling/checksums.ini b/build/pkgs/hatchling/checksums.ini index 5d8b02696c5..1d5f9b819ce 100644 --- a/build/pkgs/hatchling/checksums.ini +++ b/build/pkgs/hatchling/checksums.ini @@ -1,5 +1,5 @@ tarball=hatchling-VERSION-py3-none-any.whl -sha1=aa9d69b9dd820716440252d737a4aeaf9b4e541f -md5=20e5ea4deea21f91759fb2269b71f0dd -cksum=446304413 +sha1=2212af13a26dbaea72c7a4ecbdb950c05f6e7c00 +md5=0301aa5bc8739e9f4d58e29d285fb2f7 +cksum=1232194081 upstream_url=https://pypi.io/packages/py3/h/hatchling/hatchling-VERSION-py3-none-any.whl diff --git a/build/pkgs/hatchling/package-version.txt b/build/pkgs/hatchling/package-version.txt index 39893559155..da9594fd66f 100644 --- a/build/pkgs/hatchling/package-version.txt +++ b/build/pkgs/hatchling/package-version.txt @@ -1 +1 @@ -1.20.0 +1.22.5 diff --git a/build/pkgs/importlib_metadata/dependencies b/build/pkgs/importlib_metadata/dependencies index 3c8d3aeb24e..5f8dc152989 100644 --- a/build/pkgs/importlib_metadata/dependencies +++ b/build/pkgs/importlib_metadata/dependencies @@ -1,4 +1,4 @@ - zipp typing_extensions | $(PYTHON_TOOLCHAIN) tomli $(PYTHON) + zipp typing_extensions | pip tomli $(PYTHON) ---------- All lines of this file are ignored except the first. diff --git a/build/pkgs/jupyter_core/version_requirements.txt b/build/pkgs/jupyter_core/version_requirements.txt deleted file mode 100644 index a0e9af65ff5..00000000000 --- a/build/pkgs/jupyter_core/version_requirements.txt +++ /dev/null @@ -1 +0,0 @@ -jupyter_core >=4.6.3 diff --git a/build/pkgs/libgd/dependencies b/build/pkgs/libgd/dependencies index f2c5686fb06..66ad9984858 100644 --- a/build/pkgs/libgd/dependencies +++ b/build/pkgs/libgd/dependencies @@ -1,4 +1,4 @@ -libpng xz +libpng | xz # xz needed to unpack tarball when sage-bootstrap-python is Python < 3.3 ---------- diff --git a/build/pkgs/memory_allocator/checksums.ini b/build/pkgs/memory_allocator/checksums.ini index c907031eac5..76e62a79758 100644 --- a/build/pkgs/memory_allocator/checksums.ini +++ b/build/pkgs/memory_allocator/checksums.ini @@ -1,5 +1,5 @@ tarball=memory_allocator-VERSION.tar.gz -sha1=1a874de2674a1948797de109adfd1f56193e153a -md5=c3a5d0f5acf896eec84266964a8aec0e -cksum=3431157422 +sha1=21661580dd3f41aac0f2090033d8804e6ff495d9 +md5=2a5e087c686b04996ccb88ce56cad9f0 +cksum=3481909016 upstream_url=https://pypi.io/packages/source/m/memory_allocator/memory_allocator-VERSION.tar.gz diff --git a/build/pkgs/memory_allocator/package-version.txt b/build/pkgs/memory_allocator/package-version.txt index b1e80bb2480..845639eef26 100644 --- a/build/pkgs/memory_allocator/package-version.txt +++ b/build/pkgs/memory_allocator/package-version.txt @@ -1 +1 @@ -0.1.3 +0.1.4 diff --git a/build/pkgs/memory_allocator/version_requirements.txt b/build/pkgs/memory_allocator/version_requirements.txt deleted file mode 100644 index fad07b22ebe..00000000000 --- a/build/pkgs/memory_allocator/version_requirements.txt +++ /dev/null @@ -1 +0,0 @@ -memory_allocator diff --git a/build/pkgs/numpy/version_requirements.txt b/build/pkgs/numpy/version_requirements.txt deleted file mode 100644 index 45127ae0a86..00000000000 --- a/build/pkgs/numpy/version_requirements.txt +++ /dev/null @@ -1 +0,0 @@ -numpy >=1.19 diff --git a/build/pkgs/onetbb/checksums.ini b/build/pkgs/onetbb/checksums.ini index 69e7ed4d027..42e837760b6 100644 --- a/build/pkgs/onetbb/checksums.ini +++ b/build/pkgs/onetbb/checksums.ini @@ -1,5 +1,5 @@ tarball=onetbb-VERSION.tar.gz -sha1=b991f5d882aba2182871cfe011614cc43b92aa3c -md5=ba4ecedc4949f673a34b35de738a72fc -cksum=211900655 +sha1=740e86b703f42446ddde392b73a9db3dc0f5f4cd +md5=b301151120b08a17e98dcdda6e4f6011 +cksum=3287903962 upstream_url=https://github.com/oneapi-src/oneTBB/archive/refs/tags/vVERSION.tar.gz diff --git a/build/pkgs/onetbb/package-version.txt b/build/pkgs/onetbb/package-version.txt index a6513d2ae59..90431db79ad 100644 --- a/build/pkgs/onetbb/package-version.txt +++ b/build/pkgs/onetbb/package-version.txt @@ -1 +1 @@ -2021.9.0 +2021.11.0 diff --git a/build/pkgs/onetbb/patches/gcc13-154cc73ca4d359621202399cc0c3c91058e56e79.patch b/build/pkgs/onetbb/patches/gcc13-154cc73ca4d359621202399cc0c3c91058e56e79.patch deleted file mode 100644 index a2e4d939311..00000000000 --- a/build/pkgs/onetbb/patches/gcc13-154cc73ca4d359621202399cc0c3c91058e56e79.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 154cc73ca4d359621202399cc0c3c91058e56e79 Mon Sep 17 00:00:00 2001 -From: Sam James -Date: Wed, 22 Feb 2023 14:16:46 +0000 -Subject: [PATCH] test: common: include for abort() (fix build with - GCC 13) (#1031) - -GCC 13 (as usual for new compiler releases) shuffles around some -internal includes and so etc is no longer transitively included. - -See https://www.gnu.org/software/gcc/gcc-13/porting_to.html. - -Signed-off-by: Sam James ---- - test/common/utils_assert.h | 4 +++- - 1 file changed, 3 insertions(+), 1 deletion(-) - -diff --git a/test/common/utils_assert.h b/test/common/utils_assert.h -index 1df8ae72ac..0123ab881e 100644 ---- a/test/common/utils_assert.h -+++ b/test/common/utils_assert.h -@@ -1,5 +1,5 @@ - /* -- Copyright (c) 2005-2022 Intel Corporation -+ Copyright (c) 2005-2023 Intel Corporation - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. -@@ -20,6 +20,8 @@ - #include "config.h" - #include "utils_report.h" - -+#include -+ - #define REPORT_FATAL_ERROR REPORT - - namespace utils { diff --git a/build/pkgs/packaging/checksums.ini b/build/pkgs/packaging/checksums.ini index 3ee369817f0..69a230aa11c 100644 --- a/build/pkgs/packaging/checksums.ini +++ b/build/pkgs/packaging/checksums.ini @@ -1,5 +1,5 @@ tarball=packaging-VERSION-py3-none-any.whl -sha1=d3fb436d835b252ea884a5d172d7265220127f95 -md5=f6e9c6af858bd34eff07b407d3f650a1 -cksum=3531019080 +sha1=21573cef174a05ac2794b34f3841d6f9ea9fa507 +md5=8b7ed65f4b1a2175ccab25317f2efccc +cksum=4283692602 upstream_url=https://pypi.io/packages/py3/p/packaging/packaging-VERSION-py3-none-any.whl diff --git a/build/pkgs/packaging/package-version.txt b/build/pkgs/packaging/package-version.txt index 3c8ce91a469..d9133a54b63 100644 --- a/build/pkgs/packaging/package-version.txt +++ b/build/pkgs/packaging/package-version.txt @@ -1 +1 @@ -23.2 +24.0 diff --git a/build/pkgs/papilo/checksums.ini b/build/pkgs/papilo/checksums.ini index b2385565547..8bebc605f2c 100644 --- a/build/pkgs/papilo/checksums.ini +++ b/build/pkgs/papilo/checksums.ini @@ -1,5 +1,5 @@ tarball=papilo-VERSION.tar.gz -sha1=85d599ac9936aa1ddf687e04273b995522909de5 -md5=c41f5aa615ffc9914f8ca924947aa8cb -cksum=1535425476 +sha1=069f64ff25cfb08c9b2a416d1d215bd5b907c877 +md5=d58b7c991ac1c4a863de92a404409ca8 +cksum=1552180280 upstream_url=https://github.com/scipopt/papilo/archive/refs/tags/vVERSION.tar.gz diff --git a/build/pkgs/papilo/package-version.txt b/build/pkgs/papilo/package-version.txt index 3e3c2f1e5ed..ccbccc3dc62 100644 --- a/build/pkgs/papilo/package-version.txt +++ b/build/pkgs/papilo/package-version.txt @@ -1 +1 @@ -2.1.1 +2.2.0 diff --git a/build/pkgs/papilo/patches/0001-CMakeLists.txt-Do-not-require-boost-program_options-.patch b/build/pkgs/papilo/patches/0001-CMakeLists.txt-Do-not-require-boost-program_options-.patch deleted file mode 100644 index 5001ca4d67a..00000000000 --- a/build/pkgs/papilo/patches/0001-CMakeLists.txt-Do-not-require-boost-program_options-.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 1fc5aecb4eca500917407b008c8c8eb8637a9c27 Mon Sep 17 00:00:00 2001 -From: Matthias Koeppe -Date: Sat, 19 Nov 2022 19:03:37 -0800 -Subject: [PATCH] CMakeLists.txt: Do not require boost program_options for the - library - ---- - CMakeLists.txt | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/CMakeLists.txt b/CMakeLists.txt -index 7256877..db905aa 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -47,7 +47,7 @@ if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Release) - endif() - --find_package(Boost ${BOOST_MIN_VERSION} COMPONENTS program_options REQUIRED) -+find_package(Boost ${BOOST_MIN_VERSION} REQUIRED) - - if(GMP) - find_package(GMP) --- -2.37.3 - diff --git a/build/pkgs/papilo/patches/import_memory_multiprecision.patch b/build/pkgs/papilo/patches/import_memory_multiprecision.patch deleted file mode 100644 index 1f16ced0bed..00000000000 --- a/build/pkgs/papilo/patches/import_memory_multiprecision.patch +++ /dev/null @@ -1,20 +0,0 @@ -commit 855bd67a64c5c044080d790e5c9fb7e298a61ab7 -Author: Matthias Koeppe -Date: Thu Dec 8 18:10:29 2022 -0800 - - src/papilo/misc/MultiPrecision.hpp: Add #include - -diff --git a/src/papilo/misc/MultiPrecision.hpp b/src/papilo/misc/MultiPrecision.hpp -index 669014c..44d3e63 100644 ---- a/src/papilo/misc/MultiPrecision.hpp -+++ b/src/papilo/misc/MultiPrecision.hpp -@@ -26,6 +26,9 @@ - - #include "papilo/Config.hpp" - -+// work around build failure with boost on Fedora 37 -+#include -+ - #include - - #ifdef PAPILO_HAVE_FLOAT128 diff --git a/build/pkgs/pip/checksums.ini b/build/pkgs/pip/checksums.ini index 0558c5caa45..ebfbc1a26a1 100644 --- a/build/pkgs/pip/checksums.ini +++ b/build/pkgs/pip/checksums.ini @@ -1,5 +1,5 @@ tarball=pip-VERSION-py3-none-any.whl -sha1=4b2baddc0673f73017e531648a9ee27e47925e7a -md5=5d2d058044a3ae2800d18e358ddc72ca -cksum=1470281176 +sha1=e44313ae1e6af3c2bd3b60ab2fa8c34308d00555 +md5=74e3c5e4082113b1239ca0e9abfd1e82 +cksum=88131429 upstream_url=https://pypi.io/packages/py3/p/pip/pip-VERSION-py3-none-any.whl diff --git a/build/pkgs/pip/package-version.txt b/build/pkgs/pip/package-version.txt index a9a57c82265..d9133a54b63 100644 --- a/build/pkgs/pip/package-version.txt +++ b/build/pkgs/pip/package-version.txt @@ -1 +1 @@ -23.3.1 +24.0 diff --git a/build/pkgs/pkgconfig/version_requirements.txt b/build/pkgs/pkgconfig/version_requirements.txt deleted file mode 100644 index 549fd1bf164..00000000000 --- a/build/pkgs/pkgconfig/version_requirements.txt +++ /dev/null @@ -1 +0,0 @@ -pkgconfig diff --git a/build/pkgs/platformdirs/checksums.ini b/build/pkgs/platformdirs/checksums.ini index 3757f701faf..f8770e91289 100644 --- a/build/pkgs/platformdirs/checksums.ini +++ b/build/pkgs/platformdirs/checksums.ini @@ -1,5 +1,5 @@ tarball=platformdirs-VERSION-py3-none-any.whl -sha1=cafa761738da959f2df0a8a92da4c72fd8eaf93e -md5=487007776ff343efc509b68d08cd7fd7 -cksum=162426958 +sha1=487a4610a037c90b242aafbe1e3f8b6ebb3ba1c8 +md5=99200c4e22d44a64a9c3ad0c72a317af +cksum=1011122610 upstream_url=https://pypi.io/packages/py3/p/platformdirs/platformdirs-VERSION-py3-none-any.whl diff --git a/build/pkgs/platformdirs/package-version.txt b/build/pkgs/platformdirs/package-version.txt index ee74734aa22..6aba2b245a8 100644 --- a/build/pkgs/platformdirs/package-version.txt +++ b/build/pkgs/platformdirs/package-version.txt @@ -1 +1 @@ -4.1.0 +4.2.0 diff --git a/build/pkgs/pplpy/version_requirements.txt b/build/pkgs/pplpy/version_requirements.txt deleted file mode 100644 index bd048ecc980..00000000000 --- a/build/pkgs/pplpy/version_requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -# Issue #30922: pplpy 0.8.4 and earlier do not declare dependencies correctly -pplpy >=0.8.6 diff --git a/build/pkgs/pyproject_hooks/SPKG.rst b/build/pkgs/pyproject_hooks/SPKG.rst new file mode 100644 index 00000000000..31d8461a48d --- /dev/null +++ b/build/pkgs/pyproject_hooks/SPKG.rst @@ -0,0 +1,16 @@ +pyproject_hooks: Wrappers to call pyproject.toml-based build backend hooks. +=========================================================================== + +Description +----------- + +Wrappers to call pyproject.toml-based build backend hooks. + +License +------- + +Upstream Contact +---------------- + +https://pypi.org/project/pyproject_hooks/ + diff --git a/build/pkgs/pyproject_hooks/checksums.ini b/build/pkgs/pyproject_hooks/checksums.ini new file mode 100644 index 00000000000..f35e59aa59f --- /dev/null +++ b/build/pkgs/pyproject_hooks/checksums.ini @@ -0,0 +1,5 @@ +tarball=pyproject_hooks-VERSION-py3-none-any.whl +sha1=6c99163c52786fb97eac8b4e38cc13fa3af141a9 +md5=68020c2619c7a744dcee12b670c9f413 +cksum=3405759428 +upstream_url=https://pypi.io/packages/py3/p/pyproject_hooks/pyproject_hooks-VERSION-py3-none-any.whl diff --git a/build/pkgs/gambit/dependencies b/build/pkgs/pyproject_hooks/dependencies similarity index 62% rename from build/pkgs/gambit/dependencies rename to build/pkgs/pyproject_hooks/dependencies index e026bfaacf9..644ad35f773 100644 --- a/build/pkgs/gambit/dependencies +++ b/build/pkgs/pyproject_hooks/dependencies @@ -1,4 +1,4 @@ -cython | $(PYTHON_TOOLCHAIN) $(PYTHON) + | pip $(PYTHON) ---------- All lines of this file are ignored except the first. diff --git a/build/pkgs/pyproject_hooks/package-version.txt b/build/pkgs/pyproject_hooks/package-version.txt new file mode 100644 index 00000000000..3eefcb9dd5b --- /dev/null +++ b/build/pkgs/pyproject_hooks/package-version.txt @@ -0,0 +1 @@ +1.0.0 diff --git a/build/pkgs/pyproject_hooks/type b/build/pkgs/pyproject_hooks/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/pyproject_hooks/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/pyproject_hooks/version_requirements.txt b/build/pkgs/pyproject_hooks/version_requirements.txt new file mode 100644 index 00000000000..1f783738e6f --- /dev/null +++ b/build/pkgs/pyproject_hooks/version_requirements.txt @@ -0,0 +1 @@ +pyproject_hooks diff --git a/build/pkgs/pyscipopt/checksums.ini b/build/pkgs/pyscipopt/checksums.ini index 5b537d4c57d..1232c25dff5 100644 --- a/build/pkgs/pyscipopt/checksums.ini +++ b/build/pkgs/pyscipopt/checksums.ini @@ -1,5 +1,5 @@ tarball=PySCIPOpt-VERSION.tar.gz -sha1=5ae7f3d7e9d8b344ee9a4413154ae80a5ee137de -md5=32a4dced7e74a1c290b32ef0da751129 -cksum=2258510906 +sha1=713e32cc0ff112500c4f43487614094ece4a8bbf +md5=ee425a362744a4475228510b48781be9 +cksum=1541742154 upstream_url=https://pypi.io/packages/source/p/pyscipopt/PySCIPOpt-VERSION.tar.gz diff --git a/build/pkgs/pyscipopt/package-version.txt b/build/pkgs/pyscipopt/package-version.txt index fdc6698807a..0062ac97180 100644 --- a/build/pkgs/pyscipopt/package-version.txt +++ b/build/pkgs/pyscipopt/package-version.txt @@ -1 +1 @@ -4.4.0 +5.0.0 diff --git a/build/pkgs/pyscipopt/patches/792.patch b/build/pkgs/pyscipopt/patches/792.patch deleted file mode 100644 index faff6076eb4..00000000000 --- a/build/pkgs/pyscipopt/patches/792.patch +++ /dev/null @@ -1,1217 +0,0 @@ -From 96acc6be39b6d153cfcfe7b14741956eaf7f5851 Mon Sep 17 00:00:00 2001 -From: Matthias Koeppe -Date: Sun, 18 Feb 2024 19:47:52 -0800 -Subject: [PATCH] src/pyscipopt: Add 'noexcept' to functions that use 'with - gil' - ---- - src/pyscipopt/benders.pxi | 30 ++++++++--------- - src/pyscipopt/benderscut.pxi | 14 ++++---- - src/pyscipopt/branchrule.pxi | 18 +++++----- - src/pyscipopt/conshdlr.pxi | 64 ++++++++++++++++++------------------ - src/pyscipopt/cutsel.pxi | 14 ++++---- - src/pyscipopt/event.pxi | 18 +++++----- - src/pyscipopt/heuristic.pxi | 14 ++++---- - src/pyscipopt/nodesel.pxi | 16 ++++----- - src/pyscipopt/presol.pxi | 14 ++++---- - src/pyscipopt/pricer.pxi | 16 ++++----- - src/pyscipopt/propagator.pxi | 22 ++++++------- - src/pyscipopt/reader.pxi | 8 ++--- - src/pyscipopt/relax.pxi | 14 ++++---- - src/pyscipopt/scip.pxd | 2 +- - src/pyscipopt/scip.pxi | 4 +-- - src/pyscipopt/sepa.pxi | 16 ++++----- - 16 files changed, 142 insertions(+), 142 deletions(-) - -diff --git a/src/pyscipopt/benders.pxi b/src/pyscipopt/benders.pxi -index 3e11db189..66a394d8d 100644 ---- a/src/pyscipopt/benders.pxi -+++ b/src/pyscipopt/benders.pxi -@@ -70,10 +70,10 @@ cdef Variable getPyVar(SCIP_VAR* var): - return vardata - - --cdef SCIP_RETCODE PyBendersCopy (SCIP* scip, SCIP_BENDERS* benders, SCIP_Bool threadsafe) with gil: -+cdef SCIP_RETCODE PyBendersCopy (SCIP* scip, SCIP_BENDERS* benders, SCIP_Bool threadsafe) noexcept with gil: - return SCIP_OKAY - --cdef SCIP_RETCODE PyBendersFree (SCIP* scip, SCIP_BENDERS* benders) with gil: -+cdef SCIP_RETCODE PyBendersFree (SCIP* scip, SCIP_BENDERS* benders) noexcept with gil: - cdef SCIP_BENDERSDATA* bendersdata - bendersdata = SCIPbendersGetData(benders) - PyBenders = bendersdata -@@ -81,56 +81,56 @@ cdef SCIP_RETCODE PyBendersFree (SCIP* scip, SCIP_BENDERS* benders) with gil: - Py_DECREF(PyBenders) - return SCIP_OKAY - --cdef SCIP_RETCODE PyBendersInit (SCIP* scip, SCIP_BENDERS* benders) with gil: -+cdef SCIP_RETCODE PyBendersInit (SCIP* scip, SCIP_BENDERS* benders) noexcept with gil: - cdef SCIP_BENDERSDATA* bendersdata - bendersdata = SCIPbendersGetData(benders) - PyBenders = bendersdata - PyBenders.bendersinit() - return SCIP_OKAY - --cdef SCIP_RETCODE PyBendersExit (SCIP* scip, SCIP_BENDERS* benders) with gil: -+cdef SCIP_RETCODE PyBendersExit (SCIP* scip, SCIP_BENDERS* benders) noexcept with gil: - cdef SCIP_BENDERSDATA* bendersdata - bendersdata = SCIPbendersGetData(benders) - PyBenders = bendersdata - PyBenders.bendersexit() - return SCIP_OKAY - --cdef SCIP_RETCODE PyBendersInitpre (SCIP* scip, SCIP_BENDERS* benders) with gil: -+cdef SCIP_RETCODE PyBendersInitpre (SCIP* scip, SCIP_BENDERS* benders) noexcept with gil: - cdef SCIP_BENDERSDATA* bendersdata - bendersdata = SCIPbendersGetData(benders) - PyBenders = bendersdata - PyBenders.bendersinitpre() - return SCIP_OKAY - --cdef SCIP_RETCODE PyBendersExitpre (SCIP* scip, SCIP_BENDERS* benders) with gil: -+cdef SCIP_RETCODE PyBendersExitpre (SCIP* scip, SCIP_BENDERS* benders) noexcept with gil: - cdef SCIP_BENDERSDATA* bendersdata - bendersdata = SCIPbendersGetData(benders) - PyBenders = bendersdata - PyBenders.bendersexitpre() - return SCIP_OKAY - --cdef SCIP_RETCODE PyBendersInitsol (SCIP* scip, SCIP_BENDERS* benders) with gil: -+cdef SCIP_RETCODE PyBendersInitsol (SCIP* scip, SCIP_BENDERS* benders) noexcept with gil: - cdef SCIP_BENDERSDATA* bendersdata - bendersdata = SCIPbendersGetData(benders) - PyBenders = bendersdata - PyBenders.bendersinitsol() - return SCIP_OKAY - --cdef SCIP_RETCODE PyBendersExitsol (SCIP* scip, SCIP_BENDERS* benders) with gil: -+cdef SCIP_RETCODE PyBendersExitsol (SCIP* scip, SCIP_BENDERS* benders) noexcept with gil: - cdef SCIP_BENDERSDATA* bendersdata - bendersdata = SCIPbendersGetData(benders) - PyBenders = bendersdata - PyBenders.bendersexitsol() - return SCIP_OKAY - --cdef SCIP_RETCODE PyBendersCreatesub (SCIP* scip, SCIP_BENDERS* benders, int probnumber) with gil: -+cdef SCIP_RETCODE PyBendersCreatesub (SCIP* scip, SCIP_BENDERS* benders, int probnumber) noexcept with gil: - cdef SCIP_BENDERSDATA* bendersdata - bendersdata = SCIPbendersGetData(benders) - PyBenders = bendersdata - PyBenders.benderscreatesub(probnumber) - return SCIP_OKAY - --cdef SCIP_RETCODE PyBendersPresubsolve (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, SCIP_BENDERSENFOTYPE type, SCIP_Bool checkint, SCIP_Bool* infeasible, SCIP_Bool* auxviol, SCIP_Bool* skipsolve, SCIP_RESULT* result) with gil: -+cdef SCIP_RETCODE PyBendersPresubsolve (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, SCIP_BENDERSENFOTYPE type, SCIP_Bool checkint, SCIP_Bool* infeasible, SCIP_Bool* auxviol, SCIP_Bool* skipsolve, SCIP_RESULT* result) noexcept with gil: - cdef SCIP_BENDERSDATA* bendersdata - bendersdata = SCIPbendersGetData(benders) - PyBenders = bendersdata -@@ -146,7 +146,7 @@ cdef SCIP_RETCODE PyBendersPresubsolve (SCIP* scip, SCIP_BENDERS* benders, SCIP_ - result[0] = result_dict.get("result", result[0]) - return SCIP_OKAY - --cdef SCIP_RETCODE PyBendersSolvesubconvex (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, int probnumber, SCIP_Bool onlyconvex, SCIP_Real* objective, SCIP_RESULT* result) with gil: -+cdef SCIP_RETCODE PyBendersSolvesubconvex (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, int probnumber, SCIP_Bool onlyconvex, SCIP_Real* objective, SCIP_RESULT* result) noexcept with gil: - cdef SCIP_BENDERSDATA* bendersdata - bendersdata = SCIPbendersGetData(benders) - PyBenders = bendersdata -@@ -159,7 +159,7 @@ cdef SCIP_RETCODE PyBendersSolvesubconvex (SCIP* scip, SCIP_BENDERS* benders, SC - result[0] = result_dict.get("result", result[0]) - return SCIP_OKAY - --cdef SCIP_RETCODE PyBendersSolvesub (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, int probnumber, SCIP_Real* objective, SCIP_RESULT* result) with gil: -+cdef SCIP_RETCODE PyBendersSolvesub (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, int probnumber, SCIP_Real* objective, SCIP_RESULT* result) noexcept with gil: - cdef SCIP_BENDERSDATA* bendersdata - bendersdata = SCIPbendersGetData(benders) - PyBenders = bendersdata -@@ -174,7 +174,7 @@ cdef SCIP_RETCODE PyBendersSolvesub (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL - - cdef SCIP_RETCODE PyBendersPostsolve (SCIP* scip, SCIP_BENDERS* benders, SCIP_SOL* sol, - SCIP_BENDERSENFOTYPE type, int* mergecands, int npriomergecands, int nmergecands, SCIP_Bool checkint, -- SCIP_Bool infeasible, SCIP_Bool* merged) with gil: -+ SCIP_Bool infeasible, SCIP_Bool* merged) noexcept with gil: - cdef SCIP_BENDERSDATA* bendersdata - bendersdata = SCIPbendersGetData(benders) - PyBenders = bendersdata -@@ -190,7 +190,7 @@ cdef SCIP_RETCODE PyBendersPostsolve (SCIP* scip, SCIP_BENDERS* benders, SCIP_SO - merged[0] = result_dict.get("merged", False) - return SCIP_OKAY - --cdef SCIP_RETCODE PyBendersFreesub (SCIP* scip, SCIP_BENDERS* benders, int probnumber) with gil: -+cdef SCIP_RETCODE PyBendersFreesub (SCIP* scip, SCIP_BENDERS* benders, int probnumber) noexcept with gil: - cdef SCIP_BENDERSDATA* bendersdata - bendersdata = SCIPbendersGetData(benders) - PyBenders = bendersdata -@@ -198,7 +198,7 @@ cdef SCIP_RETCODE PyBendersFreesub (SCIP* scip, SCIP_BENDERS* benders, int probn - return SCIP_OKAY - - #TODO: Really need to ask about the passing and returning of variables --cdef SCIP_RETCODE PyBendersGetvar (SCIP* scip, SCIP_BENDERS* benders, SCIP_VAR* var, SCIP_VAR** mappedvar, int probnumber) with gil: -+cdef SCIP_RETCODE PyBendersGetvar (SCIP* scip, SCIP_BENDERS* benders, SCIP_VAR* var, SCIP_VAR** mappedvar, int probnumber) noexcept with gil: - cdef SCIP_BENDERSDATA* bendersdata - bendersdata = SCIPbendersGetData(benders) - PyBenders = bendersdata -diff --git a/src/pyscipopt/benderscut.pxi b/src/pyscipopt/benderscut.pxi -index 506a6f065..1ce561a06 100644 ---- a/src/pyscipopt/benderscut.pxi -+++ b/src/pyscipopt/benderscut.pxi -@@ -24,10 +24,10 @@ cdef class Benderscut: - print("python error in benderscutexec: this method needs to be implemented") - return {} - --cdef SCIP_RETCODE PyBenderscutCopy (SCIP* scip, SCIP_BENDERS* benders, SCIP_BENDERSCUT* benderscut) with gil: -+cdef SCIP_RETCODE PyBenderscutCopy (SCIP* scip, SCIP_BENDERS* benders, SCIP_BENDERSCUT* benderscut) noexcept with gil: - return SCIP_OKAY - --cdef SCIP_RETCODE PyBenderscutFree (SCIP* scip, SCIP_BENDERSCUT* benderscut) with gil: -+cdef SCIP_RETCODE PyBenderscutFree (SCIP* scip, SCIP_BENDERSCUT* benderscut) noexcept with gil: - cdef SCIP_BENDERSCUTDATA* benderscutdata - benderscutdata = SCIPbenderscutGetData(benderscut) - PyBenderscut = benderscutdata -@@ -35,35 +35,35 @@ cdef SCIP_RETCODE PyBenderscutFree (SCIP* scip, SCIP_BENDERSCUT* benderscut) wit - Py_DECREF(PyBenderscut) - return SCIP_OKAY - --cdef SCIP_RETCODE PyBenderscutInit (SCIP* scip, SCIP_BENDERSCUT* benderscut) with gil: -+cdef SCIP_RETCODE PyBenderscutInit (SCIP* scip, SCIP_BENDERSCUT* benderscut) noexcept with gil: - cdef SCIP_BENDERSCUTDATA* benderscutdata - benderscutdata = SCIPbenderscutGetData(benderscut) - PyBenderscut = benderscutdata - PyBenderscut.benderscutinit() - return SCIP_OKAY - --cdef SCIP_RETCODE PyBenderscutExit (SCIP* scip, SCIP_BENDERSCUT* benderscut) with gil: -+cdef SCIP_RETCODE PyBenderscutExit (SCIP* scip, SCIP_BENDERSCUT* benderscut) noexcept with gil: - cdef SCIP_BENDERSCUTDATA* benderscutdata - benderscutdata = SCIPbenderscutGetData(benderscut) - PyBenderscut = benderscutdata - PyBenderscut.benderscutexit() - return SCIP_OKAY - --cdef SCIP_RETCODE PyBenderscutInitsol (SCIP* scip, SCIP_BENDERSCUT* benderscut) with gil: -+cdef SCIP_RETCODE PyBenderscutInitsol (SCIP* scip, SCIP_BENDERSCUT* benderscut) noexcept with gil: - cdef SCIP_BENDERSCUTDATA* benderscutdata - benderscutdata = SCIPbenderscutGetData(benderscut) - PyBenderscut = benderscutdata - PyBenderscut.benderscutinitsol() - return SCIP_OKAY - --cdef SCIP_RETCODE PyBenderscutExitsol (SCIP* scip, SCIP_BENDERSCUT* benderscut) with gil: -+cdef SCIP_RETCODE PyBenderscutExitsol (SCIP* scip, SCIP_BENDERSCUT* benderscut) noexcept with gil: - cdef SCIP_BENDERSCUTDATA* benderscutdata - benderscutdata = SCIPbenderscutGetData(benderscut) - PyBenderscut = benderscutdata - PyBenderscut.benderscutexitsol() - return SCIP_OKAY - --cdef SCIP_RETCODE PyBenderscutExec (SCIP* scip, SCIP_BENDERS* benders, SCIP_BENDERSCUT* benderscut, SCIP_SOL* sol, int probnumber, SCIP_BENDERSENFOTYPE type, SCIP_RESULT* result) with gil: -+cdef SCIP_RETCODE PyBenderscutExec (SCIP* scip, SCIP_BENDERS* benders, SCIP_BENDERSCUT* benderscut, SCIP_SOL* sol, int probnumber, SCIP_BENDERSENFOTYPE type, SCIP_RESULT* result) noexcept with gil: - cdef SCIP_BENDERSCUTDATA* benderscutdata - benderscutdata = SCIPbenderscutGetData(benderscut) - PyBenderscut = benderscutdata -diff --git a/src/pyscipopt/branchrule.pxi b/src/pyscipopt/branchrule.pxi -index 251aa33b1..2d3411d2c 100644 ---- a/src/pyscipopt/branchrule.pxi -+++ b/src/pyscipopt/branchrule.pxi -@@ -39,10 +39,10 @@ cdef class Branchrule: - - - --cdef SCIP_RETCODE PyBranchruleCopy (SCIP* scip, SCIP_BRANCHRULE* branchrule) with gil: -+cdef SCIP_RETCODE PyBranchruleCopy (SCIP* scip, SCIP_BRANCHRULE* branchrule) noexcept with gil: - return SCIP_OKAY - --cdef SCIP_RETCODE PyBranchruleFree (SCIP* scip, SCIP_BRANCHRULE* branchrule) with gil: -+cdef SCIP_RETCODE PyBranchruleFree (SCIP* scip, SCIP_BRANCHRULE* branchrule) noexcept with gil: - cdef SCIP_BRANCHRULEDATA* branchruledata - branchruledata = SCIPbranchruleGetData(branchrule) - PyBranchrule = branchruledata -@@ -50,35 +50,35 @@ cdef SCIP_RETCODE PyBranchruleFree (SCIP* scip, SCIP_BRANCHRULE* branchrule) wit - Py_DECREF(PyBranchrule) - return SCIP_OKAY - --cdef SCIP_RETCODE PyBranchruleInit (SCIP* scip, SCIP_BRANCHRULE* branchrule) with gil: -+cdef SCIP_RETCODE PyBranchruleInit (SCIP* scip, SCIP_BRANCHRULE* branchrule) noexcept with gil: - cdef SCIP_BRANCHRULEDATA* branchruledata - branchruledata = SCIPbranchruleGetData(branchrule) - PyBranchrule = branchruledata - PyBranchrule.branchinit() - return SCIP_OKAY - --cdef SCIP_RETCODE PyBranchruleExit (SCIP* scip, SCIP_BRANCHRULE* branchrule) with gil: -+cdef SCIP_RETCODE PyBranchruleExit (SCIP* scip, SCIP_BRANCHRULE* branchrule) noexcept with gil: - cdef SCIP_BRANCHRULEDATA* branchruledata - branchruledata = SCIPbranchruleGetData(branchrule) - PyBranchrule = branchruledata - PyBranchrule.branchexit() - return SCIP_OKAY - --cdef SCIP_RETCODE PyBranchruleInitsol (SCIP* scip, SCIP_BRANCHRULE* branchrule) with gil: -+cdef SCIP_RETCODE PyBranchruleInitsol (SCIP* scip, SCIP_BRANCHRULE* branchrule) noexcept with gil: - cdef SCIP_BRANCHRULEDATA* branchruledata - branchruledata = SCIPbranchruleGetData(branchrule) - PyBranchrule = branchruledata - PyBranchrule.branchinitsol() - return SCIP_OKAY - --cdef SCIP_RETCODE PyBranchruleExitsol (SCIP* scip, SCIP_BRANCHRULE* branchrule) with gil: -+cdef SCIP_RETCODE PyBranchruleExitsol (SCIP* scip, SCIP_BRANCHRULE* branchrule) noexcept with gil: - cdef SCIP_BRANCHRULEDATA* branchruledata - branchruledata = SCIPbranchruleGetData(branchrule) - PyBranchrule = branchruledata - PyBranchrule.branchexitsol() - return SCIP_OKAY - --cdef SCIP_RETCODE PyBranchruleExeclp (SCIP* scip, SCIP_BRANCHRULE* branchrule, SCIP_Bool allowaddcons, SCIP_RESULT* result) with gil: -+cdef SCIP_RETCODE PyBranchruleExeclp (SCIP* scip, SCIP_BRANCHRULE* branchrule, SCIP_Bool allowaddcons, SCIP_RESULT* result) noexcept with gil: - cdef SCIP_BRANCHRULEDATA* branchruledata - branchruledata = SCIPbranchruleGetData(branchrule) - PyBranchrule = branchruledata -@@ -86,7 +86,7 @@ cdef SCIP_RETCODE PyBranchruleExeclp (SCIP* scip, SCIP_BRANCHRULE* branchrule, S - result[0] = result_dict.get("result", result[0]) - return SCIP_OKAY - --cdef SCIP_RETCODE PyBranchruleExecext(SCIP* scip, SCIP_BRANCHRULE* branchrule, SCIP_Bool allowaddcons, SCIP_RESULT* result) with gil: -+cdef SCIP_RETCODE PyBranchruleExecext(SCIP* scip, SCIP_BRANCHRULE* branchrule, SCIP_Bool allowaddcons, SCIP_RESULT* result) noexcept with gil: - cdef SCIP_BRANCHRULEDATA* branchruledata - branchruledata = SCIPbranchruleGetData(branchrule) - PyBranchrule = branchruledata -@@ -94,7 +94,7 @@ cdef SCIP_RETCODE PyBranchruleExecext(SCIP* scip, SCIP_BRANCHRULE* branchrule, S - result[0] = result_dict.get("result", result[0]) - return SCIP_OKAY - --cdef SCIP_RETCODE PyBranchruleExecps(SCIP* scip, SCIP_BRANCHRULE* branchrule, SCIP_Bool allowaddcons, SCIP_RESULT* result) with gil: -+cdef SCIP_RETCODE PyBranchruleExecps(SCIP* scip, SCIP_BRANCHRULE* branchrule, SCIP_Bool allowaddcons, SCIP_RESULT* result) noexcept with gil: - cdef SCIP_BRANCHRULEDATA* branchruledata - branchruledata = SCIPbranchruleGetData(branchrule) - PyBranchrule = branchruledata -diff --git a/src/pyscipopt/conshdlr.pxi b/src/pyscipopt/conshdlr.pxi -index 80c60c17c..1299ad35c 100644 ---- a/src/pyscipopt/conshdlr.pxi -+++ b/src/pyscipopt/conshdlr.pxi -@@ -150,16 +150,16 @@ cdef Constraint getPyCons(SCIP_CONS* cons): - - - --cdef SCIP_RETCODE PyConshdlrCopy (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_Bool* valid) with gil: -+cdef SCIP_RETCODE PyConshdlrCopy (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_Bool* valid) noexcept with gil: - return SCIP_OKAY - --cdef SCIP_RETCODE PyConsFree (SCIP* scip, SCIP_CONSHDLR* conshdlr) with gil: -+cdef SCIP_RETCODE PyConsFree (SCIP* scip, SCIP_CONSHDLR* conshdlr) noexcept with gil: - PyConshdlr = getPyConshdlr(conshdlr) - PyConshdlr.consfree() - Py_DECREF(PyConshdlr) - return SCIP_OKAY - --cdef SCIP_RETCODE PyConsInit (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss) with gil: -+cdef SCIP_RETCODE PyConsInit (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss) noexcept with gil: - PyConshdlr = getPyConshdlr(conshdlr) - cdef constraints = [] - for i in range(nconss): -@@ -167,7 +167,7 @@ cdef SCIP_RETCODE PyConsInit (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** c - PyConshdlr.consinit(constraints) - return SCIP_OKAY - --cdef SCIP_RETCODE PyConsExit (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss) with gil: -+cdef SCIP_RETCODE PyConsExit (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss) noexcept with gil: - PyConshdlr = getPyConshdlr(conshdlr) - cdef constraints = [] - for i in range(nconss): -@@ -175,7 +175,7 @@ cdef SCIP_RETCODE PyConsExit (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** c - PyConshdlr.consexit(constraints) - return SCIP_OKAY - --cdef SCIP_RETCODE PyConsInitpre (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss) with gil: -+cdef SCIP_RETCODE PyConsInitpre (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss) noexcept with gil: - PyConshdlr = getPyConshdlr(conshdlr) - cdef constraints = [] - for i in range(nconss): -@@ -183,7 +183,7 @@ cdef SCIP_RETCODE PyConsInitpre (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* - PyConshdlr.consinitpre(constraints) - return SCIP_OKAY - --cdef SCIP_RETCODE PyConsExitpre (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss) with gil: -+cdef SCIP_RETCODE PyConsExitpre (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss) noexcept with gil: - PyConshdlr = getPyConshdlr(conshdlr) - cdef constraints = [] - for i in range(nconss): -@@ -191,7 +191,7 @@ cdef SCIP_RETCODE PyConsExitpre (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* - PyConshdlr.consexitpre(constraints) - return SCIP_OKAY - --cdef SCIP_RETCODE PyConsInitsol (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss) with gil: -+cdef SCIP_RETCODE PyConsInitsol (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss) noexcept with gil: - PyConshdlr = getPyConshdlr(conshdlr) - cdef constraints = [] - for i in range(nconss): -@@ -199,7 +199,7 @@ cdef SCIP_RETCODE PyConsInitsol (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* - PyConshdlr.consinitsol(constraints) - return SCIP_OKAY - --cdef SCIP_RETCODE PyConsExitsol (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, SCIP_Bool restart) with gil: -+cdef SCIP_RETCODE PyConsExitsol (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, SCIP_Bool restart) noexcept with gil: - PyConshdlr = getPyConshdlr(conshdlr) - cdef constraints = [] - for i in range(nconss): -@@ -207,7 +207,7 @@ cdef SCIP_RETCODE PyConsExitsol (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* - PyConshdlr.consexitsol(constraints, restart) - return SCIP_OKAY - --cdef SCIP_RETCODE PyConsDelete (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons, SCIP_CONSDATA** consdata) with gil: -+cdef SCIP_RETCODE PyConsDelete (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons, SCIP_CONSDATA** consdata) noexcept with gil: - PyConshdlr = getPyConshdlr(conshdlr) - PyCons = getPyCons(cons) - assert consdata[0] == PyCons -@@ -216,7 +216,7 @@ cdef SCIP_RETCODE PyConsDelete (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* - Py_DECREF(PyCons) - return SCIP_OKAY - --cdef SCIP_RETCODE PyConsTrans (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* sourcecons, SCIP_CONS** targetcons) with gil: -+cdef SCIP_RETCODE PyConsTrans (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* sourcecons, SCIP_CONS** targetcons) noexcept with gil: - cdef Constraint PyTargetCons - PyConshdlr = getPyConshdlr(conshdlr) - PySourceCons = getPyCons(sourcecons) -@@ -235,7 +235,7 @@ cdef SCIP_RETCODE PyConsTrans (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* s - PySourceCons.isRemovable(), PySourceCons.isStickingAtNode())) - return SCIP_OKAY - --cdef SCIP_RETCODE PyConsInitlp (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, SCIP_Bool* infeasible) with gil: -+cdef SCIP_RETCODE PyConsInitlp (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, SCIP_Bool* infeasible) noexcept with gil: - PyConshdlr = getPyConshdlr(conshdlr) - cdef constraints = [] - for i in range(nconss): -@@ -244,7 +244,7 @@ cdef SCIP_RETCODE PyConsInitlp (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** - infeasible[0] = result_dict.get("infeasible", infeasible[0]) - return SCIP_OKAY - --cdef SCIP_RETCODE PyConsSepalp (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, int nusefulconss, SCIP_RESULT* result) with gil: -+cdef SCIP_RETCODE PyConsSepalp (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, int nusefulconss, SCIP_RESULT* result) noexcept with gil: - PyConshdlr = getPyConshdlr(conshdlr) - cdef constraints = [] - for i in range(nconss): -@@ -254,7 +254,7 @@ cdef SCIP_RETCODE PyConsSepalp (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** - return SCIP_OKAY - - cdef SCIP_RETCODE PyConsSepasol (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, int nusefulconss, -- SCIP_SOL* sol, SCIP_RESULT* result) with gil: -+ SCIP_SOL* sol, SCIP_RESULT* result) noexcept with gil: - PyConshdlr = getPyConshdlr(conshdlr) - cdef constraints = [] - for i in range(nconss): -@@ -265,7 +265,7 @@ cdef SCIP_RETCODE PyConsSepasol (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* - return SCIP_OKAY - - cdef SCIP_RETCODE PyConsEnfolp (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, int nusefulconss, -- SCIP_Bool solinfeasible, SCIP_RESULT* result) with gil: -+ SCIP_Bool solinfeasible, SCIP_RESULT* result) noexcept with gil: - PyConshdlr = getPyConshdlr(conshdlr) - cdef constraints = [] - for i in range(nconss): -@@ -274,7 +274,7 @@ cdef SCIP_RETCODE PyConsEnfolp (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** - result[0] = result_dict.get("result", result[0]) - return SCIP_OKAY - --cdef SCIP_RETCODE PyConsEnforelax (SCIP* scip, SCIP_SOL* sol, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, int nusefulconss, SCIP_Bool solinfeasible, SCIP_RESULT* result) with gil: -+cdef SCIP_RETCODE PyConsEnforelax (SCIP* scip, SCIP_SOL* sol, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, int nusefulconss, SCIP_Bool solinfeasible, SCIP_RESULT* result) noexcept with gil: - PyConshdlr = getPyConshdlr(conshdlr) - cdef constraints = [] - for i in range(nconss): -@@ -285,7 +285,7 @@ cdef SCIP_RETCODE PyConsEnforelax (SCIP* scip, SCIP_SOL* sol, SCIP_CONSHDLR* con - return SCIP_OKAY - - cdef SCIP_RETCODE PyConsEnfops (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, int nusefulconss, -- SCIP_Bool solinfeasible, SCIP_Bool objinfeasible, SCIP_RESULT* result) with gil: -+ SCIP_Bool solinfeasible, SCIP_Bool objinfeasible, SCIP_RESULT* result) noexcept with gil: - PyConshdlr = getPyConshdlr(conshdlr) - cdef constraints = [] - for i in range(nconss): -@@ -295,7 +295,7 @@ cdef SCIP_RETCODE PyConsEnfops (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** - return SCIP_OKAY - - cdef SCIP_RETCODE PyConsCheck (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, SCIP_SOL* sol, SCIP_Bool checkintegrality, -- SCIP_Bool checklprows, SCIP_Bool printreason, SCIP_Bool completely, SCIP_RESULT* result) with gil: -+ SCIP_Bool checklprows, SCIP_Bool printreason, SCIP_Bool completely, SCIP_RESULT* result) noexcept with gil: - PyConshdlr = getPyConshdlr(conshdlr) - cdef constraints = [] - for i in range(nconss): -@@ -306,7 +306,7 @@ cdef SCIP_RETCODE PyConsCheck (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** - return SCIP_OKAY - - cdef SCIP_RETCODE PyConsProp (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss, int nusefulconss, int nmarkedconss, -- SCIP_PROPTIMING proptiming, SCIP_RESULT* result) with gil: -+ SCIP_PROPTIMING proptiming, SCIP_RESULT* result) noexcept with gil: - PyConshdlr = getPyConshdlr(conshdlr) - cdef constraints = [] - for i in range(nconss): -@@ -319,7 +319,7 @@ cdef SCIP_RETCODE PyConsPresol (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** - int nnewfixedvars, int nnewaggrvars, int nnewchgvartypes, int nnewchgbds, int nnewholes, - int nnewdelconss, int nnewaddconss, int nnewupgdconss, int nnewchgcoefs, int nnewchgsides, - int* nfixedvars, int* naggrvars, int* nchgvartypes, int* nchgbds, int* naddholes, -- int* ndelconss, int* naddconss, int* nupgdconss, int* nchgcoefs, int* nchgsides, SCIP_RESULT* result) with gil: -+ int* ndelconss, int* naddconss, int* nupgdconss, int* nchgcoefs, int* nchgsides, SCIP_RESULT* result) noexcept with gil: - PyConshdlr = getPyConshdlr(conshdlr) - cdef constraints = [] - for i in range(nconss): -@@ -354,12 +354,12 @@ cdef SCIP_RETCODE PyConsPresol (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** - return SCIP_OKAY - - cdef SCIP_RETCODE PyConsResprop (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons, SCIP_VAR* infervar, int inferinfo, -- SCIP_BOUNDTYPE boundtype, SCIP_BDCHGIDX* bdchgidx, SCIP_Real relaxedbd, SCIP_RESULT* result) with gil: -+ SCIP_BOUNDTYPE boundtype, SCIP_BDCHGIDX* bdchgidx, SCIP_Real relaxedbd, SCIP_RESULT* result) noexcept with gil: - PyConshdlr = getPyConshdlr(conshdlr) - PyConshdlr.consresprop() - return SCIP_OKAY - --cdef SCIP_RETCODE PyConsLock (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons, SCIP_LOCKTYPE locktype, int nlockspos, int nlocksneg) with gil: -+cdef SCIP_RETCODE PyConsLock (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons, SCIP_LOCKTYPE locktype, int nlockspos, int nlocksneg) noexcept with gil: - PyConshdlr = getPyConshdlr(conshdlr) - if cons == NULL: - PyConshdlr.conslock(None, locktype, nlockspos, nlocksneg) -@@ -368,31 +368,31 @@ cdef SCIP_RETCODE PyConsLock (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* co - PyConshdlr.conslock(PyCons, locktype, nlockspos, nlocksneg) - return SCIP_OKAY - --cdef SCIP_RETCODE PyConsActive (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons) with gil: -+cdef SCIP_RETCODE PyConsActive (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons) noexcept with gil: - PyConshdlr = getPyConshdlr(conshdlr) - PyCons = getPyCons(cons) - PyConshdlr.consactive(PyCons) - return SCIP_OKAY - --cdef SCIP_RETCODE PyConsDeactive (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons) with gil: -+cdef SCIP_RETCODE PyConsDeactive (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons) noexcept with gil: - PyConshdlr = getPyConshdlr(conshdlr) - PyCons = getPyCons(cons) - PyConshdlr.consdeactive(PyCons) - return SCIP_OKAY - --cdef SCIP_RETCODE PyConsEnable (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons) with gil: -+cdef SCIP_RETCODE PyConsEnable (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons) noexcept with gil: - PyConshdlr = getPyConshdlr(conshdlr) - PyCons = getPyCons(cons) - PyConshdlr.consenable(PyCons) - return SCIP_OKAY - --cdef SCIP_RETCODE PyConsDisable (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons) with gil: -+cdef SCIP_RETCODE PyConsDisable (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons) noexcept with gil: - PyConshdlr = getPyConshdlr(conshdlr) - PyCons = getPyCons(cons) - PyConshdlr.consdisable(PyCons) - return SCIP_OKAY - --cdef SCIP_RETCODE PyConsDelvars (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss) with gil: -+cdef SCIP_RETCODE PyConsDelvars (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** conss, int nconss) noexcept with gil: - PyConshdlr = getPyConshdlr(conshdlr) - cdef constraints = [] - for i in range(nconss): -@@ -400,7 +400,7 @@ cdef SCIP_RETCODE PyConsDelvars (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* - PyConshdlr.consdelvars(constraints) - return SCIP_OKAY - --cdef SCIP_RETCODE PyConsPrint (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons, FILE* file) with gil: -+cdef SCIP_RETCODE PyConsPrint (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons, FILE* file) noexcept with gil: - PyConshdlr = getPyConshdlr(conshdlr) - PyCons = getPyCons(cons) - # TODO: pass file -@@ -411,7 +411,7 @@ cdef SCIP_RETCODE PyConsCopy (SCIP* scip, SCIP_CONS** cons, const char* name, SC - SCIP_CONS* sourcecons, SCIP_HASHMAP* varmap, SCIP_HASHMAP* consmap, SCIP_Bool initial, - SCIP_Bool separate, SCIP_Bool enforce, SCIP_Bool check, SCIP_Bool propagate, SCIP_Bool local, - SCIP_Bool modifiable, SCIP_Bool dynamic, SCIP_Bool removable, SCIP_Bool stickingatnode, -- SCIP_Bool isglobal, SCIP_Bool* valid) with gil: -+ SCIP_Bool isglobal, SCIP_Bool* valid) noexcept with gil: - # TODO everything! - PyConshdlr = getPyConshdlr(sourceconshdlr) - PyConshdlr.conscopy() -@@ -421,14 +421,14 @@ cdef SCIP_RETCODE PyConsCopy (SCIP* scip, SCIP_CONS** cons, const char* name, SC - cdef SCIP_RETCODE PyConsParse (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS** cons, const char* name, const char* str, - SCIP_Bool initial, SCIP_Bool separate, SCIP_Bool enforce, SCIP_Bool check, SCIP_Bool propagate, - SCIP_Bool local, SCIP_Bool modifiable, SCIP_Bool dynamic, SCIP_Bool removable, -- SCIP_Bool stickingatnode, SCIP_Bool* success) with gil: -+ SCIP_Bool stickingatnode, SCIP_Bool* success) noexcept with gil: - # TODO everything! - PyConshdlr = getPyConshdlr(conshdlr) - PyConshdlr.consparse() - success[0] = False - return SCIP_OKAY - --cdef SCIP_RETCODE PyConsGetvars (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons, SCIP_VAR** vars, int varssize, SCIP_Bool* success) with gil: -+cdef SCIP_RETCODE PyConsGetvars (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons, SCIP_VAR** vars, int varssize, SCIP_Bool* success) noexcept with gil: - # TODO - PyConshdlr = getPyConshdlr(conshdlr) - PyCons = getPyCons(cons) -@@ -436,7 +436,7 @@ cdef SCIP_RETCODE PyConsGetvars (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* - success[0] = False - return SCIP_OKAY - --cdef SCIP_RETCODE PyConsGetnvars (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons, int* nvars, SCIP_Bool* success) with gil: -+cdef SCIP_RETCODE PyConsGetnvars (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons, int* nvars, SCIP_Bool* success) noexcept with gil: - PyConshdlr = getPyConshdlr(conshdlr) - PyCons = getPyCons(cons) - result_dict = PyConshdlr.consgetnvars(PyCons) -@@ -445,7 +445,7 @@ cdef SCIP_RETCODE PyConsGetnvars (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS - return SCIP_OKAY - - cdef SCIP_RETCODE PyConsGetdivebdchgs (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_DIVESET* diveset, SCIP_SOL* sol, -- SCIP_Bool* success, SCIP_Bool* infeasible) with gil: -+ SCIP_Bool* success, SCIP_Bool* infeasible) noexcept with gil: - # TODO - PyConshdlr = getPyConshdlr(conshdlr) - PyConshdlr.consgetdivebdchgs() -diff --git a/src/pyscipopt/cutsel.pxi b/src/pyscipopt/cutsel.pxi -index e953cb1e9..d259fb28e 100644 ---- a/src/pyscipopt/cutsel.pxi -+++ b/src/pyscipopt/cutsel.pxi -@@ -29,10 +29,10 @@ cdef class Cutsel: - return {} - - --cdef SCIP_RETCODE PyCutselCopy (SCIP* scip, SCIP_CUTSEL* cutsel) with gil: -+cdef SCIP_RETCODE PyCutselCopy (SCIP* scip, SCIP_CUTSEL* cutsel) noexcept with gil: - return SCIP_OKAY - --cdef SCIP_RETCODE PyCutselFree (SCIP* scip, SCIP_CUTSEL* cutsel) with gil: -+cdef SCIP_RETCODE PyCutselFree (SCIP* scip, SCIP_CUTSEL* cutsel) noexcept with gil: - cdef SCIP_CUTSELDATA* cutseldata - cutseldata = SCIPcutselGetData(cutsel) - PyCutsel = cutseldata -@@ -40,7 +40,7 @@ cdef SCIP_RETCODE PyCutselFree (SCIP* scip, SCIP_CUTSEL* cutsel) with gil: - Py_DECREF(PyCutsel) - return SCIP_OKAY - --cdef SCIP_RETCODE PyCutselInit (SCIP* scip, SCIP_CUTSEL* cutsel) with gil: -+cdef SCIP_RETCODE PyCutselInit (SCIP* scip, SCIP_CUTSEL* cutsel) noexcept with gil: - cdef SCIP_CUTSELDATA* cutseldata - cutseldata = SCIPcutselGetData(cutsel) - PyCutsel = cutseldata -@@ -48,21 +48,21 @@ cdef SCIP_RETCODE PyCutselInit (SCIP* scip, SCIP_CUTSEL* cutsel) with gil: - return SCIP_OKAY - - --cdef SCIP_RETCODE PyCutselExit (SCIP* scip, SCIP_CUTSEL* cutsel) with gil: -+cdef SCIP_RETCODE PyCutselExit (SCIP* scip, SCIP_CUTSEL* cutsel) noexcept with gil: - cdef SCIP_CUTSELDATA* cutseldata - cutseldata = SCIPcutselGetData(cutsel) - PyCutsel = cutseldata - PyCutsel.cutselexit() - return SCIP_OKAY - --cdef SCIP_RETCODE PyCutselInitsol (SCIP* scip, SCIP_CUTSEL* cutsel) with gil: -+cdef SCIP_RETCODE PyCutselInitsol (SCIP* scip, SCIP_CUTSEL* cutsel) noexcept with gil: - cdef SCIP_CUTSELDATA* cutseldata - cutseldata = SCIPcutselGetData(cutsel) - PyCutsel = cutseldata - PyCutsel.cutselinitsol() - return SCIP_OKAY - --cdef SCIP_RETCODE PyCutselExitsol (SCIP* scip, SCIP_CUTSEL* cutsel) with gil: -+cdef SCIP_RETCODE PyCutselExitsol (SCIP* scip, SCIP_CUTSEL* cutsel) noexcept with gil: - cdef SCIP_CUTSELDATA* cutseldata - cutseldata = SCIPcutselGetData(cutsel) - PyCutsel = cutseldata -@@ -71,7 +71,7 @@ cdef SCIP_RETCODE PyCutselExitsol (SCIP* scip, SCIP_CUTSEL* cutsel) with gil: - - cdef SCIP_RETCODE PyCutselSelect (SCIP* scip, SCIP_CUTSEL* cutsel, SCIP_ROW** cuts, int ncuts, - SCIP_ROW** forcedcuts, int nforcedcuts, SCIP_Bool root, int maxnselectedcuts, -- int* nselectedcuts, SCIP_RESULT* result) with gil: -+ int* nselectedcuts, SCIP_RESULT* result) noexcept with gil: - cdef SCIP_CUTSELDATA* cutseldata - cdef SCIP_ROW* scip_row - cutseldata = SCIPcutselGetData(cutsel) -diff --git a/src/pyscipopt/event.pxi b/src/pyscipopt/event.pxi -index 95c8bc1f4..914e882ed 100644 ---- a/src/pyscipopt/event.pxi -+++ b/src/pyscipopt/event.pxi -@@ -39,48 +39,48 @@ cdef class Eventhdlr: - - - # local helper functions for the interface --cdef Eventhdlr getPyEventhdlr(SCIP_EVENTHDLR* eventhdlr) with gil: -+cdef Eventhdlr getPyEventhdlr(SCIP_EVENTHDLR* eventhdlr) noexcept with gil: - cdef SCIP_EVENTHDLRDATA* eventhdlrdata - eventhdlrdata = SCIPeventhdlrGetData(eventhdlr) - return eventhdlrdata - --cdef SCIP_RETCODE PyEventCopy (SCIP* scip, SCIP_EVENTHDLR* eventhdlr) with gil: -+cdef SCIP_RETCODE PyEventCopy (SCIP* scip, SCIP_EVENTHDLR* eventhdlr) noexcept with gil: - PyEventhdlr = getPyEventhdlr(eventhdlr) - PyEventhdlr.eventcopy() - return SCIP_OKAY - --cdef SCIP_RETCODE PyEventFree (SCIP* scip, SCIP_EVENTHDLR* eventhdlr) with gil: -+cdef SCIP_RETCODE PyEventFree (SCIP* scip, SCIP_EVENTHDLR* eventhdlr) noexcept with gil: - PyEventhdlr = getPyEventhdlr(eventhdlr) - PyEventhdlr.eventfree() - Py_DECREF(PyEventhdlr) - return SCIP_OKAY - --cdef SCIP_RETCODE PyEventInit (SCIP* scip, SCIP_EVENTHDLR* eventhdlr) with gil: -+cdef SCIP_RETCODE PyEventInit (SCIP* scip, SCIP_EVENTHDLR* eventhdlr) noexcept with gil: - PyEventhdlr = getPyEventhdlr(eventhdlr) - PyEventhdlr.eventinit() - return SCIP_OKAY - --cdef SCIP_RETCODE PyEventExit (SCIP* scip, SCIP_EVENTHDLR* eventhdlr) with gil: -+cdef SCIP_RETCODE PyEventExit (SCIP* scip, SCIP_EVENTHDLR* eventhdlr) noexcept with gil: - PyEventhdlr = getPyEventhdlr(eventhdlr) - PyEventhdlr.eventexit() - return SCIP_OKAY - --cdef SCIP_RETCODE PyEventInitsol (SCIP* scip, SCIP_EVENTHDLR* eventhdlr) with gil: -+cdef SCIP_RETCODE PyEventInitsol (SCIP* scip, SCIP_EVENTHDLR* eventhdlr) noexcept with gil: - PyEventhdlr = getPyEventhdlr(eventhdlr) - PyEventhdlr.eventinitsol() - return SCIP_OKAY - --cdef SCIP_RETCODE PyEventExitsol (SCIP* scip, SCIP_EVENTHDLR* eventhdlr) with gil: -+cdef SCIP_RETCODE PyEventExitsol (SCIP* scip, SCIP_EVENTHDLR* eventhdlr) noexcept with gil: - PyEventhdlr = getPyEventhdlr(eventhdlr) - PyEventhdlr.eventexitsol() - return SCIP_OKAY - --cdef SCIP_RETCODE PyEventDelete (SCIP* scip, SCIP_EVENTHDLR* eventhdlr, SCIP_EVENTDATA** eventdata) with gil: -+cdef SCIP_RETCODE PyEventDelete (SCIP* scip, SCIP_EVENTHDLR* eventhdlr, SCIP_EVENTDATA** eventdata) noexcept with gil: - PyEventhdlr = getPyEventhdlr(eventhdlr) - PyEventhdlr.eventdelete() - return SCIP_OKAY - --cdef SCIP_RETCODE PyEventExec (SCIP* scip, SCIP_EVENTHDLR* eventhdlr, SCIP_EVENT* event, SCIP_EVENTDATA* eventdata) with gil: -+cdef SCIP_RETCODE PyEventExec (SCIP* scip, SCIP_EVENTHDLR* eventhdlr, SCIP_EVENT* event, SCIP_EVENTDATA* eventdata) noexcept with gil: - PyEventhdlr = getPyEventhdlr(eventhdlr) - PyEvent = Event() - PyEvent.event = event -diff --git a/src/pyscipopt/heuristic.pxi b/src/pyscipopt/heuristic.pxi -index 2980a1aee..930315630 100644 ---- a/src/pyscipopt/heuristic.pxi -+++ b/src/pyscipopt/heuristic.pxi -@@ -31,10 +31,10 @@ cdef class Heur: - - - --cdef SCIP_RETCODE PyHeurCopy (SCIP* scip, SCIP_HEUR* heur) with gil: -+cdef SCIP_RETCODE PyHeurCopy (SCIP* scip, SCIP_HEUR* heur) noexcept with gil: - return SCIP_OKAY - --cdef SCIP_RETCODE PyHeurFree (SCIP* scip, SCIP_HEUR* heur) with gil: -+cdef SCIP_RETCODE PyHeurFree (SCIP* scip, SCIP_HEUR* heur) noexcept with gil: - cdef SCIP_HEURDATA* heurdata - heurdata = SCIPheurGetData(heur) - PyHeur = heurdata -@@ -42,35 +42,35 @@ cdef SCIP_RETCODE PyHeurFree (SCIP* scip, SCIP_HEUR* heur) with gil: - Py_DECREF(PyHeur) - return SCIP_OKAY - --cdef SCIP_RETCODE PyHeurInit (SCIP* scip, SCIP_HEUR* heur) with gil: -+cdef SCIP_RETCODE PyHeurInit (SCIP* scip, SCIP_HEUR* heur) noexcept with gil: - cdef SCIP_HEURDATA* heurdata - heurdata = SCIPheurGetData(heur) - PyHeur = heurdata - PyHeur.heurinit() - return SCIP_OKAY - --cdef SCIP_RETCODE PyHeurExit (SCIP* scip, SCIP_HEUR* heur) with gil: -+cdef SCIP_RETCODE PyHeurExit (SCIP* scip, SCIP_HEUR* heur) noexcept with gil: - cdef SCIP_HEURDATA* heurdata - heurdata = SCIPheurGetData(heur) - PyHeur = heurdata - PyHeur.heurexit() - return SCIP_OKAY - --cdef SCIP_RETCODE PyHeurInitsol (SCIP* scip, SCIP_HEUR* heur) with gil: -+cdef SCIP_RETCODE PyHeurInitsol (SCIP* scip, SCIP_HEUR* heur) noexcept with gil: - cdef SCIP_HEURDATA* heurdata - heurdata = SCIPheurGetData(heur) - PyHeur = heurdata - PyHeur.heurinitsol() - return SCIP_OKAY - --cdef SCIP_RETCODE PyHeurExitsol (SCIP* scip, SCIP_HEUR* heur) with gil: -+cdef SCIP_RETCODE PyHeurExitsol (SCIP* scip, SCIP_HEUR* heur) noexcept with gil: - cdef SCIP_HEURDATA* heurdata - heurdata = SCIPheurGetData(heur) - PyHeur = heurdata - PyHeur.heurexitsol() - return SCIP_OKAY - --cdef SCIP_RETCODE PyHeurExec (SCIP* scip, SCIP_HEUR* heur, SCIP_HEURTIMING heurtiming, SCIP_Bool nodeinfeasible, SCIP_RESULT* result) with gil: -+cdef SCIP_RETCODE PyHeurExec (SCIP* scip, SCIP_HEUR* heur, SCIP_HEURTIMING heurtiming, SCIP_Bool nodeinfeasible, SCIP_RESULT* result) noexcept with gil: - cdef SCIP_HEURDATA* heurdata - heurdata = SCIPheurGetData(heur) - PyHeur = heurdata -diff --git a/src/pyscipopt/nodesel.pxi b/src/pyscipopt/nodesel.pxi -index 4d795bc9d..a3e832f15 100644 ---- a/src/pyscipopt/nodesel.pxi -+++ b/src/pyscipopt/nodesel.pxi -@@ -42,10 +42,10 @@ cdef class Nodesel: - return 0 - - --cdef SCIP_RETCODE PyNodeselCopy (SCIP* scip, SCIP_NODESEL* nodesel) with gil: -+cdef SCIP_RETCODE PyNodeselCopy (SCIP* scip, SCIP_NODESEL* nodesel) noexcept with gil: - return SCIP_OKAY - --cdef SCIP_RETCODE PyNodeselFree (SCIP* scip, SCIP_NODESEL* nodesel) with gil: -+cdef SCIP_RETCODE PyNodeselFree (SCIP* scip, SCIP_NODESEL* nodesel) noexcept with gil: - cdef SCIP_NODESELDATA* nodeseldata - nodeseldata = SCIPnodeselGetData(nodesel) - PyNodesel = nodeseldata -@@ -53,7 +53,7 @@ cdef SCIP_RETCODE PyNodeselFree (SCIP* scip, SCIP_NODESEL* nodesel) with gil: - Py_DECREF(PyNodesel) - return SCIP_OKAY - --cdef SCIP_RETCODE PyNodeselInit (SCIP* scip, SCIP_NODESEL* nodesel) with gil: -+cdef SCIP_RETCODE PyNodeselInit (SCIP* scip, SCIP_NODESEL* nodesel) noexcept with gil: - cdef SCIP_NODESELDATA* nodeseldata - nodeseldata = SCIPnodeselGetData(nodesel) - PyNodesel = nodeseldata -@@ -61,28 +61,28 @@ cdef SCIP_RETCODE PyNodeselInit (SCIP* scip, SCIP_NODESEL* nodesel) with gil: - return SCIP_OKAY - - --cdef SCIP_RETCODE PyNodeselExit (SCIP* scip, SCIP_NODESEL* nodesel) with gil: -+cdef SCIP_RETCODE PyNodeselExit (SCIP* scip, SCIP_NODESEL* nodesel) noexcept with gil: - cdef SCIP_NODESELDATA* nodeseldata - nodeseldata = SCIPnodeselGetData(nodesel) - PyNodesel = nodeseldata - PyNodesel.nodeexit() - return SCIP_OKAY - --cdef SCIP_RETCODE PyNodeselInitsol (SCIP* scip, SCIP_NODESEL* nodesel) with gil: -+cdef SCIP_RETCODE PyNodeselInitsol (SCIP* scip, SCIP_NODESEL* nodesel) noexcept with gil: - cdef SCIP_NODESELDATA* nodeseldata - nodeseldata = SCIPnodeselGetData(nodesel) - PyNodesel = nodeseldata - PyNodesel.nodeinitsol() - return SCIP_OKAY - --cdef SCIP_RETCODE PyNodeselExitsol (SCIP* scip, SCIP_NODESEL* nodesel) with gil: -+cdef SCIP_RETCODE PyNodeselExitsol (SCIP* scip, SCIP_NODESEL* nodesel) noexcept with gil: - cdef SCIP_NODESELDATA* nodeseldata - nodeseldata = SCIPnodeselGetData(nodesel) - PyNodesel = nodeseldata - PyNodesel.nodeexitsol() - return SCIP_OKAY - --cdef SCIP_RETCODE PyNodeselSelect (SCIP* scip, SCIP_NODESEL* nodesel, SCIP_NODE** selnode) with gil: -+cdef SCIP_RETCODE PyNodeselSelect (SCIP* scip, SCIP_NODESEL* nodesel, SCIP_NODE** selnode) noexcept with gil: - cdef SCIP_NODESELDATA* nodeseldata - nodeseldata = SCIPnodeselGetData(nodesel) - PyNodesel = nodeseldata -@@ -91,7 +91,7 @@ cdef SCIP_RETCODE PyNodeselSelect (SCIP* scip, SCIP_NODESEL* nodesel, SCIP_NODE* - selnode[0] = selected_node.scip_node - return SCIP_OKAY - --cdef int PyNodeselComp (SCIP* scip, SCIP_NODESEL* nodesel, SCIP_NODE* node1, SCIP_NODE* node2) with gil: -+cdef int PyNodeselComp (SCIP* scip, SCIP_NODESEL* nodesel, SCIP_NODE* node1, SCIP_NODE* node2) noexcept with gil: - cdef SCIP_NODESELDATA* nodeseldata - nodeseldata = SCIPnodeselGetData(nodesel) - PyNodesel = nodeseldata -diff --git a/src/pyscipopt/presol.pxi b/src/pyscipopt/presol.pxi -index d2b9115a5..13bd9a623 100644 ---- a/src/pyscipopt/presol.pxi -+++ b/src/pyscipopt/presol.pxi -@@ -30,10 +30,10 @@ cdef class Presol: - - - --cdef SCIP_RETCODE PyPresolCopy (SCIP* scip, SCIP_PRESOL* presol) with gil: -+cdef SCIP_RETCODE PyPresolCopy (SCIP* scip, SCIP_PRESOL* presol) noexcept with gil: - return SCIP_OKAY - --cdef SCIP_RETCODE PyPresolFree (SCIP* scip, SCIP_PRESOL* presol) with gil: -+cdef SCIP_RETCODE PyPresolFree (SCIP* scip, SCIP_PRESOL* presol) noexcept with gil: - cdef SCIP_PRESOLDATA* presoldata - presoldata = SCIPpresolGetData(presol) - PyPresol = presoldata -@@ -41,14 +41,14 @@ cdef SCIP_RETCODE PyPresolFree (SCIP* scip, SCIP_PRESOL* presol) with gil: - Py_DECREF(PyPresol) - return SCIP_OKAY - --cdef SCIP_RETCODE PyPresolInit (SCIP* scip, SCIP_PRESOL* presol) with gil: -+cdef SCIP_RETCODE PyPresolInit (SCIP* scip, SCIP_PRESOL* presol) noexcept with gil: - cdef SCIP_PRESOLDATA* presoldata - presoldata = SCIPpresolGetData(presol) - PyPresol = presoldata - PyPresol.presolinit() - return SCIP_OKAY - --cdef SCIP_RETCODE PyPresolExit (SCIP* scip, SCIP_PRESOL* presol) with gil: -+cdef SCIP_RETCODE PyPresolExit (SCIP* scip, SCIP_PRESOL* presol) noexcept with gil: - cdef SCIP_PRESOLDATA* presoldata - presoldata = SCIPpresolGetData(presol) - PyPresol = presoldata -@@ -56,14 +56,14 @@ cdef SCIP_RETCODE PyPresolExit (SCIP* scip, SCIP_PRESOL* presol) with gil: - return SCIP_OKAY - - --cdef SCIP_RETCODE PyPresolInitpre (SCIP* scip, SCIP_PRESOL* presol) with gil: -+cdef SCIP_RETCODE PyPresolInitpre (SCIP* scip, SCIP_PRESOL* presol) noexcept with gil: - cdef SCIP_PRESOLDATA* presoldata - presoldata = SCIPpresolGetData(presol) - PyPresol = presoldata - PyPresol.presolinitpre() - return SCIP_OKAY - --cdef SCIP_RETCODE PyPresolExitpre (SCIP* scip, SCIP_PRESOL* presol) with gil: -+cdef SCIP_RETCODE PyPresolExitpre (SCIP* scip, SCIP_PRESOL* presol) noexcept with gil: - cdef SCIP_PRESOLDATA* presoldata - presoldata = SCIPpresolGetData(presol) - PyPresol = presoldata -@@ -74,7 +74,7 @@ cdef SCIP_RETCODE PyPresolExec (SCIP* scip, SCIP_PRESOL* presol, int nrounds, SC - int nnewfixedvars, int nnewaggrvars, int nnewchgvartypes, int nnewchgbds, int nnewholes, - int nnewdelconss, int nnewaddconss, int nnewupgdconss, int nnewchgcoefs, int nnewchgsides, - int* nfixedvars, int* naggrvars, int* nchgvartypes, int* nchgbds, int* naddholes, -- int* ndelconss, int* naddconss, int* nupgdconss, int* nchgcoefs, int* nchgsides, SCIP_RESULT* result) with gil: -+ int* ndelconss, int* naddconss, int* nupgdconss, int* nchgcoefs, int* nchgsides, SCIP_RESULT* result) noexcept with gil: - cdef SCIP_PRESOLDATA* presoldata - presoldata = SCIPpresolGetData(presol) - PyPresol = presoldata -diff --git a/src/pyscipopt/pricer.pxi b/src/pyscipopt/pricer.pxi -index 1368572de..a218b254c 100644 ---- a/src/pyscipopt/pricer.pxi -+++ b/src/pyscipopt/pricer.pxi -@@ -33,10 +33,10 @@ cdef class Pricer: - - - --cdef SCIP_RETCODE PyPricerCopy (SCIP* scip, SCIP_PRICER* pricer, SCIP_Bool* valid) with gil: -+cdef SCIP_RETCODE PyPricerCopy (SCIP* scip, SCIP_PRICER* pricer, SCIP_Bool* valid) noexcept with gil: - return SCIP_OKAY - --cdef SCIP_RETCODE PyPricerFree (SCIP* scip, SCIP_PRICER* pricer) with gil: -+cdef SCIP_RETCODE PyPricerFree (SCIP* scip, SCIP_PRICER* pricer) noexcept with gil: - cdef SCIP_PRICERDATA* pricerdata - pricerdata = SCIPpricerGetData(pricer) - PyPricer = pricerdata -@@ -44,35 +44,35 @@ cdef SCIP_RETCODE PyPricerFree (SCIP* scip, SCIP_PRICER* pricer) with gil: - Py_DECREF(PyPricer) - return SCIP_OKAY - --cdef SCIP_RETCODE PyPricerInit (SCIP* scip, SCIP_PRICER* pricer) with gil: -+cdef SCIP_RETCODE PyPricerInit (SCIP* scip, SCIP_PRICER* pricer) noexcept with gil: - cdef SCIP_PRICERDATA* pricerdata - pricerdata = SCIPpricerGetData(pricer) - PyPricer = pricerdata - PyPricer.pricerinit() - return SCIP_OKAY - --cdef SCIP_RETCODE PyPricerExit (SCIP* scip, SCIP_PRICER* pricer) with gil: -+cdef SCIP_RETCODE PyPricerExit (SCIP* scip, SCIP_PRICER* pricer) noexcept with gil: - cdef SCIP_PRICERDATA* pricerdata - pricerdata = SCIPpricerGetData(pricer) - PyPricer = pricerdata - PyPricer.pricerexit() - return SCIP_OKAY - --cdef SCIP_RETCODE PyPricerInitsol (SCIP* scip, SCIP_PRICER* pricer) with gil: -+cdef SCIP_RETCODE PyPricerInitsol (SCIP* scip, SCIP_PRICER* pricer) noexcept with gil: - cdef SCIP_PRICERDATA* pricerdata - pricerdata = SCIPpricerGetData(pricer) - PyPricer = pricerdata - PyPricer.pricerinitsol() - return SCIP_OKAY - --cdef SCIP_RETCODE PyPricerExitsol (SCIP* scip, SCIP_PRICER* pricer) with gil: -+cdef SCIP_RETCODE PyPricerExitsol (SCIP* scip, SCIP_PRICER* pricer) noexcept with gil: - cdef SCIP_PRICERDATA* pricerdata - pricerdata = SCIPpricerGetData(pricer) - PyPricer = pricerdata - PyPricer.pricerexitsol() - return SCIP_OKAY - --cdef SCIP_RETCODE PyPricerRedcost (SCIP* scip, SCIP_PRICER* pricer, SCIP_Real* lowerbound, SCIP_Bool* stopearly, SCIP_RESULT* result) with gil: -+cdef SCIP_RETCODE PyPricerRedcost (SCIP* scip, SCIP_PRICER* pricer, SCIP_Real* lowerbound, SCIP_Bool* stopearly, SCIP_RESULT* result) noexcept with gil: - cdef SCIP_PRICERDATA* pricerdata - pricerdata = SCIPpricerGetData(pricer) - PyPricer = pricerdata -@@ -82,7 +82,7 @@ cdef SCIP_RETCODE PyPricerRedcost (SCIP* scip, SCIP_PRICER* pricer, SCIP_Real* l - stopearly[0] = result_dict.get("stopearly", stopearly[0]) - return SCIP_OKAY - --cdef SCIP_RETCODE PyPricerFarkas (SCIP* scip, SCIP_PRICER* pricer, SCIP_RESULT* result) with gil: -+cdef SCIP_RETCODE PyPricerFarkas (SCIP* scip, SCIP_PRICER* pricer, SCIP_RESULT* result) noexcept with gil: - cdef SCIP_PRICERDATA* pricerdata - pricerdata = SCIPpricerGetData(pricer) - PyPricer = pricerdata -diff --git a/src/pyscipopt/propagator.pxi b/src/pyscipopt/propagator.pxi -index d792577d9..4508efe78 100644 ---- a/src/pyscipopt/propagator.pxi -+++ b/src/pyscipopt/propagator.pxi -@@ -47,10 +47,10 @@ cdef class Prop: - - - --cdef SCIP_RETCODE PyPropCopy (SCIP* scip, SCIP_PROP* prop) with gil: -+cdef SCIP_RETCODE PyPropCopy (SCIP* scip, SCIP_PROP* prop) noexcept with gil: - return SCIP_OKAY - --cdef SCIP_RETCODE PyPropFree (SCIP* scip, SCIP_PROP* prop) with gil: -+cdef SCIP_RETCODE PyPropFree (SCIP* scip, SCIP_PROP* prop) noexcept with gil: - cdef SCIP_PROPDATA* propdata - propdata = SCIPpropGetData(prop) - PyProp = propdata -@@ -58,42 +58,42 @@ cdef SCIP_RETCODE PyPropFree (SCIP* scip, SCIP_PROP* prop) with gil: - Py_DECREF(PyProp) - return SCIP_OKAY - --cdef SCIP_RETCODE PyPropInit (SCIP* scip, SCIP_PROP* prop) with gil: -+cdef SCIP_RETCODE PyPropInit (SCIP* scip, SCIP_PROP* prop) noexcept with gil: - cdef SCIP_PROPDATA* propdata - propdata = SCIPpropGetData(prop) - PyProp = propdata - PyProp.propinit() - return SCIP_OKAY - --cdef SCIP_RETCODE PyPropExit (SCIP* scip, SCIP_PROP* prop) with gil: -+cdef SCIP_RETCODE PyPropExit (SCIP* scip, SCIP_PROP* prop) noexcept with gil: - cdef SCIP_PROPDATA* propdata - propdata = SCIPpropGetData(prop) - PyProp = propdata - PyProp.propexit() - return SCIP_OKAY - --cdef SCIP_RETCODE PyPropInitpre (SCIP* scip, SCIP_PROP* prop) with gil: -+cdef SCIP_RETCODE PyPropInitpre (SCIP* scip, SCIP_PROP* prop) noexcept with gil: - cdef SCIP_PROPDATA* propdata - propdata = SCIPpropGetData(prop) - PyProp = propdata - PyProp.propinitpre() - return SCIP_OKAY - --cdef SCIP_RETCODE PyPropExitpre (SCIP* scip, SCIP_PROP* prop) with gil: -+cdef SCIP_RETCODE PyPropExitpre (SCIP* scip, SCIP_PROP* prop) noexcept with gil: - cdef SCIP_PROPDATA* propdata - propdata = SCIPpropGetData(prop) - PyProp = propdata - PyProp.propexitpre() - return SCIP_OKAY - --cdef SCIP_RETCODE PyPropInitsol (SCIP* scip, SCIP_PROP* prop) with gil: -+cdef SCIP_RETCODE PyPropInitsol (SCIP* scip, SCIP_PROP* prop) noexcept with gil: - cdef SCIP_PROPDATA* propdata - propdata = SCIPpropGetData(prop) - PyProp = propdata - PyProp.propinitsol() - return SCIP_OKAY - --cdef SCIP_RETCODE PyPropExitsol (SCIP* scip, SCIP_PROP* prop, SCIP_Bool restart) with gil: -+cdef SCIP_RETCODE PyPropExitsol (SCIP* scip, SCIP_PROP* prop, SCIP_Bool restart) noexcept with gil: - cdef SCIP_PROPDATA* propdata - propdata = SCIPpropGetData(prop) - PyProp = propdata -@@ -104,7 +104,7 @@ cdef SCIP_RETCODE PyPropPresol (SCIP* scip, SCIP_PROP* prop, int nrounds, SCIP_P - int nnewfixedvars, int nnewaggrvars, int nnewchgvartypes, int nnewchgbds, int nnewholes, - int nnewdelconss, int nnewaddconss, int nnewupgdconss, int nnewchgcoefs, int nnewchgsides, - int* nfixedvars, int* naggrvars, int* nchgvartypes, int* nchgbds, int* naddholes, -- int* ndelconss, int* naddconss, int* nupgdconss, int* nchgcoefs, int* nchgsides, SCIP_RESULT* result) with gil: -+ int* ndelconss, int* naddconss, int* nupgdconss, int* nchgcoefs, int* nchgsides, SCIP_RESULT* result) noexcept with gil: - cdef SCIP_PROPDATA* propdata - propdata = SCIPpropGetData(prop) - PyProp = propdata -@@ -137,7 +137,7 @@ cdef SCIP_RETCODE PyPropPresol (SCIP* scip, SCIP_PROP* prop, int nrounds, SCIP_P - nchgsides[0] = result_dict["nchgsides"] - return SCIP_OKAY - --cdef SCIP_RETCODE PyPropExec (SCIP* scip, SCIP_PROP* prop, SCIP_PROPTIMING proptiming, SCIP_RESULT* result) with gil: -+cdef SCIP_RETCODE PyPropExec (SCIP* scip, SCIP_PROP* prop, SCIP_PROPTIMING proptiming, SCIP_RESULT* result) noexcept with gil: - cdef SCIP_PROPDATA* propdata - propdata = SCIPpropGetData(prop) - PyProp = propdata -@@ -147,7 +147,7 @@ cdef SCIP_RETCODE PyPropExec (SCIP* scip, SCIP_PROP* prop, SCIP_PROPTIMING propt - return SCIP_OKAY - - cdef SCIP_RETCODE PyPropResProp (SCIP* scip, SCIP_PROP* prop, SCIP_VAR* infervar, int inferinfo, -- SCIP_BOUNDTYPE boundtype, SCIP_BDCHGIDX* bdchgidx, SCIP_Real relaxedbd, SCIP_RESULT* result) with gil: -+ SCIP_BOUNDTYPE boundtype, SCIP_BDCHGIDX* bdchgidx, SCIP_Real relaxedbd, SCIP_RESULT* result) noexcept with gil: - cdef SCIP_PROPDATA* propdata - cdef SCIP_VAR* tmp - tmp = infervar -diff --git a/src/pyscipopt/reader.pxi b/src/pyscipopt/reader.pxi -index df0b3a288..2c45585d6 100644 ---- a/src/pyscipopt/reader.pxi -+++ b/src/pyscipopt/reader.pxi -@@ -18,10 +18,10 @@ cdef class Reader: - return {} - - --cdef SCIP_RETCODE PyReaderCopy (SCIP* scip, SCIP_READER* reader) with gil: -+cdef SCIP_RETCODE PyReaderCopy (SCIP* scip, SCIP_READER* reader) noexcept with gil: - return SCIP_OKAY - --cdef SCIP_RETCODE PyReaderFree (SCIP* scip, SCIP_READER* reader) with gil: -+cdef SCIP_RETCODE PyReaderFree (SCIP* scip, SCIP_READER* reader) noexcept with gil: - cdef SCIP_READERDATA* readerdata - readerdata = SCIPreaderGetData(reader) - PyReader = readerdata -@@ -29,7 +29,7 @@ cdef SCIP_RETCODE PyReaderFree (SCIP* scip, SCIP_READER* reader) with gil: - Py_DECREF(PyReader) - return SCIP_OKAY - --cdef SCIP_RETCODE PyReaderRead (SCIP* scip, SCIP_READER* reader, const char* filename, SCIP_RESULT* result) with gil: -+cdef SCIP_RETCODE PyReaderRead (SCIP* scip, SCIP_READER* reader, const char* filename, SCIP_RESULT* result) noexcept with gil: - cdef SCIP_READERDATA* readerdata - readerdata = SCIPreaderGetData(reader) - PyReader = readerdata -@@ -44,7 +44,7 @@ cdef SCIP_RETCODE PyReaderWrite (SCIP* scip, SCIP_READER* reader, FILE* file, - SCIP_VAR** vars, int nvars, int nbinvars, int nintvars, int nimplvars, int ncontvars, - SCIP_VAR** fixedvars, int nfixedvars, int startnvars, - SCIP_CONS** conss, int nconss, int maxnconss, int startnconss, -- SCIP_Bool genericnames, SCIP_RESULT* result) with gil: -+ SCIP_Bool genericnames, SCIP_RESULT* result) noexcept with gil: - cdef SCIP_READERDATA* readerdata - readerdata = SCIPreaderGetData(reader) - cdef int fd = fileno(file) -diff --git a/src/pyscipopt/relax.pxi b/src/pyscipopt/relax.pxi -index 2b52c2643..81695e8bb 100644 ---- a/src/pyscipopt/relax.pxi -+++ b/src/pyscipopt/relax.pxi -@@ -30,10 +30,10 @@ cdef class Relax: - return{} - - --cdef SCIP_RETCODE PyRelaxCopy (SCIP* scip, SCIP_RELAX* relax) with gil: -+cdef SCIP_RETCODE PyRelaxCopy (SCIP* scip, SCIP_RELAX* relax) noexcept with gil: - return SCIP_OKAY - --cdef SCIP_RETCODE PyRelaxFree (SCIP* scip, SCIP_RELAX* relax) with gil: -+cdef SCIP_RETCODE PyRelaxFree (SCIP* scip, SCIP_RELAX* relax) noexcept with gil: - cdef SCIP_RELAXDATA* relaxdata - relaxdata = SCIPrelaxGetData(relax) - PyRelax = relaxdata -@@ -41,35 +41,35 @@ cdef SCIP_RETCODE PyRelaxFree (SCIP* scip, SCIP_RELAX* relax) with gil: - Py_DECREF(PyRelax) - return SCIP_OKAY - --cdef SCIP_RETCODE PyRelaxInit (SCIP* scip, SCIP_RELAX* relax) with gil: -+cdef SCIP_RETCODE PyRelaxInit (SCIP* scip, SCIP_RELAX* relax) noexcept with gil: - cdef SCIP_RELAXDATA* relaxdata - relaxdata = SCIPrelaxGetData(relax) - PyRelax = relaxdata - PyRelax.relaxinit() - return SCIP_OKAY - --cdef SCIP_RETCODE PyRelaxExit (SCIP* scip, SCIP_RELAX* relax) with gil: -+cdef SCIP_RETCODE PyRelaxExit (SCIP* scip, SCIP_RELAX* relax) noexcept with gil: - cdef SCIP_RELAXDATA* relaxdata - relaxdata = SCIPrelaxGetData(relax) - PyRelax = relaxdata - PyRelax.relaxexit() - return SCIP_OKAY - --cdef SCIP_RETCODE PyRelaxInitsol (SCIP* scip, SCIP_RELAX* relax) with gil: -+cdef SCIP_RETCODE PyRelaxInitsol (SCIP* scip, SCIP_RELAX* relax) noexcept with gil: - cdef SCIP_RELAXDATA* relaxdata - relaxdata = SCIPrelaxGetData(relax) - PyRelax = relaxdata - PyRelax.relaxinitsol() - return SCIP_OKAY - --cdef SCIP_RETCODE PyRelaxExitsol (SCIP* scip, SCIP_RELAX* relax) with gil: -+cdef SCIP_RETCODE PyRelaxExitsol (SCIP* scip, SCIP_RELAX* relax) noexcept with gil: - cdef SCIP_RELAXDATA* relaxdata - relaxdata = SCIPrelaxGetData(relax) - PyRelax = relaxdata - PyRelax.relaxexitsol() - return SCIP_OKAY - --cdef SCIP_RETCODE PyRelaxExec (SCIP* scip, SCIP_RELAX* relax, SCIP_Real* lowerbound, SCIP_RESULT* result) with gil: -+cdef SCIP_RETCODE PyRelaxExec (SCIP* scip, SCIP_RELAX* relax, SCIP_Real* lowerbound, SCIP_RESULT* result) noexcept with gil: - cdef SCIP_RELAXDATA* relaxdata - relaxdata = SCIPrelaxGetData(relax) - PyRelax = relaxdata -diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd -index 12815dbc4..f35a42486 100644 ---- a/src/pyscipopt/scip.pxd -+++ b/src/pyscipopt/scip.pxd -@@ -501,7 +501,7 @@ cdef extern from "scip/scip.h": - ctypedef union SCIP_DOMCHG: - pass - -- ctypedef void (*messagecallback) (SCIP_MESSAGEHDLR *messagehdlr, FILE *file, const char *msg) -+ ctypedef void (*messagecallback) (SCIP_MESSAGEHDLR *messagehdlr, FILE *file, const char *msg) noexcept - ctypedef void (*errormessagecallback) (void *data, FILE *file, const char *msg) - ctypedef SCIP_RETCODE (*messagehdlrfree) (SCIP_MESSAGEHDLR *messagehdlr) - -diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi -index 0b2332d88..552197785 100644 ---- a/src/pyscipopt/scip.pxi -+++ b/src/pyscipopt/scip.pxi -@@ -975,10 +975,10 @@ cdef class Constraint: - and self.scip_cons == (other).scip_cons) - - --cdef void relayMessage(SCIP_MESSAGEHDLR *messagehdlr, FILE *file, const char *msg): -+cdef void relayMessage(SCIP_MESSAGEHDLR *messagehdlr, FILE *file, const char *msg) noexcept: - sys.stdout.write(msg.decode('UTF-8')) - --cdef void relayErrorMessage(void *messagehdlr, FILE *file, const char *msg): -+cdef void relayErrorMessage(void *messagehdlr, FILE *file, const char *msg) noexcept: - sys.stderr.write(msg.decode('UTF-8')) - - # - remove create(), includeDefaultPlugins(), createProbBasic() methods -diff --git a/src/pyscipopt/sepa.pxi b/src/pyscipopt/sepa.pxi -index 271945db1..94355a7d2 100644 ---- a/src/pyscipopt/sepa.pxi -+++ b/src/pyscipopt/sepa.pxi -@@ -34,10 +34,10 @@ cdef class Sepa: - - - --cdef SCIP_RETCODE PySepaCopy (SCIP* scip, SCIP_SEPA* sepa) with gil: -+cdef SCIP_RETCODE PySepaCopy (SCIP* scip, SCIP_SEPA* sepa) noexcept with gil: - return SCIP_OKAY - --cdef SCIP_RETCODE PySepaFree (SCIP* scip, SCIP_SEPA* sepa) with gil: -+cdef SCIP_RETCODE PySepaFree (SCIP* scip, SCIP_SEPA* sepa) noexcept with gil: - cdef SCIP_SEPADATA* sepadata - sepadata = SCIPsepaGetData(sepa) - PySepa = sepadata -@@ -45,35 +45,35 @@ cdef SCIP_RETCODE PySepaFree (SCIP* scip, SCIP_SEPA* sepa) with gil: - Py_DECREF(PySepa) - return SCIP_OKAY - --cdef SCIP_RETCODE PySepaInit (SCIP* scip, SCIP_SEPA* sepa) with gil: -+cdef SCIP_RETCODE PySepaInit (SCIP* scip, SCIP_SEPA* sepa) noexcept with gil: - cdef SCIP_SEPADATA* sepadata - sepadata = SCIPsepaGetData(sepa) - PySepa = sepadata - PySepa.sepainit() - return SCIP_OKAY - --cdef SCIP_RETCODE PySepaExit (SCIP* scip, SCIP_SEPA* sepa) with gil: -+cdef SCIP_RETCODE PySepaExit (SCIP* scip, SCIP_SEPA* sepa) noexcept with gil: - cdef SCIP_SEPADATA* sepadata - sepadata = SCIPsepaGetData(sepa) - PySepa = sepadata - PySepa.sepaexit() - return SCIP_OKAY - --cdef SCIP_RETCODE PySepaInitsol (SCIP* scip, SCIP_SEPA* sepa) with gil: -+cdef SCIP_RETCODE PySepaInitsol (SCIP* scip, SCIP_SEPA* sepa) noexcept with gil: - cdef SCIP_SEPADATA* sepadata - sepadata = SCIPsepaGetData(sepa) - PySepa = sepadata - PySepa.sepainitsol() - return SCIP_OKAY - --cdef SCIP_RETCODE PySepaExitsol (SCIP* scip, SCIP_SEPA* sepa) with gil: -+cdef SCIP_RETCODE PySepaExitsol (SCIP* scip, SCIP_SEPA* sepa) noexcept with gil: - cdef SCIP_SEPADATA* sepadata - sepadata = SCIPsepaGetData(sepa) - PySepa = sepadata - PySepa.sepaexitsol() - return SCIP_OKAY - --cdef SCIP_RETCODE PySepaExeclp (SCIP* scip, SCIP_SEPA* sepa, SCIP_RESULT* result, unsigned int allowlocal, int depth) with gil: -+cdef SCIP_RETCODE PySepaExeclp (SCIP* scip, SCIP_SEPA* sepa, SCIP_RESULT* result, unsigned int allowlocal, int depth) noexcept with gil: - cdef SCIP_SEPADATA* sepadata - sepadata = SCIPsepaGetData(sepa) - PySepa = sepadata -@@ -81,7 +81,7 @@ cdef SCIP_RETCODE PySepaExeclp (SCIP* scip, SCIP_SEPA* sepa, SCIP_RESULT* result - result[0] = result_dict.get("result", result[0]) - return SCIP_OKAY - --cdef SCIP_RETCODE PySepaExecsol (SCIP* scip, SCIP_SEPA* sepa, SCIP_SOL* sol, SCIP_RESULT* result, unsigned int allowlocal, int depth) with gil: -+cdef SCIP_RETCODE PySepaExecsol (SCIP* scip, SCIP_SEPA* sepa, SCIP_SOL* sol, SCIP_RESULT* result, unsigned int allowlocal, int depth) noexcept with gil: - cdef SCIP_SEPADATA* sepadata - sepadata = SCIPsepaGetData(sepa) - solution = Solution.create(scip, sol) diff --git a/build/pkgs/python3/dependencies b/build/pkgs/python3/dependencies index 9bdcf05edc4..c00db7fa1e0 100644 --- a/build/pkgs/python3/dependencies +++ b/build/pkgs/python3/dependencies @@ -1,4 +1,4 @@ -zlib readline sqlite libpng bzip2 liblzma xz libffi openssl +zlib readline sqlite libpng bzip2 liblzma libffi openssl | xz ---------- All lines of this file are ignored except the first. diff --git a/build/pkgs/python3/distros/fedora.txt b/build/pkgs/python3/distros/fedora.txt index ba7b5ea9b21..ab50f2d9b07 100644 --- a/build/pkgs/python3/distros/fedora.txt +++ b/build/pkgs/python3/distros/fedora.txt @@ -1,2 +1,2 @@ python3-devel -python-setuptools +python3-setuptools diff --git a/build/pkgs/python_build/checksums.ini b/build/pkgs/python_build/checksums.ini new file mode 100644 index 00000000000..df10f8d3196 --- /dev/null +++ b/build/pkgs/python_build/checksums.ini @@ -0,0 +1,5 @@ +tarball=build-VERSION-py3-none-any.whl +sha1=950bf228726af5041adbe2bb04a7ca74e27bce60 +md5=6f34942a01c3307b42556ee7628f3d1d +cksum=4005002353 +upstream_url=https://pypi.io/packages/py3/b/build/build-VERSION-py3-none-any.whl diff --git a/build/pkgs/python_build/dependencies b/build/pkgs/python_build/dependencies index b72a6d1c776..4d5609413be 100644 --- a/build/pkgs/python_build/dependencies +++ b/build/pkgs/python_build/dependencies @@ -1,4 +1,4 @@ - pyparsing tomli packaging | $(PYTHON_TOOLCHAIN) $(PYTHON) +tomli packaging pyproject_hooks importlib_metadata | pip $(PYTHON) ---------- All lines of this file are ignored except the first. diff --git a/build/pkgs/python_build/package-version.txt b/build/pkgs/python_build/package-version.txt new file mode 100644 index 00000000000..6085e946503 --- /dev/null +++ b/build/pkgs/python_build/package-version.txt @@ -0,0 +1 @@ +1.2.1 diff --git a/build/pkgs/python_build/type b/build/pkgs/python_build/type index 134d9bc32d5..a6a7b9cd726 100644 --- a/build/pkgs/python_build/type +++ b/build/pkgs/python_build/type @@ -1 +1 @@ -optional +standard diff --git a/build/pkgs/python_build/requirements.txt b/build/pkgs/python_build/version_requirements.txt similarity index 100% rename from build/pkgs/python_build/requirements.txt rename to build/pkgs/python_build/version_requirements.txt diff --git a/build/pkgs/sage_conf/version_requirements.txt b/build/pkgs/sage_conf/version_requirements.txt index b2352d17cd7..8a94176716b 100644 --- a/build/pkgs/sage_conf/version_requirements.txt +++ b/build/pkgs/sage_conf/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-conf ~= 10.4b2 +sage-conf ~= 10.4b5 diff --git a/build/pkgs/sage_docbuild/version_requirements.txt b/build/pkgs/sage_docbuild/version_requirements.txt index 5fe49b28bc6..066d0bbc7d8 100644 --- a/build/pkgs/sage_docbuild/version_requirements.txt +++ b/build/pkgs/sage_docbuild/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-docbuild ~= 10.4b2 +sage-docbuild ~= 10.4b5 diff --git a/build/pkgs/sage_setup/version_requirements.txt b/build/pkgs/sage_setup/version_requirements.txt index ebca92ad49d..743b8a3afb1 100644 --- a/build/pkgs/sage_setup/version_requirements.txt +++ b/build/pkgs/sage_setup/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-setup ~= 10.4b2 +sage-setup ~= 10.4b5 diff --git a/build/pkgs/sage_sws2rst/version_requirements.txt b/build/pkgs/sage_sws2rst/version_requirements.txt index 9956ef5ccfb..191508c073f 100644 --- a/build/pkgs/sage_sws2rst/version_requirements.txt +++ b/build/pkgs/sage_sws2rst/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-sws2rst ~= 10.4b2 +sage-sws2rst ~= 10.4b5 diff --git a/build/pkgs/sagelib/version_requirements.txt b/build/pkgs/sagelib/version_requirements.txt index 4d5da1cfa32..8e5e0643152 100644 --- a/build/pkgs/sagelib/version_requirements.txt +++ b/build/pkgs/sagelib/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-standard ~= 10.4b2 +sagemath-standard ~= 10.4b5 diff --git a/build/pkgs/sagemath_bliss/version_requirements.txt b/build/pkgs/sagemath_bliss/version_requirements.txt index 0e6bc0c0cbd..aa21a391eb1 100644 --- a/build/pkgs/sagemath_bliss/version_requirements.txt +++ b/build/pkgs/sagemath_bliss/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-bliss ~= 10.4b2 +sagemath-bliss ~= 10.4b5 diff --git a/build/pkgs/sagemath_categories/version_requirements.txt b/build/pkgs/sagemath_categories/version_requirements.txt index 6e24614039e..59a93ac5c5c 100644 --- a/build/pkgs/sagemath_categories/version_requirements.txt +++ b/build/pkgs/sagemath_categories/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-categories ~= 10.4b2 +sagemath-categories ~= 10.4b5 diff --git a/build/pkgs/sagemath_coxeter3/version_requirements.txt b/build/pkgs/sagemath_coxeter3/version_requirements.txt index 3949915c19a..5bd13da7dc7 100644 --- a/build/pkgs/sagemath_coxeter3/version_requirements.txt +++ b/build/pkgs/sagemath_coxeter3/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-coxeter3 ~= 10.4b2 +sagemath-coxeter3 ~= 10.4b5 diff --git a/build/pkgs/sagemath_environment/version_requirements.txt b/build/pkgs/sagemath_environment/version_requirements.txt index e0afddbcc32..d608b96ded2 100644 --- a/build/pkgs/sagemath_environment/version_requirements.txt +++ b/build/pkgs/sagemath_environment/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-environment ~= 10.4b2 +sagemath-environment ~= 10.4b5 diff --git a/build/pkgs/sagemath_mcqd/version_requirements.txt b/build/pkgs/sagemath_mcqd/version_requirements.txt index 228e1ac3b7f..7701f84d6e6 100644 --- a/build/pkgs/sagemath_mcqd/version_requirements.txt +++ b/build/pkgs/sagemath_mcqd/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-mcqd ~= 10.4b2 +sagemath-mcqd ~= 10.4b5 diff --git a/build/pkgs/sagemath_meataxe/version_requirements.txt b/build/pkgs/sagemath_meataxe/version_requirements.txt index e4cbc453d92..c8a11e39149 100644 --- a/build/pkgs/sagemath_meataxe/version_requirements.txt +++ b/build/pkgs/sagemath_meataxe/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-meataxe ~= 10.4b2 +sagemath-meataxe ~= 10.4b5 diff --git a/build/pkgs/sagemath_objects/spkg-check b/build/pkgs/sagemath_objects/spkg-check index 87cea0fc38f..99a8528e0da 100755 --- a/build/pkgs/sagemath_objects/spkg-check +++ b/build/pkgs/sagemath_objects/spkg-check @@ -1,15 +1,45 @@ #!/usr/bin/env bash cd src +if [ ! -r tox.ini ]; then + echo "Not testing the package because there is no tox.ini" + exit 0 +fi + +for lib in "$SAGE_SRC/bin/sage-src-env-config" "$SAGE_SRC/bin/sage-env-config" "$SAGE_SRC/bin/sage-env" "$SAGE_ROOT/build/bin/sage-build-env-config" "$SAGE_ROOT/build/bin/sage-build-env"; do + source "$lib" + if [ $? -ne 0 ]; then + echo >&2 "Error: failed to source $lib" + echo >&2 "Is $SAGE_ROOT the correct SAGE_ROOT?" + exit 1 + fi +done + export PIP_NO_INDEX=true export PIP_FIND_LINKS="file://$SAGE_SPKG_WHEELS" -export TOX_PARALLEL_NO_SPINNER=1 +unset tox_args + wheel="$(sed -n '1s,.*@ file://,,p' $SAGE_SPKG_SCRIPTS/$PKG_BASE/spkg-requirements.txt)" -echo Running "tox -r -p auto -v --installpkg $wheel" -tox -r -p auto -v --installpkg "$wheel" +if [ -n "$wheel" ]; then + tox_envs=$(tox -l) + tox_args="-r -p auto -v --installpkg $wheel" +elif [ "$SAGE_EDITABLE" = yes ]; then + tox_envs=$(tox -l | sed s/norequirements/editable/) + # FIXME: Should use -r if sage_setup or another build requirement changes + tox_args="-r -v -v -v -v -e $(echo $tox_envs | sed 's/ /,/g')" +else + echo "Not testing the package because SAGE_WHEELS=$SAGE_WHEELS and SAGE_EDITABLE=$SAGE_EDITABLE" + exit 0 +fi + +export TOX_PARALLEL_NO_SPINNER=1 + +echo Running "tox $tox_args" +tox $tox_args status=$? case $status:$SAGE_CHECK:$([ -r known-test-failures.json ]; echo $?) in + 0:no:*) echo "Not testing the package because SAGE_CHECK=no";; 0:*:0) echo "Passed the test suite (modulo baseline known-test-failures*.json)";; 0:*:*) echo "Passed the test suite";; *:warn:0) echo "Warning: New failures (not in baseline known-test-failures*.json (ignored)"; status=0;; @@ -18,10 +48,12 @@ case $status:$SAGE_CHECK:$([ -r known-test-failures.json ]; echo $?) in *:yes:*) echo "Failures testing the package";; esac # Show summaries of failures (suppress lines ending with '[failed in baseline]') -for f in $(pwd)/.tox/sagepython-sagewheels-nopypi-norequirements*/log/*-command*.log; do - if [ -r "$f" ]; then - echo "$f" - grep '^sage -t.*#[^]]*$' "$f" - fi +for e in $tox_envs; do + for f in $(pwd)/.tox/$e/log/*-command*.log; do + if [ -r "$f" ]; then + echo "$f" + grep '^sage -t.*#[^]]*$' "$f" + fi + done done exit $status diff --git a/build/pkgs/sagemath_objects/version_requirements.txt b/build/pkgs/sagemath_objects/version_requirements.txt index 722c2a6bf92..542ae36504a 100644 --- a/build/pkgs/sagemath_objects/version_requirements.txt +++ b/build/pkgs/sagemath_objects/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-objects ~= 10.4b2 +sagemath-objects ~= 10.4b5 diff --git a/build/pkgs/sagemath_repl/version_requirements.txt b/build/pkgs/sagemath_repl/version_requirements.txt index 7f3c89386bf..6ea51fe6efe 100644 --- a/build/pkgs/sagemath_repl/version_requirements.txt +++ b/build/pkgs/sagemath_repl/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-repl ~= 10.4b2 +sagemath-repl ~= 10.4b5 diff --git a/build/pkgs/sagemath_sirocco/version_requirements.txt b/build/pkgs/sagemath_sirocco/version_requirements.txt index 757e0e89c19..ca0ac089f26 100644 --- a/build/pkgs/sagemath_sirocco/version_requirements.txt +++ b/build/pkgs/sagemath_sirocco/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-sirocco ~= 10.4b2 +sagemath-sirocco ~= 10.4b5 diff --git a/build/pkgs/sagemath_tdlib/version_requirements.txt b/build/pkgs/sagemath_tdlib/version_requirements.txt index 4445572b5fd..a99c2c26a9d 100644 --- a/build/pkgs/sagemath_tdlib/version_requirements.txt +++ b/build/pkgs/sagemath_tdlib/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-tdlib ~= 10.4b2 +sagemath-tdlib ~= 10.4b5 diff --git a/build/pkgs/sbcl/SPKG.rst b/build/pkgs/sbcl/SPKG.rst new file mode 100644 index 00000000000..e3a383239c7 --- /dev/null +++ b/build/pkgs/sbcl/SPKG.rst @@ -0,0 +1,24 @@ +sbcl: a lisp compiler and runtime system +===================================================================== + +Description +----------- + +Steel Bank Common Lisp (SBCL) is a high performance Common Lisp compiler. It is +open source / free software, with a permissive license (see https://www.sbcl.org/history.html). +In addition to the compiler and runtime system for ANSI Common Lisp, it provides an interactive +environment including a debugger, a statistical profiler, a code coverage tool, +and many other extensions. + +(taken from https://www.sbcl.org) + +License +------- + +- a mix of BSD-style and public domain + + +Upstream Contact +---------------- + +- https://www.sbcl.org diff --git a/build/pkgs/sbcl/distros/alpine.txt b/build/pkgs/sbcl/distros/alpine.txt new file mode 100644 index 00000000000..0e94cb07231 --- /dev/null +++ b/build/pkgs/sbcl/distros/alpine.txt @@ -0,0 +1 @@ +sbcl diff --git a/build/pkgs/sbcl/distros/arch.txt b/build/pkgs/sbcl/distros/arch.txt new file mode 100644 index 00000000000..0e94cb07231 --- /dev/null +++ b/build/pkgs/sbcl/distros/arch.txt @@ -0,0 +1 @@ +sbcl diff --git a/build/pkgs/sbcl/distros/conda.txt b/build/pkgs/sbcl/distros/conda.txt new file mode 100644 index 00000000000..0e94cb07231 --- /dev/null +++ b/build/pkgs/sbcl/distros/conda.txt @@ -0,0 +1 @@ +sbcl diff --git a/build/pkgs/sbcl/distros/cygwin.txt b/build/pkgs/sbcl/distros/cygwin.txt new file mode 100644 index 00000000000..0e94cb07231 --- /dev/null +++ b/build/pkgs/sbcl/distros/cygwin.txt @@ -0,0 +1 @@ +sbcl diff --git a/build/pkgs/sbcl/distros/debian.txt b/build/pkgs/sbcl/distros/debian.txt new file mode 100644 index 00000000000..0e94cb07231 --- /dev/null +++ b/build/pkgs/sbcl/distros/debian.txt @@ -0,0 +1 @@ +sbcl diff --git a/build/pkgs/sbcl/distros/fedora.txt b/build/pkgs/sbcl/distros/fedora.txt new file mode 100644 index 00000000000..0e94cb07231 --- /dev/null +++ b/build/pkgs/sbcl/distros/fedora.txt @@ -0,0 +1 @@ +sbcl diff --git a/build/pkgs/sbcl/distros/freebsd.txt b/build/pkgs/sbcl/distros/freebsd.txt new file mode 100644 index 00000000000..51399eefa59 --- /dev/null +++ b/build/pkgs/sbcl/distros/freebsd.txt @@ -0,0 +1 @@ +lang/sbcl diff --git a/build/pkgs/sbcl/distros/gentoo.txt b/build/pkgs/sbcl/distros/gentoo.txt new file mode 100644 index 00000000000..e02e0542b58 --- /dev/null +++ b/build/pkgs/sbcl/distros/gentoo.txt @@ -0,0 +1 @@ +dev-lisp/sbcl diff --git a/build/pkgs/sbcl/distros/homebrew.txt b/build/pkgs/sbcl/distros/homebrew.txt new file mode 100644 index 00000000000..0e94cb07231 --- /dev/null +++ b/build/pkgs/sbcl/distros/homebrew.txt @@ -0,0 +1 @@ +sbcl diff --git a/build/pkgs/sbcl/distros/macports.txt b/build/pkgs/sbcl/distros/macports.txt new file mode 100644 index 00000000000..0e94cb07231 --- /dev/null +++ b/build/pkgs/sbcl/distros/macports.txt @@ -0,0 +1 @@ +sbcl diff --git a/build/pkgs/sbcl/distros/nix.txt b/build/pkgs/sbcl/distros/nix.txt new file mode 100644 index 00000000000..0e94cb07231 --- /dev/null +++ b/build/pkgs/sbcl/distros/nix.txt @@ -0,0 +1 @@ +sbcl diff --git a/build/pkgs/sbcl/distros/openbsd.txt b/build/pkgs/sbcl/distros/openbsd.txt new file mode 100644 index 00000000000..51399eefa59 --- /dev/null +++ b/build/pkgs/sbcl/distros/openbsd.txt @@ -0,0 +1 @@ +lang/sbcl diff --git a/build/pkgs/sbcl/distros/opensuse.txt b/build/pkgs/sbcl/distros/opensuse.txt new file mode 100644 index 00000000000..0e94cb07231 --- /dev/null +++ b/build/pkgs/sbcl/distros/opensuse.txt @@ -0,0 +1 @@ +sbcl diff --git a/build/pkgs/sbcl/distros/repology.txt b/build/pkgs/sbcl/distros/repology.txt new file mode 100644 index 00000000000..0e94cb07231 --- /dev/null +++ b/build/pkgs/sbcl/distros/repology.txt @@ -0,0 +1 @@ +sbcl diff --git a/build/pkgs/sbcl/distros/void.txt b/build/pkgs/sbcl/distros/void.txt new file mode 100644 index 00000000000..0e94cb07231 --- /dev/null +++ b/build/pkgs/sbcl/distros/void.txt @@ -0,0 +1 @@ +sbcl diff --git a/build/pkgs/gambit/math b/build/pkgs/sbcl/math similarity index 100% rename from build/pkgs/gambit/math rename to build/pkgs/sbcl/math diff --git a/build/pkgs/sbcl/spkg-configure.m4 b/build/pkgs/sbcl/spkg-configure.m4 new file mode 100644 index 00000000000..aef49ce770b --- /dev/null +++ b/build/pkgs/sbcl/spkg-configure.m4 @@ -0,0 +1,20 @@ +SAGE_SPKG_CONFIGURE([sbcl], [dnl + m4_pushdef([SAGE_SBCL_MINVER], ["1.4.16"]) + AC_CACHE_CHECK([for sbcl >= SAGE_SBCL_MINVER], [ac_cv_path_SBCL], [ + AC_PATH_PROGS_FEATURE_CHECK([SBCL], [sbcl], [ + sbcl_version=`$ac_path_SBCL --version 2>&1 \ + | $SED -n -e 's/SBCL *\([[0-9]]*\.[[0-9]]*\.[[0-9]]*\)/\1/p'` + AS_IF([test -n "$sbcl_version"], [ + AX_COMPARE_VERSION([$sbcl_version], [ge], [SAGE_SBCL_MINVER], [ + ac_cv_path_SBCL="$ac_path_SBCL" + ac_path_SBCL_found=: + ]) + ]) + ]) + ]) + AS_IF([test -z "$ac_cv_path_SBCL" ], [ + sage_spkg_install_sbcl=yes + AC_SUBST(SAGE_FRICAS_LISP, ecl)], [ + AC_SUBST(SAGE_FRICAS_LISP, "sbcl --dynamic-space-size 4Gb")]) + m4_popdef([SAGE_SBCL_MINVER]) +]) diff --git a/build/pkgs/sbcl/type b/build/pkgs/sbcl/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/sbcl/type @@ -0,0 +1 @@ +optional diff --git a/build/pkgs/scip/checksums.ini b/build/pkgs/scip/checksums.ini index 97b8c8a7084..9dce3926f9b 100644 --- a/build/pkgs/scip/checksums.ini +++ b/build/pkgs/scip/checksums.ini @@ -1,5 +1,5 @@ -tarball=scip-VERSION.tar.gz -sha1=2637767428e285b6ddda8c462f1cc31d66833d80 -md5=b657369986ecd9b2944206d11ecce2e4 -cksum=967379932 -upstream_url=https://github.com/scipopt/scip/archive/refs/tags/vVERSION.tar.gz +tarball=scip-${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_MICRO}.tar.gz +sha1=bb28aef4bad00a1ff2c7f4ee982961709d15b9f8 +md5=2fdbc40c98d380b12586bcef6819b9d7 +cksum=1262698285 +upstream_url=https://github.com/scipopt/scip/archive/refs/tags/v${VERSION_MAJOR}${VERSION_MINOR}${VERSION_MICRO}.tar.gz diff --git a/build/pkgs/scip/dependencies b/build/pkgs/scip/dependencies index 62836bef5a5..fb21a14a5dd 100644 --- a/build/pkgs/scip/dependencies +++ b/build/pkgs/scip/dependencies @@ -1,4 +1,4 @@ -$(MP_LIBRARY) readline soplex papilo zlib | cmake +$(MP_LIBRARY) readline soplex papilo zlib bliss | cmake ---------- All lines of this file are ignored except the first. diff --git a/build/pkgs/scip/package-version.txt b/build/pkgs/scip/package-version.txt index 227b54a0631..486ff8cd7ba 100644 --- a/build/pkgs/scip/package-version.txt +++ b/build/pkgs/scip/package-version.txt @@ -1 +1 @@ -802 +9.0.0.p0 diff --git a/build/pkgs/scip/patches/no_rpath.patch b/build/pkgs/scip/patches/0001-CMakeLists.txt-Remove-hardcoded-RPATH-settings.patch similarity index 52% rename from build/pkgs/scip/patches/no_rpath.patch rename to build/pkgs/scip/patches/0001-CMakeLists.txt-Remove-hardcoded-RPATH-settings.patch index 28b4220b6b3..ad9449c59cb 100644 --- a/build/pkgs/scip/patches/no_rpath.patch +++ b/build/pkgs/scip/patches/0001-CMakeLists.txt-Remove-hardcoded-RPATH-settings.patch @@ -1,24 +1,27 @@ -commit eaff18abb55c86e90d44583731550c874dc3c3e0 -Author: Matthias Koeppe -Date: Sun Nov 27 14:12:32 2022 -0800 +From 5a05bcd05b75ca8f6b2228b08f57ff71ba46329b Mon Sep 17 00:00:00 2001 +From: Matthias Koeppe +Date: Sun, 3 Mar 2024 18:15:12 -0800 +Subject: [PATCH] CMakeLists.txt: Remove hardcoded RPATH settings - CMakeLists.txt: Remove hardcoded RPATH settings +--- + src/CMakeLists.txt | 9 +-------- + 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt -index 8629ace18b..db2505d74b 100644 +index 5392127c5d..995d194aee 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt -@@ -3,8 +3,7 @@ - # +@@ -5,8 +5,7 @@ include(GNUInstallDirs) + function(setLibProperties targetname outputname) set_target_properties(${targetname} PROPERTIES - OUTPUT_NAME ${outputname} -- MACOSX_RPATH "${CMAKE_INSTALL_PREFIX}/lib") +- MACOSX_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") + OUTPUT_NAME ${outputname}) endfunction(setLibProperties) set(CMAKE_C_STANDARD 99) -@@ -1092,7 +1091,6 @@ add_dependencies(scip scip_update_githash) +@@ -1118,7 +1117,6 @@ add_dependencies(scip scip_update_githash) set_target_properties(libscip PROPERTIES VERSION ${SCIP_VERSION_MAJOR}.${SCIP_VERSION_MINOR}.${SCIP_VERSION_PATCH}.${SCIP_VERSION_SUB} SOVERSION ${SCIP_VERSION_MAJOR}.${SCIP_VERSION_MINOR} @@ -26,15 +29,18 @@ index 8629ace18b..db2505d74b 100644 CXX_VISIBILITY_PRESET hidden C_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1) -@@ -1102,11 +1100,6 @@ target_include_directories(scip PUBLIC +@@ -1128,11 +1126,6 @@ target_include_directories(scip BEFORE PUBLIC $ $) -# set the install rpath to the installed destination -set_target_properties(scip PROPERTIES -- INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib" +- INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}" - INSTALL_RPATH_USE_LINK_PATH TRUE) - # install the header files of scip - install(FILES ${lpiheaders} DESTINATION include/lpi) - install(FILES ${dijkstraheaders} DESTINATION include/dijkstra) + install(FILES ${lpiheaders} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/lpi) + install(FILES ${dijkstraheaders} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/dijkstra) +-- +2.42.0 + diff --git a/build/pkgs/scip/patches/struct_symmetry_header.patch b/build/pkgs/scip/patches/struct_symmetry_header.patch new file mode 100644 index 00000000000..39d9114a58e --- /dev/null +++ b/build/pkgs/scip/patches/struct_symmetry_header.patch @@ -0,0 +1,14 @@ +From https://github.com/scipopt/SCIP-SDP/issues/12#issuecomment-1969453709 + +diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt +index 5392127c5d..b66e86dec5 100644 +--- a/src/CMakeLists.txt ++++ b/src/CMakeLists.txt +@@ -991,6 +991,7 @@ set(scipheaders + set(symheaders + symmetry/build_sassy_graph.h + symmetry/compute_symmetry.h ++ symmetry/struct_symmetry.h + symmetry/type_symmetry.h + ) + diff --git a/build/pkgs/scip_sdp/checksums.ini b/build/pkgs/scip_sdp/checksums.ini index b0e38cf751d..94370bcab39 100644 --- a/build/pkgs/scip_sdp/checksums.ini +++ b/build/pkgs/scip_sdp/checksums.ini @@ -1,5 +1,5 @@ tarball=scipsdp-VERSION.tgz -sha1=dcfb090a95f79df8524bcc63d34d7ddc6692924e -md5=4f900c60456b3f08160ca494bec8e9f4 -cksum=2622380399 +sha1=d5482fd414fdcaa38d6d80111bbe8931aeef63ea +md5=0de7752ba6602e3432940eaaf79dfa9d +cksum=162799809 upstream_url=http://www.opt.tu-darmstadt.de/scipsdp/downloads/scipsdp-VERSION.tgz diff --git a/build/pkgs/scip_sdp/package-version.txt b/build/pkgs/scip_sdp/package-version.txt index ee74734aa22..80895903a15 100644 --- a/build/pkgs/scip_sdp/package-version.txt +++ b/build/pkgs/scip_sdp/package-version.txt @@ -1 +1 @@ -4.1.0 +4.3.0 diff --git a/build/pkgs/scip_sdp/patches/findlapack-09e79cf75146ca34c637f919d1f4527a63743300.patch b/build/pkgs/scip_sdp/patches/findlapack-09e79cf75146ca34c637f919d1f4527a63743300.patch deleted file mode 100644 index a2ec71a5e71..00000000000 --- a/build/pkgs/scip_sdp/patches/findlapack-09e79cf75146ca34c637f919d1f4527a63743300.patch +++ /dev/null @@ -1,55 +0,0 @@ -From 09e79cf75146ca34c637f919d1f4527a63743300 Mon Sep 17 00:00:00 2001 -From: Marc Pfetsch -Date: Fri, 9 Dec 2022 18:52:09 +0100 -Subject: [PATCH] use cmake FindLAPACK - ---- - CMakeLists.txt | 14 +++++--------- - src/CMakeLists.txt | 6 +++--- - 2 files changed, 8 insertions(+), 12 deletions(-) - -diff --git a/CMakeLists.txt b/CMakeLists.txt -index 15e4edc..d4f14f5 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -79,16 +79,12 @@ add_custom_target(scip_update_githash - COMMAND ${CMAKE_COMMAND} -DDST=${PROJECT_SOURCE_DIR}/scipsdpgithash.c - -P ${CMAKE_BINARY_DIR}/scip_update_githash.cmake) - -- - # find lapack and blas --find_library(LAPACK_LIBRARY lapack) --if(LAPACK_LIBRARY) -- message(STATUS "Found lapack library: " ${LAPACK_LIBRARY}) --endif() -- --find_library(BLAS_LIBRARY blas) --if(BLAS_LIBRARY) -- message(STATUS "Found blas library: " ${BLAS_LIBRARY}) -+find_package(LAPACK REQUIRED) -+if(LAPACK_FOUND) -+ message(STATUS "Found lapack library: " ${LAPACK_LIBRARIES}) -+else() -+ message(FATAL_ERROR "Lapack not found") - endif() - - # search the selected symmetry computation program -diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt -index 30c66b7..5e5d7d3 100644 ---- a/src/CMakeLists.txt -+++ b/src/CMakeLists.txt -@@ -125,11 +125,11 @@ setLibProperties(libscipsdp "scipsdp") - #put binary in bin directory - set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) - if(SHARED) -- target_link_libraries(libscipsdp PRIVATE ${ZIMPL_PIC_LIBRARIES} ${SDPS_PIC_LIBRARIES} ${SYM_LIBRARIES} ${LAPACK_LIBRARY} ${BLAS_LIBRARY} m) -+ target_link_libraries(libscipsdp PRIVATE ${ZIMPL_PIC_LIBRARIES} ${SDPS_PIC_LIBRARIES} ${SYM_LIBRARIES} ${LAPACK_LIBRARIES} m) - add_executable(scipsdp scipsdp/main.c ${scipsdpsources} ${objscipsdpsources} ${sdpisources} ${sym}) -- target_link_libraries(scipsdp ${ZLIB_LIBRARIES} ${Readline_LIBRARY} ${GMP_LIBRARIES} ${ZIMPL_LIBRARIES} ${SDPS_LIBRARIES} ${SCIP_LIBRARIES} ${SYM_LIBRARIES} ${LAPACK_LIBRARY} ${BLAS_LIBRARY} m) -+ target_link_libraries(scipsdp ${ZLIB_LIBRARIES} ${Readline_LIBRARY} ${GMP_LIBRARIES} ${ZIMPL_LIBRARIES} ${SDPS_LIBRARIES} ${SCIP_LIBRARIES} ${SYM_LIBRARIES} ${LAPACK_LIBRARIES} m) - else() -- target_link_libraries(libscipsdp PRIVATE ${ZIMPL_LIBRARIES} ${SDPS_LIBRARIES} ${SYM_LIBRARIES} ${LAPACK_LIBRARY} ${BLAS_LIBRARY} m) -+ target_link_libraries(libscipsdp PRIVATE ${ZIMPL_LIBRARIES} ${SDPS_LIBRARIES} ${SYM_LIBRARIES} ${LAPACK_LIBRARIES} m) - add_executable(scipsdp scipsdp/main.c ${scipsdpsources} ${objscipsdpsources} ${sdpisources} ${sym}) - target_link_libraries(scipsdp libscipsdp ${SCIP_LIBRARIES}) - endif() diff --git a/build/pkgs/scip_sdp/patches/macos_link.patch b/build/pkgs/scip_sdp/patches/macos_link.patch deleted file mode 100644 index 16f2da86d22..00000000000 --- a/build/pkgs/scip_sdp/patches/macos_link.patch +++ /dev/null @@ -1,19 +0,0 @@ -commit 09f4b596870a2774f7104e318d9812cd5c7ace4a -Author: Matthias Koeppe -Date: Wed Dec 7 14:59:49 2022 -0800 - - src/CMakeLists.txt: Link libscipsdp through with libscip - -diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt -index 5e5d7d3..ac3841e 100644 ---- a/src/CMakeLists.txt -+++ b/src/CMakeLists.txt -@@ -125,7 +125,7 @@ setLibProperties(libscipsdp "scipsdp") - #put binary in bin directory - set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) - if(SHARED) -- target_link_libraries(libscipsdp PRIVATE ${ZIMPL_PIC_LIBRARIES} ${SDPS_PIC_LIBRARIES} ${SYM_LIBRARIES} ${LAPACK_LIBRARIES} m) -+ target_link_libraries(libscipsdp PRIVATE ${ZIMPL_PIC_LIBRARIES} ${SDPS_PIC_LIBRARIES} ${SCIP_LIBRARIES} ${SYM_LIBRARIES} ${LAPACK_LIBRARIES} m) - add_executable(scipsdp scipsdp/main.c ${scipsdpsources} ${objscipsdpsources} ${sdpisources} ${sym}) - target_link_libraries(scipsdp ${ZLIB_LIBRARIES} ${Readline_LIBRARY} ${GMP_LIBRARIES} ${ZIMPL_LIBRARIES} ${SDPS_LIBRARIES} ${SCIP_LIBRARIES} ${SYM_LIBRARIES} ${LAPACK_LIBRARIES} m) - else() diff --git a/build/pkgs/scip_sdp/patches/no_rpath.patch b/build/pkgs/scip_sdp/patches/no_rpath.patch deleted file mode 100644 index a4509df7fc7..00000000000 --- a/build/pkgs/scip_sdp/patches/no_rpath.patch +++ /dev/null @@ -1,35 +0,0 @@ -commit b12db4b95295fec9b32131039a6ee4a746c6f832 -Author: Matthias Koeppe -Date: Wed Dec 7 15:03:48 2022 -0800 - - src/CMakeLists.txt: Remote hardcoded RPATH settings - -diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt -index f293115..64ac3d1 100644 ---- a/src/CMakeLists.txt -+++ b/src/CMakeLists.txt -@@ -5,8 +5,7 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}) - # - function(setLibProperties targetname outputname) - set_target_properties(${targetname} PROPERTIES -- OUTPUT_NAME ${outputname} -- MACOSX_RPATH "${CMAKE_INSTALL_PREFIX}/lib") -+ OUTPUT_NAME ${outputname}) - endfunction(setLibProperties) - - set(CMAKE_C_STANDARD 99) -@@ -141,13 +140,7 @@ target_compile_definitions(scipsdp PRIVATE EXTERN=extern) - - set_target_properties(libscipsdp PROPERTIES - VERSION ${SCIPSDP_VERSION_MAJOR}.${SCIPSDP_VERSION_MINOR}.${SCIPSDP_VERSION_PATCH} -- SOVERSION ${SCIPSDP_VERSION_MAJOR}.${SCIPSDP_VERSION_MINOR} -- INSTALL_RPATH_USE_LINK_PATH TRUE) -- --# set the install rpath to the installed destination --set_target_properties(scipsdp PROPERTIES -- INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib" -- INSTALL_RPATH_USE_LINK_PATH TRUE) -+ SOVERSION ${SCIPSDP_VERSION_MAJOR}.${SCIPSDP_VERSION_MINOR}) - - # install the header files of scip - install(FILES ${sdpiheaders} DESTINATION include/sdpi) diff --git a/build/pkgs/scip_sdp/patches/zz_another_blas_change.patch b/build/pkgs/scip_sdp/patches/zz_another_blas_change.patch deleted file mode 100644 index 132f13eab82..00000000000 --- a/build/pkgs/scip_sdp/patches/zz_another_blas_change.patch +++ /dev/null @@ -1,80 +0,0 @@ -commit acb468a1805055e9f34dc1057eea186335bc2e13 -Author: Matthias Koeppe -Date: Fri Dec 9 15:53:05 2022 -0800 - - cmake/Modules/Find{DSDP,MOSEK}.cmake: Do not hardcode blas/lapack lib names here - -commit 8c8f7f82a16af70e044526f2911a94429d65b588 -Author: Matthias Koeppe -Date: Fri Dec 9 19:09:51 2022 -0800 - - CMakeLists.txt, src/CMakeLists.txt: Explicitly link through with blas - - -diff --git a/cmake/Modules/FindDSDP.cmake b/cmake/Modules/FindDSDP.cmake -index 8f43ae8..376fbdb 100644 ---- a/cmake/Modules/FindDSDP.cmake -+++ b/cmake/Modules/FindDSDP.cmake -@@ -8,7 +8,7 @@ find_library(DSDP_LIBRARY - HINTS ${DSDP_DIR} $ENV{DSDP_DIR} - PATH_SUFFIXES lib) - --set(DSDP_LIBRARIES ${DSDP_LIBRARY} -llapack -lblas) -+set(DSDP_LIBRARIES ${DSDP_LIBRARY}) - - include(FindPackageHandleStandardArgs) - find_package_handle_standard_args(DSDP DEFAULT_MSG DSDP_INCLUDE_DIRS DSDP_LIBRARIES) -diff --git a/cmake/Modules/FindMOSEK.cmake b/cmake/Modules/FindMOSEK.cmake -index a8ee1d6..e3b7ab7 100644 ---- a/cmake/Modules/FindMOSEK.cmake -+++ b/cmake/Modules/FindMOSEK.cmake -@@ -15,10 +15,10 @@ find_library(IOMP5_LIBRARY - PATH_SUFFIXES bin) - - if(IOMPS_LIBRARY) -- set(MOSEK_LIBRARIES ${MOSEK_LIBRARY} ${IOMP5_LIBRARY} -llapack -lblas -pthread) -+ set(MOSEK_LIBRARIES ${MOSEK_LIBRARY} ${IOMP5_LIBRARY} -pthread) - else() - # if libiomps is not available, we skip it -- set(MOSEK_LIBRARIES ${MOSEK_LIBRARY} -llapack -lblas -pthread) -+ set(MOSEK_LIBRARIES ${MOSEK_LIBRARY} -pthread) - endif() - - include(FindPackageHandleStandardArgs) - -diff --git a/CMakeLists.txt b/CMakeLists.txt -index d4f14f5..fd2bdf9 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -86,6 +86,12 @@ if(LAPACK_FOUND) - else() - message(FATAL_ERROR "Lapack not found") - endif() -+find_package(BLAS REQUIRED) -+if(BLAS_FOUND) -+ message(STATUS "Found blas library: " ${BLAS_LIBRARIES}) -+else() -+ message(FATAL_ERROR "Blas not found") -+endif() - - # search the selected symmetry computation program - message(STATUS "Finding symmetry computation program \"${SYM}\"") -diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt -index 5e5d7d3..b3e2be9 100644 ---- a/src/CMakeLists.txt -+++ b/src/CMakeLists.txt -@@ -125,11 +125,11 @@ setLibProperties(libscipsdp "scipsdp") - #put binary in bin directory - set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) - if(SHARED) -- target_link_libraries(libscipsdp PRIVATE ${ZIMPL_PIC_LIBRARIES} ${SDPS_PIC_LIBRARIES} ${SCIP_LIBRARIES} ${SYM_LIBRARIES} ${LAPACK_LIBRARIES} m) -+ target_link_libraries(libscipsdp PRIVATE ${ZIMPL_PIC_LIBRARIES} ${SDPS_PIC_LIBRARIES} ${SCIP_LIBRARIES} ${SYM_LIBRARIES} ${LAPACK_LIBRARIES} ${BLAS_LIBRARIES} m) - add_executable(scipsdp scipsdp/main.c ${scipsdpsources} ${objscipsdpsources} ${sdpisources} ${sym}) -- target_link_libraries(scipsdp ${ZLIB_LIBRARIES} ${Readline_LIBRARY} ${GMP_LIBRARIES} ${ZIMPL_LIBRARIES} ${SDPS_LIBRARIES} ${SCIP_LIBRARIES} ${SYM_LIBRARIES} ${LAPACK_LIBRARIES} m) -+ target_link_libraries(scipsdp ${ZLIB_LIBRARIES} ${Readline_LIBRARY} ${GMP_LIBRARIES} ${ZIMPL_LIBRARIES} ${SDPS_LIBRARIES} ${SCIP_LIBRARIES} ${SYM_LIBRARIES} ${LAPACK_LIBRARIES} ${BLAS_LIBRARIES} m) - else() -- target_link_libraries(libscipsdp PRIVATE ${ZIMPL_LIBRARIES} ${SDPS_LIBRARIES} ${SYM_LIBRARIES} ${LAPACK_LIBRARIES} m) -+ target_link_libraries(libscipsdp PRIVATE ${ZIMPL_LIBRARIES} ${SDPS_LIBRARIES} ${SYM_LIBRARIES} ${LAPACK_LIBRARIES} ${BLAS_LIBRARIES} m) - add_executable(scipsdp scipsdp/main.c ${scipsdpsources} ${objscipsdpsources} ${sdpisources} ${sym}) - target_link_libraries(scipsdp libscipsdp ${SCIP_LIBRARIES}) - endif() diff --git a/build/pkgs/scip_sdp/spkg-check.in b/build/pkgs/scip_sdp/spkg-check.in index 8df53fc6a36..8ef3bb74f54 100644 --- a/build/pkgs/scip_sdp/spkg-check.in +++ b/build/pkgs/scip_sdp/spkg-check.in @@ -1,3 +1,6 @@ cd src cd build $MAKE test +if [ $? != 0 ]; then + ctest --rerun-failed --output-on-failure +fi diff --git a/build/pkgs/setuptools/checksums.ini b/build/pkgs/setuptools/checksums.ini index 1a88d2a7464..bcd55128882 100644 --- a/build/pkgs/setuptools/checksums.ini +++ b/build/pkgs/setuptools/checksums.ini @@ -1,5 +1,5 @@ tarball=setuptools-VERSION-py3-none-any.whl -sha1=4227225bb193e3a45542f45966caf777d4c913e8 -md5=f096ed836f4036a11aa277fa16dc09ff -cksum=263664173 +sha1=49841be6743b2d129d01d02d5fd339dd693c99dc +md5=1555b24e28b53f3342e557500dedf8f3 +cksum=3445997019 upstream_url=https://pypi.io/packages/py3/s/setuptools/setuptools-VERSION-py3-none-any.whl diff --git a/build/pkgs/setuptools/distros/fedora.txt b/build/pkgs/setuptools/distros/fedora.txt index e1ad17860cd..1c0901c0374 100644 --- a/build/pkgs/setuptools/distros/fedora.txt +++ b/build/pkgs/setuptools/distros/fedora.txt @@ -1 +1 @@ -python-setuptools +python3-setuptools diff --git a/build/pkgs/setuptools/package-version.txt b/build/pkgs/setuptools/package-version.txt index 2c021f541a8..2a93f495d25 100644 --- a/build/pkgs/setuptools/package-version.txt +++ b/build/pkgs/setuptools/package-version.txt @@ -1 +1 @@ -69.0.2 +69.5.1 diff --git a/build/pkgs/setuptools/version_requirements.txt b/build/pkgs/setuptools/version_requirements.txt deleted file mode 100644 index c12b5900873..00000000000 --- a/build/pkgs/setuptools/version_requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -# 68.1.0 Promote pyproject.toml's [tool.setuptools] out of beta. -# 68.1.1 Fix editable install finder handling of nested packages -setuptools >= 68.1.1 diff --git a/build/pkgs/soplex/checksums.ini b/build/pkgs/soplex/checksums.ini index 5689a553333..866f76b3ba9 100644 --- a/build/pkgs/soplex/checksums.ini +++ b/build/pkgs/soplex/checksums.ini @@ -1,5 +1,5 @@ tarball=soplex-VERSION.tar.gz -sha1=6777fa6e7fd02ea6805901dbf60d873b4c312b62 -md5=2865c3a95ee903307d4bd32b0c9594e7 -cksum=278250056 -upstream_url=https://github.com/scipopt/soplex/archive/refs/tags/release-VERSION.tar.gz +sha1=5d0e7fa41b45aa0877134a5b8e261d9608505636 +md5=28be7e9aa5579ccd62cbe492d874ca2a +cksum=232471152 +upstream_url=https://github.com/scipopt/soplex/archive/refs/tags/release-${VERSION_MAJOR}${VERSION_MINOR}${VERSION_MICRO}.tar.gz diff --git a/build/pkgs/soplex/package-version.txt b/build/pkgs/soplex/package-version.txt index 85c3d27e59d..66ce77b7ead 100644 --- a/build/pkgs/soplex/package-version.txt +++ b/build/pkgs/soplex/package-version.txt @@ -1 +1 @@ -602 +7.0.0 diff --git a/build/pkgs/soplex/patches/no_rpath.patch b/build/pkgs/soplex/patches/0001-CMakeLists.txt-src-CMakeLists.txt-Remove-hardcoded-R.patch similarity index 57% rename from build/pkgs/soplex/patches/no_rpath.patch rename to build/pkgs/soplex/patches/0001-CMakeLists.txt-src-CMakeLists.txt-Remove-hardcoded-R.patch index 7cad9b059f7..b875781ebc1 100644 --- a/build/pkgs/soplex/patches/no_rpath.patch +++ b/build/pkgs/soplex/patches/0001-CMakeLists.txt-src-CMakeLists.txt-Remove-hardcoded-R.patch @@ -1,14 +1,19 @@ -commit 0c2527842fe4eaed8d9e5107d6a6621b3d6a716f -Author: Matthias Koeppe -Date: Sat Nov 26 17:08:40 2022 -0800 +From d96dd5fbfb790ab961243ef2cb5f1d0137e1f8a5 Mon Sep 17 00:00:00 2001 +From: Matthias Koeppe +Date: Sun, 3 Mar 2024 18:09:53 -0800 +Subject: [PATCH] CMakeLists.txt, src/CMakeLists.txt: Remove hardcoded RPATH + settings - CMakeLists.txt, src/CMakeLists.txt: Remove hardcoded RPATH settings +--- + CMakeLists.txt | 3 --- + src/CMakeLists.txt | 6 +----- + 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt -index 4f8635fc..6f7a6e1b 100644 +index 25317fc0..310d29a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt -@@ -64,9 +64,6 @@ if(NOT CMAKE_BUILD_TYPE) +@@ -65,9 +65,6 @@ if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif() @@ -19,7 +24,7 @@ index 4f8635fc..6f7a6e1b 100644 set(CMAKE_CXX_STANDARD 14) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt -index 27d52f14..fd3705ed 100644 +index d259115d..ffdf0435 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,8 +3,7 @@ @@ -27,18 +32,21 @@ index 27d52f14..fd3705ed 100644 function(setLibProperties targetname outputname) set_target_properties(${targetname} PROPERTIES - OUTPUT_NAME ${outputname} -- MACOSX_RPATH "${CMAKE_INSTALL_PREFIX}/lib") +- MACOSX_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") + OUTPUT_NAME ${outputname}) endfunction(setLibProperties) - include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}) -@@ -200,9 +199,6 @@ endif() + include(GNUInstallDirs) +@@ -208,9 +207,6 @@ endif() add_executable(example EXCLUDE_FROM_ALL example.cpp) target_link_libraries(example libsoplex) -# set the install rpath to the installed destination --set_target_properties(soplex PROPERTIES INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") +-set_target_properties(soplex PROPERTIES INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") - # install the header files of soplex - install(FILES ${headers} ${PROJECT_BINARY_DIR}/soplex/config.h DESTINATION include/soplex) - install(FILES soplex.h soplex.hpp soplex_interface.h DESTINATION include) + install(FILES ${headers} ${PROJECT_BINARY_DIR}/soplex/config.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/soplex) + install(FILES soplex.h soplex.hpp soplex_interface.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) +-- +2.42.0 + diff --git a/build/pkgs/symmetrica/dependencies b/build/pkgs/symmetrica/dependencies index 606ceeaec80..eb5b66f34f2 100644 --- a/build/pkgs/symmetrica/dependencies +++ b/build/pkgs/symmetrica/dependencies @@ -1,4 +1,4 @@ -xz +| xz xz is needed for unpacking the tarball when sage-bootstrap-python is ancient diff --git a/build/pkgs/trove_classifiers/checksums.ini b/build/pkgs/trove_classifiers/checksums.ini index 7350f444667..8bd0eca4933 100644 --- a/build/pkgs/trove_classifiers/checksums.ini +++ b/build/pkgs/trove_classifiers/checksums.ini @@ -1,5 +1,5 @@ tarball=trove_classifiers-VERSION-py3-none-any.whl -sha1=c341abee77b5c87d913b86dc587e544553f0658c -md5=78e67f128f53b8417134429192810701 -cksum=3034057088 +sha1=36240d053d16400380aee01f0879785693008a96 +md5=02b3e7b2eb81c3656fa859a87482f120 +cksum=1500381935 upstream_url=https://pypi.io/packages/py3/t/trove_classifiers/trove_classifiers-VERSION-py3-none-any.whl diff --git a/build/pkgs/trove_classifiers/package-version.txt b/build/pkgs/trove_classifiers/package-version.txt index a33bd2f9968..c296ac66b65 100644 --- a/build/pkgs/trove_classifiers/package-version.txt +++ b/build/pkgs/trove_classifiers/package-version.txt @@ -1 +1 @@ -2023.11.29 +2024.4.10 diff --git a/build/pkgs/uri_template/dependencies b/build/pkgs/uri_template/dependencies index 47296a7bace..644ad35f773 100644 --- a/build/pkgs/uri_template/dependencies +++ b/build/pkgs/uri_template/dependencies @@ -1,4 +1,4 @@ - | $(PYTHON_TOOLCHAIN) $(PYTHON) + | pip $(PYTHON) ---------- All lines of this file are ignored except the first. diff --git a/build/pkgs/wheel/checksums.ini b/build/pkgs/wheel/checksums.ini index 4f2b8c5c534..6e28451ce44 100644 --- a/build/pkgs/wheel/checksums.ini +++ b/build/pkgs/wheel/checksums.ini @@ -1,5 +1,5 @@ tarball=wheel-VERSION-py3-none-any.whl -sha1=fcf4ad8d5d8216d661bc98eede0d9210cbc5b697 -md5=779d91395ceb12e15e3a585b30b53f9f -cksum=1421399426 +sha1=71a83a2237cb57ab45bdafed364564e36ca5dc95 +md5=e65b1197e1dfc6bbc8df362935f5943d +cksum=1664872683 upstream_url=https://pypi.io/packages/py3/w/wheel/wheel-VERSION-py3-none-any.whl diff --git a/build/pkgs/wheel/package-version.txt b/build/pkgs/wheel/package-version.txt index 787ffc30a81..8298bb08b2d 100644 --- a/build/pkgs/wheel/package-version.txt +++ b/build/pkgs/wheel/package-version.txt @@ -1 +1 @@ -0.42.0 +0.43.0 diff --git a/build/pkgs/wheel/version_requirements.txt b/build/pkgs/wheel/version_requirements.txt deleted file mode 100644 index 43f74ab0144..00000000000 --- a/build/pkgs/wheel/version_requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -# https://github.com/sagemath/sage/issues/31050 - version constraint for macOS Big Sur support -wheel >=0.36.2 diff --git a/configure.ac b/configure.ac index 96cb9926ff0..7816aca796e 100644 --- a/configure.ac +++ b/configure.ac @@ -418,7 +418,7 @@ AC_ARG_ENABLE([cvxopt], AC_ARG_ENABLE([notebook], AS_HELP_STRING([--disable-notebook], [disable build of the Jupyter notebook and related packages]), [ - for pkg in notebook nbconvert beautifulsoup4 sagenb_export nbformat nbclient terminado send2trash prometheus_client mistune pandocfilters bleach defusedxml jsonschema jupyter_jsmol argon2_cffi argon2_cffi_bindings webencodings tinycss2 ipympl soupsieve fastjsonschema anyio arrow async_lru fqdn isoduration json5 jsonpointer jsonschema_specifications jupyter_events jupyter_lsp jupyter_server jupyter_server_terminals jupyterlab jupyterlab_mathjax2 notebook_shim overrides python_json_logger pyyaml referencing rfc3339_validator rfc3986_validator sniffio types_python_dateutil uri_template webcolors websocket_client; do + for pkg in notebook nbconvert beautifulsoup4 sagenb_export nbformat nbclient terminado send2trash prometheus_client mistune pandocfilters bleach defusedxml jsonschema jupyter_jsmol argon2_cffi argon2_cffi_bindings webencodings tinycss2 ipympl soupsieve fastjsonschema anyio arrow async_lru fqdn isoduration json5 jsonpointer jsonschema_specifications jupyter_events jupyter_lsp jupyter_server jupyter_server_terminals jupyterlab jupyterlab_server jupyterlab_pygments jupyterlab_mathjax2 notebook_shim overrides python_json_logger pyyaml referencing rfc3339_validator rfc3986_validator sniffio types_python_dateutil uri_template webcolors websocket_client; do AS_VAR_SET([SAGE_ENABLE_$pkg], [$enableval]) done ]) @@ -426,7 +426,7 @@ AC_ARG_ENABLE([notebook], AC_ARG_ENABLE([r], AS_HELP_STRING([--disable-r], [disable build of the R package and related packages]), [ - for pkg in r rpy2 r_jupyter tzlocal pytz_deprecation_shim; do + for pkg in r rpy2 r_jupyter tzlocal pytz_deprecation_shim tzdata; do AS_VAR_SET([SAGE_ENABLE_$pkg], [$enableval]) done ]) diff --git a/m4/pyproject_toml_metadata.m4 b/m4/pyproject_toml_metadata.m4 index 139af7e43e4..0d9824b1f2e 100644 --- a/m4/pyproject_toml_metadata.m4 +++ b/m4/pyproject_toml_metadata.m4 @@ -13,6 +13,7 @@ classifiers = [ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Scientific/Engineering :: Mathematics", ] diff --git a/m4/setup_cfg_metadata.m4 b/m4/setup_cfg_metadata.m4 index 896ca37eb7f..fb202433fcd 100644 --- a/m4/setup_cfg_metadata.m4 +++ b/m4/setup_cfg_metadata.m4 @@ -16,5 +16,6 @@ classifiers = Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 Programming Language :: Python :: Implementation :: CPython Topic :: Scientific/Engineering :: Mathematics diff --git a/pkgs/sage-conf/README.rst b/pkgs/sage-conf/README.rst index 590e5ba1356..e86c483c089 100644 --- a/pkgs/sage-conf/README.rst +++ b/pkgs/sage-conf/README.rst @@ -87,7 +87,7 @@ sage_conf for conda The version of the distribution package in the directory `pkgs/sage-conf_conda `_ -is used in an experimental installation method of SageMath, where all packages +may be used in an installation method of SageMath, where all packages are provided by conda. This method is described in https://doc.sagemath.org/html/en/installation/conda.html#using-conda-to-provide-all-dependencies-for-the-sage-library-experimental diff --git a/pkgs/sage-conf/VERSION.txt b/pkgs/sage-conf/VERSION.txt index 770c88a80e6..ef206d78947 100644 --- a/pkgs/sage-conf/VERSION.txt +++ b/pkgs/sage-conf/VERSION.txt @@ -1 +1 @@ -10.4.beta2 +10.4.beta5 diff --git a/pkgs/sage-conf_conda/VERSION.txt b/pkgs/sage-conf_conda/VERSION.txt index 770c88a80e6..ef206d78947 100644 --- a/pkgs/sage-conf_conda/VERSION.txt +++ b/pkgs/sage-conf_conda/VERSION.txt @@ -1 +1 @@ -10.4.beta2 +10.4.beta5 diff --git a/pkgs/sage-conf_pypi/VERSION.txt b/pkgs/sage-conf_pypi/VERSION.txt index 770c88a80e6..ef206d78947 100644 --- a/pkgs/sage-conf_pypi/VERSION.txt +++ b/pkgs/sage-conf_pypi/VERSION.txt @@ -1 +1 @@ -10.4.beta2 +10.4.beta5 diff --git a/pkgs/sage-conf_pypi/tox.ini b/pkgs/sage-conf_pypi/tox.ini index fadf9a9cc6c..7160d4db678 100644 --- a/pkgs/sage-conf_pypi/tox.ini +++ b/pkgs/sage-conf_pypi/tox.ini @@ -1,6 +1,10 @@ [tox] envlist = py39, py310, py311, py39-user, py310-user, py311-user +requires = + # Because of https://github.com/tox-dev/tox/issues/3238, need <4.14.1 + tox<4.14.1 + [testenv:.pkg] basepython = py311 passenv = diff --git a/pkgs/sage-docbuild/VERSION.txt b/pkgs/sage-docbuild/VERSION.txt index 770c88a80e6..ef206d78947 100644 --- a/pkgs/sage-docbuild/VERSION.txt +++ b/pkgs/sage-docbuild/VERSION.txt @@ -1 +1 @@ -10.4.beta2 +10.4.beta5 diff --git a/pkgs/sage-docbuild/pyproject.toml b/pkgs/sage-docbuild/pyproject.toml index 77840653af0..87e7c6521f8 100644 --- a/pkgs/sage-docbuild/pyproject.toml +++ b/pkgs/sage-docbuild/pyproject.toml @@ -18,6 +18,7 @@ classifiers = [ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Scientific/Engineering :: Mathematics", ] diff --git a/pkgs/sage-docbuild/tox.ini b/pkgs/sage-docbuild/tox.ini index 77da5a78ede..efa222028ff 100644 --- a/pkgs/sage-docbuild/tox.ini +++ b/pkgs/sage-docbuild/tox.ini @@ -8,6 +8,10 @@ # [tox] +requires = + # Because of https://github.com/tox-dev/tox/issues/3238, need <4.14.1 + tox<4.14.1 + [testenv] deps = -rrequirements.txt diff --git a/pkgs/sage-setup/VERSION.txt b/pkgs/sage-setup/VERSION.txt index 770c88a80e6..ef206d78947 100644 --- a/pkgs/sage-setup/VERSION.txt +++ b/pkgs/sage-setup/VERSION.txt @@ -1 +1 @@ -10.4.beta2 +10.4.beta5 diff --git a/pkgs/sage-setup/pyproject.toml b/pkgs/sage-setup/pyproject.toml index 4a558643864..96b079cec62 100644 --- a/pkgs/sage-setup/pyproject.toml +++ b/pkgs/sage-setup/pyproject.toml @@ -18,6 +18,7 @@ classifiers = [ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Scientific/Engineering :: Mathematics", ] diff --git a/pkgs/sage-setup/tox.ini b/pkgs/sage-setup/tox.ini index fd935f2e978..e8a22f03101 100644 --- a/pkgs/sage-setup/tox.ini +++ b/pkgs/sage-setup/tox.ini @@ -12,6 +12,10 @@ # [tox] +requires = + # Because of https://github.com/tox-dev/tox/issues/3238, need <4.14.1 + tox<4.14.1 + [testenv] deps = -rrequirements.txt diff --git a/pkgs/sage-sws2rst/VERSION.txt b/pkgs/sage-sws2rst/VERSION.txt index 770c88a80e6..ef206d78947 100644 --- a/pkgs/sage-sws2rst/VERSION.txt +++ b/pkgs/sage-sws2rst/VERSION.txt @@ -1 +1 @@ -10.4.beta2 +10.4.beta5 diff --git a/pkgs/sagemath-bliss/VERSION.txt b/pkgs/sagemath-bliss/VERSION.txt index 770c88a80e6..ef206d78947 100644 --- a/pkgs/sagemath-bliss/VERSION.txt +++ b/pkgs/sagemath-bliss/VERSION.txt @@ -1 +1 @@ -10.4.beta2 +10.4.beta5 diff --git a/pkgs/sagemath-categories/VERSION.txt b/pkgs/sagemath-categories/VERSION.txt index 770c88a80e6..ef206d78947 100644 --- a/pkgs/sagemath-categories/VERSION.txt +++ b/pkgs/sagemath-categories/VERSION.txt @@ -1 +1 @@ -10.4.beta2 +10.4.beta5 diff --git a/pkgs/sagemath-categories/known-test-failures.json b/pkgs/sagemath-categories/known-test-failures.json index ddae185a7d9..ab27f65ca8d 100644 --- a/pkgs/sagemath-categories/known-test-failures.json +++ b/pkgs/sagemath-categories/known-test-failures.json @@ -26,7 +26,7 @@ "ntests": 28 }, "sage.categories.affine_weyl_groups": { - "ntests": 14 + "ntests": 15 }, "sage.categories.algebra_ideals": { "failed": true, @@ -38,7 +38,7 @@ }, "sage.categories.algebras": { "failed": true, - "ntests": 20 + "ntests": 25 }, "sage.categories.algebras_with_basis": { "failed": true, @@ -126,7 +126,7 @@ }, "sage.categories.commutative_rings": { "failed": true, - "ntests": 39 + "ntests": 41 }, "sage.categories.complete_discrete_valuation": { "failed": true, @@ -148,15 +148,19 @@ }, "sage.categories.coxeter_groups": { "failed": true, - "ntests": 362 + "ntests": 392 }, "sage.categories.cw_complexes": { "failed": true, "ntests": 36 }, + "sage.categories.dedekind_domains": { + "failed": true, + "ntests": 30 + }, "sage.categories.discrete_valuation": { "failed": true, - "ntests": 23 + "ntests": 28 }, "sage.categories.distributive_magmas_and_additive_magmas": { "failed": true, @@ -169,9 +173,6 @@ "failed": true, "ntests": 7 }, - "sage.categories.drinfeld_modules": { - "ntests": 2 - }, "sage.categories.dual": { "failed": true, "ntests": 1 @@ -297,18 +298,18 @@ }, "sage.categories.filtered_modules_with_basis": { "failed": true, - "ntests": 65 + "ntests": 74 }, "sage.categories.finite_complex_reflection_groups": { "failed": true, - "ntests": 178 + "ntests": 189 }, "sage.categories.finite_coxeter_groups": { "ntests": 6 }, "sage.categories.finite_dimensional_algebras_with_basis": { "failed": true, - "ntests": 87 + "ntests": 91 }, "sage.categories.finite_dimensional_bialgebras_with_basis": { "failed": true, @@ -328,11 +329,11 @@ }, "sage.categories.finite_dimensional_lie_algebras_with_basis": { "failed": true, - "ntests": 34 + "ntests": 73 }, "sage.categories.finite_dimensional_modules_with_basis": { "failed": true, - "ntests": 55 + "ntests": 89 }, "sage.categories.finite_dimensional_nilpotent_lie_algebras_with_basis": { "failed": true, @@ -493,10 +494,6 @@ "failed": true, "ntests": 13 }, - "sage.categories.inner_product_spaces": { - "failed": true, - "ntests": 9 - }, "sage.categories.integral_domains": { "failed": true, "ntests": 22 @@ -530,7 +527,7 @@ }, "sage.categories.lie_algebras": { "failed": true, - "ntests": 125 + "ntests": 135 }, "sage.categories.lie_algebras_with_basis": { "ntests": 19 @@ -586,7 +583,7 @@ }, "sage.categories.modules_with_basis": { "failed": true, - "ntests": 221 + "ntests": 240 }, "sage.categories.monoid_algebras": { "failed": true, @@ -598,7 +595,7 @@ }, "sage.categories.morphism": { "failed": true, - "ntests": 99 + "ntests": 126 }, "sage.categories.number_fields": { "failed": true, @@ -668,14 +665,14 @@ }, "sage.categories.rings": { "failed": true, - "ntests": 141 + "ntests": 146 }, "sage.categories.rngs": { "ntests": 6 }, "sage.categories.schemes": { "failed": true, - "ntests": 23 + "ntests": 41 }, "sage.categories.semigroups": { "failed": true, @@ -711,7 +708,7 @@ }, "sage.categories.simplicial_sets": { "failed": true, - "ntests": 57 + "ntests": 98 }, "sage.categories.subobjects": { "ntests": 2 @@ -765,7 +762,7 @@ }, "sage.categories.unital_algebras": { "failed": true, - "ntests": 39 + "ntests": 37 }, "sage.categories.vector_spaces": { "failed": true, @@ -783,7 +780,7 @@ }, "sage.cpython.debug": { "failed": true, - "ntests": 14 + "ntests": 13 }, "sage.cpython.dict_del_by_value": { "failed": true, @@ -808,30 +805,28 @@ }, "sage.doctest.control": { "failed": true, - "ntests": 0 + "ntests": 230 }, "sage.doctest.external": { "ntests": 42 }, "sage.doctest.fixtures": { - "failed": true, "ntests": 59 }, "sage.doctest.forker": { "failed": true, - "ntests": 433 + "ntests": 413 }, "sage.doctest.parsing": { "failed": true, - "ntests": 321 + "ntests": 313 }, "sage.doctest.reporting": { - "failed": true, - "ntests": 124 + "ntests": 115 }, "sage.doctest.sources": { "failed": true, - "ntests": 378 + "ntests": 343 }, "sage.doctest.test": { "failed": true, @@ -846,7 +841,7 @@ "ntests": 41 }, "sage.features": { - "ntests": 145 + "ntests": 143 }, "sage.features.all": { "ntests": 14 @@ -865,17 +860,23 @@ }, "sage.features.databases": { "failed": true, - "ntests": 26 + "ntests": 38 }, "sage.features.dvipng": { "ntests": 4 }, + "sage.features.ecm": { + "ntests": 4 + }, "sage.features.ffmpeg": { "ntests": 4 }, "sage.features.four_ti_2": { "ntests": 6 }, + "sage.features.fricas": { + "ntests": 6 + }, "sage.features.gap": { "ntests": 6 }, @@ -901,7 +902,11 @@ "sage.features.internet": { "ntests": 5 }, + "sage.features.jmol": { + "ntests": 4 + }, "sage.features.join_feature": { + "failed": true, "ntests": 25 }, "sage.features.kenzo": { @@ -960,7 +965,7 @@ }, "sage.features.sagemath": { "failed": true, - "ntests": 147 + "ntests": 151 }, "sage.features.singular": { "ntests": 4 @@ -968,9 +973,16 @@ "sage.features.sphinx": { "ntests": 4 }, + "sage.features.symengine_py": { + "ntests": 4 + }, "sage.features.tdlib": { "ntests": 2 }, + "sage.features.threejs": { + "failed": true, + "ntests": 6 + }, "sage.misc.abstract_method": { "failed": true, "ntests": 33 @@ -1001,7 +1013,7 @@ }, "sage.misc.decorators": { "failed": true, - "ntests": 126 + "ntests": 125 }, "sage.misc.fast_methods": { "failed": true, @@ -1059,7 +1071,7 @@ }, "sage.misc.package_dir": { "failed": true, - "ntests": 29 + "ntests": 35 }, "sage.misc.persist": { "failed": true, @@ -1091,7 +1103,7 @@ }, "sage.misc.sageinspect": { "failed": true, - "ntests": 329 + "ntests": 322 }, "sage.misc.superseded": { "failed": true, @@ -1157,7 +1169,7 @@ }, "sage.repl.interpreter": { "failed": true, - "ntests": 118 + "ntests": 114 }, "sage.repl.ipython_extension": { "failed": true, @@ -1165,13 +1177,14 @@ }, "sage.repl.ipython_kernel.install": { "failed": true, - "ntests": 40 + "ntests": 35 }, "sage.repl.ipython_kernel.interact": { "failed": true, "ntests": 42 }, "sage.repl.ipython_kernel.kernel": { + "failed": true, "ntests": 12 }, "sage.repl.ipython_kernel.widgets": { @@ -1188,7 +1201,7 @@ }, "sage.repl.load": { "failed": true, - "ntests": 42 + "ntests": 38 }, "sage.repl.preparse": { "failed": true, @@ -1206,7 +1219,7 @@ }, "sage.repl.rich_output.backend_ipython": { "failed": true, - "ntests": 78 + "ntests": 75 }, "sage.repl.rich_output.buffer": { "failed": true, @@ -1214,7 +1227,7 @@ }, "sage.repl.rich_output.display_manager": { "failed": true, - "ntests": 88 + "ntests": 86 }, "sage.repl.rich_output.output_basic": { "ntests": 47 @@ -1251,7 +1264,7 @@ }, "sage.rings.ring": { "failed": true, - "ntests": 337 + "ntests": 298 }, "sage.sets.pythonclass": { "failed": true, @@ -1263,7 +1276,7 @@ }, "sage.structure.coerce": { "failed": true, - "ntests": 314 + "ntests": 310 }, "sage.structure.coerce_actions": { "failed": true, @@ -1298,7 +1311,7 @@ }, "sage.structure.factorization": { "failed": true, - "ntests": 203 + "ntests": 200 }, "sage.structure.factorization_integer": { "failed": true, @@ -1315,9 +1328,6 @@ "sage.structure.global_options": { "ntests": 145 }, - "sage.structure.graphics_file": { - "ntests": 8 - }, "sage.structure.indexed_generators": { "failed": true, "ntests": 90 @@ -1345,7 +1355,7 @@ }, "sage.structure.parent": { "failed": true, - "ntests": 300 + "ntests": 303 }, "sage.structure.parent_gens": { "failed": true, diff --git a/pkgs/sagemath-categories/pyproject.toml.m4 b/pkgs/sagemath-categories/pyproject.toml.m4 index eed48a1db15..edbafa59242 100644 --- a/pkgs/sagemath-categories/pyproject.toml.m4 +++ b/pkgs/sagemath-categories/pyproject.toml.m4 @@ -37,3 +37,20 @@ include-package-data = false [tool.setuptools.dynamic] version = {file = ["VERSION.txt"]} + +[external] +# External dependencies in the format proposed by https://peps.python.org/pep-0725 +build-requires = [ + "virtual:compiler/c", + "virtual:compiler/cpp", + "pkg:generic/pkg-config", +] + +host-requires = [ + "pkg:generic/gmp", + "pkg:generic/mpc", + "pkg:generic/mpfr", +] + +dependencies = [ +] diff --git a/pkgs/sagemath-categories/requirements-editable.txt.m4 b/pkgs/sagemath-categories/requirements-editable.txt.m4 new file mode 100644 index 00000000000..1c0abbe40ae --- /dev/null +++ b/pkgs/sagemath-categories/requirements-editable.txt.m4 @@ -0,0 +1,7 @@ +include(`sage_spkg_versions.m4')dnl +dnl Same as setup.cfg.m4 install_requires; FIXME: should pin to built wheels. +SPKG_INSTALL_REQUIRES_gmpy2 +SPKG_INSTALL_REQUIRES_cysignals +SPKG_INSTALL_REQUIRES_memory_allocator +-e ../sagemath-environment +-e ../sagemath-objects diff --git a/pkgs/sagemath-categories/tox.ini b/pkgs/sagemath-categories/tox.ini index b4bb49d132a..841209e88f1 100644 --- a/pkgs/sagemath-categories/tox.ini +++ b/pkgs/sagemath-categories/tox.ini @@ -13,7 +13,9 @@ envlist = requires = # Auto-provision a modern tox. # [pkgenv] added in 4.2 - https://tox.wiki/en/latest/upgrading.html#packaging-configuration-and-inheritance + # Because of https://github.com/tox-dev/tox/issues/3238, need <4.14.1 tox>=4.2 + tox<4.14.1 [pkgenv] # Environment in which to build the sdist. @@ -49,6 +51,11 @@ passenv = {[pkgenv]passenv} setenv = {[pkgenv]setenv} # Sage scripts such as sage-runtests like to use $HOME/.sage HOME={envdir} + # Stop 'sage -t --installed' from picking up doc installed in SAGE_LOCAL + SAGE_DOC=/doesnotexist + KNOWN_TEST_FAILURES={toxinidir}/known-test-failures.json + # See src/bin/sage-env + PYDEVD_DISABLE_FILE_VALIDATION=1 allowlist_externals = bash @@ -61,7 +68,7 @@ commands = {envpython} -c 'import sys; "" in sys.path and sys.path.remove(""); from sage.categories.all import *; SimplicialComplexes(); FunctionFields()' bash -c 'cd $(python -c "import sys; \"\" in sys.path and sys.path.remove(\"\"); from sage.env import SAGE_LIB; print(SAGE_LIB)") \ - && sage-runtests -p --initial --environment=sage.all__sagemath_categories --probe all --baseline-stats-path={toxinidir}/known-test-failures.json --optional=sage --installed' + && sage-runtests -p --force-lib --initial --environment=sage.all__sagemath_categories --probe all --baseline-stats-path=$KNOWN_TEST_FAILURES --optional=sage --installed' [testenv:.tox] # Allow access to PyPI for auto-provisioning a suitable tox version @@ -84,6 +91,11 @@ setenv = {[pkgenv]setenv} basepython = {env:SAGE_VENV}/bin/python3 +[testenv:.pkg-sagepython-sagewheels-nopypi-editable] +config_settings_build_editable = + editable_mode = strict + + [testenv:sagepython] basepython = {env:SAGE_VENV}/bin/python3 package_env = .pkg-sagepython @@ -92,10 +104,6 @@ package_env = .pkg-sagepython basepython = {env:SAGE_VENV}/bin/python3 package_env = .pkg-sagepython-sagewheels-nopypi -[testenv:sagepython-sagewheels-nopypi-norequirements] -basepython = {env:SAGE_VENV}/bin/python3 -package_env = .pkg-sagepython-sagewheels-nopypi - [testenv:sagepython-sagewheels] basepython = {env:SAGE_VENV}/bin/python package_env = .pkg-sagepython @@ -103,3 +111,16 @@ package_env = .pkg-sagepython [testenv:sagepython-norequirements] basepython = {env:SAGE_VENV}/bin/python3 package_env = .pkg-sagepython + + +[testenv:sagepython-sagewheels-nopypi-norequirements] +basepython = {env:SAGE_VENV}/bin/python3 +package_env = .pkg-sagepython-sagewheels-nopypi + +[testenv:sagepython-sagewheels-nopypi-editable] +basepython = {env:SAGE_VENV}/bin/python3 +package_env = .pkg-sagepython-sagewheels-nopypi-editable +package = editable +deps = -r requirements-editable.txt +config_settings_build_editable = + editable_mode = strict diff --git a/pkgs/sagemath-coxeter3/VERSION.txt b/pkgs/sagemath-coxeter3/VERSION.txt index 770c88a80e6..ef206d78947 100644 --- a/pkgs/sagemath-coxeter3/VERSION.txt +++ b/pkgs/sagemath-coxeter3/VERSION.txt @@ -1 +1 @@ -10.4.beta2 +10.4.beta5 diff --git a/pkgs/sagemath-environment/VERSION.txt b/pkgs/sagemath-environment/VERSION.txt index 770c88a80e6..ef206d78947 100644 --- a/pkgs/sagemath-environment/VERSION.txt +++ b/pkgs/sagemath-environment/VERSION.txt @@ -1 +1 @@ -10.4.beta2 +10.4.beta5 diff --git a/pkgs/sagemath-environment/requirements-editable.txt.m4 b/pkgs/sagemath-environment/requirements-editable.txt.m4 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/pkgs/sagemath-environment/tox.ini b/pkgs/sagemath-environment/tox.ini index 6bf1f2a6ebb..5d741bd0b48 100644 --- a/pkgs/sagemath-environment/tox.ini +++ b/pkgs/sagemath-environment/tox.ini @@ -13,7 +13,9 @@ envlist = requires = # Auto-provision a modern tox. # [pkgenv] added in 4.2 - https://tox.wiki/en/latest/upgrading.html#packaging-configuration-and-inheritance + # Because of https://github.com/tox-dev/tox/issues/3238, need <4.14.1 tox>=4.2 + tox<4.14.1 [pkgenv] # Environment in which to build the sdist. @@ -49,6 +51,9 @@ passenv = {[pkgenv]passenv} setenv = {[pkgenv]setenv} # Sage scripts such as sage-runtests like to use $HOME/.sage HOME={envdir} + # Stop 'sage -t --installed' from picking up doc installed in SAGE_LOCAL + SAGE_DOC=/doesnotexist + KNOWN_TEST_FAILURES={toxinidir}/known-test-failures.json allowlist_externals = bash @@ -78,6 +83,11 @@ setenv = {[pkgenv]setenv} basepython = {env:SAGE_VENV}/bin/python3 +[testenv:.pkg-sagepython-sagewheels-nopypi-editable] +config_settings_build_editable = + editable_mode = strict + + [testenv:sagepython] basepython = {env:SAGE_VENV}/bin/python3 package_env = .pkg-sagepython @@ -98,3 +108,11 @@ package_env = .pkg-sagepython [testenv:sagepython-sagewheels-nopypi-norequirements] basepython = {env:SAGE_VENV}/bin/python3 package_env = .pkg-sagepython-sagewheels-nopypi + +[testenv:sagepython-sagewheels-nopypi-editable] +basepython = {env:SAGE_VENV}/bin/python3 +package_env = .pkg-sagepython-sagewheels-nopypi-editable +package = editable +deps = -r requirements-editable.txt +config_settings_build_editable = + editable_mode = strict diff --git a/pkgs/sagemath-mcqd/VERSION.txt b/pkgs/sagemath-mcqd/VERSION.txt index 770c88a80e6..ef206d78947 100644 --- a/pkgs/sagemath-mcqd/VERSION.txt +++ b/pkgs/sagemath-mcqd/VERSION.txt @@ -1 +1 @@ -10.4.beta2 +10.4.beta5 diff --git a/pkgs/sagemath-meataxe/VERSION.txt b/pkgs/sagemath-meataxe/VERSION.txt index 770c88a80e6..ef206d78947 100644 --- a/pkgs/sagemath-meataxe/VERSION.txt +++ b/pkgs/sagemath-meataxe/VERSION.txt @@ -1 +1 @@ -10.4.beta2 +10.4.beta5 diff --git a/pkgs/sagemath-objects/VERSION.txt b/pkgs/sagemath-objects/VERSION.txt index 770c88a80e6..ef206d78947 100644 --- a/pkgs/sagemath-objects/VERSION.txt +++ b/pkgs/sagemath-objects/VERSION.txt @@ -1 +1 @@ -10.4.beta2 +10.4.beta5 diff --git a/pkgs/sagemath-objects/pyproject.toml.m4 b/pkgs/sagemath-objects/pyproject.toml.m4 index a8d7d83a44a..68cfbafe935 100644 --- a/pkgs/sagemath-objects/pyproject.toml.m4 +++ b/pkgs/sagemath-objects/pyproject.toml.m4 @@ -46,3 +46,20 @@ version = {file = ["VERSION.txt"]} "python_debug.h", ] "sage.rings" = ["integer_fake.h"] + +[external] +# External dependencies in the format proposed by https://peps.python.org/pep-0725 +build-requires = [ + "virtual:compiler/c", + "virtual:compiler/cpp", + "pkg:generic/pkg-config", +] + +host-requires = [ + "pkg:generic/gmp", + "pkg:generic/mpc", + "pkg:generic/mpfr", +] + +dependencies = [ +] diff --git a/pkgs/sagemath-objects/requirements-editable.txt.m4 b/pkgs/sagemath-objects/requirements-editable.txt.m4 new file mode 100644 index 00000000000..df9b22b43aa --- /dev/null +++ b/pkgs/sagemath-objects/requirements-editable.txt.m4 @@ -0,0 +1,4 @@ +include(`sage_spkg_versions.m4')dnl +dnl Same as setup.cfg.m4 install_requires; FIXME: should pin to built wheels. +SPKG_INSTALL_REQUIRES_gmpy2 +SPKG_INSTALL_REQUIRES_cysignals diff --git a/pkgs/sagemath-objects/tox.ini b/pkgs/sagemath-objects/tox.ini index a8f5a6d6a76..a7b91f55990 100644 --- a/pkgs/sagemath-objects/tox.ini +++ b/pkgs/sagemath-objects/tox.ini @@ -13,7 +13,9 @@ envlist = requires = # Auto-provision a modern tox. # [pkgenv] added in 4.2 - https://tox.wiki/en/latest/upgrading.html#packaging-configuration-and-inheritance + # Because of https://github.com/tox-dev/tox/issues/3238, need <4.14.1 tox>=4.2 + tox<4.14.1 [pkgenv] # Environment in which to build the sdist. @@ -49,6 +51,9 @@ passenv = {[pkgenv]passenv} setenv = {[pkgenv]setenv} # Sage scripts such as sage-runtests like to use $HOME/.sage HOME={envdir} + # Stop 'sage -t --installed' from picking up doc installed in SAGE_LOCAL + SAGE_DOC=/doesnotexist + KNOWN_TEST_FAILURES={toxinidir}/known-test-failures.json allowlist_externals = bash @@ -82,6 +87,11 @@ setenv = {[pkgenv]setenv} basepython = {env:SAGE_VENV}/bin/python3 +[testenv:.pkg-sagepython-sagewheels-nopypi-editable] +config_settings_build_editable = + editable_mode = strict + + [testenv:sagepython] basepython = {env:SAGE_VENV}/bin/python3 package_env = .pkg-sagepython @@ -102,3 +112,11 @@ package_env = .pkg-sagepython [testenv:sagepython-sagewheels-nopypi-norequirements] basepython = {env:SAGE_VENV}/bin/python3 package_env = .pkg-sagepython-sagewheels-nopypi + +[testenv:sagepython-sagewheels-nopypi-editable] +basepython = {env:SAGE_VENV}/bin/python3 +package_env = .pkg-sagepython-sagewheels-nopypi-editable +package = editable +deps = -r requirements-editable.txt +config_settings_build_editable = + editable_mode = strict diff --git a/pkgs/sagemath-repl/VERSION.txt b/pkgs/sagemath-repl/VERSION.txt index 770c88a80e6..ef206d78947 100644 --- a/pkgs/sagemath-repl/VERSION.txt +++ b/pkgs/sagemath-repl/VERSION.txt @@ -1 +1 @@ -10.4.beta2 +10.4.beta5 diff --git a/pkgs/sagemath-repl/pyproject.toml.m4 b/pkgs/sagemath-repl/pyproject.toml.m4 index 2d2f13008be..459a599f1fb 100644 --- a/pkgs/sagemath-repl/pyproject.toml.m4 +++ b/pkgs/sagemath-repl/pyproject.toml.m4 @@ -13,8 +13,10 @@ description = "Sage: Open Source Mathematics Software: IPython kernel, Sage prep dependencies = [ SPKG_INSTALL_REQUIRES_sagemath_objects SPKG_INSTALL_REQUIRES_sagemath_environment + SPKG_INSTALL_REQUIRES_ipykernel SPKG_INSTALL_REQUIRES_ipython SPKG_INSTALL_REQUIRES_ipywidgets + SPKG_INSTALL_REQUIRES_jupyter_client ] dynamic = ["version"] include(`pyproject_toml_metadata.m4')dnl' diff --git a/pkgs/sagemath-repl/requirements-editable.txt.m4 b/pkgs/sagemath-repl/requirements-editable.txt.m4 new file mode 100644 index 00000000000..fa89ade7621 --- /dev/null +++ b/pkgs/sagemath-repl/requirements-editable.txt.m4 @@ -0,0 +1,11 @@ +include(`sage_spkg_versions.m4')dnl +dnl Same as setup.cfg.m4 install_requires (+ their install-requires) +dnl FIXME: should pin to built wheels. +SPKG_INSTALL_REQUIRES_gmpy2 +SPKG_INSTALL_REQUIRES_cysignals +SPKG_INSTALL_REQUIRES_memory_allocator +SPKG_INSTALL_REQUIRES_ipython +SPKG_INSTALL_REQUIRES_ipywidgets +dnl To be added when ready for editable: +-e ../sagemath-environment +-e ../sagemath-objects diff --git a/pkgs/sagemath-repl/tox.ini b/pkgs/sagemath-repl/tox.ini index 679153a2947..d7b557761ac 100644 --- a/pkgs/sagemath-repl/tox.ini +++ b/pkgs/sagemath-repl/tox.ini @@ -13,7 +13,9 @@ envlist = requires = # Auto-provision a modern tox. # [pkgenv] added in 4.2 - https://tox.wiki/en/latest/upgrading.html#packaging-configuration-and-inheritance + # Because of https://github.com/tox-dev/tox/issues/3238, need <4.14.1 tox>=4.2 + tox<4.14.1 [pkgenv] # Environment in which to build the sdist. @@ -49,6 +51,11 @@ passenv = {[pkgenv]passenv} setenv = {[pkgenv]setenv} # Sage scripts such as sage-runtests like to use $HOME/.sage HOME={envdir} + # Stop 'sage -t --installed' from picking up doc installed in SAGE_LOCAL + SAGE_DOC=/doesnotexist + KNOWN_TEST_FAILURES={toxinidir}/known-test-failures.json + # See src/bin/sage-env + PYDEVD_DISABLE_FILE_VALIDATION=1 allowlist_externals = bash @@ -57,7 +64,7 @@ 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 $({envpython} -c "import sys; \"\" in sys.path and sys.path.remove(\"\"); from sage.env import SAGE_LIB; print(SAGE_LIB)") && sage-runtests -p --environment=sage.all__sagemath_repl --baseline-stats-path={toxinidir}/known-test-failures.json --initial --optional=sage sage/repl sage/doctest sage/misc/sage_input.py sage/misc/sage_eval.py' + bash -c 'cd $({envpython} -c "import sys; \"\" in sys.path and sys.path.remove(\"\"); from sage.env import SAGE_LIB; print(SAGE_LIB)") && sage-runtests -p --environment=sage.all__sagemath_repl --baseline-stats-path=$KNOWN_TEST_FAILURES --initial --optional=sage sage/repl sage/doctest sage/misc/sage_input.py sage/misc/sage_eval.py' [testenv:.tox] # Allow access to PyPI for auto-provisioning a suitable tox version @@ -80,6 +87,11 @@ setenv = {[pkgenv]setenv} basepython = {env:SAGE_VENV}/bin/python3 +[testenv:.pkg-sagepython-sagewheels-nopypi-editable] +config_settings_build_editable = + editable_mode = strict + + [testenv:sagepython] basepython = {env:SAGE_VENV}/bin/python3 package_env = .pkg-sagepython @@ -88,10 +100,6 @@ package_env = .pkg-sagepython basepython = {env:SAGE_VENV}/bin/python3 package_env = .pkg-sagepython-sagewheels-nopypi -[testenv:sagepython-sagewheels-nopypi-norequirements] -basepython = {env:SAGE_VENV}/bin/python3 -package_env = .pkg-sagepython-sagewheels-nopypi - [testenv:sagepython-sagewheels] basepython = {env:SAGE_VENV}/bin/python package_env = .pkg-sagepython @@ -99,3 +107,16 @@ package_env = .pkg-sagepython [testenv:sagepython-norequirements] basepython = {env:SAGE_VENV}/bin/python3 package_env = .pkg-sagepython + + +[testenv:sagepython-sagewheels-nopypi-norequirements] +basepython = {env:SAGE_VENV}/bin/python3 +package_env = .pkg-sagepython-sagewheels-nopypi + +[testenv:sagepython-sagewheels-nopypi-editable] +basepython = {env:SAGE_VENV}/bin/python3 +package_env = .pkg-sagepython-sagewheels-nopypi-editable +package = editable +deps = -r requirements-editable.txt +config_settings_build_editable = + editable_mode = strict diff --git a/pkgs/sagemath-sirocco/VERSION.txt b/pkgs/sagemath-sirocco/VERSION.txt index 770c88a80e6..ef206d78947 100644 --- a/pkgs/sagemath-sirocco/VERSION.txt +++ b/pkgs/sagemath-sirocco/VERSION.txt @@ -1 +1 @@ -10.4.beta2 +10.4.beta5 diff --git a/pkgs/sagemath-standard/pyproject.toml.m4 b/pkgs/sagemath-standard/pyproject.toml.m4 deleted file mode 120000 index 25dbae84866..00000000000 --- a/pkgs/sagemath-standard/pyproject.toml.m4 +++ /dev/null @@ -1 +0,0 @@ -../../src/pyproject.toml.m4 \ No newline at end of file diff --git a/pkgs/sagemath-standard/sage_setup b/pkgs/sagemath-standard/sage_setup new file mode 120000 index 00000000000..88b8133df49 --- /dev/null +++ b/pkgs/sagemath-standard/sage_setup @@ -0,0 +1 @@ +../../src/sage_setup \ No newline at end of file diff --git a/pkgs/sagemath-standard/tox.ini b/pkgs/sagemath-standard/tox.ini index 6aae1ef1bfa..c197f49d9be 100644 --- a/pkgs/sagemath-standard/tox.ini +++ b/pkgs/sagemath-standard/tox.ini @@ -62,7 +62,9 @@ envlist = requires = # Auto-provision a modern tox. # [pkgenv] added in 4.2 - https://tox.wiki/en/latest/upgrading.html#packaging-configuration-and-inheritance + # Because of https://github.com/tox-dev/tox/issues/3238, need <4.14.1 tox>=4.2 + tox<4.14.1 [pkgenv] # Environment in which to build the sdist. diff --git a/pkgs/sagemath-tdlib/VERSION.txt b/pkgs/sagemath-tdlib/VERSION.txt index 770c88a80e6..ef206d78947 100644 --- a/pkgs/sagemath-tdlib/VERSION.txt +++ b/pkgs/sagemath-tdlib/VERSION.txt @@ -1 +1 @@ -10.4.beta2 +10.4.beta5 diff --git a/src/MANIFEST.in b/src/MANIFEST.in index 89d9d8e368e..a4613448097 100644 --- a/src/MANIFEST.in +++ b/src/MANIFEST.in @@ -3,10 +3,15 @@ include VERSION.txt recursive-include sage *.pxi *.pxd *.h *.hpp prune sage/ext/interpreters # In particular, __init__.py must not be present in the distribution; or sage_setup.autogen.interpreters.rebuild will not generate the code -prune sage_setup prune sage_docbuild prune doc +# include sage_setup +recursive-include sage_setup * +prune sage_setup/autogen/flint* +exclude sage_setup/autogen/flint* +exclude sage_setup/autogen/giacpy* + # # Most C and C++ files are generated by Cython and should not # be included in the sdist. diff --git a/src/VERSION.txt b/src/VERSION.txt index 770c88a80e6..ef206d78947 100644 --- a/src/VERSION.txt +++ b/src/VERSION.txt @@ -1 +1 @@ -10.4.beta2 +10.4.beta5 diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index b15410a73db..90e01157b13 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='10.4.beta2' -SAGE_RELEASE_DATE='2024-04-08' -SAGE_VERSION_BANNER='SageMath version 10.4.beta2, Release Date: 2024-04-08' +SAGE_VERSION='10.4.beta5' +SAGE_RELEASE_DATE='2024-05-02' +SAGE_VERSION_BANNER='SageMath version 10.4.beta5, Release Date: 2024-05-02' diff --git a/src/doc/de/tutorial/programming.rst b/src/doc/de/tutorial/programming.rst index 9da919fedae..ceeed63c84c 100644 --- a/src/doc/de/tutorial/programming.rst +++ b/src/doc/de/tutorial/programming.rst @@ -177,7 +177,6 @@ Polynome, usw.: #!/usr/bin/env sage import sys - from sage.all import * if len(sys.argv) != 2: print("Usage: %s " % sys.argv[0]) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index c586b030ed8..8522fca3b9f 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -1303,11 +1303,11 @@ framework. Here is a comprehensive list: that is, commas, hyphens, semicolons, ..., after the first word ends the list of packages. Hyphens or colons between the word ``optional`` and the first package name are allowed. Therefore, - you should not write ``# optional - depends on package CHomP`` but simply - ``# optional - CHomP``. + you should not write ``# optional - depends on package bliss`` but simply + ``# optional - bliss``. - Optional tags are case-insensitive, so you could also write ``# optional - - chOMP``. + Bliss``. If ``# optional`` or ``# needs`` is placed right after the ``sage:`` prompt, it is a block-scoped tag, which applies to all doctest lines until diff --git a/src/doc/en/developer/coding_in_cython.rst b/src/doc/en/developer/coding_in_cython.rst index 829b8234e7b..d0c7b0d521f 100644 --- a/src/doc/en/developer/coding_in_cython.rst +++ b/src/doc/en/developer/coding_in_cython.rst @@ -191,3 +191,23 @@ original object. As an example, the following code snippet is the .. _python pickling documentation: http://docs.python.org/library/pickle.html#pickle-protocol +Deprecation +=========== + +When making a **backward-incompatible** modification in Sage, the old code should +keep working and display a message indicating how it should be updated/written +in the future. We call this a *deprecation*. + +.. NOTE:: + + Deprecated code can only be removed one year after the first + stable release in which it appeared. + +Each deprecation warning contains the number of the GitHub PR that defines +it. We use 666 in the example below. + +.. CODE-BLOCK:: cython + + from sage.misc.superseded import deprecation_cython + deprecation_cython(666, "Do not use your computer to compute 1+1. Use your brain.") + diff --git a/src/doc/en/developer/coding_in_python.rst b/src/doc/en/developer/coding_in_python.rst index 37552e5bcfe..82dda02f209 100644 --- a/src/doc/en/developer/coding_in_python.rst +++ b/src/doc/en/developer/coding_in_python.rst @@ -748,6 +748,13 @@ documentation for more information on its behaviour and optional arguments. from sage.misc.superseded import deprecation deprecation(666, "Do not use your computer to compute 1+1. Use your brain.") +Note that these decorators only work for (pure) Python. There is no implementation +of decorators in Cython. Hence, when in need to rename a keyword/function/method/... +in a Cython (.pyx) file and/or to deprecate something, forget about decorators and +just use :func:`~sage.misc.superseded.deprecation_cython` instead. The usage of +:func:`~sage.misc.superseded.deprecation_cython` is exactly the same as +:func:`~sage.misc.superseded.deprecation`. + Experimental/unstable code -------------------------- diff --git a/src/doc/en/developer/packaging.rst b/src/doc/en/developer/packaging.rst index 679d12f8be7..83720282c35 100644 --- a/src/doc/en/developer/packaging.rst +++ b/src/doc/en/developer/packaging.rst @@ -640,6 +640,8 @@ and upper bounds). The constraints are in the format of the `_ or `setup.py `_. +An exception are build time dependencies of Sage library, which should instead +be declared in the ``requires`` block of ``pyproject.toml``. Sage uses these version constraints for two purposes: diff --git a/src/doc/en/developer/portability_platform_table.rst b/src/doc/en/developer/portability_platform_table.rst index d024671c9d8..bf843b0a353 100644 --- a/src/doc/en/developer/portability_platform_table.rst +++ b/src/doc/en/developer/portability_platform_table.rst @@ -1,57 +1,3 @@ -.. |image-ubuntu-trusty-toolchain-gcc_9-minimal-with-system-packages| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-ubuntu-trusty-toolchain-gcc_9-minimal-with-system-packages/size?tag=dev&label=with-system-packages&color=%23696969 - :target: https://ghcr.io/sagemath/sage/sage-ubuntu-trusty-toolchain-gcc_9-minimal-with-system-packages - -.. |image-ubuntu-trusty-toolchain-gcc_9-minimal-configured| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-ubuntu-trusty-toolchain-gcc_9-minimal-configured/latest_tag?ignore=latest,dev,*-failed&label=configured&color=%23696969 - :target: https://ghcr.io/sagemath/sage/sage-ubuntu-trusty-toolchain-gcc_9-minimal-configured - -.. |image-ubuntu-trusty-toolchain-gcc_9-minimal-with-targets-pre| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-ubuntu-trusty-toolchain-gcc_9-minimal-with-targets-pre/latest_tag?ignore=latest,dev,*-failed&label=with-targets-pre&color=%23677895 - :target: https://ghcr.io/sagemath/sage/sage-ubuntu-trusty-toolchain-gcc_9-minimal-with-targets-pre - -.. |image-ubuntu-trusty-toolchain-gcc_9-minimal-with-targets| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-ubuntu-trusty-toolchain-gcc_9-minimal-with-targets/latest_tag?ignore=latest,dev,*-failed&label=with-targets&color=%236686c1 - :target: https://ghcr.io/sagemath/sage/sage-ubuntu-trusty-toolchain-gcc_9-minimal-with-targets - -.. |image-ubuntu-trusty-toolchain-gcc_9-minimal-with-targets-optional| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-ubuntu-trusty-toolchain-gcc_9-minimal-with-targets-optional/latest_tag?ignore=latest,dev,*-failed&label=with-targets-optional&color=%236495ed - :target: https://ghcr.io/sagemath/sage/sage-ubuntu-trusty-toolchain-gcc_9-minimal-with-targets-optional - -.. |codespace-ubuntu-trusty-toolchain-gcc_9-minimal| image:: https://github.com/codespaces/badge.svg - :target: https://codespaces.new/sagemath/sage?devcontainer_path=.devcontainer%2Fportability-ubuntu-trusty-toolchain-gcc_9-minimal%2Fdevcontainer.json - -.. |image-ubuntu-trusty-toolchain-gcc_9-standard-with-system-packages| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-ubuntu-trusty-toolchain-gcc_9-standard-with-system-packages/size?tag=dev&label=with-system-packages&color=%23696969 - :target: https://ghcr.io/sagemath/sage/sage-ubuntu-trusty-toolchain-gcc_9-standard-with-system-packages - -.. |image-ubuntu-trusty-toolchain-gcc_9-standard-configured| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-ubuntu-trusty-toolchain-gcc_9-standard-configured/latest_tag?ignore=latest,dev,*-failed&label=configured&color=%23696969 - :target: https://ghcr.io/sagemath/sage/sage-ubuntu-trusty-toolchain-gcc_9-standard-configured - -.. |image-ubuntu-trusty-toolchain-gcc_9-standard-with-targets-pre| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-ubuntu-trusty-toolchain-gcc_9-standard-with-targets-pre/latest_tag?ignore=latest,dev,*-failed&label=with-targets-pre&color=%235d8a4c - :target: https://ghcr.io/sagemath/sage/sage-ubuntu-trusty-toolchain-gcc_9-standard-with-targets-pre - -.. |image-ubuntu-trusty-toolchain-gcc_9-standard-with-targets| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-ubuntu-trusty-toolchain-gcc_9-standard-with-targets/latest_tag?ignore=latest,dev,*-failed&label=with-targets&color=%2350ab2e - :target: https://ghcr.io/sagemath/sage/sage-ubuntu-trusty-toolchain-gcc_9-standard-with-targets - -.. |image-ubuntu-trusty-toolchain-gcc_9-standard-with-targets-optional| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-ubuntu-trusty-toolchain-gcc_9-standard-with-targets-optional/latest_tag?ignore=latest,dev,*-failed&label=with-targets-optional&color=%2344cc11 - :target: https://ghcr.io/sagemath/sage/sage-ubuntu-trusty-toolchain-gcc_9-standard-with-targets-optional - -.. |codespace-ubuntu-trusty-toolchain-gcc_9-standard| image:: https://github.com/codespaces/badge.svg - :target: https://codespaces.new/sagemath/sage?devcontainer_path=.devcontainer%2Fportability-ubuntu-trusty-toolchain-gcc_9-standard%2Fdevcontainer.json - -.. |image-ubuntu-trusty-toolchain-gcc_9-maximal-with-system-packages| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-ubuntu-trusty-toolchain-gcc_9-maximal-with-system-packages/size?tag=dev&label=with-system-packages&color=%23696969 - :target: https://ghcr.io/sagemath/sage/sage-ubuntu-trusty-toolchain-gcc_9-maximal-with-system-packages - -.. |image-ubuntu-trusty-toolchain-gcc_9-maximal-configured| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-ubuntu-trusty-toolchain-gcc_9-maximal-configured/latest_tag?ignore=latest,dev,*-failed&label=configured&color=%23696969 - :target: https://ghcr.io/sagemath/sage/sage-ubuntu-trusty-toolchain-gcc_9-maximal-configured - -.. |image-ubuntu-trusty-toolchain-gcc_9-maximal-with-targets-pre| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-ubuntu-trusty-toolchain-gcc_9-maximal-with-targets-pre/latest_tag?ignore=latest,dev,*-failed&label=with-targets-pre&color=%238f6b8d - :target: https://ghcr.io/sagemath/sage/sage-ubuntu-trusty-toolchain-gcc_9-maximal-with-targets-pre - -.. |image-ubuntu-trusty-toolchain-gcc_9-maximal-with-targets| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-ubuntu-trusty-toolchain-gcc_9-maximal-with-targets/latest_tag?ignore=latest,dev,*-failed&label=with-targets&color=%23b46eb2 - :target: https://ghcr.io/sagemath/sage/sage-ubuntu-trusty-toolchain-gcc_9-maximal-with-targets - -.. |image-ubuntu-trusty-toolchain-gcc_9-maximal-with-targets-optional| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-ubuntu-trusty-toolchain-gcc_9-maximal-with-targets-optional/latest_tag?ignore=latest,dev,*-failed&label=with-targets-optional&color=%23da70d6 - :target: https://ghcr.io/sagemath/sage/sage-ubuntu-trusty-toolchain-gcc_9-maximal-with-targets-optional - -.. |codespace-ubuntu-trusty-toolchain-gcc_9-maximal| image:: https://github.com/codespaces/badge.svg - :target: https://codespaces.new/sagemath/sage?devcontainer_path=.devcontainer%2Fportability-ubuntu-trusty-toolchain-gcc_9-maximal%2Fdevcontainer.json - .. |image-ubuntu-xenial-toolchain-gcc_9-minimal-with-system-packages| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-ubuntu-xenial-toolchain-gcc_9-minimal-with-system-packages/size?tag=dev&label=with-system-packages&color=%23696969 :target: https://ghcr.io/sagemath/sage/sage-ubuntu-xenial-toolchain-gcc_9-minimal-with-system-packages @@ -376,6 +322,60 @@ .. |codespace-ubuntu-mantic-maximal| image:: https://github.com/codespaces/badge.svg :target: https://codespaces.new/sagemath/sage?devcontainer_path=.devcontainer%2Fportability-ubuntu-mantic-maximal%2Fdevcontainer.json +.. |image-ubuntu-noble-minimal-with-system-packages| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-ubuntu-noble-minimal-with-system-packages/size?tag=dev&label=with-system-packages&color=%23696969 + :target: https://ghcr.io/sagemath/sage/sage-ubuntu-noble-minimal-with-system-packages + +.. |image-ubuntu-noble-minimal-configured| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-ubuntu-noble-minimal-configured/latest_tag?ignore=latest,dev,*-failed&label=configured&color=%23696969 + :target: https://ghcr.io/sagemath/sage/sage-ubuntu-noble-minimal-configured + +.. |image-ubuntu-noble-minimal-with-targets-pre| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-ubuntu-noble-minimal-with-targets-pre/latest_tag?ignore=latest,dev,*-failed&label=with-targets-pre&color=%23677895 + :target: https://ghcr.io/sagemath/sage/sage-ubuntu-noble-minimal-with-targets-pre + +.. |image-ubuntu-noble-minimal-with-targets| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-ubuntu-noble-minimal-with-targets/latest_tag?ignore=latest,dev,*-failed&label=with-targets&color=%236686c1 + :target: https://ghcr.io/sagemath/sage/sage-ubuntu-noble-minimal-with-targets + +.. |image-ubuntu-noble-minimal-with-targets-optional| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-ubuntu-noble-minimal-with-targets-optional/latest_tag?ignore=latest,dev,*-failed&label=with-targets-optional&color=%236495ed + :target: https://ghcr.io/sagemath/sage/sage-ubuntu-noble-minimal-with-targets-optional + +.. |codespace-ubuntu-noble-minimal| image:: https://github.com/codespaces/badge.svg + :target: https://codespaces.new/sagemath/sage?devcontainer_path=.devcontainer%2Fportability-ubuntu-noble-minimal%2Fdevcontainer.json + +.. |image-ubuntu-noble-standard-with-system-packages| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-ubuntu-noble-standard-with-system-packages/size?tag=dev&label=with-system-packages&color=%23696969 + :target: https://ghcr.io/sagemath/sage/sage-ubuntu-noble-standard-with-system-packages + +.. |image-ubuntu-noble-standard-configured| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-ubuntu-noble-standard-configured/latest_tag?ignore=latest,dev,*-failed&label=configured&color=%23696969 + :target: https://ghcr.io/sagemath/sage/sage-ubuntu-noble-standard-configured + +.. |image-ubuntu-noble-standard-with-targets-pre| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-ubuntu-noble-standard-with-targets-pre/latest_tag?ignore=latest,dev,*-failed&label=with-targets-pre&color=%235d8a4c + :target: https://ghcr.io/sagemath/sage/sage-ubuntu-noble-standard-with-targets-pre + +.. |image-ubuntu-noble-standard-with-targets| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-ubuntu-noble-standard-with-targets/latest_tag?ignore=latest,dev,*-failed&label=with-targets&color=%2350ab2e + :target: https://ghcr.io/sagemath/sage/sage-ubuntu-noble-standard-with-targets + +.. |image-ubuntu-noble-standard-with-targets-optional| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-ubuntu-noble-standard-with-targets-optional/latest_tag?ignore=latest,dev,*-failed&label=with-targets-optional&color=%2344cc11 + :target: https://ghcr.io/sagemath/sage/sage-ubuntu-noble-standard-with-targets-optional + +.. |codespace-ubuntu-noble-standard| image:: https://github.com/codespaces/badge.svg + :target: https://codespaces.new/sagemath/sage?devcontainer_path=.devcontainer%2Fportability-ubuntu-noble-standard%2Fdevcontainer.json + +.. |image-ubuntu-noble-maximal-with-system-packages| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-ubuntu-noble-maximal-with-system-packages/size?tag=dev&label=with-system-packages&color=%23696969 + :target: https://ghcr.io/sagemath/sage/sage-ubuntu-noble-maximal-with-system-packages + +.. |image-ubuntu-noble-maximal-configured| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-ubuntu-noble-maximal-configured/latest_tag?ignore=latest,dev,*-failed&label=configured&color=%23696969 + :target: https://ghcr.io/sagemath/sage/sage-ubuntu-noble-maximal-configured + +.. |image-ubuntu-noble-maximal-with-targets-pre| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-ubuntu-noble-maximal-with-targets-pre/latest_tag?ignore=latest,dev,*-failed&label=with-targets-pre&color=%238f6b8d + :target: https://ghcr.io/sagemath/sage/sage-ubuntu-noble-maximal-with-targets-pre + +.. |image-ubuntu-noble-maximal-with-targets| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-ubuntu-noble-maximal-with-targets/latest_tag?ignore=latest,dev,*-failed&label=with-targets&color=%23b46eb2 + :target: https://ghcr.io/sagemath/sage/sage-ubuntu-noble-maximal-with-targets + +.. |image-ubuntu-noble-maximal-with-targets-optional| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-ubuntu-noble-maximal-with-targets-optional/latest_tag?ignore=latest,dev,*-failed&label=with-targets-optional&color=%23da70d6 + :target: https://ghcr.io/sagemath/sage/sage-ubuntu-noble-maximal-with-targets-optional + +.. |codespace-ubuntu-noble-maximal| image:: https://github.com/codespaces/badge.svg + :target: https://codespaces.new/sagemath/sage?devcontainer_path=.devcontainer%2Fportability-ubuntu-noble-maximal%2Fdevcontainer.json + .. |image-debian-buster-gcc_spkg-minimal-with-system-packages| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-debian-buster-gcc_spkg-minimal-with-system-packages/size?tag=dev&label=with-system-packages&color=%23696969 :target: https://ghcr.io/sagemath/sage/sage-debian-buster-gcc_spkg-minimal-with-system-packages @@ -970,6 +970,60 @@ .. |codespace-linuxmint-21.2-maximal| image:: https://github.com/codespaces/badge.svg :target: https://codespaces.new/sagemath/sage?devcontainer_path=.devcontainer%2Fportability-linuxmint-21.2-maximal%2Fdevcontainer.json +.. |image-linuxmint-21.3-minimal-with-system-packages| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-linuxmint-21.3-minimal-with-system-packages/size?tag=dev&label=with-system-packages&color=%23696969 + :target: https://ghcr.io/sagemath/sage/sage-linuxmint-21.3-minimal-with-system-packages + +.. |image-linuxmint-21.3-minimal-configured| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-linuxmint-21.3-minimal-configured/latest_tag?ignore=latest,dev,*-failed&label=configured&color=%23696969 + :target: https://ghcr.io/sagemath/sage/sage-linuxmint-21.3-minimal-configured + +.. |image-linuxmint-21.3-minimal-with-targets-pre| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-linuxmint-21.3-minimal-with-targets-pre/latest_tag?ignore=latest,dev,*-failed&label=with-targets-pre&color=%23677895 + :target: https://ghcr.io/sagemath/sage/sage-linuxmint-21.3-minimal-with-targets-pre + +.. |image-linuxmint-21.3-minimal-with-targets| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-linuxmint-21.3-minimal-with-targets/latest_tag?ignore=latest,dev,*-failed&label=with-targets&color=%236686c1 + :target: https://ghcr.io/sagemath/sage/sage-linuxmint-21.3-minimal-with-targets + +.. |image-linuxmint-21.3-minimal-with-targets-optional| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-linuxmint-21.3-minimal-with-targets-optional/latest_tag?ignore=latest,dev,*-failed&label=with-targets-optional&color=%236495ed + :target: https://ghcr.io/sagemath/sage/sage-linuxmint-21.3-minimal-with-targets-optional + +.. |codespace-linuxmint-21.3-minimal| image:: https://github.com/codespaces/badge.svg + :target: https://codespaces.new/sagemath/sage?devcontainer_path=.devcontainer%2Fportability-linuxmint-21.3-minimal%2Fdevcontainer.json + +.. |image-linuxmint-21.3-standard-with-system-packages| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-linuxmint-21.3-standard-with-system-packages/size?tag=dev&label=with-system-packages&color=%23696969 + :target: https://ghcr.io/sagemath/sage/sage-linuxmint-21.3-standard-with-system-packages + +.. |image-linuxmint-21.3-standard-configured| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-linuxmint-21.3-standard-configured/latest_tag?ignore=latest,dev,*-failed&label=configured&color=%23696969 + :target: https://ghcr.io/sagemath/sage/sage-linuxmint-21.3-standard-configured + +.. |image-linuxmint-21.3-standard-with-targets-pre| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-linuxmint-21.3-standard-with-targets-pre/latest_tag?ignore=latest,dev,*-failed&label=with-targets-pre&color=%235d8a4c + :target: https://ghcr.io/sagemath/sage/sage-linuxmint-21.3-standard-with-targets-pre + +.. |image-linuxmint-21.3-standard-with-targets| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-linuxmint-21.3-standard-with-targets/latest_tag?ignore=latest,dev,*-failed&label=with-targets&color=%2350ab2e + :target: https://ghcr.io/sagemath/sage/sage-linuxmint-21.3-standard-with-targets + +.. |image-linuxmint-21.3-standard-with-targets-optional| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-linuxmint-21.3-standard-with-targets-optional/latest_tag?ignore=latest,dev,*-failed&label=with-targets-optional&color=%2344cc11 + :target: https://ghcr.io/sagemath/sage/sage-linuxmint-21.3-standard-with-targets-optional + +.. |codespace-linuxmint-21.3-standard| image:: https://github.com/codespaces/badge.svg + :target: https://codespaces.new/sagemath/sage?devcontainer_path=.devcontainer%2Fportability-linuxmint-21.3-standard%2Fdevcontainer.json + +.. |image-linuxmint-21.3-maximal-with-system-packages| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-linuxmint-21.3-maximal-with-system-packages/size?tag=dev&label=with-system-packages&color=%23696969 + :target: https://ghcr.io/sagemath/sage/sage-linuxmint-21.3-maximal-with-system-packages + +.. |image-linuxmint-21.3-maximal-configured| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-linuxmint-21.3-maximal-configured/latest_tag?ignore=latest,dev,*-failed&label=configured&color=%23696969 + :target: https://ghcr.io/sagemath/sage/sage-linuxmint-21.3-maximal-configured + +.. |image-linuxmint-21.3-maximal-with-targets-pre| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-linuxmint-21.3-maximal-with-targets-pre/latest_tag?ignore=latest,dev,*-failed&label=with-targets-pre&color=%238f6b8d + :target: https://ghcr.io/sagemath/sage/sage-linuxmint-21.3-maximal-with-targets-pre + +.. |image-linuxmint-21.3-maximal-with-targets| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-linuxmint-21.3-maximal-with-targets/latest_tag?ignore=latest,dev,*-failed&label=with-targets&color=%23b46eb2 + :target: https://ghcr.io/sagemath/sage/sage-linuxmint-21.3-maximal-with-targets + +.. |image-linuxmint-21.3-maximal-with-targets-optional| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-linuxmint-21.3-maximal-with-targets-optional/latest_tag?ignore=latest,dev,*-failed&label=with-targets-optional&color=%23da70d6 + :target: https://ghcr.io/sagemath/sage/sage-linuxmint-21.3-maximal-with-targets-optional + +.. |codespace-linuxmint-21.3-maximal| image:: https://github.com/codespaces/badge.svg + :target: https://codespaces.new/sagemath/sage?devcontainer_path=.devcontainer%2Fportability-linuxmint-21.3-maximal%2Fdevcontainer.json + .. |image-fedora-30-minimal-with-system-packages| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-fedora-30-minimal-with-system-packages/size?tag=dev&label=with-system-packages&color=%23696969 :target: https://ghcr.io/sagemath/sage/sage-fedora-30-minimal-with-system-packages @@ -1510,6 +1564,60 @@ .. |codespace-fedora-39-maximal| image:: https://github.com/codespaces/badge.svg :target: https://codespaces.new/sagemath/sage?devcontainer_path=.devcontainer%2Fportability-fedora-39-maximal%2Fdevcontainer.json +.. |image-fedora-40-minimal-with-system-packages| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-fedora-40-minimal-with-system-packages/size?tag=dev&label=with-system-packages&color=%23696969 + :target: https://ghcr.io/sagemath/sage/sage-fedora-40-minimal-with-system-packages + +.. |image-fedora-40-minimal-configured| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-fedora-40-minimal-configured/latest_tag?ignore=latest,dev,*-failed&label=configured&color=%23696969 + :target: https://ghcr.io/sagemath/sage/sage-fedora-40-minimal-configured + +.. |image-fedora-40-minimal-with-targets-pre| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-fedora-40-minimal-with-targets-pre/latest_tag?ignore=latest,dev,*-failed&label=with-targets-pre&color=%23677895 + :target: https://ghcr.io/sagemath/sage/sage-fedora-40-minimal-with-targets-pre + +.. |image-fedora-40-minimal-with-targets| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-fedora-40-minimal-with-targets/latest_tag?ignore=latest,dev,*-failed&label=with-targets&color=%236686c1 + :target: https://ghcr.io/sagemath/sage/sage-fedora-40-minimal-with-targets + +.. |image-fedora-40-minimal-with-targets-optional| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-fedora-40-minimal-with-targets-optional/latest_tag?ignore=latest,dev,*-failed&label=with-targets-optional&color=%236495ed + :target: https://ghcr.io/sagemath/sage/sage-fedora-40-minimal-with-targets-optional + +.. |codespace-fedora-40-minimal| image:: https://github.com/codespaces/badge.svg + :target: https://codespaces.new/sagemath/sage?devcontainer_path=.devcontainer%2Fportability-fedora-40-minimal%2Fdevcontainer.json + +.. |image-fedora-40-standard-with-system-packages| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-fedora-40-standard-with-system-packages/size?tag=dev&label=with-system-packages&color=%23696969 + :target: https://ghcr.io/sagemath/sage/sage-fedora-40-standard-with-system-packages + +.. |image-fedora-40-standard-configured| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-fedora-40-standard-configured/latest_tag?ignore=latest,dev,*-failed&label=configured&color=%23696969 + :target: https://ghcr.io/sagemath/sage/sage-fedora-40-standard-configured + +.. |image-fedora-40-standard-with-targets-pre| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-fedora-40-standard-with-targets-pre/latest_tag?ignore=latest,dev,*-failed&label=with-targets-pre&color=%235d8a4c + :target: https://ghcr.io/sagemath/sage/sage-fedora-40-standard-with-targets-pre + +.. |image-fedora-40-standard-with-targets| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-fedora-40-standard-with-targets/latest_tag?ignore=latest,dev,*-failed&label=with-targets&color=%2350ab2e + :target: https://ghcr.io/sagemath/sage/sage-fedora-40-standard-with-targets + +.. |image-fedora-40-standard-with-targets-optional| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-fedora-40-standard-with-targets-optional/latest_tag?ignore=latest,dev,*-failed&label=with-targets-optional&color=%2344cc11 + :target: https://ghcr.io/sagemath/sage/sage-fedora-40-standard-with-targets-optional + +.. |codespace-fedora-40-standard| image:: https://github.com/codespaces/badge.svg + :target: https://codespaces.new/sagemath/sage?devcontainer_path=.devcontainer%2Fportability-fedora-40-standard%2Fdevcontainer.json + +.. |image-fedora-40-maximal-with-system-packages| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-fedora-40-maximal-with-system-packages/size?tag=dev&label=with-system-packages&color=%23696969 + :target: https://ghcr.io/sagemath/sage/sage-fedora-40-maximal-with-system-packages + +.. |image-fedora-40-maximal-configured| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-fedora-40-maximal-configured/latest_tag?ignore=latest,dev,*-failed&label=configured&color=%23696969 + :target: https://ghcr.io/sagemath/sage/sage-fedora-40-maximal-configured + +.. |image-fedora-40-maximal-with-targets-pre| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-fedora-40-maximal-with-targets-pre/latest_tag?ignore=latest,dev,*-failed&label=with-targets-pre&color=%238f6b8d + :target: https://ghcr.io/sagemath/sage/sage-fedora-40-maximal-with-targets-pre + +.. |image-fedora-40-maximal-with-targets| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-fedora-40-maximal-with-targets/latest_tag?ignore=latest,dev,*-failed&label=with-targets&color=%23b46eb2 + :target: https://ghcr.io/sagemath/sage/sage-fedora-40-maximal-with-targets + +.. |image-fedora-40-maximal-with-targets-optional| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-fedora-40-maximal-with-targets-optional/latest_tag?ignore=latest,dev,*-failed&label=with-targets-optional&color=%23da70d6 + :target: https://ghcr.io/sagemath/sage/sage-fedora-40-maximal-with-targets-optional + +.. |codespace-fedora-40-maximal| image:: https://github.com/codespaces/badge.svg + :target: https://codespaces.new/sagemath/sage?devcontainer_path=.devcontainer%2Fportability-fedora-40-maximal%2Fdevcontainer.json + .. |image-centos-7-devtoolset-gcc_11-minimal-with-system-packages| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-centos-7-devtoolset-gcc_11-minimal-with-system-packages/size?tag=dev&label=with-system-packages&color=%23696969 :target: https://ghcr.io/sagemath/sage/sage-centos-7-devtoolset-gcc_11-minimal-with-system-packages @@ -1888,6 +1996,60 @@ .. |codespace-gentoo-python3.11-maximal| image:: https://github.com/codespaces/badge.svg :target: https://codespaces.new/sagemath/sage?devcontainer_path=.devcontainer%2Fportability-gentoo-python3.11-maximal%2Fdevcontainer.json +.. |image-gentoo-python3.12-minimal-with-system-packages| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-gentoo-python3.12-minimal-with-system-packages/size?tag=dev&label=with-system-packages&color=%23696969 + :target: https://ghcr.io/sagemath/sage/sage-gentoo-python3.12-minimal-with-system-packages + +.. |image-gentoo-python3.12-minimal-configured| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-gentoo-python3.12-minimal-configured/latest_tag?ignore=latest,dev,*-failed&label=configured&color=%23696969 + :target: https://ghcr.io/sagemath/sage/sage-gentoo-python3.12-minimal-configured + +.. |image-gentoo-python3.12-minimal-with-targets-pre| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-gentoo-python3.12-minimal-with-targets-pre/latest_tag?ignore=latest,dev,*-failed&label=with-targets-pre&color=%23677895 + :target: https://ghcr.io/sagemath/sage/sage-gentoo-python3.12-minimal-with-targets-pre + +.. |image-gentoo-python3.12-minimal-with-targets| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-gentoo-python3.12-minimal-with-targets/latest_tag?ignore=latest,dev,*-failed&label=with-targets&color=%236686c1 + :target: https://ghcr.io/sagemath/sage/sage-gentoo-python3.12-minimal-with-targets + +.. |image-gentoo-python3.12-minimal-with-targets-optional| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-gentoo-python3.12-minimal-with-targets-optional/latest_tag?ignore=latest,dev,*-failed&label=with-targets-optional&color=%236495ed + :target: https://ghcr.io/sagemath/sage/sage-gentoo-python3.12-minimal-with-targets-optional + +.. |codespace-gentoo-python3.12-minimal| image:: https://github.com/codespaces/badge.svg + :target: https://codespaces.new/sagemath/sage?devcontainer_path=.devcontainer%2Fportability-gentoo-python3.12-minimal%2Fdevcontainer.json + +.. |image-gentoo-python3.12-standard-with-system-packages| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-gentoo-python3.12-standard-with-system-packages/size?tag=dev&label=with-system-packages&color=%23696969 + :target: https://ghcr.io/sagemath/sage/sage-gentoo-python3.12-standard-with-system-packages + +.. |image-gentoo-python3.12-standard-configured| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-gentoo-python3.12-standard-configured/latest_tag?ignore=latest,dev,*-failed&label=configured&color=%23696969 + :target: https://ghcr.io/sagemath/sage/sage-gentoo-python3.12-standard-configured + +.. |image-gentoo-python3.12-standard-with-targets-pre| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-gentoo-python3.12-standard-with-targets-pre/latest_tag?ignore=latest,dev,*-failed&label=with-targets-pre&color=%235d8a4c + :target: https://ghcr.io/sagemath/sage/sage-gentoo-python3.12-standard-with-targets-pre + +.. |image-gentoo-python3.12-standard-with-targets| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-gentoo-python3.12-standard-with-targets/latest_tag?ignore=latest,dev,*-failed&label=with-targets&color=%2350ab2e + :target: https://ghcr.io/sagemath/sage/sage-gentoo-python3.12-standard-with-targets + +.. |image-gentoo-python3.12-standard-with-targets-optional| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-gentoo-python3.12-standard-with-targets-optional/latest_tag?ignore=latest,dev,*-failed&label=with-targets-optional&color=%2344cc11 + :target: https://ghcr.io/sagemath/sage/sage-gentoo-python3.12-standard-with-targets-optional + +.. |codespace-gentoo-python3.12-standard| image:: https://github.com/codespaces/badge.svg + :target: https://codespaces.new/sagemath/sage?devcontainer_path=.devcontainer%2Fportability-gentoo-python3.12-standard%2Fdevcontainer.json + +.. |image-gentoo-python3.12-maximal-with-system-packages| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-gentoo-python3.12-maximal-with-system-packages/size?tag=dev&label=with-system-packages&color=%23696969 + :target: https://ghcr.io/sagemath/sage/sage-gentoo-python3.12-maximal-with-system-packages + +.. |image-gentoo-python3.12-maximal-configured| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-gentoo-python3.12-maximal-configured/latest_tag?ignore=latest,dev,*-failed&label=configured&color=%23696969 + :target: https://ghcr.io/sagemath/sage/sage-gentoo-python3.12-maximal-configured + +.. |image-gentoo-python3.12-maximal-with-targets-pre| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-gentoo-python3.12-maximal-with-targets-pre/latest_tag?ignore=latest,dev,*-failed&label=with-targets-pre&color=%238f6b8d + :target: https://ghcr.io/sagemath/sage/sage-gentoo-python3.12-maximal-with-targets-pre + +.. |image-gentoo-python3.12-maximal-with-targets| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-gentoo-python3.12-maximal-with-targets/latest_tag?ignore=latest,dev,*-failed&label=with-targets&color=%23b46eb2 + :target: https://ghcr.io/sagemath/sage/sage-gentoo-python3.12-maximal-with-targets + +.. |image-gentoo-python3.12-maximal-with-targets-optional| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-gentoo-python3.12-maximal-with-targets-optional/latest_tag?ignore=latest,dev,*-failed&label=with-targets-optional&color=%23da70d6 + :target: https://ghcr.io/sagemath/sage/sage-gentoo-python3.12-maximal-with-targets-optional + +.. |codespace-gentoo-python3.12-maximal| image:: https://github.com/codespaces/badge.svg + :target: https://codespaces.new/sagemath/sage?devcontainer_path=.devcontainer%2Fportability-gentoo-python3.12-maximal%2Fdevcontainer.json + .. |image-archlinux-latest-minimal-with-system-packages| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-archlinux-latest-minimal-with-system-packages/size?tag=dev&label=with-system-packages&color=%23696969 :target: https://ghcr.io/sagemath/sage/sage-archlinux-latest-minimal-with-system-packages @@ -1942,114 +2104,6 @@ .. |codespace-archlinux-latest-maximal| image:: https://github.com/codespaces/badge.svg :target: https://codespaces.new/sagemath/sage?devcontainer_path=.devcontainer%2Fportability-archlinux-latest-maximal%2Fdevcontainer.json -.. |image-opensuse-15.3-gcc_11-python3.9-minimal-with-system-packages| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-opensuse-15.3-gcc_11-python3.9-minimal-with-system-packages/size?tag=dev&label=with-system-packages&color=%23696969 - :target: https://ghcr.io/sagemath/sage/sage-opensuse-15.3-gcc_11-python3.9-minimal-with-system-packages - -.. |image-opensuse-15.3-gcc_11-python3.9-minimal-configured| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-opensuse-15.3-gcc_11-python3.9-minimal-configured/latest_tag?ignore=latest,dev,*-failed&label=configured&color=%23696969 - :target: https://ghcr.io/sagemath/sage/sage-opensuse-15.3-gcc_11-python3.9-minimal-configured - -.. |image-opensuse-15.3-gcc_11-python3.9-minimal-with-targets-pre| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-opensuse-15.3-gcc_11-python3.9-minimal-with-targets-pre/latest_tag?ignore=latest,dev,*-failed&label=with-targets-pre&color=%23677895 - :target: https://ghcr.io/sagemath/sage/sage-opensuse-15.3-gcc_11-python3.9-minimal-with-targets-pre - -.. |image-opensuse-15.3-gcc_11-python3.9-minimal-with-targets| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-opensuse-15.3-gcc_11-python3.9-minimal-with-targets/latest_tag?ignore=latest,dev,*-failed&label=with-targets&color=%236686c1 - :target: https://ghcr.io/sagemath/sage/sage-opensuse-15.3-gcc_11-python3.9-minimal-with-targets - -.. |image-opensuse-15.3-gcc_11-python3.9-minimal-with-targets-optional| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-opensuse-15.3-gcc_11-python3.9-minimal-with-targets-optional/latest_tag?ignore=latest,dev,*-failed&label=with-targets-optional&color=%236495ed - :target: https://ghcr.io/sagemath/sage/sage-opensuse-15.3-gcc_11-python3.9-minimal-with-targets-optional - -.. |codespace-opensuse-15.3-gcc_11-python3.9-minimal| image:: https://github.com/codespaces/badge.svg - :target: https://codespaces.new/sagemath/sage?devcontainer_path=.devcontainer%2Fportability-opensuse-15.3-gcc_11-python3.9-minimal%2Fdevcontainer.json - -.. |image-opensuse-15.3-gcc_11-python3.9-standard-with-system-packages| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-opensuse-15.3-gcc_11-python3.9-standard-with-system-packages/size?tag=dev&label=with-system-packages&color=%23696969 - :target: https://ghcr.io/sagemath/sage/sage-opensuse-15.3-gcc_11-python3.9-standard-with-system-packages - -.. |image-opensuse-15.3-gcc_11-python3.9-standard-configured| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-opensuse-15.3-gcc_11-python3.9-standard-configured/latest_tag?ignore=latest,dev,*-failed&label=configured&color=%23696969 - :target: https://ghcr.io/sagemath/sage/sage-opensuse-15.3-gcc_11-python3.9-standard-configured - -.. |image-opensuse-15.3-gcc_11-python3.9-standard-with-targets-pre| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-opensuse-15.3-gcc_11-python3.9-standard-with-targets-pre/latest_tag?ignore=latest,dev,*-failed&label=with-targets-pre&color=%235d8a4c - :target: https://ghcr.io/sagemath/sage/sage-opensuse-15.3-gcc_11-python3.9-standard-with-targets-pre - -.. |image-opensuse-15.3-gcc_11-python3.9-standard-with-targets| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-opensuse-15.3-gcc_11-python3.9-standard-with-targets/latest_tag?ignore=latest,dev,*-failed&label=with-targets&color=%2350ab2e - :target: https://ghcr.io/sagemath/sage/sage-opensuse-15.3-gcc_11-python3.9-standard-with-targets - -.. |image-opensuse-15.3-gcc_11-python3.9-standard-with-targets-optional| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-opensuse-15.3-gcc_11-python3.9-standard-with-targets-optional/latest_tag?ignore=latest,dev,*-failed&label=with-targets-optional&color=%2344cc11 - :target: https://ghcr.io/sagemath/sage/sage-opensuse-15.3-gcc_11-python3.9-standard-with-targets-optional - -.. |codespace-opensuse-15.3-gcc_11-python3.9-standard| image:: https://github.com/codespaces/badge.svg - :target: https://codespaces.new/sagemath/sage?devcontainer_path=.devcontainer%2Fportability-opensuse-15.3-gcc_11-python3.9-standard%2Fdevcontainer.json - -.. |image-opensuse-15.3-gcc_11-python3.9-maximal-with-system-packages| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-opensuse-15.3-gcc_11-python3.9-maximal-with-system-packages/size?tag=dev&label=with-system-packages&color=%23696969 - :target: https://ghcr.io/sagemath/sage/sage-opensuse-15.3-gcc_11-python3.9-maximal-with-system-packages - -.. |image-opensuse-15.3-gcc_11-python3.9-maximal-configured| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-opensuse-15.3-gcc_11-python3.9-maximal-configured/latest_tag?ignore=latest,dev,*-failed&label=configured&color=%23696969 - :target: https://ghcr.io/sagemath/sage/sage-opensuse-15.3-gcc_11-python3.9-maximal-configured - -.. |image-opensuse-15.3-gcc_11-python3.9-maximal-with-targets-pre| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-opensuse-15.3-gcc_11-python3.9-maximal-with-targets-pre/latest_tag?ignore=latest,dev,*-failed&label=with-targets-pre&color=%238f6b8d - :target: https://ghcr.io/sagemath/sage/sage-opensuse-15.3-gcc_11-python3.9-maximal-with-targets-pre - -.. |image-opensuse-15.3-gcc_11-python3.9-maximal-with-targets| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-opensuse-15.3-gcc_11-python3.9-maximal-with-targets/latest_tag?ignore=latest,dev,*-failed&label=with-targets&color=%23b46eb2 - :target: https://ghcr.io/sagemath/sage/sage-opensuse-15.3-gcc_11-python3.9-maximal-with-targets - -.. |image-opensuse-15.3-gcc_11-python3.9-maximal-with-targets-optional| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-opensuse-15.3-gcc_11-python3.9-maximal-with-targets-optional/latest_tag?ignore=latest,dev,*-failed&label=with-targets-optional&color=%23da70d6 - :target: https://ghcr.io/sagemath/sage/sage-opensuse-15.3-gcc_11-python3.9-maximal-with-targets-optional - -.. |codespace-opensuse-15.3-gcc_11-python3.9-maximal| image:: https://github.com/codespaces/badge.svg - :target: https://codespaces.new/sagemath/sage?devcontainer_path=.devcontainer%2Fportability-opensuse-15.3-gcc_11-python3.9-maximal%2Fdevcontainer.json - -.. |image-opensuse-15.4-gcc_11-python3.10-minimal-with-system-packages| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-opensuse-15.4-gcc_11-python3.10-minimal-with-system-packages/size?tag=dev&label=with-system-packages&color=%23696969 - :target: https://ghcr.io/sagemath/sage/sage-opensuse-15.4-gcc_11-python3.10-minimal-with-system-packages - -.. |image-opensuse-15.4-gcc_11-python3.10-minimal-configured| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-opensuse-15.4-gcc_11-python3.10-minimal-configured/latest_tag?ignore=latest,dev,*-failed&label=configured&color=%23696969 - :target: https://ghcr.io/sagemath/sage/sage-opensuse-15.4-gcc_11-python3.10-minimal-configured - -.. |image-opensuse-15.4-gcc_11-python3.10-minimal-with-targets-pre| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-opensuse-15.4-gcc_11-python3.10-minimal-with-targets-pre/latest_tag?ignore=latest,dev,*-failed&label=with-targets-pre&color=%23677895 - :target: https://ghcr.io/sagemath/sage/sage-opensuse-15.4-gcc_11-python3.10-minimal-with-targets-pre - -.. |image-opensuse-15.4-gcc_11-python3.10-minimal-with-targets| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-opensuse-15.4-gcc_11-python3.10-minimal-with-targets/latest_tag?ignore=latest,dev,*-failed&label=with-targets&color=%236686c1 - :target: https://ghcr.io/sagemath/sage/sage-opensuse-15.4-gcc_11-python3.10-minimal-with-targets - -.. |image-opensuse-15.4-gcc_11-python3.10-minimal-with-targets-optional| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-opensuse-15.4-gcc_11-python3.10-minimal-with-targets-optional/latest_tag?ignore=latest,dev,*-failed&label=with-targets-optional&color=%236495ed - :target: https://ghcr.io/sagemath/sage/sage-opensuse-15.4-gcc_11-python3.10-minimal-with-targets-optional - -.. |codespace-opensuse-15.4-gcc_11-python3.10-minimal| image:: https://github.com/codespaces/badge.svg - :target: https://codespaces.new/sagemath/sage?devcontainer_path=.devcontainer%2Fportability-opensuse-15.4-gcc_11-python3.10-minimal%2Fdevcontainer.json - -.. |image-opensuse-15.4-gcc_11-python3.10-standard-with-system-packages| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-opensuse-15.4-gcc_11-python3.10-standard-with-system-packages/size?tag=dev&label=with-system-packages&color=%23696969 - :target: https://ghcr.io/sagemath/sage/sage-opensuse-15.4-gcc_11-python3.10-standard-with-system-packages - -.. |image-opensuse-15.4-gcc_11-python3.10-standard-configured| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-opensuse-15.4-gcc_11-python3.10-standard-configured/latest_tag?ignore=latest,dev,*-failed&label=configured&color=%23696969 - :target: https://ghcr.io/sagemath/sage/sage-opensuse-15.4-gcc_11-python3.10-standard-configured - -.. |image-opensuse-15.4-gcc_11-python3.10-standard-with-targets-pre| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-opensuse-15.4-gcc_11-python3.10-standard-with-targets-pre/latest_tag?ignore=latest,dev,*-failed&label=with-targets-pre&color=%235d8a4c - :target: https://ghcr.io/sagemath/sage/sage-opensuse-15.4-gcc_11-python3.10-standard-with-targets-pre - -.. |image-opensuse-15.4-gcc_11-python3.10-standard-with-targets| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-opensuse-15.4-gcc_11-python3.10-standard-with-targets/latest_tag?ignore=latest,dev,*-failed&label=with-targets&color=%2350ab2e - :target: https://ghcr.io/sagemath/sage/sage-opensuse-15.4-gcc_11-python3.10-standard-with-targets - -.. |image-opensuse-15.4-gcc_11-python3.10-standard-with-targets-optional| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-opensuse-15.4-gcc_11-python3.10-standard-with-targets-optional/latest_tag?ignore=latest,dev,*-failed&label=with-targets-optional&color=%2344cc11 - :target: https://ghcr.io/sagemath/sage/sage-opensuse-15.4-gcc_11-python3.10-standard-with-targets-optional - -.. |codespace-opensuse-15.4-gcc_11-python3.10-standard| image:: https://github.com/codespaces/badge.svg - :target: https://codespaces.new/sagemath/sage?devcontainer_path=.devcontainer%2Fportability-opensuse-15.4-gcc_11-python3.10-standard%2Fdevcontainer.json - -.. |image-opensuse-15.4-gcc_11-python3.10-maximal-with-system-packages| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-opensuse-15.4-gcc_11-python3.10-maximal-with-system-packages/size?tag=dev&label=with-system-packages&color=%23696969 - :target: https://ghcr.io/sagemath/sage/sage-opensuse-15.4-gcc_11-python3.10-maximal-with-system-packages - -.. |image-opensuse-15.4-gcc_11-python3.10-maximal-configured| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-opensuse-15.4-gcc_11-python3.10-maximal-configured/latest_tag?ignore=latest,dev,*-failed&label=configured&color=%23696969 - :target: https://ghcr.io/sagemath/sage/sage-opensuse-15.4-gcc_11-python3.10-maximal-configured - -.. |image-opensuse-15.4-gcc_11-python3.10-maximal-with-targets-pre| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-opensuse-15.4-gcc_11-python3.10-maximal-with-targets-pre/latest_tag?ignore=latest,dev,*-failed&label=with-targets-pre&color=%238f6b8d - :target: https://ghcr.io/sagemath/sage/sage-opensuse-15.4-gcc_11-python3.10-maximal-with-targets-pre - -.. |image-opensuse-15.4-gcc_11-python3.10-maximal-with-targets| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-opensuse-15.4-gcc_11-python3.10-maximal-with-targets/latest_tag?ignore=latest,dev,*-failed&label=with-targets&color=%23b46eb2 - :target: https://ghcr.io/sagemath/sage/sage-opensuse-15.4-gcc_11-python3.10-maximal-with-targets - -.. |image-opensuse-15.4-gcc_11-python3.10-maximal-with-targets-optional| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-opensuse-15.4-gcc_11-python3.10-maximal-with-targets-optional/latest_tag?ignore=latest,dev,*-failed&label=with-targets-optional&color=%23da70d6 - :target: https://ghcr.io/sagemath/sage/sage-opensuse-15.4-gcc_11-python3.10-maximal-with-targets-optional - -.. |codespace-opensuse-15.4-gcc_11-python3.10-maximal| image:: https://github.com/codespaces/badge.svg - :target: https://codespaces.new/sagemath/sage?devcontainer_path=.devcontainer%2Fportability-opensuse-15.4-gcc_11-python3.10-maximal%2Fdevcontainer.json - .. |image-opensuse-15.5-gcc_11-python3.11-minimal-with-system-packages| image:: https://ghcr-badge.egpl.dev/sagemath/sage/sage-opensuse-15.5-gcc_11-python3.11-minimal-with-system-packages/size?tag=dev&label=with-system-packages&color=%23696969 :target: https://ghcr.io/sagemath/sage/sage-opensuse-15.5-gcc_11-python3.11-minimal-with-system-packages @@ -2382,17 +2436,6 @@ * - Platform - Images - - * - **ubuntu**-trusty-toolchain-gcc_9 - -    ‑*minimal* - - |image-ubuntu-trusty-toolchain-gcc_9-minimal-with-system-packages| |image-ubuntu-trusty-toolchain-gcc_9-minimal-with-targets-pre| |image-ubuntu-trusty-toolchain-gcc_9-minimal-with-targets| |image-ubuntu-trusty-toolchain-gcc_9-minimal-with-targets-optional| - - |codespace-ubuntu-trusty-toolchain-gcc_9-minimal| - * -    ‑*standard* - - |image-ubuntu-trusty-toolchain-gcc_9-standard-with-system-packages| |image-ubuntu-trusty-toolchain-gcc_9-standard-with-targets-pre| |image-ubuntu-trusty-toolchain-gcc_9-standard-with-targets| |image-ubuntu-trusty-toolchain-gcc_9-standard-with-targets-optional| - - |codespace-ubuntu-trusty-toolchain-gcc_9-standard| - * -    ‑*maximal* - - |image-ubuntu-trusty-toolchain-gcc_9-maximal-with-system-packages| |image-ubuntu-trusty-toolchain-gcc_9-maximal-with-targets-pre| - - * - **ubuntu**-xenial-toolchain-gcc_9    ‑*minimal* @@ -2459,6 +2502,17 @@ * -    ‑*maximal* - |image-ubuntu-mantic-maximal-with-system-packages| |image-ubuntu-mantic-maximal-with-targets-pre| - + * - **ubuntu**-noble + +    ‑*minimal* + - |image-ubuntu-noble-minimal-with-system-packages| |image-ubuntu-noble-minimal-with-targets-pre| |image-ubuntu-noble-minimal-with-targets| |image-ubuntu-noble-minimal-with-targets-optional| + - |codespace-ubuntu-noble-minimal| + * -    ‑*standard* + - |image-ubuntu-noble-standard-with-system-packages| |image-ubuntu-noble-standard-with-targets-pre| |image-ubuntu-noble-standard-with-targets| |image-ubuntu-noble-standard-with-targets-optional| + - |codespace-ubuntu-noble-standard| + * -    ‑*maximal* + - |image-ubuntu-noble-maximal-with-system-packages| |image-ubuntu-noble-maximal-with-targets-pre| + - * - **debian**-buster-gcc_spkg    ‑*minimal* @@ -2580,6 +2634,17 @@ * -    ‑*maximal* - |image-linuxmint-21.2-maximal-with-system-packages| |image-linuxmint-21.2-maximal-with-targets-pre| - + * - **linuxmint**-21.3 + +    ‑*minimal* + - |image-linuxmint-21.3-minimal-with-system-packages| |image-linuxmint-21.3-minimal-with-targets-pre| |image-linuxmint-21.3-minimal-with-targets| |image-linuxmint-21.3-minimal-with-targets-optional| + - |codespace-linuxmint-21.3-minimal| + * -    ‑*standard* + - |image-linuxmint-21.3-standard-with-system-packages| |image-linuxmint-21.3-standard-with-targets-pre| |image-linuxmint-21.3-standard-with-targets| |image-linuxmint-21.3-standard-with-targets-optional| + - |codespace-linuxmint-21.3-standard| + * -    ‑*maximal* + - |image-linuxmint-21.3-maximal-with-system-packages| |image-linuxmint-21.3-maximal-with-targets-pre| + - * - **fedora**-30    ‑*minimal* @@ -2690,6 +2755,17 @@ * -    ‑*maximal* - |image-fedora-39-maximal-with-system-packages| |image-fedora-39-maximal-with-targets-pre| - + * - **fedora**-40 + +    ‑*minimal* + - |image-fedora-40-minimal-with-system-packages| |image-fedora-40-minimal-with-targets-pre| |image-fedora-40-minimal-with-targets| |image-fedora-40-minimal-with-targets-optional| + - |codespace-fedora-40-minimal| + * -    ‑*standard* + - |image-fedora-40-standard-with-system-packages| |image-fedora-40-standard-with-targets-pre| |image-fedora-40-standard-with-targets| |image-fedora-40-standard-with-targets-optional| + - |codespace-fedora-40-standard| + * -    ‑*maximal* + - |image-fedora-40-maximal-with-system-packages| |image-fedora-40-maximal-with-targets-pre| + - * - **centos**-7-devtoolset-gcc_11    ‑*minimal* @@ -2767,6 +2843,17 @@ * -    ‑*maximal* - |image-gentoo-python3.11-maximal-with-system-packages| |image-gentoo-python3.11-maximal-with-targets-pre| - + * - **gentoo**-python3.12 + +    ‑*minimal* + - |image-gentoo-python3.12-minimal-with-system-packages| |image-gentoo-python3.12-minimal-with-targets-pre| |image-gentoo-python3.12-minimal-with-targets| |image-gentoo-python3.12-minimal-with-targets-optional| + - |codespace-gentoo-python3.12-minimal| + * -    ‑*standard* + - |image-gentoo-python3.12-standard-with-system-packages| |image-gentoo-python3.12-standard-with-targets-pre| |image-gentoo-python3.12-standard-with-targets| |image-gentoo-python3.12-standard-with-targets-optional| + - |codespace-gentoo-python3.12-standard| + * -    ‑*maximal* + - |image-gentoo-python3.12-maximal-with-system-packages| |image-gentoo-python3.12-maximal-with-targets-pre| + - * - **archlinux**-latest    ‑*minimal* @@ -2778,28 +2865,6 @@ * -    ‑*maximal* - |image-archlinux-latest-maximal-with-system-packages| |image-archlinux-latest-maximal-with-targets-pre| - - * - **opensuse**-15.3-gcc_11-python3.9 - -    ‑*minimal* - - |image-opensuse-15.3-gcc_11-python3.9-minimal-with-system-packages| |image-opensuse-15.3-gcc_11-python3.9-minimal-with-targets-pre| |image-opensuse-15.3-gcc_11-python3.9-minimal-with-targets| |image-opensuse-15.3-gcc_11-python3.9-minimal-with-targets-optional| - - |codespace-opensuse-15.3-gcc_11-python3.9-minimal| - * -    ‑*standard* - - |image-opensuse-15.3-gcc_11-python3.9-standard-with-system-packages| |image-opensuse-15.3-gcc_11-python3.9-standard-with-targets-pre| |image-opensuse-15.3-gcc_11-python3.9-standard-with-targets| |image-opensuse-15.3-gcc_11-python3.9-standard-with-targets-optional| - - |codespace-opensuse-15.3-gcc_11-python3.9-standard| - * -    ‑*maximal* - - |image-opensuse-15.3-gcc_11-python3.9-maximal-with-system-packages| |image-opensuse-15.3-gcc_11-python3.9-maximal-with-targets-pre| - - - * - **opensuse**-15.4-gcc_11-python3.10 - -    ‑*minimal* - - |image-opensuse-15.4-gcc_11-python3.10-minimal-with-system-packages| |image-opensuse-15.4-gcc_11-python3.10-minimal-with-targets-pre| |image-opensuse-15.4-gcc_11-python3.10-minimal-with-targets| |image-opensuse-15.4-gcc_11-python3.10-minimal-with-targets-optional| - - |codespace-opensuse-15.4-gcc_11-python3.10-minimal| - * -    ‑*standard* - - |image-opensuse-15.4-gcc_11-python3.10-standard-with-system-packages| |image-opensuse-15.4-gcc_11-python3.10-standard-with-targets-pre| |image-opensuse-15.4-gcc_11-python3.10-standard-with-targets| |image-opensuse-15.4-gcc_11-python3.10-standard-with-targets-optional| - - |codespace-opensuse-15.4-gcc_11-python3.10-standard| - * -    ‑*maximal* - - |image-opensuse-15.4-gcc_11-python3.10-maximal-with-system-packages| |image-opensuse-15.4-gcc_11-python3.10-maximal-with-targets-pre| - - * - **opensuse**-15.5-gcc_11-python3.11    ‑*minimal* diff --git a/src/doc/en/developer/portability_testing.rst b/src/doc/en/developer/portability_testing.rst index c7925b3392a..55cefd0bf60 100644 --- a/src/doc/en/developer/portability_testing.rst +++ b/src/doc/en/developer/portability_testing.rst @@ -349,7 +349,7 @@ Generating dockerfiles Sage also provides a script for generating a ``Dockerfile``, which is a recipe for automatically building a new image:: - [mkoeppe@sage sage]$ build/bin/write-dockerfile.sh debian ":standard: :optional:" > Dockerfile + [mkoeppe@sage sage]$ .ci/write-dockerfile.sh debian ":standard: :optional:" > Dockerfile (The second argument is passed to ``sage -package list`` to find packages for the listed package types.) @@ -360,7 +360,7 @@ new Docker image. Let us take a quick look at the generated file; this is slightly simplified:: [mkoeppe@sage sage]$ cat Dockerfile - # Automatically generated by SAGE_ROOT/build/bin/write-dockerfile.sh + # Automatically generated by SAGE_ROOT/.ci/write-dockerfile.sh # the :comments: separate the generated file into sections # to simplify writing scripts that customize this file ... diff --git a/src/doc/en/installation/conda.rst b/src/doc/en/installation/conda.rst index 8e514f3a529..49495d60ebf 100644 --- a/src/doc/en/installation/conda.rst +++ b/src/doc/en/installation/conda.rst @@ -138,9 +138,14 @@ Here we assume that you are using a git checkout. - Bootstrap the source tree and install the build prerequisites and the Sage library:: $ ./bootstrap - $ pip install --no-build-isolation -v -v --editable ./pkgs/sage-conf_conda ./pkgs/sage-setup $ pip install --no-build-isolation --config-settings editable_mode=compat -v -v --editable ./src + If you encounter any errors, try to install the ``sage-conf`` package first:: + + $ pip install --no-build-isolation -v -v --editable ./pkgs/sage-conf_conda + + and then run the last command again. + - Verify that Sage has been installed:: $ sage -c 'print(version())' diff --git a/src/doc/en/reference/curves/index.rst b/src/doc/en/reference/curves/index.rst index 2bc0d098cba..fbe7626b4e5 100644 --- a/src/doc/en/reference/curves/index.rst +++ b/src/doc/en/reference/curves/index.rst @@ -1,5 +1,11 @@ +Plane and Space Curves +====================== + +Sage enables computations with curves in affine and projective ambient spaces, +curves over `\CC` as Riemann surfaces, and Jacobians of projective curves. + Curves -====== +------ .. toctree:: :maxdepth: 1 @@ -7,15 +13,14 @@ Curves sage/schemes/curves/constructor sage/schemes/curves/curve sage/schemes/curves/affine_curve + sage/schemes/curves/plane_curve_arrangement sage/schemes/curves/projective_curve sage/schemes/curves/point sage/schemes/curves/closed_point sage/schemes/curves/zariski_vankampen - sage/schemes/jacobians/abstract_jacobian - Plane conics -============ +------------ .. toctree:: :maxdepth: 1 @@ -28,7 +33,7 @@ Plane conics sage/schemes/plane_conics/con_rational_function_field Plane quartics -========================= +-------------- .. toctree:: :maxdepth: 1 @@ -37,11 +42,19 @@ Plane quartics sage/schemes/plane_quartics/quartic_generic Riemann surfaces -================ +---------------- .. toctree:: :maxdepth: 1 sage/schemes/riemann_surfaces/riemann_surface +Jacobians +--------- + +.. toctree:: + :maxdepth: 1 + + sage/schemes/jacobians/abstract_jacobian + .. include:: ../footer.txt diff --git a/src/doc/en/reference/discrete_geometry/index.rst b/src/doc/en/reference/discrete_geometry/index.rst index 7c0fb57bb1e..c64c6d9cd8a 100644 --- a/src/doc/en/reference/discrete_geometry/index.rst +++ b/src/doc/en/reference/discrete_geometry/index.rst @@ -12,6 +12,7 @@ Hyperplane arrangements :maxdepth: 1 sage/geometry/hyperplane_arrangement/arrangement + sage/geometry/hyperplane_arrangement/ordered_arrangement sage/geometry/hyperplane_arrangement/library sage/geometry/hyperplane_arrangement/hyperplane sage/geometry/hyperplane_arrangement/affine_subspace diff --git a/src/doc/en/reference/function_fields/index.rst b/src/doc/en/reference/function_fields/index.rst index 9f9a7e8c42d..7100f300b47 100644 --- a/src/doc/en/reference/function_fields/index.rst +++ b/src/doc/en/reference/function_fields/index.rst @@ -38,6 +38,19 @@ algebraic closure of `\QQ`. A basic reference for the theory of algebraic function fields is [Stich2009]_. +Jacobians of function fields +---------------------------- + +Arithmetic in Jacobians of function fields are available in two flavors. + +.. toctree:: + :maxdepth: 1 + + sage/rings/function_field/jacobian_base + sage/rings/function_field/jacobian_hess + sage/rings/function_field/jacobian_khuri_makdisi + sage/rings/function_field/khuri_makdisi + A Support Module ---------------- diff --git a/src/doc/en/reference/game_theory/index.rst b/src/doc/en/reference/game_theory/index.rst index eb7dab7193c..8b2fe6f250b 100644 --- a/src/doc/en/reference/game_theory/index.rst +++ b/src/doc/en/reference/game_theory/index.rst @@ -8,7 +8,6 @@ Game Theory sage/game_theory/matching_game sage/game_theory/normal_form_game sage/game_theory/catalog_normal_form_games - sage/game_theory/gambit_docs sage/game_theory/parser .. include:: ../footer.txt diff --git a/src/doc/en/reference/homology/index.rst b/src/doc/en/reference/homology/index.rst index 8188233a52b..e1f2adabb5e 100644 --- a/src/doc/en/reference/homology/index.rst +++ b/src/doc/en/reference/homology/index.rst @@ -19,6 +19,5 @@ computing homology groups. sage/homology/algebraic_topological_model sage/homology/homology_morphism sage/homology/matrix_utils - sage/interfaces/chomp .. include:: ../footer.txt diff --git a/src/doc/en/reference/misc/index.rst b/src/doc/en/reference/misc/index.rst index 0b660cd9732..bdc30927a55 100644 --- a/src/doc/en/reference/misc/index.rst +++ b/src/doc/en/reference/misc/index.rst @@ -219,7 +219,6 @@ Distribution :maxdepth: 1 sage/misc/package - sage/misc/dist Credits diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 9eff350a0fc..228e25b2f82 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -695,6 +695,11 @@ REFERENCES: *Finite hypergeometric functions*, :arxiv:`1505.02900` +.. [BEdG2009] Dietrich Burde, Bettina Eick, and Willem de Graaf. + *Computing faithful representations for nilpotent Lie algebras*. + J. Algebra, **322** no. 3 (2009) pp. 602-612. + doi:`10.1016/j.jalgebra.2009.04.041`, arxiv:`0807.2345`. + .. [Bee] Robert A. Beezer, *A First Course in Linear Algebra*, http://linear.ups.edu/. Accessed 15 July 2010. @@ -1814,6 +1819,10 @@ REFERENCES: .. [Com2019] Camille Combe, *Réalisation cubique du poset des intervalles de Tamari*, preprint :arxiv:`1904.00658` +.. [Com1974] Comtet Louis, *Identities and Expansions*. + In: Advanced Combinatorics. 1974. pp. 127-175 + :doi:`10.1007/978-94-010-2196-8_3` + .. [Con] Keith Conrad, *Groups of order 12*, http://www.math.uconn.edu/~kconrad/blurbs/grouptheory/group12.pdf, accessed 21 October 2009. @@ -3176,6 +3185,9 @@ REFERENCES: Cryptanalysis* ; 2002' available at http://www.engr.mun.ca/~howard/PAPERS/ldc_tutorial.pdf +.. [Hes2004] Florian Hess, "Computing relations in divisor class groups of + algebraic curves over finite fields," Preprint, 2004. + .. [Hes2002] Florian Hess, "Computing Riemann-Roch spaces in algebraic function fields and related topics," J. Symbolic Comput. 33 (2002), no. 4, 425--445. @@ -3771,6 +3783,9 @@ REFERENCES: block cipher*, Lightweight Cryptography Workshop, 2016. https://www.nist.gov/sites/default/files/documents/2016/10/18/karpman-paper-lwc2016.pdf +.. [Khu2004] \K. Khuri-Makdisi. *Linear algebra algorithms for divisors on an algebraic curve*, + Mathematics of Computation 73, no. 245 (2004) pp. 333-357. + .. [Kin1992] Nancy G. Kinnersley, *The vertex separation number of a graph equals its path-width*, Information Processing Letters 42(6):345-350, 1992. :doi:`10.1016/0020-0190(92)90234-M`. @@ -4202,6 +4217,10 @@ REFERENCES: .. [Lei2013] Tom Leinster, *The magnitude of metric spaces*. Doc. Math. 18 (2013), 857-905. +.. [Ler2022] Antonin Leroux: *Quaternion algebras and isogeny-based cryptography*, + PhD Thesis, 2022. + https://www.lix.polytechnique.fr/Labo/Antonin.LEROUX/manuscrit_these.pdf + .. [Lev2014] Lionel Levine. *Threshold state and a conjecture of Poghosyan, Poghosyan, Priezzhev and Ruelle*, Communications in Mathematical Physics. @@ -4984,6 +5003,11 @@ REFERENCES: .. [Nie] Johan S. R. Nielsen, Codinglib, https://bitbucket.org/jsrn/codinglib/. +.. [Niez1998] Marek Niezgoda. + Group majorization and Schur type inequalities. + Linear Algebra and its Applications, 268(1):9-30, 1998. + :doi:`10.1016/S0024-3795(97)89322-6`. + .. [NW1978] \A. Nijenhuis and H. Wilf, Combinatorial Algorithms, Academic Press (1978). @@ -5556,6 +5580,10 @@ REFERENCES: imaginary quadratic fields. Invent. Math. 103 (1991), no. 1, 25--68. +.. [RS2010] RUBIN, K., & SILVERBERG, A. (2010). CHOOSING THE CORRECT ELLIPTIC + CURVE IN THE CM METHOD. Mathematics of Computation, 79(269), + 545–561. :doi:`10.1090/S0025-5718-09-02266-2` + .. [Rud1958] \M. E. Rudin. *An unshellable triangulation of a tetrahedron*. Bull. Amer. Math. Soc. 64 (1958), 90-91. diff --git a/src/doc/en/thematic_tutorials/coercion_and_categories.rst b/src/doc/en/thematic_tutorials/coercion_and_categories.rst index edd89ad3c42..afa83bfa754 100644 --- a/src/doc/en/thematic_tutorials/coercion_and_categories.rst +++ b/src/doc/en/thematic_tutorials/coercion_and_categories.rst @@ -837,7 +837,7 @@ The four axioms requested for coercions sage: ZZ(P2.gen(1)) Traceback (most recent call last): ... - TypeError: not a constant polynomial + TypeError: v is not a constant polynomial Hence, we only have a *partial* map. This is fine for a *conversion*, but a partial map does not qualify as a *coercion*. diff --git a/src/doc/en/tutorial/programming.rst b/src/doc/en/tutorial/programming.rst index 57dac68ae1c..7e4fd9b3468 100644 --- a/src/doc/en/tutorial/programming.rst +++ b/src/doc/en/tutorial/programming.rst @@ -166,7 +166,6 @@ etc: #!/usr/bin/env sage import sys - from sage.all import * if len(sys.argv) != 2: print("Usage: %s " % sys.argv[0]) diff --git a/src/doc/fr/tutorial/programming.rst b/src/doc/fr/tutorial/programming.rst index f109e121414..32f465cfc5c 100644 --- a/src/doc/fr/tutorial/programming.rst +++ b/src/doc/fr/tutorial/programming.rst @@ -176,7 +176,6 @@ entiers, des polynômes, etc. : #!/usr/bin/env sage import sys - from sage.all import * if len(sys.argv) != 2: print("Usage: %s " % sys.argv[0]) diff --git a/src/doc/ja/tutorial/programming.rst b/src/doc/ja/tutorial/programming.rst index df8d6d4112c..1232c1bf1e6 100644 --- a/src/doc/ja/tutorial/programming.rst +++ b/src/doc/ja/tutorial/programming.rst @@ -140,10 +140,9 @@ Cythonソースファイルから生成されたC言語コードをコンパイ :: - #!/usr/bin/env sage -python + #!/usr/bin/env sage import sys - from sage.all import * if len(sys.argv) != 2: print("Usage: %s " % sys.argv[0]) diff --git a/src/doc/pt/tutorial/programming.rst b/src/doc/pt/tutorial/programming.rst index 08c0a4713e3..ea1d6b2e348 100644 --- a/src/doc/pt/tutorial/programming.rst +++ b/src/doc/pt/tutorial/programming.rst @@ -192,7 +192,6 @@ O seguinte script em Sage fatora inteiros, polinômios, etc: #!/usr/bin/env sage import sys - from sage.all import * if len(sys.argv) != 2: print("Usage: %s " % sys.argv[0]) diff --git a/src/doc/ru/tutorial/programming.rst b/src/doc/ru/tutorial/programming.rst index 826d3b6df4a..0ea10634c0b 100644 --- a/src/doc/ru/tutorial/programming.rst +++ b/src/doc/ru/tutorial/programming.rst @@ -162,7 +162,6 @@ C и обработан компилятором C. #!/usr/bin/env sage import sys - from sage.all import * if len(sys.argv) != 2: print("Usage: %s " % sys.argv[0]) diff --git a/src/pyproject.toml b/src/pyproject.toml new file mode 100644 index 00000000000..781af00a8d7 --- /dev/null +++ b/src/pyproject.toml @@ -0,0 +1,26 @@ +[build-system] +# Minimum requirements for the build system to execute. +requires = [ + # 68.1.0 Promote pyproject.toml's [tool.setuptools] out of beta. + # 68.1.1 Fix editable install finder handling of nested packages + 'setuptools >= 68.1.1', + # version constraint for macOS Big Sur support (see https://github.com/sagemath/sage/issues/31050) + 'wheel >=0.36.2', + 'cypari2 >=2.1.1', + 'cysignals >=1.10.2', + # Exclude 3.0.3 because of https://github.com/cython/cython/issues/5748 + 'cython >=3.0, != 3.0.3, <4.0', + 'gmpy2 ~=2.1.b999', + 'jupyter_core >=4.6.3', + 'memory_allocator', + 'numpy >=1.19', + 'pkgconfig', + # pplpy 0.8.4 and earlier do not declare dependencies correctly (see https://github.com/sagemath/sage/issues/30922) + 'pplpy >=0.8.6', +] +build-backend = "setuptools.build_meta" + +[tool.conda-lock] +platforms = [ + 'osx-64', 'linux-64', 'linux-aarch64', 'osx-arm64' +] diff --git a/src/pyproject.toml.m4 b/src/pyproject.toml.m4 deleted file mode 100644 index 7065ba1468c..00000000000 --- a/src/pyproject.toml.m4 +++ /dev/null @@ -1,29 +0,0 @@ -[build-system] -# Minimum requirements for the build system to execute. -requires = [ - "sage_setup[autogen]", - # Some version of sage-conf is required. - # Note that PEP517/518 have no notion of optional sage_spkg dependencies: - # https://github.com/pypa/pip/issues/6144 - esyscmd(`sage-get-system-packages install-requires-toml \ - sage_conf \ - setuptools \ - wheel \ - sage_setup \ - cypari \ - cysignals \ - cython \ - gmpy2 \ - jinja2 \ - jupyter_core \ - numpy \ - pkgconfig \ - pplpy \ - memory_allocator \ - ')] -build-backend = "setuptools.build_meta" - -[tool.conda-lock] -platforms = [ - 'osx-64', 'linux-64', 'linux-aarch64', 'osx-arm64' -] diff --git a/src/sage/algebras/lie_algebras/classical_lie_algebra.py b/src/sage/algebras/lie_algebras/classical_lie_algebra.py index 01dd668e711..fae28403d4f 100644 --- a/src/sage/algebras/lie_algebras/classical_lie_algebra.py +++ b/src/sage/algebras/lie_algebras/classical_lie_algebra.py @@ -1700,10 +1700,53 @@ def _construct_struct_coeffs(self, R, p_roots): [((alpha[1], -alpha[1]), {alphacheck[1]: 1}), ((alpha[1], alphacheck[1]), {alpha[1]: -2}), ((alphacheck[1], -alpha[1]), {-alpha[1]: -2})] + + TESTS: + + Check that we can construct Lie algebras over positive characteristic + fields (:issue:`37773`):: + + sage: sl3 = LieAlgebra(GF(3), cartan_type=['A',2]) + sage: sl3.center().basis() + Family (2*h1 + h2,) + sage: sl4 = lie_algebras.sl(GF(3), 4) + sage: sl4.center().dimension() + 0 + sage: sl4.is_nilpotent() + False + sage: sl4.lower_central_series() + (Lie algebra of ['A', 3] in the Chevalley basis,) + sage: sl4.is_solvable() + False + sage: sl4.is_semisimple() + True + sage: sl4.killing_form_matrix().det() + 2 + sage: sl5 = lie_algebras.sl(GF(3), 5) + sage: sl5.killing_form_matrix().det() + 2 + + This also includes characteristic 2:: + + sage: sl4 = LieAlgebra(GF(2), cartan_type=['A',3]) + sage: sl4.center().basis() + Family (h1 + h3,) + sage: sp6 = LieAlgebra(GF(2), cartan_type=['C',3]) + sage: sp6.killing_form_matrix().det() + 0 + sage: F4 = LieAlgebra(GF(2), cartan_type=['F',4]) + sage: F4.killing_form_matrix().det() # long time + 0 + sage: G2 = LieAlgebra(GF(2), cartan_type=['G',2]) + sage: G2.killing_form_matrix().det() + 0 """ alphacheck = self._Q.simple_coroots() roots = frozenset(self._Q.roots()) - one = R.one() + # We do everything initially over QQ and then convert to R at the end + # since this is a ZZ-basis. + from sage.rings.rational_field import QQ + one = QQ.one() # Determine the signs for the structure coefficients from the root system # We first create the special roots @@ -1755,15 +1798,15 @@ def e_coeff(r, s): for i,r in enumerate(p_roots): # [e_r, h_i] and [h_i, f_r] for ac in alphacheck: - c = r.scalar(ac) + c = R(r.scalar(ac)) if c == 0: continue s_coeffs[(r, ac)] = {r: -c} s_coeffs[(ac, -r)] = {-r: -c} # [e_r, f_r] - s_coeffs[(r, -r)] = {alphacheck[j]: c - for j, c in r.associated_coroot()} + s_coeffs[(r, -r)] = {alphacheck[j]: Rc + for j, c in r.associated_coroot() if (Rc := R(c))} # [e_r, e_s] and [e_r, f_s] with r != +/-s # We assume s is positive, as otherwise we negate @@ -1779,16 +1822,19 @@ def e_coeff(r, s): c *= -sp_sign[(b, a)] else: c *= sp_sign[(a, b)] - s_coeffs[(-r, s)] = {a: -c} - s_coeffs[(r, -s)] = {-a: c} + c = R(c) + if c: + s_coeffs[(-r, s)] = {a: -c} + s_coeffs[(r, -s)] = {-a: c} # [e_r, e_s] a = r + s if a in p_roots: # (r, s) is a special pair - c = e_coeff(r, s) * sp_sign[(r, s)] - s_coeffs[(r, s)] = {a: c} - s_coeffs[(-r, -s)] = {-a: -c} + c = R(e_coeff(r, s) * sp_sign[(r, s)]) + if c: + s_coeffs[(r, s)] = {a: c} + s_coeffs[(-r, -s)] = {-a: -c} return s_coeffs diff --git a/src/sage/algebras/lie_algebras/quotient.py b/src/sage/algebras/lie_algebras/quotient.py index e744cfbd252..db9831cbdd4 100644 --- a/src/sage/algebras/lie_algebras/quotient.py +++ b/src/sage/algebras/lie_algebras/quotient.py @@ -218,8 +218,10 @@ def __classcall_private__(cls, I, ambient=None, names=None, index_set = [i for i in sorted_indices if i not in I_supp] if names is None: - amb_names = dict(zip(sorted_indices, ambient.variable_names())) - names = [amb_names[i] for i in index_set] + if ambient._names is not None: + # ambient has assigned variable names, so use those + amb_names = dict(zip(sorted_indices, ambient.variable_names())) + names = [amb_names[i] for i in index_set] elif isinstance(names, str): if len(index_set) == 1: names = [names] @@ -252,7 +254,7 @@ def __init__(self, I, L, names, index_set, category=None): B = L.basis() sm = L.module().submodule_with_basis([I.reduce(B[i]).to_vector() for i in index_set]) - SB = sm.basis() + SB = [L.from_vector(b) for b in sm.basis()] # compute and normalize structural coefficients for the quotient s_coeff = {} @@ -260,7 +262,7 @@ def __init__(self, I, L, names, index_set, category=None): for j in range(i + 1, len(index_set)): ind_j = index_set[j] - brkt = I.reduce(L.bracket(SB[i], SB[j])) + brkt = I.reduce(SB[i].bracket(SB[j])) brktvec = sm.coordinate_vector(brkt.to_vector()) s_coeff[(ind_i, ind_j)] = dict(zip(index_set, brktvec)) s_coeff = LieAlgebraWithStructureCoefficients._standardize_s_coeff( @@ -291,11 +293,26 @@ def _repr_(self): L: General linear Lie algebra of rank 2 over Rational Field I: Ideal ([0 0] [0 1]) + + sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() + sage: a,b,c = L.gens() + sage: I = L.ideal([a + 2*b, b + 3*c]) + sage: Q = L.quotient(I) + sage: Q + Lie algebra quotient L/I of dimension 1 over Rational Field where + L: An example of a finite dimensional Lie algebra with basis: + the 3-dimensional abelian Lie algebra over Rational Field + I: Ideal ((1, 0, -6), (0, 1, 3)) """ + try: + ideal_repr = self._I._repr_short() + except AttributeError: + ideal_repr = repr(tuple(self._I.gens())) + return ("Lie algebra quotient L/I of dimension %s" " over %s where\nL: %s\nI: Ideal %s" % ( self.dimension(), self.base_ring(), - self.ambient(), self._I._repr_short())) + self.ambient(), ideal_repr)) def _repr_generator(self, i): r""" diff --git a/src/sage/algebras/lie_algebras/representation.py b/src/sage/algebras/lie_algebras/representation.py index 9d96c42c6fc..c6bd62dd0cc 100644 --- a/src/sage/algebras/lie_algebras/representation.py +++ b/src/sage/algebras/lie_algebras/representation.py @@ -16,7 +16,10 @@ # https://www.gnu.org/licenses/ # **************************************************************************** +from sage.misc.lazy_attribute import lazy_attribute +from sage.misc.cachefunc import cached_method from sage.sets.family import Family, AbstractFamily +from sage.matrix.constructor import matrix from sage.combinat.free_module import CombinatorialFreeModule from sage.categories.modules import Modules from copy import copy @@ -94,6 +97,44 @@ def _test_representation(self, **options): for v in S: tester.assertEqual(x.bracket(y) * v, x * (y * v) - y * (x * v)) + def representation_matrix(self, elt): + """ + Return the matrix for the action of ``elt`` on ``self``. + + EXAMPLES:: + + sage: H1 = lie_algebras.Heisenberg(QQ, 1) + sage: F = H1.faithful_representation(algorithm="minimal") + sage: P1 = F.representation_matrix(H1.gen(0)); P1 + [0 0 0] + [0 0 0] + [1 0 0] + sage: Q1 = F.representation_matrix(H1.gen(1)); Q1 + [ 0 0 0] + [ 0 0 -1] + [ 0 0 0] + sage: Z = P1.commutator(Q1); Z + [0 0 0] + [1 0 0] + [0 0 0] + sage: P1.commutator(Z) == Q1.commutator(Z) == 0 + True + sage: (H1.gen(0) * F.an_element()).to_vector() + (0, 0, 2) + sage: P1 * F.an_element().to_vector() + (0, 0, 2) + sage: (H1.gen(1) * F.an_element()).to_vector() + (0, -3, 0) + sage: Q1 * F.an_element().to_vector() + (0, -3, 0) + sage: (H1.basis()['z'] * F.an_element()).to_vector() + (0, 2, 0) + sage: Z * F.an_element().to_vector() + (0, 2, 0) + """ + B = self.basis() + return matrix([(elt * B[k]).to_vector() for k in self.get_order()]).transpose() + class RepresentationByMorphism(CombinatorialFreeModule, Representation_abstract): r""" @@ -433,3 +474,320 @@ def _acted_upon_(self, scalar, self_on_left=False): return None return P.zero() return super()._acted_upon_(scalar, self_on_left) + + +class FaithfulRepresentationNilpotentPBW(CombinatorialFreeModule, Representation_abstract): + r""" + Return a faithful reprensetation of a nilpotent Lie algebra + constructed using the PBW basis. + + Let `L` be a `k`-step nilpotent Lie algebra. Define a weight function + on elements in `L` by the lower central series of `L`. Then a faithful + representation of `L` is `U(L) / U(L)^{k+1}`, where `U(L)^{k+1}` + is the (twosided) ideal of `U(L)` generated by all monomials + of weight at least `k + 1`. + + We can also expand the ideal keeping the property that `I \cap Z(L) = 0`. + The resulting quotient `U(L) / I` remains faithful and is a minimal + faithful representation of `L` in the sense that it has no faithful + submodules or quotients. (Note: this is not necessarily the smallest + dimensional faithful representation of `L`.) + + We consider an example of the rank 2 Heisenberg Lie algebra, + but with a non-standard basis given by `a = p_1 + z`, `b = q_1`, + and `c = q_1 + z`:: + + sage: scoeffs = {('a','b'): {'b':-1, 'c':1}, ('a','c'): {'b':-1, 'c':1}} + sage: L. = LieAlgebra(QQ, scoeffs) + sage: TestSuite(L).run(elements=list(L.basis())) + sage: L.is_nilpotent() + True + sage: L.derived_series() + (Lie algebra on 3 generators (a, b, c) over Rational Field, + Ideal (b - c) of Lie algebra on 3 generators (a, b, c) over Rational Field, + Ideal () of Lie algebra on 3 generators (a, b, c) over Rational Field) + sage: F = L.faithful_representation() + sage: L.an_element() * F.an_element() + 2*F[1, 0, 0] + 8*F[1, 1, 0] + 3*F[2, 0, 0] + 4*F[0, 1, 0] + + 4*F[0, 2, 0] + 4*F[0, 0, 1] + + sage: MF = L.faithful_representation(algorithm="minimal") + sage: MF.dimension() + 3 + sage: [MF.representation_matrix(be) for be in L.basis()] + [ + [0 0 0] [ 0 0 0] [ 0 0 0] + [0 0 0] [ 0 0 -1] [ 1 0 -1] + [1 0 0], [ 0 0 0], [ 0 0 0] + ] + + An example with ``minimal=True`` for `H_2 \oplus A_1`, where `A_1` is + a `1`-dimensional Abelian Lie algebra:: + + sage: scoeffs = {('a','b'): {'b':-1, 'c':1}, ('a','c'): {'b':-1, 'c':1}} + sage: L. = LieAlgebra(QQ, scoeffs) + sage: F = L.faithful_representation(); F + Faithful 11 dimensional representation of Lie algebra on 4 + generators (a, b, c, d) over Rational Field + sage: MF = L.faithful_representation(algorithm="minimal"); MF + Minimal faithful representation of Lie algebra on 4 + generators (a, b, c, d) over Rational Field + sage: MF.dimension() + 4 + + INPUT: + + - ``minimal`` -- boolean (default: ``False``); whether to construct + the minimal basis or not + + REFERENCES: + + - [BEdG2009]_ + """ + def __init__(self, L, minimal=False): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: H2 = lie_algebras.Heisenberg(QQ, 2) + sage: F = H2.faithful_representation() + sage: TestSuite(F).run(elements=list(F.basis())) + sage: MF = H2.faithful_representation(algorithm="minimal") + sage: TestSuite(MF).run(elements=list(MF.basis())) + + sage: sc = {('a','b'): {'b':-1, 'c':1}, ('a','c'): {'b':-1, 'c':1}} + sage: L. = LieAlgebra(QQ, sc) + sage: F = L.faithful_representation() + sage: TestSuite(F).run(elements=list(F.basis())) + sage: MF = L.faithful_representation(algorithm="minimal") + sage: TestSuite(MF).run(elements=list(MF.basis())) + """ + LCS = L.lower_central_series() + if LCS[-1].dimension() != 0: + raise ValueError("the Lie algebra must be nilpotent") + # construct an appropriate basis of L + basis_by_deg = {} + self._step = len(LCS) - 1 + self._minimal = minimal + if self._minimal: + Z = L.center() + ZB = [L(b) for b in Z.basis()] + prev = LCS[-1] + for D in reversed(LCS[:-1]): + cur = [] + for ind in range(len(ZB) - 1, -1, -1): + z = ZB[ind] + if z in D: + ZB.pop(ind) + cur.append(z) + k = self._step - len(basis_by_deg) + basis_by_deg[k] = cur + temp = [bred for b in D.basis() if (bred := Z.reduce(prev.reduce(L(b))))] + basis_by_deg[k].extend(L.echelon_form(temp)) + prev = D + else: + prev = LCS[-1] + for D in reversed(LCS[:-1]): + temp = [L(bred) for b in D.basis() if (bred := prev.reduce(L(b)))] + basis_by_deg[self._step-len(basis_by_deg)] = L.echelon_form(temp) + prev = D + + L_basis = sum((basis_by_deg[deg] for deg in sorted(basis_by_deg)), []) + + if all(len(b.support()) == 1 for b in L_basis): + self._Lp = L + else: + cob = matrix([b._vector_() for b in L_basis]).transpose() + self._invcob = cob.inverse() + scoeffs = {} + for i, b in enumerate(L_basis): + for j, bp in enumerate(L_basis[i+1:], start=i+1): + scoeffs[i, j] = (self._invcob * b.bracket(bp)._vector_()).dict() + index_set = tuple(range(L.dimension())) + from sage.algebras.lie_algebras.lie_algebra import LieAlgebra + self._Lp = LieAlgebra(L.base_ring(), scoeffs, index_set=index_set) + + self._pbw = self._Lp.pbw_basis() + self._degrees = tuple(sum(([deg] * len(B) for deg, B in sorted(basis_by_deg.items())), [])) + + from sage.sets.disjoint_union_enumerated_sets import DisjointUnionEnumeratedSets + from sage.combinat.integer_vector_weighted import WeightedIntegerVectors + indices = DisjointUnionEnumeratedSets([WeightedIntegerVectors(n, self._degrees) + for n in range(self._step+1)]) + + if self._minimal: + X = set([tuple(index) for index in indices]) + monoid = self._pbw._indices + I = monoid._indices + one = L.base_ring().one() + pbw_gens = self._pbw.algebra_generators() + ZB = frozenset([L(b) for b in Z.basis()]) + Zind = [i for i, b in enumerate(L_basis) if b in ZB] + Ztup = set() + for i in Zind: + vec = [0] * L.dimension() + vec[i] = 1 + Ztup.add(tuple(vec)) + + def as_exp(s): + sm = s._monomial + return tuple([sm[i] if i in sm else 0 for i in I]) + + def test_ideal(m, X): + elt = self._pbw.element_class(self._pbw, {monoid(list(zip(I, m))): one}) + for g in pbw_gens: + gelt = g * elt + if any(as_exp(s) in X for s in gelt.support()): + return False + return True + + to_remove = set([None]) + while to_remove: + X -= to_remove + to_remove = set() + for m in X: + m = tuple(m) + if m in Ztup or not test_ideal(m, X): + continue + to_remove.add(m) + indices = sorted(X) + + Representation_abstract.__init__(self, L) + CombinatorialFreeModule.__init__(self, L.base_ring(), indices, prefix='F', bracket=False) + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: H2 = lie_algebras.Heisenberg(QQ, 2) + sage: H2.faithful_representation() + Faithful 16 dimensional representation of Heisenberg algebra + of rank 2 over Rational Field + """ + if self._minimal: + return "Minimal faithful representation of {}".format(self._lie_algebra) + return "Faithful {} dimensional representation of {}".format(self.dimension(), self._lie_algebra) + + def _latex_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: H2 = lie_algebras.Heisenberg(QQ, 2) + sage: latex(H2.faithful_representation()) + U(\text{\texttt{Heisenberg...}}) / U(\text{\texttt{Heisenberg...}})^{3} + """ + from sage.misc.latex import latex + g = latex(self._lie_algebra) + ret = "U({0}) / U({0})^{{{1}}}".format(g, self._step+1) + if self._minimal: + return "\\min " + ret + return ret + + def _project(self, elt): + r""" + The projection to ``self`` from the PBW basis. + + EXAMPLES:: + + sage: sc = {('a','b'): {'b':-1, 'c':1}, ('a','c'): {'b':-1, 'c':1}} + sage: L. = LieAlgebra(QQ, sc) + sage: F = L.faithful_representation() + sage: elt = F._to_pbw(a + b + c)^2; elt + PBW[0]^2 + 4*PBW[0]*PBW[1] - 2*PBW[0]*PBW[2] + 4*PBW[1]^2 + - 4*PBW[1]*PBW[2] + PBW[2]^2 + 2*PBW[2] + sage: F._project(elt) + 2*F[0, 0, 1] + 4*F[0, 2, 0] + 4*F[1, 1, 0] + F[2, 0, 0] + sage: F._project(F._to_pbw(a + b + c)^3) + 0 + """ + ret = {} + I = self._pbw._indices._indices + if self._minimal: + for m, c in elt._monomial_coefficients.items(): + mm = m._monomial + vec = tuple([mm[i] if i in mm else 0 for i in I]) + if vec in self._indices: + ret[self._indices(vec)] = c + else: + for m, c in elt._monomial_coefficients.items(): + mm = m._monomial + vec = [mm[i] if i in mm else 0 for i in I] + if sum(e * d for e, d in zip(vec, self._degrees)) <= self._step: + ret[self._indices(vec)] = c + return self.element_class(self, ret) + + def _to_pbw(self, elt): + """ + Return the PBW element corresponding to ``elt``. + + EXAMPLES:: + + sage: sc = {('a','b'): {'b':-1, 'c':1}, ('a','c'): {'b':-1, 'c':1}} + sage: L. = LieAlgebra(QQ, sc) + sage: F = L.faithful_representation() + sage: F._to_pbw(a) + PBW[0] + sage: F._to_pbw(b) + PBW[1] + sage: F._to_pbw(c) + PBW[1] - PBW[2] + + sage: H2 = lie_algebras.Heisenberg(QQ, 2) + sage: F = H2.faithful_representation() + sage: F._to_pbw(sum(H2.basis())) + PBW['p1'] + PBW['p2'] + PBW['q1'] + PBW['q2'] + PBW['z'] + """ + if self._Lp is self._lie_algebra: + return self._pbw(elt) + return self._pbw(self._Lp.from_vector(self._invcob * elt._vector_())) + + class Element(CombinatorialFreeModule.Element): + def _lift_pbw(self): + """ + Return ``self`` as an element of the PBW basis. + + EXAMPLES:: + + sage: H2 = lie_algebras.Heisenberg(QQ, 2) + sage: F = H2.faithful_representation() + sage: F.an_element()._lift_pbw() + 3*PBW['q1'] + 2*PBW['q2'] + 2 + """ + P = self.parent() + monoid = P._pbw._indices + I = monoid._indices + return P._pbw.element_class(P._pbw, {monoid(list(zip(I, m))): coeff + for m, coeff in self._monomial_coefficients.items()}) + + def _acted_upon_(self, scalar, self_on_left=False): + r""" + Return the action of ``scalar`` on ``self``. + + EXAMPLES:: + + sage: H2 = lie_algebras.Heisenberg(QQ, 2) + sage: F = H2.faithful_representation() + sage: H2.an_element() + p1 + sage: F.an_element() + 2*F[0, 0, 0, 0, 0] + 2*F[0, 0, 0, 1, 0] + 3*F[0, 0, 1, 0, 0] + sage: H2.an_element() * F.an_element() + 2*F[1, 0, 0, 0, 0] + 2*F[1, 0, 0, 1, 0] + 3*F[1, 0, 1, 0, 0] + sage: 5 * F.an_element() + 10*F[0, 0, 0, 0, 0] + 10*F[0, 0, 0, 1, 0] + 15*F[0, 0, 1, 0, 0] + """ + P = self.parent() + if scalar in P._lie_algebra: + if self_on_left: + return None + if not self: # we are (already) the zero vector + return self + scalar = P._lie_algebra(scalar) + return P._project(P._to_pbw(scalar) * self._lift_pbw()) + + return super()._acted_upon_(scalar, self_on_left) diff --git a/src/sage/algebras/lie_algebras/structure_coefficients.py b/src/sage/algebras/lie_algebras/structure_coefficients.py index 6c3eedd0a21..6ef1accb83b 100644 --- a/src/sage/algebras/lie_algebras/structure_coefficients.py +++ b/src/sage/algebras/lie_algebras/structure_coefficients.py @@ -133,7 +133,7 @@ def __classcall_private__(cls, R, s_coeff, names=None, index_set=None, **kwds): from sage.algebras.lie_algebras.abelian import AbelianLieAlgebra return AbelianLieAlgebra(R, names, index_set, **kwds) - if (names is None and len(index_set) <= 1) or len(names) <= 1: + if (names is None and len(index_set) <= 1) or (names is not None and len(names) <= 1): from sage.algebras.lie_algebras.abelian import AbelianLieAlgebra return AbelianLieAlgebra(R, names, index_set, **kwds) diff --git a/src/sage/algebras/lie_algebras/subalgebra.py b/src/sage/algebras/lie_algebras/subalgebra.py index 441b55a9fad..b7b5e2f9866 100644 --- a/src/sage/algebras/lie_algebras/subalgebra.py +++ b/src/sage/algebras/lie_algebras/subalgebra.py @@ -163,7 +163,6 @@ class LieSubalgebra_finite_dimensional_with_basis(Parent, UniqueRepresentation): sage: J.reduce(I(Z) + I(W)) W """ - @staticmethod def __classcall_private__(cls, ambient, gens, ideal=False, order=None, category=None): @@ -193,10 +192,42 @@ def __classcall_private__(cls, ambient, gens, ideal=False, sage: T3 = L.subalgebra([0, 0]) sage: T1 is T2 and T2 is T3 True + + Subalgebras generated by a subalgebra returns the same subalgebra:: + + sage: S1 = L.subalgebra(X) + sage: S2 = L.subalgebra(S1) + sage: S1 is S2 + True + + We can have subalgebras generated by subalgebras:: + + sage: scoeffs = {('a','d'): {'a':1}, ('a','e'): {'b':-1}, + ....: ('b','d'): {'b':1}, ('b','e'): {'a':1}, + ....: ('d','e'): {'c':1}} + sage: L. = LieAlgebra(QQ, scoeffs) + sage: S1 = L.subalgebra(a) + sage: S2 = L.subalgebra([b, e]) + sage: S = L.subalgebra([S1, S2]) + sage: S + Subalgebra generated by (a, a, b, e) of Lie algebra on + 5 generators (a, b, c, d, e) over Rational Field + sage: S.basis() + Family (a, b, e) """ + if isinstance(gens, LieSubalgebra_finite_dimensional_with_basis): + if not ideal or gens._is_ideal: + return gens + gens = gens.lie_algebra_generators() if not isinstance(gens, (list, tuple)): gens = [gens] - gens = tuple(ambient(gen) for gen in gens if not gen.is_zero()) + new_gens = [] + for gen in gens: + if isinstance(gen, LieSubalgebra_finite_dimensional_with_basis): + new_gens.extend(ambient(b) for b in gen.basis()) + elif not gen.is_zero(): + new_gens.append(ambient(gen)) + gens = tuple(new_gens) if not ideal and isinstance(ambient, LieSubalgebra_finite_dimensional_with_basis): @@ -281,8 +312,8 @@ def __contains__(self, x): True """ - if x in self.ambient(): - x = self.ambient()(x) + if x in self._ambient: + x = self._ambient(x) return x.to_vector() in self.module() return super().__contains__(x) @@ -398,7 +429,7 @@ def _element_constructor_(self, x): P = x.parent() if P is self: return x - if P == self.ambient(): + if P == self._ambient: return self.retract(x) except AttributeError: pass @@ -448,7 +479,7 @@ def _to_m(self, X): (2, 3, 1) """ mc = X.monomial_coefficients() - M = self.ambient().module() + M = self._ambient.module() R = M.base_ring() B = M.basis() return M.sum(R(mc[self._reversed_indices[i]]) * B[i] @@ -767,8 +798,8 @@ def from_vector(self, v, order=None, coerce=False): sage: el.parent() == S True """ - if len(v) == self.ambient().dimension(): - return self.retract(self.ambient().from_vector(v)) + if len(v) == self._ambient.dimension(): + return self.retract(self._ambient.from_vector(v)) return super().from_vector(v) def basis_matrix(self): @@ -811,9 +842,9 @@ def module(self, sparse=False): [0 0 3] """ try: - m = self.ambient().module(sparse=sparse) + m = self._ambient.module(sparse=sparse) except TypeError: - m = self.ambient().module() + m = self._ambient.module() ambientbasis = [self.lift(X).to_vector() for X in self.basis()] return m.submodule_with_basis(ambientbasis) @@ -853,75 +884,10 @@ def is_ideal(self, A): sage: L.is_ideal(I) False """ - if A == self.ambient() and self._is_ideal: + if A == self._ambient and self._is_ideal: return True return super().is_ideal(A) - def reduce(self, X): - r""" - Reduce an element of the ambient Lie algebra modulo the - ideal ``self``. - - INPUT: - - - ``X`` -- an element of the ambient Lie algebra - - OUTPUT: - - An element `Y` of the ambient Lie algebra that is contained in a fixed - complementary submodule `V` to ``self`` such that `X = Y` mod ``self``. - - When the base ring of ``self`` is a field, the complementary submodule - `V` is spanned by the elements of the basis that are not the leading - supports of the basis of ``self``. - - EXAMPLES: - - An example reduction in a 6 dimensional Lie algebra:: - - sage: sc = {('a','b'): {'d': 1}, ('a','c'): {'e': 1}, - ....: ('b','c'): {'f': 1}} - sage: L. = LieAlgebra(QQ, sc) - sage: I = L.ideal(c) - sage: I.reduce(a + b + c + d + e + f) - a + b + d - - The reduction of an element is zero if and only if the - element belongs to the subalgebra:: - - sage: I.reduce(c + e) - 0 - sage: c + e in I - True - - Over non-fields, the complementary submodule may not be spanned by - a subset of the basis of the ambient Lie algebra:: - - sage: L. = LieAlgebra(ZZ, {('X','Y'): {'Z': 3}}) - sage: I = L.ideal(Y) - sage: I.basis() - Family (Y, 3*Z) - sage: I.reduce(3*Z) - 0 - sage: I.reduce(Y + 14*Z) - 2*Z - """ - R = self.base_ring() - for Y in self.basis(): - Y = self.lift(Y) - k, c = Y.leading_item(key=self._order) - - if R.is_field(): - X = X - X[k] / c * Y - else: - try: - q, _ = X[k].quo_rem(c) - X = X - q * Y - except AttributeError: - pass - - return X - class Element(LieSubalgebraElementWrapper): def adjoint_matrix(self, sparse=False): """ diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 02d0ccf0326..4077d52de00 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -82,6 +82,8 @@ from sage.categories.algebras import Algebras from sage.categories.number_fields import NumberFields +from sage.structure.richcmp import richcmp_method + ######################################################## # Constructor ######################################################## @@ -688,11 +690,13 @@ def __init__(self, base_ring, a, b, names='i,j,k'): self._gens = (self([0, 1, 0, 0]), self([0, 0, 1, 0]), self([0, 0, 0, 1])) @cached_method - def maximal_order(self, take_shortcuts=True): + def maximal_order(self, take_shortcuts=True, order_basis=None): r""" Return a maximal order in this quaternion algebra. - The algorithm used is from [Voi2012]_. + If ``order_basis`` is specified, the resulting maximal order + will contain the order of the quaternion algebra given by this + basis. The algorithm used is from [Voi2012]_. INPUT: @@ -700,6 +704,9 @@ def maximal_order(self, take_shortcuts=True): prime and the invariants of the algebra are of a nice form, use Proposition 5.2 of [Piz1980]_. + - ``order_basis`` -- (optional, default: ``None``) a basis of an + order of this quaternion algebra + OUTPUT: A maximal order in this quaternion algebra. @@ -756,8 +763,20 @@ def maximal_order(self, take_shortcuts=True): sage: for d in ( m for m in range(1, 750) if is_squarefree(m) ): # long time (3s) ....: A = QuaternionAlgebra(d) - ....: R = A.maximal_order(take_shortcuts=False) - ....: assert A.discriminant() == R.discriminant() + ....: assert A.maximal_order(take_shortcuts=False).is_maximal() + + Specifying an order basis gives an extension of orders:: + + sage: A. = QuaternionAlgebra(-292, -732) + sage: alpha = A.random_element() + sage: while alpha.is_zero(): + ....: alpha = A.random_element() + sage: conj = [alpha * b * alpha.inverse() for b in [k,i,j]] + sage: order_basis = tuple(conj) + (A.one(),) + sage: O = A.quaternion_order(basis=order_basis) + sage: R = A.maximal_order(order_basis=order_basis) + sage: O <= R and R.is_maximal() + True We do not support number fields other than the rationals yet:: @@ -767,9 +786,24 @@ def maximal_order(self, take_shortcuts=True): ... NotImplementedError: maximal order only implemented for rational quaternion algebras + + TESTS: + + Check that :issue:`37417` and the first part of :issue:`37217` are fixed:: + + sage: invars = [(-4, -28), (-292, -732), (-48, -564), (-436, -768), + ....: (-752, -708), (885, 545), (411, -710), (-411, 593), + ....: (805, -591), (-921, 353), (409, 96), (394, 873), + ....: (353, -722), (730, 830), (-466, -427), (-213, -630), + ....: (-511, 608), (493, 880), (105, -709), (-213, 530), + ....: (97, 745)] + sage: all(QuaternionAlgebra(a, b).maximal_order().is_maximal() + ....: for (a, b) in invars) + True """ if self.base_ring() != QQ: - raise NotImplementedError("maximal order only implemented for rational quaternion algebras") + raise NotImplementedError("maximal order only implemented for " + "rational quaternion algebras") d_A = self.discriminant() @@ -778,7 +812,8 @@ def maximal_order(self, take_shortcuts=True): # (every quaternion algebra of prime discriminant has a representation # of such a form though) a, b = self.invariants() - if take_shortcuts and d_A.is_prime() and a in ZZ and b in ZZ: + if (not order_basis and take_shortcuts and d_A.is_prime() + and a in ZZ and b in ZZ): a = ZZ(a) b = ZZ(b) i, j, k = self.gens() @@ -811,16 +846,28 @@ def maximal_order(self, take_shortcuts=True): return self.quaternion_order(basis) # The following code should always work (over QQ) - # Start with <1,i,j,k> - R = self.quaternion_order((1,) + self.gens()) - d_R = R.discriminant() + # If no order basis is given, start with <1,i,j,k> + if not order_basis: + order_basis = (self.one(),) + self.gens() + + try: + R = self.quaternion_order(order_basis) + d_R = R.discriminant() + except (TypeError, ValueError): + raise ValueError('order_basis is not a basis of an order of the' + ' given quaternion algebra') + + # Since Voight's algorithm only works for a starting basis having 1 as + # its first vector, we derive such a basis from the given order basis + basis = basis_for_quaternion_lattice(order_basis, reverse=True) e_new_gens = [] # For each prime at which R is not yet maximal, make it bigger for p, _ in d_R.factor(): - e = R.basis() - while self.quaternion_order(e).discriminant().valuation(p) > d_A.valuation(p): + e = basis + disc = d_R + while disc.valuation(p) > d_A.valuation(p): # Compute a normalized basis at p f = normalize_basis_at_p(list(e), p) @@ -893,13 +940,18 @@ def maximal_order(self, take_shortcuts=True): e_n = basis_for_quaternion_lattice(list(e) + e_n[1:], reverse=True) # e_n now contains elements that locally at p give a bigger order, - # but the basis may be messed up at other primes (it might not even - # be an order). We will join them all together at the end + # but the basis may be messed up at other primes (it might not + # even be an order). We will join them all together at the end e = e_n + # Since e might not define an order at this point, we need to + # manually calculate the updated discriminant + L = [[x.pair(y) for y in e] for x in e] + disc = matrix(QQ, 4, 4, L).determinant().sqrt() + e_new_gens.extend(e[1:]) - e_new = basis_for_quaternion_lattice(list(R.basis()) + e_new_gens, reverse=True) + e_new = basis_for_quaternion_lattice(list(basis) + e_new_gens, reverse=True) return self.quaternion_order(e_new) def order_with_level(self, level): @@ -1190,9 +1242,12 @@ def ramified_primes(self): if not is_RationalField(self.base_ring()): raise ValueError("base field must be the rational numbers") - return sorted([p for p in set([2]).union(prime_divisors(self._a.numerator()), - prime_divisors(self._a.denominator()), prime_divisors(self._b.numerator()), - prime_divisors(self._b.denominator())) if hilbert_symbol(self._a, self._b, p) == -1]) + a, b = self._a, self._b + return sorted(p for p in {2}.union(prime_divisors(a.numerator()), + prime_divisors(a.denominator()), + prime_divisors(b.numerator()), + prime_divisors(b.denominator())) + if hilbert_symbol(self._a, self._b, p) == -1) def is_isomorphic(self, A) -> bool: """ @@ -1472,6 +1527,7 @@ def unpickle_QuaternionAlgebra_v0(*key): return QuaternionAlgebra(*key) +@richcmp_method class QuaternionOrder(Parent): """ An order in a quaternion algebra. @@ -1685,48 +1741,58 @@ def gen(self, n): """ return self.__basis[n] - def __eq__(self, R): + def __richcmp__(self, other, op): """ - Compare orders self and other. + Compare this quaternion order to ``other``. EXAMPLES:: - sage: R = QuaternionAlgebra(-11,-1).maximal_order() + sage: R = QuaternionAlgebra(-1, -11).maximal_order() sage: R == R # indirect doctest True - sage: R == QuaternionAlgebra(-1,-1).maximal_order() - False sage: R == 5 False - sage: Q. = QuaternionAlgebra(-1,-19) + sage: R == QuaternionAlgebra(-1, -7).maximal_order() + False Orders can be equal even if they are defined by different bases (see :issue:`32245`):: + sage: Q. = QuaternionAlgebra(-1, -19) sage: Q.quaternion_order([1,-i,k,j+i*7]) == Q.quaternion_order([1,i,j,k]) True - """ - if not isinstance(R, QuaternionOrder): - return False - return (self.__quaternion_algebra == R.__quaternion_algebra and - self.unit_ideal() == R.unit_ideal()) - def __ne__(self, other): - """ - Compare orders self and other. - - Two orders are equal if they - have the same basis and are in the same quaternion algebra. - - EXAMPLES:: + TESTS:: - sage: R = QuaternionAlgebra(-11,-1).maximal_order() - sage: R != R # indirect doctest + sage: B = QuaternionAlgebra(-1, -11) + sage: i,j,k = B.gens() + sage: O = B.quaternion_order([1,i,j,k]) + sage: O == O + True + sage: R = B.quaternion_order([1,i,(i+j)/2,(1+k)/2]) + sage: O <= R # indirect doctest + True + sage: O >= R False - sage: R != QuaternionAlgebra(-1,-1).maximal_order() + sage: O != R + True + sage: O == R + False + sage: O != O + False + sage: O < O + False + sage: O < R + True + sage: O <= O + True + sage: R >= R True """ - return not self.__eq__(other) + from sage.structure.richcmp import richcmp, op_NE + if not isinstance(other, QuaternionOrder): + return op == op_NE + return richcmp(self.unit_ideal(), other.unit_ideal(), op) def __hash__(self): """ @@ -1894,8 +1960,9 @@ def discriminant(self): sage: type(S.discriminant()) <... 'sage.rings.rational.Rational'> """ - L = [[d.pair(e) for e in self.basis()] for d in self.basis()] - return (MatrixSpace(QQ, 4, 4)(L)).determinant().sqrt() + e = self.basis() + L = [[x.pair(y) for y in e] for x in e] + return matrix(QQ, 4, 4, L).determinant().sqrt() def is_maximal(self) -> bool: r""" @@ -3316,6 +3383,195 @@ def multiply_by_conjugate(self, J): R = self.quaternion_algebra() return R.ideal(basis, check=False) + def pushforward(self, J, side=None): + """ + Compute the ideal which is the pushforward of ``self`` through an ideal ``J``. + + Uses Lemma 2.1.7 of [Ler2022]_. Only works for integral ideals. + + INPUT: + + - ``J`` -- a fractional quaternion ideal with norm coprime to ``self`` and either + the same left order or right order as ``self`` + + - ``side`` -- string (optional, default ``None``) set to ``"left"`` or ``"right"`` to + perform pushforward of left or right ideals respectively. If ``None`` the side + is determined by the matching left or right orders + + OUTPUT: a fractional quaternion ideal + + EXAMPLES:: + + sage: B = QuaternionAlgebra(419) + sage: i,j,k = B.gens() + sage: I1 = B.ideal([1/2 + 3/2*j + 2*k, 1/2*i + j + 3/2*k, 3*j, 3*k]) + sage: I2 = B.ideal([1/2 + 9/2*j, 1/2*i + 9/2*k, 5*j, 5*k]) + sage: I1.left_order() == I2.left_order() + True + sage: I1.pushforward(I2, side="left") + Fractional ideal (1/2 + 3/2*j + 5*k, 1/10*i + 2*j + 39/10*k, 3*j, 15*k) + + TESTS:: + + sage: B = QuaternionAlgebra(419) + sage: i,j,k = B.gens() + sage: O0 = B.maximal_order() + sage: O0.unit_ideal().pushforward(O0.unit_ideal()) + Traceback (most recent call last): + ... + ValueError: self and J have same left and right orders, side of pushforward must be specified + sage: O0.unit_ideal().pushforward(O0.unit_ideal(), "left") + Fractional ideal (1/2 + 1/2*j, 1/2*i + 1/2*k, j, k) + sage: I1 = B.ideal([1/2 + 3/2*j + 2*k, 1/2*i + j + 3/2*k, 3*j, 3*k]) + sage: I2 = B.ideal([1/2 + 9/2*j, 1/2*i + 9/2*k, 5*j, 5*k]) + sage: I1.pushforward(I2) + Fractional ideal (1/2 + 3/2*j + 5*k, 1/10*i + 2*j + 39/10*k, 3*j, 15*k) + sage: I1.pushforward(I2, side="right") + Traceback (most recent call last): + ... + ValueError: self and J must have the same right orders + sage: I1.conjugate().pushforward(I2.conjugate()) + Fractional ideal (1/2 + 3/2*j + 10*k, 1/10*i + 2*j + 39/10*k, 3*j, 15*k) + sage: I1.conjugate().pushforward(I2.conjugate(), side="left") + Traceback (most recent call last): + ... + ValueError: self and J must have the same left orders + sage: I1.pushforward(I1, side="left") + Traceback (most recent call last): + ... + ValueError: self and J must have coprime norms + sage: I3 = B.ideal([1/2 + 13/2*j + 6*k, 1/2*i + 3*j + 13/2*k, 9*j, 9*k]) + sage: I3.pushforward(I3*(1/3), side="left") + Traceback (most recent call last): + ... + NotImplementedError: quaternion ideal pushforward not implemented for non-integral ideals + """ + if not isinstance(J, QuaternionFractionalIdeal_rational): + raise TypeError("can only pushforward through a quaternion ideal") + + if side == "left": + if self.left_order() != J.left_order(): + raise ValueError("self and J must have the same left orders") + if not self.is_integral() or not J.is_integral(): + raise NotImplementedError("quaternion ideal pushforward not implemented for non-integral ideals") + Jnorm = J.norm() + if gcd(self.norm(), Jnorm) != 1: + raise ValueError("self and J must have coprime norms") + return (1 / Jnorm) * (J.conjugate() * self.intersection(J)) + + if side == "right": + if self.right_order() != J.right_order(): + raise ValueError("self and J must have the same right orders") + return self.conjugate().pushforward(J.conjugate(), side="left").conjugate() + + if side is None: + same_left_order = bool(self.left_order() == J.left_order()) + same_right_order = bool(self.right_order() == J.right_order()) + if not same_left_order and not same_right_order: + raise ValueError("self and J must share a left or right order") + if same_left_order and same_right_order: + raise ValueError("self and J have same left and right orders, side of pushforward must be specified") + if same_left_order: + return self.pushforward(J, side="left") + return self.pushforward(J, side="right") + + raise ValueError('side must be "left", "right" or None') + + def pullback(self, J, side=None): + """ + Compute the ideal which is the pullback of ``self`` through an ideal ``J``. + + Uses Lemma 2.1.7 of [Ler2022]_. Only works for integral ideals. + + INPUT: + + - ``J`` -- a fractional quaternion ideal with norm coprime to ``self`` and either + left order equal to the right order of ``self``, or vice versa + + - ``side`` -- string (optional, default ``None``) set to ``"left"`` or ``"right"`` to + perform pullback of left or right ideals respectively. If ``None`` the side + is determined by the matching left and right orders + + OUTPUT: a fractional quaternion ideal + + EXAMPLES:: + + sage: B = QuaternionAlgebra(419) + sage: i,j,k = B.gens() + sage: I1 = B.ideal([1/2 + 3/2*j + 2*k, 1/2*i + j + 3/2*k, 3*j, 3*k]) + sage: I2 = B.ideal([1/2 + 9/2*j, 1/2*i + 9/2*k, 5*j, 5*k]) + sage: I3 = I1.pushforward(I2, side="left") + sage: I3.left_order() == I2.right_order() + True + sage: I3.pullback(I2, side="left") == I1 + True + + TESTS:: + + sage: B = QuaternionAlgebra(419) + sage: i,j,k = B.gens() + sage: O0 = B.maximal_order() + sage: O0.unit_ideal().pullback(O0.unit_ideal()) + Traceback (most recent call last): + ... + ValueError: self and J have same left and right orders, side of pullback must be specified + sage: O0.unit_ideal().pullback(O0.unit_ideal(), "left") + Fractional ideal (1/2 + 1/2*j, 1/2*i + 1/2*k, j, k) + sage: I1 = B.ideal([1/2 + 3/2*j + 2*k, 1/2*i + j + 3/2*k, 3*j, 3*k]) + sage: I2 = B.ideal([1/2 + 15/2*j + 2*k, 1/6*i + 43/3*j + 5/2*k, 15*j, 5*k]) + sage: I2.pullback(I1) + Fractional ideal (1/2 + 5/2*j + 2*k, 1/2*i + 3*j + 5/2*k, 5*j, 5*k) + sage: I2.pullback(I1, side="right") + Traceback (most recent call last): + ... + ValueError: right order of self should be left order of J + sage: I2.conjugate().pullback(I1.conjugate(), side="right") + Fractional ideal (1/2 + 5/2*j + 3*k, 1/2*i + 3*j + 5/2*k, 5*j, 5*k) + sage: I2.conjugate().pullback(I1.conjugate(), side="left") + Traceback (most recent call last): + ... + ValueError: left order of self should be right order of J + sage: I1.pullback(I1.conjugate(), side="left") + Traceback (most recent call last): + ... + ValueError: self and J must have coprime norms + sage: I3 = B.ideal([1/2 + 13/2*j + 6*k, 1/2*i + 3*j + 13/2*k, 9*j, 9*k]) + sage: I3.pullback(I3.conjugate()*(1/3), side="left") + Traceback (most recent call last): + ... + NotImplementedError: quaternion ideal pullback not implemented for non-integral ideals + """ + if not isinstance(J, QuaternionFractionalIdeal_rational): + raise TypeError("can only pullback through a quaternion ideal") + + if side == "left": + if self.left_order() != J.right_order(): + raise ValueError("left order of self should be right order of J") + if not self.is_integral() or not J.is_integral(): + raise NotImplementedError("quaternion ideal pullback not implemented for non-integral ideals") + N = self.norm() + if gcd(N, J.norm()) != 1: + raise ValueError("self and J must have coprime norms") + return J*self + N*J.left_order() + + if side == "right": + if self.right_order() != J.left_order(): + raise ValueError("right order of self should be left order of J") + return self.conjugate().pullback(J.conjugate(), side="left").conjugate() + + if side is None: + is_side_left = bool(self.left_order() == J.right_order()) + is_side_right = bool(self.right_order() == J.left_order()) + if not is_side_left and not is_side_right: + raise ValueError("left order of self must equal right order of J, or vice versa") + if is_side_left and is_side_right: + raise ValueError("self and J have same left and right orders, side of pullback must be specified") + if is_side_left: + return self.pullback(J, side="left") + return self.pullback(J, side="right") + + raise ValueError('side must be "left", "right" or None') + def is_equivalent(self, J, B=10, certificate=False, side=None): r""" Checks whether ``self`` and ``J`` are equivalent as ideals. @@ -3909,7 +4165,18 @@ def normalize_basis_at_p(e, p, B=QuaternionAlgebraElement_abstract.pair): sage: e = [A(1), k, j, 1/2 + 1/2*i + 1/2*j + 1/2*k] sage: normalize_basis_at_p(e, 2) [(1, 0), (1/2 + 1/2*i + 1/2*j + 1/2*k, 0), (-34/105*i - 463/735*j + 71/105*k, 1), - (-34/105*i - 463/735*j + 71/105*k, 1)] + (1/7*i - 8/49*j + 1/7*k, 1)] + + TESTS: + + We check that the second part of :issue:`37217` is fixed:: + + sage: A. = QuaternionAlgebra(-1,-7) + sage: e = [A(1), k, j, 1/2 + 1/2*i + 1/2*j + 1/2*k] + sage: e_norm = normalize_basis_at_p(e, 2) + sage: V = QQ**4 + sage: V.span([V(x.coefficient_tuple()) for (x,_) in e_norm]).dimension() + 4 """ N = len(e) @@ -3957,8 +4224,9 @@ def normalize_basis_at_p(e, p, B=QuaternionAlgebraElement_abstract.pair): # Ensures that (B(f0,f0)/2).valuation(p) <= B(f0,f1).valuation(p) if B(f0, f1).valuation(p) + 1 < B(f0, f0).valuation(p): + g = f0 f0 += f1 - f1 = f0 + f1 = g # Make remaining vectors orthogonal to span of f0, f1 e[min_m] = e[0] @@ -3971,7 +4239,7 @@ def normalize_basis_at_p(e, p, B=QuaternionAlgebraElement_abstract.pair): tu = [(B01 * B(f1, e[l]) - B11 * B(f0, e[l]), B01 * B(f0, e[l]) - B00 * B(f1, e[l])) for l in range(2, N)] - e[2:n] = [e[l] + tu[l-2][0]/d * f0 + tu[l-2][1]/d * f1 for l in range(2, N)] + e[2:N] = [e[l] + tu[l-2][0]/d * f0 + tu[l-2][1]/d * f1 for l in range(2, N)] # Recursively normalize remaining vectors f = normalize_basis_at_p(e[2:N], p) diff --git a/src/sage/arith/functions.pyx b/src/sage/arith/functions.pyx index f0c7c03220e..a3a9c4ae216 100644 --- a/src/sage/arith/functions.pyx +++ b/src/sage/arith/functions.pyx @@ -30,7 +30,7 @@ def lcm(a, b=None): - ``a,b`` -- two elements of a ring with lcm or - - ``a`` -- a list or tuple of elements of a ring with lcm + - ``a`` -- a list, tuple or iterable of elements of a ring with lcm OUTPUT: @@ -135,7 +135,7 @@ cpdef LCM_list(v): INPUT: - - ``v`` -- an iterable + - ``v`` -- an iterable OUTPUT: integer diff --git a/src/sage/calculus/functional.py b/src/sage/calculus/functional.py index 109de21a5d9..d457c448dd1 100644 --- a/src/sage/calculus/functional.py +++ b/src/sage/calculus/functional.py @@ -215,8 +215,8 @@ def integral(f, *args, **kwds): symbolically:: sage: f(x) = 1/(sqrt(2*pi)) * e^(-x^2/2) - sage: P = plot(f, -4, 4, hue=0.8, thickness=2) - sage: P.show(ymin=0, ymax=0.4) + sage: P = plot(f, -4, 4, hue=0.8, thickness=2) # needs sage.plot + sage: P.show(ymin=0, ymax=0.4) # needs sage.plot sage: numerical_integral(f, -4, 4) # random output (0.99993665751633376, 1.1101527003413533e-14) sage: integrate(f, x) diff --git a/src/sage/calculus/interpolators.pyx b/src/sage/calculus/interpolators.pyx index 0e1f5fc7209..221b52369e2 100644 --- a/src/sage/calculus/interpolators.pyx +++ b/src/sage/calculus/interpolators.pyx @@ -50,13 +50,14 @@ def polygon_spline(pts): sage: ps = polygon_spline(pts) sage: fx = lambda x: ps.value(x).real sage: fy = lambda x: ps.value(x).imag - sage: show(parametric_plot((fx, fy), (0, 2*pi))) # needs sage.plot + sage: show(parametric_plot((fx, fy), (0, 2*pi))) # needs sage.plot sage.symbolic sage: m = Riemann_Map([lambda x: ps.value(real(x))], ....: [lambda x: ps.derivative(real(x))], 0) sage: show(m.plot_colored() + m.plot_spiderweb()) # needs sage.plot Polygon approximation of a circle:: + sage: # needs sage.symbolic sage: pts = [e^(I*t / 25) for t in range(25)] sage: ps = polygon_spline(pts) sage: ps.derivative(2) diff --git a/src/sage/calculus/riemann.pyx b/src/sage/calculus/riemann.pyx index a4f9545bffb..9527736872d 100644 --- a/src/sage/calculus/riemann.pyx +++ b/src/sage/calculus/riemann.pyx @@ -408,7 +408,7 @@ cdef class Riemann_Map: sage: m = Riemann_Map([f], [fprime], 0) sage: sz = m.get_szego(boundary=0) sage: points = m.get_szego(absolute_value=True) - sage: list_plot(points) + sage: list_plot(points) # needs sage.plot Graphics object consisting of 1 graphics primitive Extending the points by a spline:: @@ -416,7 +416,7 @@ cdef class Riemann_Map: sage: s = spline(points) sage: s(3*pi / 4) 0.0012158... - sage: plot(s,0,2*pi) # plot the kernel + sage: plot(s,0,2*pi) # plot the kernel # needs sage.plot Graphics object consisting of 1 graphics primitive The unit circle with a small hole:: @@ -483,7 +483,7 @@ cdef class Riemann_Map: sage: fprime(t) = I*e^(I*t) + 0.5*I*e^(-I*t) sage: m = Riemann_Map([f], [fprime], 0) sage: points = m.get_theta_points() - sage: list_plot(points) + sage: list_plot(points) # needs sage.plot Graphics object consisting of 1 graphics primitive Extending the points by a spline:: @@ -740,12 +740,12 @@ cdef class Riemann_Map: Default plot:: - sage: m.plot_boundaries() + sage: m.plot_boundaries() # needs sage.plot Graphics object consisting of 1 graphics primitive Big blue collocation points:: - sage: m.plot_boundaries(plotjoined=False, rgbcolor=[0,0,1], thickness=6) + sage: m.plot_boundaries(plotjoined=False, rgbcolor=[0,0,1], thickness=6) # needs sage.plot Graphics object consisting of 1 graphics primitive """ from sage.plot.all import list_plot @@ -905,17 +905,18 @@ cdef class Riemann_Map: Default plot:: - sage: m.plot_spiderweb() + sage: m.plot_spiderweb() # needs sage.plot Graphics object consisting of 21 graphics primitives Simplified plot with many discrete points:: - sage: m.plot_spiderweb(spokes=4, circles=1, pts=400, linescale=0.95, plotjoined=False) + sage: m.plot_spiderweb(spokes=4, circles=1, pts=400, # needs sage.plot + ....: linescale=0.95, plotjoined=False) Graphics object consisting of 6 graphics primitives Plot with thick, red lines:: - sage: m.plot_spiderweb(rgbcolor=[1,0,0], thickness=3) + sage: m.plot_spiderweb(rgbcolor=[1,0,0], thickness=3) # needs sage.plot Graphics object consisting of 21 graphics primitives To generate the unit circle map, it's helpful to see what the @@ -924,7 +925,7 @@ cdef class Riemann_Map: sage: f(t) = e^(I*t) sage: fprime(t) = I*e^(I*t) sage: m = Riemann_Map([f], [fprime], 0, 1000) - sage: m.plot_spiderweb() + sage: m.plot_spiderweb() # needs sage.plot Graphics object consisting of 21 graphics primitives A multiply connected region with corners. We set ``min_mag`` higher @@ -934,8 +935,10 @@ cdef class Riemann_Map: sage: z1 = lambda t: ps.value(t); z1p = lambda t: ps.derivative(t) sage: z2(t) = -2+exp(-I*t); z2p(t) = -I*exp(-I*t) sage: z3(t) = 2+exp(-I*t); z3p(t) = -I*exp(-I*t) - sage: m = Riemann_Map([z1,z2,z3],[z1p,z2p,z3p],0,ncorners=4) # long time - sage: p = m.plot_spiderweb(withcolor=True,plot_points=500, thickness = 2.0, min_mag=0.1) # long time + sage: m = Riemann_Map([z1,z2,z3], [z1p,z2p,z3p], 0, # long time + ....: ncorners=4) + sage: p = m.plot_spiderweb(withcolor=True, plot_points=500, # long time, needs sage.plot + ....: thickness=2.0, min_mag=0.1) """ from sage.plot.complex_plot import ComplexPlot from sage.plot.all import list_plot, Graphics @@ -1028,17 +1031,17 @@ cdef class Riemann_Map: sage: f(t) = e^(I*t) - 0.5*e^(-I*t) sage: fprime(t) = I*e^(I*t) + 0.5*I*e^(-I*t) sage: m = Riemann_Map([f], [fprime], 0) - sage: m.plot_colored() + sage: m.plot_colored() # needs sage.plot Graphics object consisting of 1 graphics primitive Plot zoomed in on a specific spot:: - sage: m.plot_colored(plot_range=[0,1,.25,.75]) + sage: m.plot_colored(plot_range=[0,1,.25,.75]) # needs sage.plot Graphics object consisting of 1 graphics primitive High resolution plot:: - sage: m.plot_colored(plot_points=1000) # long time (29s on sage.math, 2012) + sage: m.plot_colored(plot_points=1000) # long time (29s on sage.math, 2012), needs sage.plot Graphics object consisting of 1 graphics primitive To generate the unit circle map, it's helpful to see what the @@ -1047,7 +1050,7 @@ cdef class Riemann_Map: sage: f(t) = e^(I*t) sage: fprime(t) = I*e^(I*t) sage: m = Riemann_Map([f], [fprime], 0, 1000) - sage: m.plot_colored() + sage: m.plot_colored() # needs sage.plot Graphics object consisting of 1 graphics primitive """ from sage.plot.complex_plot import ComplexPlot @@ -1081,7 +1084,7 @@ cdef comp_pt(clist, loop=True): sage: f(t) = e^(I*t) - 0.5*e^(-I*t) sage: fprime(t) = I*e^(I*t) + 0.5*I*e^(-I*t) sage: m = Riemann_Map([f], [fprime], 0) - sage: m.plot_spiderweb() + sage: m.plot_spiderweb() # needs sage.plot Graphics object consisting of 21 graphics primitives """ list2 = [(c.real, c.imag) for c in clist] diff --git a/src/sage/calculus/transforms/dft.py b/src/sage/calculus/transforms/dft.py index 1b165fa6503..cd0c76bfc07 100644 --- a/src/sage/calculus/transforms/dft.py +++ b/src/sage/calculus/transforms/dft.py @@ -294,13 +294,15 @@ def dft(self, chi=None): Indexed sequence: [6, 0, 0, 0, 0, 0] indexed by [0, 1, 2, 3, 4, 5] - sage: # needs sage.groups + sage: # needs sage.combinat sage.groups sage: G = SymmetricGroup(3) sage: J = G.conjugacy_classes_representatives() sage: s = IndexedSequence([1,2,3], J) # 1,2,3 are the values of a class fcn on G sage: s.dft() # the "scalar-valued Fourier transform" of this class fcn Indexed sequence: [8, 2, 2] indexed by [(), (1,2), (1,2,3)] + + sage: # needs sage.rings.number_field sage: J = AbelianGroup(2, [2,3], names='ab') sage: s = IndexedSequence([1,2,3,4,5,6], J) sage: s.dft() # the precision of output is somewhat random and architecture dependent. @@ -311,6 +313,8 @@ def dft(self, chi=None): -0.00000000000000976996261670137 - 0.0000000000000159872115546022*I, -0.00000000000000621724893790087 - 0.0000000000000106581410364015*I] indexed by Multiplicative Abelian group isomorphic to C2 x C3 + + sage: # needs sage.groups sage.rings.number_field sage: J = CyclicPermutationGroup(6) sage: s = IndexedSequence([1,2,3,4,5,6], J) sage: s.dft() # the precision of output is somewhat random and architecture dependent. diff --git a/src/sage/calculus/transforms/fft.pyx b/src/sage/calculus/transforms/fft.pyx index 363fa65a836..ca7fd73cb64 100644 --- a/src/sage/calculus/transforms/fft.pyx +++ b/src/sage/calculus/transforms/fft.pyx @@ -243,7 +243,7 @@ cdef class FastFourierTransform_complex(FastFourierTransform_base): EXAMPLES:: sage: a = FastFourierTransform(4) - sage: a._plot_polar(0,2) # needs sage.plot + sage: a._plot_polar(0,2) # needs sage.plot sage.symbolic Graphics object consisting of 2 graphics primitives """ @@ -304,9 +304,9 @@ cdef class FastFourierTransform_complex(FastFourierTransform_base): - ``style`` -- Style of the plot, options are ``"rect"`` or ``"polar"`` - - ``rect`` -- height represents real part, color represents + - ``"rect"`` -- height represents real part, color represents imaginary part. - - ``polar`` -- height represents absolute value, color + - ``"polar"`` -- height represents absolute value, color represents argument. - ``xmin`` -- The lower bound of the slice to plot. 0 by default. @@ -319,13 +319,14 @@ cdef class FastFourierTransform_complex(FastFourierTransform_base): EXAMPLES:: + sage: # needs sage.plot sage: a = FastFourierTransform(16) sage: for i in range(16): a[i] = (random(),random()) - sage: A = plot(a) # needs sage.plot - sage: B = plot(a, style='polar') # needs sage.plot - sage: type(A) # needs sage.plot + sage: A = plot(a) + sage: B = plot(a, style='polar') # needs sage.symbolic + sage: type(A) - sage: type(B) # needs sage.plot + sage: type(B) # needs sage.symbolic sage: a = FastFourierTransform(125) sage: b = FastFourierTransform(125) @@ -333,7 +334,7 @@ cdef class FastFourierTransform_complex(FastFourierTransform_base): sage: for i in range(1, 60): b[i]=1 sage: a.forward_transform() sage: a.inverse_transform() - sage: a.plot() + b.plot() # needs sage.plot + sage: a.plot() + b.plot() Graphics object consisting of 250 graphics primitives """ diff --git a/src/sage/categories/action.pyx b/src/sage/categories/action.pyx index 27cf3e0b26c..8f2f2beb96f 100644 --- a/src/sage/categories/action.pyx +++ b/src/sage/categories/action.pyx @@ -148,7 +148,7 @@ cdef class Action(Functor): sage: A(x, 5) Traceback (most recent call last): ... - TypeError: not a constant polynomial + TypeError: x is not a constant polynomial sage: A = IntegerMulAction(ZZ, R, False) # Right action sage: A(x, 5) 5*x @@ -157,7 +157,7 @@ cdef class Action(Functor): sage: A(5, x) Traceback (most recent call last): ... - TypeError: not a constant polynomial + TypeError: x is not a constant polynomial """ if len(args) == 2: # Normal case, called with (g, x) or (x, g) as arguments diff --git a/src/sage/categories/all.py b/src/sage/categories/all.py index a344ed2a296..e4171ef4439 100644 --- a/src/sage/categories/all.py +++ b/src/sage/categories/all.py @@ -104,7 +104,7 @@ # schemes and varieties from sage.categories.modular_abelian_varieties import ModularAbelianVarieties -from sage.categories.schemes import Schemes, AbelianVarieties +from sage.categories.schemes import Schemes, AbelianVarieties, Jacobians # * with basis from sage.categories.modules_with_basis import ModulesWithBasis diff --git a/src/sage/categories/category.py b/src/sage/categories/category.py index 84ba7163bd8..b71331c5ab5 100644 --- a/src/sage/categories/category.py +++ b/src/sage/categories/category.py @@ -2597,6 +2597,7 @@ def category_sample(): Category of Hecke modules over Rational Field, Category of Hopf algebras over Rational Field, Category of Hopf algebras with basis over Rational Field, + Category of Jacobians over Rational Field, Category of Lie algebras over Rational Field, Category of Weyl groups, Category of abelian varieties over Rational Field, diff --git a/src/sage/categories/enumerated_sets.py b/src/sage/categories/enumerated_sets.py index 08150e61360..513ef129c8a 100644 --- a/src/sage/categories/enumerated_sets.py +++ b/src/sage/categories/enumerated_sets.py @@ -990,8 +990,12 @@ def map(self, f, name=None, *, is_injective=True): ....: '_test_enumerated_set_contains', ....: '_test_some_elements']) """ - from sage.combinat.combinat import MapCombinatorialClass - return MapCombinatorialClass(self, f, name, is_injective=is_injective) + from sage.sets.image_set import ImageSubobject + + image = ImageSubobject(f, self, is_injective=is_injective) + if name: + image.rename(name) + return image # # Consistency test suite for an enumerated set: diff --git a/src/sage/categories/examples/finite_dimensional_lie_algebras_with_basis.py b/src/sage/categories/examples/finite_dimensional_lie_algebras_with_basis.py index e8db64d11a9..452cf12aa1e 100644 --- a/src/sage/categories/examples/finite_dimensional_lie_algebras_with_basis.py +++ b/src/sage/categories/examples/finite_dimensional_lie_algebras_with_basis.py @@ -95,6 +95,10 @@ def __init__(self, R, n=None, M=None, ambient=None): self._ambient = ambient Parent.__init__(self, base=R, category=cat) + from sage.categories.lie_algebras import LiftMorphism + self._lift_uea = LiftMorphism(self, self._construct_UEA()) + self._lift_uea.register_as_coercion() + def _repr_(self): """ EXAMPLES:: @@ -135,6 +139,57 @@ def _element_constructor_(self, x): x = x.value return self.element_class(self, self._M(x)) + def lift(self, x): + r""" + Return the lift of ``self``. + + EXAMPLES:: + + sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() + sage: a, b, c = L.gens() + sage: L.lift(a) + b0 + sage: L.lift(b).parent() is L.universal_enveloping_algebra() + True + + sage: I = L.ideal([a + 2*b, b + 3*c]) + sage: I.lift(I.basis()[0]) + (1, 0, -6) + """ + # FIXME: This method can likely be simplified or removed once we + # disentangle the UEA lift from the generic lift + A = self._ambient + if A is self: + return self._lift_uea(x) + return A.element_class(A, A._M(x.value)) + + def universal_enveloping_algebra(self): + r""" + Return the universal enveloping algebra of ``self``. + + EXAMPLES:: + + sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() + sage: L.universal_enveloping_algebra() + Noncommutative Multivariate Polynomial Ring in b0, b1, b2 + over Rational Field, nc-relations: {} + """ + # FIXME: This method can likely be removed once we + # disentangle the UEA lift from the generic lift + return self._lift_uea.codomain() + + def _order(self, x): + r""" + Return a key for sorting for the index ``x``. + + TESTS:: + + sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() + sage: L._order(2) + 2 + """ + return x + @cached_method def zero(self): """ @@ -194,6 +249,8 @@ def subalgebra(self, gens): [ 1 0 -1/2] [ 0 1 1] """ + if isinstance(gens, AbelianLieAlgebra): + gens = [self(g) for g in gens.gens()] N = self._M.subspace([g.value for g in gens]) return AbelianLieAlgebra(self.base_ring(), M=N, ambient=self._ambient) @@ -252,7 +309,7 @@ def gens(self) -> tuple: sage: L.gens() ((1, 0, 0), (0, 1, 0), (0, 0, 1)) """ - return tuple(self._M.basis()) + return tuple([self.element_class(self, b) for b in self._M.basis()]) def module(self): """ @@ -302,7 +359,36 @@ def from_vector(self, v, order=None): """ return self.element_class(self, self._M(v)) + def leading_monomials(self): + r""" + Return the set of leading monomials of the basis of ``self``. + + EXAMPLES:: + + sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() + sage: a, b, c = L.lie_algebra_generators() + sage: I = L.ideal([2*a + b, b + c]) + sage: I.leading_monomials() + ((1, 0, 0), (0, 1, 0)) + """ + # for free modules, the leading monomial is actually the trailing monomial + return tuple([self._ambient._M(b.value).trailing_monomial() + for b in self.basis()]) + class Element(BaseExample.Element): + def __init__(self, parent, value): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() + sage: a, b, c = L.lie_algebra_generators() + sage: TestSuite(a).run() + """ + value.set_immutable() + super().__init__(parent, value) + def __iter__(self): """ Iterate over ``self`` by returning pairs ``(i, c)`` where ``i`` diff --git a/src/sage/categories/examples/lie_algebras.py b/src/sage/categories/examples/lie_algebras.py index 135c122efa2..15ace1987db 100644 --- a/src/sage/categories/examples/lie_algebras.py +++ b/src/sage/categories/examples/lie_algebras.py @@ -198,6 +198,20 @@ def __ne__(self, rhs): """ return not self.__eq__(rhs) + def __hash__(self): + r""" + Return the hash of ``self``. + + EXAMPLES:: + + sage: # needs sage.combinat sage.groups + sage: L = LieAlgebras(QQ).example() + sage: x, y = L.lie_algebra_generators() + sage: hash(x) == hash(x.value) + True + """ + return hash(self.value) + def __bool__(self) -> bool: """ Check non-zero. diff --git a/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py b/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py index 6cb7badd99a..4477401a909 100644 --- a/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py +++ b/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py @@ -123,12 +123,21 @@ def _construct_UEA(self): Universal enveloping algebra of The 6-Witt Lie algebra over Ring of integers modulo 6 in the Poincare-Birkhoff-Witt basis + + Corner case for the trivial (0-dimensional) Lie algebra:: + + sage: L. = LieAlgebra(QQ, abelian=True) + sage: I = L.product_space(L) + sage: I._construct_UEA() + Free Algebra on 0 generators () over Rational Field """ from sage.algebras.free_algebra import FreeAlgebra # Create the UEA relations # We need to get names for the basis elements, not just the generators I = self._basis_ordering + if not I: # trivial Lie algebra + return FreeAlgebra(self.base_ring(), []) try: names = [str(x) for x in I] @@ -445,7 +454,7 @@ def centralizer_basis(self, S): sage: H.centralizer_basis(H) [z] - sage: # needs sage.combinat sage.groupssage.modules + sage: # needs sage.combinat sage.groups sage.modules sage: D = DescentAlgebra(QQ, 4).D() sage: L = LieAlgebra(associative=D) sage: L.centralizer_basis(L) @@ -456,6 +465,15 @@ def centralizer_basis(self, S): (D{}, D{1} + D{1, 2} + D{2, 3} + D{3}, D{1, 2, 3} + D{1, 3} + D{2}) + + sage: scoeffs = {('a','d'): {'a':1}, ('a','e'): {'b':-1}, + ....: ('b','d'): {'b':1}, ('b','e'): {'a':1}, + ....: ('d','e'): {'c':1}} + sage: L. = LieAlgebra(QQ, scoeffs) + sage: L.centralizer_basis([a, c]) + [a, b, c] + sage: L.centralizer_basis([a, e]) + [c] """ from sage.matrix.constructor import matrix @@ -481,7 +499,7 @@ def centralizer_basis(self, S): [[sum(m[i,j] * sc[x,xp][k] for j,xp in enumerate(X) if (x, xp) in sc) for x in X] - for i in range(d) for k in range(d)]) + for i in range(m.nrows()) for k in range(d)]) C = c_mat.right_kernel().basis_matrix() return [self.from_vector(c) for c in C] @@ -513,6 +531,7 @@ def centralizer(self, S): """ return self.subalgebra(self.centralizer_basis(S)) + @cached_method def center(self): """ Return the center of ``self``. @@ -531,6 +550,99 @@ def center(self): """ return self.centralizer(self) + def normalizer_basis(self, S): + r""" + Return a basis of the normalizer of ``S`` in ``self``. + + INPUT: + + - ``S`` -- a subalgebra of ``self`` or a list of elements that + represent generators for a subalgebra + + .. SEEALSO:: + + :meth:`normalizer` + + EXAMPLES:: + + sage: scoeffs = {('a','d'): {'a':1}, ('a','e'): {'b':-1}, + ....: ('b','d'): {'b':1}, ('b','e'): {'a':1}, + ....: ('d','e'): {'c':1}} + sage: L. = LieAlgebra(QQ, scoeffs) + sage: L.normalizer_basis([a, e]) + [b, c] + + sage: S = L.subalgebra([a, e]) + sage: L.normalizer_basis(S) + [a, b, c, e] + + When the subalgebra is the ambient Lie algebra, we return the + basis of the ambient Lie algebra:: + + sage: L.normalizer_basis(L) + Finite family {'a': a, 'b': b, 'c': c, 'd': d, 'e': e} + sage: L.normalizer_basis([a, b, c, a, d + e, a + e]) + Finite family {'a': a, 'b': b, 'c': c, 'd': d, 'e': e} + """ + from sage.matrix.constructor import matrix + + if S is self: + return self.basis() + if isinstance(S, (list, tuple)): + m = matrix([v.to_vector() for v in self.echelon_form(S)]) + else: + m = self.subalgebra(S).basis_matrix() + + if m.nrows() == self.dimension(): + return self.basis() + + S = self.structure_coefficients() + sc = {} + for k in S.keys(): + v = S[k].to_vector() + sc[k] = v + sc[k[1], k[0]] = -v + X = self.basis().keys() + d = len(X) + ret = [] + t = m.nrows() + c_mat = matrix(self.base_ring(), + [[sum(m[i,j] * sc[x,xp][k] for j, xp in enumerate(X) + if (x, xp) in sc) + for x in X] + + [0]*(i*t) + [-m[j,k] for j in range(t)] + [0]*((t-i-1)*t) + for i in range(t) for k in range(d)]) + C = c_mat.right_kernel().basis_matrix() + return [self.from_vector(c[:d]) for c in C] + + def normalizer(self, S): + r""" + Return the normalizer of ``S`` in ``self``. + + INPUT: + + - ``S`` -- a subalgebra of ``self`` or a list of elements that + represent generators for a subalgebra + + .. SEEALSO:: + + :meth:`normalizer_basis` + + EXAMPLES:: + + sage: scoeffs = {('a','d'): {'a':1}, ('a','e'): {'b':-1}, + ....: ('b','d'): {'b':1}, ('b','e'): {'a':1}, + ....: ('d','e'): {'c':1}} + sage: L. = LieAlgebra(QQ, scoeffs) + sage: L.normalizer([a, e]) + Subalgebra generated by (b, c) of Lie algebra on + 5 generators (a, b, c, d, e) over Rational Field + sage: L.normalizer([a, c, e]) + Subalgebra generated by (b, c, d) of Lie algebra on + 5 generators (a, b, c, d, e) over Rational Field + """ + return self.subalgebra(self.normalizer_basis(S)) + @cached_method def derivations_basis(self): r""" @@ -963,14 +1075,19 @@ def derived_series(self): sage: # needs sage.combinat sage.modules sage: L. = LieAlgebra(QQ, {('x','y'): {'x':1}}) - sage: L.derived_series() # not implemented + sage: L.derived_series() (Lie algebra on 2 generators (x, y) over Rational Field, - Subalgebra generated of - Lie algebra on 2 generators (x, y) over Rational Field - with basis: (x,), - Subalgebra generated of - Lie algebra on 2 generators (x, y) over Rational Field - with basis: ()) + Ideal (x) of Lie algebra on 2 generators (x, y) over Rational Field, + Ideal () of Lie algebra on 2 generators (x, y) over Rational Field) + + sage: scoeffs = {('a','d'): {'a':1}, ('a','e'): {'b':-1}, + ....: ('b','d'): {'b':1}, ('b','e'): {'a':1}, + ....: ('d','e'): {'c':1}} + sage: L. = LieAlgebra(QQ, scoeffs) + sage: L.derived_series() + (Lie algebra on 5 generators (a, b, c, d, e) over Rational Field, + Ideal (a, b, c) of Lie algebra on 5 generators (a, b, c, d, e) over Rational Field, + Ideal () of Lie algebra on 5 generators (a, b, c, d, e) over Rational Field) """ L = [self] while L[-1].dimension() > 0: @@ -1033,11 +1150,18 @@ def lower_central_series(self, submodule=False): sage: # needs sage.combinat sage.modules sage: L. = LieAlgebra(QQ, {('x','y'): {'x':1}}) - sage: L.lower_central_series() # not implemented + sage: L.lower_central_series() (Lie algebra on 2 generators (x, y) over Rational Field, - Subalgebra generated of - Lie algebra on 2 generators (x, y) over Rational Field - with basis: (x,)) + Ideal (x) of Lie algebra on 2 generators (x, y) over Rational Field) + + sage: scoeffs = {('a','d'): {'a':1}, ('a','e'): {'b':-1}, + ....: ('b','d'): {'b':1}, ('b','e'): {'a':1}, + ....: ('d','e'): {'c':1}} + sage: L. = LieAlgebra(QQ, scoeffs) + sage: L.lower_central_series() + (Lie algebra on 5 generators (a, b, c, d, e) over Rational Field, + Ideal (a, b, c) of Lie algebra on 5 generators (a, b, c, d, e) over Rational Field, + Ideal (a, b) of Lie algebra on 5 generators (a, b, c, d, e) over Rational Field) """ if submodule: L = [self.module()] @@ -1050,6 +1174,82 @@ def lower_central_series(self, submodule=False): L.append(s) return tuple(L) + @cached_method + def upper_central_series(self): + r""" + Return the upper central series `(Z_i(\mathfrak{g}))_i` + of ``self`` where the rightmost + `Z_k(\mathfrak{g}) = Z_{k+1}(\mathfrak{g}) = \cdots`. + + The *upper central series* of a Lie algebra `\mathfrak{g}` is + defined recursively by `Z_0(\mathfrak{g}) := Z(\mathfrak{g})` and + + .. MATH:: + + Z_{k+1}(\mathfrak{g}) / Z_k(\mathfrak{g}) + = Z(\mathfrak{g} / Z_k(\mathfrak{g}), + + and recall that `Z(\mathfrak{g})` is the :meth:`center` + of `\mathfrak{g}`. + + EXAMPLES:: + + sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() + sage: L.upper_central_series() + [An example of a finite dimensional Lie algebra with basis: + the 3-dimensional abelian Lie algebra over Rational Field] + + sage: L. = LieAlgebra(QQ, {('x','y'): {'x':1}}) + sage: L.upper_central_series() + [Subalgebra generated by () of Lie algebra on 2 generators (x, y) over Rational Field] + + sage: scoeffs = {('a','d'): {'a':1}, ('a','e'): {'b':-1}, + ....: ('b','d'): {'b':1}, ('b','e'): {'a':1}, + ....: ('d','e'): {'c':1}} + sage: L. = LieAlgebra(QQ, scoeffs) + sage: L.upper_central_series() + [Subalgebra generated by (c) of Lie algebra on 5 generators (a, b, c, d, e) over Rational Field] + + sage: L = lie_algebras.Heisenberg(QQ, 3) + sage: L.upper_central_series() + [Subalgebra generated by (z) of Heisenberg algebra of rank 3 over Rational Field, + Heisenberg algebra of rank 3 over Rational Field] + """ + I = self.center() + if I.dimension() == 0: + return [I] + ret = [I] + dim = self.dimension() + while True: + Q = self.quotient(I) + Z = Q.center() + if not Z.dimension(): # we did not add anything + return ret + new_gens = [Q.lift(b.value) for b in Z.basis()] + I = self.ideal(list(I.basis()) + new_gens) + if I.dimension() == dim: + ret.append(self) + return ret + ret.append(I) + + def hypercenter(self): + r""" + Return the hypercenter of ``self``. + + EXAMPLES:: + + sage: SGA3 = SymmetricGroup(3).algebra(QQ) + sage: L = LieAlgebra(associative=SGA3) + sage: L.hypercenter() + Subalgebra generated by ((), (1,2,3) + (1,3,2), (2,3) + (1,2) + (1,3)) + of Lie algebra of Symmetric group algebra of order 3 over Rational Field + + sage: L = lie_algebras.Heisenberg(QQ, 3) + sage: L.hypercenter() + Heisenberg algebra of rank 3 over Rational Field + """ + return self.upper_central_series()[-1] + def is_abelian(self): """ Return if ``self`` is an abelian Lie algebra. @@ -1927,6 +2127,90 @@ def to_prod(vec, index): return [UEA.sum(to_prod(vec, index) for index in vec.support()) for vec in tens] + def faithful_representation(self, algorithm=None): + r""" + Return a faithful representation of ``self``. + + By Ado's and Iwasawa's theorems, every finite dimensional + Lie algebra has a faithful finite dimensional representation. + + INPUT: + + - ``algorithm`` -- one of the following depending on the + classification of the Lie algebra: + + Nilpotent Lie algebras: + + * ``'regular'`` -- use the universal enveloping algebra quotient + :class:`~sage.algebras.lie_algebras.representation.FaithfulRepresentationNilpotentPBW` + * ``'minimal'`` -- construct the minimal representation (for + precise details, see the documentation of + :class:`~sage.algebras.lie_algebras.representation.FaithfulRepresentationNilpotentPBW`) + + Solvable but not nilpotent: + + * Not implemented + + Semisimple: + + * Not implemented + + General case + + * Not implemented + + EXAMPLES:: + + sage: H2 = lie_algebras.Heisenberg(QQ, 2) + sage: H2.is_nilpotent() + True + sage: F = H2.faithful_representation(); F + Faithful 16 dimensional representation of + Heisenberg algebra of rank 2 over Rational Field + sage: M = H2.faithful_representation(algorithm="minimal"); M + Minimal faithful representation of + Heisenberg algebra of rank 2 over Rational Field + sage: M.dimension() + 4 + sage: H2.faithful_representation(algorithm="invalid") + Traceback (most recent call last): + ... + ValueError: invalid algorithm 'invalid' + + sage: scoeffs = {('a','d'): {'a':1}, ('a','e'): {'b':-1}, + ....: ('b','d'): {'b':1}, ('b','e'): {'a':1}, + ....: ('d','e'): {'c':1}} + sage: L. = LieAlgebra(QQ, scoeffs) + sage: L.is_nilpotent() + False + sage: L.is_solvable() + True + sage: L.faithful_representation() + Traceback (most recent call last): + ... + NotImplementedError: only implemented for nilpotent Lie algebras + + sage: sl3 = LieAlgebra(QQ, cartan_type=['A', 2]) + sage: sl3.is_semisimple() + True + sage: sl3.faithful_representation() + Traceback (most recent call last): + ... + NotImplementedError: only implemented for nilpotent Lie algebras + """ + if self.is_nilpotent(): + if algorithm is None: + algorithm = "regular" + if algorithm == "regular": + from sage.algebras.lie_algebras.representation import FaithfulRepresentationNilpotentPBW + return FaithfulRepresentationNilpotentPBW(self, minimal=False) + if algorithm == "minimal": + from sage.algebras.lie_algebras.representation import FaithfulRepresentationNilpotentPBW + return FaithfulRepresentationNilpotentPBW(self, minimal=True) + else: + raise NotImplementedError("only implemented for nilpotent Lie algebras") + raise ValueError("invalid algorithm '{}'".format(algorithm)) + class ElementMethods: def adjoint_matrix(self, sparse=False): # In #11111 (more or less) by using matrix of a morphism """ @@ -2068,3 +2352,71 @@ def basis_matrix(self): [ 1 0 -1/2] [ 0 1 1] """ + + def reduce(self, X): + r""" + Reduce an element of the ambient Lie algebra modulo the + ideal ``self``. + + INPUT: + + - ``X`` -- an element of the ambient Lie algebra + + OUTPUT: + + An element `Y` of the ambient Lie algebra that is contained + in a fixed complementary submodule `V` to ``self`` such that + `X = Y` mod ``self``. + + When the base ring of ``self`` is a field, the complementary + submodule `V` is spanned by the elements of the basis that + are not the leading supports of the basis of ``self``. + + EXAMPLES: + + An example reduction in a 6 dimensional Lie algebra:: + + sage: sc = {('a','b'): {'d': 1}, ('a','c'): {'e': 1}, + ....: ('b','c'): {'f': 1}} + sage: L. = LieAlgebra(QQ, sc) + sage: I = L.ideal(c) + sage: I.reduce(a + b + c + d + e + f) + a + b + d + + The reduction of an element is zero if and only if the + element belongs to the subalgebra:: + + sage: I.reduce(c + e) + 0 + sage: c + e in I + True + + Over non-fields, the complementary submodule may not be spanned + by a subset of the basis of the ambient Lie algebra:: + + sage: L. = LieAlgebra(ZZ, {('X','Y'): {'Z': 3}}) + sage: I = L.ideal(Y) + sage: I.basis() + Family (Y, 3*Z) + sage: I.reduce(3*Z) + 0 + sage: I.reduce(Y + 14*Z) + 2*Z + """ + R = self.base_ring() + from sage.categories.fields import Fields + is_field = R in Fields() + for Y in self.basis(): + Y = self.lift(Y) + k, c = Y.leading_item(key=self._order) + + if is_field: + X -= (X[k] / c) * Y + else: + try: + q, _ = X[k].quo_rem(c) + X -= q * Y + except AttributeError: + break + + return X diff --git a/src/sage/categories/finite_dimensional_modules_with_basis.py b/src/sage/categories/finite_dimensional_modules_with_basis.py index ab22cd355d5..58916af2aaf 100644 --- a/src/sage/categories/finite_dimensional_modules_with_basis.py +++ b/src/sage/categories/finite_dimensional_modules_with_basis.py @@ -10,10 +10,13 @@ # ***************************************************************************** import operator -from sage.categories.category_with_axiom import CategoryWithAxiom_over_base_ring +from sage.categories.category_with_axiom import CategoryWithAxiom, CategoryWithAxiom_over_base_ring from sage.categories.fields import Fields +from sage.categories.homsets import HomsetsCategory from sage.categories.tensor import TensorProductsCategory from sage.misc.cachefunc import cached_method +from sage.misc.lazy_attribute import lazy_attribute + class FiniteDimensionalModulesWithBasis(CategoryWithAxiom_over_base_ring): """ @@ -676,6 +679,96 @@ def matrix(self, base_ring=None, side="left"): m.set_immutable() return m + def _repr_matrix(self): + r""" + Return a string representation of this morphism (as a matrix). + + EXAMPLES:: + + sage: M = matrix(ZZ, [[1, 0, 0], [0, 1, 0]], + ....: column_keys=['a', 'b', 'c'], + ....: row_keys=['v', 'w']); M + Generic morphism: + From: Free module generated by {'a', 'b', 'c'} over Integer Ring + To: Free module generated by {'v', 'w'} over Integer Ring + sage: M._repr_ = M._repr_matrix + sage: M # indirect doctest + a b c + v[1 0 0] + w[0 1 0] + """ + matrix = self.matrix() + + from sage.matrix.constructor import options + + if matrix.nrows() <= options.max_rows() and matrix.ncols() <= options.max_cols(): + return matrix.str(top_border=self.domain().basis().keys(), + left_border=self.codomain().basis().keys()) + + return repr(matrix) + + def _ascii_art_matrix(self): + r""" + Return an ASCII art representation of this morphism (as a matrix). + + EXAMPLES:: + + sage: M = matrix(ZZ, [[1, 0, 0], [0, 1, 0]], + ....: column_keys=['a', 'b', 'c'], + ....: row_keys=['v', 'w']); M + Generic morphism: + From: Free module generated by {'a', 'b', 'c'} over Integer Ring + To: Free module generated by {'v', 'w'} over Integer Ring + sage: M._ascii_art_ = M._ascii_art_matrix + sage: ascii_art(M) # indirect doctest + a b c + v[1 0 0] + w[0 1 0] + """ + matrix = self.matrix() + + from sage.matrix.constructor import options + + if matrix.nrows() <= options.max_rows() and matrix.ncols() <= options.max_cols(): + return matrix.str(character_art=True, + top_border=self.domain().basis().keys(), + left_border=self.codomain().basis().keys()) + + from sage.typeset.ascii_art import AsciiArt + + return AsciiArt(repr(self).splitlines()) + + def _unicode_art_matrix(self): + r""" + Return a unicode art representation of this morphism (as a matrix). + + EXAMPLES:: + + sage: M = matrix(ZZ, [[1, 0, 0], [0, 1, 0]], + ....: column_keys=['a', 'b', 'c'], + ....: row_keys=['v', 'w']); M + Generic morphism: + From: Free module generated by {'a', 'b', 'c'} over Integer Ring + To: Free module generated by {'v', 'w'} over Integer Ring + sage: M._unicode_art_ = M._unicode_art_matrix + sage: unicode_art(M) # indirect doctest + a b c + v⎛1 0 0⎞ + w⎝0 1 0⎠ + """ + matrix = self.matrix() + + from sage.matrix.constructor import options + + if matrix.nrows() <= options.max_rows() and matrix.ncols() <= options.max_cols(): + return matrix.str(unicode=True, character_art=True, + top_border=self.domain().basis().keys(), + left_border=self.codomain().basis().keys()) + + from sage.typeset.unicode_art import UnicodeArt + + return UnicodeArt(repr(self).splitlines()) + def __invert__(self): """ Return the inverse morphism of ``self``. @@ -797,6 +890,165 @@ def image(self): return C.submodule(self.image_basis(), already_echelonized=True, category=self.category_for()) + class Homsets(HomsetsCategory): + + class Endset(CategoryWithAxiom): + + class ElementMethods: + + @lazy_attribute + def characteristic_polynomial(self): + r""" + Return the characteristic polynomial of this endomorphism. + + :meth:`characteristic_polynomial` and :meth:`charpoly` are the same method. + + INPUT: + + - ``var`` -- variable + + EXAMPLES:: + + sage: V = ZZ^2; phi = V.hom([V.0 + V.1, 2*V.1]) + sage: phi.characteristic_polynomial() + x^2 - 3*x + 2 + sage: phi.charpoly() + x^2 - 3*x + 2 + sage: phi.matrix().charpoly() + x^2 - 3*x + 2 + sage: phi.charpoly('T') + T^2 - 3*T + 2 + + sage: W = CombinatorialFreeModule(ZZ, ['x', 'y']) + sage: M = matrix(ZZ, [[1, 0], [1, 2]]) + sage: psi = W.module_morphism(matrix=M, codomain=W) + sage: psi.charpoly() + x^2 - 3*x + 2 + """ + return self.matrix().charpoly + + charpoly = characteristic_polynomial + + @lazy_attribute + def determinant(self): + """ + Return the determinant of this endomorphism. + + :meth:`determinant` and :meth:`det` are the same method. + + EXAMPLES:: + + sage: V = ZZ^2; phi = V.hom([V.0 + V.1, 2*V.1]) + sage: phi.determinant() + 2 + sage: phi.det() + 2 + + sage: W = CombinatorialFreeModule(ZZ, ['x', 'y']) + sage: M = matrix(ZZ, [[1, 0], [1, 2]]) + sage: psi = W.module_morphism(matrix=M, codomain=W) + sage: psi.det() + 2 + """ + return self.matrix().determinant + + det = determinant + + @lazy_attribute + def fcp(self): + """ + Return the factorization of the characteristic polynomial. + + INPUT: + + - ``var`` -- variable + + EXAMPLES:: + + sage: V = ZZ^2; phi = V.hom([V.0 + V.1, 2*V.1]) + sage: phi.fcp() # needs sage.libs.pari + (x - 2) * (x - 1) + sage: phi.fcp('T') # needs sage.libs.pari + (T - 2) * (T - 1) + + sage: W = CombinatorialFreeModule(ZZ, ['x', 'y']) + sage: M = matrix(ZZ, [[1, 0], [1, 2]]) + sage: psi = W.module_morphism(matrix=M, codomain=W) + sage: psi.fcp() # needs sage.libs.pari + (x - 2) * (x - 1) + """ + return self.matrix().fcp + + @lazy_attribute + def minimal_polynomial(self): + r""" + Return the minimal polynomial of this endomorphism. + + :meth:`minimal_polynomial` and :meth:`minpoly` are the same method. + + INPUT: + + - ``var`` -- string (default: ``'x'``); a variable name + + EXAMPLES: + + Compute the minimal polynomial, and check it. :: + + sage: V = GF(7)^3 + sage: H = V.Hom(V)([[0,1,2], [-1,0,3], [2,4,1]]); H + Vector space morphism represented by the matrix: + [0 1 2] + [6 0 3] + [2 4 1] + Domain: Vector space of dimension 3 over Finite Field of size 7 + Codomain: Vector space of dimension 3 over Finite Field of size 7 + sage: H.minpoly() # needs sage.libs.pari + x^3 + 6*x^2 + 6*x + 1 + sage: H.minimal_polynomial() # needs sage.libs.pari + x^3 + 6*x^2 + 6*x + 1 + sage: H^3 + (H^2)*6 + H*6 + 1 + Vector space morphism represented by the matrix: + [0 0 0] + [0 0 0] + [0 0 0] + Domain: Vector space of dimension 3 over Finite Field of size 7 + Codomain: Vector space of dimension 3 over Finite Field of size 7 + + sage: # needs sage.rings.finite_rings + sage: k = GF(9, 'c') + sage: V = CombinatorialFreeModule(k, ['x', 'y', 'z', 'w']) + sage: A = matrix(k, 4, [1,1,0,0, 0,1,0,0, 0,0,5,0, 0,0,0,5]) + sage: phi = V.module_morphism(matrix=A, codomain=V) + sage: factor(phi.minpoly()) + (x + 1) * (x + 2)^2 + sage: A.minpoly()(A) == 0 + True + sage: factor(phi.charpoly()) + (x + 1)^2 * (x + 2)^2 + """ + return self.matrix().minimal_polynomial + + minpoly = minimal_polynomial + + @lazy_attribute + def trace(self): + r""" + Return the trace of this endomorphism. + + EXAMPLES:: + + sage: V = ZZ^2; phi = V.hom([V.0 + V.1, 2*V.1]) + sage: phi.trace() + 3 + + sage: W = CombinatorialFreeModule(ZZ, ['x', 'y']) + sage: M = matrix(ZZ, [[1, 0], [1, 2]]) + sage: psi = W.module_morphism(matrix=M, codomain=W) + sage: psi.trace() + 3 + """ + return self.matrix().trace + class TensorProducts(TensorProductsCategory): def extra_super_categories(self): diff --git a/src/sage/categories/map.pyx b/src/sage/categories/map.pyx index b41b7538c59..134b88ef1ba 100644 --- a/src/sage/categories/map.pyx +++ b/src/sage/categories/map.pyx @@ -1272,7 +1272,7 @@ cdef class Map(Element): def section(self): """ - Return a section of self. + Return a section of ``self``. .. NOTE:: @@ -1439,6 +1439,7 @@ cdef class Section(Map): """ return self._inverse + cdef class FormalCompositeMap(Map): """ Formal composite maps. @@ -2087,7 +2088,7 @@ cdef class FormalCompositeMap(Map): sage: ZZ(3*x + 45) # indirect doctest Traceback (most recent call last): ... - TypeError: not a constant polynomial + TypeError: 3*x + 45 is not a constant polynomial """ sections = [] for m in reversed(list(self)): diff --git a/src/sage/categories/modules_with_basis.py b/src/sage/categories/modules_with_basis.py index e1fa796ba3b..a93925e5a6f 100644 --- a/src/sage/categories/modules_with_basis.py +++ b/src/sage/categories/modules_with_basis.py @@ -28,6 +28,7 @@ from sage.categories.fields import Fields from sage.categories.modules import Modules from sage.categories.poor_man_map import PoorManMap +from sage.categories.map import Map from sage.structure.element import Element, parent @@ -115,9 +116,10 @@ class ModulesWithBasis(CategoryWithAxiom_over_base_ring): Some more playing around with categories and higher order homsets:: sage: H.category() # needs sage.modules - Category of homsets of modules with basis over Rational Field + Category of homsets of finite dimensional modules with basis over Rational Field sage: Hom(H, H).category() # needs sage.modules - Category of endsets of homsets of modules with basis over Rational Field + Category of endsets of + homsets of finite dimensional modules with basis over Rational Field .. TODO:: ``End(X)`` is an algebra. @@ -173,7 +175,7 @@ def _call_(self, x): if M.base_ring() != self.base_ring(): M = M.change_ring(self.base_ring()) except (TypeError, AttributeError) as msg: - raise TypeError("%s\nunable to coerce x (=%s) into %s" % (msg,x,self)) + raise TypeError("%s\nunable to coerce x (=%s) into %s" % (msg, x, self)) return M def is_abelian(self): @@ -581,7 +583,7 @@ def module_morphism(self, on_basis=None, matrix=None, function=None, return ModuleMorphismByLinearity( domain=self, on_basis=on_basis, **keywords) else: - return ModuleMorphismFromFunction( # Or just SetMorphism? + return ModuleMorphismFromFunction( # Or just SetMorphism? domain=self, function=function, **keywords) _module_morphism = module_morphism @@ -1264,7 +1266,7 @@ def _apply_module_morphism(self, x, on_basis, codomain=False): except Exception: raise ValueError('codomain could not be determined') - if hasattr( codomain, 'linear_combination' ): + if hasattr(codomain, 'linear_combination'): mc = x.monomial_coefficients(copy=False) return codomain.linear_combination((on_basis(key), coeff) for key, coeff in mc.items()) @@ -1288,8 +1290,8 @@ def _apply_module_endomorphism(self, x, on_basis): 2*s[2, 1] + 2*s[3] """ mc = x.monomial_coefficients(copy=False) - return self.linear_combination( (on_basis(key), coeff) - for key, coeff in mc.items()) + return self.linear_combination((on_basis(key), coeff) + for key, coeff in mc.items()) def dimension(self): """ @@ -1332,7 +1334,7 @@ def _from_dict(self, d, coerce=True, remove_zeros=True): sage: A._from_dict(d, coerce=True) # needs sage.modules Traceback (most recent call last): ... - TypeError: not a constant polynomial + TypeError: y is not a constant polynomial """ R = self.base_ring() B = self.basis() @@ -2062,17 +2064,26 @@ def trailing_term(self, *args, **kwds): """ return self.parent().term(*self.trailing_item(*args, **kwds)) - def map_coefficients(self, f): + def map_coefficients(self, f, new_base_ring=None): """ - Mapping a function on coefficients. + Return the element obtained by applying ``f`` to the non-zero + coefficients of ``self``. + + If ``f`` is a :class:`sage.categories.map.Map`, then the resulting + polynomial will be defined over the codomain of ``f``. Otherwise, the + resulting polynomial will be over the same ring as ``self``. Set + ``new_base_ring`` to override this behaviour. + + An error is raised if the coefficients cannot be + converted to the new base ring. INPUT: - - ``f`` -- an endofunction on the coefficient ring of the - free module + - ``f`` -- a callable that will be applied to the + coefficients of ``self`` - Return a new element of ``self.parent()`` obtained by applying the - function ``f`` to all of the coefficients of ``self``. + - ``new_base_ring`` -- (optional) if given, the resulting element + will be defined over this ring EXAMPLES:: @@ -2096,8 +2107,42 @@ def map_coefficients(self, f): sage: a = s([2,1]) + 2*s([3,2]) # needs sage.combinat sage.modules sage: a.map_coefficients(lambda x: x * 2) # needs sage.combinat sage.modules 2*s[2, 1] + 4*s[3, 2] + + We can map into a different base ring:: + + sage: F = CombinatorialFreeModule(QQ, ['a','b','c']) + sage: B = F.basis() + sage: a = 1/2*(B['a'] + 3*B['c']); a + 1/2*B['a'] + 3/2*B['c'] + sage: b = a.map_coefficients(lambda c: 2*c, ZZ); b + B['a'] + 3*B['c'] + sage: b.parent() + Free module generated by {'a', 'b', 'c'} over Integer Ring + sage: b.map_coefficients(lambda c: 1/2*c, ZZ) + Traceback (most recent call last): + ... + TypeError: no conversion of this rational to integer + + Coefficients are converted to the new base ring after + applying the map:: + + sage: B['a'].map_coefficients(lambda c: 2*c, GF(2)) + 0 + sage: B['a'].map_coefficients(lambda c: GF(2)(c), QQ) + B['a'] + """ - return self.parent().sum_of_terms( (m, f(c)) for m,c in self ) + R = self.parent() + if isinstance(f, Map): + B = f.codomain() + else: + B = self.base_ring() + if new_base_ring is not None: + B = new_base_ring + if B is not self.base_ring(): + R = R.change_ring(B) + mc = self.monomial_coefficients(copy=False) + return R.sum_of_terms((m, B(f(c))) for m, c in mc.items()) def map_support(self, f): """ @@ -2139,7 +2184,7 @@ def map_support(self, f): sage: y.parent() is B # needs sage.modules True """ - return self.parent().sum_of_terms( (f(m), c) for m,c in self ) + return self.parent().sum_of_terms((f(m), c) for m, c in self) def map_support_skip_none(self, f): """ @@ -2173,7 +2218,9 @@ def map_support_skip_none(self, f): sage: y.parent() is B # needs sage.modules True """ - return self.parent().sum_of_terms( (fm,c) for (fm,c) in ((f(m), c) for m,c in self) if fm is not None) + return self.parent().sum_of_terms((fm, c) + for fm, c in ((f(m), c) for m, c in self) + if fm is not None) def map_item(self, f): """ @@ -2207,7 +2254,7 @@ def map_item(self, f): sage: a.map_item(f) # needs sage.combinat sage.modules 2*s[2, 1] + 2*s[3] """ - return self.parent().sum_of_terms( f(m,c) for m,c in self ) + return self.parent().sum_of_terms(f(m, c) for m, c in self) def tensor(*elements): """ @@ -2543,11 +2590,13 @@ def apply_multilinear_morphism(self, f, codomain=None): except AttributeError: codomain = f(*[module.zero() for module in modules]).parent() if codomain in ModulesWithBasis(K): - return codomain.linear_combination((f(*[module.monomial(t) for (module,t) in zip(modules, m)]), c) - for m,c in self) + return codomain.linear_combination((f(*[module.monomial(t) + for module, t in zip(modules, m)]), c) + for m, c in self) else: - return sum((c * f(*[module.monomial(t) for (module,t) in zip(modules, m)]) - for m,c in self), + return sum((c * f(*[module.monomial(t) + for module, t in zip(modules, m)]) + for m, c in self), codomain.zero()) class DualObjects(DualObjectsCategory): diff --git a/src/sage/categories/morphism.pxd b/src/sage/categories/morphism.pxd index e5befc8207e..52847fd83b7 100644 --- a/src/sage/categories/morphism.pxd +++ b/src/sage/categories/morphism.pxd @@ -8,3 +8,6 @@ cdef class Morphism(Map): cdef class SetMorphism(Morphism): cdef object _function cpdef bint _eq_c_impl(left, Element right) noexcept + +cdef class SetIsomorphism(SetMorphism): + cdef object _inverse diff --git a/src/sage/categories/morphism.pyx b/src/sage/categories/morphism.pyx index 0e4805ef1b4..d4b412dd126 100644 --- a/src/sage/categories/morphism.pyx +++ b/src/sage/categories/morphism.pyx @@ -570,7 +570,7 @@ cdef class SetMorphism(Morphism): - ``parent`` -- a Homset - ``function`` -- a Python function that takes elements - of the domain as input and returns elements of the domain. + of the domain as input and returns elements of the codomain. EXAMPLES:: @@ -736,3 +736,159 @@ cdef class SetMorphism(Morphism): return not (isinstance(right, Element) and self._eq_c_impl(right)) else: return False + + +cdef class SetIsomorphism(SetMorphism): + r""" + An isomorphism of sets. + + INPUT: + + - ``parent`` -- a Homset + - ``function`` -- a Python function that takes elements + of the domain as input and returns elements of the codomain. + + EXAMPLES:: + + sage: f = sage.categories.morphism.SetIsomorphism(Hom(ZZ, ZZ, Sets()), + ....: operator.__neg__); f + Generic endomorphism of Integer Ring + sage: f._set_inverse(f) + sage: ~f is f + True + """ + def _set_inverse(self, inverse): + r""" + Set the inverse morphism of ``self`` to be ``inverse``. + + INPUT: + + - ``inverse`` -- a :class:`SetIsomorphism` + + EXAMPLES:: + + sage: f = sage.categories.morphism.SetIsomorphism(Hom(ZZ, ZZ, Sets()), + ....: operator.__neg__) + sage: f._set_inverse(f) + sage: ~f is f + True + """ + self._inverse = inverse + + def __invert__(self): + r""" + Return the inverse morphism of ``self``. + + If :meth:`_set_inverse` has not been called yet, an error is raised. + + EXAMPLES:: + + sage: f = sage.categories.morphism.SetIsomorphism(Hom(ZZ, ZZ, Sets()), + ....: operator.__neg__) + sage: ~f + Traceback (most recent call last): + ... + RuntimeError: inverse morphism has not been set + sage: f._set_inverse(f) + sage: ~f + Generic endomorphism of Integer Ring + """ + if not self._inverse: + raise RuntimeError('inverse morphism has not been set') + return self._inverse + + cdef dict _extra_slots(self) noexcept: + """ + Extend the dictionary with extra slots for this class. + + INPUT: + + - ``_slots`` -- a dictionary + + EXAMPLES:: + + sage: f = sage.categories.morphism.SetIsomorphism(Hom(ZZ, ZZ, Sets()), + ....: operator.__neg__) + sage: f._set_inverse(f) + sage: f._extra_slots_test() + {'_codomain': Integer Ring, + '_domain': Integer Ring, + '_function': , + '_inverse': Generic endomorphism of Integer Ring, + '_is_coercion': False, + '_repr_type_str': None} + """ + slots = SetMorphism._extra_slots(self) + slots['_inverse'] = self._inverse + return slots + + cdef _update_slots(self, dict _slots) noexcept: + """ + Update the slots of ``self`` from the data in the dictionary. + + INPUT: + + - ``_slots`` -- a dictionary + + EXAMPLES:: + + sage: f = sage.categories.morphism.SetIsomorphism(Hom(ZZ, ZZ, Sets()), + ....: operator.__neg__) + sage: f._update_slots_test({'_function': operator.__neg__, + ....: '_inverse': f, + ....: '_domain': QQ, + ....: '_codomain': QQ, + ....: '_repr_type_str': 'bla'}) + sage: f(3) + -3 + sage: f._repr_type() + 'bla' + sage: f.domain() + Rational Field + sage: f.codomain() + Rational Field + sage: f.inverse() == f + True + """ + self._inverse = _slots['_inverse'] + SetMorphism._update_slots(self, _slots) + + def section(self): + """ + Return a section of this morphism. + + EXAMPLES:: + + sage: f = sage.categories.morphism.SetIsomorphism(Hom(ZZ, ZZ, Sets()), + ....: operator.__neg__) + sage: f._set_inverse(f) + sage: f.section() is f + True + """ + return self.__invert__() + + def is_surjective(self): + r""" + Return whether this morphism is surjective. + + EXAMPLES:: + + sage: f = sage.categories.morphism.SetIsomorphism(Hom(ZZ, ZZ, Sets()), + ....: operator.__neg__) + sage: f.is_surjective() + True + """ + return True + + def is_injective(self): + r""" + Return whether this morphism is injective. + + EXAMPLES:: + + sage: f = sage.categories.morphism.SetIsomorphism(Hom(ZZ, ZZ, Sets()), + ....: operator.__neg__) + sage: f.is_injective() + True + """ + return True diff --git a/src/sage/categories/schemes.py b/src/sage/categories/schemes.py index cf78a901209..0d62237a636 100644 --- a/src/sage/categories/schemes.py +++ b/src/sage/categories/schemes.py @@ -1,14 +1,18 @@ r""" Schemes """ + # **************************************************************************** -# Copyright (C) 2005 David Kohel +# Copyright (C) 2005 David Kohel # William Stein -# 2008-2012 Nicolas M. Thiery +# 2008-2012 Nicolas M. Thiery # -# Distributed under the terms of the GNU General Public License (GPL) +# 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.categories.category import Category from sage.categories.category_types import Category_over_base @@ -18,6 +22,7 @@ from sage.categories.rings import Rings from sage.categories.fields import Fields from sage.categories.homsets import HomsetsCategory +from sage.misc.abstract_method import abstract_method class Schemes(Category): @@ -55,7 +60,6 @@ class Schemes(Category): sage: Schemes().Homsets().super_categories() [Category of homsets] """ - @staticmethod def __classcall_private__(cls, X=None): """ @@ -73,8 +77,7 @@ def __classcall_private__(cls, X=None): Category of schemes over Integer Ring """ if X is not None: - from sage.schemes.generic.scheme import is_Scheme - if not is_Scheme(X): + if X not in Schemes(): X = Schemes()(X) return Schemes_over_base(X) return super().__classcall__(cls) @@ -155,9 +158,6 @@ def _call_(self, x): raise TypeError("No way to create an object or morphism in %s from %s" % (self, x)) -############################################################# -# Schemes over a given base scheme. -############################################################# class Schemes_over_base(Category_over_base): """ The category of schemes over a given base scheme. @@ -172,7 +172,6 @@ class Schemes_over_base(Category_over_base): sage: C = Schemes(ZZ) sage: TestSuite(C).run() """ - def base_scheme(self): """ EXAMPLES:: @@ -198,12 +197,12 @@ def _repr_object_names(self): sage: Schemes(Spec(ZZ)) # indirect doctest Category of schemes over Integer Ring """ - # To work around the name of the class (schemes_over_base) from sage.schemes.generic.scheme import is_AffineScheme - if is_AffineScheme(self.base_scheme()): - return "schemes over %s" % self.base_scheme().coordinate_ring() - else: - return "schemes over %s" % self.base_scheme() + base = self.base() + if is_AffineScheme(base): + base = base.coordinate_ring() + return f"schemes over {base}" + class AbelianVarieties(Schemes_over_base): r""" @@ -236,6 +235,18 @@ def __init__(self, base): raise ValueError('category of abelian varieties is only defined over fields') super().__init__(base) + def base_scheme(self): + """ + EXAMPLES:: + + sage: Schemes(Spec(ZZ)).base_scheme() + Spectrum of Integer Ring + """ + base = self.base() + if base not in Schemes(): + base = Schemes()(base) + return base + def super_categories(self): """ EXAMPLES:: @@ -253,7 +264,7 @@ def _repr_object_names(self): sage: AbelianVarieties(Spec(QQ)) # indirect doctest Category of abelian varieties over Rational Field """ - return "abelian varieties over %s" % self.base_scheme() + return "abelian varieties over %s" % self.base() class Homsets(HomsetsCategory): r""" @@ -294,3 +305,87 @@ def extra_super_categories(self): True """ return [Rings()] + + +class Jacobians(Schemes_over_base): + """ + The category of Jacobians attached to curves or function fields. + + EXAMPLES:: + + sage: Jacobians(QQ) + Category of Jacobians over Rational Field + + TESTS:: + + sage: TestSuite(Jacobians(QQ)).run() + """ + def __init__(self, base): + r""" + Constructor of this category. + + EXAMPLES:: + + sage: Jacobians(QQ) + Category of Jacobians over Rational Field + sage: Jacobians(Spec(QQ)) + Category of Jacobians over Rational Field + """ + from sage.schemes.generic.scheme import is_AffineScheme + if is_AffineScheme(base): + base = base.coordinate_ring() + if base not in Fields(): + raise ValueError('category of Jacobians is only defined over fields') + super().__init__(base) + + def base_scheme(self): + """ + Return the base scheme of this Jacobians category. + + EXAMPLES:: + + sage: Jacobians(QQ).base_scheme() + Spectrum of Rational Field + """ + base = self.base() + if base not in Schemes(): + base = Schemes()(base) + return base + + def super_categories(self): + """ + Return the super categories of this Jacobians category. + + EXAMPLES:: + + sage: Jacobians(QQ).super_categories() + [Category of abelian varieties over Rational Field] + """ + return [AbelianVarieties(self.base_scheme())] + + def _repr_object_names(self): + """ + Return the string representation of this category. + + EXAMPLES:: + + sage: Jacobians(Spec(QQ)) # indirect doctest + Category of Jacobians over Rational Field + """ + return "Jacobians over %s" % self.base() + + class ParentMethods: + + @abstract_method + def base_curve(self): + """ + Return the curve to which this Jacobian is attached. + + EXAMPLES:: + + sage: # needs sage.rings.function_field + sage: K. = FunctionField(GF(2)) + sage: J = K.jacobian() + sage: J.base_curve() + Rational function field in x over Finite Field of size 2 + """ diff --git a/src/sage/combinat/affine_permutation.py b/src/sage/combinat/affine_permutation.py index 193d1f1c966..3cdff883922 100644 --- a/src/sage/combinat/affine_permutation.py +++ b/src/sage/combinat/affine_permutation.py @@ -2244,7 +2244,7 @@ def one(self): True sage: TestSuite(A).run() """ - return self([i for i in range(1,self.k+2)]) + return self(list(range(1, self.k + 2))) #------------------------ #Type-unique methods. diff --git a/src/sage/combinat/all.py b/src/sage/combinat/all.py index d1f391013e2..4ab35568bb4 100644 --- a/src/sage/combinat/all.py +++ b/src/sage/combinat/all.py @@ -56,8 +56,7 @@ from sage.misc.lazy_import import lazy_import -from .combinat import (CombinatorialClass, CombinatorialObject, - MapCombinatorialClass, +from .combinat import (CombinatorialObject, bell_number, bell_polynomial, bernoulli_polynomial, catalan_number, euler_number, fibonacci, fibonacci_sequence, fibonacci_xrange, @@ -66,12 +65,6 @@ polygonal_number, stirling_number1, stirling_number2, tuples, unordered_tuples) -lazy_import('sage.combinat.combinat', - ('InfiniteAbstractCombinatorialClass', 'UnionCombinatorialClass', - 'FilteredCombinatorialClass'), - deprecation=(31545, 'this class is deprecated, do not use')) - - from .expnums import expnums from sage.combinat.chas.all import * diff --git a/src/sage/combinat/cartesian_product.py b/src/sage/combinat/cartesian_product.py index c5a16d3f8ee..ef151e55ffa 100644 --- a/src/sage/combinat/cartesian_product.py +++ b/src/sage/combinat/cartesian_product.py @@ -112,6 +112,16 @@ def iterfunc(): category=category, cache=False) + def __hash__(self): + r""" + EXAMPLES:: + + sage: from sage.combinat.cartesian_product import CartesianProduct_iters + sage: cp = CartesianProduct_iters((1,2), (3,4)) + sage: hash(cp) == CartesianProduct_iters((1,2), (3,4)) + """ + return hash(tuple(self.iters)) + def __contains__(self, x): """ EXAMPLES:: diff --git a/src/sage/combinat/combinat.py b/src/sage/combinat/combinat.py index bd5e7102d66..382c915db77 100644 --- a/src/sage/combinat/combinat.py +++ b/src/sage/combinat/combinat.py @@ -180,8 +180,6 @@ from sage.misc.lazy_import import lazy_import from sage.misc.lazy_attribute import lazy_attribute from .combinat_cython import _stirling_number2 -from sage.categories.enumerated_sets import EnumeratedSets -from sage.misc.classcall_metaclass import ClasscallMetaclass from sage.misc.inherit_comparison import InheritComparisonClasscallMetaclass from sage.structure.element import Element @@ -1581,1011 +1579,6 @@ def __init__(self, parent, *args, **kwds): super().__init__(L) super(CombinatorialObject, self).__init__(parent) - -class CombinatorialClass(Parent, metaclass=ClasscallMetaclass): - """ - This class is deprecated, and will disappear as soon as all derived - classes in Sage's library will have been fixed. Please derive - directly from Parent and use the category :class:`EnumeratedSets`, - :class:`FiniteEnumeratedSets`, or :class:`InfiniteEnumeratedSets`, as - appropriate. - - For examples, see:: - - sage: FiniteEnumeratedSets().example() - An example of a finite enumerated set: {1,2,3} - sage: InfiniteEnumeratedSets().example() - An example of an infinite enumerated set: the non negative integers - """ - - def __init__(self, category=None): - """ - TESTS:: - - sage: C = sage.combinat.combinat.CombinatorialClass() - sage: C.category() - Category of enumerated sets - sage: C.__class__ - - sage: isinstance(C, Parent) - True - sage: C = sage.combinat.combinat.CombinatorialClass(category = FiniteEnumeratedSets()) - sage: C.category() - Category of finite enumerated sets - """ - Parent.__init__(self, category=EnumeratedSets().or_subcategory(category)) - - def is_finite(self) -> bool: - """ - Return whether ``self`` is finite or not. - - EXAMPLES:: - - sage: Partitions(5).is_finite() # needs sage.combinat - True - sage: Permutations().is_finite() - False - """ - return self.cardinality() != infinity - - def __getitem__(self, i): - """ - Return the combinatorial object of rank i. - - EXAMPLES:: - - sage: class C(CombinatorialClass): - ....: def __iter__(self): - ....: return iter([1,2,3]) - sage: c = C() - sage: c[0] - 1 - sage: c[2] - 3 - sage: c[4] - Traceback (most recent call last): - ... - ValueError: the value must be between 0 and 2 inclusive - """ - return self.unrank(i) - - def __str__(self) -> str: - """ - Return a string representation of self. - - EXAMPLES:: - - sage: str(Partitions(5)) # needs sage.combinat - 'Partitions of the integer 5' - """ - return repr(self) - - def _repr_(self) -> str: - """ - EXAMPLES:: - - sage: repr(Partitions(5)) # indirect doctest # needs sage.combinat - 'Partitions of the integer 5' - """ - if hasattr(self, '_name') and self._name: - return self._name - else: - return "Combinatorial Class -- REDEFINE ME!" - - def __contains__(self, x) -> bool: - """ - Test whether or not the combinatorial class contains the object x. - This raises a NotImplementedError as a default since _all_ - subclasses of CombinatorialClass should override this. - - Note that we could replace this with a default implementation that - just iterates through the elements of the combinatorial class and - checks for equality. However, since we use __contains__ for - type checking, this operation should be cheap and should be - implemented manually for each combinatorial class. - - EXAMPLES:: - - sage: C = CombinatorialClass() - sage: x in C # needs sage.symbolic - Traceback (most recent call last): - ... - NotImplementedError - """ - raise NotImplementedError - - def __eq__(self, other): - """ - Compare two different combinatorial classes. - - For now, the comparison is done just on their repr's. - - EXAMPLES:: - - sage: # needs sage.combinat - sage: p5 = Partitions(5) - sage: p6 = Partitions(6) - sage: repr(p5) == repr(p6) - False - sage: p5 == p6 - False - """ - return repr(self) == repr(other) - - def __ne__(self, other): - """ - Test unequality of ``self`` and ``other``. - - EXAMPLES:: - - sage: p5 = Partitions(5) # needs sage.combinat - sage: p6 = Partitions(6) # needs sage.combinat - sage: p5 != p6 # needs sage.combinat - True - """ - return not (self == other) - - def __hash__(self): - """ - Create a hash value. This is based on the string representation. - - Note that in Python 3 objects that define __eq__ do not inherit their __hash__ - function. Without an explicit __hash__ they are no longer hashable. - - TESTS:: - - sage: C = CombinatorialClass() - sage: hash(C) == hash(repr(C)) - True - """ - return hash(repr(self)) - - def __cardinality_from_iterator(self) -> Integer | infinity: - """ - Default implementation of cardinality which just goes through the iterator - of the combinatorial class to count the number of objects. - - EXAMPLES:: - - sage: class C(CombinatorialClass): - ....: def __iter__(self): - ....: return iter([1,2,3]) - sage: C().cardinality() #indirect doctest - 3 - """ - c = Integer(0) - one = Integer(1) - for _ in self: - c += one - return c - - cardinality = __cardinality_from_iterator - - # __call__, element_class, and _element_constructor_ are poor - # man's versions of those from Parent. This is for transition, - # until all combinatorial classes are proper parents (in Parent) - # and use coercion, etcc - - def __call__(self, x): - """ - Return x as an element of the combinatorial class's object class. - - EXAMPLES:: - - sage: # needs sage.combinat - sage: p5 = Partitions(5) - sage: a = [2,2,1] - sage: type(a) - - sage: a = p5(a) - sage: type(a) - - sage: p5([2,1]) - Traceback (most recent call last): - ... - ValueError: [2, 1] is not an element of Partitions of the integer 5 - """ - if x in self: - return self._element_constructor_(x) - else: - raise ValueError("%s not in %s" % (x, self)) - - Element = CombinatorialObject # mostly for backward compatibility - - @lazy_attribute - def element_class(self): - """ - This function is a temporary helper so that a CombinatorialClass - behaves as a parent for creating elements. This will disappear when - combinatorial classes will be turned into actual parents (in the - category EnumeratedSets). - - TESTS:: - - sage: P5 = Partitions(5) # needs sage.combinat - sage: P5.element_class # needs sage.combinat - - """ - # assert not isinstance(self, Parent) # Raises an alert if we override the proper definition from Parent - return self.Element - - def _element_constructor_(self, x): - """ - This function is a temporary helper so that a CombinatorialClass - behaves as a parent for creating elements. This will disappear when - combinatorial classes will be turned into actual parents (in the - category EnumeratedSets). - - TESTS:: - - sage: P5 = Partitions(5) # needs sage.combinat - sage: p = P5([3,2]) # indirect doctest # needs sage.combinat - sage: type(p) # needs sage.combinat - - """ - # assert not isinstance(self, Parent) # Raises an alert if we override the proper definition from Parent - return self.element_class(x) - - def __list_from_iterator(self): - """ - The default implementation of list which builds the list from the - iterator. - - EXAMPLES:: - - sage: class C(CombinatorialClass): - ....: def __iter__(self): - ....: return iter([1,2,3]) - sage: C().list() #indirect doctest - [1, 2, 3] - """ - return [x for x in self] - - # Set list to the default implementation - list = __list_from_iterator - - # Set the default object class to be CombinatorialObject - Element = CombinatorialObject - - def __iterator_from_next(self) -> Iterator: - """ - An iterator to use when the .first() and .next(x) methods are provided. - - EXAMPLES:: - - sage: C = CombinatorialClass() - sage: C.first = lambda: 0 - sage: C.next = lambda c: c+1 - sage: it = iter(C) # indirect doctest - sage: [next(it) for _ in range(4)] - [0, 1, 2, 3] - """ - f = self.first() - yield f - while True: - try: - f = self.next(f) - except (TypeError, ValueError): - break - - if f is None or f is False: - break - else: - yield f - - def __iterator_from_previous(self): - """ - An iterator to use when .last() and .previous() are provided. Note - that this requires the combinatorial class to be finite. It is not - recommended to implement combinatorial classes using last and - previous. - - EXAMPLES:: - - sage: C = CombinatorialClass() - sage: C.last = lambda: 4 - sage: def prev(c): - ....: if c <= 1: - ....: return None - ....: else: - ....: return c-1 - sage: C.previous = prev - sage: it = iter(C) # indirect doctest - sage: [next(it) for _ in range(4)] - [1, 2, 3, 4] - """ - l = self.last() - li = [l] - while True: - try: - l = self.previous(l) - except (TypeError, ValueError): - break - - if l is None: - break - else: - li.append(l) - return reversed(li) - - def __iterator_from_unrank(self) -> Iterator: - """ - An iterator to use when .unrank() is provided. - - EXAMPLES:: - - sage: C = CombinatorialClass() - sage: l = [1,2,3] - sage: C.unrank = lambda c: l[c] - sage: list(C) # indirect doctest - [1, 2, 3] - """ - r = 0 - u = self.unrank(r) - yield u - while True: - r += 1 - try: - u = self.unrank(r) - except (TypeError, ValueError, IndexError): - break - - if u is None: - break - else: - yield u - - def __iterator_from_list(self) -> Iterator: - """ - An iterator to use when .list() is provided() - - EXAMPLES:: - - sage: C = CombinatorialClass() - sage: C.list = lambda: [1, 2, 3] - sage: list(C) # indirect doctest - [1, 2, 3] - """ - yield from self.list() - - def __iter__(self): - """ - Allows the combinatorial class to be treated as an iterator. Default - implementation. - - EXAMPLES:: - - sage: p5 = Partitions(5) # needs sage.combinat - sage: [i for i in p5] # needs sage.combinat - [[5], [4, 1], [3, 2], [3, 1, 1], [2, 2, 1], [2, 1, 1, 1], [1, 1, 1, 1, 1]] - sage: C = CombinatorialClass() - sage: iter(C) - Traceback (most recent call last): - ... - NotImplementedError: iterator called but not implemented - """ - # Check whether .first() and .next(x) are overridden in the subclass - if (self.first != self.__first_from_iterator and - self.next != self.__next_from_iterator): - return self.__iterator_from_next() - # Check whether .last() and .previous() are overridden in the subclass - elif (self.last != self.__last_from_iterator and - self.previous != self.__previous_from_iterator): - return self.__iterator_from_previous() - # Check whether .unrank() is overridden in the subclass - elif self.unrank != self.__unrank_from_iterator: - return self.__iterator_from_unrank() - # Check whether .list() is overridden in the subclass - elif self.list != self.__list_from_iterator: - return self.__iterator_from_list() - else: - raise NotImplementedError("iterator called but not implemented") - - def __unrank_from_iterator(self, r): - """ - Default implementation of unrank which goes through the iterator. - - EXAMPLES:: - - sage: C = CombinatorialClass() - sage: C.list = lambda: [1,2,3] - sage: C.unrank(1) # indirect doctest - 2 - """ - counter = 0 - for u in self: - if counter == r: - return u - counter += 1 - raise ValueError("the value must be between %s and %s inclusive" % (0, counter - 1)) - - # Set the default implementation of unrank - unrank = __unrank_from_iterator - - def __random_element_from_unrank(self): - """ - Default implementation of random which uses unrank. - - EXAMPLES:: - - sage: C = CombinatorialClass() - sage: C.list = lambda: [1,2,3] - sage: C.random_element() # random # indirect doctest - 1 - """ - c = self.cardinality() - r = randint(0, c - 1) - return self.unrank(r) - - # Set the default implementation of random - random_element = __random_element_from_unrank - - def __rank_from_iterator(self, obj): - """ - Default implementation of rank which uses iterator. - - EXAMPLES:: - - sage: C = CombinatorialClass() - sage: C.list = lambda: [1,2,3] - sage: C.rank(3) # indirect doctest - 2 - """ - r = 0 - for i in self: - if i == obj: - return r - r += 1 - raise ValueError - - rank = __rank_from_iterator - - def __first_from_iterator(self): - """ - Default implementation for first which uses iterator. - - EXAMPLES:: - - sage: C = CombinatorialClass() - sage: C.list = lambda: [1,2,3] - sage: C.first() # indirect doctest - 1 - """ - for i in self: - return i - - first = __first_from_iterator - - def __last_from_iterator(self): - """ - Default implementation for first which uses iterator. - - EXAMPLES:: - - sage: C = CombinatorialClass() - sage: C.list = lambda: [1,2,3] - sage: C.last() # indirect doctest - 3 - """ - for i in self: - pass - return i - - last = __last_from_iterator - - def __next_from_iterator(self, obj): - """ - Default implementation for next which uses iterator. - - EXAMPLES:: - - sage: C = CombinatorialClass() - sage: C.list = lambda: [1,2,3] - sage: C.next(2) # indirect doctest - 3 - """ - found = False - for i in self: - if found: - return i - if i == obj: - found = True - return None - - next = __next_from_iterator - - def __previous_from_iterator(self, obj): - """ - Default implementation for next which uses iterator. - - EXAMPLES:: - - sage: C = CombinatorialClass() - sage: C.list = lambda: [1,2,3] - sage: C.previous(2) # indirect doctest - 1 - """ - prev = None - for i in self: - if i == obj: - break - prev = i - return prev - - previous = __previous_from_iterator - - def filter(self, f, name=None): - """ - Return the combinatorial subclass of f which consists of the - elements x of ``self`` such that f(x) is ``True``. - - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3).filter(lambda x: x.avoids([1,2])) - sage: P.list() # needs sage.combinat - [[3, 2, 1]] - """ - return FilteredCombinatorialClass(self, f, name=name) - - def union(self, right_cc, name=None): - """ - Return the combinatorial class representing the union of ``self`` and - ``right_cc``. - - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(2).union(Permutations_CC(1)) - sage: P.list() - [[1, 2], [2, 1], [1]] - """ - if not isinstance(right_cc, CombinatorialClass): - raise TypeError("right_cc must be a CombinatorialClass") - return UnionCombinatorialClass(self, right_cc, name=name) - - def map(self, f, name=None, *, is_injective=True): - r""" - Return the image `\{f(x) | x \in \text{self}\}` of this combinatorial - class by `f`, as a combinatorial class. - - INPUT: - - - ``is_injective`` -- boolean (default: ``True``) whether to assume - that ``f`` is injective. - - EXAMPLES:: - - sage: R = Permutations(3).map(attrcall('reduced_word')); R - Image of Standard permutations of 3 by - The map *.reduced_word() from Standard permutations of 3 - sage: R.cardinality() - 6 - sage: R.list() - [[], [2], [1], [1, 2], [2, 1], [2, 1, 2]] - sage: [ r for r in R] - [[], [2], [1], [1, 2], [2, 1], [2, 1, 2]] - - If the function is not injective, then there may be repeated elements:: - - sage: P = Partitions(4) # needs sage.combinat - sage: P.list() # needs sage.combinat - [[4], [3, 1], [2, 2], [2, 1, 1], [1, 1, 1, 1]] - sage: P.map(len).list() # needs sage.combinat - [1, 2, 2, 3, 4] - - Use ``is_injective=False`` to get a correct result in this case:: - - sage: P.map(len, is_injective=False).list() # needs sage.combinat - [1, 2, 3, 4] - - TESTS:: - - sage: R = Permutations(3).map(attrcall('reduced_word')) - sage: R == loads(dumps(R)) - True - """ - return MapCombinatorialClass(self, f, name, is_injective=is_injective) - - -class FilteredCombinatorialClass(CombinatorialClass): - def __init__(self, combinatorial_class, f, name=None): - """ - A filtered combinatorial class F is a subset of another - combinatorial class C specified by a function f that takes in an - element c of C and returns True if and only if c is in F. - - TESTS:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: Permutations_CC(3).filter(lambda x: x.avoids([1,2])) - Filtered subclass of Standard permutations of 3 - """ - self.f = f - self.combinatorial_class = combinatorial_class - self._name = name - - def __repr__(self): - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3).filter(lambda x: x.avoids([1,2])) - sage: P.__repr__() - 'Filtered subclass of Standard permutations of 3' - sage: P._name = 'Permutations avoiding [1, 2]' - sage: P.__repr__() - 'Permutations avoiding [1, 2]' - """ - if self._name: - return self._name - else: - return "Filtered subclass of " + repr(self.combinatorial_class) - - def __contains__(self, x) -> bool: - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3).filter(lambda x: x.avoids([1,2])) - sage: 'cat' in P - False - sage: [4,3,2,1] in P - False - sage: Permutation([1,2,3]) in P # needs sage.combinat - False - sage: Permutation([3,2,1]) in P # needs sage.combinat - True - """ - return x in self.combinatorial_class and self.f(x) - - def cardinality(self) -> Integer: - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3).filter(lambda x: x.avoids([1,2])) - sage: P.cardinality() # needs sage.combinat - 1 - """ - c = 0 - for _ in self: - c += 1 - return c - - def __iter__(self) -> Iterator: - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3).filter(lambda x: x.avoids([1,2])) - sage: list(P) # needs sage.combinat - [[3, 2, 1]] - """ - for x in self.combinatorial_class: - if self.f(x): - yield x - - -class UnionCombinatorialClass(CombinatorialClass): - def __init__(self, left_cc, right_cc, name=None): - """ - A UnionCombinatorialClass is a union of two other combinatorial - classes. - - TESTS:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3).union(Permutations_CC(2)) - sage: P == loads(dumps(P)) - True - """ - self.left_cc = left_cc - self.right_cc = right_cc - self._name = name - - def __repr__(self) -> str: - """ - TESTS:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: print(repr(Permutations_CC(3).union(Permutations_CC(2)))) - Union combinatorial class of - Standard permutations of 3 - and - Standard permutations of 2 - """ - if self._name: - return self._name - else: - return "Union combinatorial class of \n %s\nand\n %s" % (self.left_cc, self.right_cc) - - def __contains__(self, x) -> bool: - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3).union(Permutations_CC(2)) - sage: [1,2] in P - True - sage: [3,2,1] in P - True - sage: [1,2,3,4] in P - False - """ - return x in self.left_cc or x in self.right_cc - - def cardinality(self) -> Integer | infinity: - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3).union(Permutations_CC(2)) - sage: P.cardinality() - 8 - """ - return self.left_cc.cardinality() + self.right_cc.cardinality() - - def list(self): - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3).union(Permutations_CC(2)) - sage: P.list() - [[1, 2, 3], - [1, 3, 2], - [2, 1, 3], - [2, 3, 1], - [3, 1, 2], - [3, 2, 1], - [1, 2], - [2, 1]] - """ - return self.left_cc.list() + self.right_cc.list() - - def __iter__(self) -> Iterator: - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3).union(Permutations_CC(2)) - sage: list(P) - [[1, 2, 3], - [1, 3, 2], - [2, 1, 3], - [2, 3, 1], - [3, 1, 2], - [3, 2, 1], - [1, 2], - [2, 1]] - """ - for x in self.left_cc: - yield x - for x in self.right_cc: - yield x - - def first(self): - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3).union(Permutations_CC(2)) - sage: P.first() - [1, 2, 3] - """ - return self.left_cc.first() - - def last(self): - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3).union(Permutations_CC(2)) - sage: P.last() - [2, 1] - """ - return self.right_cc.last() - - def rank(self, x): - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3).union(Permutations_CC(2)) - sage: P.rank(Permutation([2,1])) - 7 - sage: P.rank(Permutation([1,2,3])) - 0 - """ - try: - return self.left_cc.rank(x) - except (TypeError, ValueError): - return self.left_cc.cardinality() + self.right_cc.rank(x) - - def unrank(self, x): - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3).union(Permutations_CC(2)) - sage: P.unrank(7) - [2, 1] - sage: P.unrank(0) - [1, 2, 3] - """ - try: - return self.left_cc.unrank(x) - except (TypeError, ValueError): - return self.right_cc.unrank(x - self.left_cc.cardinality()) - - -class Permutations_CC(CombinatorialClass): - """ - A testing class for :class:`CombinatorialClass` since :class:`Permutations` - no longer inherits from :class:`CombinatorialClass` in :issue:`14772`. - """ - - def __init__(self, n): - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(4) - sage: loads(dumps(P)) == P - True - """ - from sage.combinat.permutation import StandardPermutations_n - self._permutations = StandardPermutations_n(n) - - def __repr__(self) -> str: - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: Permutations_CC(3) - Standard permutations of 3 - """ - return repr(self._permutations) - - def __contains__(self, x) -> bool: - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3) - sage: [1, 3, 2] in P - True - """ - return x in self._permutations - - def __iter__(self): - """ - EXAMPLES:: - - sage: from sage.combinat.combinat import Permutations_CC - sage: P = Permutations_CC(3) - sage: P.list() - [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]] - """ - return iter(self._permutations) - - -############################################################################## -from sage.sets.image_set import ImageSubobject - - -class MapCombinatorialClass(ImageSubobject, CombinatorialClass): - r""" - The image of a combinatorial class through a function. - - INPUT: - - - ``is_injective`` -- boolean (default: ``True``) whether to assume - that ``f`` is injective. - - See :meth:`CombinatorialClass.map` for examples - - EXAMPLES:: - - sage: # needs sage.groups - sage: R = SymmetricGroup(10).map(attrcall('reduced_word')) - sage: R.an_element() - [9, 8, 7, 6, 5, 4, 3, 2] - sage: R.cardinality() - 3628800 - sage: i = iter(R) - sage: next(i), next(i), next(i) - ([], [1, 2, 3, 4, 5, 6, 7, 8, 9], [1]) - """ - - def __init__(self, cc, f, name=None, *, is_injective=True): - """ - TESTS:: - - sage: Partitions(3).map(attrcall('conjugate')) # needs sage.combinat - Image of Partitions of the integer 3 by The map *.conjugate() - from Partitions of the integer 3 - """ - ImageSubobject.__init__(self, f, cc, is_injective=is_injective) - self.cc = cc - self.f = f - if name: - self.rename(name) - - -############################################################################## -class InfiniteAbstractCombinatorialClass(CombinatorialClass): - r""" - This is an internal class that should not be used directly. A class which - inherits from InfiniteAbstractCombinatorialClass inherits the standard - methods list and count. - - If self._infinite_cclass_slice exists then self.__iter__ returns an - iterator for self, otherwise raise NotImplementedError. The method - self._infinite_cclass_slice is supposed to accept any integer as an - argument and return something which is iterable. - """ - - def cardinality(self) -> Integer | infinity: - """ - Count the elements of the combinatorial class. - - EXAMPLES:: - - sage: R = InfiniteAbstractCombinatorialClass() - doctest:warning... - DeprecationWarning: this class is deprecated, do not use - See https://github.com/sagemath/sage/issues/31545 for details. - - sage: R.cardinality() - +Infinity - """ - return infinity - - def list(self): - """ - Return an error since ``self`` is an infinite combinatorial class. - - EXAMPLES:: - - sage: R = InfiniteAbstractCombinatorialClass() - sage: R.list() - Traceback (most recent call last): - ... - NotImplementedError: infinite list - """ - raise NotImplementedError("infinite list") - - def __iter__(self) -> Iterator: - """ - Return an iterator for the infinite combinatorial class ``self`` if - possible or raise a NotImplementedError. - - EXAMPLES:: - - sage: R = InfiniteAbstractCombinatorialClass() - sage: next(iter(R)) - Traceback (most recent call last): - ... - NotImplementedError - - sage: c = iter(Compositions()) # indirect doctest - sage: next(c), next(c), next(c), next(c), next(c), next(c) - ([], [1], [1, 1], [2], [1, 1, 1], [1, 2]) - sage: next(c), next(c), next(c), next(c), next(c), next(c) - ([2, 1], [3], [1, 1, 1, 1], [1, 1, 2], [1, 2, 1], [1, 3]) - """ - try: - finite = self._infinite_cclass_slice - except AttributeError: - raise NotImplementedError - i = 0 - while True: - yield from finite(i) - i += 1 - - ##################################################### # combinatorial sets/lists @@ -2918,42 +1911,123 @@ def unshuffle_iterator(a, one=1) -> Iterator: (one if sign else - one)) -def bell_polynomial(n: Integer, k: Integer): +def bell_polynomial(n: Integer, k=None, ordinary=False): r""" - Return the Bell Polynomial + Return the (partial) (exponential/ordinary) bell Polynomial. + + The partial (exponential) *Bell polynomial* is defined by the formula .. MATH:: - B_{n,k}(x_0, x_1, \ldots, x_{n-k}) = - \sum_{\sum{j_i}=k, \sum{(i+1) j_i}=n} + B_{n,k}(x_0, x_1, \ldots, x_{n-k}) = + \sum_{\substack{j_0 + \ldots + j_{n-k} = k \\ 1 j_0 + \ldots + (n-k+1) j_{n-k} = n}} \frac{n!}{j_0!j_1!\cdots j_{n-k}!} \left(\frac{x_0}{(0+1)!}\right)^{j_0} \left(\frac{x_1}{(1+1)!}\right)^{j_1} \cdots \left(\frac{x_{n-k}}{(n-k+1)!}\right)^{j_{n-k}}. - INPUT: + The complete (exponential) Bell Polynomial is defined as - - ``n`` -- integer + .. MATH:: - - ``k`` -- integer + B_n(x_0, x_1, \ldots, x_{n-k}) = + \sum_{k=0}^n B_{n,k}(x_0, x_1, \ldots, x_{n-k}). - OUTPUT: + The ordinary variant of the partial Bell polynomial is defined by - - a polynomial in `n-k+1` variables over `\ZZ` + .. MATH:: - EXAMPLES:: + \hat B_{n,k}(x_0, x_1, \ldots, x_{n-k}) = + \sum_{\substack{j_0 + \ldots + j_{n-k} = k \\ 1 j_0 + \ldots + (n-k+1) j_{n-k} = n}} + \binom{k}{j_0, j_1, \ldots, j_{n-k}} + x_0^{j_0} x_1^{j_1} \cdots x_{n-k}^{j_{n-k}}, + + where we have used the multinomial coefficient. + The complete version has the same definition as its exponential counterpart. + + If we define `f(z) = \sum_{n=1}^\infty x_{n-1} z^n/n!` + then these are alternative definitions for exponential Bell polynomials - sage: bell_polynomial(6,2) # needs sage.combinat - 10*x2^2 + 15*x1*x3 + 6*x0*x4 - sage: bell_polynomial(6,3) # needs sage.combinat + .. MATH:: + + \begin{aligned} + \exp(f(z)) & = \sum_{n=0}^\infty B_n(x_0, \ldots, x_{n-1}) \frac{z^n}{n!}, \\ + \frac{f(z)^k}{k!} & = \sum_{n=k}^\infty B_{n, k}(x_0, \ldots, x_{n-k}) \frac{z^n}{n!}. + \end{aligned} + + Defining `g(z) = \sum_{n=1}^\infty x_{n-1} z^n`, + we have the analoguous alternative definitions + + .. MATH:: + + \begin{aligned} + \frac1{1-f(z)} & = \sum_{n=0}^\infty \hat B_n(x_0, \ldots, x_{n-1}) z^n, \\ + f(z)^k & = \sum_{n=k}^\infty \hat B_{n, k}(x_0, \ldots, x_{n-k}) z^n, + \end{aligned} + + (see reference). + + INPUT: + + - ``k`` -- (optional) if specified, returns the partial Bell + polynomial, otherwise returns the complete Bell polynomial + - ``ordinary`` -- (default: ``False``) if ``True``, returns the + (partial) ordinary Bell polynomial, otherwise returns + the (partial) exponential Bell polynomial + + EXAMPLES: + + The complete and partial Bell polynomials:: + + sage: # needs sage.combinat + sage: bell_polynomial(3) + x0^3 + 3*x0*x1 + x2 + sage: bell_polynomial(4) + x0^4 + 6*x0^2*x1 + 3*x1^2 + 4*x0*x2 + x3 + sage: bell_polynomial(6, 3) 15*x1^3 + 60*x0*x1*x2 + 15*x0^2*x3 + sage: bell_polynomial(6, 6) + x0^6 + + The ordinary variants are:: + + sage: # needs sage.combinat sage.arith + sage: bell_polynomial(3, ordinary=True) + x0^3 + 2*x0*x1 + x2 + sage: bell_polynomial(4, ordinary=True) + x0^4 + 3*x0^2*x1 + x1^2 + 2*x0*x2 + x3 + sage: bell_polynomial(6, 3, True) + x1^3 + 6*x0*x1*x2 + 3*x0^2*x3 + sage: bell_polynomial(6, 6, True) + x0^6 + + We verify the alternative definition of the different Bell polynomials + using the functions `f` and `g` given above:: + + sage: # needs sage.combinat sage.arith + sage: n = 6 # positive integer + sage: k = 4 # positive integer + sage: R. = InfinitePolynomialRing(QQ) + sage: PR = PolynomialRing(QQ, 'x', n) + sage: d = {x[i]: PR.gen(i) for i in range(n)} #substitution dictionnary + sage: L. = LazyPowerSeriesRing(R) + sage: f = L(lambda i: x[i-1]/factorial(i), valuation=1) + sage: all(exp(f)[i].subs(d) * factorial(i) == bell_polynomial(i) for i in range(n+1)) + True + sage: all((f^k/factorial(k))[i].subs(d) * factorial(i) == bell_polynomial(i, k) for i in range(k, n+k)) + True + sage: g = L(lambda i: x[i-1], valuation=1) + sage: all((1/(1-g))[i].subs(d) == bell_polynomial(i, ordinary=True) for i in range(n+1)) + True + sage: all((g^k)[i].subs(d) == bell_polynomial(i, k, True) for i in range(k, n+k)) + True TESTS: Check that :issue:`18338` is fixed:: - sage: bell_polynomial(0,0).parent() # needs sage.combinat - Multivariate Polynomial Ring in x over Integer Ring + sage: bell_polynomial(0, 0).parent() # needs sage.combinat + Univariate Polynomial Ring in x0 over Integer Ring sage: for n in (0..4): # needs sage.combinat ....: print([bell_polynomial(n,k).coefficients() for k in (0..n)]) @@ -2963,28 +2037,65 @@ def bell_polynomial(n: Integer, k: Integer): [[], [1], [3], [1]] [[], [1], [3, 4], [6], [1]] + Further checks for :issue:`37727`:: + + sage: # needs sage.combinat sage.arith + sage: bell_polynomial(0, 0) + 1 + sage: bell_polynomial(0, 0, True) + 1 + sage: bell_polynomial(1, 1) + x0 + sage: bell_polynomial(2, 2, True) + x0^2 + sage: bell_polynomial(5) + x0^5 + 10*x0^3*x1 + 15*x0*x1^2 + 10*x0^2*x2 + 10*x1*x2 + 5*x0*x3 + x4 + sage: sum(bell_polynomial(5, k) for k in range(6)) + x0^5 + 10*x0^3*x1 + 15*x0*x1^2 + 10*x0^2*x2 + 10*x1*x2 + 5*x0*x3 + x4 + sage: bell_polynomial(5, None, True) + x0^5 + 4*x0^3*x1 + 3*x0*x1^2 + 3*x0^2*x2 + 2*x1*x2 + 2*x0*x3 + x4 + sage: sum(bell_polynomial(5, k, True) for k in range(6)) + x0^5 + 4*x0^3*x1 + 3*x0*x1^2 + 3*x0^2*x2 + 2*x1*x2 + 2*x0*x3 + x4 + sage: bell_polynomial(0).parent() + Univariate Polynomial Ring in x0 over Integer Ring REFERENCES: - [Bel1927]_ + - [Com1974]_ AUTHORS: - Blair Sutton (2009-01-26) - Thierry Monteil (2015-09-29): the result must always be a polynomial. + - Kei Beauduin (2024-04-06): when univariate, + the polynomial is in variable ``x0``. extended to complete exponential, + partial ordinary and complete ordinary Bell polynomials. """ from sage.combinat.partition import Partitions - R = PolynomialRing(ZZ, 'x', n - k + 1) + from sage.arith.misc import multinomial + if k is None: + partitions = Partitions(n) + # We set k = 1 to use the correct ring + # It is not used in the computation otherwise + k = 1 + else: + partitions = Partitions(n, length=k) + if n <= k: + R = PolynomialRing(ZZ, 'x0') + else: + R = PolynomialRing(ZZ, 'x', n - k + 1) vars = R.gens() result = R.zero() - for p in Partitions(n, length=k): # type:ignore - factorial_product = 1 - power_factorial_product = 1 - for part, count in p.to_exp_dict().items(): - factorial_product *= factorial(count) - power_factorial_product *= factorial(part)**count - coefficient = factorial(n) // (factorial_product * power_factorial_product) - result += coefficient * prod([vars[i - 1] for i in p]) + for p in partitions: + if ordinary: + coefficient = multinomial(p.to_exp()) + else: + factorial_product = 1 + for part, count in p.to_exp_dict().items(): + factorial_product *= factorial(count) * factorial(part)**count + coefficient = factorial(n) // factorial_product + result += coefficient * prod(vars[i - 1] for i in p) return result diff --git a/src/sage/combinat/constellation.py b/src/sage/combinat/constellation.py index 0ad3151925e..652ddb4b898 100644 --- a/src/sage/combinat/constellation.py +++ b/src/sage/combinat/constellation.py @@ -400,7 +400,7 @@ def __copy__(self): sage: c is copy(c) False """ - return self.parent()([gg for gg in self._g], + return self.parent()(list(self._g), check=False, mutable=self._mutable) @@ -417,7 +417,7 @@ def mutable_copy(self): sage: d.is_mutable() True """ - return self.parent()([gg for gg in self._g], + return self.parent()(list(self._g), check=False, mutable=True) diff --git a/src/sage/combinat/crystals/affine_factorization.py b/src/sage/combinat/crystals/affine_factorization.py index b1d820032af..81fa6a74d0a 100644 --- a/src/sage/combinat/crystals/affine_factorization.py +++ b/src/sage/combinat/crystals/affine_factorization.py @@ -323,7 +323,7 @@ def bracketing(self, i): right_n.remove(min(l)) else: left_unbracketed += [m] - return [[j for j in left_unbracketed],[j for j in right_n]] + return [list(left_unbracketed), list(right_n)] def to_tableau(self): """ @@ -473,16 +473,16 @@ def _call_(self, x): """ p = [] q = [] - for i,factor in enumerate(reversed(x.value)): + for i, factor in enumerate(reversed(x.value)): word = factor.reduced_word() - p += [i+1]*len(word) + p += [i + 1] * len(word) # We sort for those pesky commutative elements # The word is most likely in reverse order to begin with q += sorted(reversed(word)) C = self.codomain() return C(RSK(p, q, insertion=RSK.rules.EG)[1]) - def is_isomorphism(self): + def is_isomorphism(self) -> bool: """ Return ``True`` as this is an isomorphism. diff --git a/src/sage/combinat/crystals/fast_crystals.py b/src/sage/combinat/crystals/fast_crystals.py index aebc499e9fb..fe32db2a419 100644 --- a/src/sage/combinat/crystals/fast_crystals.py +++ b/src/sage/combinat/crystals/fast_crystals.py @@ -154,14 +154,14 @@ def __init__(self, ct, shape, format): self.shape = shape for i in range(self.size): - target = [x for x in self.delpat[i]] + target = list(self.delpat[i]) target[0] = target[0]-1 e1 = None if target not in self.delpat else self.delpat.index(target) target[0] = target[0]+1+1 f1 = None if target not in self.delpat else self.delpat.index(target) - target = [x for x in self.gampat[i]] + target = list(self.gampat[i]) target[0] = target[0]-1 e2 = None if target not in self.gampat else self.gampat.index(target) target[0] = target[0]+1+1 diff --git a/src/sage/combinat/crystals/fully_commutative_stable_grothendieck.py b/src/sage/combinat/crystals/fully_commutative_stable_grothendieck.py index e63e1a4a3ce..d11846d7766 100644 --- a/src/sage/combinat/crystals/fully_commutative_stable_grothendieck.py +++ b/src/sage/combinat/crystals/fully_commutative_stable_grothendieck.py @@ -728,8 +728,8 @@ def bracketing(self, i): m = P.factors L = list(self.value[m-i-1]) R = list(self.value[m-i]) - right_n = [j for j in R] - left_n = [j for j in L] + right_n = list(R) + left_n = list(L) left_unbracketed = [] while left_n: m = max(left_n) @@ -739,7 +739,7 @@ def bracketing(self, i): right_n.remove(min(l)) else: left_unbracketed += [m] - return [[j for j in left_unbracketed], [j for j in right_n]] + return [list(left_unbracketed), list(right_n)] #################### diff --git a/src/sage/combinat/crystals/infinity_crystals.py b/src/sage/combinat/crystals/infinity_crystals.py index 6219da38391..8b2053bec8b 100644 --- a/src/sage/combinat/crystals/infinity_crystals.py +++ b/src/sage/combinat/crystals/infinity_crystals.py @@ -250,7 +250,7 @@ def module_generator(self): [[1, 1, 1], [2, 2], [3]] """ n = self._cartan_type.rank() - p = Partition([x for x in reversed(range(1, n+1))]) + p = Partition(list(reversed(range(1, n + 1)))) # The column canonical tableau, read by columns module_generator = flatten([[p[j]-i for i in range(p[j])] for j in range(n)]) return self(list=[self.letters(x) for x in module_generator]) @@ -624,7 +624,7 @@ def module_generator(self): [[1, 1, 1], [2, 2], [3]] """ n = self._cartan_type.rank() - p = Partition([x for x in reversed(range(1, n))]) + p = Partition(list(reversed(range(1, n)))) # The column canonical tableau, read by columns module_generator = flatten([[p[j]-i for i in range(p[j])] for j in range(n-1)]) return self(list=[self.letters(x) for x in module_generator]) diff --git a/src/sage/combinat/crystals/kirillov_reshetikhin.py b/src/sage/combinat/crystals/kirillov_reshetikhin.py index 1e58c8ead35..c1872c793be 100644 --- a/src/sage/combinat/crystals/kirillov_reshetikhin.py +++ b/src/sage/combinat/crystals/kirillov_reshetikhin.py @@ -3138,7 +3138,7 @@ def promotion_on_highest_weight_vectors(self): ind.remove(1) C = T.cartan_type() n = C.n - sh = [i for i in T.shapes[0]] + sh = list(T.shapes[0]) sh[n-1] = -sh[n-1] T_dual = CrystalOfTableaux(C, shape=sh) hw = [t for t in T if t.is_highest_weight(index_set=ind)] @@ -3997,7 +3997,7 @@ def horizontal_dominoes_removed(r, s): sage: sage.combinat.crystals.kirillov_reshetikhin.horizontal_dominoes_removed(3,2) [[], [2], [2, 2], [2, 2, 2]] """ - ulist = [ [y for y in x] + [0]*(r-x.length()) for x in partitions_in_box(r, s//2) ] + ulist = [list(x) + [0]*(r-x.length()) for x in partitions_in_box(r, s//2)] two = lambda x : 2 * (x - s // 2) + s return [Partition([two(y) for y in x]) for x in ulist] diff --git a/src/sage/combinat/crystals/tensor_product.py b/src/sage/combinat/crystals/tensor_product.py index 726c974e781..e0482ec74eb 100644 --- a/src/sage/combinat/crystals/tensor_product.py +++ b/src/sage/combinat/crystals/tensor_product.py @@ -583,8 +583,6 @@ def __iter__(self): for x in self.cartesian_product: yield self(*x) -# list = CombinatorialClass._CombinatorialClass__list_from_iterator - def cardinality(self): """ Return the cardinality of ``self``. diff --git a/src/sage/combinat/decorated_permutation.py b/src/sage/combinat/decorated_permutation.py index f687818ef25..817be65223d 100644 --- a/src/sage/combinat/decorated_permutation.py +++ b/src/sage/combinat/decorated_permutation.py @@ -189,7 +189,7 @@ def __contains__(self, pi): if isinstance(pi, DecoratedPermutation): return len(pi) == self._n - values = [v for v in pi] + values = list(pi) if len(values) != self._n: return False abs_values = [abs(v) for v in values] diff --git a/src/sage/combinat/derangements.py b/src/sage/combinat/derangements.py index e9e1de647e8..6b2d868b90d 100644 --- a/src/sage/combinat/derangements.py +++ b/src/sage/combinat/derangements.py @@ -422,7 +422,7 @@ def cardinality(self): R = PolynomialRing(QQ, 'x', len(A)) S = sum(R.gens()) e = prod((S - x)**y for (x, y) in zip(R.gens(), A)) - return Integer(e.coefficient(dict([(x, y) for (x, y) in zip(R.gens(), A)]))) + return Integer(e.coefficient(dict(zip(R.gens(), A)))) return self._count_der(len(self._set)) def _rand_der(self): diff --git a/src/sage/combinat/descent_algebra.py b/src/sage/combinat/descent_algebra.py index bb7db9e326e..8ab3886012f 100644 --- a/src/sage/combinat/descent_algebra.py +++ b/src/sage/combinat/descent_algebra.py @@ -13,25 +13,26 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -from sage.misc.cachefunc import cached_method -from sage.misc.bindable_class import BindableClass -from sage.misc.lazy_attribute import lazy_attribute -from sage.structure.parent import Parent -from sage.structure.unique_representation import UniqueRepresentation +from sage.arith.misc import factorial from sage.categories.algebras import Algebras from sage.categories.commutative_rings import CommutativeRings -from sage.categories.realizations import Realizations, Category_realization_of_parent +from sage.categories.fields import Fields from sage.categories.finite_dimensional_algebras_with_basis import FiniteDimensionalAlgebrasWithBasis -from sage.rings.integer_ring import ZZ -from sage.rings.rational_field import QQ -from sage.arith.misc import factorial -from sage.combinat.free_module import CombinatorialFreeModule -from sage.combinat.permutation import Permutations +from sage.categories.realizations import Realizations, Category_realization_of_parent from sage.combinat.composition import Compositions +from sage.combinat.free_module import CombinatorialFreeModule from sage.combinat.integer_matrices import IntegerMatrices +from sage.combinat.ncsf_qsym.ncsf import NonCommutativeSymmetricFunctions +from sage.combinat.permutation import Permutations from sage.combinat.subset import SubsetsSorted from sage.combinat.symmetric_group_algebra import SymmetricGroupAlgebra -from sage.combinat.ncsf_qsym.ncsf import NonCommutativeSymmetricFunctions +from sage.misc.bindable_class import BindableClass +from sage.misc.cachefunc import cached_method +from sage.misc.lazy_attribute import lazy_attribute +from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ +from sage.structure.parent import Parent +from sage.structure.unique_representation import UniqueRepresentation class DescentAlgebra(UniqueRepresentation, Parent): @@ -134,12 +135,33 @@ def __init__(self, R, n): EXAMPLES:: sage: TestSuite(DescentAlgebra(QQ, 4)).run() + + TESTS:: + + sage: B = DescentAlgebra(QQ, 4).B() + sage: B.is_commutative() + False + sage: B = DescentAlgebra(QQ, 1).B() + sage: B.is_commutative() + True + + sage: B = DescentAlgebra(QQ, 4).B() + sage: B in Fields() + False + sage: B = DescentAlgebra(QQ, 1).B() + sage: B in Fields() + True """ self._n = n - self._category = FiniteDimensionalAlgebrasWithBasis(R) + cat = FiniteDimensionalAlgebrasWithBasis(R) + if R in CommutativeRings() and n <= 2: + cat = cat.Commutative() + if R in Fields() and n <= 1: + cat &= Fields() + self._category = cat Parent.__init__(self, base=R, category=self._category.WithRealizations()) - def _repr_(self): + def _repr_(self) -> str: r""" Return a string representation of ``self``. @@ -239,7 +261,7 @@ def _element_constructor_(self, x): return CombinatorialFreeModule._element_constructor_(self, x) # We need to overwrite this since our basis elements must be indexed by tuples - def _repr_term(self, S): + def _repr_term(self, S) -> str: r""" EXAMPLES:: @@ -263,7 +285,7 @@ def product_on_basis(self, S, T): return self(self.to_B_basis(S) * self.to_B_basis(T)) @cached_method - def one_basis(self): + def one_basis(self) -> tuple: r""" Return the identity element, as per ``AlgebrasWithBasis.ParentMethods.one_basis``. @@ -309,7 +331,8 @@ def to_B_basis(self, S): n = self.realization_of()._n C = Compositions(n) - return B.sum_of_terms([(C.from_subset(T, n), (-1)**(len(S) - len(T))) + lenS = len(S) + return B.sum_of_terms([(C.from_subset(T, n), (-1)**(lenS - len(T))) for T in SubsetsSorted(S)]) def to_symmetric_group_algebra_on_basis(self, S): @@ -838,7 +861,7 @@ def __init__(self, base): """ Category_realization_of_parent.__init__(self, base) - def _repr_(self): + def _repr_(self) -> str: r""" Return the representation of ``self``. @@ -851,7 +874,7 @@ def _repr_(self): """ return "Category of bases of {}".format(self.base()) - def super_categories(self): + def super_categories(self) -> list: r""" The super categories of ``self``. @@ -867,7 +890,7 @@ def super_categories(self): return [self.base()._category, Realizations(self.base())] class ParentMethods: - def _repr_(self): + def _repr_(self) -> str: """ Text representation of this basis of a descent algebra. @@ -913,39 +936,6 @@ def __getitem__(self, p): p = [p] return self.monomial(C(p)) - def is_field(self, proof=True): - """ - Return whether this descent algebra is a field. - - EXAMPLES:: - - sage: B = DescentAlgebra(QQ, 4).B() - sage: B.is_field() - False - sage: B = DescentAlgebra(QQ, 1).B() - sage: B.is_field() - True - """ - if self.realization_of()._n <= 1: - return self.base_ring().is_field() - return False - - def is_commutative(self) -> bool: - """ - Return whether this descent algebra is commutative. - - EXAMPLES:: - - sage: B = DescentAlgebra(QQ, 4).B() - sage: B.is_commutative() - False - sage: B = DescentAlgebra(QQ, 1).B() - sage: B.is_commutative() - True - """ - return (self.base_ring() in CommutativeRings() - and self.realization_of()._n <= 2) - @lazy_attribute def to_symmetric_group_algebra(self): """ diff --git a/src/sage/combinat/designs/database.py b/src/sage/combinat/designs/database.py index e4d80442f3c..943f7c9ba22 100644 --- a/src/sage/combinat/designs/database.py +++ b/src/sage/combinat/designs/database.py @@ -4118,7 +4118,7 @@ def RBIBD_120_8_1(): if p in B: equiv.append([x for x in B if x not in hyperoval]) else: - new_BIBD.append([x for x in B]) + new_BIBD.append(list(B)) BIBD = new_BIBD @@ -4658,7 +4658,7 @@ def BIBD_79_13_2(): permAction = libgap.Action(G, points, action) - baseBlocks = [libgap.Set(list(map(lambda p: libgap.Position(points, p), B))) for B in [B1, B2, B3, B4]] + baseBlocks = [libgap.Set([libgap.Position(points, p) for p in B]) for B in [B1, B2, B3, B4]] B3Orbit = libgap.Orbit(permAction, baseBlocks[2], libgap.OnSets) B4Orbit = libgap.Orbit(permAction, baseBlocks[3], libgap.OnSets) diff --git a/src/sage/combinat/designs/difference_family.py b/src/sage/combinat/designs/difference_family.py index 8c2b2b0b01c..a569a722104 100644 --- a/src/sage/combinat/designs/difference_family.py +++ b/src/sage/combinat/designs/difference_family.py @@ -2936,7 +2936,7 @@ def complementary_difference_setsII(n, check=True): if t % 2 == 0: rho = G.multiplicative_generator() C0 = list({el**8 for el in G if el != 0}) - C1, C2, C3, C6, C7 = map(lambda i: [rho**i * el for el in C0], [1, 2, 3, 6, 7]) + C1, C2, C3, C6, C7 = ([rho**i * el for el in C0] for i in [1, 2, 3, 6, 7]) A = C0 + C1 + C2 + C3 B = C0 + C1 + C6 + C7 else: diff --git a/src/sage/combinat/designs/ext_rep.py b/src/sage/combinat/designs/ext_rep.py index 2227a1e9f63..75df1889c41 100644 --- a/src/sage/combinat/designs/ext_rep.py +++ b/src/sage/combinat/designs/ext_rep.py @@ -922,7 +922,7 @@ def _end_element(self, name): self.block_design_proc(self.current_node[2][0]) if self.save_designs: init_bd = XTree(self.current_node[2][0]) - self.list_of_designs.append((init_bd.v, [b for b in init_bd.blocks])) + self.list_of_designs.append((init_bd.v, list(init_bd.blocks))) #print_subxt(self.current_node[2][0], level=2, outf=self.outf) self._init() elif name == 'info': diff --git a/src/sage/combinat/designs/orthogonal_arrays.py b/src/sage/combinat/designs/orthogonal_arrays.py index b4a14b0e7b6..4ba9b60ecc2 100644 --- a/src/sage/combinat/designs/orthogonal_arrays.py +++ b/src/sage/combinat/designs/orthogonal_arrays.py @@ -1563,7 +1563,7 @@ def OA_relabel(OA, k, n, blocks=tuple(), matrix=None, symbol_list=None): OA = [[matrix[i][j] if j is not None else None for i,j in enumerate(R)] for R in OA] if symbol_list: - mapping = {index: symbol for index, symbol in enumerate(symbol_list)} + mapping = dict(enumerate(symbol_list)) OA = [[mapping[element] for element in row] for row in OA] return OA diff --git a/src/sage/combinat/diagram_algebras.py b/src/sage/combinat/diagram_algebras.py index a5c1045028a..ce1758b15c4 100644 --- a/src/sage/combinat/diagram_algebras.py +++ b/src/sage/combinat/diagram_algebras.py @@ -1069,7 +1069,7 @@ def bijection_on_free_nodes(self, two_line=False): sage: elm2.bijection_on_free_nodes(two_line=True) [[1, 2, 3], [-2, -3, -1]] """ - terms = sorted(sorted(list(v), reverse=True) for v in self.diagram() + terms = sorted(sorted(v, reverse=True) for v in self.diagram() if max(v) > 0 and min(v) < 0) if two_line: terms = [[t[i] for t in terms] for i in range(2)] @@ -3898,6 +3898,245 @@ def convertI(x): return self._from_dict(d, remove_zeros=True) +class HalfTemperleyLiebDiagrams(UniqueRepresentation, Parent): + r""" + Half diagrams for the Temperley-Lieb algebra cell modules. + """ + def __init__(self, order, defects): + r""" + Initialize ``self``. + + TESTS:: + + sage: import sage.combinat.diagram_algebras as da + sage: htld = da.HalfTemperleyLiebDiagrams(7, 3) + sage: TestSuite(htld).run() + """ + Parent.__init__(self, category=FiniteEnumeratedSets()) + self._order = ZZ(order) + self._defects = ZZ(defects) + if (self._order - self._defects) % 2: + raise ValueError("the number of non-defects must be even") + + def _repr_(self): + r""" + TESTS:: + + sage: import sage.combinat.diagram_algebras as da + sage: da.HalfTemperleyLiebDiagrams(7, 5) + Half Temperley-Lieb diagrams of order 7 with 5 defects + """ + return "Half Temperley-Lieb diagrams of order {} with {} defects".format(self._order, self._defects) + + def __iter__(self): + r""" + TESTS:: + + sage: import sage.combinat.diagram_algebras as da + sage: list(da.HalfTemperleyLiebDiagrams(5, 3)) + [{{1, 2}}, {{2, 3}}, {{3, 4}}, {{4, 5}}] + """ + n = self._order + k = self._defects + b = (n - k) // 2 + from sage.combinat.dyck_word import DyckWords + for dw in DyckWords(b+k, b): + ret = [] + offset = 0 + for D in dw.catalan_factorization(): + ret.extend((offset+a+1, offset+b) for (a, b) in D.tunnels()) + offset += len(D) + 1 + yield self.element_class(self, ret) + + def __contains__(self, obj): + r""" + Check containment. + + TESTS:: + + sage: import sage.combinat.diagram_algebras as da + sage: htld = da.HalfTemperleyLiebDiagrams(7, 3) + sage: htld.an_element() in htld + True + """ + return isinstance(obj, self.Element) and obj.parent() is self + + def cardinality(self): + r""" + Return the cardinality of ``self``. + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: htld = da.HalfTemperleyLiebDiagrams(7, 3) + sage: htld.cardinality() + 14 + """ + from sage.functions.other import binomial + n = self._order + k = self._defects + b = (n - k) // 2 + return (k + 1) * binomial(n, b) // (b + k + 1) + + def _element_constructor_(self, d): + r""" + Construct an element of ``self``. + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: htld = da.HalfTemperleyLiebDiagrams(7, 3) + sage: htld([[1, 4], [2, 3]]) + {{1, 4}, {2, 3}} + """ + return self.element_class(self, d) + + class Element(AbstractPartitionDiagram): + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: htld = da.HalfTemperleyLiebDiagrams(7, 3) + sage: d = htld([[1, 2], [4, 5]]) + sage: latex(d) + \begin{tikzpicture}[scale = 0.5,thick, baseline={(0,-1ex/2)}] + \tikzstyle{vertex} = [shape = circle, minimum size = 7pt, inner sep = 1pt] + \node[vertex] (G--7) at (9.0, -1) [shape = circle, draw] {}; + \node[vertex] (G--7) at (9.0, -1) [shape = circle, draw] {}; + \node[vertex] (G--6) at (7.5, -1) [shape = circle, draw] {}; + \node[vertex] (G--6) at (7.5, -1) [shape = circle, draw] {}; + \node[vertex] (G--5) at (6.0, -1) [shape = circle, draw] {}; + \node[vertex] (G--4) at (4.5, -1) [shape = circle, draw] {}; + \node[vertex] (G--3) at (3.0, -1) [shape = circle, draw] {}; + \node[vertex] (G--3) at (3.0, -1) [shape = circle, draw] {}; + \node[vertex] (G--2) at (1.5, -1) [shape = circle, draw] {}; + \node[vertex] (G--1) at (0.0, -1) [shape = circle, draw] {}; + \draw[] (G--7) .. controls +(0.0, 0.4) and +(-0.0, 0.4) .. (G--7); + \draw[] (G--6) .. controls +(0.0, 0.4) and +(-0.0, 0.4) .. (G--6); + \draw[] (G--5) .. controls +(-0.5, 0.5) and +(0.5, 0.5) .. (G--4); + \draw[] (G--3) .. controls +(0.0, 0.4) and +(-0.0, 0.4) .. (G--3); + \draw[] (G--2) .. controls +(-0.5, 0.5) and +(0.5, 0.5) .. (G--1); + \end{tikzpicture} + """ + defects = self.defects() + temp = [[-b, -a] for (a, b) in self] + [[-d, -d] for d in defects] + temp.sort() + return diagram_latex(temp) + + def _ascii_art_(self): + r""" + Return an ascii art representation of ``self``. + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: htld = da.HalfTemperleyLiebDiagrams(7, 3) + sage: d = htld([[1, 2], [4, 5]]) + sage: ascii_art(d) + .-. | .-. | | + o o o o o o o + sage: htld = da.HalfTemperleyLiebDiagrams(8, 0) + sage: d = htld([[1, 6], [2, 3], [4, 5], [7, 8]]) + sage: ascii_art(d) + .---------. + | .-. .-. | .-. + o o o o o o o o + """ + defects = self.defects() + temp = [[-b, -a] for (a, b) in self] + [[-d, d] for d in defects] + rank = self.parent()._order + temp.append([rank, rank]) + temp.sort() + ret = TL_diagram_ascii_art(temp) + from sage.typeset.ascii_art import AsciiArt + return AsciiArt(ret[2:]) + + def _unicode_art_(self): + r""" + Return an ascii art representation of ``self``. + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: htld = da.HalfTemperleyLiebDiagrams(7, 3) + sage: d = htld([[1, 2], [4, 5]]) + sage: unicode_art(d) + ╭─╮ │ ╭─╮ │ │ + ⚬ ⚬ ⚬ ⚬ ⚬ ⚬ ⚬ + sage: htld = da.HalfTemperleyLiebDiagrams(8, 0) + sage: d = htld([[1, 6], [2, 3], [4, 5], [7, 8]]) + sage: unicode_art(d) + ╭─────────╮ + │ ╭─╮ ╭─╮ │ ╭─╮ + ⚬ ⚬ ⚬ ⚬ ⚬ ⚬ ⚬ ⚬ + """ + defects = self.defects() + temp = [[-b, -a] for (a, b) in self] + [[-d, d] for d in defects] + rank = self.parent()._order + temp.append([rank, rank]) + temp.sort() + ret = TL_diagram_ascii_art(temp, use_unicode=True) + from sage.typeset.unicode_art import UnicodeArt + return UnicodeArt(ret[2:]) + + def defects(self): + r""" + Return the defects of ``self``. + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: htld = da.HalfTemperleyLiebDiagrams(7, 3) + sage: d = htld([[1, 2], [4, 5]]) + sage: d.defects() + frozenset({3, 6, 7}) + """ + order = self.parent()._order + return frozenset(range(1, order+1)) - frozenset(e for B in self for e in B) + + def check(self): + r""" + Check the validity of the input of ``self``. + + EXAMPLES:: + + sage: import sage.combinat.diagram_algebras as da + sage: htld = da.HalfTemperleyLiebDiagrams(7, 3) + sage: htld([[1,2], [3,4]]) # indirect doctest + {{1, 2}, {3, 4}} + sage: htld([[1,2], [-1, -2]]) # indirect doctest + Traceback (most recent call last): + ... + ValueError: {{-2, -1}, {1, 2}} does not represent a half TL diagram of order 7 + sage: htld([[1,2,3], [4,5]]) # indirect doctest + Traceback (most recent call last): + ... + ValueError: all blocks of {{1, 2, 3}, {4, 5}} must be of size 2 + sage: htld([[1,2], [3,4], [5,6]]) # indirect doctest + Traceback (most recent call last): + ... + ValueError: {{1, 2}, {3, 4}, {5, 6}} does not have 3 defects + sage: htld([[1,3], [2,4]]) # indirect doctest + Traceback (most recent call last): + ... + ValueError: {{1, 3}, {2, 4}} is not planar + """ + tst = frozenset(e for B in self._base_diagram for e in B) + P = self.parent() + if not (tst <= frozenset(range(1, P._order+1))): + raise ValueError("{} does not represent a half TL diagram of order {}".format( + self, self.parent()._order)) + if any(len(block) != 2 for block in self): + raise ValueError("all blocks of {} must be of size 2".format(self)) + if len(tst) != P._order - P._defects: + raise ValueError("{} does not have {} defects".format(self, P._defects)) + if not self.is_planar(): + raise ValueError("{} is not planar".format(self)) + + class TemperleyLiebAlgebra(SubPartitionAlgebra, UnitDiagramMixin): r""" A Temperley--Lieb algebra. @@ -3948,6 +4187,60 @@ class TemperleyLiebAlgebra(SubPartitionAlgebra, UnitDiagramMixin): True sage: b[0]^5 == x^4*b[0] True + + The Temperley-Lieb algebra is a cellular algebra, and we verify that + the dimensions of the simple modules at `q = 0` is given by + :oeis:`A050166`:: + + sage: for k in range(1,5): + ....: TL = TemperleyLiebAlgebra(2*k, 0, QQ) + ....: print("".join("{:3}".format(TL.cell_module(la).simple_module().dimension()) + ....: for la in reversed(TL.cell_poset()) if la != 0)) + 1 + 1 2 + 1 4 5 + 1 6 14 14 + sage: for k in range(1,4): + ....: TL = TemperleyLiebAlgebra(2*k+1, 0, QQ) + ....: print("".join("{:3}".format(TL.cell_module(la).simple_module().dimension()) + ....: for la in reversed(TL.cell_poset()) if la != 0)) + 1 2 + 1 4 5 + 1 6 14 14 + + Additional examples when the Temperley-Lieb algebra is not semisimple:: + + sage: TL = TemperleyLiebAlgebra(8, -1, QQ) + sage: for la in TL.cell_poset(): + ....: CM = TL.cell_module(la) + ....: if not CM.nonzero_bilinear_form(): + ....: continue + ....: print(la, CM.dimension(), CM.simple_module().dimension()) + ....: + 0 14 1 + 2 28 28 + 4 20 13 + 6 7 7 + 8 1 1 + sage: for k in range(1,5): + ....: TL = TemperleyLiebAlgebra(2*k, -1, QQ) + ....: print("".join("{:3}".format(TL.cell_module(la).simple_module().dimension()) + ....: for la in reversed(TL.cell_poset()) + ....: if TL.cell_module(la).nonzero_bilinear_form())) + 1 1 + 1 3 1 + 1 4 9 1 + 1 7 13 28 1 + sage: C5. = CyclotomicField(5) + sage: for k in range(1,5): + ....: TL = TemperleyLiebAlgebra(2*k, z5+~z5, C5) + ....: print("".join("{:3}".format(TL.cell_module(la).simple_module().dimension()) + ....: for la in reversed(TL.cell_poset()) + ....: if TL.cell_module(la).nonzero_bilinear_form())) + 1 1 + 1 3 2 + 1 5 8 5 + 1 7 20 21 13 """ @staticmethod def __classcall_private__(cls, k, q, base_ring=None, prefix="T"): @@ -3976,8 +4269,12 @@ def __init__(self, k, q, base_ring, prefix): sage: R. = QQ[] sage: TL = TemperleyLiebAlgebra(2, q, R) sage: TestSuite(TL).run() + + sage: TL = TemperleyLiebAlgebra(3, 0, QQ) + sage: TestSuite(TL).run() """ - SubPartitionAlgebra.__init__(self, k, q, base_ring, prefix, TemperleyLiebDiagrams(k)) + cat = AssociativeAlgebras(base_ring.category()).Unital().FiniteDimensional().WithBasis().Cellular() + SubPartitionAlgebra.__init__(self, k, q, base_ring, prefix, TemperleyLiebDiagrams(k), category=cat) def _repr_(self): """ @@ -4059,6 +4356,153 @@ def _unicode_art_term(self, diagram): """ return TL_diagram_ascii_art(diagram, use_unicode=True) + @cached_method + def cell_poset(self): + """ + Return the cell poset of ``self``. + + EXAMPLES:: + + sage: R. = QQ[] + sage: TL = TemperleyLiebAlgebra(7, q, R) + sage: TL.cell_poset().cover_relations() + [[1, 3], [3, 5], [5, 7]] + + sage: TL = TemperleyLiebAlgebra(8, q, R) + sage: TL.cell_poset().cover_relations() + [[0, 2], [2, 4], [4, 6], [6, 8]] + """ + from sage.combinat.posets.posets import Poset + return Poset({k-2: [k] for k in range(self._k, 1, -2)}) + + def cell_module_indices(self, la): + r""" + Return the indices of the cell module of ``self`` + indexed by ``la`` . + + This is the finite set `M(\lambda)`. + + EXAMPLES:: + + sage: R. = QQ[] + sage: TL = TemperleyLiebAlgebra(8, q, R) + sage: TL.cell_module_indices(4) + Half Temperley-Lieb diagrams of order 8 with 4 defects + """ + return HalfTemperleyLiebDiagrams(self._k, la) + + def _to_cellular_element(self, d): + r""" + Return the image in the cellular basis of the basis element + of ``self`` indexed by ``d``. + + EXAMPLES:: + + sage: R. = QQ[] + sage: TL = TemperleyLiebAlgebra(3, q, R) + sage: for d in TL.basis().keys(): + ....: print(d) + ....: print(TL._to_cellular_element(d)) + {{-3, 3}, {-2, -1}, {1, 2}} + C(1, {{1, 2}}, {{1, 2}}) + {{-3, 1}, {-2, -1}, {2, 3}} + C(1, {{2, 3}}, {{1, 2}}) + {{-3, -2}, {-1, 1}, {2, 3}} + C(1, {{2, 3}}, {{2, 3}}) + {{-3, -2}, {-1, 3}, {1, 2}} + C(1, {{1, 2}}, {{2, 3}}) + {{-3, 3}, {-2, 2}, {-1, 1}} + C(3, {}, {}) + """ + C = self.cellular_basis() + top = [] + bottom = [] + defects = ZZ.zero() + for (a, b) in d: + if b < 0: + bottom.append((-b, -a)) + elif a > 0: + top.append((a, b)) + else: + defects += ZZ.one() + CMI = self.cell_module_indices(defects) + tup = (defects, CMI(top), CMI(bottom)) + return C.monomial(C._indices(tup)) + + def _from_cellular_index(self, x): + r""" + Return the image in ``self`` from the index of the + cellular basis ``x``. + + EXAMPLES:: + + sage: R. = QQ[] + sage: TL = TemperleyLiebAlgebra(3, q, R) + sage: C = TL.cellular_basis() + sage: for i in C.basis().keys(): + ....: print(i) + ....: print(TL._from_cellular_index(i)) + (1, {{1, 2}}, {{1, 2}}) + T{{-3, 3}, {-2, -1}, {1, 2}} + (1, {{1, 2}}, {{2, 3}}) + T{{-3, -2}, {-1, 3}, {1, 2}} + (1, {{2, 3}}, {{1, 2}}) + T{{-3, 1}, {-2, -1}, {2, 3}} + (1, {{2, 3}}, {{2, 3}}) + T{{-3, -2}, {-1, 1}, {2, 3}} + (3, {}, {}) + T{{-3, 3}, {-2, 2}, {-1, 1}} + + sage: TL = TemperleyLiebAlgebra(4, QQ.zero(), QQ) + sage: C = TL.cellular_basis() + sage: [TL._from_cellular_index(i) for i in C.basis().keys()] + [T{{-4, -3}, {-2, -1}, {1, 2}, {3, 4}}, + T{{-4, -1}, {-3, -2}, {1, 2}, {3, 4}}, + T{{-4, -3}, {-2, -1}, {1, 4}, {2, 3}}, + T{{-4, -1}, {-3, -2}, {1, 4}, {2, 3}}, + T{{-4, 4}, {-3, 3}, {-2, -1}, {1, 2}}, + T{{-4, 4}, {-3, -2}, {-1, 3}, {1, 2}}, + T{{-4, -3}, {-2, 4}, {-1, 3}, {1, 2}}, + T{{-4, 4}, {-3, 1}, {-2, -1}, {2, 3}}, + T{{-4, 4}, {-3, -2}, {-1, 1}, {2, 3}}, + T{{-4, -3}, {-2, 4}, {-1, 1}, {2, 3}}, + T{{-4, 2}, {-3, 1}, {-2, -1}, {3, 4}}, + T{{-4, 2}, {-3, -2}, {-1, 1}, {3, 4}}, + T{{-4, -3}, {-2, 2}, {-1, 1}, {3, 4}}, + T{{-4, 4}, {-3, 3}, {-2, 2}, {-1, 1}}] + """ + _, top, bottom = x + bottom = [[-b, -a] for (a, b) in bottom] + tmiss = frozenset(range(1, self._k+1)) - frozenset(e for B in top for e in B) + bmiss = frozenset(range(-1, -self._k-1, -1)) - frozenset(e for B in bottom for e in B) + prop = list(zip(sorted(tmiss, reverse=True), sorted(bmiss))) + return self.monomial(self._indices(bottom + prop + list(top))) + + def cellular_involution(self, x): + r""" + Return the cellular involution of ``x`` in ``self``. + + EXAMPLES:: + + sage: TL = TemperleyLiebAlgebra(4, QQ.zero(), QQ) + sage: ascii_art(TL.an_element()) + o o o o o o o o + o o o o | `-` | | `-` | + 2* `-` `-` + 2* `-----` + 3* `---. | + .-. .-. .-. .-. .-. | | + o o o o o o o o o o o o + sage: ascii_art(TL.cellular_involution(TL.an_element())) + o o o o o o o o + o o o o `-` `-` `-` | | + 2* `-` `-` + 2* .-----. + 3* .---` | + .-. .-. | .-. | | .-. | + o o o o o o o o o o o o + """ + M = x.monomial_coefficients(copy=False) + I = self._indices + return self._from_dict({d.dual(): c for d, c in M.items()}, + remove_zeros=False) + class PlanarAlgebra(SubPartitionAlgebra, UnitDiagramMixin): r""" diff --git a/src/sage/combinat/free_module.py b/src/sage/combinat/free_module.py index 19cec377fc8..069ebb88fb6 100644 --- a/src/sage/combinat/free_module.py +++ b/src/sage/combinat/free_module.py @@ -498,15 +498,35 @@ def change_ring(self, R): Free module generated by {'a', 'b', 'c'} over Rational Field sage: F_QQ.change_ring(ZZ) == F True + + sage: T = F.tensor(F); T + Free module generated by {'a', 'b', 'c'} over Integer Ring + # Free module generated by {'a', 'b', 'c'} over Integer Ring + sage: T.change_ring(QQ) + Free module generated by {'a', 'b', 'c'} over Rational Field + # Free module generated by {'a', 'b', 'c'} over Rational Field + + sage: G = CombinatorialFreeModule(ZZ, ['x','y']); G + Free module generated by {'x', 'y'} over Integer Ring + sage: T = F.cartesian_product(G); T + Free module generated by {'a', 'b', 'c'} over Integer Ring + (+) Free module generated by {'x', 'y'} over Integer Ring + sage: T.change_ring(QQ) + Free module generated by {'a', 'b', 'c'} over Rational Field + (+) Free module generated by {'x', 'y'} over Rational Field """ if R is self.base_ring(): return self construction = self.construction() if construction is not None: - functor, _ = construction + functor, args = construction from sage.categories.pushout import VectorFunctor if isinstance(functor, VectorFunctor): return functor(R) + from sage.categories.tensor import TensorProductFunctor + from sage.categories.cartesian_product import CartesianProductFunctor + if isinstance(functor, (TensorProductFunctor, CartesianProductFunctor)): + return functor([f.change_ring(R) for f in args]) raise NotImplementedError('the method change_ring() has not yet been implemented') # For backwards compatibility @@ -1003,10 +1023,10 @@ def from_vector(self, vector, order=None, coerce=True): if order is None: order = self.get_order() if not coerce or vector.base_ring() is self.base_ring(): - return self._from_dict({order[i]: c for i,c in vector.items()}, + return self._from_dict({order[i]: c for i, c in vector.items()}, coerce=False) R = self.base_ring() - return self._from_dict({order[i]: R(c) for i,c in vector.items() if R(c)}, + return self._from_dict({order[i]: R(c) for i, c in vector.items() if R(c)}, coerce=False, remove_zeros=False) def sum(self, iter_of_elements): @@ -1243,430 +1263,434 @@ def _from_dict(self, d, coerce=False, remove_zeros=True): class CombinatorialFreeModule_Tensor(CombinatorialFreeModule): - """ - Tensor Product of Free Modules + """ + Tensor Product of Free Modules - EXAMPLES: + EXAMPLES: - We construct two free modules, assign them short names, and construct their tensor product:: + We construct two free modules, assign them short names, and construct their tensor product:: - 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 + 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 - sage: T.category() - Category of tensor products of - finite dimensional modules with basis over Integer Ring + sage: T.category() + Category of tensor products of + finite dimensional modules with basis over Integer Ring - sage: T.construction() # todo: not implemented - [tensor, ] + sage: T.construction() # todo: not implemented + [tensor, ] - T is a free module, with same base ring as F and G:: + T is a free module, with same base ring as F and G:: - sage: T.base_ring() - Integer Ring + sage: T.base_ring() + Integer Ring - The basis of T is indexed by tuples of basis indices of F and G:: + The basis of T is indexed by tuples of basis indices of F and G:: - sage: T.basis().keys() - Image of Cartesian product of {1, 2}, {3, 4} - by The map from Cartesian product of {1, 2}, {3, 4} - sage: T.basis().keys().list() - [(1, 3), (1, 4), (2, 3), (2, 4)] + sage: T.basis().keys() + Image of Cartesian product of {1, 2}, {3, 4} + by The map from Cartesian product of {1, 2}, {3, 4} + sage: T.basis().keys().list() + [(1, 3), (1, 4), (2, 3), (2, 4)] - FIXME: Should elements of a CartesianProduct be tuples (making them hashable)? + FIXME: Should elements of a CartesianProduct be tuples (making them hashable)? - Here are the basis elements themselves:: + Here are the basis elements themselves:: - sage: T.basis().cardinality() - 4 - sage: list(T.basis()) - [B[1] # B[3], B[1] # B[4], B[2] # B[3], B[2] # B[4]] + sage: T.basis().cardinality() + 4 + sage: list(T.basis()) + [B[1] # B[3], B[1] # B[4], B[2] # B[3], B[2] # B[4]] - The tensor product is associative and flattens sub tensor products:: + The tensor product is associative and flattens sub tensor products:: - sage: H = CombinatorialFreeModule(ZZ, [5,6]); H.rename("H") - sage: tensor([F, tensor([G, H])]) - F # G # H - sage: tensor([tensor([F, G]), H]) - F # G # H - sage: tensor([F, G, H]) - F # G # H + sage: H = CombinatorialFreeModule(ZZ, [5,6]); H.rename("H") + sage: tensor([F, tensor([G, H])]) + F # G # H + sage: tensor([tensor([F, G]), H]) + F # G # H + sage: tensor([F, G, H]) + F # G # H - We now compute the tensor product of elements of free modules:: + We now compute the tensor product of elements of free modules:: - sage: f = F.monomial(1) + 2 * F.monomial(2) - sage: g = 2*G.monomial(3) + G.monomial(4) - sage: h = H.monomial(5) + H.monomial(6) - sage: tensor([f, g]) - 2*B[1] # B[3] + B[1] # B[4] + 4*B[2] # B[3] + 2*B[2] # B[4] + sage: f = F.monomial(1) + 2 * F.monomial(2) + sage: g = 2*G.monomial(3) + G.monomial(4) + sage: h = H.monomial(5) + H.monomial(6) + sage: tensor([f, g]) + 2*B[1] # B[3] + B[1] # B[4] + 4*B[2] # B[3] + 2*B[2] # B[4] - Again, the tensor product is associative on elements:: + Again, the tensor product is associative on elements:: - sage: tensor([f, tensor([g, h])]) == tensor([f, g, h]) - True - sage: tensor([tensor([f, g]), h]) == tensor([f, g, h]) - True + sage: tensor([f, tensor([g, h])]) == tensor([f, g, h]) + True + sage: tensor([tensor([f, g]), h]) == tensor([f, g, h]) + True - Note further that the tensor product spaces need not preexist:: + Note further that the tensor product spaces need not preexist:: - sage: t = tensor([f, g, h]) - sage: t.parent() - F # G # H + sage: t = tensor([f, g, h]) + sage: t.parent() + F # G # H + TESTS:: + + sage: tensor([tensor([F, G]), H]) == tensor([F, G, H]) + True + sage: tensor([F, tensor([G, H])]) == tensor([F, G, H]) + True + """ + @staticmethod + def __classcall_private__(cls, modules, **options): + """ TESTS:: + sage: F = CombinatorialFreeModule(ZZ, [1,2]) + sage: G = CombinatorialFreeModule(ZZ, [3,4]) + sage: H = CombinatorialFreeModule(ZZ, [4]) sage: tensor([tensor([F, G]), H]) == tensor([F, G, H]) True sage: tensor([F, tensor([G, H])]) == tensor([F, G, H]) True + + Check that :issue:`19608` is fixed:: + + sage: T = tensor([F, G, H]) + sage: T in Modules(ZZ).FiniteDimensional() + True """ - @staticmethod - def __classcall_private__(cls, modules, **options): - """ - TESTS:: - - sage: F = CombinatorialFreeModule(ZZ, [1,2]) - sage: G = CombinatorialFreeModule(ZZ, [3,4]) - sage: H = CombinatorialFreeModule(ZZ, [4]) - sage: tensor([tensor([F, G]), H]) == tensor([F, G, H]) - True - sage: tensor([F, tensor([G, H])]) == tensor([F, G, H]) - True - - Check that :issue:`19608` is fixed:: - - sage: T = tensor([F, G, H]) - sage: T in Modules(ZZ).FiniteDimensional() - True - """ - assert (len(modules) > 0) - R = modules[0].base_ring() - assert (all(module in ModulesWithBasis(R)) for module in modules) - # should check the base ring - # flatten the list of modules so that tensor(A, tensor(B,C)) gets rewritten into tensor(A, B, C) - modules = sum([module._sets if isinstance(module, CombinatorialFreeModule_Tensor) else (module,) for module in modules], ()) - if all('FiniteDimensional' in M.category().axioms() for M in modules): - options['category'] = options['category'].FiniteDimensional() - return super(CombinatorialFreeModule.Tensor, cls).__classcall__(cls, modules, **options) - - def __init__(self, modules, **options): - """ - TESTS:: - - sage: F = CombinatorialFreeModule(ZZ, [1,2]); F - F - """ - self._sets = modules - indices = CartesianProduct_iters(*[module.basis().keys() - for module in modules]).map(tuple) - CombinatorialFreeModule.__init__(self, modules[0].base_ring(), indices, **options) - # the following is not the best option, but it's better than nothing. - if 'tensor_symbol' in options: - self._print_options['tensor_symbol'] = options['tensor_symbol'] - - def _repr_(self): - r""" - This is customizable by setting - ``self.print_options('tensor_symbol'=...)``. - - TESTS:: - - sage: F = CombinatorialFreeModule(ZZ, [1,2,3]) - sage: G = CombinatorialFreeModule(ZZ, [1,2,3,8]) - sage: F.rename("F") - sage: G.rename("G") - sage: T = tensor([F, G]) - sage: T # indirect doctest - F # G - sage: T.print_options(tensor_symbol=' @ ') # note the spaces - sage: T # indirect doctest - F @ G - - To avoid a side\--effect on another doctest, we revert the change:: - - sage: T.print_options(tensor_symbol=' # ') - """ - from sage.categories.tensor import tensor - if hasattr(self, "_print_options"): - symb = self._print_options['tensor_symbol'] - if symb is None: - symb = tensor.symbol - else: + assert (len(modules) > 0) + R = modules[0].base_ring() + assert (all(module in ModulesWithBasis(R)) for module in modules) + # should check the base ring + # flatten the list of modules so that tensor(A, tensor(B,C)) gets rewritten into tensor(A, B, C) + modules = sum([module._sets if isinstance(module, CombinatorialFreeModule_Tensor) else (module,) for module in modules], ()) + if all('FiniteDimensional' in M.category().axioms() for M in modules): + options['category'] = options['category'].FiniteDimensional() + return super(CombinatorialFreeModule.Tensor, cls).__classcall__(cls, modules, **options) + + def __init__(self, modules, **options): + """ + TESTS:: + + sage: F = CombinatorialFreeModule(ZZ, [1,2]); F + F + """ + self._sets = modules + indices = CartesianProduct_iters(*[module.basis().keys() + for module in modules]).map(tuple, is_injective=True) + CombinatorialFreeModule.__init__(self, modules[0].base_ring(), indices, **options) + # the following is not the best option, but it's better than nothing. + if 'tensor_symbol' in options: + self._print_options['tensor_symbol'] = options['tensor_symbol'] + + def _repr_(self): + r""" + This is customizable by setting + ``self.print_options('tensor_symbol'=...)``. + + TESTS:: + + sage: F = CombinatorialFreeModule(ZZ, [1,2,3]) + sage: G = CombinatorialFreeModule(ZZ, [1,2,3,8]) + sage: F.rename("F") + sage: G.rename("G") + sage: T = tensor([F, G]) + sage: T # indirect doctest + F # G + sage: T.print_options(tensor_symbol=' @ ') # note the spaces + sage: T # indirect doctest + F @ G + + To avoid a side\--effect on another doctest, we revert the change:: + + sage: T.print_options(tensor_symbol=' # ') + """ + from sage.categories.tensor import tensor + if hasattr(self, "_print_options"): + symb = self._print_options['tensor_symbol'] + if symb is None: symb = tensor.symbol - 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:: - - sage: R = NonCommutativeSymmetricFunctions(QQ).R() # needs sage.combinat - sage: Partitions.options(diagram_str="#", convention="french") # needs sage.combinat - sage: s = ascii_art(tensor((R[1,2], R[3,1,2]))); s # needs sage.combinat - R # R - # ### - ## # - ## - - Check that the breakpoints are correct (:issue:`29202`):: - - sage: s._breakpoints # needs sage.combinat - [6] - """ - if hasattr(self, "_print_options"): - symb = self._print_options['tensor_symbol'] - if symb is None: - symb = tensor.symbol - else: + else: + symb = tensor.symbol + 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:: + + sage: R = NonCommutativeSymmetricFunctions(QQ).R() # needs sage.combinat + sage: Partitions.options(diagram_str="#", convention="french") # needs sage.combinat + sage: s = ascii_art(tensor((R[1,2], R[3,1,2]))); s # needs sage.combinat + R # R + # ### + ## # + ## + + Check that the breakpoints are correct (:issue:`29202`):: + + sage: s._breakpoints # needs sage.combinat + [6] + """ + if hasattr(self, "_print_options"): + symb = self._print_options['tensor_symbol'] + if symb is None: symb = tensor.symbol - return ascii_art(*(module._ascii_art_term(t) - for module, t in zip(self._sets, term)), - sep=AsciiArt([symb], breakpoints=[len(symb)])) - - _ascii_art_term = _ascii_art_ - - def _unicode_art_(self, term): - """ - TESTS:: - - sage: R = NonCommutativeSymmetricFunctions(QQ).R() # needs sage.combinat - sage: Partitions.options(diagram_str="#", convention="french") # needs sage.combinat - sage: s = unicode_art(tensor((R[1,2], R[3,1,2]))); s # needs sage.combinat - R ⊗ R - ┌┐ ┌┬┬┐ - ├┼┐ └┴┼┤ - └┴┘ ├┼┐ - └┴┘ - - Check that the breakpoints are correct (:issue:`29202`):: - - sage: s._breakpoints # needs sage.combinat - [7] - """ - if hasattr(self, "_print_options"): - symb = self._print_options['tensor_symbol'] - if symb is None: - symb = tensor.unicode_symbol - else: + else: + symb = tensor.symbol + return ascii_art(*(module._ascii_art_term(t) + for module, t in zip(self._sets, term)), + sep=AsciiArt([symb], breakpoints=[len(symb)])) + + _ascii_art_term = _ascii_art_ + + def _unicode_art_(self, term): + """ + TESTS:: + + sage: R = NonCommutativeSymmetricFunctions(QQ).R() # needs sage.combinat + sage: Partitions.options(diagram_str="#", convention="french") # needs sage.combinat + sage: s = unicode_art(tensor((R[1,2], R[3,1,2]))); s # needs sage.combinat + R ⊗ R + ┌┐ ┌┬┬┐ + ├┼┐ └┴┼┤ + └┴┘ ├┼┐ + └┴┘ + + Check that the breakpoints are correct (:issue:`29202`):: + + sage: s._breakpoints # needs sage.combinat + [7] + """ + if hasattr(self, "_print_options"): + symb = self._print_options['tensor_symbol'] + if symb is None: symb = tensor.unicode_symbol - return unicode_art(*(module._unicode_art_term(t) - for module, t in zip(self._sets, term)), - sep=UnicodeArt([symb], breakpoints=[len(symb)])) - - _unicode_art_term = _unicode_art_ - - def _latex_(self): - r""" - TESTS:: - - sage: F = CombinatorialFreeModule(ZZ, [1,2,3]) - sage: G = CombinatorialFreeModule(ZZ, [1,2,3,8]) - sage: F.rename("F") - sage: G.rename("G") - sage: latex(tensor([F, F, G])) # indirect doctest - \text{\texttt{F}} \otimes \text{\texttt{F}} \otimes \text{\texttt{G}} - sage: F._latex_ = lambda : "F" - sage: G._latex_ = lambda : "G" - sage: latex(tensor([F, F, G])) # indirect doctest - F \otimes F \otimes G - """ - from sage.misc.latex import latex - symb = " \\otimes " - return symb.join("%s" % latex(module) for module in self._sets) - - def _repr_term(self, term): - """ - TESTS:: - - sage: F = CombinatorialFreeModule(ZZ, [1,2,3], prefix="F") - sage: G = CombinatorialFreeModule(ZZ, [1,2,3,4], prefix="G") - sage: f = F.monomial(1) + 2 * F.monomial(2) - sage: g = 2*G.monomial(3) + G.monomial(4) - sage: tensor([f, g]) # indirect doctest - 2*F[1] # G[3] + F[1] # G[4] + 4*F[2] # G[3] + 2*F[2] # G[4] - """ - if hasattr(self, "_print_options"): - symb = self._print_options['tensor_symbol'] - if symb is None: - symb = tensor.symbol - else: + else: + symb = tensor.unicode_symbol + return unicode_art(*(module._unicode_art_term(t) + for module, t in zip(self._sets, term)), + sep=UnicodeArt([symb], breakpoints=[len(symb)])) + + _unicode_art_term = _unicode_art_ + + def _latex_(self): + r""" + TESTS:: + + sage: F = CombinatorialFreeModule(ZZ, [1,2,3]) + sage: G = CombinatorialFreeModule(ZZ, [1,2,3,8]) + sage: F.rename("F") + sage: G.rename("G") + sage: latex(tensor([F, F, G])) # indirect doctest + \text{\texttt{F}} \otimes \text{\texttt{F}} \otimes \text{\texttt{G}} + sage: F._latex_ = lambda : "F" + sage: G._latex_ = lambda : "G" + sage: latex(tensor([F, F, G])) # indirect doctest + F \otimes F \otimes G + """ + from sage.misc.latex import latex + symb = " \\otimes " + return symb.join("%s" % latex(module) for module in self._sets) + + def _repr_term(self, term): + """ + TESTS:: + + sage: F = CombinatorialFreeModule(ZZ, [1,2,3], prefix="F") + sage: G = CombinatorialFreeModule(ZZ, [1,2,3,4], prefix="G") + sage: f = F.monomial(1) + 2 * F.monomial(2) + sage: g = 2*G.monomial(3) + G.monomial(4) + sage: tensor([f, g]) # indirect doctest + 2*F[1] # G[3] + F[1] # G[4] + 4*F[2] # G[3] + 2*F[2] # G[4] + """ + if hasattr(self, "_print_options"): + symb = self._print_options['tensor_symbol'] + if symb is None: symb = tensor.symbol - return symb.join(module._repr_term(t) for (module, t) in zip(self._sets, term)) - - def _latex_term(self, term): - r""" - TESTS:: - - sage: F = CombinatorialFreeModule(ZZ, [1,2,3], prefix='x') - sage: G = CombinatorialFreeModule(ZZ, [1,2,3,4], prefix='y') - sage: f = F.monomial(1) + 2 * F.monomial(2) - sage: g = 2*G.monomial(3) + G.monomial(4) - sage: latex(tensor([f, g])) # indirect doctest - 2 x_{1} \otimes y_{3} + x_{1} \otimes y_{4} + 4 x_{2} \otimes y_{3} + 2 x_{2} \otimes y_{4} - """ - symb = " \\otimes " - return symb.join(module._latex_term(t) for (module, t) in zip(self._sets, term)) - - @cached_method - def tensor_constructor(self, modules): - r""" - INPUT: - - - ``modules`` -- a tuple `(F_1,\dots,F_n)` of - free modules whose tensor product is self - - Returns the canonical multilinear morphism from - `F_1 \times \dots \times F_n` to `F_1 \otimes \dots \otimes F_n` - - EXAMPLES:: - - 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) - sage: g = 2*G.monomial(3) + G.monomial(4) - sage: h = H.monomial(5) + H.monomial(6) - sage: FG = tensor([F, G]) - sage: phi_fg = FG.tensor_constructor((F, G)) - sage: phi_fg(f, g) - 2*B[1] # B[3] + B[1] # B[4] + 4*B[2] # B[3] + 2*B[2] # B[4] - - sage: FGH = tensor([F, G, H]) - sage: phi_fgh = FGH.tensor_constructor((F, G, H)) - sage: phi_fgh(f, g, h) - 2*B[1] # B[3] # B[5] + 2*B[1] # B[3] # B[6] + B[1] # B[4] # B[5] - + B[1] # B[4] # B[6] + 4*B[2] # B[3] # B[5] + 4*B[2] # B[3] # B[6] - + 2*B[2] # B[4] # B[5] + 2*B[2] # B[4] # B[6] - - sage: phi_fg_h = FGH.tensor_constructor((FG, H)) - sage: phi_fg_h(phi_fg(f, g), h) - 2*B[1] # B[3] # B[5] + 2*B[1] # B[3] # B[6] + B[1] # B[4] # B[5] - + B[1] # B[4] # B[6] + 4*B[2] # B[3] # B[5] + 4*B[2] # B[3] # B[6] - + 2*B[2] # B[4] # B[5] + 2*B[2] # B[4] # B[6] - """ - assert (module in ModulesWithBasis(self.base_ring()) for module in modules) - assert (tensor(modules) == self) - # a list l such that l[i] is True if modules[i] is readily a tensor product - is_tensor = [isinstance(module, CombinatorialFreeModule_Tensor) for module in modules] - # the tensor_constructor, on basis elements - result = self.monomial * CartesianProductWithFlattening(is_tensor) - # TODO: make this into an element of Hom( A x B, C ) when those will exist - for i in range(len(modules)): - result = modules[i]._module_morphism(result, position=i, - codomain=self) - return result - - def _tensor_of_elements(self, elements): - """ - Returns the tensor product of the specified elements. - The result should be in ``self``. - - EXAMPLES:: - - 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) - sage: g = 2*G.monomial(3) + G.monomial(4) - sage: h = H.monomial(5) + H.monomial(6) - - sage: GH = tensor([G, H]) - sage: gh = GH._tensor_of_elements([g, h]); gh - 2*B[3] # B[5] + 2*B[3] # B[6] + B[4] # B[5] + B[4] # B[6] - - sage: FGH = tensor([F, G, H]) - sage: FGH._tensor_of_elements([f, g, h]) - 2*B[1] # B[3] # B[5] + 2*B[1] # B[3] # B[6] + B[1] # B[4] # B[5] + B[1] # B[4] # B[6] + 4*B[2] # B[3] # B[5] + 4*B[2] # B[3] # B[6] + 2*B[2] # B[4] # B[5] + 2*B[2] # B[4] # B[6] - - sage: FGH._tensor_of_elements([f, gh]) - 2*B[1] # B[3] # B[5] + 2*B[1] # B[3] # B[6] + B[1] # B[4] # B[5] + B[1] # B[4] # B[6] + 4*B[2] # B[3] # B[5] + 4*B[2] # B[3] # B[6] + 2*B[2] # B[4] # B[5] + 2*B[2] # B[4] # B[6] - """ - return self.tensor_constructor(tuple(element.parent() for element in elements))(*elements) - - def _coerce_map_from_(self, R): - """ - Return ``True`` if there is a coercion from ``R`` into ``self`` and - ``False`` otherwise. The things that coerce into ``self`` are: - - - Anything with a coercion into ``self.base_ring()``. - - - A tensor algebra whose factors have a coercion into the - corresponding factors of ``self``. - - TESTS:: - - sage: C = CombinatorialFreeModule(ZZ, ZZ) - sage: C2 = CombinatorialFreeModule(ZZ, NN) - sage: M = C.module_morphism(lambda x: C2.monomial(abs(x)), codomain=C2) - sage: M.register_as_coercion() - sage: C2(C.basis()[3]) - B[3] - sage: C2(C.basis()[3] + C.basis()[-3]) - 2*B[3] - sage: S = C.tensor(C) - sage: S2 = C2.tensor(C2) - sage: S2.has_coerce_map_from(S) - True - sage: S.has_coerce_map_from(S2) - False - sage: S.an_element() - 3*B[0] # B[-1] + 2*B[0] # B[0] + 2*B[0] # B[1] - sage: S2(S.an_element()) - 2*B[0] # B[0] + 5*B[0] # B[1] - - :: - - sage: C = CombinatorialFreeModule(ZZ, Set([1,2])) - sage: D = CombinatorialFreeModule(ZZ, Set([2,4])) - sage: f = C.module_morphism(on_basis=lambda x: D.monomial(2*x), codomain=D) - sage: f.register_as_coercion() - sage: T = tensor((C,C)) - sage: p = D.an_element() - sage: T(tensor((p,p))) - Traceback (most recent call last): - ... - NotImplementedError - sage: T = tensor((D,D)) - sage: p = C.an_element() - sage: T(tensor((p,p))) - 4*B[2] # B[2] + 4*B[2] # B[4] + 4*B[4] # B[2] + 4*B[4] # B[4] - """ - if ((R in ModulesWithBasis(self.base_ring()).TensorProducts() or - R in GradedAlgebrasWithBasis(self.base_ring()).SignedTensorProducts()) - and isinstance(R, CombinatorialFreeModule_Tensor) - and len(R._sets) == len(self._sets) - and all(self._sets[i].has_coerce_map_from(M) - for i, M in enumerate(R._sets))): - modules = R._sets - vector_map = [self._sets[i]._internal_coerce_map_from(M) - for i, M in enumerate(modules)] - return R.module_morphism(lambda x: self._tensor_of_elements( - [vector_map[i](M.monomial(x[i])) - for i, M in enumerate(modules)]), codomain=self) - - return super()._coerce_map_from_(R) + else: + symb = tensor.symbol + return symb.join(module._repr_term(t) for (module, t) in zip(self._sets, term)) + + def _latex_term(self, term): + r""" + TESTS:: + + sage: F = CombinatorialFreeModule(ZZ, [1,2,3], prefix='x') + sage: G = CombinatorialFreeModule(ZZ, [1,2,3,4], prefix='y') + sage: f = F.monomial(1) + 2 * F.monomial(2) + sage: g = 2*G.monomial(3) + G.monomial(4) + sage: latex(tensor([f, g])) # indirect doctest + 2 x_{1} \otimes y_{3} + x_{1} \otimes y_{4} + 4 x_{2} \otimes y_{3} + 2 x_{2} \otimes y_{4} + """ + symb = " \\otimes " + return symb.join(module._latex_term(t) for (module, t) in zip(self._sets, term)) + + @cached_method + def tensor_constructor(self, modules): + r""" + INPUT: + + - ``modules`` -- a tuple `(F_1,\dots,F_n)` of + free modules whose tensor product is self + + Returns the canonical multilinear morphism from + `F_1 \times \dots \times F_n` to `F_1 \otimes \dots \otimes F_n` + + EXAMPLES:: + + 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) + sage: g = 2*G.monomial(3) + G.monomial(4) + sage: h = H.monomial(5) + H.monomial(6) + sage: FG = tensor([F, G]) + sage: phi_fg = FG.tensor_constructor((F, G)) + sage: phi_fg(f, g) + 2*B[1] # B[3] + B[1] # B[4] + 4*B[2] # B[3] + 2*B[2] # B[4] + + sage: FGH = tensor([F, G, H]) + sage: phi_fgh = FGH.tensor_constructor((F, G, H)) + sage: phi_fgh(f, g, h) + 2*B[1] # B[3] # B[5] + 2*B[1] # B[3] # B[6] + B[1] # B[4] # B[5] + + B[1] # B[4] # B[6] + 4*B[2] # B[3] # B[5] + 4*B[2] # B[3] # B[6] + + 2*B[2] # B[4] # B[5] + 2*B[2] # B[4] # B[6] + + sage: phi_fg_h = FGH.tensor_constructor((FG, H)) + sage: phi_fg_h(phi_fg(f, g), h) + 2*B[1] # B[3] # B[5] + 2*B[1] # B[3] # B[6] + B[1] # B[4] # B[5] + + B[1] # B[4] # B[6] + 4*B[2] # B[3] # B[5] + 4*B[2] # B[3] # B[6] + + 2*B[2] # B[4] # B[5] + 2*B[2] # B[4] # B[6] + """ + assert (module in ModulesWithBasis(self.base_ring()) for module in modules) + assert (tensor(modules) == self) + # a list l such that l[i] is True if modules[i] is readily a tensor product + is_tensor = [isinstance(module, CombinatorialFreeModule_Tensor) for module in modules] + # the tensor_constructor, on basis elements + result = self.monomial * CartesianProductWithFlattening(is_tensor) + # TODO: make this into an element of Hom( A x B, C ) when those will exist + for i in range(len(modules)): + result = modules[i]._module_morphism(result, position=i, + codomain=self) + return result + + def _tensor_of_elements(self, elements): + """ + Returns the tensor product of the specified elements. + The result should be in ``self``. + + EXAMPLES:: + + 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) + sage: g = 2*G.monomial(3) + G.monomial(4) + sage: h = H.monomial(5) + H.monomial(6) + + sage: GH = tensor([G, H]) + sage: gh = GH._tensor_of_elements([g, h]); gh + 2*B[3] # B[5] + 2*B[3] # B[6] + B[4] # B[5] + B[4] # B[6] + + sage: FGH = tensor([F, G, H]) + sage: FGH._tensor_of_elements([f, g, h]) + 2*B[1] # B[3] # B[5] + 2*B[1] # B[3] # B[6] + B[1] # B[4] # B[5] + + B[1] # B[4] # B[6] + 4*B[2] # B[3] # B[5] + 4*B[2] # B[3] # B[6] + + 2*B[2] # B[4] # B[5] + 2*B[2] # B[4] # B[6] + + sage: FGH._tensor_of_elements([f, gh]) + 2*B[1] # B[3] # B[5] + 2*B[1] # B[3] # B[6] + B[1] # B[4] # B[5] + + B[1] # B[4] # B[6] + 4*B[2] # B[3] # B[5] + 4*B[2] # B[3] # B[6] + + 2*B[2] # B[4] # B[5] + 2*B[2] # B[4] # B[6] + """ + return self.tensor_constructor(tuple(element.parent() for element in elements))(*elements) + + def _coerce_map_from_(self, R): + """ + Return ``True`` if there is a coercion from ``R`` into ``self`` and + ``False`` otherwise. The things that coerce into ``self`` are: + + - Anything with a coercion into ``self.base_ring()``. + + - A tensor algebra whose factors have a coercion into the + corresponding factors of ``self``. + + TESTS:: + + sage: C = CombinatorialFreeModule(ZZ, ZZ) + sage: C2 = CombinatorialFreeModule(ZZ, NN) + sage: M = C.module_morphism(lambda x: C2.monomial(abs(x)), codomain=C2) + sage: M.register_as_coercion() + sage: C2(C.basis()[3]) + B[3] + sage: C2(C.basis()[3] + C.basis()[-3]) + 2*B[3] + sage: S = C.tensor(C) + sage: S2 = C2.tensor(C2) + sage: S2.has_coerce_map_from(S) + True + sage: S.has_coerce_map_from(S2) + False + sage: S.an_element() + 3*B[0] # B[-1] + 2*B[0] # B[0] + 2*B[0] # B[1] + sage: S2(S.an_element()) + 2*B[0] # B[0] + 5*B[0] # B[1] + + :: + + sage: C = CombinatorialFreeModule(ZZ, Set([1,2])) + sage: D = CombinatorialFreeModule(ZZ, Set([2,4])) + sage: f = C.module_morphism(on_basis=lambda x: D.monomial(2*x), codomain=D) + sage: f.register_as_coercion() + sage: T = tensor((C,C)) + sage: p = D.an_element() + sage: T(tensor((p,p))) + Traceback (most recent call last): + ... + NotImplementedError + sage: T = tensor((D,D)) + sage: p = C.an_element() + sage: T(tensor((p,p))) + 4*B[2] # B[2] + 4*B[2] # B[4] + 4*B[4] # B[2] + 4*B[4] # B[4] + """ + if ((R in ModulesWithBasis(self.base_ring()).TensorProducts() or + R in GradedAlgebrasWithBasis(self.base_ring()).SignedTensorProducts()) + and isinstance(R, CombinatorialFreeModule_Tensor) + and len(R._sets) == len(self._sets) + and all(self._sets[i].has_coerce_map_from(M) + for i, M in enumerate(R._sets))): + modules = R._sets + vector_map = [self._sets[i]._internal_coerce_map_from(M) + for i, M in enumerate(modules)] + return R.module_morphism(lambda x: self._tensor_of_elements( + [vector_map[i](M.monomial(x[i])) + for i, M in enumerate(modules)]), codomain=self) + + return super()._coerce_map_from_(R) class CartesianProductWithFlattening: diff --git a/src/sage/combinat/integer_vector_weighted.py b/src/sage/combinat/integer_vector_weighted.py index 7540aca9a07..9a28d632c85 100644 --- a/src/sage/combinat/integer_vector_weighted.py +++ b/src/sage/combinat/integer_vector_weighted.py @@ -240,7 +240,7 @@ def __iter__(self): perm = Word(self._weights).standard_permutation() perm = [len(self._weights) - i for i in perm] - l = [x for x in sorted(self._weights, reverse=True)] + l = sorted(self._weights, reverse=True) for x in iterator_fast(self._n, l): yield self.element_class(self, [x[i] for i in perm]) # .action(x) diff --git a/src/sage/combinat/knutson_tao_puzzles.py b/src/sage/combinat/knutson_tao_puzzles.py index 1c0215fa093..b8b62c6a440 100644 --- a/src/sage/combinat/knutson_tao_puzzles.py +++ b/src/sage/combinat/knutson_tao_puzzles.py @@ -817,8 +817,8 @@ def __repr__(self) -> str: Nablas : [a\b/c, b\c/a, c\a/b] Deltas : [a/c\b, b/a\c, c/b\a] """ - s = "Nablas : %s\n" % sorted([p for p in self._nabla_pieces], key=str) - s += "Deltas : %s" % sorted([p for p in self._delta_pieces], key=str) + s = "Nablas : %s\n" % sorted(self._nabla_pieces, key=str) + s += "Deltas : %s" % sorted(self._delta_pieces, key=str) return s def delta_pieces(self): diff --git a/src/sage/combinat/lr_tableau.py b/src/sage/combinat/lr_tableau.py index a94f576a134..364291cee7c 100644 --- a/src/sage/combinat/lr_tableau.py +++ b/src/sage/combinat/lr_tableau.py @@ -304,5 +304,5 @@ def _tableau_join(t1, t2, shift=0): sage: _tableau_join([[1,2]],[[None,None,2],[3]],shift=5) [[1, 2, 7], [8]] """ - return [[e1 for e1 in row1] + [e2+shift for e2 in row2 if e2 is not None] + return [list(row1) + [e2+shift for e2 in row2 if e2 is not None] for (row1, row2) in zip_longest(t1, t2, fillvalue=[])] diff --git a/src/sage/combinat/matrices/hadamard_matrix.py b/src/sage/combinat/matrices/hadamard_matrix.py index 14d6a30a8f5..8c6b2f10395 100644 --- a/src/sage/combinat/matrices/hadamard_matrix.py +++ b/src/sage/combinat/matrices/hadamard_matrix.py @@ -592,7 +592,7 @@ def pmtoZ(s): if n not in db: raise ValueError("The Williamson type quadruple of order %s is not yet implemented." % n) - a, b, c, d = map(lambda s: vector(pmtoZ(s)), db[n]) + a, b, c, d = (vector(pmtoZ(s)) for s in db[n]) return a, b, c, d @@ -736,7 +736,7 @@ def construction_four_symbol_delta_code_I(X, Y, Z, W): def autocorrelation(seq, j): return sum([seq[i]*seq[i+j] for i in range(len(seq)-j)]) for j in range(1, n): - assert sum(map(lambda seq: autocorrelation(seq, j), [X, Y, Z, W])) == 0 + assert sum(autocorrelation(seq, j) for seq in [X, Y, Z, W]) == 0 T1 = X + Z T2 = X + [-z for z in Z] @@ -812,7 +812,7 @@ def autocorrelation(seq, j): return sum([seq[i]*seq[i+j] for i in range(len(seq)-j)]) for j in range(1, n): - assert sum(map(lambda seq: autocorrelation(seq, j), [X, Y, Z, W])) == 0 + assert sum(autocorrelation(seq, j) for seq in [X, Y, Z, W]) == 0 def alternate(seq1, seq2): return [seq1[i//2] if i % 2 == 0 else seq2[(i-1)//2] for i in range(len(seq1)+len(seq2))] diff --git a/src/sage/combinat/multiset_partition_into_sets_ordered.py b/src/sage/combinat/multiset_partition_into_sets_ordered.py index 967f6ad93af..55d6a9aeaf9 100755 --- a/src/sage/combinat/multiset_partition_into_sets_ordered.py +++ b/src/sage/combinat/multiset_partition_into_sets_ordered.py @@ -246,9 +246,9 @@ def _repr_normal(self): """ # TODO: simplify if/once ``_repr_`` method for ``Set`` sorts its elements. if self._n: - string_parts = map(lambda k: str(sorted(k)), self) + string_parts = (str(sorted(k)) for k in self) else: - string_parts = map(lambda k: str(sorted(k, key=str)), self) + string_parts = (str(sorted(k, key=str)) for k in self) string_parts = ", ".join(string_parts).replace("[","{").replace("]","}") return "[" + string_parts + "]" diff --git a/src/sage/combinat/nu_tamari_lattice.py b/src/sage/combinat/nu_tamari_lattice.py index d28faf6c7ed..56cfa020194 100644 --- a/src/sage/combinat/nu_tamari_lattice.py +++ b/src/sage/combinat/nu_tamari_lattice.py @@ -263,7 +263,7 @@ def AltNuTamariLattice(nu, delta=None): deltamax = [len(a) for a in nu.split(sep='1')[1:]] if delta is None: delta = deltamax - elif len(delta) != len(deltamax) or any([delta[i] > deltamax[i] for i in range(len(delta))]): + elif len(delta) != len(deltamax) or any(delta[i] > deltamax[i] for i in range(len(delta))): raise ValueError("delta is not a valid increment vector") def covers(p): diff --git a/src/sage/combinat/partition.py b/src/sage/combinat/partition.py index 83d21dba8c6..9c797840331 100644 --- a/src/sage/combinat/partition.py +++ b/src/sage/combinat/partition.py @@ -1851,7 +1851,7 @@ def down_list(self): sage: Partition([]).down_list() #checks :issue:`11435` [] """ - return [p for p in self.down()] + return list(self.down()) @combinatorial_map(name="cell poset") def cell_poset(self, orientation="SE"): diff --git a/src/sage/combinat/partition_tuple.py b/src/sage/combinat/partition_tuple.py index 182c0b8813d..b4d15d2dc55 100644 --- a/src/sage/combinat/partition_tuple.py +++ b/src/sage/combinat/partition_tuple.py @@ -767,7 +767,7 @@ def components(self): *** ** """ - return [t for t in self] + return list(self) def diagram(self): r""" @@ -883,7 +883,7 @@ def up(self): """ for c in range(len(self)): for nu in self[c].up(): - up = [tau for tau in self] + up = list(self) up[c] = nu yield PartitionTuple(up) @@ -900,7 +900,7 @@ def up_list(self): [([1], [], [], []), ([], [1], [], []), ([], [], [1], []), ([], [], [], [1])] """ - return [mu for mu in self.up()] + return list(self.up()) def down(self): r""" @@ -917,7 +917,7 @@ def down(self): """ for c in range(len(self)): for nu in self[c].down(): - down = [tau for tau in self] + down = list(self) down[c] = nu yield PartitionTuple(down) @@ -933,7 +933,7 @@ def down_list(self): sage: PartitionTuple([[],[],[]]).down_list() [] """ - return [mu for mu in self.down()] + return list(self.down()) def cells(self): """ @@ -1515,7 +1515,7 @@ def young_subgroup_generators(self): m = 0 for comp in self: for row in comp: - gens.extend([c for c in range(m + 1, m + row)]) + gens.extend(list(range(m + 1, m + row))) m += row return gens diff --git a/src/sage/combinat/rigged_configurations/rigged_configurations.py b/src/sage/combinat/rigged_configurations/rigged_configurations.py index 33de9927c12..535787875b5 100644 --- a/src/sage/combinat/rigged_configurations/rigged_configurations.py +++ b/src/sage/combinat/rigged_configurations/rigged_configurations.py @@ -1549,7 +1549,7 @@ def to_virtual(self, rc): for a,rp in enumerate(rc): g = gamma[a+1] for i in sigma[a+1]: - partitions[i-1] = RiggedPartition([row_len for row_len in rp._list], + partitions[i-1] = RiggedPartition(list(rp._list), [rig_val*g for rig_val in rp.rigging], [vac_num*g for vac_num in rp.vacancy_numbers]) return self.virtual.element_class(self.virtual, partitions, use_vacancy_numbers=True) @@ -1585,7 +1585,7 @@ def from_virtual(self, vrc): for a in range(n): rp = vrc[sigma[a+1][0] - 1] g = gamma[a+1] - partitions[a] = RiggedPartition([row_len for row_len in rp._list], + partitions[a] = RiggedPartition(list(rp._list), [rig_val//g for rig_val in rp.rigging], [vac_val//g for vac_val in rp.vacancy_numbers]) return self.element_class(self, partitions, use_vacancy_numbers=True) @@ -1874,7 +1874,7 @@ def to_virtual(self, rc): for a,rp in enumerate(rc): g = gammatilde[a+1] for i in sigma[a+1]: - partitions[i-1] = RiggedPartition([row_len for row_len in rp._list], + partitions[i-1] = RiggedPartition(list(rp._list), [rig_val*g for rig_val in rp.rigging]) return self.virtual.element_class(self.virtual, partitions) @@ -1910,7 +1910,7 @@ def from_virtual(self, vrc): for a in range(n): rp = vrc[sigma[a+1][0] - 1] g = gammatilde[a+1] - partitions[a] = RiggedPartition([row_len for row_len in rp._list], + partitions[a] = RiggedPartition(list(rp._list), [rig_val/g for rig_val in rp.rigging]) return self.element_class(self, partitions) diff --git a/src/sage/combinat/root_system/fundamental_group.py b/src/sage/combinat/root_system/fundamental_group.py index 8905471a5ab..6dc13e302ec 100644 --- a/src/sage/combinat/root_system/fundamental_group.py +++ b/src/sage/combinat/root_system/fundamental_group.py @@ -393,7 +393,7 @@ def leading_support(beta): cartan_type = cartan_type.dual() if cartan_type.is_untwisted_affine(): cartan_type_classical = cartan_type.classical() - I = [i for i in cartan_type_classical.index_set()] + I = list(cartan_type_classical.index_set()) Q = RootSystem(cartan_type_classical).root_lattice() alpha = Q.simple_roots() omega = RootSystem(cartan_type_classical).weight_lattice().fundamental_weights() diff --git a/src/sage/combinat/root_system/hecke_algebra_representation.py b/src/sage/combinat/root_system/hecke_algebra_representation.py index 1dc8d16ad59..d5eb99adbb7 100644 --- a/src/sage/combinat/root_system/hecke_algebra_representation.py +++ b/src/sage/combinat/root_system/hecke_algebra_representation.py @@ -637,8 +637,8 @@ def Y_lambdacheck(self, lambdacheck): # works for our two main examples (action of affine W on W, # and Macdonald polynomials) if self._side == "left": - word = tuple([x for x in reversed(word)]) - signs = tuple([x for x in reversed(signs)]) + word = tuple(reversed(word)) + signs = tuple(reversed(signs)) # The power of q implements the fact that Y^\deltacheck = 1/q. # The classical simple coroots have no \deltacheck term. # alpha[0] has a \deltacheck with coefficient one diff --git a/src/sage/combinat/root_system/plot.py b/src/sage/combinat/root_system/plot.py index 5d591febe43..483c510eb61 100644 --- a/src/sage/combinat/root_system/plot.py +++ b/src/sage/combinat/root_system/plot.py @@ -1412,7 +1412,7 @@ def cone(self, rays=[], lines=[], color="black", thickness=1, alpha=1, wireframe # TODO: we currently convert lines into rays, which simplify a # bit the calculation of the intersection. But it would be # nice to benefit from the new ``lines`` option of Polyhedra - rays = list(rays) + [ray for ray in lines] + [-ray for ray in lines] + rays = list(rays) + list(lines) + [-ray for ray in lines] # Compute the intersection at level 1, if needed if self.level: diff --git a/src/sage/combinat/root_system/reflection_group_complex.py b/src/sage/combinat/root_system/reflection_group_complex.py index 0b80aa73a71..3287b516d08 100644 --- a/src/sage/combinat/root_system/reflection_group_complex.py +++ b/src/sage/combinat/root_system/reflection_group_complex.py @@ -499,7 +499,7 @@ def distinguished_reflections(self): """ # makes sure that the simple reflections come first gens = self.gens() - R = [t for t in gens] + R = list(gens) # Then import all distinguished reflections from gap, # the Set is used as every such appears multiple times. for r in self._gap_group.Reflections(): diff --git a/src/sage/combinat/root_system/root_lattice_realizations.py b/src/sage/combinat/root_system/root_lattice_realizations.py index ea1f4d285b2..f03d1d44e7f 100644 --- a/src/sage/combinat/root_system/root_lattice_realizations.py +++ b/src/sage/combinat/root_system/root_lattice_realizations.py @@ -18,6 +18,7 @@ from sage.misc.lazy_import import LazyImport from sage.categories.coxeter_groups import CoxeterGroups from sage.categories.category_types import Category_over_base_ring +from sage.categories.enumerated_sets import EnumeratedSets from sage.categories.modules_with_basis import ModulesWithBasis from sage.structure.element import Element from sage.sets.family import Family @@ -715,7 +716,8 @@ def positive_roots(self, index_set=None): index_set = tuple(self.cartan_type().index_set()) return RecursivelyEnumeratedSet([self.simple_root(i) for i in index_set], attrcall('pred', index_set=index_set), - structure='graded', enumeration='breadth') + structure='graded', enumeration='breadth', + category=EnumeratedSets().Finite()) @cached_method def nonparabolic_positive_roots(self, index_set=None): @@ -1229,7 +1231,7 @@ def negative_roots(self): """ if not self.cartan_type().is_finite(): raise ValueError("%s is not a finite Cartan type" % self.cartan_type()) - return self.positive_roots().map(attrcall('__neg__')) + return self.positive_roots().map(attrcall('__neg__'), is_injective=True) ########################################################################## # coroots @@ -4279,7 +4281,7 @@ def weyl_action(self, element, inverse=False): # TODO, some day: accept an iterator if isinstance(element, (tuple, list, range)): # Action by a (reduced) word - the_word = [x for x in element] + the_word = list(element) I = self.parent().index_set() if not all(i in I for i in the_word): raise ValueError("Not all members of %s are in the index set of the %s" % (element, self.parent())) diff --git a/src/sage/combinat/root_system/root_space.py b/src/sage/combinat/root_system/root_space.py index a375b3b8074..54d3c3d71f2 100644 --- a/src/sage/combinat/root_system/root_space.py +++ b/src/sage/combinat/root_system/root_space.py @@ -443,7 +443,7 @@ def max_quantum_element(self): word = [] while self != Qvee.zero(): beta = self.max_coroot_le() - word += [x for x in beta.associated_reflection()] + word += list(beta.associated_reflection()) self = self - beta.associated_coroot() W = self.parent().weyl_group() return (W.demazure_product(word)).reduced_word() diff --git a/src/sage/combinat/set_partition.py b/src/sage/combinat/set_partition.py index 0c22c6a2edc..4c0a3ec186d 100644 --- a/src/sage/combinat/set_partition.py +++ b/src/sage/combinat/set_partition.py @@ -2033,6 +2033,14 @@ class SetPartitions(UniqueRepresentation, Parent): sage: SetPartitions('aabcd').cardinality() # needs sage.libs.flint 15 + If the number of parts exceeds the length of the set, + an empty iterator is returned (:issue:`37643`):: + + sage: SetPartitions(range(3), 4).list() + [] + sage: SetPartitions('abcd', 6).list() + [] + REFERENCES: - :wikipedia:`Partition_of_a_set` @@ -2061,18 +2069,16 @@ def __classcall_private__(cls, s=None, part=None): pass s = frozenset(s) - if part is not None: + if part is None: + return SetPartitions_set(s) + else: if isinstance(part, (int, Integer)): - if len(s) < part: - raise ValueError("part must be <= len(set)") return SetPartitions_setn(s, part) else: part = sorted(part, reverse=True) if part not in Partitions(len(s)): raise ValueError("part must be an integer partition of %s" % len(s)) return SetPartitions_setparts(s, Partition(part)) - else: - return SetPartitions_set(s) def __contains__(self, x): """ diff --git a/src/sage/combinat/set_partition_iterator.pyx b/src/sage/combinat/set_partition_iterator.pyx index ff7d2e7c6c3..c8a7938875b 100644 --- a/src/sage/combinat/set_partition_iterator.pyx +++ b/src/sage/combinat/set_partition_iterator.pyx @@ -129,5 +129,7 @@ def set_partition_iterator_blocks(base_set, Py_ssize_t k): cdef Py_ssize_t n = len(base) cdef list a = list(range(n)) # TODO: implement _set_partition_block_gen as an iterative algorithm + if n < k: + return for P in _set_partition_block_gen(n, k, a): yield from_word( P, base) diff --git a/src/sage/combinat/sf/dual.py b/src/sage/combinat/sf/dual.py index aae10602f2d..207e411a2c6 100644 --- a/src/sage/combinat/sf/dual.py +++ b/src/sage/combinat/sf/dual.py @@ -292,7 +292,7 @@ def basis_name(self): Return the name of the basis of ``self``. This is used for output and, for the classical bases of - symmetric functions, to connect this basis with Symmetrica. + symmetric functions, to connect this basis with :ref:`Symmetrica `. EXAMPLES:: diff --git a/src/sage/combinat/sf/sfa.py b/src/sage/combinat/sf/sfa.py index d569b6ce1dd..d2d9d6385e5 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -641,7 +641,8 @@ def corresponding_basis_over(self, R): sage: Sym = SymmetricFunctions(P) sage: mj = Sym.macdonald().J() sage: mj.corresponding_basis_over(Integers(13)['q','t']) - Symmetric Functions over Multivariate Polynomial Ring in q, t over Ring of integers modulo 13 in the Macdonald J basis + Symmetric Functions over Multivariate Polynomial Ring in q, t over + Ring of integers modulo 13 in the Macdonald J basis TESTS: @@ -6634,7 +6635,7 @@ def _apply_functor(self, R): sage: F(QQ) Traceback (most recent call last): ... - TypeError: not a constant polynomial + TypeError: t is not a constant polynomial """ from sage.combinat.sf.sf import SymmetricFunctions return self._basis(self._family(SymmetricFunctions(R), *self._args)) diff --git a/src/sage/combinat/sf/witt.py b/src/sage/combinat/sf/witt.py index 37a07f8139f..cf14fa826c6 100644 --- a/src/sage/combinat/sf/witt.py +++ b/src/sage/combinat/sf/witt.py @@ -23,7 +23,6 @@ from sage.arith.misc import divisors from sage.combinat.integer_lists.invlex import IntegerListsLex from sage.combinat.partitions import ZS1_iterator -from sage.matrix.constructor import matrix from sage.misc.cachefunc import cached_method @@ -667,7 +666,7 @@ def _omega_even(self, lam_even): True """ dct = self._h(self.monomial(lam_even)).monomial_coefficients(copy=False) - eelt = self._e.element_class(self._e, {mu: c for mu, c in dct.items()}) + eelt = self._e.element_class(self._e, dict(dct.items())) return self(eelt) class Element(multiplicative.SymmetricFunctionAlgebra_multiplicative.Element): diff --git a/src/sage/combinat/shifted_primed_tableau.py b/src/sage/combinat/shifted_primed_tableau.py index a05a8b0bee8..b678c84171a 100644 --- a/src/sage/combinat/shifted_primed_tableau.py +++ b/src/sage/combinat/shifted_primed_tableau.py @@ -357,7 +357,7 @@ def _repr_list(self): sage: ShiftedPrimedTableau([['2p',3],[2,2]], skew=[2])._repr_list() "[(None, None, 2', 3), (2, 2)]" """ - return repr([row for row in self]) + return repr(list(self)) def _repr_tab(self): """ diff --git a/src/sage/combinat/sloane_functions.py b/src/sage/combinat/sloane_functions.py index e9911d47506..76a05d6dc18 100644 --- a/src/sage/combinat/sloane_functions.py +++ b/src/sage/combinat/sloane_functions.py @@ -859,7 +859,7 @@ def _eval(self, n): sage: [sloane.A003418._eval(n) for n in range(1,11)] [1, 2, 6, 12, 60, 60, 420, 840, 2520, 2520] """ - return arith.lcm([i for i in range(1, n+1)]) + return arith.lcm(range(1, n + 1)) class A007318(SloaneSequence): diff --git a/src/sage/combinat/specht_module.py b/src/sage/combinat/specht_module.py index 127903829f7..260a3fbde43 100644 --- a/src/sage/combinat/specht_module.py +++ b/src/sage/combinat/specht_module.py @@ -1351,12 +1351,12 @@ def tabloid_gram_matrix(la, base_ring): [4 1 1 2 4] """ from sage.combinat.tableau import StandardTableaux - ST = StandardTableaux(la) + ST = list(StandardTableaux(la)) def bilinear_form(p1, p2): if len(p2) < len(p1): p1, p2 = p2, p1 - return sum(c1 * p2.get(T1, 0) for T1, c1 in p1.items() if c1) + return sum(c1 * p2[T1] for T1, c1 in p1.items() if c1 and T1 in p2) PT = {T: polytabloid(T) for T in ST} gram_matrix = [[bilinear_form(PT[T1], PT[T2]) for T1 in ST] for T2 in ST] diff --git a/src/sage/combinat/species/partition_species.py b/src/sage/combinat/species/partition_species.py index dea38378a7a..8d9a83d1e00 100644 --- a/src/sage/combinat/species/partition_species.py +++ b/src/sage/combinat/species/partition_species.py @@ -182,7 +182,7 @@ def _structures(self, structure_class, labels): yield structure_class(self, labels, []) return - u = [i for i in reversed(range(1, n + 1))] + u = list(range(n, 0, -1)) s0 = u.pop() # Reconstruct the set partitions from diff --git a/src/sage/combinat/species/structure.py b/src/sage/combinat/species/structure.py index b3003ed0f13..c5629212f5d 100644 --- a/src/sage/combinat/species/structure.py +++ b/src/sage/combinat/species/structure.py @@ -37,8 +37,10 @@ # # http://www.gnu.org/licenses/ #***************************************************************************** -from sage.combinat.combinat import CombinatorialClass, CombinatorialObject +from sage.categories.enumerated_sets import EnumeratedSets +from sage.combinat.combinat import CombinatorialObject from sage.rings.integer import Integer +from sage.structure.parent import Parent from copy import copy @@ -326,7 +328,7 @@ def change_labels(self, labels): ############################################################## -class SpeciesWrapper(CombinatorialClass): +class SpeciesWrapper(Parent): def __init__(self, species, labels, iterator, generating_series, name, structure_class): """ This is a abstract base class for the set of structures of a @@ -350,6 +352,7 @@ def __init__(self, species, labels, iterator, generating_series, name, structure sage: S.cardinality() 1 """ + Parent.__init__(self, category=EnumeratedSets().Finite()) self._species = species self._labels = labels self._iterator = iterator @@ -357,6 +360,46 @@ def __init__(self, species, labels, iterator, generating_series, name, structure self._name = "%s for %s with labels %s" % (name, species, labels) self._structure_class = structure_class if structure_class is not None else species._default_structure_class + def __eq__(self, other) -> bool: + r""" + EXAMPLES:: + + sage: from sage.combinat.species.structure import SpeciesWrapper + sage: F = species.SetSpecies() + sage: S = SpeciesWrapper(F, [1,2,3], "_structures", "generating_series", 'Structures', None) + sage: S == SpeciesWrapper(F, [1,2,3], "_structures", "generating_series", 'Structures', None) + True + """ + return ((self._species, self._labels, + self._iterator, self._generating_series, + self._name, self._structure_class) == (other._species, other._labels, + other._iterator, other._generating_series, + other._name, other._structure_class)) + + def __ne__(self, other) -> bool: + r""" + EXAMPLES:: + + sage: from sage.combinat.species.structure import SpeciesWrapper + sage: F = species.SetSpecies() + sage: S = SpeciesWrapper(F, [1,2,3], "_structures", "generating_series", 'Structures', None) + sage: S != SpeciesWrapper(F, [1,2,3], "_structures", "generating_series", 'Structures', None) + False + """ + return not (self == other) + + def _repr_(self) -> str: + """ + EXAMPLES:: + + sage: from sage.combinat.species.structure import SpeciesWrapper + sage: F = species.SetSpecies() + sage: S = SpeciesWrapper(F, [1,2,3], "_structures", "generating_series", 'Structures', None) + sage: repr(S) # indirect doctest + 'Structures for Set species with labels [1, 2, 3]' + """ + return self._name + def labels(self): """ Returns the labels used on these structures. If `X` is the diff --git a/src/sage/combinat/subset.py b/src/sage/combinat/subset.py index 822420d6514..4d036ad6c93 100644 --- a/src/sage/combinat/subset.py +++ b/src/sage/combinat/subset.py @@ -741,7 +741,8 @@ def last(self): if self._k > self._s.cardinality(): raise EmptySetError - return self.element_class([i for i in itertools.islice(reversed(self._s), int(self._k))]) + return self.element_class(list(itertools.islice(reversed(self._s), + int(self._k)))) def _fast_iterator(self): r""" diff --git a/src/sage/combinat/subword_complex.py b/src/sage/combinat/subword_complex.py index 0439d7f72f0..f280a5901f3 100644 --- a/src/sage/combinat/subword_complex.py +++ b/src/sage/combinat/subword_complex.py @@ -1854,8 +1854,8 @@ def cover_relations(self, label=False): """ N = len(self.group().long_element(as_word=True)) F = self.greedy_facet(side="positive") - Fs = set([F]) - seen = set([F]) + Fs = {F} + seen = {F} covers = [] while Fs: F = Fs.pop() @@ -1895,7 +1895,7 @@ def increasing_flip_graph(self, label=True): from sage.graphs.digraph import DiGraph return DiGraph(self.cover_relations(label=label)) - def interval(self, I, J): + def interval(self, I, J) -> set: """ Return the interval [I,J] in the increasing flip graph subword complex. @@ -1924,7 +1924,7 @@ def interval(self, I, J): """ G = self.increasing_flip_graph() paths = G.all_paths(I, J) - return set(K for path in paths for K in path) + return {K for path in paths for K in path} def increasing_flip_poset(self): """ @@ -2125,8 +2125,8 @@ def _greedy_flip_algorithm(Q, w): flip_to_ancestors.append(j) next_index = i + 1 has_new_child = True - facet_list.append([x for x in F]) - extended_root_conf_indices_list.append([x for x in R]) + facet_list.append(list(F)) + extended_root_conf_indices_list.append(list(R)) if not has_new_child: i = flip_to_ancestors.pop() if i != -1: diff --git a/src/sage/combinat/superpartition.py b/src/sage/combinat/superpartition.py index a2e77f8a5b1..e16e87fa9f0 100644 --- a/src/sage/combinat/superpartition.py +++ b/src/sage/combinat/superpartition.py @@ -691,7 +691,7 @@ def add_horizontal_border_strip_star(self, h) -> list: # TODO: Check that this is not supposed to be # a tuple of size 1 + [(i) for i in circ_list if row_changed[i[0]] == 0]] - if len(set([k for (j, k) in new_sp[1]])) == len(new_sp[1]): + if len({k for j, k in new_sp[1]}) == len(new_sp[1]): out += [SuperPartition.from_circled_diagram(*new_sp)] return out diff --git a/src/sage/combinat/symmetric_group_algebra.py b/src/sage/combinat/symmetric_group_algebra.py index bda0590af5f..f79413e2be3 100644 --- a/src/sage/combinat/symmetric_group_algebra.py +++ b/src/sage/combinat/symmetric_group_algebra.py @@ -1979,10 +1979,15 @@ def seminormal_basis(self, mult='l2r'): basis.append(self.epsilon_ik(t1, t2, mult=mult)) return basis - def dft(self, form="seminormal", mult='l2r'): - """ + def dft(self, form=None, mult='l2r'): + r""" Return the discrete Fourier transform for ``self``. + See [Mur1983]_ for the construction of central primitive orthogonal idempotents. + For each idempotent `e_i` we have a homomorphic projection `v \mapsto v e_i`. + Choose a basis for each submodule spanned by `\{\sigma e_i | \sigma \in S_n\}`. + The change-of-basis from the standard basis `\{\sigma\}_\sigma` is returned. + INPUT: - ``mult`` -- string (default: `l2r`). If set to `r2l`, @@ -2000,15 +2005,32 @@ def dft(self, form="seminormal", mult='l2r'): [ 0 1 0 -1 1 -1] [ 1 -1/2 1 -1/2 -1/2 -1/2] [ 1 -1 -1 1 1 -1] + + Over fields of characteristic `p > 0` such that `p \mid n!`, we use the + modular Fourier transform (:issue:`37751`):: + + sage: GF2S3 = SymmetricGroupAlgebra(GF(2), 3) + sage: GF2S3.dft() + [1 0 0 0 1 0] + [0 1 0 0 0 1] + [0 0 1 0 0 1] + [0 0 0 1 1 0] + [1 0 0 1 1 0] + [0 1 1 0 0 1] """ + if form is None: + form = "modular" if self.base_ring().characteristic().divides(self.group().cardinality()) else "seminormal" if form == "seminormal": + if self.base_ring().characteristic().divides(self.group().cardinality()): + raise ValueError("seminormal does not work when p | n!") return self._dft_seminormal(mult=mult) - else: - raise ValueError("invalid form (= %s)" % form) + if form == "modular": + return self._dft_modular() + raise ValueError("invalid form (= %s)" % form) def _dft_seminormal(self, mult='l2r'): """ - Return the seminormal form of the discrete Fourier for ``self``. + Return the seminormal form of the discrete Fourier transform for ``self``. INPUT: @@ -2035,6 +2057,32 @@ def _dft_seminormal(self, mult='l2r'): snb = self.seminormal_basis(mult=mult) return matrix([vector(b) for b in snb]).inverse().transpose() + def _dft_modular(self): + r""" + Return the discrete Fourier transform when the characteristic divides the order of the group. + + EXAMPLES:: + + sage: GF3S3 = SymmetricGroupAlgebra(GF(3), 3) + sage: GF3S3._dft_modular() + [1 0 0 0 0 0] + [0 1 0 0 0 0] + [0 0 1 0 0 0] + [0 0 0 1 0 0] + [0 0 0 0 1 0] + [0 0 0 0 0 1] + """ + idempotents = self.central_orthogonal_idempotents() + # project v onto each block U_i = F_p[S_n]*e_i via \pi_i: v |--> v*e_i + B = self.basis() + blocks = [self.submodule([b * idem for b in B]) for idem in idempotents] + # compute the list of basis vectors lifted to the SGA from each block + block_decomposition_basis = [u.lift() for block in blocks for u in block.basis()] + # construct the matrix to the standard basis in the order given by the group + G = self.group() + mat = [[b[g] for b in block_decomposition_basis] for g in G] + return matrix(self.base_ring(), mat) + def epsilon_ik(self, itab, ktab, star=0, mult='l2r'): r""" Return the seminormal basis element of ``self`` corresponding to the diff --git a/src/sage/combinat/tableau.py b/src/sage/combinat/tableau.py index bb1aeaf28be..fda9458f0f2 100644 --- a/src/sage/combinat/tableau.py +++ b/src/sage/combinat/tableau.py @@ -1381,7 +1381,7 @@ def schuetzenberger_involution(self, n=None, check=True): t = t.bump(k) if isinstance(self, StandardTableau): return StandardTableau(list(t)) - elif isinstance(self, SemistandardTableau): + if isinstance(self, SemistandardTableau): return SemistandardTableau(list(t)) return t @@ -1910,7 +1910,7 @@ def vertical_flip(self): if not self.is_rectangular(): raise TypeError("the tableau must be rectangular to use vertical_flip()") - return Tableau([row for row in reversed(self)]) + return Tableau(list(reversed(self))) def rotate_180(self): """ @@ -1926,7 +1926,7 @@ def rotate_180(self): if not self.is_rectangular(): raise TypeError("the tableau must be rectangular to use rotate_180()") - return Tableau([[rline for rline in reversed(row)] for row in reversed(self)]) + return Tableau([list(reversed(row)) for row in reversed(self)]) def cells(self): """ @@ -2447,7 +2447,7 @@ def insert_word(self, w, left=False): [[1, 3], [2, 5], [4]] """ if left: - w = [i for i in reversed(w)] + w = list(reversed(w)) res = self for i in w: res = res.schensted_insert(i, left=left) @@ -3891,7 +3891,7 @@ def flush(self): for s in S: if (s[0][0] != len(self)-1 and s[1] == len(self[s[0][0]+1]) and self[s[0][0]+1][-1] <= s[0][1]) \ - or (s[0][0] == len(self)-1 and s[1] == 0): + or (s[0][0] == len(self)-1 and s[1] == 0): f += 1 else: for t in S: @@ -4491,7 +4491,7 @@ def __classcall_private__(self, t): """ if isinstance(t, SemistandardTableau): return t - elif t in SemistandardTableaux(): + if t in SemistandardTableaux(): return SemistandardTableaux_all().element_class(SemistandardTableaux_all(), t) # t is not a semistandard tableau so we give an appropriate error message @@ -4676,9 +4676,9 @@ def check(self): super().check() # We have checked that t is tableau, so it remains to check that # the entries of t are positive integers that increase along rows. - flatx = sorted(sum((list(row) for row in self), [])) + flatx = sorted(c for row in self for c in row) if (flatx != list(range(1, len(flatx)+1)) - or any(row[i] >= row[i+1] for row in self for i in range(len(row)-1))): + or any(row[i] >= row[i+1] for row in self for i in range(len(row)-1))): raise ValueError("the entries in a row standard tableau must increase" " along rows and contain the numbers 1,2,...,n") @@ -5657,13 +5657,13 @@ class options(GlobalOptions): alias=dict(array="diagram", ferrers_diagram="diagram", young_diagram="diagram"), case_sensitive=False) convention = dict(default="English", - description='Sets the convention used for displaying tableaux and partitions', - values=dict( - English='use the English convention', - French='use the French convention', - Russian='use the Russian convention', - ), - case_sensitive=False) + description='Sets the convention used for displaying tableaux and partitions', + values=dict( + English='use the English convention', + French='use the French convention', + Russian='use the Russian convention', + ), + case_sensitive=False) notation = dict(alt_name="convention") def _element_constructor_(self, t): @@ -5726,7 +5726,7 @@ def __contains__(self, x): from sage.combinat.partition import _Partitions if isinstance(x, Tableau): return True - elif isinstance(x, list): + if isinstance(x, list): try: for row in x: iter(row) @@ -6062,7 +6062,7 @@ def __classcall_private__(cls, *args, **kwargs): if size is not None: if not isinstance(size, (int, Integer)): raise ValueError("size must be an integer") - elif size < 0: + if size < 0: raise ValueError("size must be non-negative") if shape is not None: @@ -6269,9 +6269,9 @@ def __contains__(self, t): return (self.max_entry is None or len(t) == 0 or max(max(row) for row in t) <= self.max_entry) - elif not t: + if not t: return True - elif Tableaux.__contains__(self, t): + if Tableaux.__contains__(self, t): for row in t: if not all(c > 0 for c in row): return False @@ -6648,7 +6648,7 @@ def random_element(self): tot += weights[pos] # we now have pos elements over the diagonal and n - 2 * pos on it m = diagonal_matrix(list(IntegerVectors(self.size - 2 * pos, - self.max_entry).random_element())) + self.max_entry).random_element())) above_diagonal = list(IntegerVectors(pos, kchoose2m1 + 1).random_element()) index = 0 for i in range(self.max_entry - 1): @@ -7309,10 +7309,10 @@ def __contains__(self, x): """ if isinstance(x, RowStandardTableau): return True - elif Tableaux.__contains__(self, x): - flatx = sorted(sum((list(row) for row in x), [])) + if Tableaux.__contains__(self, x): + flatx = sorted(c for row in x for c in row) return (flatx == list(range(1, len(flatx)+1)) - and all(row[i] < row[i+1] for row in x for i in range(len(row)-1))) + and all(row[i] < row[i+1] for row in x for i in range(len(row)-1))) return False @@ -7561,7 +7561,7 @@ def cardinality(self): sage: RowStandardTableaux([]).cardinality() 1 """ - return Integer(multinomial([m for m in self.shape])) + return Integer(multinomial(list(self.shape))) ######################## @@ -7710,12 +7710,12 @@ def __contains__(self, x): if isinstance(x, StandardTableau): return True elif Tableaux.__contains__(self, x): - flatx = sorted(sum((list(row) for row in x), [])) + flatx = sorted(c for row in x for c in row) return flatx == list(range(1, len(flatx)+1)) and (len(x) == 0 or (all(row[i] < row[i+1] for row in x for i in range(len(row)-1)) and - all(x[r][c] < x[r+1][c] for r in range(len(x)-1) - for c in range(len(x[r+1]))) - )) + all(x[r][c] < x[r+1][c] for r in range(len(x)-1) + for c in range(len(x[r+1]))) + )) return False @@ -7936,7 +7936,7 @@ def random_element(self): # We add the number of involutions with ``fixed_point_number`` # fixed points. partial_sum += binomial(self.size, fixed_point_number) * \ - prod(range(1, self.size - fixed_point_number, 2)) + prod(range(1, self.size - fixed_point_number, 2)) # If the partial sum is greater than the involution index, # then the random involution that we want to generate has # ``fixed_point_number`` fixed points. @@ -7954,7 +7954,7 @@ def random_element(self): matching = PerfectMatchings(set(range(1, self.size + 1)) - set(fixed_point_positions)).random_element() permutation_cycle_rep = ([(fixed_point,) for fixed_point in fixed_point_positions] - + [(a, b) for a, b in matching]) + + [tuple(ab) for ab in matching]) return from_cycles(self.size, permutation_cycle_rep).robinson_schensted()[0] @@ -8198,7 +8198,7 @@ def list(self): [[1, 2, 4], [3, 5], [6]], [[1, 2, 3], [4, 5], [6]]] """ - return [y for y in self] + return list(self) def random_element(self): """ diff --git a/src/sage/combinat/tableau_residues.py b/src/sage/combinat/tableau_residues.py index ae3f65e7196..db94461f6ef 100644 --- a/src/sage/combinat/tableau_residues.py +++ b/src/sage/combinat/tableau_residues.py @@ -340,7 +340,7 @@ def __getitem__(self, k): except (IndexError, KeyError): raise IndexError('k must be in the range 1, 2, ..., {}'.format(len(self))) - def residues(self): + def residues(self) -> list: r""" Return a list of the residue sequence. @@ -350,7 +350,7 @@ def residues(self): sage: ResidueSequence(3,(0,0,1),[0,0,1,1,2,2,3,3]).residues() [0, 0, 1, 1, 2, 2, 0, 0] """ - return [r for r in self] + return list(self) def restrict(self, m): r""" diff --git a/src/sage/combinat/tableau_tuple.py b/src/sage/combinat/tableau_tuple.py index 1568175325e..511b98d3e1b 100644 --- a/src/sage/combinat/tableau_tuple.py +++ b/src/sage/combinat/tableau_tuple.py @@ -597,7 +597,7 @@ def components(self): 6 7 8 9 """ - return [t for t in self] + return list(self) def to_list(self): """ @@ -2371,9 +2371,8 @@ def list(self): ([[3, 5], [4]], [[1, 2]])] """ if self.is_finite(): - return [y for y in self] - else: - raise NotImplementedError('this is an infinite set of tableaux') + return list(self) + raise NotImplementedError('this is an infinite set of tableaux') class TableauTuples_all(TableauTuples): @@ -2666,7 +2665,7 @@ def an_element(self): if self.size() == 0: return self.element_class(self, [[] for _ in range(self.level())]) - tab = [[[m for m in range(1, self.size() + 1)]]] + tab = [[list(range(1, self.size() + 1))]] for _ in range(self.level() - 1): tab.append([]) return self.element_class(self, tab) diff --git a/src/sage/combinat/tamari_lattices.py b/src/sage/combinat/tamari_lattices.py index b84b587e1b3..f3e4de13a28 100644 --- a/src/sage/combinat/tamari_lattices.py +++ b/src/sage/combinat/tamari_lattices.py @@ -92,11 +92,11 @@ def paths_in_triangle(i, j, a, b) -> list[tuple[int, ...]]: return [tuple([1] * j)] if (j - 1) * a >= (i) * b: - result = [u + tuple([1]) for u in paths_in_triangle(i, j - 1, a, b)] - result += [u + tuple([0]) for u in paths_in_triangle(i - 1, j, a, b)] + result = [u + (1,) for u in paths_in_triangle(i, j - 1, a, b)] + result += [u + (0,) for u in paths_in_triangle(i - 1, j, a, b)] return result - return [u + tuple([0]) for u in paths_in_triangle(i - 1, j, a, b)] + return [u + (0,) for u in paths_in_triangle(i - 1, j, a, b)] def swap(p, i, m=1) -> tuple[int, ...]: @@ -156,7 +156,7 @@ def swap(p, i, m=1) -> tuple[int, ...]: height -= 1 if height <= 0: found = True - q = [k for k in p] + q = list(p) for k in range(i, j): q[k] = p[k + 1] q[j] = 0 diff --git a/src/sage/combinat/tiling.py b/src/sage/combinat/tiling.py index a6284b83e1b..43c7817f13a 100644 --- a/src/sage/combinat/tiling.py +++ b/src/sage/combinat/tiling.py @@ -991,12 +991,13 @@ def canonical_isometric_copies(self, orientation_preserving=True, """ if mod_box_isometries: L = ncube_isometry_group_cosets(self._dimension, orientation_preserving) - P_cosets = set(frozenset((m * self).canonical() for m in coset) for coset in L) + P_cosets = {frozenset((m * self).canonical() for m in coset) + for coset in L} P_cosets_representents = [min(s, key=lambda a: a.sorted_list()) for s in P_cosets] - return sorted(P_cosets_representents, key=lambda a:a.sorted_list()) + return sorted(P_cosets_representents, key=lambda a: a.sorted_list()) else: L = ncube_isometry_group(self._dimension, orientation_preserving) - P_images = set((m * self).canonical() for m in L) + P_images = {(m * self).canonical() for m in L} return sorted(P_images, key=lambda a: a.sorted_list()) def translated_copies(self, box): @@ -1222,8 +1223,8 @@ def isometric_copies(self, box, orientation_preserving=True, raise ValueError("Dimension of input box must match the " "dimension of the polyomino") box_min_coords, box_max_coords = box.bounding_box() - if mod_box_isometries and len(set(b-a for (a,b) in zip(box_min_coords, - box_max_coords))) < box._dimension: + if mod_box_isometries and len({b - a for a, b in zip(box_min_coords, + box_max_coords)}) < box._dimension: raise NotImplementedError("The code below assumes that the" " sizes of the box (={}) are all distinct when" " argument `mod_box_isometries` is True.".format(box)) @@ -1267,8 +1268,8 @@ def isometric_copies_intersection(self, box, orientation_preserving=True): """ all_distinct_cano = self.canonical_isometric_copies(orientation_preserving, mod_box_isometries=False) - return set([t for cano in all_distinct_cano - for t in cano.translated_copies_intersection(box=box)]) + return {t for cano in all_distinct_cano + for t in cano.translated_copies_intersection(box=box)} def neighbor_edges(self): r""" @@ -1766,11 +1767,10 @@ def coord_to_int_dict(self): ((2, 0), 4), ((2, 1), 5)] """ if self._reusable: - return dict((c, i) for i, c in enumerate(self.space())) - else: - number_of_pieces = len(self._pieces) - return dict((c, i+number_of_pieces) - for i, c in enumerate(self.space())) + return {c: i for i, c in enumerate(self.space())} + + number_of_pieces = len(self._pieces) + return {c: i + number_of_pieces for i, c in enumerate(self.space())} @cached_method def int_to_coord_dict(self): @@ -1811,14 +1811,10 @@ def int_to_coord_dict(self): True sage: all(B[A[i]] == i for i in A) True - """ if self._reusable: - return dict((i, c) for i, c in enumerate(self.space())) - else: - number_of_pieces = len(self._pieces) - return dict((i+number_of_pieces, c) - for i, c in enumerate(self.space())) + return dict(enumerate(self.space())) + return dict(enumerate(self.space(), start=len(self._pieces))) @cached_method def rows_for_piece(self, i, mod_box_isometries=False): diff --git a/src/sage/combinat/tuple.py b/src/sage/combinat/tuple.py index 54d1caf9181..196d869769d 100644 --- a/src/sage/combinat/tuple.py +++ b/src/sage/combinat/tuple.py @@ -77,7 +77,7 @@ def __init__(self, S, k): """ self.S = S self.k = k - self._index_list = list(set(S.index(s) for s in S)) + self._index_list = list({S.index(s) for s in S}) category = FiniteEnumeratedSets() Parent.__init__(self, category=category) @@ -168,7 +168,7 @@ def __init__(self, S, k): """ self.S = S self.k = k - self._index_list = list(set(S.index(s) for s in S)) + self._index_list = list({S.index(s) for s in S}) category = FiniteEnumeratedSets() Parent.__init__(self, category=category) diff --git a/src/sage/combinat/words/finite_word.py b/src/sage/combinat/words/finite_word.py index b596f87e56d..1bdd38e92c7 100644 --- a/src/sage/combinat/words/finite_word.py +++ b/src/sage/combinat/words/finite_word.py @@ -1426,7 +1426,7 @@ def factor_set(self, n=None, algorithm='suffix tree'): return Set(self.factor_iterator(n)) elif algorithm == 'naive': if n is None: - S = set([self[0:0]]) + S = {self[0:0]} for n in range(1, self.length()+1): for i in range(self.length()-n+1): S.add(self[i:i+n]) @@ -3006,7 +3006,7 @@ def palindromes(self, f=None): [word: , word: ab, word: abbabaab, word: ba, word: baba, word: bbabaa] """ LPS = self.lps_lengths(f) - return set(self[i - LPS[i]: i] for i in range(len(self) + 1)) + return {self[i - LPS[i]: i] for i in range(len(self) + 1)} def palindromic_complexity(self, n): r""" @@ -6415,7 +6415,7 @@ def delta(self): return Words()([]) ss = self[0] c = 0 - v = list() + v = [] max_c = 0 for s in self: if s == ss: diff --git a/src/sage/combinat/words/morphism.py b/src/sage/combinat/words/morphism.py index f490ac96cd0..9424a3370b7 100644 --- a/src/sage/combinat/words/morphism.py +++ b/src/sage/combinat/words/morphism.py @@ -401,7 +401,7 @@ def __init__(self, data, domain=None, codomain=None): self._morph = {} - dom_alph = list() + dom_alph = [] for key, val in data.items(): dom_alph.append(key) if val in codomain.alphabet(): @@ -950,7 +950,8 @@ def __mul__(self, other): sage: m * WordMorphism('') WordMorphism: """ - return WordMorphism(dict((key, self(w)) for key, w in other._morph.items()), codomain=self.codomain()) + return WordMorphism({key: self(w) for key, w in other._morph.items()}, + codomain=self.codomain()) def __pow__(self, exp): r""" @@ -1090,7 +1091,8 @@ def restrict_domain(self, alphabet): ... TypeError: 'sage.rings.integer.Integer' object is not iterable """ - return WordMorphism(dict((a, self(a)) for a in alphabet if a in self.domain().alphabet())) + return WordMorphism({a: self(a) for a in alphabet + if a in self.domain().alphabet()}) def _matrix_(self, R=None): r""" @@ -1276,7 +1278,7 @@ def image(self, letter): """ return self._morph[letter] - def images(self): + def images(self) -> list: r""" Return the list of all the images of the letters of the alphabet under ``self``. @@ -1698,7 +1700,7 @@ def is_prolongable(self, letter): def is_uniform(self, k=None): r""" - Return True if self is a `k`-uniform morphism. + Return ``True`` if ``self`` is a `k`-uniform morphism. Let `k` be a positive integer. A morphism `\phi` is called `k`-uniform if for every letter `\alpha`, we have `|\phi(\alpha)| = k`. In other @@ -1707,9 +1709,10 @@ def is_uniform(self, k=None): INPUT: - - ``k`` - a positive integer or None. If set to a positive integer, - then the function return True if self is `k`-uniform. If set to - None, then the function return True if self is uniform. + - ``k`` - a positive integer or ``None``. If set to a positive integer, + then the function return ``True`` if ``self`` is `k`-uniform. + If set to ``None``, then the function return ``True`` if ``self`` + is uniform. EXAMPLES:: @@ -1725,11 +1728,19 @@ def is_uniform(self, k=None): False sage: tau.is_uniform(k=2) True + + TESTS:: + + sage: phi = WordMorphism('') + sage: phi.is_uniform() + True """ if k is None: - return len(set(w.length() for w in self.images())) == 1 - else: - return all(w.length() == k for w in self.images()) + try: + k = self.images()[0].length() + except IndexError: + return True + return all(w.length() == k for w in self.images()) def fixed_point(self, letter): r""" @@ -1980,7 +1991,7 @@ def periodic_points(self): raise NotImplementedError("f should be non erasing") A = self.domain().alphabet() - d = dict((letter, self(letter)[0]) for letter in A) + d = {letter: self(letter)[0] for letter in A} G = set(self.growing_letters()) res = [] @@ -2173,7 +2184,8 @@ def conjugate(self, pos): sage: m.conjugate(2) WordMorphism: a->cdeab, b->zxy """ - return WordMorphism(dict((key, w.conjugate(pos)) for (key, w) in self._morph.items())) + return WordMorphism({key: w.conjugate(pos) + for (key, w) in self._morph.items()}) def has_left_conjugate(self): r""" @@ -3785,7 +3797,7 @@ def try_create_h(f, k): h = {letter: [letter] if image else [] for letter, image in f.items()} elif len(Y) < len(X): # Trivial case #2. k = {x: [y] for x, y in zip(X, Y)} - k_inverse = {y: x for y, x in zip(Y, X)} + k_inverse = dict(zip(Y, X)) h = {x: [k_inverse[y] for y in image] for x, image in f.items()} elif not self.is_injective(): # Non-trivial but a fast case. k = dict(f) @@ -3822,7 +3834,7 @@ def try_create_h(f, k): for comb in combinations(factors, len(X) - 1): if any(x.is_proper_prefix(y) for x in comb for y in comb): continue - k = {x: image for x, image in zip(X, comb)} + k = dict(zip(X, comb)) h = try_create_h(f, k) if h: break diff --git a/src/sage/combinat/words/suffix_trees.py b/src/sage/combinat/words/suffix_trees.py index 8375bde1a8a..8c56ba3d4b3 100644 --- a/src/sage/combinat/words/suffix_trees.py +++ b/src/sage/combinat/words/suffix_trees.py @@ -136,7 +136,7 @@ def _process_letter(self, letter): self._suffix_link[old_s] = self._transition_function[(r, letter)] # update the active state self._active_state = \ - self._transition_function[(self._active_state, letter)] + self._transition_function[(self._active_state, letter)] def process_letter(self, letter): r""" @@ -177,7 +177,7 @@ def process_letter(self, letter): # functionality to the class. ##### - def _repr_(self): + def _repr_(self) -> str: """ TESTS:: @@ -189,12 +189,12 @@ def _repr_(self): def node_to_word(self, state=0): r""" - Returns the word obtained by reading the edge labels from 0 to + Return the word obtained by reading the edge labels from 0 to ``state``. INPUT: - - ``state`` - (default: 0) a state + - ``state`` -- (default: 0) a state EXAMPLES:: @@ -213,18 +213,18 @@ def node_to_word(self, state=0): # Starting from the active state, # read labels along the unique path to the root. - (u,letter) = tf_inv[state] + u, letter = tf_inv[state] w = letter s = u while s != 0: - (u,letter) = tf_inv[s] + u, letter = tf_inv[s] w = letter * w s = u return w def word(self): r""" - Returns the word whose suffix tree this is. + Return the word whose suffix tree this is. EXAMPLES:: @@ -238,10 +238,10 @@ def word(self): """ return self.node_to_word(self._active_state) - def __eq__(self,other): + def __eq__(self, other) -> bool: r""" - If self and other have the same transition function, the same - suffix link, and the same word, then they are equal. + If ``self`` and ``other`` have the same transition function, + the same suffix link, and the same word, then they are equal. TESTS:: @@ -259,7 +259,7 @@ def __eq__(self,other): sage: t == s True """ - if not isinstance(other,SuffixTrie): + if not isinstance(other, SuffixTrie): return False return self._transition_function == other._transition_function \ and self._suffix_link == other._suffix_link \ @@ -267,13 +267,13 @@ def __eq__(self,other): def transition_function(self, node, word): r""" - Returns the state reached by beginning at ``node`` and following the + Return the state reached by beginning at ``node`` and following the arrows in the transition graph labelled by the letters of ``word``. INPUT: - - ``node`` - a node - - ``word`` - a word + - ``node`` -- a node + - ``word`` -- a word EXAMPLES:: @@ -281,7 +281,7 @@ def transition_function(self, node, word): sage: w = Words([0,1])([0,1,0,1,1]) sage: t = SuffixTrie(w) sage: all(t.transition_function(u, letter) == v - ....: for ((u, letter), v) in t._transition_function.items()) + ....: for (u, letter), v in t._transition_function.items()) True """ if node == -1: @@ -316,7 +316,8 @@ def states(self): def suffix_link(self, state): r""" - Evaluates the suffix link map of the suffix trie on ``state``. + Evaluate the suffix link map of the suffix trie on ``state``. + Note that the suffix link map is not defined on -1. INPUT: @@ -351,7 +352,7 @@ def suffix_link(self, state): ... TypeError: 17 is not a state """ - if not isinstance(state, (int,Integer)): + if not isinstance(state, (int, Integer)): raise TypeError("%s is not an integer" % state) if state == -1: raise TypeError("suffix link is not defined for -1") @@ -361,8 +362,10 @@ def suffix_link(self, state): def active_state(self): r""" - Returns the active state of the suffix trie. This is the state - corresponding to the word as a suffix of itself. + Return the active state of the suffix trie. + + This is the state corresponding to the word as a suffix of + itself. EXAMPLES:: @@ -383,10 +386,11 @@ def active_state(self): def final_states(self): r""" - Returns the set of final states of the suffix trie. These are the - states corresponding to the suffixes of ``self.word()``. They are - obtained be repeatedly following the suffix link from the active - state until we reach 0. + Return the set of final states of the suffix trie. + + These are the states corresponding to the suffixes of + ``self.word()``. They are obtained be repeatedly following the + suffix link from the active state until we reach 0. EXAMPLES:: @@ -403,7 +407,7 @@ def final_states(self): F.append(s) return Set(F) - def has_suffix(self,word): + def has_suffix(self, word) -> bool: r""" Return ``True`` if and only if ``word`` is a suffix of ``self.word()``. @@ -423,16 +427,16 @@ def has_suffix(self,word): q = self._active_state if q == s: return True - else: - while q != 0: - q = self._suffix_link[q] - if q == s: - return True + + while q != 0: + q = self._suffix_link[q] + if q == s: + return True return False def to_digraph(self): r""" - Returns a ``DiGraph`` object of the transition graph of the suffix + Return a ``DiGraph`` object of the transition graph of the suffix trie. EXAMPLES:: @@ -451,14 +455,14 @@ def to_digraph(self): [0 0 0 0 0 0] """ dag = {} - for ((u, letter), v) in self._transition_function.items(): + for (u, letter), v in self._transition_function.items(): dag.setdefault(u, {})[v] = letter return DiGraph(dag) def plot(self, layout='tree', tree_root=0, tree_orientation='up', - vertex_colors=None, edge_labels=True, *args, **kwds): + vertex_colors=None, edge_labels=True, *args, **kwds): r""" - Returns a Graphics object corresponding to the transition graph of + Return a Graphics object corresponding to the transition graph of the suffix trie. EXAMPLES:: @@ -474,20 +478,20 @@ def plot(self, layout='tree', tree_root=0, tree_orientation='up', """ tree = self.to_digraph() - for (u,v,label) in tree.edge_iterator(): + for u, v, label in tree.edge_iterator(): tree.set_edge_label(u, v, label.string_rep()) if vertex_colors is None: suffix_nodes = self.final_states() non_suffix_nodes = list(set(self.states()) - set(suffix_nodes)) - vertex_colors = {'#fec7b8':suffix_nodes,'#ffffff':non_suffix_nodes} + vertex_colors = {'#fec7b8': suffix_nodes, '#ffffff': non_suffix_nodes} return tree.plot(layout=layout, tree_root=tree_root, - tree_orientation=tree_orientation, - vertex_colors=vertex_colors, edge_labels=edge_labels, - *args, **kwds) + tree_orientation=tree_orientation, + vertex_colors=vertex_colors, edge_labels=edge_labels, + *args, **kwds) def show(self, *args, **kwds): r""" - Displays the output of ``self.plot()``. + Display the output of :meth:`plot`. EXAMPLES:: @@ -581,9 +585,9 @@ def __init__(self, word): Implicit Suffix Tree of the word: cacao """ # For constructing the suffix tree. - self._transition_function = {0:{}} - self._suffix_link = {0:-1} - self._active_state = (0,(1,1)) + self._transition_function = {0: {}} + self._suffix_link = {0: -1} + self._active_state = (0, (1, 1)) self._letters = [] for letter in word: self._letters.append(letter) @@ -631,34 +635,38 @@ def _process_letter(self, letter): sage: s Implicit Suffix Tree of the word: 01011 """ - (s,(k,i)) = self._active_state + s, (k, i) = self._active_state old_r = 0 - (end_state, r) = self._test_and_split(s,(k,i-1),letter) + end_state, r = self._test_and_split(s, (k, i-1), letter) while not end_state: # adjoin a new state rr and create a transition from r to rr rr = len(self._transition_function) self._transition_function[rr] = {} - self._transition_function[r][(i,None)] = rr + self._transition_function[r][(i, None)] = rr # update the suffix link, if necessary if old_r != 0: self._suffix_link[old_r] = r old_r = r # follow the suffix link to the next state - (s, k) = self._canonize(self._suffix_link[s], (k,i-1)) - (end_state, r) = self._test_and_split(s, (k,i-1), letter) + s, k = self._canonize(self._suffix_link[s], (k, i-1)) + end_state, r = self._test_and_split(s, (k, i-1), letter) # update the suffix link, if necessary if old_r != 0: self._suffix_link[old_r] = s # set the active state - (s,k) = self._canonize(s,(k,i)) + s, k = self._canonize(s, (k, i)) self._active_state = (s, (k, i+1)) return def _test_and_split(self, s, k_p, letter): r""" - Helper function for _process_letter. Tests to see whether an edge - needs to be split. Returns ``(True, state)``, where ``state`` is the - next state to process (either a newly created state or the original s). + Helper function for :meth:`_process_letter`. Test to see + whether an edge needs to be split. + + OUTPUT: + + ``(True, state)``, where ``state`` is the next state to + process (either a newly created state or the original ``s``). TESTS:: @@ -669,15 +677,15 @@ def _test_and_split(self, s, k_p, letter): sage: t._test_and_split(0, (4,5), w.parent().alphabet().rank("o")) (False, 3) """ - (k, p) = k_p + k, p = k_p if k <= p: # find the transition from s that begins with k-th letter - ((kk,pp), ss) = self._find_transition(s, self._letters[k-1]) + (kk, pp), ss = self._find_transition(s, self._letters[k - 1]) if letter == self._letters[kk + p - k]: return (True, s) else: # replace transition above by transitions - del self._transition_function[s][(kk,pp)] + del self._transition_function[s][(kk, pp)] r = len(self._transition_function) self._transition_function[r] = {} self._transition_function[s][(kk, kk+p-k)] = r @@ -692,7 +700,7 @@ def _test_and_split(self, s, k_p, letter): def _canonize(self, s, k_p): r""" - Given an implicit or explicit reference pair for a node, returns + Given an implicit or explicit reference pair for a node, return the canonical reference pair. Recall that a node r is referenced as (s, (k,p)), where s is an @@ -709,22 +717,23 @@ def _canonize(self, s, k_p): sage: t._canonize(0,(2,5)) (5, 3) """ - (k, p) = k_p + k, p = k_p if p < k: return (s, k) else: - ((kk,pp), ss) = self._find_transition(s, self._letters[k-1]) + (kk, pp), ss = self._find_transition(s, self._letters[k - 1]) while pp is not None and pp - kk <= p - k: k = k + pp - kk + 1 s = ss if k <= p: - ((kk,pp), ss) = self._find_transition(s, self._letters[k-1]) + (kk, pp), ss = self._find_transition(s, self._letters[k-1]) return (s, k) def _find_transition(self, state, letter): r""" - Returns the transition from state that begins with letter. Returns - ``None`` if no such transition exists. + Return the transition from state that begins with letter. + + This returns ``None`` if no such transition exists. The transitions are stored as a dictionary of dictionaries: keyed by the nodes, with the corresponding dictionary keyed by pairs @@ -754,12 +763,12 @@ def _find_transition(self, state, letter): """ if state == -1: return ((0, 0), 0) - else: - if state in self._transition_function: - for ((k,p),s) in self._transition_function[state].items(): - if self._letters[k-1] == letter: - return ((k,p), s) - return None + + if state in self._transition_function: + for (k, p), s in self._transition_function[state].items(): + if self._letters[k - 1] == letter: + return ((k, p), s) + return None ##### # The following are not necessary for constructing the implicit suffix @@ -770,7 +779,7 @@ def _find_transition(self, state, letter): # Visualization ##### - def _repr_(self): + def _repr_(self) -> str: r""" TESTS:: @@ -782,7 +791,7 @@ def _repr_(self): def word(self): r""" - Returns the word whose implicit suffix tree this is. + Return the word whose implicit suffix tree this is. TESTS:: @@ -792,9 +801,10 @@ def word(self): """ return self._word - def transition_function_dictionary(self): + def transition_function_dictionary(self) -> dict: r""" - Returns the transition function as a dictionary of dictionaries. + Return the transition function as a dictionary of dictionaries. + The format is consistent with the input format for ``DiGraph``. EXAMPLES:: @@ -813,13 +823,13 @@ def transition_function_dictionary(self): {0: {1: (0, None), 2: (1, None)}} """ d = {} - for (u,v,(i,j)) in self.edge_iterator(): - d.setdefault(u, {})[v] = (i,j) + for u, v, (i, j) in self.edge_iterator(): + d.setdefault(u, {})[v] = (i, j) return d def to_digraph(self, word_labels=False): r""" - Returns a ``DiGraph`` object of the transition graph of the suffix tree. + Return a ``DiGraph`` object of the transition graph of the suffix tree. INPUT: @@ -840,18 +850,18 @@ def to_digraph(self, word_labels=False): return DiGraph(d) d = self.transition_function_dictionary() for u in d: - for (v, (i, j)) in d[u].items(): + for v, (i, j) in d[u].items(): if word_labels: d[u][v] = self._word[i:j] elif j is None: - d[u][v] = (i,len(self._letters)) + d[u][v] = (i, len(self._letters)) return DiGraph(d) def plot(self, word_labels=False, layout='tree', tree_root=0, - tree_orientation='up', vertex_colors=None, edge_labels=True, - *args, **kwds): + tree_orientation='up', vertex_colors=None, edge_labels=True, + *args, **kwds): r""" - Returns a Graphics object corresponding to the transition graph of + Return a Graphics object corresponding to the transition graph of the suffix tree. INPUT: @@ -883,18 +893,18 @@ def plot(self, word_labels=False, layout='tree', tree_root=0, """ tree = self.to_digraph(word_labels=word_labels) if word_labels: - for (u,v,label) in tree.edge_iterator(): + for u, v, label in tree.edge_iterator(): tree.set_edge_label(u, v, label.string_rep()) if vertex_colors is None: - vertex_colors = {'#fec7b8':tree.vertices(sort=True)} + vertex_colors = {'#fec7b8': tree.vertices(sort=True)} return tree.plot(layout=layout, tree_root=tree_root, - tree_orientation=tree_orientation, - vertex_colors=vertex_colors, edge_labels=edge_labels, - *args, **kwds) + tree_orientation=tree_orientation, + vertex_colors=vertex_colors, edge_labels=edge_labels, + *args, **kwds) def show(self, word_labels=None, *args, **kwds): r""" - Displays the output of ``self.plot()``. + Display the output of :meth:`plot`. INPUT: @@ -917,10 +927,10 @@ def show(self, word_labels=None, *args, **kwds): # Various methods ##### - def __eq__(self,other): + def __eq__(self, other) -> bool: r""" - If self and other have the same transition function and the - same word, then they are equal. + If ``self`` and ``other`` have the same transition function + and the same word, then they are equal. TESTS:: @@ -930,22 +940,25 @@ def __eq__(self,other): sage: ImplicitSuffixTree(w) == ImplicitSuffixTree(u) True """ - if not isinstance(other,ImplicitSuffixTree): + if not isinstance(other, ImplicitSuffixTree): return False return self._transition_function == other._transition_function \ and self._letters == other._letters def transition_function(self, word, node=0): r""" - Returns the node obtained by starting from ``node`` and following the - edges labelled by the letters of ``word``. Returns ``("explicit", - end_node)`` if we end at ``end_node``, or ``("implicit", edge, d)`` - if we end `d` spots along an edge. + Return the node obtained by starting from ``node`` and following the + edges labelled by the letters of ``word``. + + OUTPUT: + + ``("explicit", end_node)`` if we end at ``end_node``, or + ``("implicit", edge, d)`` if we end `d` spots along an edge. INPUT: - - ``word`` - a word - - ``node`` - (default: 0) starting node + - ``word`` -- a word + - ``node`` -- (default: 0) starting node EXAMPLES:: @@ -963,31 +976,31 @@ def transition_function(self, word, node=0): """ if word.is_empty(): return "explicit", node - ((k,p),s) = self._find_transition(node, word[0]) + (k, p), s = self._find_transition(node, word[0]) if p is None: # test that word is a prefix of self._letters[k-1:] if word == self._word[k-1:(k-1)+word.length()]: if word.length() == len(self._letters) - k + 1: return "explicit", s else: - edge = (node,s) + edge = (node, s) return "implicit", edge, word.length() else: # find longest common prefix - m = min(p-k+1,word.length()) + m = min(p-k+1, word.length()) i = 0 while i < m and self._word[k-1+i] == word[i]: i += 1 if i == p-k+1: - return self.transition_function(word[p-k+1:],s) + return self.transition_function(word[p-k+1:], s) else: - edge = (node,s) + edge = (node, s) return "implicit", edge, i return "explicit", node - def states(self): + def states(self) -> list: r""" - Returns the states (explicit nodes) of the suffix tree. + Return the states (explicit nodes) of the suffix tree. EXAMPLES:: @@ -1001,7 +1014,8 @@ def states(self): def suffix_link(self, state): r""" - Evaluates the suffix link map of the implicit suffix tree on ``state``. + Evaluate the suffix link map of the implicit suffix tree on ``state``. + Note that the suffix link is not defined for all states. The suffix link of a state `x'` that corresponds to the suffix `x` is @@ -1030,12 +1044,11 @@ def suffix_link(self, state): """ if state in self._suffix_link: return self._suffix_link[state] - else: - raise TypeError("there is no suffix link from %s" % state) + raise TypeError("there is no suffix link from %s" % state) def active_state(self): r""" - Returns the active state of the suffix tree. + Return the active state of the suffix tree. EXAMPLES:: @@ -1049,7 +1062,7 @@ def active_state(self): def process_letter(self, letter): r""" - Modifies the current implicit suffix tree producing the implicit + Modify the current implicit suffix tree producing the implicit suffix tree for ``self.word() + letter``. EXAMPLES:: @@ -1075,10 +1088,12 @@ def process_letter(self, letter): def to_explicit_suffix_tree(self): r""" - Converts self to an explicit suffix tree. It is obtained by - processing an end of string letter as if it were a regular - letter, except that no new leaf nodes are created (thus, the only - thing that happens is that some implicit nodes become explicit). + Convert ``self`` to an explicit suffix tree. + + It is obtained by processing an end of string letter as if it + were a regular letter, except that no new leaf nodes are + created (thus, the only thing that happens is that some + implicit nodes become explicit). EXAMPLES:: @@ -1096,20 +1111,21 @@ def to_explicit_suffix_tree(self): # append a new unique symbol to the word and process the new letter end_of_string = object() self._letters.append(end_of_string) - (s,(k,i)) = self._active_state - (end_state, r) = self._test_and_split(s,(k,i-1), end_of_string) + s, (k, i) = self._active_state + end_state, r = self._test_and_split(s, (k, i-1), end_of_string) while not end_state: - (s, k) = self._canonize(self._suffix_link[s], (k,i-1)) - (end_state, r) = self._test_and_split(s, (k,i-1), end_of_string) + s, k = self._canonize(self._suffix_link[s], (k, i-1)) + end_state, r = self._test_and_split(s, (k, i-1), end_of_string) # remove the end of string symbol from the word self._letters.pop() return def edge_iterator(self): r""" - Returns an iterator over the edges of the suffix tree. The - edge from `u` to `v` labelled by `(i,j)` is returned as the tuple - `(u,v,(i,j))`. + Return an iterator over the edges of the suffix tree. + + The edge from `u` to `v` labelled by `(i,j)` is yielded as + the tuple `(u,v,(i,j))`. EXAMPLES:: @@ -1124,11 +1140,11 @@ def edge_iterator(self): queue = [0] while queue: v = queue.pop() - for ((i,j),u) in self._transition_function[v].items(): - yield (v,u,(i-1,j)) + for (i, j), u in self._transition_function[v].items(): + yield (v, u, (i - 1, j)) queue.append(u) - def number_of_factors(self,n=None): + def number_of_factors(self, n=None): r""" Count the number of distinct factors of ``self.word()``. @@ -1189,7 +1205,7 @@ def number_of_factors(self,n=None): if n is None: length_word = self.word().length() num_factors = 1 # empty word - for (u, v, (i, j)) in self.edge_iterator(): + for u, v, (i, j) in self.edge_iterator(): if j is None: num_factors += length_word - i else: @@ -1198,12 +1214,12 @@ def number_of_factors(self,n=None): num_factors = 0 queue = [(0, 0)] while queue: - (v, l) = queue.pop() + v, l = queue.pop() if l == n: num_factors += 1 if l < n: - if self._transition_function[v] != {}: - for ((i, j), u) in self._transition_function[v].items(): + if self._transition_function[v]: + for (i, j), u in self._transition_function[v].items(): if j is None: j = self.word().length() if j - i >= n - l: @@ -1256,27 +1272,27 @@ def factor_iterator(self, n=None): queue = [(0, 0, -1, 0)] yield w[0:0] while queue: - (v,i,j,l) = queue.pop() - for k in range(i,j+1): + v, i, j, l = queue.pop() + for k in range(i, j+1): yield w[j-l:k] - for ((i,j),u) in self._transition_function[v].items(): + for (i, j), u in self._transition_function[v].items(): if j is None: j = wlen - queue.append((u,i,j, l+j-i+1)) + queue.append((u, i, j, l+j-i+1)) elif isinstance(n, (int, Integer)): queue = [(0, 0, -1, 0)] while queue: - (v,i,j,l) = queue.pop() + v, i, j, l = queue.pop() if l == n: yield w[j-l:j] if l < n: - for ((i,j),u) in self._transition_function[v].items(): + for (i, j), u in self._transition_function[v].items(): if j is None: j = wlen if j - i >= n - l: yield w[i-l-1:i-l+n-1] else: - queue.append((u,i,j, l+j-i+1)) + queue.append((u, i, j, l+j-i+1)) else: raise TypeError("not an integer or None: %s" % n) @@ -1319,7 +1335,7 @@ def LZ_decomposition(self): w = self.word() while i < len(w): l = 0 - ((x, y), successor) = self._find_transition(0, w[i]) + (x, y), successor = self._find_transition(0, w[i]) x = x-1 while x < i+l: if y is None: @@ -1329,7 +1345,7 @@ def LZ_decomposition(self): if i+l >= len(w): l = len(w)-i break - ((x, y), successor) = self._find_transition(successor, w[i+l]) + (x, y), successor = self._find_transition(successor, w[i+l]) x = x-1 i += max(1, l) iB.append(i) @@ -1407,14 +1423,15 @@ def suffix_walk(self, edge, l): sage: T.suffix_walk((7, 3), 1) ('implicit', (9, 4), 1) """ + start, end = edge # Select the transition that corresponds to edge - for (i, j) in self._transition_function[edge[0]]: - if self._transition_function[edge[0]][(i, j)] == edge[1]: - break + ij = next(ij for ij, target in self._transition_function[start].items() + if target == end) + # self.word()[i-1:j] is the word on the edges - i -= 1 - parent = self.suffix_link(edge[0]) - return self._count_and_skip(parent, i, i+l) + i = ij[0] - 1 + parent = self.suffix_link(start) + return self._count_and_skip(parent, i, i + l) def leftmost_covering_set(self): r""" @@ -1458,8 +1475,8 @@ def condition1_square_pairs(i): """ for k in range(1, B[i+1]-B[i]+1): q = B[i+1]-k - k1 = w.longest_forward_extension(B[i+1],q) if B[i+1] < len(w) else 0 - k2 = w.longest_backward_extension(B[i+1]-1,q-1) if q > 0 else 0 + k1 = w.longest_forward_extension(B[i+1], q) if B[i+1] < len(w) else 0 + k2 = w.longest_backward_extension(B[i+1]-1, q-1) if q > 0 else 0 start = max(q-k2, q-k+1) if k1+k2 >= k and k1 > 0 and start >= B[i]: yield (start, 2*k) @@ -1481,13 +1498,13 @@ def condition2_square_pairs(i): k2 = w.longest_backward_extension(B[i]-1, q-1) if B[i] > 0 else 0 start = max(B[i]-k2, B[i]-k+1) if k1+k2 >= k and k1 > 0 and start+k <= B[i+1] and k2 > 0: - yield (start,2*k) + yield (start, 2*k) w = self.word() B = self.LZ_decomposition() P = [[] for _ in w] - for i in range(len(B)-1): - for (i,l) in chain(condition2_square_pairs(i), condition1_square_pairs(i)): + for i in range(len(B) - 1): + for i, l in chain(condition2_square_pairs(i), condition1_square_pairs(i)): P[i].append(l) for l in P: l.reverse() @@ -1499,9 +1516,10 @@ def condition2_square_pairs(i): def uncompactify(self): r""" - Returns the tree obtained from self by splitting edges so that they - are labelled by exactly one letter. The resulting tree is - isomorphic to the suffix trie. + Return the tree obtained from ``self`` by splitting edges so that they + are labelled by exactly one letter. + + The resulting tree is isomorphic to the suffix trie. EXAMPLES:: @@ -1516,21 +1534,21 @@ def uncompactify(self): newtree = DiGraph() newtree.add_vertices(range(tree.order())) new_node = tree.order() + 1 - for (u,v,label) in tree.edge_iterator(): + for u, v, label in tree.edge_iterator(): if len(label) == 1: - newtree.add_edge(u,v) + newtree.add_edge(u, v) else: - newtree.add_edge(u,new_node,label[0]) + newtree.add_edge(u, new_node, label[0]) for w in label[1:-1]: - newtree.add_edge(new_node,new_node+1,w) + newtree.add_edge(new_node, new_node+1, w) new_node += 1 - newtree.add_edge(new_node,v,label[-1]) + newtree.add_edge(new_node, v, label[-1]) new_node += 1 return newtree def trie_type_dict(self): r""" - Returns a dictionary in a format compatible with that of the suffix + Return a dictionary in a format compatible with that of the suffix trie transition function. EXAMPLES:: @@ -1546,17 +1564,17 @@ def trie_type_dict(self): """ d = {} new_node = len(self._transition_function) - for (u, dd) in self._transition_function.items(): - for (sl, v) in dd.items(): + for u, dd in self._transition_function.items(): + for sl, v in dd.items(): w = self._word[sl[0]-1:sl[1]] if w.length() == 1: - d[u,w] = v + d[u, w] = v else: - d[u,w[0:1]] = new_node - for i in range(1,w.length()-1): + d[u, w[0:1]] = new_node + for i in range(1, w.length()-1): d[new_node, w[i:i+1]] = new_node + 1 new_node += 1 - d[new_node,w[-1:]] = v + d[new_node, w[-1:]] = v new_node += 1 return d @@ -1639,13 +1657,15 @@ def __repr__(self): """ w = self.word() if len(w) > 40: - w = str(w[:40])+'...' - return "Decorated suffix tree of : {}".format(w) + w = str(w[:40]) + '...' + return f"Decorated suffix tree of : {w}" def _partial_labeling(self): r""" Make a depth-first search in the suffix tree and mark some squares of a - leftmost covering set of the tree. Used by ``self._complete_labeling``. + leftmost covering set of the tree. + + This is used by :meth:`_complete_labeling`. EXAMPLES:: @@ -1671,12 +1691,13 @@ def node_processing(node, parent, head): OUTPUT: ``(i, pos)``, the new head of ``P(node)`` """ i, pos = head + pano = (parent, node) while pos < len(P[i]) and P[i][pos] > string_depth[parent]: label = P[i][pos] - string_depth[parent] - if (parent, node) in labeling: - labeling[(parent, node)].append(label) + if pano in labeling: + labeling[pano].append(label) else: - labeling[(parent, node)] = [label] + labeling[pano] = [label] pos += 1 return (i, pos) @@ -1703,11 +1724,11 @@ def treat_node(current_node, parent): if current_node in D: node_list = (n, 0) for child in D[current_node]: - (i, j) = D[current_node][child] + i, j = D[current_node][child] if j is None: j = n - string_depth[child] = string_depth[current_node]+j-i - child_list = treat_node(child,current_node) + string_depth[child] = string_depth[current_node] + j - i + child_list = treat_node(child, current_node) if child_list[0] < node_list[0]: node_list = child_list else: # The node is a child @@ -1725,7 +1746,7 @@ def treat_node(current_node, parent): def _complete_labeling(self): r""" - Returns a dictionary of edges of ``self``, with marked points for the end + Return a dictionary of edges of ``self``, with marked points for the end of each distinct squares that can be found in ``self.word()``. EXAMPLES:: @@ -1759,7 +1780,7 @@ def walk_chain(u, v, l, start): successful = False if final_state[0] == 'explicit': parent = final_state[1] - transition = self._find_transition(parent,self._letters[start]) + transition = self._find_transition(parent, self._letters[start]) if transition is not None: child = transition[1] successful = True @@ -1782,14 +1803,16 @@ def walk_chain(u, v, l, start): def treat_node(current_node, i, j): r""" - Execute a depth-first search on self and start a suffix walk for - labeled points on each edges of T. The function is recursive, call - treat_node(0,0,0) to initiate the search. + Execute a depth-first search on ``self`` and start a suffix walk + for labeled points on each edges of T. + + The function is recursive, call + ``treat_node(0,0,0)`` to initiate the search. INPUT: - - ``current_node`` - The node to treat - - ``(i, j)`` - Pair of index such that the path from 0 to + - ``current_node`` -- The node to treat + - ``(i, j)`` -- Pair of index such that the path from 0 to ``current_node`` reads ``self.word()[i:j]`` """ @@ -1844,14 +1867,13 @@ def treat_node(current_node, i, j): pair = (square_start, edge_label[0]+l-square_start) squares.append(pair) - if output != "pair" and output != "word": - raise ValueError("``output`` should be 'pair' or 'word'; got {}".format( - output)) + if output not in ["pair", "word"]: + msg = f"output should be 'pair' or 'word'; got {output}" + raise ValueError(msg) D = self.transition_function_dictionary() Q = self.labeling squares = [(0, 0)] treat_node(0, 0, 0) if output == "pair": return squares - else: - return [self.word()[i:i+l] for (i, l) in squares] + return [self.word()[i:i + l] for i, l in squares] diff --git a/src/sage/combinat/yang_baxter_graph.py b/src/sage/combinat/yang_baxter_graph.py index bcfac20fdb3..f2fdb7a5a47 100644 --- a/src/sage/combinat/yang_baxter_graph.py +++ b/src/sage/combinat/yang_baxter_graph.py @@ -335,7 +335,7 @@ def _edges_in_bfs(self): seen[self._root] = True while queue: u = queue.pop() - l = sorted(list(digraph.neighbor_out_iterator(u))) + l = sorted(digraph.neighbor_out_iterator(u)) for w in l: if w not in seen: seen[w] = True @@ -585,7 +585,7 @@ def __init__(self, partition): """ self._partition = partition beta = sorted(self._partition, reverse=True) - root = sum([tuple(range(b)) for b in beta], tuple())[::-1] + root = sum((tuple(range(b)) for b in beta), ())[::-1] operators = [SwapIncreasingOperator(i) for i in range(sum(partition) - 1)] super().__init__(root, operators) diff --git a/src/sage/data_structures/bitset.pyx b/src/sage/data_structures/bitset.pyx index 8a5e91c0a9a..e90ffea8fca 100644 --- a/src/sage/data_structures/bitset.pyx +++ b/src/sage/data_structures/bitset.pyx @@ -72,7 +72,7 @@ cdef class FrozenBitset: - string -- If a nonempty string, then the bitset is initialized by including an element if the index of the string is ``1``. If the - string is empty, then raise a ``ValueError``. + string is empty, then raise a :class:`ValueError`. - iterable -- If an iterable, then it is assumed to contain a list of nonnegative integers and those integers are placed in the set. @@ -1798,8 +1798,10 @@ cdef class Bitset(FrozenBitset): cpdef remove(self, unsigned long n): """ - Update the bitset by removing ``n``. Raises ``KeyError`` if ``n`` is - not contained in the bitset. + Update the bitset by removing ``n``. + + This raises a :class:`KeyError` if ``n`` is not contained + in the bitset. EXAMPLES:: @@ -1871,8 +1873,9 @@ cdef class Bitset(FrozenBitset): cpdef pop(self): """ - Remove and return an arbitrary element from the set. Raises - ``KeyError`` if the set is empty. + Remove and return an arbitrary element from the set. + + This raises a :class:`KeyError` if the set is empty. EXAMPLES:: @@ -1899,7 +1902,7 @@ cdef class Bitset(FrozenBitset): cpdef clear(self): """ - Removes all elements from the bitset. + Remove all elements from the bitset. EXAMPLES:: diff --git a/src/sage/data_structures/bitset_base.pxd b/src/sage/data_structures/bitset_base.pxd index df40d666d86..f8949d05e78 100644 --- a/src/sage/data_structures/bitset_base.pxd +++ b/src/sage/data_structures/bitset_base.pxd @@ -455,7 +455,9 @@ cdef inline bint bitset_not_in(fused_bitset_t bits, mp_bitcnt_t n) noexcept: cdef inline bint bitset_remove(fused_bitset_t bits, mp_bitcnt_t n) except -1: """ - Remove n from bits. Raise KeyError if n is not contained in bits. + Remove ``n`` from ``bits``. + + This raises a :class:`KeyError` if ``n`` is not contained in ``bits``. """ if not bitset_in(bits, n): raise KeyError(n) @@ -558,8 +560,9 @@ cdef inline long bitset_first_in_complement(fused_bitset_t a) noexcept: cdef inline long bitset_pop(fused_bitset_t a) except -1: """ - Remove and return an arbitrary element from the set. Raise - KeyError if the set is empty. + Remove and return an arbitrary element from the set. + + This raises a :class:`KeyError` if the set is empty. """ cdef long i = bitset_first(a) if i == -1: diff --git a/src/sage/data_structures/mutable_poset.py b/src/sage/data_structures/mutable_poset.py index 03b5a7019c2..72ec82d3bb4 100644 --- a/src/sage/data_structures/mutable_poset.py +++ b/src/sage/data_structures/mutable_poset.py @@ -2299,21 +2299,21 @@ def remove(self, key, raise_key_error=True): - ``key`` -- the key of an object. - ``raise_key_error`` -- (default: ``True``) switch raising - ``KeyError`` on and off. + :class:`KeyError` on and off. OUTPUT: Nothing. If the element is not a member and ``raise_key_error`` is set - (default), raise a ``KeyError``. + (default), raise a :class:`KeyError`. .. NOTE:: As with Python's ``set``, the methods :meth:`remove` and :meth:`discard` only differ in their behavior when an element is not contained in the poset: :meth:`remove` - raises a ``KeyError`` whereas :meth:`discard` does not + raises a :class:`KeyError` whereas :meth:`discard` does not raise any exception. This default behavior can be overridden with the @@ -2479,21 +2479,21 @@ def discard(self, key, raise_key_error=False): - ``key`` -- the key of an object. - ``raise_key_error`` -- (default: ``False``) switch raising - ``KeyError`` on and off. + :class:`KeyError` on and off. OUTPUT: Nothing. If the element is not a member and ``raise_key_error`` is set - (not default), raise a ``KeyError``. + (not default), raise a :class:`KeyError`. .. NOTE:: As with Python's ``set``, the methods :meth:`remove` and :meth:`discard` only differ in their behavior when an element is not contained in the poset: :meth:`remove` - raises a ``KeyError`` whereas :meth:`discard` does not + raises a :class:`KeyError` whereas :meth:`discard` does not raise any exception. This default behavior can be overridden with the diff --git a/src/sage/databases/cremona.py b/src/sage/databases/cremona.py index 172d212fc97..169f2e7c31a 100644 --- a/src/sage/databases/cremona.py +++ b/src/sage/databases/cremona.py @@ -953,13 +953,14 @@ def data_from_coefficients(self, ainvs): def elliptic_curve_from_ainvs(self, ainvs): """ - Return the elliptic curve in the database of with minimal - ``ainvs``, if it exists, or raises a ``RuntimeError`` exception - otherwise. + Return the elliptic curve in the database of with minimal ``ainvs`` + if it exists. + + This raises a :class:`RuntimeError` exception otherwise. INPUT: - - ``ainvs`` - list (5-tuple of int's); the minimal + - ``ainvs`` -- list (5-tuple of int's); the minimal Weierstrass model for an elliptic curve OUTPUT: EllipticCurve diff --git a/src/sage/doctest/control.py b/src/sage/doctest/control.py index 38c99e6dd72..e4635803dc2 100644 --- a/src/sage/doctest/control.py +++ b/src/sage/doctest/control.py @@ -608,8 +608,8 @@ def second_on_modern_computer(self): Float. The wall time on your computer that would be equivalent to one second on a modern computer. Unless you have kick-ass - hardware this should always be >= 1.0. Raises a - ``RuntimeError`` if there are no stored timings to use as + hardware this should always be >= 1.0. This raises a + :class:`RuntimeError` if there are no stored timings to use as benchmark. EXAMPLES:: @@ -1102,9 +1102,7 @@ def source_baseline(self, source): EXAMPLES:: sage: from sage.doctest.control import DocTestDefaults, DocTestController - sage: from sage.env import SAGE_SRC - sage: import os - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','util.py') + sage: filename = sage.doctest.util.__file__ sage: DD = DocTestDefaults() sage: DC = DocTestController(DD, [filename]) sage: DC.expand_files_into_sources() diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index 42598a761c9..3e5b4c52811 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -575,11 +575,10 @@ def _run(self, test, compileflags, out): sage: from sage.doctest.forker import SageDocTestRunner sage: from sage.doctest.sources import FileDocTestSource sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults() - sage: from sage.env import SAGE_SRC sage: import doctest, sys, os sage: DTR = SageDocTestRunner(SageOutputChecker(), verbose=False, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS) - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','forker.py') - sage: FDS = FileDocTestSource(filename,DD) + sage: filename = sage.doctest.forker.__file__ + sage: FDS = FileDocTestSource(filename, DD) sage: doctests, extras = FDS.create_doctests(globals()) sage: DTR.run(doctests[0], clear_globs=False) # indirect doctest TestResults(failed=0, attempted=4) @@ -876,12 +875,11 @@ def run(self, test, compileflags=0, out=None, clear_globs=True): sage: from sage.doctest.forker import SageDocTestRunner sage: from sage.doctest.sources import FileDocTestSource sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults() - sage: from sage.env import SAGE_SRC sage: import doctest, sys, os sage: DTR = SageDocTestRunner(SageOutputChecker(), verbose=False, sage_options=DD, ....: optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS) - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','forker.py') - sage: FDS = FileDocTestSource(filename,DD) + sage: filename = sage.doctest.forker.__file__ + sage: FDS = FileDocTestSource(filename, DD) sage: doctests, extras = FDS.create_doctests(globals()) sage: DTR.run(doctests[0], clear_globs=False) TestResults(failed=0, attempted=4) @@ -1021,11 +1019,10 @@ def update_digests(self, example): sage: from sage.doctest.forker import SageDocTestRunner sage: from sage.doctest.sources import FileDocTestSource sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults() - sage: from sage.env import SAGE_SRC sage: import doctest, sys, os, hashlib sage: DTR = SageDocTestRunner(SageOutputChecker(), verbose=False, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS) - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','forker.py') - sage: FDS = FileDocTestSource(filename,DD) + sage: filename = sage.doctest.forker.__file__ + sage: FDS = FileDocTestSource(filename, DD) sage: doctests, extras = FDS.create_doctests(globals()) sage: DTR.running_global_digest.hexdigest() 'd41d8cd98f00b204e9800998ecf8427e' @@ -1083,12 +1080,11 @@ def compile_and_execute(self, example, compiler, globs): sage: from sage.doctest.sources import FileDocTestSource sage: from sage.doctest.util import RecordingDict sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults() - sage: from sage.env import SAGE_SRC sage: import doctest, sys, os, hashlib sage: DTR = SageDocTestRunner(SageOutputChecker(), verbose=False, sage_options=DD, ....: optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS) sage: DTR.running_doctest_digest = hashlib.md5() - sage: filename = os.path.join(SAGE_SRC, 'sage', 'doctest', 'forker.py') + sage: filename = sage.doctest.forker.__file__ sage: FDS = FileDocTestSource(filename, DD) sage: globs = RecordingDict(globals()) sage: 'doctest_var' in globs @@ -1203,11 +1199,10 @@ def _failure_header(self, test, example, message='Failed example:'): sage: from sage.doctest.forker import SageDocTestRunner sage: from sage.doctest.sources import FileDocTestSource sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults() - sage: from sage.env import SAGE_SRC sage: import doctest, sys, os sage: DTR = SageDocTestRunner(SageOutputChecker(), verbose=False, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS) - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','forker.py') - sage: FDS = FileDocTestSource(filename,DD) + sage: filename = sage.doctest.forker.__file__ + sage: FDS = FileDocTestSource(filename, DD) sage: doctests, extras = FDS.create_doctests(globals()) sage: ex = doctests[0].examples[0] sage: print(DTR._failure_header(doctests[0], ex)) @@ -1315,11 +1310,10 @@ def report_start(self, out, test, example): sage: from sage.doctest.forker import SageDocTestRunner sage: from sage.doctest.sources import FileDocTestSource sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults() - sage: from sage.env import SAGE_SRC sage: import doctest, sys, os sage: DTR = SageDocTestRunner(SageOutputChecker(), verbose=True, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS) - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','forker.py') - sage: FDS = FileDocTestSource(filename,DD) + sage: filename = sage.doctest.forker.__file__ + sage: FDS = FileDocTestSource(filename, DD) sage: doctests, extras = FDS.create_doctests(globals()) sage: ex = doctests[0].examples[0] sage: DTR.report_start(sys.stdout.write, doctests[0], ex) @@ -1369,11 +1363,10 @@ def report_success(self, out, test, example, got, *, check_duration=0): sage: from sage.doctest.sources import FileDocTestSource sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults() sage: from sage.misc.timing import walltime - sage: from sage.env import SAGE_SRC sage: import doctest, sys, os sage: DTR = SageDocTestRunner(SageOutputChecker(), verbose=True, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS) - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','forker.py') - sage: FDS = FileDocTestSource(filename,DD) + sage: filename = sage.doctest.forker.__file__ + sage: FDS = FileDocTestSource(filename, DD) sage: doctests, extras = FDS.create_doctests(globals()) sage: ex = doctests[0].examples[0] sage: ex.walltime = 0.0 @@ -1410,11 +1403,10 @@ def report_failure(self, out, test, example, got, globs): sage: from sage.doctest.forker import SageDocTestRunner sage: from sage.doctest.sources import FileDocTestSource sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults() - sage: from sage.env import SAGE_SRC sage: import doctest, sys, os sage: DTR = SageDocTestRunner(SageOutputChecker(), verbose=True, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS) - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','forker.py') - sage: FDS = FileDocTestSource(filename,DD) + sage: filename = sage.doctest.forker.__file__ + sage: FDS = FileDocTestSource(filename, DD) sage: doctests, extras = FDS.create_doctests(globals()) sage: ex = doctests[0].examples[0] sage: DTR.no_failure_yet = True @@ -1544,11 +1536,10 @@ def report_overtime(self, out, test, example, got, *, check_duration=0): sage: from sage.doctest.sources import FileDocTestSource sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults() sage: from sage.misc.timing import walltime - sage: from sage.env import SAGE_SRC sage: import doctest, sys, os sage: DTR = SageDocTestRunner(SageOutputChecker(), verbose=True, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS) - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','forker.py') - sage: FDS = FileDocTestSource(filename,DD) + sage: filename = sage.doctest.forker.__file__ + sage: FDS = FileDocTestSource(filename, DD) sage: doctests, extras = FDS.create_doctests(globals()) sage: ex = doctests[0].examples[0] sage: ex.walltime = 1.23 @@ -1672,11 +1663,10 @@ def update_results(self, D): sage: from sage.doctest.forker import SageDocTestRunner sage: from sage.doctest.sources import FileDocTestSource, DictAsObject sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults() - sage: from sage.env import SAGE_SRC sage: import doctest, sys, os sage: DTR = SageDocTestRunner(SageOutputChecker(), verbose=False, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS) - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','forker.py') - sage: FDS = FileDocTestSource(filename,DD) + sage: filename = sage.doctest.forker.__file__ + sage: FDS = FileDocTestSource(filename, DD) sage: doctests, extras = FDS.create_doctests(globals()) sage: from sage.doctest.util import Timer sage: T = Timer().start() @@ -1754,7 +1744,6 @@ def serial_dispatch(self): sage: from sage.doctest.forker import DocTestDispatcher sage: from sage.doctest.reporting import DocTestReporter sage: from sage.doctest.util import Timer - sage: from sage.env import SAGE_SRC sage: import os sage: homset = os.path.join(SAGE_SRC, 'sage', 'rings', 'homset.py') sage: ideal = os.path.join(SAGE_SRC, 'sage', 'rings', 'ideal.py') @@ -1801,7 +1790,6 @@ def parallel_dispatch(self): sage: from sage.doctest.forker import DocTestDispatcher sage: from sage.doctest.reporting import DocTestReporter sage: from sage.doctest.util import Timer - sage: from sage.env import SAGE_SRC sage: import os sage: crem = os.path.join(SAGE_SRC, 'sage', 'databases', 'cremona.py') sage: bigo = os.path.join(SAGE_SRC, 'sage', 'rings', 'big_oh.py') @@ -2136,7 +2124,6 @@ def dispatch(self): sage: from sage.doctest.forker import DocTestDispatcher sage: from sage.doctest.reporting import DocTestReporter sage: from sage.doctest.util import Timer - sage: from sage.env import SAGE_SRC sage: import os sage: freehom = os.path.join(SAGE_SRC, 'sage', 'modules', 'free_module_homspace.py') sage: bigo = os.path.join(SAGE_SRC, 'sage', 'rings', 'big_oh.py') @@ -2191,10 +2178,9 @@ class should be accessed by the child process. sage: from sage.doctest.sources import FileDocTestSource sage: from sage.doctest.reporting import DocTestReporter sage: from sage.doctest.control import DocTestController, DocTestDefaults - sage: from sage.env import SAGE_SRC - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','util.py') + sage: filename = sage.doctest.util.__file__ sage: DD = DocTestDefaults() - sage: FDS = FileDocTestSource(filename,DD) + sage: FDS = FileDocTestSource(filename, DD) sage: W = DocTestWorker(FDS, DD) sage: W.start() sage: DC = DocTestController(DD, filename) @@ -2316,10 +2302,9 @@ def start(self): sage: from sage.doctest.sources import FileDocTestSource sage: from sage.doctest.reporting import DocTestReporter sage: from sage.doctest.control import DocTestController, DocTestDefaults - sage: from sage.env import SAGE_SRC - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','util.py') + sage: filename = sage.doctest.util.__file__ sage: DD = DocTestDefaults() - sage: FDS = FileDocTestSource(filename,DD) + sage: FDS = FileDocTestSource(filename, DD) sage: W = DocTestWorker(FDS, DD) sage: W.start() sage: try: @@ -2356,10 +2341,9 @@ def read_messages(self): sage: from sage.doctest.sources import FileDocTestSource sage: from sage.doctest.reporting import DocTestReporter sage: from sage.doctest.control import DocTestController, DocTestDefaults - sage: from sage.env import SAGE_SRC - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','util.py') + sage: filename = sage.doctest.util.__file__ sage: DD = DocTestDefaults(verbose=True,nthreads=2) - sage: FDS = FileDocTestSource(filename,DD) + sage: FDS = FileDocTestSource(filename, DD) sage: W = DocTestWorker(FDS, DD) sage: W.start() sage: while W.rmessages is not None: @@ -2391,10 +2375,9 @@ def save_result_output(self): sage: from sage.doctest.sources import FileDocTestSource sage: from sage.doctest.reporting import DocTestReporter sage: from sage.doctest.control import DocTestController, DocTestDefaults - sage: from sage.env import SAGE_SRC - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','util.py') + sage: filename = sage.doctest.util.__file__ sage: DD = DocTestDefaults() - sage: FDS = FileDocTestSource(filename,DD) + sage: FDS = FileDocTestSource(filename, DD) sage: W = DocTestWorker(FDS, DD) sage: W.start() sage: W.join() @@ -2452,10 +2435,9 @@ def kill(self): sage: from sage.doctest.sources import FileDocTestSource sage: from sage.doctest.reporting import DocTestReporter sage: from sage.doctest.control import DocTestController, DocTestDefaults - sage: from sage.env import SAGE_SRC sage: filename = os.path.join(SAGE_SRC,'sage','doctest','tests','99seconds.rst') sage: DD = DocTestDefaults() - sage: FDS = FileDocTestSource(filename,DD) + sage: FDS = FileDocTestSource(filename, DD) We set up the worker to start by blocking ``SIGQUIT``, such that killing will fail initially:: @@ -2522,11 +2504,10 @@ class DocTestTask(): sage: from sage.doctest.forker import DocTestTask sage: from sage.doctest.sources import FileDocTestSource sage: from sage.doctest.control import DocTestDefaults, DocTestController - sage: from sage.env import SAGE_SRC sage: import os - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','sources.py') + sage: filename = sage.doctest.sources.__file__ sage: DD = DocTestDefaults() - sage: FDS = FileDocTestSource(filename,DD) + sage: FDS = FileDocTestSource(filename, DD) sage: DTT = DocTestTask(FDS) sage: DC = DocTestController(DD,[filename]) sage: ntests, results = DTT(options=DD) @@ -2545,10 +2526,9 @@ def __init__(self, source): sage: from sage.doctest.forker import DocTestTask sage: from sage.doctest.sources import FileDocTestSource sage: from sage.doctest.control import DocTestDefaults - sage: from sage.env import SAGE_SRC sage: import os - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','sources.py') - sage: FDS = FileDocTestSource(filename,DocTestDefaults()) + sage: filename = sage.doctest.sources.__file__ + sage: FDS = FileDocTestSource(filename, DocTestDefaults()) sage: DocTestTask(FDS) """ @@ -2588,11 +2568,10 @@ def __call__(self, options, outtmpfile=None, msgfile=None, result_queue=None, *, sage: from sage.doctest.forker import DocTestTask sage: from sage.doctest.sources import FileDocTestSource sage: from sage.doctest.control import DocTestDefaults, DocTestController - sage: from sage.env import SAGE_SRC sage: import os - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','parsing.py') + sage: filename = sage.doctest.parsing.__file__ sage: DD = DocTestDefaults() - sage: FDS = FileDocTestSource(filename,DD) + sage: FDS = FileDocTestSource(filename, DD) sage: DTT = DocTestTask(FDS) sage: DC = DocTestController(DD, [filename]) sage: ntests, runner = DTT(options=DD) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 8c70c723b90..8485525f8e0 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -782,10 +782,8 @@ class OriginalSource(): sage: from sage.doctest.sources import FileDocTestSource sage: from sage.doctest.control import DocTestDefaults - sage: from sage.env import SAGE_SRC - sage: import os - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','forker.py') - sage: FDS = FileDocTestSource(filename,DocTestDefaults()) + sage: filename = sage.doctest.forker.__file__ + sage: FDS = FileDocTestSource(filename, DocTestDefaults()) sage: doctests, extras = FDS.create_doctests(globals()) sage: ex = doctests[0].examples[0] sage: ex.sage_source @@ -809,10 +807,8 @@ def __init__(self, example): sage: from sage.doctest.sources import FileDocTestSource sage: from sage.doctest.control import DocTestDefaults - sage: from sage.env import SAGE_SRC - sage: import os - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','forker.py') - sage: FDS = FileDocTestSource(filename,DocTestDefaults()) + sage: filename = sage.doctest.forker.__file__ + sage: FDS = FileDocTestSource(filename, DocTestDefaults()) sage: doctests, extras = FDS.create_doctests(globals()) sage: ex = doctests[0].examples[0] sage: from sage.doctest.parsing import OriginalSource @@ -827,10 +823,8 @@ def __enter__(self): sage: from sage.doctest.sources import FileDocTestSource sage: from sage.doctest.control import DocTestDefaults - sage: from sage.env import SAGE_SRC - sage: import os - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','forker.py') - sage: FDS = FileDocTestSource(filename,DocTestDefaults()) + sage: filename = sage.doctest.forker.__file__ + sage: FDS = FileDocTestSource(filename, DocTestDefaults()) sage: doctests, extras = FDS.create_doctests(globals()) sage: ex = doctests[0].examples[0] sage: from sage.doctest.parsing import OriginalSource @@ -847,10 +841,8 @@ def __exit__(self, *args): sage: from sage.doctest.sources import FileDocTestSource sage: from sage.doctest.control import DocTestDefaults - sage: from sage.env import SAGE_SRC - sage: import os - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','forker.py') - sage: FDS = FileDocTestSource(filename,DocTestDefaults()) + sage: filename = sage.doctest.forker.__file__ + sage: FDS = FileDocTestSource(filename, DocTestDefaults()) sage: doctests, extras = FDS.create_doctests(globals()) sage: ex = doctests[0].examples[0] sage: from sage.doctest.parsing import OriginalSource diff --git a/src/sage/doctest/reporting.py b/src/sage/doctest/reporting.py index 1161bafd31c..68da2754fc9 100644 --- a/src/sage/doctest/reporting.py +++ b/src/sage/doctest/reporting.py @@ -41,6 +41,7 @@ # https://www.gnu.org/licenses/ # **************************************************************************** +import re from sys import stdout from signal import (SIGABRT, SIGALRM, SIGBUS, SIGFPE, SIGHUP, SIGILL, SIGINT, SIGKILL, SIGPIPE, SIGQUIT, SIGSEGV, SIGTERM) @@ -109,10 +110,8 @@ def __init__(self, controller): sage: from sage.doctest.reporting import DocTestReporter sage: from sage.doctest.control import DocTestController, DocTestDefaults - sage: from sage.env import SAGE_SRC - sage: import os - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','reporting.py') - sage: DC = DocTestController(DocTestDefaults(),[filename]) + sage: filename = sage.doctest.reporting.__file__ + sage: DC = DocTestController(DocTestDefaults(), [filename]) sage: DTR = DocTestReporter(DC) """ self.controller = controller @@ -133,10 +132,8 @@ def were_doctests_with_optional_tag_run(self, tag): sage: from sage.doctest.reporting import DocTestReporter sage: from sage.doctest.control import DocTestController, DocTestDefaults - sage: from sage.env import SAGE_SRC - sage: import os - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','reporting.py') - sage: DC = DocTestController(DocTestDefaults(),[filename]) + sage: filename = sage.doctest.reporting.__file__ + sage: DC = DocTestController(DocTestDefaults(), [filename]) sage: DTR = DocTestReporter(DC) :: @@ -149,8 +146,9 @@ def were_doctests_with_optional_tag_run(self, tag): When latex is available, doctests marked with optional tag ``latex`` are run by default since :issue:`32174`:: - sage: filename = os.path.join(SAGE_SRC,'sage','misc','latex.py') - sage: DC = DocTestController(DocTestDefaults(),[filename]) + sage: # needs SAGE_SRC + sage: filename = os.path.join(SAGE_SRC, 'sage', 'misc', 'latex.py') + sage: DC = DocTestController(DocTestDefaults(), [filename]) sage: DTR = DocTestReporter(DC) sage: DTR.were_doctests_with_optional_tag_run('latex') # optional - latex True @@ -178,10 +176,9 @@ def report_head(self, source, fail_msg=None): sage: from sage.doctest.control import DocTestController, DocTestDefaults sage: from sage.doctest.sources import FileDocTestSource sage: from sage.doctest.forker import SageDocTestRunner - sage: from sage.env import SAGE_SRC - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','reporting.py') + sage: filename = sage.doctest.reporting.__file__ sage: DD = DocTestDefaults() - sage: FDS = FileDocTestSource(filename,DD) + sage: FDS = FileDocTestSource(filename, DD) sage: DC = DocTestController(DD, [filename]) sage: DTR = DocTestReporter(DC) sage: print(DTR.report_head(FDS)) @@ -222,6 +219,69 @@ def report_head(self, source, fail_msg=None): cmd += f" [failed in baseline: {failed}]" return cmd + def _log_failure(self, source, fail_msg, event, output=None): + r""" + Report on the result of a failed doctest run. + + INPUT: + + - ``source`` -- a source from :mod:`sage.doctest.sources` + + - ``fail_msg`` -- a string + + - ``event`` -- a string + + - ``output`` -- optional string + + EXAMPLES:: + + sage: from sage.doctest.reporting import DocTestReporter + sage: from sage.doctest.control import DocTestController, DocTestDefaults + sage: from sage.doctest.sources import FileDocTestSource + sage: from sage.env import SAGE_SRC + sage: import os + sage: filename = os.path.join(SAGE_SRC, 'sage', 'doctest', 'reporting.py') + sage: DD = DocTestDefaults() + sage: FDS = FileDocTestSource(filename, DD) + sage: DC = DocTestController(DD,[filename]) + sage: DTR = DocTestReporter(DC) + sage: DTR._log_failure(FDS, "Timed out", "process (pid=1234) timed out", "Output so far...") + Timed out + ********************************************************************** + Tests run before process (pid=1234) timed out: + Output so far... + ********************************************************************** + """ + log = self.controller.log + format = self.controller.options.format + if format == 'sage': + stars = "*" * 70 + log(f" {fail_msg}\n{stars}\n") + if output: + log(f"Tests run before {event}:") + log(output) + log(stars) + elif format == 'github': + # https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#using-workflow-commands-to-access-toolkit-functions + command = f'::error title={fail_msg}' + command += f',file={source.printpath}' + if output: + if m := re.search("## line ([0-9]+) ##\n-{40,100}\n(.*)", output, re.MULTILINE | re.DOTALL): + lineno = m.group(1) + message = m.group(2) + command += f',line={lineno}' + else: + message = output + # Urlencoding trick for multi-line annotations + # https://github.com/actions/starter-workflows/issues/68#issuecomment-581479448 + message = message.replace('\n', '%0A') + else: + message = "" + command += f'::{message}' + log(command) + else: + raise ValueError(f'unknown format option: {format}') + def report(self, source, timeout, return_code, results, output, pid=None): """ Report on the result of running doctests on a given source. @@ -261,12 +321,11 @@ def report(self, source, timeout, return_code, results, output, pid=None): sage: from sage.doctest.forker import SageDocTestRunner sage: from sage.doctest.parsing import SageOutputChecker sage: from sage.doctest.util import Timer - sage: from sage.env import SAGE_SRC - sage: import os, sys, doctest - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','reporting.py') + sage: import doctest + sage: filename = sage.doctest.reporting.__file__ sage: DD = DocTestDefaults() - sage: FDS = FileDocTestSource(filename,DD) - sage: DC = DocTestController(DD,[filename]) + sage: FDS = FileDocTestSource(filename, DD) + sage: DC = DocTestController(DD, [filename]) sage: DTR = DocTestReporter(DC) You can report a timeout:: @@ -339,13 +398,15 @@ def report(self, source, timeout, return_code, results, output, pid=None): Or tell the user that everything succeeded:: sage: doctests, extras = FDS.create_doctests(globals()) - sage: runner = SageDocTestRunner(SageOutputChecker(), verbose=False, sage_options=DD, - ....: optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS) + sage: runner = SageDocTestRunner( + ....: SageOutputChecker(), verbose=False, sage_options=DD, + ....: optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS) sage: Timer().start().stop().annotate(runner) sage: D = DictAsObject({'err':None}) sage: runner.update_results(D) 0 - sage: DTR.report(FDS, False, 0, (sum([len(t.examples) for t in doctests]), D), "Good tests") + sage: DTR.report(FDS, False, 0, (sum([len(t.examples) for t in doctests]), D), + ....: "Good tests") [... tests, ... s] sage: DTR.stats {'sage.doctest.reporting': {'ntests': ..., 'walltime': ...}} @@ -355,7 +416,8 @@ def report(self, source, timeout, return_code, results, output, pid=None): sage: runner.failures = 1 sage: runner.update_results(D) 1 - sage: DTR.report(FDS, False, 0, (sum([len(t.examples) for t in doctests]), D), "Doctest output including the failure...") + sage: DTR.report(FDS, False, 0, (sum([len(t.examples) for t in doctests]), D), + ....: "Doctest output including the failure...") [... tests, 1 failure, ... s] If the user has requested that we report on skipped doctests, @@ -365,7 +427,7 @@ def report(self, source, timeout, return_code, results, output, pid=None): sage: from collections import defaultdict sage: optionals = defaultdict(int) sage: optionals['magma'] = 5; optionals['long time'] = 4; optionals[''] = 1; optionals['not tested'] = 2 - sage: D = DictAsObject(dict(err=None,optionals=optionals)) + sage: D = DictAsObject(dict(err=None, optionals=optionals)) sage: runner.failures = 0 sage: runner.update_results(D) 0 @@ -391,20 +453,23 @@ def report(self, source, timeout, return_code, results, output, pid=None): sage: DC = DocTestController(DD, [filename]) sage: DTR = DocTestReporter(DC) sage: doctests, extras = FDS.create_doctests(globals()) - sage: runner = SageDocTestRunner(SageOutputChecker(), verbose=False, sage_options=DD, - ....: optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS) + sage: runner = SageDocTestRunner( + ....: SageOutputChecker(), verbose=False, sage_options=DD, + ....: optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS) sage: Timer().start().stop().annotate(runner) sage: D = DictAsObject({'err':None}) sage: runner.update_results(D) 0 - sage: DTR.report(FDS, False, 0, (sum([len(t.examples) for t in doctests]), D), "Good tests") + sage: DTR.report(FDS, False, 0, (sum([len(t.examples) for t in doctests]), D), + ....: "Good tests") However, failures are still output in the errors-only mode:: sage: runner.failures = 1 sage: runner.update_results(D) 1 - sage: DTR.report(FDS, False, 0, (sum([len(t.examples) for t in doctests]), D), "Failed test") + sage: DTR.report(FDS, False, 0, (sum([len(t.examples) for t in doctests]), D), + ....: "Failed test") [... tests, 1 failure, ... s] """ log = self.controller.log @@ -434,9 +499,7 @@ def report(self, source, timeout, return_code, results, output, pid=None): fail_msg += " (and interrupt failed)" else: fail_msg += " (with %s after interrupt)" % signal_name(sig) - log(" %s\n%s\nTests run before %s timed out:" % (fail_msg, "*"*70, process_name)) - log(output) - log("*"*70) + self._log_failure(source, fail_msg, f"{process_name} timed out", output) postscript['lines'].append(self.report_head(source, fail_msg)) stats[basename] = {"failed": True, "walltime": 1e6, "ntests": ntests} if not baseline.get('failed', False): @@ -448,9 +511,7 @@ def report(self, source, timeout, return_code, results, output, pid=None): fail_msg = "Killed due to %s" % signal_name(-return_code) if ntests > 0: fail_msg += " after testing finished" - log(" %s\n%s\nTests run before %s failed:" % (fail_msg,"*"*70, process_name)) - log(output) - log("*"*70) + self._log_failure(source, fail_msg, f"{process_name} failed", output) postscript['lines'].append(self.report_head(source, fail_msg)) stats[basename] = {"failed": True, "walltime": 1e6, "ntests": ntests} if not baseline.get('failed', False): @@ -465,15 +526,11 @@ def report(self, source, timeout, return_code, results, output, pid=None): else: cpu = 1e6 if result_dict.err == 'badresult': - log(" Error in doctesting framework (bad result returned)\n%s\nTests run before error:" % ("*"*70)) - log(output) - log("*"*70) + self._log_failure(source, "Error in doctesting framework (bad result returned)", "error", output) postscript['lines'].append(self.report_head(source, "Testing error: bad result")) self.error_status |= 64 elif result_dict.err == 'noresult': - log(" Error in doctesting framework (no result returned)\n%s\nTests run before error:" % ("*"*70)) - log(output) - log("*"*70) + self._log_failure(source, "Error in doctesting framework (no result returned)", "error", output) postscript['lines'].append(self.report_head(source, "Testing error: no result")) self.error_status |= 64 elif result_dict.err == 'tab': @@ -499,11 +556,7 @@ def report(self, source, timeout, return_code, results, output, pid=None): else: err = repr(result_dict.err) fail_msg = "%s in doctesting framework" % err - - log(" %s\n%s" % (fail_msg, "*"*70)) - if output: - log("Tests run before doctest exception:\n" + output) - log("*"*70) + self._log_failure(source, fail_msg, "exception", output) postscript['lines'].append(self.report_head(source, fail_msg)) if hasattr(result_dict, 'tb'): log(result_dict.tb) @@ -598,12 +651,11 @@ def finalize(self): sage: from sage.doctest.forker import SageDocTestRunner sage: from sage.doctest.parsing import SageOutputChecker sage: from sage.doctest.util import Timer - sage: from sage.env import SAGE_SRC - sage: import os, sys, doctest - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','reporting.py') + sage: import doctest + sage: filename = sage.doctest.reporting.__file__ sage: DD = DocTestDefaults() - sage: FDS = FileDocTestSource(filename,DD) - sage: DC = DocTestController(DD,[filename]) + sage: FDS = FileDocTestSource(filename, DD) + sage: DC = DocTestController(DD, [filename]) sage: DTR = DocTestReporter(DC) Now we pretend to run some doctests:: @@ -621,19 +673,23 @@ def finalize(self): Output before bad exit ********************************************************************** sage: doctests, extras = FDS.create_doctests(globals()) - sage: runner = SageDocTestRunner(SageOutputChecker(), verbose=False, sage_options=DD,optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS) + sage: runner = SageDocTestRunner( + ....: SageOutputChecker(), verbose=False, sage_options=DD, + ....: optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS) sage: t = Timer().start().stop() sage: t.annotate(runner) sage: DC.timer = t sage: D = DictAsObject({'err':None}) sage: runner.update_results(D) 0 - sage: DTR.report(FDS, False, 0, (sum([len(t.examples) for t in doctests]), D), "Good tests") + sage: DTR.report(FDS, False, 0, (sum([len(t.examples) for t in doctests]), D), + ....: "Good tests") [... tests, ... s] sage: runner.failures = 1 sage: runner.update_results(D) 1 - sage: DTR.report(FDS, False, 0, (sum([len(t.examples) for t in doctests]), D), "Doctest output including the failure...") + sage: DTR.report(FDS, False, 0, (sum([len(t.examples) for t in doctests]), D), + ....: "Doctest output including the failure...") [... tests, 1 failure, ... s] Now we can show the output of finalize:: diff --git a/src/sage/doctest/sources.py b/src/sage/doctest/sources.py index fdad2749083..eff2a853e2b 100644 --- a/src/sage/doctest/sources.py +++ b/src/sage/doctest/sources.py @@ -82,11 +82,10 @@ def get_basename(path): EXAMPLES:: sage: from sage.doctest.sources import get_basename - sage: from sage.env import SAGE_SRC sage: import os - sage: get_basename(os.path.join(SAGE_SRC, 'sage', 'doctest', 'sources.py')) + sage: get_basename(sage.doctest.sources.__file__) 'sage.doctest.sources' - sage: get_basename(os.path.join(SAGE_SRC, 'sage', 'structure', 'element.pxd')) + sage: get_basename(os.path.join(sage.structure.__path__[0], 'element.pxd')) 'sage.structure.element.pxd' """ if path is None: @@ -140,10 +139,8 @@ def __init__(self, options): sage: from sage.doctest.control import DocTestDefaults sage: from sage.doctest.sources import FileDocTestSource - sage: from sage.env import SAGE_SRC - sage: import os - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','sources.py') - sage: FDS = FileDocTestSource(filename,DocTestDefaults()) + sage: filename = sage.doctest.sources.__file__ + sage: FDS = FileDocTestSource(filename, DocTestDefaults()) sage: TestSuite(FDS).run() """ self.options = options @@ -156,12 +153,10 @@ def __eq__(self, other): sage: from sage.doctest.control import DocTestDefaults sage: from sage.doctest.sources import FileDocTestSource - sage: from sage.env import SAGE_SRC - sage: import os - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','sources.py') + sage: filename = sage.doctest.sources.__file__ sage: DD = DocTestDefaults() - sage: FDS = FileDocTestSource(filename,DD) - sage: FDS2 = FileDocTestSource(filename,DD) + sage: FDS = FileDocTestSource(filename, DD) + sage: FDS2 = FileDocTestSource(filename, DD) sage: FDS == FDS2 True """ @@ -177,12 +172,10 @@ def __ne__(self, other): sage: from sage.doctest.control import DocTestDefaults sage: from sage.doctest.sources import FileDocTestSource - sage: from sage.env import SAGE_SRC - sage: import os - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','sources.py') + sage: filename = sage.doctest.sources.__file__ sage: DD = DocTestDefaults() - sage: FDS = FileDocTestSource(filename,DD) - sage: FDS2 = FileDocTestSource(filename,DD) + sage: FDS = FileDocTestSource(filename, DD) + sage: FDS2 = FileDocTestSource(filename, DD) sage: FDS != FDS2 False """ @@ -217,10 +210,8 @@ def _process_doc(self, doctests, doc, namespace, start): sage: from sage.doctest.control import DocTestDefaults sage: from sage.doctest.sources import FileDocTestSource sage: from sage.doctest.parsing import SageDocTestParser - sage: from sage.env import SAGE_SRC - sage: import os - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','util.py') - sage: FDS = FileDocTestSource(filename,DocTestDefaults()) + sage: filename = sage.doctest.util.__file__ + sage: FDS = FileDocTestSource(filename, DocTestDefaults()) sage: doctests, _ = FDS.create_doctests({}) sage: manual_doctests = [] sage: for dt in doctests: @@ -291,10 +282,8 @@ def _create_doctests(self, namespace, tab_okay=None): sage: from sage.doctest.control import DocTestDefaults sage: from sage.doctest.sources import FileDocTestSource sage: from sage.doctest.util import NestedName - sage: from sage.env import SAGE_SRC - sage: import os - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','sources.py') - sage: FDS = FileDocTestSource(filename,DocTestDefaults()) + sage: filename = sage.doctest.sources.__file__ + sage: FDS = FileDocTestSource(filename, DocTestDefaults()) sage: FDS.qualified_name = NestedName('sage.doctest.sources') sage: doctests, extras = FDS._create_doctests({}) sage: len(doctests) @@ -523,10 +512,8 @@ class FileDocTestSource(DocTestSource): sage: from sage.doctest.control import DocTestDefaults sage: from sage.doctest.sources import FileDocTestSource - sage: from sage.env import SAGE_SRC - sage: import os - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','sources.py') - sage: FDS = FileDocTestSource(filename,DocTestDefaults()) + sage: filename = sage.doctest.sources.__file__ + sage: FDS = FileDocTestSource(filename, DocTestDefaults()) sage: FDS.basename 'sage.doctest.sources' @@ -539,7 +526,7 @@ class FileDocTestSource(DocTestSource): sage: from sage.doctest.control import DocTestDefaults sage: from sage.doctest.sources import FileDocTestSource sage: filename = tmp_filename(ext=".txtt") - sage: FDS = FileDocTestSource(filename,DocTestDefaults()) + sage: FDS = FileDocTestSource(filename, DocTestDefaults()) Traceback (most recent call last): ... ValueError: unknown extension for the file to test (=...txtt), @@ -554,10 +541,8 @@ def __init__(self, path, options): sage: from sage.doctest.control import DocTestDefaults sage: from sage.doctest.sources import FileDocTestSource - sage: from sage.env import SAGE_SRC - sage: import os - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','sources.py') - sage: FDS = FileDocTestSource(filename,DocTestDefaults(randorder=0)) + sage: filename = sage.doctest.sources.__file__ + sage: FDS = FileDocTestSource(filename, DocTestDefaults(randorder=0)) sage: FDS.options.randorder 0 """ @@ -645,16 +630,17 @@ def printpath(self): sage: from sage.doctest.control import DocTestDefaults sage: from sage.doctest.sources import FileDocTestSource - sage: from sage.env import SAGE_SRC sage: import os - sage: root = os.path.realpath(os.path.join(SAGE_SRC,'sage')) - sage: filename = os.path.join(root,'doctest','sources.py') + sage: filename = os.path.realpath(sage.doctest.sources.__file__) + sage: root = os.path.join(os.path.dirname(filename), '..') sage: cwd = os.getcwd() sage: os.chdir(root) - sage: FDS = FileDocTestSource(filename,DocTestDefaults(randorder=0,abspath=False)) + sage: FDS = FileDocTestSource(filename, DocTestDefaults(randorder=0, + ....: abspath=False)) sage: FDS.printpath 'doctest/sources.py' - sage: FDS = FileDocTestSource(filename,DocTestDefaults(randorder=0,abspath=True)) + sage: FDS = FileDocTestSource(filename, DocTestDefaults(randorder=0, + ....: abspath=True)) sage: FDS.printpath '.../sage/doctest/sources.py' sage: os.chdir(cwd) @@ -677,10 +663,8 @@ def basename(self): sage: from sage.doctest.control import DocTestDefaults sage: from sage.doctest.sources import FileDocTestSource - sage: from sage.env import SAGE_SRC - sage: import os sage: filename = os.path.join(SAGE_SRC,'sage','rings','integer.pyx') - sage: FDS = FileDocTestSource(filename,DocTestDefaults()) + sage: FDS = FileDocTestSource(filename, DocTestDefaults()) sage: FDS.basename 'sage.rings.integer' """ @@ -700,7 +684,6 @@ def in_lib(self): sage: from sage.doctest.control import DocTestDefaults sage: from sage.doctest.sources import FileDocTestSource - sage: from sage.env import SAGE_SRC sage: import os sage: filename = os.path.join(SAGE_SRC, 'sage', 'rings', 'integer.pyx') sage: FDS = FileDocTestSource(filename, DocTestDefaults()) @@ -713,10 +696,10 @@ def in_lib(self): You can override the default:: - sage: FDS = FileDocTestSource("hello_world.py",DocTestDefaults()) + sage: FDS = FileDocTestSource("hello_world.py", DocTestDefaults()) sage: FDS.in_lib False - sage: FDS = FileDocTestSource("hello_world.py",DocTestDefaults(force_lib=True)) + sage: FDS = FileDocTestSource("hello_world.py", DocTestDefaults(force_lib=True)) sage: FDS.in_lib True """ @@ -732,9 +715,7 @@ def file_optional_tags(self): sage: from sage.doctest.control import DocTestDefaults sage: from sage.doctest.sources import FileDocTestSource - sage: from sage.env import SAGE_SRC - sage: import os - sage: filename = os.path.join(SAGE_SRC, 'sage', 'repl', 'user_globals.py') + sage: filename = sage.repl.user_globals.__file__ sage: FDS = FileDocTestSource(filename, DocTestDefaults()) sage: FDS.file_optional_tags {'sage.modules': None} @@ -760,10 +741,8 @@ def create_doctests(self, namespace): sage: from sage.doctest.control import DocTestDefaults sage: from sage.doctest.sources import FileDocTestSource - sage: from sage.env import SAGE_SRC - sage: import os - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','sources.py') - sage: FDS = FileDocTestSource(filename,DocTestDefaults()) + sage: filename = sage.doctest.sources.__file__ + sage: FDS = FileDocTestSource(filename, DocTestDefaults()) sage: doctests, extras = FDS.create_doctests(globals()) sage: len(doctests) 43 @@ -774,8 +753,8 @@ def create_doctests(self, namespace): sage: doctests[20].name 'sage.doctest.sources.FileDocTestSource.create_doctests' - sage: doctests[20].examples[10].source - 'doctests[Integer(20)].examples[Integer(10)].source\n' + sage: doctests[20].examples[8].source + 'doctests[Integer(20)].examples[Integer(8)].source\n' TESTS: @@ -787,7 +766,7 @@ def create_doctests(self, namespace): sage: gp.get_precision() == 38 # needs sage.libs.pari False # 32-bit True # 64-bit - sage: ex = doctests[20].examples[13] + sage: ex = doctests[20].examples[11] sage: ((bitness == '64' and ex.want == 'True \n') # needs sage.libs.pari ....: or (bitness == '32' and ex.want == 'False \n')) True @@ -835,7 +814,6 @@ def _test_enough_doctests(self, check_extras=True, verbose=True): sage: # not tested (because the output will change when source files are changed) sage: from sage.doctest.control import DocTestDefaults sage: from sage.doctest.sources import FileDocTestSource - sage: from sage.env import SAGE_SRC sage: cwd = os.getcwd() sage: os.chdir(SAGE_SRC) sage: import itertools @@ -942,10 +920,8 @@ def parse_docstring(self, docstring, namespace, start): sage: from sage.doctest.sources import FileDocTestSource sage: from sage.doctest.parsing import SageDocTestParser sage: from sage.doctest.util import NestedName - sage: from sage.env import SAGE_SRC - sage: import os - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','util.py') - sage: FDS = FileDocTestSource(filename,DocTestDefaults()) + sage: filename = sage.doctest.util.__file__ + sage: FDS = FileDocTestSource(filename, DocTestDefaults()) sage: doctests, _ = FDS.create_doctests({}) sage: for dt in doctests: ....: FDS.qualified_name = dt.name @@ -964,10 +940,8 @@ class PythonSource(SourceLanguage): sage: from sage.doctest.control import DocTestDefaults sage: from sage.doctest.sources import FileDocTestSource - sage: from sage.env import SAGE_SRC - sage: import os - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','sources.py') - sage: FDS = FileDocTestSource(filename,DocTestDefaults()) + sage: filename = sage.doctest.sources.__file__ + sage: FDS = FileDocTestSource(filename, DocTestDefaults()) sage: type(FDS) """ @@ -982,10 +956,8 @@ def _init(self): sage: from sage.doctest.control import DocTestDefaults sage: from sage.doctest.sources import FileDocTestSource - sage: from sage.env import SAGE_SRC - sage: import os - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','sources.py') - sage: FDS = FileDocTestSource(filename,DocTestDefaults()) + sage: filename = sage.doctest.sources.__file__ + sage: FDS = FileDocTestSource(filename, DocTestDefaults()) sage: FDS._init() sage: FDS.last_indent -1 @@ -1019,10 +991,8 @@ def _update_quotetype(self, line): sage: from sage.doctest.control import DocTestDefaults sage: from sage.doctest.sources import FileDocTestSource - sage: from sage.env import SAGE_SRC - sage: import os - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','sources.py') - sage: FDS = FileDocTestSource(filename,DocTestDefaults()) + sage: filename = sage.doctest.sources.__file__ + sage: FDS = FileDocTestSource(filename, DocTestDefaults()) sage: FDS._init() sage: FDS._update_quotetype('\"\"\"'); print(" ".join(list(FDS.quotetype))) " " " @@ -1116,10 +1086,8 @@ def starting_docstring(self, line): sage: from sage.doctest.control import DocTestDefaults sage: from sage.doctest.sources import FileDocTestSource sage: from sage.doctest.util import NestedName - sage: from sage.env import SAGE_SRC - sage: import os - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','sources.py') - sage: FDS = FileDocTestSource(filename,DocTestDefaults()) + sage: filename = sage.doctest.sources.__file__ + sage: FDS = FileDocTestSource(filename, DocTestDefaults()) sage: FDS._init() sage: FDS.starting_docstring("r'''") <...Match object...> @@ -1185,7 +1153,7 @@ def ending_docstring(self, line): OUTPUT: - an object that, when evaluated in a boolean context, gives - True or False depending on whether the input line marks the + ``True`` or ``False`` depending on whether the input line marks the end of a docstring. EXAMPLES:: @@ -1193,10 +1161,8 @@ def ending_docstring(self, line): sage: from sage.doctest.control import DocTestDefaults sage: from sage.doctest.sources import FileDocTestSource sage: from sage.doctest.util import NestedName - sage: from sage.env import SAGE_SRC - sage: import os - sage: filename = os.path.join(SAGE_SRC,'sage','doctest','sources.py') - sage: FDS = FileDocTestSource(filename,DocTestDefaults()) + sage: filename = sage.doctest.sources.__file__ + sage: FDS = FileDocTestSource(filename, DocTestDefaults()) sage: FDS._init() sage: FDS.quotetype = "'''" sage: FDS.ending_docstring("'''") @@ -1229,7 +1195,8 @@ def _neutralize_doctests(self, reindent): sage: from sage.doctest.sources import StringDocTestSource, PythonSource sage: from sage.structure.dynamic_class import dynamic_class sage: s = "'''\n sage: 2 + 2\n 4\n'''" - sage: PythonStringSource = dynamic_class('PythonStringSource',(StringDocTestSource, PythonSource)) + sage: PythonStringSource = dynamic_class('PythonStringSource', + ....: (StringDocTestSource, PythonSource)) sage: PSS = PythonStringSource('', s, DocTestDefaults(), 'runtime') sage: print(PSS._neutralize_doctests(0)) ''' @@ -1264,7 +1231,7 @@ class TexSource(SourceLanguage): sage: from sage.doctest.control import DocTestDefaults sage: from sage.doctest.sources import FileDocTestSource sage: filename = "sage_paper.tex" - sage: FDS = FileDocTestSource(filename,DocTestDefaults()) + sage: FDS = FileDocTestSource(filename, DocTestDefaults()) sage: type(FDS) """ @@ -1280,7 +1247,7 @@ def _init(self): sage: from sage.doctest.control import DocTestDefaults sage: from sage.doctest.sources import FileDocTestSource sage: filename = "sage_paper.tex" - sage: FDS = FileDocTestSource(filename,DocTestDefaults()) + sage: FDS = FileDocTestSource(filename, DocTestDefaults()) sage: FDS._init() sage: FDS.skipping False @@ -1312,7 +1279,7 @@ def starting_docstring(self, line): sage: from sage.doctest.control import DocTestDefaults sage: from sage.doctest.sources import FileDocTestSource sage: filename = "sage_paper.tex" - sage: FDS = FileDocTestSource(filename,DocTestDefaults()) + sage: FDS = FileDocTestSource(filename, DocTestDefaults()) sage: FDS._init() We start docstrings with \begin{verbatim} or \begin{lstlisting}:: @@ -1391,7 +1358,7 @@ def ending_docstring(self, line, check_skip=True): sage: from sage.doctest.control import DocTestDefaults sage: from sage.doctest.sources import FileDocTestSource sage: filename = "sage_paper.tex" - sage: FDS = FileDocTestSource(filename,DocTestDefaults()) + sage: FDS = FileDocTestSource(filename, DocTestDefaults()) sage: FDS._init() sage: FDS.ending_docstring(r"\end{verbatim}") True @@ -1442,7 +1409,7 @@ class RestSource(SourceLanguage): sage: from sage.doctest.control import DocTestDefaults sage: from sage.doctest.sources import FileDocTestSource sage: filename = "sage_doc.rst" - sage: FDS = FileDocTestSource(filename,DocTestDefaults()) + sage: FDS = FileDocTestSource(filename, DocTestDefaults()) sage: type(FDS) """ @@ -1458,7 +1425,7 @@ def _init(self): sage: from sage.doctest.control import DocTestDefaults sage: from sage.doctest.sources import FileDocTestSource sage: filename = "sage_doc.rst" - sage: FDS = FileDocTestSource(filename,DocTestDefaults()) + sage: FDS = FileDocTestSource(filename, DocTestDefaults()) sage: FDS._init() sage: FDS.link_all False @@ -1490,7 +1457,7 @@ def starting_docstring(self, line): sage: from sage.doctest.control import DocTestDefaults sage: from sage.doctest.sources import FileDocTestSource sage: filename = "sage_doc.rst" - sage: FDS = FileDocTestSource(filename,DocTestDefaults()) + sage: FDS = FileDocTestSource(filename, DocTestDefaults()) sage: FDS._init() sage: FDS.starting_docstring("Hello world::") True @@ -1548,7 +1515,7 @@ def ending_docstring(self, line): sage: from sage.doctest.control import DocTestDefaults sage: from sage.doctest.sources import FileDocTestSource sage: filename = "sage_doc.rst" - sage: FDS = FileDocTestSource(filename,DocTestDefaults()) + sage: FDS = FileDocTestSource(filename, DocTestDefaults()) sage: FDS._init() sage: FDS.starting_docstring("Hello world::") True @@ -1594,7 +1561,7 @@ def parse_docstring(self, docstring, namespace, start): sage: from sage.doctest.parsing import SageDocTestParser sage: from sage.doctest.util import NestedName sage: filename = "sage_doc.rst" - sage: FDS = FileDocTestSource(filename,DocTestDefaults()) + sage: FDS = FileDocTestSource(filename, DocTestDefaults()) sage: FDS.parser = SageDocTestParser(set(['sage'])) sage: FDS.qualified_name = NestedName('sage_doc') sage: s = "Some text::\n\n def example_python_function(a, \ diff --git a/src/sage/dynamics/arithmetic_dynamics/dynamical_semigroup.py b/src/sage/dynamics/arithmetic_dynamics/dynamical_semigroup.py index ebb8702a7db..93c402d4695 100644 --- a/src/sage/dynamics/arithmetic_dynamics/dynamical_semigroup.py +++ b/src/sage/dynamics/arithmetic_dynamics/dynamical_semigroup.py @@ -697,7 +697,7 @@ def orbit(self, p, n): sage: d.orbit(2, x) Traceback (most recent call last): ... - TypeError: not a constant polynomial + TypeError: x is not a constant polynomial :: diff --git a/src/sage/dynamics/finite_dynamical_system.py b/src/sage/dynamics/finite_dynamical_system.py index 4295070615a..915db6dc580 100644 --- a/src/sage/dynamics/finite_dynamical_system.py +++ b/src/sage/dynamics/finite_dynamical_system.py @@ -98,22 +98,15 @@ class DiscreteDynamicalSystem(SageObject, metaclass=ClasscallMetaclass): A *discrete dynamical system* (henceforth *DDS*) is a pair `(X, \phi)` of a set `X` and a map `\phi : X \to X`. - This set `X` is called the *ground set* of the DDS, while - the map `\phi` is called the *evolution* of the DDS. - - A *discrete dynamical system* (short: *DDS*) is a pair - `(X, \phi)` of a set `X` and a map `\phi : X \to X`. - (This is one of several things known as a "discrete - dynamical system" in mathematics.) - Thus, a DDS is the same as an endomorphism of a set. - The DDS is said to be *finite* if `X` is finite. - The DDS is said to be *invertible* if the map `\phi` is - invertible. The set `X` is called the *ground set* of the DDS; the map `\phi` is called the *evolution* of the DDS; the inverse map `\phi^{-1}` (when it exists) is called the *inverse evolution* of the DDS. + The DDS is called *finite* if `X` is finite. + The DDS is called *invertible* if the map `\phi` is + invertible. + Given a DDS `(X, \phi)`, we can study * its orbits (i.e., the lists @@ -138,7 +131,7 @@ class DiscreteDynamicalSystem(SageObject, metaclass=ClasscallMetaclass): - ``X`` -- set, list, tuple, or another iterable, or ``None`` (default: ``None``); the ground set for the DDS. - Tthis can be ``None`` (in which case Sage will not know + This can be ``None`` (in which case Sage will not know the ground set, but can still apply evolution to any elements that are provided to it). Make sure to set the ``create_tuple`` argument to diff --git a/src/sage/ext/fast_eval.pyx b/src/sage/ext/fast_eval.pyx index 52fbc8f7406..3db7d6fa1dc 100644 --- a/src/sage/ext/fast_eval.pyx +++ b/src/sage/ext/fast_eval.pyx @@ -31,20 +31,20 @@ AUTHORS: from sage.ext.fast_callable import fast_callable, Wrapper -def fast_float(f, *vars, old=None, expect_one_var=False): +def fast_float(f, *vars, expect_one_var=False): """ - Tries to create a function that evaluates f quickly using - floating-point numbers, if possible. There are two implementations - of fast_float in Sage; by default we use the newer, which is - slightly faster on most tests. + Try to create a function that evaluates f quickly using + floating-point numbers, if possible. On failure, returns the input unchanged. + This is an alternative interface to :func:`sage.ext.fast_callable.fast_callable`. + :issue:`32268` proposes to deprecate this function. + INPUT: - ``f`` -- an expression - ``vars`` -- the names of the arguments - - ``old`` -- deprecated, do not use - ``expect_one_var`` -- don't give deprecation warning if ``vars`` is omitted, as long as expression has only one var @@ -67,12 +67,6 @@ def fast_float(f, *vars, old=None, expect_one_var=False): sage: f(1,2) 1.0 """ - if old: - raise ValueError("the old implementation of fast_float has been removed") - if old is not None: - from sage.misc.superseded import deprecation - deprecation(32234, "passing old=False to fast_float is deprecated") - if isinstance(f, (tuple, list)): return tuple([fast_float(x, *vars, expect_one_var=expect_one_var) for x in f]) diff --git a/src/sage/ext/memory_allocator.pxd b/src/sage/ext/memory_allocator.pxd deleted file mode 100644 index 5fc54a1cd27..00000000000 --- a/src/sage/ext/memory_allocator.pxd +++ /dev/null @@ -1,145 +0,0 @@ -cimport cython -from libc.stdint cimport uintptr_t - - -cdef extern from *: - int unlikely(int) nogil # defined by Cython - - -cdef inline void* align(void* ptr, size_t alignment) noexcept: - """ - Round up ``ptr`` to the nearest multiple of ``alignment``, which - must be a power of 2 - """ - cdef uintptr_t x = ptr - x = (x + alignment - 1) & ~(alignment - 1) - return x - - -@cython.final -cdef class MemoryAllocator: - cdef size_t n - cdef size_t size - cdef void** pointers - cdef void* static_pointers[16] # If n <= 16, store pointers here - - cdef void* malloc(self, size_t size) except? NULL - cdef void* calloc(self, size_t nmemb, size_t size) except? NULL - cdef void* allocarray(self, size_t nmemb, size_t size) except? NULL - cdef void* realloc(self, void* ptr, size_t size) except? NULL - cdef void* reallocarray(self, void* ptr, size_t nmemb, - size_t size) except? NULL - - cdef int resize(self, size_t new_size) except -1 - cdef void** find_pointer(self, void* ptr) except NULL - - cdef inline int enlarge_if_needed(self) except -1: - r""" - Enlarge the list of pointers if needed such that there is at - least one free entry. - """ - if unlikely(self.n >= self.size): - return self.resize(self.size * 2) - - cdef inline void* aligned_malloc(self, size_t alignment, - size_t size) except? NULL: - r""" - Returns new aligned pointer. Stores it to be automatically freed later. - - Alignment must be a power of two. - - .. NOTE:: - - If you want to allocate multiple (small) aligned arrays with the - same alignment and really want to be memory efficient, you can - allocate one large aligned array instead. - - TESTS:: - - sage: cython( # needs sage.misc.cython - ....: ''' - ....: from sage.ext.memory_allocator cimport MemoryAllocator - ....: cdef MemoryAllocator mem = MemoryAllocator() - ....: cdef void* ptr - ....: for i in range(12): - ....: ptr = mem.aligned_malloc(2**i, 4048) - ....: assert ptr == ( ptr) & ~(2**i-1) - ....: ''') - doctest:...: DeprecationWarning: this class is deprecated; - use the class from the python package `memory_allocator` - See https://github.com/sagemath/sage/issues/31591 for details. - """ - cdef size_t extra = alignment - 1 - return align(self.malloc(size + extra), alignment) - - cdef inline void* aligned_calloc(self, size_t alignment, size_t nmemb, - size_t size) except? NULL: - r""" - Returns new aligned pointer. Stores it to be automatically freed later. - - Alignment must be a power of two. - - .. NOTE:: - - If you want to allocate multiple (small) aligned arrays with the - same alignment and really want to be memory efficient, you can - allocate one large aligned array instead. - - TESTS:: - - sage: cython( # needs sage.misc.cython - ....: ''' - ....: from sage.ext.memory_allocator cimport MemoryAllocator - ....: def foo(): - ....: cdef MemoryAllocator mem = MemoryAllocator() - ....: cdef void* ptr - ....: for i in range(12): - ....: ptr = mem.aligned_calloc(2**i, i, 2**i) - ....: assert ptr == ( ptr) & ~(2**i-1) - ....: ''') - sage: foo() # needs sage.misc.cython - doctest:...: DeprecationWarning: this class is deprecated; - use the class from the python package `memory_allocator` - See https://github.com/sagemath/sage/issues/31591 for details. - """ - # Find extra such that (nmemb + extra) * size >= nmemb * size + alignment - 1 - # ⇔ extra * size >= alignment - 1 - # ⇔ extra >= ceil( (alignment - 1) / size) - # ⇔ extra >= (alignment - 1 + size - 1) // size - cdef size_t extra = (alignment + size - 2) // size - return align(self.calloc(nmemb + extra, size), alignment) - - cdef inline void* aligned_allocarray(self, size_t alignment, size_t nmemb, - size_t size) except? NULL: - r""" - Returns new aligned pointer. Stores it to be automatically freed later. - - Alignment must be a power of two. - - .. NOTE:: - - If you want to allocate multiple (small) aligned arrays with the - same alignment and really want to be memory efficient, you can - allocate one large aligned array instead. - - TESTS:: - - sage: cython( # needs sage.misc.cython - ....: ''' - ....: from sage.ext.memory_allocator cimport MemoryAllocator - ....: def foo(): - ....: cdef MemoryAllocator mem = MemoryAllocator() - ....: cdef void* ptr - ....: for i in range(12): - ....: ptr = mem.aligned_allocarray(2**i, i, 2**i) - ....: assert ptr == ( ptr) & ~(2**i-1) - ....: ''') - sage: foo() # random # might raise deprecation warning # needs sage.misc.cython - sage: foo() # needs sage.misc.cython - """ - # Find extra such that (nmemb + extra) * size >= nmemb * size + alignment - 1 - # ⇔ extra * size >= alignment - 1 - # ⇔ extra >= ceil( (alignment - 1) / size) - # ⇔ extra >= (alignment - 1 + size - 1) // size - cdef size_t extra = (alignment + size - 2) // size - return align(self.allocarray(nmemb + extra, size), alignment) diff --git a/src/sage/ext/memory_allocator.pyx b/src/sage/ext/memory_allocator.pyx deleted file mode 100644 index 0c2814658e9..00000000000 --- a/src/sage/ext/memory_allocator.pyx +++ /dev/null @@ -1,180 +0,0 @@ -# sage.doctest: needs sage.misc.cython - -from cysignals.memory cimport * -from sage.misc.superseded import deprecation - - -cdef class MemoryAllocator: - r""" - An object for memory allocation, whose resources are freed upon - ``__dealloc__``. - - EXAMPLES:: - - sage: cython( - ....: ''' - ....: from sage.ext.memory_allocator cimport MemoryAllocator - ....: cdef MemoryAllocator mem = MemoryAllocator() - ....: cdef void* ptr - ....: for n in range(100): - ....: ptr = mem.malloc(n) - ....: mem.realloc(ptr, 2*n) - ....: mem.calloc(n, n) - ....: ptr = mem.allocarray(n, n) - ....: mem.reallocarray(ptr, n + 1, n) - ....: mem.aligned_malloc(32, (n//32 + 1)*32) - ....: mem.aligned_calloc(16, n, 16) - ....: mem.aligned_allocarray(8, n, 8) - ....: ''') - doctest:...: DeprecationWarning: this class is deprecated; use the class from the python package `memory_allocator` - See https://github.com/sagemath/sage/issues/31591 for details. - """ - def __cinit__(self): - """ - EXAMPLES:: - - sage: cython( - ....: ''' - ....: from sage.ext.memory_allocator cimport MemoryAllocator - ....: def foo(): - ....: cdef MemoryAllocator mem = MemoryAllocator.__new__(MemoryAllocator) - ....: mem.malloc(10000) - ....: print(mem.n) - ....: print(mem.size) - ....: ''') - sage: foo() - doctest:...: DeprecationWarning: this class is deprecated; use the class from the python package `memory_allocator` - See https://github.com/sagemath/sage/issues/31591 for details. - 1 - 16 - """ - deprecation(31591, "this class is deprecated; use the class from the python package `memory_allocator`") - self.n = 0 - self.size = 16 - self.pointers = self.static_pointers - - cdef int resize(self, size_t new_size) except -1: - r""" - Resize the list of pointers to contain ``new_size`` elements. - - It is required that ``new_size`` is at least ``self.n``, but - this condition is not checked. - """ - cdef size_t i - if self.pointers == self.static_pointers: - # Case 1: allocate pointers for the first time - self.pointers = check_allocarray(new_size, sizeof(void*)) - for i in range(self.n): - self.pointers[i] = self.static_pointers[i] - else: - # Case 2: resize pointers - self.pointers = check_reallocarray(self.pointers, new_size, sizeof(void*)) - self.size = new_size - - cdef void** find_pointer(self, void* ptr) except NULL: - r""" - Return the address in the list of stored pointers where ``ptr`` - is stored. If ``ptr`` is not found in the existing pointers and - ``ptr`` is not ``NULL``, then an exception is raised. If ``ptr`` - is ``NULL``, then we simply add ``NULL`` as an additional - pointer and return the address of that. - """ - cdef size_t i = 0 - for i in range(self.n): - if self.pointers[i] == ptr: - return &self.pointers[i] - if ptr != NULL: - raise ValueError("given pointer not found in MemoryAllocator") - self.enlarge_if_needed() - addr = &self.pointers[self.n] - self.n += 1 - return addr - - cdef void* malloc(self, size_t size) except? NULL: - r""" - Returns a new pointer and stores it to be automatically freed later. - """ - self.enlarge_if_needed() - cdef void* val = check_malloc(size) - self.pointers[self.n] = val - self.n += 1 - return val - - cdef void* calloc(self, size_t nmemb, size_t size) except? NULL: - r""" - Returns a new pointer and stores it to be automatically freed later. - """ - self.enlarge_if_needed() - cdef void* val = check_calloc(nmemb, size) - self.pointers[self.n] = val - self.n += 1 - return val - - cdef void* allocarray(self, size_t nmemb, size_t size) except? NULL: - r""" - Returns a new pointer and stores it to be automatically freed later. - """ - self.enlarge_if_needed() - cdef void* val = check_allocarray(nmemb, size) - self.pointers[self.n] = val - self.n += 1 - return val - - cdef void* realloc(self, void* ptr, size_t size) except? NULL: - r""" - Re-allocates `ptr` and automatically frees it later. - - TESTS:: - - sage: cython(''' - ....: from sage.ext.memory_allocator cimport MemoryAllocator - ....: def test_realloc_good(): - ....: cdef MemoryAllocator mem = MemoryAllocator() - ....: ptr = mem.malloc(20) - ....: mem.realloc(ptr, 21) - ....: def test_realloc_NULL(): - ....: cdef MemoryAllocator mem = MemoryAllocator() - ....: mem.realloc(NULL, 21) - ....: def test_realloc_bad(): - ....: cdef MemoryAllocator mem = MemoryAllocator() - ....: cdef MemoryAllocator mem2 = MemoryAllocator() - ....: ptr = mem.malloc(20) - ....: mem2.realloc(ptr, 21) - ....: ''') - sage: test_realloc_good() # random # might raise deprecation warning - sage: test_realloc_good() - sage: test_realloc_NULL() - sage: test_realloc_bad() - Traceback (most recent call last): - ... - ValueError: given pointer not found in MemoryAllocator - """ - cdef void** addr = self.find_pointer(ptr) - cdef void* val = check_realloc(ptr, size) - addr[0] = val - return val - - cdef void* reallocarray(self, void* ptr, size_t nmemb, - size_t size) except? NULL: - r""" - Re-allocates `ptr` and automatically frees it later. - """ - cdef void** addr = self.find_pointer(ptr) - cdef void* val = check_reallocarray(ptr, nmemb, size) - addr[0] = val - return val - - def __dealloc__(self): - r""" - Free the allocated resources - - EXAMPLES:: - - sage: from sage.ext.memory_allocator import MemoryAllocator - sage: _ = MemoryAllocator() - """ - cdef size_t i - for i in range(self.n): - sig_free(self.pointers[i]) - if self.pointers != self.static_pointers: - sig_free(self.pointers) diff --git a/src/sage/features/__init__.py b/src/sage/features/__init__.py index a8b11ef7db5..ebeb55527a8 100644 --- a/src/sage/features/__init__.py +++ b/src/sage/features/__init__.py @@ -948,7 +948,10 @@ def _is_present(self): # Available since https://setuptools.pypa.io/en/latest/history.html#v59-0-0 from setuptools.errors import CCompilerError except ImportError: - from distutils.errors import CCompilerError + try: + from distutils.errors import CCompilerError + except ImportError: + CCompilerError = () with open(tmp_filename(ext=".pyx"), 'w') as pyx: pyx.write(self.test_code) try: diff --git a/src/sage/features/sagemath.py b/src/sage/features/sagemath.py index 75a925895c5..2001f793a2f 100644 --- a/src/sage/features/sagemath.py +++ b/src/sage/features/sagemath.py @@ -43,6 +43,31 @@ from .join_feature import JoinFeature +class SAGE_SRC(StaticFile): + r""" + A :class:`~sage.features.Feature` which describes the presence of the + monolithic source tree of the Sage library. + + """ + def __init__(self): + r""" + TESTS:: + + sage: from sage.features.sagemath import SAGE_SRC + sage: isinstance(SAGE_SRC(), SAGE_SRC) + True + """ + from sage.env import SAGE_SRC + # We check the file bin/sage-src-env-config.in, which by design is: + # - never installed, + # - not included in the sagemath-standard sdist, + # - included only in one modularized sdist, of pkgs/sage-conf_pypi, + # where it appears in a subdirectory (sage_root/src/bin/) + StaticFile.__init__(self, 'SAGE_SRC', + filename='bin/sage-src-env-config.in', + search_path=(SAGE_SRC,) if SAGE_SRC else ()) + + class sagemath_doc_html(StaticFile): r""" A :class:`~sage.features.Feature` which describes the presence of the documentation @@ -1095,7 +1120,8 @@ def all_features(): sage: list(all_features()) [...Feature('sage.combinat'), ...] """ - return [sagemath_doc_html(), + return [SAGE_SRC(), + sagemath_doc_html(), sage__combinat(), sage__geometry__polyhedron(), sage__graphs(), diff --git a/src/sage/functions/error.py b/src/sage/functions/error.py index 76509ce5ef0..db6312af807 100644 --- a/src/sage/functions/error.py +++ b/src/sage/functions/error.py @@ -265,7 +265,7 @@ def _evalf_(self, x, parent=None, algorithm=None): sage: gp.set_real_precision(59) # random # needs sage.libs.pari 38 - sage: print(gp.eval("1 - erfc(1)")); print(erf(1).n(200)) # needs sage.libs.pari + sage: print(gp.eval("1 - erfc(1)")); print(erf(1).n(200)) # needs mpmath sage.libs.pari 0.84270079294971486934122063508260925929606699796630290845994 0.84270079294971486934122063508260925929606699796630290845994 diff --git a/src/sage/functions/exp_integral.py b/src/sage/functions/exp_integral.py index 913cb577bef..b5638f8d35f 100644 --- a/src/sage/functions/exp_integral.py +++ b/src/sage/functions/exp_integral.py @@ -1470,7 +1470,7 @@ def exponential_integral_1(x, n=0): EXAMPLES:: - sage: # needs sage.libs.pari + sage: # needs sage.libs.pari sage.rings.real_mpfr sage: exponential_integral_1(2) 0.0489005107080611 sage: exponential_integral_1(2, 4) # abs tol 1e-18 @@ -1519,7 +1519,7 @@ def exponential_integral_1(x, n=0): ....: if e >= c: ....: print("exponential_integral_1(%s, %s)[%s] with precision %s has error of %s >= %s"%(a, n, i, prec, e, c)) - ALGORITHM: use the PARI C-library function ``eint1``. + ALGORITHM: use the PARI C-library function :pari:`eint1`. REFERENCE: diff --git a/src/sage/functions/gamma.py b/src/sage/functions/gamma.py index 0af99e5dc20..be2d60d0861 100644 --- a/src/sage/functions/gamma.py +++ b/src/sage/functions/gamma.py @@ -40,9 +40,9 @@ def __init__(self): EXAMPLES:: sage: from sage.functions.gamma import gamma1 - sage: gamma1(CDF(0.5, 14)) # needs sage.libs.pari + sage: gamma1(CDF(0.5, 14)) # needs sage.libs.pari sage.rings.complex_double -4.0537030780372815e-10 - 5.773299834553605e-10*I - sage: gamma1(CDF(I)) # needs sage.libs.pari sage.symbolic + sage: gamma1(CDF(I)) # needs sage.libs.pari sage.rings.complex_double sage.symbolic -0.15494982830181067 - 0.49801566811835607*I Recall that `\Gamma(n)` is `n-1` factorial:: @@ -99,7 +99,7 @@ def __init__(self): 1*x^(-2) + (-2*euler_gamma)*x^(-1) + (2*euler_gamma^2 + 1/6*pi^2) + Order(x) - To prevent automatic evaluation use the ``hold`` argument:: + To prevent automatic evaluation, use the ``hold`` argument:: sage: gamma1(1/2, hold=True) # needs sage.symbolic gamma(1/2) @@ -138,9 +138,9 @@ def __init__(self): Infinity sage: (-1.).gamma() # needs sage.rings.real_mpfr NaN - sage: CC(-1).gamma() # needs sage.libs.pari + sage: CC(-1).gamma() # needs sage.libs.pari sage.rings.real_mpfr Infinity - sage: RDF(-1).gamma() + sage: RDF(-1).gamma() # needs sage.rings.real_mpfr NaN sage: CDF(-1).gamma() # needs sage.libs.pari sage.rings.complex_double Infinity @@ -691,9 +691,9 @@ def gamma(a, *args, **kwds): :: - sage: gamma(CDF(I)) # needs sage.libs.pari sage.symbolic + sage: gamma(CDF(I)) # needs sage.libs.pari sage.rings.complex_double sage.symbolic -0.15494982830181067 - 0.49801566811835607*I - sage: gamma(CDF(0.5, 14)) # needs sage.libs.pari + sage: gamma(CDF(0.5, 14)) # needs sage.libs.pari sage.rings.complex_double -4.0537030780372815e-10 - 5.773299834553605e-10*I Use ``numerical_approx`` to get higher precision from @@ -721,7 +721,8 @@ def gamma(a, *args, **kwds): sage: gamma(i) # needs sage.rings.number_field sage.symbolic Traceback (most recent call last): ... - TypeError: cannot coerce arguments: no canonical coercion from Number Field in i with defining polynomial x^2 + 1 to Symbolic Ring + TypeError: cannot coerce arguments: no canonical coercion + from Number Field in i with defining polynomial x^2 + 1 to Symbolic Ring .. SEEALSO:: @@ -1004,9 +1005,9 @@ def __init__(self): INPUT: - - ``p`` - number or symbolic expression + - ``p`` -- number or symbolic expression - - ``q`` - number or symbolic expression + - ``q`` -- number or symbolic expression OUTPUT: number or symbolic expression (if input is symbolic) @@ -1016,18 +1017,18 @@ def __init__(self): sage: # needs sage.symbolic sage: beta(3, 2) 1/12 - sage: beta(3,1) + sage: beta(3, 1) 1/3 sage: beta(1/2, 1/2) beta(1/2, 1/2) - sage: beta(-1,1) + sage: beta(-1, 1) -1 - sage: beta(-1/2,-1/2) + sage: beta(-1/2, -1/2) 0 sage: ex = beta(x/2, 3) sage: set(ex.operands()) == set([1/2*x, 3]) True - sage: beta(.5,.5) + sage: beta(.5, .5) 3.14159265358979 sage: beta(1, 2.0+I) 0.400000000000000 - 0.200000000000000*I diff --git a/src/sage/functions/generalized.py b/src/sage/functions/generalized.py index 5e66d267828..ba8222121b9 100644 --- a/src/sage/functions/generalized.py +++ b/src/sage/functions/generalized.py @@ -290,7 +290,7 @@ class FunctionUnitStep(GinacFunction): INPUT: - - ``x`` - a real number or a symbolic expression + - ``x`` -- a real number or a symbolic expression DEFINITION: @@ -331,7 +331,7 @@ def __init__(self): INPUT: - - ``x`` - a real number or a symbolic expression + - ``x`` -- a real number or a symbolic expression EXAMPLES:: @@ -378,7 +378,7 @@ class FunctionSignum(BuiltinFunction): INPUT: - - ``x`` - a real number or a symbolic expression + - ``x`` -- a real number or a symbolic expression DEFINITION: @@ -548,8 +548,8 @@ class FunctionKroneckerDelta(BuiltinFunction): INPUT: - - ``m`` - a number or a symbolic expression - - ``n`` - a number or a symbolic expression + - ``m`` -- a number or a symbolic expression + - ``n`` -- a number or a symbolic expression DEFINITION: @@ -560,9 +560,9 @@ class FunctionKroneckerDelta(BuiltinFunction): EXAMPLES:: - sage: kronecker_delta(1,2) # needs sage.rings.complex_interval_field + sage: kronecker_delta(1, 2) # needs sage.rings.complex_interval_field 0 - sage: kronecker_delta(1,1) # needs sage.rings.complex_interval_field + sage: kronecker_delta(1, 1) # needs sage.rings.complex_interval_field 1 sage: m, n = var('m,n') # needs sage.symbolic sage: kronecker_delta(m, n) # needs sage.symbolic @@ -579,9 +579,9 @@ def __init__(self): EXAMPLES:: - sage: kronecker_delta(1,2) # needs sage.rings.complex_interval_field + sage: kronecker_delta(1, 2) # needs sage.rings.complex_interval_field 0 - sage: kronecker_delta(1,1) # needs sage.rings.complex_interval_field + sage: kronecker_delta(1, 1) # needs sage.rings.complex_interval_field 1 sage: y = var('y') # needs sage.symbolic sage: kronecker_delta(x, y)._sympy_() # needs sympy sage.symbolic @@ -598,9 +598,9 @@ def _eval_(self, m, n): EXAMPLES:: - sage: kronecker_delta(1,2) # needs sage.rings.complex_interval_field + sage: kronecker_delta(1, 2) # needs sage.rings.complex_interval_field 0 - sage: kronecker_delta(1,1) # needs sage.rings.complex_interval_field + sage: kronecker_delta(1, 1) # needs sage.rings.complex_interval_field 1 Kronecker delta is a symmetric function. We keep arguments sorted to diff --git a/src/sage/functions/hyperbolic.py b/src/sage/functions/hyperbolic.py index 7c94892ba9d..6098a6de0bb 100644 --- a/src/sage/functions/hyperbolic.py +++ b/src/sage/functions/hyperbolic.py @@ -53,7 +53,7 @@ def __init__(self): EXAMPLES:: - sage: sinh(3.1415) + sage: sinh(3.1415) # needs sage.rings.real_mpfr 11.5476653707437 sage: # needs sage.symbolic @@ -91,7 +91,7 @@ def __init__(self): EXAMPLES:: - sage: cosh(3.1415) + sage: cosh(3.1415) # needs sage.rings.real_mpfr 11.5908832931176 sage: # needs sage.symbolic @@ -129,9 +129,9 @@ def __init__(self): EXAMPLES:: - sage: tanh(3.1415) + sage: tanh(3.1415) # needs sage.rings.real_mpfr 0.996271386633702 - sage: tan(3.1415/4) + sage: tan(3.1415/4) # needs sage.rings.real_mpfr 0.999953674278156 sage: # needs sage.symbolic @@ -197,7 +197,7 @@ def __init__(self): EXAMPLES:: - sage: coth(3.1415) + sage: coth(3.1415) # needs sage.rings.real_mpfr 1.00374256795520 sage: coth(complex(1, 2)) # abs tol 1e-15 # needs sage.rings.complex_double (0.8213297974938518+0.17138361290918508j) @@ -256,7 +256,7 @@ def __init__(self): EXAMPLES:: - sage: sech(3.1415) + sage: sech(3.1415) # needs sage.rings.real_mpfr 0.0862747018248192 sage: # needs sage.symbolic @@ -313,7 +313,7 @@ def __init__(self): EXAMPLES:: - sage: csch(3.1415) + sage: csch(3.1415) # needs sage.rings.real_mpfr 0.0865975907592133 sage: # needs sage.symbolic @@ -375,7 +375,7 @@ def __init__(self): sage: asinh arcsinh - sage: asinh(0.5) + sage: asinh(0.5) # needs sage.rings.real_mpfr 0.481211825059603 sage: asinh(1/2) # needs sage.symbolic arcsinh(1/2) @@ -527,7 +527,7 @@ def __init__(self): EXAMPLES:: - sage: atanh(0.5) + sage: atanh(0.5) # needs sage.rings.real_mpfr 0.549306144334055 sage: atanh(1/2) # needs sage.symbolic 1/2*log(3) diff --git a/src/sage/functions/hypergeometric.py b/src/sage/functions/hypergeometric.py index 6e2b26d284a..010c61febe0 100644 --- a/src/sage/functions/hypergeometric.py +++ b/src/sage/functions/hypergeometric.py @@ -14,13 +14,14 @@ Examples from :issue:`9908`:: + sage: # needs sage.symbolic sage: maxima('integrate(bessel_j(2, x), x)').sage() 1/24*x^3*hypergeometric((3/2,), (5/2, 3), -1/4*x^2) sage: sum(((2*I)^x/(x^3 + 1)*(1/4)^x), x, 0, oo) hypergeometric((1, 1, -1/2*I*sqrt(3) - 1/2, 1/2*I*sqrt(3) - 1/2),... (2, -1/2*I*sqrt(3) + 1/2, 1/2*I*sqrt(3) + 1/2), 1/2*I) sage: res = sum((-1)^x/((2*x + 1)*factorial(2*x + 1)), x, 0, oo) - sage: res # not tested - depends on maxima version + sage: res # not tested (depends on maxima version) hypergeometric((1/2,), (3/2, 3/2), -1/4) sage: res in [hypergeometric((1/2,), (3/2, 3/2), -1/4), sin_integral(1)] True @@ -28,6 +29,7 @@ Simplification (note that ``simplify_full`` does not yet call ``simplify_hypergeometric``):: + sage: # needs sage.symbolic sage: hypergeometric([-2], [], x).simplify_hypergeometric() x^2 - 2*x + 1 sage: hypergeometric([], [], x).simplify_hypergeometric() @@ -41,10 +43,10 @@ Equality testing:: - sage: bool(hypergeometric([], [], x).derivative(x) == + sage: bool(hypergeometric([], [], x).derivative(x) == # needs sage.symbolic ....: hypergeometric([], [], x)) # diff(e^x, x) == e^x True - sage: bool(hypergeometric([], [], x) == hypergeometric([], [1], x)) + sage: bool(hypergeometric([], [], x) == hypergeometric([], [1], x)) # needs sage.symbolic False Computing terms and series:: @@ -79,14 +81,14 @@ 1 + (-1/2)*z^2 + 1/24*z^4 + (-1/720)*z^6 + 1/40320*z^8 +... (-1/3628800)*z^10 + Order(z^11) - sage: hypergeometric([1], [5], x).series(x, 5) + sage: hypergeometric([1], [5], x).series(x, 5) # needs sage.symbolic 1 + 1/5*x + 1/30*x^2 + 1/210*x^3 + 1/1680*x^4 + Order(x^5) sage: sum(hypergeometric([1, 2], [3], 1/3).terms(6)).n() # needs sage.symbolic 1.29788359788360 sage: hypergeometric([1, 2], [3], 1/3).n() # needs sage.symbolic 1.29837194594696 - sage: hypergeometric([], [], x).series(x, 20)(x=1).n() == e.n() + sage: hypergeometric([], [], x).series(x, 20)(x=1).n() == e.n() # needs sage.symbolic True Plotting:: @@ -96,7 +98,7 @@ sage: plot(f, x, -30, 30) # needs sage.plot Graphics object consisting of 1 graphics primitive sage: g(x) = hypergeometric([x], [], 2) - sage: complex_plot(g, (-1, 1), (-1, 1)) + sage: complex_plot(g, (-1, 1), (-1, 1)) # needs sage.plot Graphics object consisting of 1 graphics primitive Numeric evaluation:: @@ -121,7 +123,7 @@ sage: maxima(hypergeometric([1, 1, 1], [3, 3, 3], x)) # needs sage.symbolic hypergeometric([1,1,1],[3,3,3],_SAGE_VAR_x) - sage: hypergeometric((5, 4), (4, 4), 3)._sympy_() # needs sage.symbolic + sage: hypergeometric((5, 4), (4, 4), 3)._sympy_() # needs sympy sage.symbolic hyper((5, 4), (4, 4), 3) sage: hypergeometric((5, 4), (4, 4), 3)._mathematica_init_() # needs sage.symbolic 'HypergeometricPFQ[{5,4},{4,4},3]' @@ -154,7 +156,7 @@ 1 + 1*x + 1/2*x^2 + Order(x^3) sage: hypergeometric_U(2, 2, x).series(x == 3, 100).subs(x=1).n() # needs sage.symbolic 0.403652637676806 - sage: hypergeometric_U(2, 2, 1).n() # needs mpmath + sage: hypergeometric_U(2, 2, 1).n() # needs mpmath sage.symbolic 0.403652637676806 """ @@ -451,7 +453,7 @@ def eliminate_parameters(self, a, b, z): sage: hypergeometric([1, 1, 2, 5], [5, 1, 4], # needs sage.symbolic ....: 1/2).eliminate_parameters() hypergeometric((1, 2), (4,), 1/2) - sage: hypergeometric([x], [x], x).eliminate_parameters() + sage: hypergeometric([x], [x], x).eliminate_parameters() # needs sage.symbolic hypergeometric((), (), x) sage: hypergeometric((5, 4), (4, 4), 3).eliminate_parameters() # needs sage.symbolic hypergeometric((5,), (4,), 3) @@ -538,11 +540,11 @@ def is_terminating(self, a, b, z): EXAMPLES:: - sage: hypergeometric([1, 2], [3, 4], x).is_terminating() + sage: hypergeometric([1, 2], [3, 4], x).is_terminating() # needs sage.symbolic False - sage: hypergeometric([1, -2], [3, 4], x).is_terminating() + sage: hypergeometric([1, -2], [3, 4], x).is_terminating() # needs sage.symbolic True - sage: hypergeometric([1, -2], [], x).is_terminating() + sage: hypergeometric([1, -2], [], x).is_terminating() # needs sage.symbolic True """ if z == 0: @@ -642,11 +644,11 @@ def terms(self, a, b, z, n=None): EXAMPLES:: - sage: list(hypergeometric([-2, 1], [3, 4], x).terms()) + sage: list(hypergeometric([-2, 1], [3, 4], x).terms()) # needs sage.symbolic [1, -1/6*x, 1/120*x^2] - sage: list(hypergeometric([-2, 1], [3, 4], x).terms(2)) + sage: list(hypergeometric([-2, 1], [3, 4], x).terms(2)) # needs sage.symbolic [1, -1/6*x] - sage: list(hypergeometric([-2, 1], [3, 4], x).terms(0)) + sage: list(hypergeometric([-2, 1], [3, 4], x).terms(0)) # needs sage.symbolic [] """ if n is None: @@ -675,8 +677,7 @@ def deflated(self, a, b, z): sage: # needs sage.symbolic sage: x = hypergeometric([6, 1], [3, 4, 5], 10) - sage: y = x.deflated() - sage: y + sage: y = x.deflated(); y 1/252*hypergeometric((4,), (7, 8), 10) + 1/12*hypergeometric((3,), (6, 7), 10) + 1/2*hypergeometric((2,), (5, 6), 10) @@ -687,8 +688,7 @@ def deflated(self, a, b, z): sage: # needs sage.symbolic sage: x = hypergeometric([6, 7], [3, 4, 5], 10) - sage: y = x.deflated() - sage: y + sage: y = x.deflated(); y 25/27216*hypergeometric((), (11,), 10) + 25/648*hypergeometric((), (10,), 10) + 265/504*hypergeometric((), (9,), 10) @@ -710,8 +710,7 @@ def _deflated(self, a, b, z): sage: # needs sage.symbolic sage: x = hypergeometric([5], [4], 3) - sage: y = x.deflated() - sage: y + sage: y = x.deflated(); y 7/4*hypergeometric((), (), 3) sage: x.n(); y.n() 35.1496896155784 @@ -958,21 +957,21 @@ class Hypergeometric_M(BuiltinFunction): EXAMPLES:: - sage: # needs mpmath + + sage: hypergeometric_M(1, 1, 1.) # needs mpmath + 2.71828182845905 + + sage: # needs sage.symbolic sage: hypergeometric_M(1, 1, 1) hypergeometric_M(1, 1, 1) - sage: hypergeometric_M(1, 1, 1.) - 2.71828182845905 - sage: hypergeometric_M(1, 1, 1).n(70) + sage: hypergeometric_M(1, 1, 1).n(70) # needs mpmath 2.7182818284590452354 sage: hypergeometric_M(1, 1, 1).simplify_hypergeometric() e sage: hypergeometric_M(1, 3/2, 1).simplify_hypergeometric() 1/2*sqrt(pi)*erf(1)*e - - sage: hypergeometric_M(1, 1/2, x).simplify_hypergeometric() # needs sage.symbolic + sage: hypergeometric_M(1, 1/2, x).simplify_hypergeometric() (-I*sqrt(pi)*x*erf(I*sqrt(-x))*e^x + sqrt(-x))/sqrt(-x) - """ def __init__(self): r""" @@ -980,7 +979,7 @@ def __init__(self): sage: maxima(hypergeometric_M(1,1,x)) # needs sage.symbolic kummer_m(1,1,_SAGE_VAR_x) - sage: latex(hypergeometric_M(1,1,x)) + sage: latex(hypergeometric_M(1,1,x)) # needs sage.symbolic M\left(1, 1, x\right) """ BuiltinFunction.__init__(self, 'hypergeometric_M', nargs=3, @@ -995,8 +994,8 @@ def _eval_(self, a, b, z, **kwargs): """ TESTS:: - sage: (a,b)=var('a,b') # needs sage.symbolic - sage: hypergeometric_M(a,b,0) # needs sage.symbolic + sage: a, b = var('a,b') # needs sage.symbolic + sage: hypergeometric_M(a, b, 0) # needs sage.symbolic 1 """ if not isinstance(z, Expression) and z == 0: @@ -1007,7 +1006,7 @@ def _evalf_(self, a, b, z, parent, algorithm=None): """ TESTS:: - sage: hypergeometric_M(1,1,1).n() # needs mpmath + sage: hypergeometric_M(1,1,1).n() # needs mpmath sage.symbolic 2.71828182845905 """ return _mpmath_utils_call(_mpmath_hyp1f1, a, b, z, parent=parent) @@ -1016,9 +1015,9 @@ def _derivative_(self, a, b, z, diff_param): """ TESTS:: - sage: diff(hypergeometric_M(1,1,x),x,3) + sage: diff(hypergeometric_M(1, 1, x), x, 3) # needs sage.symbolic hypergeometric_M(4, 4, x) - sage: diff(hypergeometric_M(x,1,1),x,3) + sage: diff(hypergeometric_M(x, 1, 1), x, 3) # needs sage.symbolic Traceback (most recent call last): ... NotImplementedError: derivative of hypergeometric function with respect to parameters @@ -1076,7 +1075,9 @@ class Hypergeometric_U(BuiltinFunction): hypergeometric_U(1, 1, 1) sage: hypergeometric_U(1, 1, 1.) 0.596347362323194 - sage: hypergeometric_U(1, 1, 1).n(70) + + sage: # needs sage.symbolic + sage: hypergeometric_U(1, 1, 1).n(70) # needs mpmath 0.59634736232319407434 sage: hypergeometric_U(10^4, 1/3, 1).n() # needs sage.libs.pari 6.60377008885811e-35745 @@ -1092,9 +1093,9 @@ def __init__(self): r""" TESTS:: - sage: maxima(hypergeometric_U(1,1,x)) # needs sage.symbolic + sage: maxima(hypergeometric_U(1, 1, x)) # needs sage.symbolic kummer_u(1,1,_SAGE_VAR_x) - sage: latex(hypergeometric_U(1,1,x)) + sage: latex(hypergeometric_U(1, 1, x)) # needs sage.symbolic U\left(1, 1, x\right) """ BuiltinFunction.__init__(self, 'hypergeometric_U', nargs=3, @@ -1112,7 +1113,7 @@ def _evalf_(self, a, b, z, parent, algorithm=None): """ TESTS:: - sage: hypergeometric_U(1,1,1).n() # needs mpmath + sage: hypergeometric_U(1, 1, 1).n() # needs mpmath sage.symbolic 0.596347362323194 """ return _mpmath_utils_call(_mpmath_hyperu, a, b, z, parent=parent) @@ -1121,9 +1122,9 @@ def _derivative_(self, a, b, z, diff_param): """ TESTS:: - sage: diff(hypergeometric_U(1,1,x),x,3) + sage: diff(hypergeometric_U(1, 1, x), x, 3) # needs sage.symbolic -6*hypergeometric_U(4, 4, x) - sage: diff(hypergeometric_U(x,1,1),x,3) + sage: diff(hypergeometric_U(x, 1, 1), x, 3) # needs sage.symbolic Traceback (most recent call last): ... NotImplementedError: derivative of hypergeometric function with respect to parameters @@ -1140,13 +1141,14 @@ def generalized(self, a, b, z): EXAMPLES:: - sage: var('a b z') # needs sage.symbolic + sage: # needs sage.symbolic + sage: var('a b z') (a, b, z) - sage: hypergeometric_U(a, b, z).generalized() # needs sage.symbolic + sage: hypergeometric_U(a, b, z).generalized() hypergeometric((a, a - b + 1), (), -1/z)/z^a - sage: hypergeometric_U(1, 3, 1/2).generalized() # needs mpmath + sage: hypergeometric_U(1, 3, 1/2).generalized() 2*hypergeometric((1, -1), (), -2) - sage: hypergeometric_U(3, I, 2).generalized() # needs sage.symbolic + sage: hypergeometric_U(3, I, 2).generalized() 1/8*hypergeometric((3, -I + 4), (), -1/2) """ diff --git a/src/sage/functions/log.py b/src/sage/functions/log.py index 37e5f601e59..903bb7cfe75 100644 --- a/src/sage/functions/log.py +++ b/src/sage/functions/log.py @@ -63,7 +63,7 @@ class Function_exp(GinacFunction): sage: exp(float(2.5)) 12.182493960703473 - sage: exp(RDF('2.5')) + sage: exp(RDF('2.5')) # needs sage.symbolic 12.182493960703473 To prevent automatic evaluation, use the ``hold`` parameter:: diff --git a/src/sage/functions/orthogonal_polys.py b/src/sage/functions/orthogonal_polys.py index 8976001bc62..1de970e0aa4 100644 --- a/src/sage/functions/orthogonal_polys.py +++ b/src/sage/functions/orthogonal_polys.py @@ -439,7 +439,7 @@ class OrthogonalFunction(BuiltinFunction): def __init__(self, name, nargs=2, latex_name=None, conversions=None): """ :class:`OrthogonalFunction` class needs the same input parameter as - it's parent class. + its parent class. EXAMPLES:: @@ -466,7 +466,7 @@ def eval_formula(self, *args): sage: from sage.functions.orthogonal_polys import OrthogonalFunction sage: P = OrthogonalFunction('testo_P') - sage: P.eval_formula(1,2.0) + sage: P.eval_formula(1, 2.0) Traceback (most recent call last): ... NotImplementedError: no explicit calculation of values implemented @@ -543,8 +543,8 @@ class ChebyshevFunction(OrthogonalFunction): """ def __call__(self, n, *args, **kwds): """ - This overides the call method from SageObject to avoid problems with coercions, - since the _eval_ method is able to handle more data types than symbolic functions + This overides the call method from :class:`SageObject` to avoid problems with coercions, + since the ``_eval_`` method is able to handle more data types than symbolic functions would normally allow. Thus we have the distinction between algebraic objects (if n is an integer), and else as symbolic function. @@ -563,7 +563,7 @@ def __call__(self, n, *args, **kwds): Univariate Polynomial Ring in x over Rational Field sage: chebyshev_T(5, 2, hold=True) # needs sage.symbolic chebyshev_T(5, 2) - sage: chebyshev_T(1,2,3) + sage: chebyshev_T(1, 2, 3) Traceback (most recent call last): ... TypeError: Symbolic function chebyshev_T takes exactly 2 arguments (3 given) @@ -599,11 +599,11 @@ def _eval_(self, n, x): chebyshev_T(3/2, x) sage: R. = QQ[] - sage: chebyshev_T(2,t) + sage: chebyshev_T(2, t) 2*t^2 - 1 - sage: chebyshev_U(2,t) + sage: chebyshev_U(2, t) 4*t^2 - 1 - sage: parent(chebyshev_T(4, RIF(5))) + sage: parent(chebyshev_T(4, RIF(5))) # needs sage.rings.real_interval_field Real Interval Field with 53 bits of precision sage: RR2 = RealField(5) # needs sage.rings.real_mpfr sage: chebyshev_T(100000, RR2(2)) # needs sage.rings.real_mpfr @@ -779,8 +779,9 @@ def _evalf_(self, n, x, **kwds): sage: chebyshev_T._evalf_(10^6, 0.1) # needs sage.rings.real_mpfr Traceback (most recent call last): ... - NoConvergence: Hypergeometric series converges too slowly. Try increasing maxterms. - sage: chebyshev_T(10^6, 0.1) + NoConvergence: Hypergeometric series converges too slowly. + Try increasing maxterms. + sage: chebyshev_T(10^6, 0.1) # needs sage.rings.real_mpfr 0.636384327171504 """ try: @@ -976,9 +977,9 @@ class Func_chebyshev_U(ChebyshevFunction): EXAMPLES:: sage: R. = QQ[] - sage: chebyshev_U(2,t) + sage: chebyshev_U(2, t) 4*t^2 - 1 - sage: chebyshev_U(3,t) + sage: chebyshev_U(3, t) 8*t^3 - 4*t """ def __init__(self): @@ -1052,7 +1053,7 @@ def eval_formula(self, n, x): 1 sage: chebyshev_U.eval_formula(1, x) 2*x - sage: chebyshev_U.eval_formula(2,0.1) == chebyshev_U._evalf_(2,0.1) + sage: chebyshev_U.eval_formula(2, 0.1) == chebyshev_U._evalf_(2, 0.1) True """ if n < -1: @@ -1086,9 +1087,9 @@ def eval_algebraic(self, n, x): Ring of integers modulo 9 sage: chebyshev_U(-3, x) + chebyshev_U(1, x) # needs sage.symbolic 0 - sage: chebyshev_U(-1,Mod(5,8)) + sage: chebyshev_U(-1, Mod(5,8)) 0 - sage: parent(chebyshev_U(-1,Mod(5,8))) + sage: parent(chebyshev_U(-1, Mod(5,8))) Ring of integers modulo 8 sage: R. = ZZ[] sage: chebyshev_U.eval_algebraic(-2, t) @@ -1102,8 +1103,10 @@ def eval_algebraic(self, n, x): sage: n = 97; x = RIF(pi/n) # needs sage.symbolic sage: chebyshev_U(n - 1, cos(x)).contains_zero() # needs sage.symbolic True - sage: R. = Zp(2, 6, 'capped-abs')[] # needs sage.rings.padics - sage: chebyshev_U(10^6 + 1, t) # needs sage.rings.padics + + sage: # needs sage.rings.padics + sage: R. = Zp(2, 6, 'capped-abs')[] + sage: chebyshev_U(10^6 + 1, t) (2 + O(2^6))*t + O(2^6) """ if n == -1: @@ -1142,7 +1145,7 @@ def _evalf_(self, n, x, **kwds): EXAMPLES:: - sage: chebyshev_U(5,-4+3.*I) # needs sage.symbolic + sage: chebyshev_U(5, -4 + 3.*I) # needs sage.symbolic 98280.0000000000 - 11310.0000000000*I sage: chebyshev_U(10, 3).n(75) # needs sage.symbolic 4.661117900000000000000e7 @@ -1676,7 +1679,7 @@ class Func_assoc_legendre_P(BuiltinFunction): -sqrt(-x^2 + 1) sage: gen_legendre_P(1, 1, 0.5) # abs tol 1e-14 # needs mpmath -0.866025403784439 - sage: gen_legendre_P.eval_gen_poly(1, 1, 0.5) # abs tol 1e-14 + sage: gen_legendre_P.eval_gen_poly(1, 1, 0.5) # abs tol 1e-14 # needs sage.rings.real_mpfr -0.866025403784439 sage: gen_legendre_P._evalf_(1, 1, 0.5) # abs tol 1e-14 # needs mpmath -0.866025403784439 @@ -1831,7 +1834,7 @@ def _evalf_(self, n, m, x, parent=None, **kwds): sage: gen_legendre_P(10, 2, 3).n() # abs tol 1e-14 # needs sage.symbolic -7.19496360000000e8 - sage: gen_legendre_P(5/2,2,1.+I) # needs sage.symbolic + sage: gen_legendre_P(5/2, 2, 1. + I) # needs sage.symbolic 14.3165258449040 - 12.7850496155152*I sage: gen_legendre_P(5/2, 2, ComplexField(70)(1+I)) # needs sage.rings.real_mpfr sage.symbolic 14.316525844904028532 - 12.785049615515157033*I @@ -2145,9 +2148,9 @@ class Func_jacobi_P(OrthogonalFunction): EXAMPLES:: sage: x = PolynomialRing(QQ, 'x').gen() - sage: jacobi_P(2,0,0,x) + sage: jacobi_P(2, 0, 0, x) # needs sage.libs.flint sage.symbolic 3/2*x^2 - 1/2 - sage: jacobi_P(2,1,2,1.2) # needs sage.symbolic + sage: jacobi_P(2, 1, 2, 1.2) # needs sage.libs.flint 5.01000000000000 """ def __init__(self): @@ -2205,18 +2208,18 @@ def _eval_(self, n, a, b, x): Check that :issue:`17192` is fixed:: sage: x = PolynomialRing(QQ, 'x').gen() - sage: jacobi_P(0,0,0,x) + sage: jacobi_P(0, 0, 0, x) # needs sage.libs.flint sage.symbolic 1 - sage: jacobi_P(-1,0,0,x) + sage: jacobi_P(-1, 0, 0, x) # needs sage.libs.flint sage.symbolic 1 - sage: jacobi_P(-1,1,1,x) + sage: jacobi_P(-1, 1, 1, x) # needs sage.libs.flint sage.symbolic Traceback (most recent call last): ... ValueError: n must be greater than -1, got n = -1 - sage: jacobi_P(-7,0,0,x) + sage: jacobi_P(-7, 0, 0, x) # needs sage.libs.flint sage.symbolic 231/16*x^6 - 315/16*x^4 + 105/16*x^2 - 5/16 - sage: jacobi_P(-7,0,2,x) + sage: jacobi_P(-7, 0, 2, x) # needs sage.symbolic Traceback (most recent call last): ... ValueError: n must be greater than -1, got n = -7 @@ -2324,11 +2327,11 @@ class Func_ultraspherical(GinacFunction): sage: # needs sage.symbolic sage: gegenbauer(2, -3, x) 12*x^2 + 3 - sage: gegenbauer(120,-99/2,3) + sage: gegenbauer(120, -99/2, 3) 1654502372608570682112687530178328494861923493372493824 sage: gegenbauer(5, 9/2, x) 21879/8*x^5 - 6435/4*x^3 + 1287/8*x - sage: gegenbauer(15,3/2,5) + sage: gegenbauer(15, 3/2, 5) 3903412392243800 sage: derivative(gegenbauer(n, a, x), x) # needs sage.symbolic @@ -2484,7 +2487,7 @@ def _pol_laguerre(self, n, x): 1/24*x^4 - 2/3*x^3 + 3*x^2 - 4*x + 1 sage: laguerre(4, x + 1) # needs mpmath 1/24*(x + 1)^4 - 2/3*(x + 1)^3 + 3*(x + 1)^2 - 4*x - 3 - sage: laguerre(10,1+I) # needs sage.symbolic + sage: laguerre(10, 1 + I) # needs sage.symbolic 142511/113400*I + 95867/22680 """ if hasattr(x, 'pyobject'): @@ -2503,7 +2506,7 @@ def _evalf_(self, n, x, **kwds): sage: laguerre(100, RealField(300)(pi)) # needs sage.symbolic -0.638322077840648311606324... - sage: laguerre(10,1.+I) # needs sage.symbolic + sage: laguerre(10, 1. + I) # needs sage.symbolic 4.22694003527337 + 1.25671075837743*I sage: laguerre(-9, 2.) # needs sage.symbolic 1566.22186244286 @@ -2638,7 +2641,7 @@ def _pol_gen_laguerre(self, n, a, x): 1/24*x^4 - 7/12*x^3 + 35/16*x^2 - 35/16*x + 35/128 sage: gen_laguerre(4, -1/2, x + 1) # needs mpmath 1/24*(x + 1)^4 - 7/12*(x + 1)^3 + 35/16*(x + 1)^2 - 35/16*x - 245/128 - sage: gen_laguerre(10, 1, 1+I) # needs sage.symbolic + sage: gen_laguerre(10, 1, 1 + I) # needs sage.symbolic 25189/2100*I + 11792/2835 """ return sum(binomial(n + a, n - k) * (-1)**k / factorial(k) * x**k diff --git a/src/sage/functions/other.py b/src/sage/functions/other.py index 3da7beec8ce..23450acb02e 100644 --- a/src/sage/functions/other.py +++ b/src/sage/functions/other.py @@ -173,7 +173,7 @@ def _eval_floor_ceil(self, x, method, bits=0, **kwds): These do not work but fail gracefully:: - sage: ceil(Infinity) + sage: ceil(Infinity) # needs sage.rings.real_interval_field Traceback (most recent call last): ... ValueError: Calling ceil() on infinity or NaN @@ -714,9 +714,9 @@ def __init__(self): EXAMPLES:: - sage: frac(5.4) + sage: frac(5.4) # needs sage.rings.real_mpfr 0.400000000000000 - sage: type(frac(5.4)) + sage: type(frac(5.4)) # needs sage.rings.real_mpfr sage: frac(456/123) 29/41 @@ -812,9 +812,9 @@ class Function_real_nth_root(BuiltinFunction): For numeric input, it gives a numerical approximation. :: - sage: real_nth_root(2., 3) + sage: real_nth_root(2., 3) # needs sage.rings.real_mpfr 1.25992104989487 - sage: real_nth_root(-2., 3) + sage: real_nth_root(-2., 3) # needs sage.rings.real_mpfr -1.25992104989487 Some symbolic calculus:: @@ -914,9 +914,9 @@ def _eval_(self, base, exp): sage: real_nth_root(x, 3) # needs sage.symbolic real_nth_root(x, 3) - sage: real_nth_root(RIF(2), 3) + sage: real_nth_root(RIF(2), 3) # needs sage.rings.real_interval_field 1.259921049894873? - sage: real_nth_root(RBF(2), 3) + sage: real_nth_root(RBF(2), 3) # needs sage.libs.flint [1.259921049894873 +/- 3.92e-16] """ if not isinstance(base, Expression) and not isinstance(exp, Expression): @@ -1145,9 +1145,9 @@ def __init__(self): sage: real(5/3) 5/3 sage: a = 2.5 - sage: real(a) + sage: real(a) # needs sage.rings.real_mpfr 2.50000000000000 - sage: type(real(a)) + sage: type(real(a)) # needs sage.rings.real_mpfr sage: real(1.0r) 1.0 diff --git a/src/sage/functions/piecewise.py b/src/sage/functions/piecewise.py index f621c324cc4..6e50aef052c 100644 --- a/src/sage/functions/piecewise.py +++ b/src/sage/functions/piecewise.py @@ -113,7 +113,7 @@ def __call__(self, function_pieces, **kwds): OUTPUT: - A piecewise-defined function. A ``ValueError`` will be raised + A piecewise-defined function. A :class:`ValueError` will be raised if the domains of the pieces are not pairwise disjoint. EXAMPLES:: @@ -291,7 +291,7 @@ def _tderivative_(self, parameters, variable, *args, **kwds): EXAMPLES:: - sage: f = piecewise([ [(-1,1), x**2], [(1,3), x**3]]) + sage: f = piecewise([[(-1,1), x**2], [(1,3), x**3]]) sage: f.diff() piecewise(x|-->2*x on (-1, 1), x|-->3*x^2 on (1, 3); x) sage: f.diff(x,x) @@ -300,7 +300,7 @@ def _tderivative_(self, parameters, variable, *args, **kwds): This still fails miserably:: sage: y = SR.var('y') - sage: f = piecewise([ [(-6,0), x+y], [(0,8), x*y]],var=x) + sage: f = piecewise([[(-6,0), x+y], [(0,8), x*y]],var=x) sage: f.derivative(x) # known bug piecewise(x|-->1 on (-6, 0), x|-->y on (0, 8); x) sage: f.derivative(y) # known bug @@ -308,7 +308,7 @@ def _tderivative_(self, parameters, variable, *args, **kwds): TESTS:: - sage: f = piecewise([((-oo, -1),0), ((-1, 1),exp(-1/(1 - x^2))), ((1, oo),0)]) + sage: f = piecewise([((-oo, -1), 0), ((-1, 1), exp(-1/(1 - x^2))), ((1, oo), 0)]) sage: f.diff() piecewise(x|-->0 on (-oo, -1), x|-->-2*x*e^(1/(x^2 - 1))/(x^2 - 1)^2 on (-1, 1), x|-->0 on (1, +oo); x) """ @@ -330,7 +330,7 @@ def __pow__(self, parameters, variable, n): EXAMPLES:: sage: f1(x) = -abs(x) + 1; f2(x) = abs(x - 2) - 1 - sage: f = piecewise([ [(-1,1), f1], [(1,3), f2]]) + sage: f = piecewise([[(-1,1), f1], [(1,3), f2]]) sage: (f^2).integral(definite=True) 4/3 """ @@ -635,9 +635,9 @@ def end_points(self, parameters, variable): EXAMPLES:: sage: f1(x) = 1 - sage: f2(x) = 1-x - sage: f3(x) = x^2-5 - sage: f = piecewise([[(0,1),f1],[(1,2),f2],[(2,3),f3]]) + sage: f2(x) = 1 - x + sage: f3(x) = x^2 - 5 + sage: f = piecewise([[(0,1), f1], [(1,2), f2], [(2,3), f3]]) sage: f.end_points() [0, 1, 2, 3] sage: f = piecewise([([0,0], sin(x)), ((0,2), cos(x))]); f @@ -665,7 +665,9 @@ def piecewise_add(self, parameters, variable, other): sage: f = piecewise([([0,1], 1), ((2,3), x)]) sage: g = piecewise([((1/2, 2), x)]) sage: f.piecewise_add(g).unextend_zero() - piecewise(x|-->1 on (0, 1/2], x|-->x + 1 on (1/2, 1], x|-->x on (1, 2) ∪ (2, 3); x) + piecewise(x|-->1 on (0, 1/2], + x|-->x + 1 on (1/2, 1], + x|-->x on (1, 2) ∪ (2, 3); x) """ points = ([minus_infinity] + sorted(set(self.end_points() + other.end_points())) + @@ -731,7 +733,7 @@ def integral(self, parameters, variable, x=None, a=None, b=None, definite=False, r""" By default, return the indefinite integral of the function. - If definite=True is given, returns the definite integral. + If ``definite=True`` is given, returns the definite integral. AUTHOR: @@ -739,8 +741,8 @@ def integral(self, parameters, variable, x=None, a=None, b=None, definite=False, EXAMPLES:: - sage: f1(x) = 1-x - sage: f = piecewise([((0,1),1), ((1,2),f1)]) + sage: f1(x) = 1 - x + sage: f = piecewise([((0,1), 1), ((1,2), f1)]) sage: f.integral(definite=True) 1/2 @@ -748,7 +750,7 @@ def integral(self, parameters, variable, x=None, a=None, b=None, definite=False, sage: f1(x) = -1 sage: f2(x) = 2 - sage: f = piecewise([((0,pi/2),f1), ((pi/2,pi),f2)]) + sage: f = piecewise([((0,pi/2), f1), ((pi/2,pi), f2)]) sage: f.integral(definite=True) 1/2*pi @@ -764,8 +766,8 @@ def integral(self, parameters, variable, x=None, a=None, b=None, definite=False, sage: f3(y) = -y - 1 sage: f4(y) = y^2 - 1 sage: f5(y) = 3 - sage: f = piecewise([[[-4,-3],f1], [(-3,-2),f2], [[-2,0],f3], - ....: [(0,2),f4], [[2,3],f5]]) + sage: f = piecewise([[[-4,-3], f1], [(-3,-2), f2], [[-2,0], f3], + ....: [(0,2), f4], [[2,3], f5]]) sage: F = f.integral(y); F piecewise(y|-->-y - 4 on [-4, -3], y|-->1/2*y^2 + 3*y + 7/2 on (-3, -2), @@ -795,7 +797,8 @@ def integral(self, parameters, variable, x=None, a=None, b=None, definite=False, sage: f1(y) = (y+3)^2 sage: f2(y) = y+3 sage: f3(y) = 3 - sage: f = piecewise([[(-infinity, -3), f1], [(-3, 0), f2], [(0, infinity), f3]]) + sage: f = piecewise([[(-infinity, -3), f1], [(-3, 0), f2], + ....: [(0, infinity), f3]]) sage: f.integral() piecewise(y|-->1/3*y^3 + 3*y^2 + 9*y + 9 on (-oo, -3), y|-->1/2*y^2 + 3*y + 9/2 on (-3, 0), @@ -823,7 +826,7 @@ def integral(self, parameters, variable, x=None, a=None, b=None, definite=False, Verify that piecewise integrals of zero work (:issue:`10841`):: sage: f0(x) = 0 - sage: f = piecewise([[[0,1],f0]]) + sage: f = piecewise([[[0,1], f0]]) sage: f.integral(x,0,1) 0 sage: f = piecewise([[[0,1], 0]]) @@ -836,9 +839,9 @@ def integral(self, parameters, variable, x=None, a=None, b=None, definite=False, Check that the algorithm keyword can be used:: sage: ex = piecewise([([0, 1], 1), ((1, oo), 1/x**2)]) - sage: integral(ex,x,0,100,algorithm='giac') + sage: integral(ex, x, 0, 100, algorithm='giac') 199/100 - sage: integral(ex,x,algorithm='giac') + sage: integral(ex, x, algorithm='giac') piecewise(x|-->x on [0, 1], x|-->-1/x + 2 on (1, +oo); x) """ if a is not None and b is not None: @@ -901,7 +904,7 @@ def critical_points(self, parameters, variable): sage: f1 = x^0 sage: f2 = 10*x - x^2 sage: f3 = 3*x^4 - 156*x^3 + 3036*x^2 - 26208*x - sage: f = piecewise([[(0,3),f1],[(3,10),f2],[(10,20),f3]]) + sage: f = piecewise([[(0,3), f1], [(3,10), f2], [(10,20), f3]]) sage: expected = [5, 12, 13, 14] sage: all(abs(e-a) < 0.001 for e,a in zip(expected, f.critical_points())) True @@ -914,7 +917,7 @@ def critical_points(self, parameters, variable): sage: f1 = y^0 sage: f2 = 10*y - y^2 sage: f3 = 3*y^4 - 156*y^3 + 3036*y^2 - 26208*y - sage: f = piecewise([[(0,3),f1],[(3,10),f2],[(10,20),f3]]) + sage: f = piecewise([[(0,3), f1], [(3,10), f2], [(10,20), f3]]) sage: expected = [5, 12, 13, 14] sage: all(abs(e-a) < 0.001 for e,a in zip(expected, f.critical_points())) True @@ -940,15 +943,22 @@ def convolution(self, parameters, variable, other): EXAMPLES:: - sage: x = PolynomialRing(QQ,'x').gen() - sage: f = piecewise([[[0,1],1]]) ## example 0 + sage: x = PolynomialRing(QQ, 'x').gen() + + Example 0:: + + sage: f = piecewise([[[0,1], 1]]) sage: g = f.convolution(f); g - piecewise(x|-->x on (0, 1], x|-->-x + 2 on (1, 2]; x) + piecewise(x|-->x on (0, 1], + x|-->-x + 2 on (1, 2]; x) sage: h = f.convolution(g); h piecewise(x|-->1/2*x^2 on (0, 1], x|-->-x^2 + 3*x - 3/2 on (1, 2], x|-->1/2*x^2 - 3*x + 9/2 on (2, 3]; x) - sage: f = piecewise([[(0,1),1], [(1,2),2], [(2,3),1]]) ## example 1 + + Example 1:: + + sage: f = piecewise([[(0,1), 1], [(1,2), 2], [(2,3), 1]]) sage: g = f.convolution(f) sage: h = f.convolution(g); h piecewise(x|-->1/2*x^2 on (0, 1], @@ -958,13 +968,16 @@ def convolution(self, parameters, variable, other): x|-->-2*x^2 + 15*x - 15/2 on (5, 6], x|-->2*x^2 - 33*x + 273/2 on (6, 8], x|-->1/2*x^2 - 9*x + 81/2 on (8, 9]; x) - sage: f = piecewise([[(-1,1),1]]) ## example 2 - sage: g = piecewise([[(0,3),x]]) + + Example 2:: + + sage: f = piecewise([[(-1,1), 1]]) + sage: g = piecewise([[(0,3), x]]) sage: f.convolution(g) piecewise(x|-->1/2*x^2 + x + 1/2 on (-1, 1], x|-->2*x on (1, 2], x|-->-1/2*x^2 + x + 4 on (2, 4]; x) - sage: g = piecewise([[(0,3),1], [(3,4),2]]) + sage: g = piecewise([[(0,3), 1], [(3,4), 2]]) sage: f.convolution(g) piecewise(x|-->x + 1 on (-1, 1], x|-->2 on (1, 2], @@ -1037,7 +1050,8 @@ def trapezoid(self, parameters, variable, N): EXAMPLES:: - sage: f = piecewise([[[0,1], x^2], [RealSet.open_closed(1,2), 5-x^2]]) + sage: f = piecewise([[[0,1], x^2], + ....: [RealSet.open_closed(1,2), 5 - x^2]]) sage: f.trapezoid(2) piecewise(x|-->1/2*x on (0, 1/2), x|-->3/2*x - 1/2 on (1/2, 1), @@ -1056,8 +1070,8 @@ def trapezoid(self, parameters, variable, N): sage: R. = QQ[] sage: f1 = y^2 - sage: f2 = 5-y^2 - sage: f = piecewise([[[0,1],f1], [RealSet.open_closed(1,2),f2]]) + sage: f2 = 5 - y^2 + sage: f = piecewise([[[0,1], f1], [RealSet.open_closed(1,2), f2]]) sage: f.trapezoid(2) piecewise(y|-->1/2*y on (0, 1/2), y|-->3/2*y - 1/2 on (1/2, 1), @@ -1082,14 +1096,14 @@ def func(x0, x1): def laplace(self, parameters, variable, x='x', s='t'): r""" - Returns the Laplace transform of self with respect to the variable + Return the Laplace transform of ``self`` with respect to the variable var. INPUT: - - ``x`` - variable of self + - ``x`` -- variable of ``self`` - - ``s`` - variable of Laplace transform. + - ``s`` -- variable of Laplace transform. We assume that a piecewise function is 0 outside of its domain and that the left-most endpoint of the domain is 0. @@ -1097,7 +1111,7 @@ def laplace(self, parameters, variable, x='x', s='t'): EXAMPLES:: sage: x, s, w = var('x, s, w') - sage: f = piecewise([[(0,1),1], [[1,2], 1 - x]]) + sage: f = piecewise([[(0,1), 1], [[1,2], 1 - x]]) sage: f.laplace(x, s) -e^(-s)/s + (s + 1)*e^(-2*s)/s^2 + 1/s - e^(-s)/s^2 sage: f.laplace(x, w) @@ -1116,8 +1130,8 @@ def laplace(self, parameters, variable, x='x', s='t'): sage: t = var('t') sage: f1(t) = -t sage: f2(t) = 2 - sage: f = piecewise([[[0,1],f1], [(1,infinity),f2]]) - sage: f.laplace(t,s) + sage: f = piecewise([[[0,1], f1], [(1,infinity), f2]]) + sage: f.laplace(t, s) (s + 1)*e^(-s)/s^2 + 2*e^(-s)/s - 1/s^2 """ from sage.symbolic.assumptions import assume, forget @@ -1172,7 +1186,7 @@ def fourier_series_cosine_coefficient(self, parameters, A triangle wave function of period 2:: - sage: f = piecewise([((0,1), x), ((1,2), 2-x)]) + sage: f = piecewise([((0,1), x), ((1,2), 2 - x)]) sage: f.fourier_series_cosine_coefficient(0) 1 sage: f.fourier_series_cosine_coefficient(3) @@ -1203,13 +1217,13 @@ def fourier_series_cosine_coefficient(self, parameters, Other examples:: sage: f(x) = x^2 - sage: f = piecewise([[(-1,1),f]]) + sage: f = piecewise([[(-1,1), f]]) sage: f.fourier_series_cosine_coefficient(2) pi^(-2) sage: f1(x) = -1 sage: f2(x) = 2 - sage: f = piecewise([[(-pi,pi/2),f1],[(pi/2,pi),f2]]) - sage: f.fourier_series_cosine_coefficient(5,pi) + sage: f = piecewise([[(-pi, pi/2), f1], [(pi/2, pi), f2]]) + sage: f.fourier_series_cosine_coefficient(5, pi) -3/5/pi """ diff --git a/src/sage/functions/spike_function.py b/src/sage/functions/spike_function.py index 4739ca7ffb4..c2fb6113e39 100644 --- a/src/sage/functions/spike_function.py +++ b/src/sage/functions/spike_function.py @@ -71,7 +71,7 @@ def __init__(self, v, eps=0.0000001): A spike function with spikes at [-3.0, -1.0, 2.0] sage: S.height [4.0, 1.0, 3.0] - sage: S.eps + sage: S.eps # needs sage.rings.real_mpfr 0.00100000000000000 """ if not v: diff --git a/src/sage/functions/transcendental.py b/src/sage/functions/transcendental.py index c0debecdc0d..236646bae52 100644 --- a/src/sage/functions/transcendental.py +++ b/src/sage/functions/transcendental.py @@ -173,7 +173,7 @@ def __init__(self): INPUT: - - ``n`` - non-negative integer + - ``n`` -- non-negative integer EXAMPLES:: @@ -265,11 +265,11 @@ def _evalf_(self, s, x, parent=None, algorithm=None): r""" TESTS:: - sage: hurwitz_zeta(11/10, 1/2).n() # needs sage.symbolic + sage: hurwitz_zeta(11/10, 1/2).n() # needs mpmath sage.symbolic 12.1038134956837 - sage: hurwitz_zeta(11/10, 1/2).n(100) # needs sage.symbolic + sage: hurwitz_zeta(11/10, 1/2).n(100) # needs mpmath sage.symbolic 12.103813495683755105709077413 - sage: hurwitz_zeta(11/10, 1 + 1j).n() + sage: hurwitz_zeta(11/10, 1 + 1j).n() # needs mpmath sage.rings.real_mpfr 9.85014164287853 - 1.06139499403981*I """ return _mpmath_utils_call(_mpmath_zeta, s, x, parent=parent) @@ -571,48 +571,54 @@ def power_series(self, n, abs_prec): """ This function returns the power series about `n+1/2` used to evaluate Dickman's function. It is scaled such that the interval - `[n,n+1]` corresponds to x in `[-1,1]`. + `[n,n+1]` corresponds to `x` in `[-1,1]`. INPUT: - - ``n`` - the lower endpoint of the interval for which + - ``n`` -- the lower endpoint of the interval for which this power series holds - - ``abs_prec`` - the absolute precision of the + - ``abs_prec`` -- the absolute precision of the resulting power series EXAMPLES:: + sage: # needs sage.rings.real_mpfr sage: f = dickman_rho.power_series(2, 20); f - -9.9376e-8*x^11 + 3.7722e-7*x^10 - 1.4684e-6*x^9 + 5.8783e-6*x^8 - 0.000024259*x^7 + 0.00010341*x^6 - 0.00045583*x^5 + 0.0020773*x^4 - 0.0097336*x^3 + 0.045224*x^2 - 0.11891*x + 0.13032 + -9.9376e-8*x^11 + 3.7722e-7*x^10 - 1.4684e-6*x^9 + 5.8783e-6*x^8 + - 0.000024259*x^7 + 0.00010341*x^6 - 0.00045583*x^5 + 0.0020773*x^4 + - 0.0097336*x^3 + 0.045224*x^2 - 0.11891*x + 0.13032 sage: f(-1), f(0), f(1) (0.30685, 0.13032, 0.048608) - sage: dickman_rho(2), dickman_rho(2.5), dickman_rho(3) # needs sage.symbolic + sage: dickman_rho(2), dickman_rho(2.5), dickman_rho(3) (0.306852819440055, 0.130319561832251, 0.0486083882911316) """ return self._compute_power_series(n, abs_prec, cache_ring=None) def _compute_power_series(self, n, abs_prec, cache_ring=None): """ - Compute the power series giving Dickman's function on [n, n+1], by - recursion in n. For internal use; self.power_series() is a wrapper + Compute the power series giving Dickman's function on `[n, n+1]`, by + recursion in `n`. For internal use; ``self.power_series()`` is a wrapper around this intended for the user. INPUT: - - ``n`` - the lower endpoint of the interval for which + - ``n`` -- the lower endpoint of the interval for which this power series holds - - ``abs_prec`` - the absolute precision of the + - ``abs_prec`` -- the absolute precision of the resulting power series - - ``cache_ring`` - for internal use, caches the power + - ``cache_ring`` -- for internal use, caches the power series at this precision. EXAMPLES:: + sage: # needs sage.rings.real_mpfr sage: f = dickman_rho.power_series(2, 20); f - -9.9376e-8*x^11 + 3.7722e-7*x^10 - 1.4684e-6*x^9 + 5.8783e-6*x^8 - 0.000024259*x^7 + 0.00010341*x^6 - 0.00045583*x^5 + 0.0020773*x^4 - 0.0097336*x^3 + 0.045224*x^2 - 0.11891*x + 0.13032 + -9.9376e-8*x^11 + 3.7722e-7*x^10 - 1.4684e-6*x^9 + 5.8783e-6*x^8 + - 0.000024259*x^7 + 0.00010341*x^6 - 0.00045583*x^5 + 0.0020773*x^4 + - 0.0097336*x^3 + 0.045224*x^2 - 0.11891*x + 0.13032 """ if n <= 1: if n <= -1: diff --git a/src/sage/functions/trig.py b/src/sage/functions/trig.py index d4e723ec6e2..b767fbbd5d4 100644 --- a/src/sage/functions/trig.py +++ b/src/sage/functions/trig.py @@ -210,6 +210,7 @@ def __init__(self): EXAMPLES:: + sage: # needs sage.rings.real_mpfr sage: tan(3.1415) -0.0000926535900581913 sage: tan(3.1415/4) @@ -323,20 +324,20 @@ def __init__(self): TESTS:: - sage: cot(float(0)) # needs sage.symbolic + sage: # needs sage.symbolic + sage: cot(float(0)) Infinity - sage: cot(SR(0)) # needs sage.symbolic + sage: cot(SR(0)) Infinity - sage: cot(float(0.1)) # needs sage.symbolic + sage: cot(float(0.1)) 9.966644423259238 sage: type(_) <... 'float'> - - sage: cot(float(0)) # needs sage.symbolic + sage: cot(float(0)) Infinity - sage: cot(SR(0)) # needs sage.symbolic + sage: cot(SR(0)) Infinity - sage: cot(float(0.1)) # needs sage.symbolic + sage: cot(float(0.1)) 9.966644423259238 sage: type(_) <... 'float'> @@ -520,7 +521,7 @@ def __init__(self): EXAMPLES:: - sage: arcsin(0.5) + sage: arcsin(0.5) # needs sage.rings.real_mpfr 0.523598775598299 sage: arcsin(1/2) # needs sage.symbolic 1/6*pi @@ -584,7 +585,7 @@ def __init__(self): EXAMPLES:: - sage: arccos(0.5) + sage: arccos(0.5) # needs sage.rings.real_mpfr 1.04719755119660 sage: arccos(1/2) # needs sage.symbolic 1/3*pi @@ -940,7 +941,7 @@ def __init__(self): sage: maxima.atan2(1, -1) # needs sage.symbolic (3*%pi)/4 - sage: math.atan2(1,-1) + sage: math.atan2(1, -1) 2.356194490192345 More examples:: diff --git a/src/sage/functions/wigner.py b/src/sage/functions/wigner.py index 0bafe13a246..5a19010f073 100644 --- a/src/sage/functions/wigner.py +++ b/src/sage/functions/wigner.py @@ -616,11 +616,11 @@ def gaunt(l_1, l_2, l_3, m_1, m_2, m_3, prec=None): It is an error to use non-integer values for `l` or `m`:: - sage: gaunt(1.2,0,1.2,0,0,0) + sage: gaunt(1.2,0,1.2,0,0,0) # needs sage.rings.real_mpfr Traceback (most recent call last): ... TypeError: Attempt to coerce non-integral RealNumber to Integer - sage: gaunt(1,0,1,1.1,0,-1.1) + sage: gaunt(1,0,1,1.1,0,-1.1) # needs sage.rings.real_mpfr Traceback (most recent call last): ... TypeError: Attempt to coerce non-integral RealNumber to Integer diff --git a/src/sage/game_theory/gambit_docs.py b/src/sage/game_theory/gambit_docs.py deleted file mode 100644 index b2d8af991e9..00000000000 --- a/src/sage/game_theory/gambit_docs.py +++ /dev/null @@ -1,139 +0,0 @@ -""" -Using Gambit as a standalone package - -This file contains some information and tests for the use of -`Gambit `_ as a stand alone package. - -To install gambit as an optional package run (from root of Sage):: - - $ sage -i gambit - -The `python API documentation for gambit -`_ shows various examples -that can be run easily in IPython. To run the IPython packaged with Sage run -(from root of Sage):: - - $ ./sage --ipython - -Here is an example that constructs the Prisoner's Dilemma:: - - In [1]: import gambit - In [2]: g = gambit.Game.new_table([2,2]) - In [3]: g.title = "A prisoner's dilemma game" - In [4]: g.players[0].label = "Alphonse" - In [5]: g.players[1].label = "Gaston" - In [6]: g - Out[6]: - NFG 1 R "A prisoner's dilemma game" { "Alphonse" "Gaston" } - - { { "1" "2" } - { "1" "2" } - } - "" - - { - { "" 0, 0 } - { "" 0, 0 } - { "" 0, 0 } - { "" 0, 0 } - } - 1 2 3 4 - - In [7]: g.players[0].strategies - Out[7]: [, - ] - In [8]: len(g.players[0].strategies) - Out[8]: 2 - - In [9]: g.players[0].strategies[0].label = "Cooperate" - In [10]: g.players[0].strategies[1].label = "Defect" - In [11]: g.players[0].strategies - Out[11]: [, - ] - - In [12]: g[0,0][0] = 8 - In [13]: g[0,0][1] = 8 - In [14]: g[0,1][0] = 2 - In [15]: g[0,1][1] = 10 - In [16]: g[1,0][0] = 10 - In [17]: g[1,1][1] = 2 - In [18]: g[1,0][1] = 2 - In [19]: g[1,1][0] = 5 - In [20]: g[1,1][1] = 5 - -Here is a list of the various solvers available in gambit: - -- ExternalEnumPureSolver -- ExternalEnumMixedSolver -- ExternalLPSolver -- ExternalLCPSolver -- ExternalSimpdivSolver -- ExternalGlobalNewtonSolver -- ExternalEnumPolySolver -- ExternalLyapunovSolver -- ExternalIteratedPolymatrixSolver -- ExternalLogitSolver - -Here is how to use the ``ExternalEnumPureSolver``:: - - In [21]: solver = gambit.nash.ExternalEnumPureSolver() - In [22]: solver.solve(g) - Out[22]: [] - -Note that the above finds the equilibria by investigating all potential pure -pure strategy pairs. This will fail to find all Nash equilibria in certain -games. For example here is an implementation of Matching Pennies:: - - In [1]: import gambit - In [2]: g = gambit.Game.new_table([2,2]) - In [3]: g[0, 0][0] = 1 - In [4]: g[0, 0][1] = -1 - In [5]: g[0, 1][0] = -1 - In [6]: g[0, 1][1] = 1 - In [7]: g[1, 0][0] = -1 - In [8]: g[1, 0][1] = 1 - In [9]: g[1, 1][0] = 1 - In [10]: g[1, 1][1] = -1 - In [11]: solver = gambit.nash.ExternalEnumPureSolver() - In [12]: solver.solve(g) - Out[12]: [] - -If we solve this with the ``LCP`` solver we get the expected Nash equilibrium:: - - In [13]: solver = gambit.nash.ExternalLCPSolver() - In [14]: solver.solve(g) - Out[14]: [] - -Note that the above examples only show how to build and find equilibria for -two player strategic form games. Gambit supports multiple player games as well -as extensive form games: for more details see http://www.gambit-project.org/. - -If one really wants to use gambit directly in Sage (without using the -``NormalFormGame`` class as a wrapper) then integers must first be -converted to Python integers (due to the preparser). Here is an example -showing the Battle of the Sexes:: - - sage: # optional - gambit - sage: import gambit - sage: g = gambit.Game.new_table([2,2]) - sage: g[int(0), int(0)][int(0)] = int(2) - sage: g[int(0), int(0)][int(1)] = int(1) - sage: g[int(0), int(1)][int(0)] = int(0) - sage: g[int(0), int(1)][int(1)] = int(0) - sage: g[int(1), int(0)][int(0)] = int(0) - sage: g[int(1), int(0)][int(1)] = int(0) - sage: g[int(1), int(1)][int(0)] = int(1) - sage: g[int(1), int(1)][int(1)] = int(2) - sage: solver = gambit.nash.ExternalLCPSolver() - sage: solver.solve(g) - [, - , - ] - -AUTHOR: - -- Vince Knight (11-2014): Original version - -""" diff --git a/src/sage/game_theory/normal_form_game.py b/src/sage/game_theory/normal_form_game.py index b053f3160b8..93cfe25a883 100644 --- a/src/sage/game_theory/normal_form_game.py +++ b/src/sage/game_theory/normal_form_game.py @@ -236,8 +236,7 @@ `Gambit `_ [Gambit]_. At present this is the only gambit algorithm available in sage but further development will hope to implement more algorithms - (in particular for games with more than 2 players). To install it, - type ``sage -i gambit`` in the shell. + (in particular for games with more than 2 players). * ``'enumeration'``: Support enumeration for 2 player games. This algorithm is hard coded in Sage and checks through all potential @@ -648,7 +647,6 @@ from sage.matrix.constructor import vector from sage.misc.temporary_file import tmp_filename from sage.numerical.mip import MixedIntegerLinearProgram -from sage.misc.package import PackageNotFoundError from sage.cpython.string import bytes_to_str try: @@ -1705,7 +1703,7 @@ def obtain_nash(self, algorithm=False, maximization=True, solver=None): if algorithm == "LCP": if Game is None: - raise PackageNotFoundError("gambit") + raise RuntimeError("gambit not found") # should later become a FeatureNotFoundError return self._solve_LCP(maximization) if algorithm.startswith('lp'): diff --git a/src/sage/geometry/all.py b/src/sage/geometry/all.py index 3b7dcf756d8..e4b4d933bc4 100644 --- a/src/sage/geometry/all.py +++ b/src/sage/geometry/all.py @@ -16,4 +16,5 @@ lazy_import('sage.geometry.voronoi_diagram', 'VoronoiDiagram') lazy_import('sage.geometry.ribbon_graph', 'RibbonGraph') lazy_import('sage.geometry.hyperplane_arrangement.arrangement', 'HyperplaneArrangements') +lazy_import('sage.geometry.hyperplane_arrangement.ordered_arrangement', 'OrderedHyperplaneArrangements') lazy_import('sage.geometry.hyperplane_arrangement.library', 'hyperplane_arrangements') diff --git a/src/sage/geometry/cone.py b/src/sage/geometry/cone.py index fc0b3731945..6dabca389f9 100644 --- a/src/sage/geometry/cone.py +++ b/src/sage/geometry/cone.py @@ -211,7 +211,6 @@ from sage.combinat.posets.posets import FinitePoset from sage.geometry.point_collection import PointCollection from sage.geometry.polyhedron.constructor import Polyhedron -from sage.geometry.polyhedron.base import is_Polyhedron from sage.geometry.hasse_diagram import lattice_from_incidences from sage.geometry.toric_lattice import (ToricLattice, is_ToricLattice, is_ToricLatticeQuotient) @@ -259,6 +258,9 @@ def is_Cone(x): sage: from sage.geometry.cone import is_Cone sage: is_Cone(1) + doctest:warning... + DeprecationWarning: is_Cone is deprecated, use isinstance instead + See https://github.com/sagemath/sage/issues/34307 for details. False sage: quadrant = Cone([(1,0), (0,1)]) sage: quadrant @@ -266,6 +268,8 @@ def is_Cone(x): sage: is_Cone(quadrant) True """ + from sage.misc.superseded import deprecation + deprecation(34307, "is_Cone is deprecated, use isinstance instead") return isinstance(x, ConvexRationalPolyhedralCone) @@ -439,7 +443,7 @@ def Cone(rays, lattice=None, check=True, normalize=True): 0-d cone in 2-d lattice N """ # Cone from Polyhedron - if is_Polyhedron(rays): + if isinstance(rays, sage.geometry.abc.Polyhedron): polyhedron = rays if lattice is None: lattice = ToricLattice(polyhedron.ambient_dim()) @@ -1894,7 +1898,7 @@ def cartesian_product(self, other, lattice=None): N+N(0, 1) in 2-d lattice N+N """ - assert is_Cone(other) + assert isinstance(other, sage.geometry.abc.ConvexRationalPolyhedralCone) rc = super().cartesian_product(other, lattice) return ConvexRationalPolyhedralCone(rc.rays(), rc.lattice()) @@ -1952,7 +1956,7 @@ def __richcmp__(self, right, op): sage: c2 is c3 False """ - if is_Cone(right): + if isinstance(right, sage.geometry.abc.ConvexRationalPolyhedralCone): # We don't care about particular type of right in this case return richcmp((self.lattice(), self.rays()), (right.lattice(), right.rays()), op) @@ -2413,7 +2417,7 @@ def embed(self, cone): ValueError: 2-d cone in 3-d lattice N is not a face of 3-d cone in 3-d lattice N! """ - assert is_Cone(cone) + assert isinstance(cone, sage.geometry.abc.ConvexRationalPolyhedralCone) if cone.ambient() is self: return cone if self.is_strictly_convex(): @@ -2935,7 +2939,7 @@ def facet_of(self): L = self._ambient._face_lattice_function() H = L.hasse_diagram() return self._sort_faces( - f for f in H.neighbors_out(L(self)) if is_Cone(f)) + f for f in H.neighbors_out(L(self)) if isinstance(f, sage.geometry.abc.ConvexRationalPolyhedralCone)) def facets(self): r""" @@ -6387,9 +6391,8 @@ def random_cone(lattice=None, min_ambient_dim=0, max_ambient_dim=None, It's hard to test the output of a random process, but we can at least make sure that we get a cone back:: - sage: from sage.geometry.cone import is_Cone sage: K = random_cone(max_ambient_dim=6, max_rays=10) - sage: is_Cone(K) + sage: isinstance(K, sage.geometry.abc.ConvexRationalPolyhedralCone) True The upper/lower bounds are respected:: diff --git a/src/sage/geometry/cone_catalog.py b/src/sage/geometry/cone_catalog.py index d6898d40087..a56de80b412 100644 --- a/src/sage/geometry/cone_catalog.py +++ b/src/sage/geometry/cone_catalog.py @@ -4,6 +4,7 @@ This module provides shortcut functions, grouped under the globally-available ``cones`` prefix, to create some common cones: +- The downward-monotone cone, - The nonnegative orthant, - The rearrangement cone of order ``p``, - The Schur cone, @@ -18,6 +19,15 @@ Here are some typical usage examples:: + sage: cones.downward_monotone(3).rays() + N( 1, 0, 0), + N( 1, 1, 0), + N( 1, 1, 1), + N(-1, -1, -1) + in 3-d lattice N + +:: + sage: cones.nonnegative_orthant(2).rays() N(1, 0), N(0, 1) @@ -150,6 +160,143 @@ def _preprocess_args(ambient_dim, lattice): return (ambient_dim, lattice) +def downward_monotone(ambient_dim=None, lattice=None): + r""" + The downward-monotone cone in ``ambient_dim`` dimensions, or + living in ``lattice``. + + The elements of the downward-monotone cone are vectors whose + components are arranged in non-increasing order. Vectors whose + entries are arranged in the reverse (non-decreasing) order are + sometimes called isotone vectors, and are used in statistics + for isotonic regression. + + The downward-monotone cone is the dual of the Schur cone. It + is also often referred to as the downward-monotone cone. + + INPUT: + + - ``ambient_dim`` -- a nonnegative integer (default: ``None``); the + dimension of the ambient space + + - ``lattice`` -- a toric lattice (default: ``None``); the lattice in + which the cone will live + + If ``ambient_dim`` is omitted, then it will be inferred from the + rank of ``lattice``. If the ``lattice`` is omitted, then the + default lattice of rank ``ambient_dim`` will be used. + + A :class:`ValueError` is raised if neither ``ambient_dim`` nor + ``lattice`` are specified. It is also a :class:`ValueError` to + specify both ``ambient_dim`` and ``lattice`` unless the rank of + ``lattice`` is equal to ``ambient_dim``. + + OUTPUT: + + A :class:`~sage.geometry.cone.ConvexRationalPolyhedralCone` living + in ``lattice`` whose elements' entries are arranged in + nonincreasing order. Each generating ray has the integer ring as + its base ring. + + A :class:`ValueError` can be raised if the inputs are incompatible + or insufficient. See the INPUT documentation for details. + + .. SEEALSO:: + + :func:`schur` + + REFERENCES: + + - [GS2010]_, Section 3.1 + + - [Niez1998]_, Example 2.2 + + EXAMPLES: + + The entries of the elements of the downward-monotone cone are in + non-increasing order:: + + sage: ambient_dim = ZZ.random_element(10) + sage: K = cones.downward_monotone(ambient_dim) + sage: all( x[i] >= x[i + 1] + ....: for i in range(ambient_dim - 1) + ....: for x in K.rays() ) + True + sage: x = K.random_element() + sage: all( x[i] >= x[i + 1] for i in range(ambient_dim - 1) ) + True + + A nontrivial downward-monotone cone is solid but not proper, + since it contains both the vector of all ones and its negation; + that, however, is the only subspace it contains:: + + sage: ambient_dim = ZZ.random_element(1,10) + sage: K = cones.downward_monotone(ambient_dim) + sage: K.is_solid() + True + sage: K.is_proper() + False + sage: K.lineality() + 1 + + The dual of the downward-monotone cone is the Schur cone + [GS2010]_ that induces the majorization preordering:: + + sage: ambient_dim = ZZ.random_element(10) + sage: K = cones.downward_monotone(ambient_dim).dual() + sage: J = cones.schur(ambient_dim, K.lattice()) + sage: K.is_equivalent(J) + True + + TESTS: + + We can construct the trivial cone as the downward-monotone cone + in a trivial vector space:: + + sage: cones.downward_monotone(0) + 0-d cone in 0-d lattice N + + If a ``lattice`` was given, it is actually used:: + + sage: L = ToricLattice(3, 'M') + sage: cones.downward_monotone(lattice=L) + 3-d cone in 3-d lattice M + + Unless the rank of the lattice disagrees with ``ambient_dim``:: + + sage: L = ToricLattice(1, 'M') + sage: cones.downward_monotone(3, lattice=L) + Traceback (most recent call last): + ... + ValueError: lattice rank=1 and ambient_dim=3 are incompatible + + We also get an error if no arguments are given:: + + sage: cones.downward_monotone() + Traceback (most recent call last): + ... + ValueError: either the ambient dimension or the lattice must + be specified + """ + from sage.geometry.cone import Cone + from sage.matrix.constructor import matrix + from sage.rings.integer_ring import ZZ + + ambient_dim, lattice = _preprocess_args(ambient_dim, lattice) + + # The generators for this cone are mentioned in Niezgoda's + # Example 2.2 if you don't want to compute them yourself. + G = matrix.identity(ZZ, ambient_dim) + for i in range(1, ambient_dim): + G.add_multiple_of_row(i, i - 1, 1) + + if G.nrows() > 0: + # Special case for when the ambient space is trivial. + G = G.insert_row(ambient_dim, -1*G.row(-1)) + + return Cone(G.rows(), lattice) + + def nonnegative_orthant(ambient_dim=None, lattice=None): r""" The nonnegative orthant in ``ambient_dim`` dimensions, or living @@ -170,20 +317,20 @@ def nonnegative_orthant(ambient_dim=None, lattice=None): rank of ``lattice``. If the ``lattice`` is omitted, then the default lattice of rank ``ambient_dim`` will be used. - A ``ValueError`` is raised if neither ``ambient_dim`` nor - ``lattice`` are specified. It is also a ``ValueError`` to specify - both ``ambient_dim`` and ``lattice`` unless the rank of + A :class:`ValueError` is raised if neither ``ambient_dim`` nor + ``lattice`` are specified. It is also a :class:`ValueError` to + specify both ``ambient_dim`` and ``lattice`` unless the rank of ``lattice`` is equal to ``ambient_dim``. OUTPUT: - A :class:`~sage.geometry.cone.ConvexRationalPolyhedralCone` living in ``lattice`` - and having ``ambient_dim`` standard basis vectors as its - generators. Each generating ray has the integer ring as its + A :class:`~sage.geometry.cone.ConvexRationalPolyhedralCone` living + in ``lattice`` and having ``ambient_dim`` standard basis vectors + as its generators. Each generating ray has the integer ring as its base ring. - A ``ValueError`` can be raised if the inputs are incompatible or - insufficient. See the INPUT documentation for details. + A :class:`ValueError` can be raised if the inputs are incompatible + or insufficient. See the INPUT documentation for details. REFERENCES: @@ -238,7 +385,7 @@ def nonnegative_orthant(ambient_dim=None, lattice=None): from sage.matrix.constructor import matrix from sage.rings.integer_ring import ZZ - (ambient_dim, lattice) = _preprocess_args(ambient_dim, lattice) + ambient_dim, lattice = _preprocess_args(ambient_dim, lattice) I = matrix.identity(ZZ, ambient_dim) return Cone(I.rows(), lattice) @@ -278,22 +425,22 @@ def rearrangement(p, ambient_dim=None, lattice=None): rank of ``lattice``. If the ``lattice`` is omitted, then the default lattice of rank ``ambient_dim`` will be used. - A ``ValueError`` is raised if neither ``ambient_dim`` nor - ``lattice`` are specified. It is also a ``ValueError`` to specify - both ``ambient_dim`` and ``lattice`` unless the rank of + A :class:`ValueError` is raised if neither ``ambient_dim`` nor + ``lattice`` are specified. It is also a :class:`ValueError` to + specify both ``ambient_dim`` and ``lattice`` unless the rank of ``lattice`` is equal to ``ambient_dim``. - It is also a ``ValueError`` to specify a non-integer ``p``. + It is also a :class:`ValueError` to specify a non-integer ``p``. OUTPUT: - A :class:`~sage.geometry.cone.ConvexRationalPolyhedralCone` representing the - rearrangement cone of order ``p`` living in ``lattice``, with - ambient dimension ``ambient_dim``. Each generating ray has the - integer ring as its base ring. + A :class:`~sage.geometry.cone.ConvexRationalPolyhedralCone` + representing the rearrangement cone of order ``p`` living in + ``lattice``, with ambient dimension ``ambient_dim``. Each + generating ray has the integer ring as its base ring. - A ``ValueError`` can be raised if the inputs are incompatible or - insufficient. See the INPUT documentation for details. + A :class:`ValueError` can be raised if the inputs are incompatible + or insufficient. See the INPUT documentation for details. ALGORITHM: @@ -464,7 +611,7 @@ def rearrangement(p, ambient_dim=None, lattice=None): from sage.matrix.constructor import matrix from sage.rings.integer_ring import ZZ - (ambient_dim, lattice) = _preprocess_args(ambient_dim, lattice) + ambient_dim, lattice = _preprocess_args(ambient_dim, lattice) if p < 1 or p > ambient_dim or p not in ZZ: raise ValueError("order p=%s should be an integer between 1 " @@ -481,11 +628,11 @@ def schur(ambient_dim=None, lattice=None): The Schur cone in ``ambient_dim`` dimensions, or living in ``lattice``. - The Schur cone in `n` dimensions induces the majorization ordering - on the ambient space. If `\left\{e_{1}, e_{2}, \ldots, + The Schur cone in `n` dimensions induces the majorization + preordering on the ambient space. If `\left\{e_{1}, e_{2}, \ldots, e_{n}\right\}` is the standard basis for the space, then its generators are `\left\{e_{i} - e_{i+1}\ |\ 1 \le i \le - n-1\right\}`. Its dual is the downward monotonic cone. + n-1\right\}`. Its dual is the downward monotone cone. INPUT: @@ -499,19 +646,24 @@ def schur(ambient_dim=None, lattice=None): rank of ``lattice``. If the ``lattice`` is omitted, then the default lattice of rank ``ambient_dim`` will be used. - A ``ValueError`` is raised if neither ``ambient_dim`` nor - ``lattice`` are specified. It is also a ``ValueError`` to specify - both ``ambient_dim`` and ``lattice`` unless the rank of + A :class:`ValueError` is raised if neither ``ambient_dim`` nor + ``lattice`` are specified. It is also a :class:`ValueError` to + specify both ``ambient_dim`` and ``lattice`` unless the rank of ``lattice`` is equal to ``ambient_dim``. OUTPUT: - A :class:`~sage.geometry.cone.ConvexRationalPolyhedralCone` representing the Schur - cone living in ``lattice``, with ambient dimension ``ambient_dim``. - Each generating ray has the integer ring as its base ring. + A :class:`~sage.geometry.cone.ConvexRationalPolyhedralCone` + representing the Schur cone living in ``lattice``, with ambient + dimension ``ambient_dim``. Each generating ray has the integer + ring as its base ring. + + A :class:`ValueError` can be raised if the inputs are incompatible + or insufficient. See the INPUT documentation for details. + + .. SEEALSO:: - A ``ValueError`` can be raised if the inputs are incompatible or - insufficient. See the INPUT documentation for details. + :func:`downward_monotone` REFERENCES: @@ -537,13 +689,13 @@ def schur(ambient_dim=None, lattice=None): sage: abs(actual - expected).n() < 1e-12 True - The dual of the Schur cone is the "downward monotonic cone" + The dual of the Schur cone is the downward-monotone cone [GS2010]_, whose elements' entries are in non-increasing order:: sage: ambient_dim = ZZ.random_element(10) sage: K = cones.schur(ambient_dim).dual() - sage: x = K.random_element() - sage: all( x[i] >= x[i+1] for i in range(ambient_dim-1) ) + sage: J = cones.downward_monotone(ambient_dim, K.lattice()) + sage: K.is_equivalent(J) True TESTS: @@ -553,21 +705,25 @@ def schur(ambient_dim=None, lattice=None): sage: cones.schur(0).is_trivial() True - The Schur cone induces the majorization ordering, as in Iusem - and Seeger's [IS2005]_ Example 7.3:: + The Schur cone induces the majorization preordering, as in Iusem + and Seeger's [IS2005]_ Example 7.3 or Niezgoda's [Niez1998]_ + Example 2.2:: + sage: ambient_dim = ZZ.random_element(10) + sage: V = VectorSpace(QQ, ambient_dim) + sage: rearrange = lambda z: V(sorted(z.list(),reverse=True)) sage: def majorized_by(x,y): + ....: x = rearrange(x) + ....: y = rearrange(y) ....: return (all(sum(x[0:i]) <= sum(y[0:i]) ....: for i in range(x.degree()-1)) ....: and sum(x) == sum(y)) - sage: ambient_dim = ZZ.random_element(10) - sage: V = VectorSpace(QQ, ambient_dim) sage: S = cones.schur(ambient_dim) sage: majorized_by(V.zero(), S.random_element()) True sage: x = V.random_element() sage: y = V.random_element() - sage: majorized_by(x,y) == ( (y-x) in S ) + sage: majorized_by(x,y) == ((rearrange(y) - rearrange(x)) in S) True If a ``lattice`` was given, it is actually used:: @@ -596,7 +752,7 @@ def schur(ambient_dim=None, lattice=None): from sage.matrix.constructor import matrix from sage.rings.integer_ring import ZZ - (ambient_dim, lattice) = _preprocess_args(ambient_dim, lattice) + ambient_dim, lattice = _preprocess_args(ambient_dim, lattice) def _f(i,j): if i == j: @@ -629,19 +785,19 @@ def trivial(ambient_dim=None, lattice=None): rank of ``lattice``. If the ``lattice`` is omitted, then the default lattice of rank ``ambient_dim`` will be used. - A ``ValueError`` is raised if neither ``ambient_dim`` nor - ``lattice`` are specified. It is also a ``ValueError`` to specify - both ``ambient_dim`` and ``lattice`` unless the rank of + A :class:`ValueError` is raised if neither ``ambient_dim`` nor + ``lattice`` are specified. It is also a :class:`ValueError` to + specify both ``ambient_dim`` and ``lattice`` unless the rank of ``lattice`` is equal to ``ambient_dim``. OUTPUT: - A :class:`~sage.geometry.cone.ConvexRationalPolyhedralCone` representing the - trivial cone with no nonzero generators living in ``lattice``, - with ambient dimension ``ambient_dim``. + A :class:`~sage.geometry.cone.ConvexRationalPolyhedralCone` + representing the trivial cone with no nonzero generators living in + ``lattice``, with ambient dimension ``ambient_dim``. - A ``ValueError`` can be raised if the inputs are incompatible or - insufficient. See the INPUT documentation for details. + A :class:`ValueError` can be raised if the inputs are incompatible + or insufficient. See the INPUT documentation for details. EXAMPLES: @@ -684,6 +840,6 @@ def trivial(ambient_dim=None, lattice=None): """ from sage.geometry.cone import Cone - (ambient_dim, lattice) = _preprocess_args(ambient_dim, lattice) + ambient_dim, lattice = _preprocess_args(ambient_dim, lattice) return Cone([], lattice) diff --git a/src/sage/geometry/fan.py b/src/sage/geometry/fan.py index 832a76bb65d..0fffcdeb58c 100644 --- a/src/sage/geometry/fan.py +++ b/src/sage/geometry/fan.py @@ -238,6 +238,8 @@ from copy import copy from warnings import warn +import sage.geometry.abc + from sage.structure.richcmp import richcmp_method, richcmp from sage.combinat.combination import Combinations from sage.combinat.posets.posets import FinitePoset @@ -245,7 +247,6 @@ Cone, ConvexRationalPolyhedralCone, IntegralRayCollection, - is_Cone, normalize_rays) from sage.geometry.hasse_diagram import lattice_from_incidences from sage.geometry.point_collection import PointCollection @@ -571,7 +572,7 @@ def result(): cones = ((), ) rays = () return result() - if is_Cone(cones[0]): + if isinstance(cones[0], sage.geometry.abc.ConvexRationalPolyhedralCone): # Construct the fan from Cone objects if lattice is None: lattice = cones[0].lattice() @@ -749,11 +750,10 @@ def FaceFan(polytope, lattice=None): ValueError: face fans are defined only for polytopes containing the origin as an interior point! """ - from sage.geometry.lattice_polytope import is_LatticePolytope interior_point_error = ValueError( "face fans are defined only for polytopes containing " "the origin as an interior point!") - if is_LatticePolytope(polytope): + if isinstance(polytope, sage.geometry.abc.LatticePolytope): if any(d <= 0 for d in polytope.distances([0] * polytope.dim())): raise interior_point_error cones = (f.ambient_vertex_indices() for f in polytope.facets()) @@ -843,8 +843,7 @@ def NormalFan(polytope, lattice=None): """ dimension_error = ValueError( 'the normal fan is only defined for full-dimensional polytopes') - from sage.geometry.lattice_polytope import is_LatticePolytope - if is_LatticePolytope(polytope): + if isinstance(polytope, sage.geometry.abc.LatticePolytope): if polytope.dim() != polytope.lattice_dim(): raise dimension_error rays = polytope.facet_normals() @@ -2425,7 +2424,7 @@ def embed(self, cone): ValueError: 2-d cone in 3-d lattice N does not belong to Rational polyhedral fan in 3-d lattice N! """ - if not is_Cone(cone): + if not isinstance(cone, sage.geometry.abc.ConvexRationalPolyhedralCone): raise TypeError("%s is not a cone!" % cone) if cone.ambient() is self: return cone diff --git a/src/sage/geometry/fan_morphism.py b/src/sage/geometry/fan_morphism.py index 6f4fa78c5a3..c12f8135027 100644 --- a/src/sage/geometry/fan_morphism.py +++ b/src/sage/geometry/fan_morphism.py @@ -88,8 +88,7 @@ from sage.misc.latex import latex from sage.misc.misc import walltime from sage.misc.misc_c import prod -from sage.modules.free_module_morphism import (FreeModuleMorphism, - is_FreeModuleMorphism) +from sage.modules.free_module_morphism import FreeModuleMorphism from sage.rings.infinity import Infinity from sage.rings.integer_ring import ZZ from sage.rings.infinity import is_Infinite @@ -276,7 +275,7 @@ def __init__(self, morphism, domain_fan, codomain, codomain_fan = codomain.lattice(), codomain else: codomain_fan = None - if is_FreeModuleMorphism(morphism): + if isinstance(morphism, FreeModuleMorphism): parent = morphism.parent() A = morphism.matrix() elif is_Matrix(morphism): diff --git a/src/sage/geometry/hyperplane_arrangement/arrangement.py b/src/sage/geometry/hyperplane_arrangement/arrangement.py index bb1e04efabe..19ae8698f8c 100644 --- a/src/sage/geometry/hyperplane_arrangement/arrangement.py +++ b/src/sage/geometry/hyperplane_arrangement/arrangement.py @@ -242,7 +242,7 @@ \chi(x) := \sum_{w\in P} \mu(w) x^{dim(w)} -where the sum is `P` is the +where `P` is the :meth:`~HyperplaneArrangementElement.intersection_poset` of the arrangement and `\mu` is the Möbius function of `P`:: @@ -335,7 +335,7 @@ arrangements. """ -#***************************************************************************** +# ***************************************************************************** # Copyright (C) 2013 David Perkinson # Volker Braun # @@ -344,25 +344,24 @@ # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # http://www.gnu.org/licenses/ -#***************************************************************************** +# ***************************************************************************** # Possible extensions for hyperplane_arrangement.py: # - the big face lattice # - create ties with the Sage matroid methods # - hyperplane arrangements over other fields +from sage.geometry.hyperplane_arrangement.hyperplane import AmbientVectorSpace, Hyperplane +from sage.matrix.constructor import matrix, vector +from sage.misc.cachefunc import cached_method +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.structure.parent import Parent from sage.structure.element import Element from sage.structure.richcmp import richcmp from sage.structure.unique_representation import UniqueRepresentation -from sage.rings.integer_ring import ZZ -from sage.rings.rational_field import QQ -from sage.misc.cachefunc import cached_method -from sage.matrix.constructor import matrix, vector -from sage.modules.free_module import VectorSpace -from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - -from sage.geometry.hyperplane_arrangement.hyperplane import AmbientVectorSpace, Hyperplane class HyperplaneArrangementElement(Element): @@ -385,10 +384,9 @@ def __init__(self, parent, hyperplanes, check=True, backend=None): - ``hyperplanes`` -- a tuple of hyperplanes - - ``check`` -- boolean (optional; default ``True``); whether - to check input + - ``check`` -- boolean (default: ``True``); whether to check input - - ``backend`` -- string (optional; default: ``None``); the backend to + - ``backend`` -- string (optional); the backend to use for the related polyhedral objects EXAMPLES:: @@ -499,7 +497,7 @@ def hyperplanes(self): OUTPUT: - An integer. + A tuple EXAMPLES:: @@ -659,10 +657,10 @@ def union(self, other): sage: H. = HyperplaneArrangements(QQ) sage: A = H([1,2,3], [0,1,1], [0,1,-1], [1,-1,0], [1,1,0]) sage: B = H([1,1,1], [1,-1,1], [1,0,-1]) - sage: A.union(B) - Arrangement of 8 hyperplanes of dimension 2 and rank 2 - sage: A | B # syntactic sugar + sage: C = A.union(B); C Arrangement of 8 hyperplanes of dimension 2 and rank 2 + sage: C == A | B # syntactic sugar + True A single hyperplane is coerced into a hyperplane arrangement if necessary:: @@ -678,9 +676,10 @@ def union(self, other): Arrangement of 6 hyperplanes of dimension 2 and rank 2 """ P = self.parent() - other = P(other) - hyperplanes = self._hyperplanes + other._hyperplanes - return P(*hyperplanes, backend=self._backend) + other_h = P(other) + hyperplanes = self._hyperplanes + other_h._hyperplanes + result = P(*hyperplanes, backend=self._backend) + return result add_hyperplane = union @@ -713,9 +712,10 @@ def cone(self, variable='t'): OUTPUT: - A new hyperplane arrangement. Its equations consist of - `[0, -d, a_1, \ldots, a_n]` for each `[d, a_1, \ldots, a_n]` in the - original arrangement and the equation `[0, 1, 0, \ldots, 0]`. + A new hyperplane arrangement `L`. + Its equations consist of `[0, -d, a_1, \ldots, a_n]` for each + `[d, a_1, \ldots, a_n]` in the original arrangement and the + equation `[0, 1, 0, \ldots, 0]` (maybe not in this order). .. WARNING:: @@ -724,7 +724,8 @@ def cone(self, variable='t'): no guarantee that the order in which they appear in ``self.hyperplanes()`` will match the order in which their counterparts in ``self.cone()`` will appear in - ``self.cone().hyperplanes()``! + ``self.cone().hyperplanes()``! This warning does not apply + to ordered hyperplane arrangements. EXAMPLES:: @@ -758,7 +759,7 @@ def cone(self, variable='t'): hyperplanes.append([0, 1] + [0] * self.dimension()) P = self.parent() names = (variable,) + P._names - H = HyperplaneArrangements(self.parent().base_ring(), names=names) + H = type(P).__base__(P.base_ring(), names=names) return H(*hyperplanes, backend=self._backend) @cached_method @@ -853,16 +854,16 @@ def intersection_poset(self, element_label="int"): W = Vector space of dimension 2 over Rational Field] """ if element_label == "int": - def update(mapping, val, I): + def update(mapping, val, I0): mapping[val] = len(mapping) elif element_label == "subset": from sage.sets.set import Set - def update(mapping, val, I): + def update(mapping, val, I0): mapping[val] = Set(val) elif element_label == "subspace": - def update(mapping, val, I): - mapping[val] = I + def update(mapping, val, I0): + mapping[val] = I0 else: raise ValueError("invalid element label type") @@ -885,13 +886,13 @@ def update(mapping, val, I): for label, T in cur_level: edges = [] for i, H in enumerate(hyperplanes): - I = H.intersection(T) - if I is not None and I != T: + I0 = H.intersection(T) + if I0 is not None and I0 != T: try: - target = new_level[I] + target = new_level[I0] except KeyError: target = set(label) - new_level[I] = target + new_level[I0] = target target.add(i) edges.append(target) hasse[label] = edges @@ -1216,7 +1217,7 @@ def deletion(self, hyperplanes): raise ValueError('hyperplane is not in the arrangement') return parent(*planes, backend=self._backend) - def restriction(self, hyperplane): + def restriction(self, hyperplane, repetitions=False): r""" Return the restriction to a hyperplane. @@ -1224,10 +1225,13 @@ def restriction(self, hyperplane): - ``hyperplane`` -- a hyperplane of the hyperplane arrangement + - ``repetitions`` -- boolean (default: ``False``); eliminate + repetitions for ordered arrangements + OUTPUT: - The restriction of the hyperplane arrangement to the given - ``hyperplane``. + The restriction `\mathcal{A}_H` of the + hyperplane arrangement `\mathcal{A}` to the given ``hyperplane`` `H`. EXAMPLES:: @@ -1236,8 +1240,12 @@ def restriction(self, hyperplane): Arrangement of 6 hyperplanes of dimension 4 and rank 3 sage: H = A[0]; H Hyperplane 0*u + 0*x + y - z + 0 - sage: R = A.restriction(H); R + sage: R = A.restriction(H); R Arrangement + sage: A.add_hyperplane(z).restriction(z) + Arrangement of 6 hyperplanes of dimension 3 and rank 3 + sage: A.add_hyperplane(u).restriction(u) + Arrangement of 6 hyperplanes of dimension 3 and rank 3 sage: D = A.deletion(H); D Arrangement of 5 hyperplanes of dimension 4 and rank 3 sage: ca = A.characteristic_polynomial() @@ -1281,8 +1289,19 @@ def restriction(self, hyperplane): hyperplanes.append([A, b]) names = list(parent._names) names.pop(pivot) - H = HyperplaneArrangements(parent.base_ring(), names=tuple(names)) - return H(*hyperplanes, signed=False, backend=self._backend) + from sage.geometry.hyperplane_arrangement.ordered_arrangement import OrderedHyperplaneArrangements + if isinstance(parent, OrderedHyperplaneArrangements): + H = OrderedHyperplaneArrangements(parent.base_ring(), names=tuple(names)) + if not repetitions: + L = list(hyperplanes) + hyperplanes = () + for h in L: + if h not in hyperplanes: + hyperplanes += (h,) + else: + H = HyperplaneArrangements(parent.base_ring(), names=tuple(names)) + result = H(*hyperplanes, signed=False, backend=self._backend) + return result def change_ring(self, base_ring): """ @@ -1665,7 +1684,8 @@ def essentialization(self): OUTPUT: - The essentialization as a new hyperplane arrangement. + The essentialization `\mathcal{A}'` of `\mathcal{A}` as a + new hyperplane arrangement. EXAMPLES:: @@ -2091,7 +2111,7 @@ def regions(self): for hyperplane in self: ieq = vector(R, hyperplane.dense_coefficient_list()) - pos_half = Polyhedron(ieqs=[ ieq], base_ring=R, backend=be) + pos_half = Polyhedron(ieqs=[ieq], base_ring=R, backend=be) neg_half = Polyhedron(ieqs=[-ieq], base_ring=R, backend=be) if not regions: # See comment above. @@ -2129,8 +2149,8 @@ def regions(self): else: # In this case, at least one of the vertices is not on the hyperplane. # So we check if any ray or line pokes the hyperplane. - if ( any(ieq[1:]*r[:]*direction < 0 for r in region.rays()) or - any(ieq[1:]*l[:] != 0 for l in region_lines)): + if (any(ieq[1:]*r[:]*direction < 0 for r in region.rays()) or + any(ieq[1:]*ll[:] != 0 for ll in region_lines)): splits = True if splits: @@ -2158,10 +2178,10 @@ def poset_of_regions(self, B=None, numbered_labels=True): INPUT: - - ``B`` -- a region (optional; default: ``None``); if ``None``, then + - ``B`` -- a region (optional); if ``None``, then an arbitrary region is chosen as the base region. - - ``numbered_labels`` -- bool (optional; default: ``True``); if ``True``, + - ``numbered_labels`` -- bool (default: ``True``); if ``True``, then the elements of the poset are numbered. Else they are labelled with the regions themselves. @@ -2345,12 +2365,13 @@ def closed_faces(self, labelled=True): ((-1, -1), A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 2 rays, (-1, -2))] - sage: a = hyperplane_arrangements.braid(3) # needs sage.graphs - sage: a.hyperplanes() # needs sage.graphs + sage: # needs sage.graphs + sage: a = hyperplane_arrangements.braid(3) + sage: a.hyperplanes() (Hyperplane 0*t0 + t1 - t2 + 0, Hyperplane t0 - t1 + 0*t2 + 0, Hyperplane t0 + 0*t1 - t2 + 0) - sage: [(v, F, F.representative_point()) for v, F in a.closed_faces()] # needs sage.graphs + sage: [(v, F, F.representative_point()) for v, F in a.closed_faces()] [((0, 0, 0), A 1-dimensional polyhedron in QQ^3 defined as the convex hull of 1 vertex and 1 line, (0, 0, 0)), ((0, 1, 1), A 2-dimensional polyhedron in QQ^3 defined @@ -2422,7 +2443,7 @@ def closed_faces(self, labelled=True): zero_half = Polyhedron(eqns=[ieq], base_ring=R, backend=be) # ``zero_half`` is the hyperplane ``hyperplane`` itself # (viewed as a polyhedron). - pos_half = Polyhedron(ieqs=[ ieq], base_ring=R, backend=be) + pos_half = Polyhedron(ieqs=[ieq], base_ring=R, backend=be) neg_half = Polyhedron(ieqs=[-ieq], base_ring=R, backend=be) subdivided = [] for signs, face in faces: @@ -2904,8 +2925,9 @@ def whitney_data(self): EXAMPLES:: + sage: # needs sage.combinat sage: A = hyperplane_arrangements.Shi(3) - sage: A.whitney_data() # needs sage.combinat + sage: A.whitney_data() ( [ 1 -6 9] [ 1 6 6] [ 0 6 -15] [ 0 6 15] @@ -3247,7 +3269,7 @@ def orlik_solomon_algebra(self, base_ring=None, ordering=None, **kwds): """ if base_ring is None: base_ring = self.base_ring() - return self.matroid().orlik_solomon_algebra(base_ring, ordering,**kwds) + return self.matroid().orlik_solomon_algebra(base_ring, ordering, **kwds) def orlik_terao_algebra(self, base_ring=None, ordering=None, **kwds): """ @@ -3394,9 +3416,10 @@ def derivation_module_free_chain(self): EXAMPLES:: - sage: W = WeylGroup(['A',3], prefix='s') # needs sage.combinat sage.groups - sage: A = W.long_element().inversion_arrangement() # needs sage.combinat sage.groups - sage: for M in A.derivation_module_free_chain(): print("%s\n"%M) # needs sage.combinat sage.groups + sage: # needs sage.combinat sage.groups + sage: W = WeylGroup(['A',3], prefix='s') + sage: A = W.long_element().inversion_arrangement() + sage: for M in A.derivation_module_free_chain(): print("%s\n"%M) [ 1 0 0] [ 0 1 0] [ 0 0 a3] @@ -3539,9 +3562,10 @@ def derivation_module_basis(self, algorithm="singular"): EXAMPLES:: - sage: W = WeylGroup(['A', 2], prefix='s') # needs sage.combinat sage.groups - sage: A = W.long_element().inversion_arrangement() # needs sage.combinat sage.groups - sage: A.derivation_module_basis() # needs sage.combinat sage.groups + sage: # needs sage.combinat sage.groups + sage: W = WeylGroup(['A', 2], prefix='s') + sage: A = W.long_element().inversion_arrangement() + sage: A.derivation_module_basis() [(a1, a2), (0, a1*a2 + a2^2)] TESTS: @@ -3560,7 +3584,7 @@ def derivation_module_basis(self, algorithm="singular"): ....: else: ....: assert exponents(B) == exponents(Bp) """ - alg = algorithm # prevent possible changes to a global variable + alg = algorithm # prevent possible changes to a global variable if alg == "singular": # import sage.libs.singular.function_factory # syz = sage.libs.singular.function_factory.ff.syz @@ -3575,7 +3599,7 @@ def derivation_module_basis(self, algorithm="singular"): # Check using Saito's criterion if det / f in f.parent().base_ring() and not det.is_zero(): return basis.rows() - except ValueError: # Non-square matrix or det = 0 + except ValueError: # Non-square matrix or det = 0 pass # Check if it is free if not self.is_free(algorithm=alg): @@ -3586,7 +3610,7 @@ def derivation_module_basis(self, algorithm="singular"): if alg == "BC": C = self.derivation_module_free_chain() if C is not None: - if not C: # C is an empty list + if not C: # C is an empty list S = self.parent().ambient_space().symmetric_space() return matrix.identity(S, self.dimension()).rows() from sage.misc.misc_c import prod @@ -3739,15 +3763,15 @@ def _element_constructor_(self, *args, **kwds): hyperplane; alternatively, a single polytope or a single hyperplane arrangement - - ``signed`` -- boolean (optional, default: ``True``); whether to + - ``signed`` -- boolean (default: ``True``); whether to preserve signs of hyperplane equations - - ``warn_duplicates`` -- boolean (optional, default: ``False``); + - ``warn_duplicates`` -- boolean (default: ``False``); whether to issue a warning if duplicate hyperplanes were passed -- note that duplicate hyperplanes are always removed, whether or not there is a warning shown - - ``check`` -- boolean (optional, default: ``True``); whether to + - ``check`` -- boolean (default: ``True``); whether to perform argument checking. EXAMPLES:: diff --git a/src/sage/geometry/hyperplane_arrangement/hyperplane.py b/src/sage/geometry/hyperplane_arrangement/hyperplane.py index 9ca1a7c42ac..dfd1e4c6169 100644 --- a/src/sage/geometry/hyperplane_arrangement/hyperplane.py +++ b/src/sage/geometry/hyperplane_arrangement/hyperplane.py @@ -108,9 +108,10 @@ # http://www.gnu.org/licenses/ # ***************************************************************************** +import sage.geometry.abc -from sage.misc.cachefunc import cached_method from sage.geometry.linear_expression import LinearExpression, LinearExpressionModule +from sage.misc.cachefunc import cached_method class Hyperplane(LinearExpression): @@ -460,9 +461,8 @@ def intersection(self, other): sage: h.intersection(polytopes.cube()) A 2-dimensional polyhedron in QQ^3 defined as the convex hull of 3 vertices """ - from sage.geometry.polyhedron.base import is_Polyhedron from sage.geometry.polyhedron.constructor import Polyhedron - if not is_Polyhedron(other): + if not isinstance(other, sage.geometry.abc.Polyhedron): try: other = other.polyhedron() except AttributeError: diff --git a/src/sage/geometry/hyperplane_arrangement/library.py b/src/sage/geometry/hyperplane_arrangement/library.py index cdfaca2faf0..98b03ceac70 100644 --- a/src/sage/geometry/hyperplane_arrangement/library.py +++ b/src/sage/geometry/hyperplane_arrangement/library.py @@ -156,7 +156,7 @@ def bigraphical(self, G, A=None, K=QQ, names=None): for u, v in G.edge_iterator(labels=False, sort_vertices=False): i = vertex_to_int[u] j = vertex_to_int[v] - hyperplanes.append( x[i] - x[j] - A[i][j]) + hyperplanes.append(x[i] - x[j] - A[i][j]) hyperplanes.append(-x[i] + x[j] - A[j][i]) return H(*hyperplanes) @@ -794,7 +794,7 @@ def Shi(self, data, K=QQ, names=None, m=1): hyperplanes = [] for a in PR: - for const in range(-m+1,m+1): + for const in range(-m + 1, m + 1): hyperplanes.append(sum(a[j]*x[j] for j in range(d))-const) A = H(*hyperplanes) x = polygen(QQ, 'x') diff --git a/src/sage/geometry/hyperplane_arrangement/ordered_arrangement.py b/src/sage/geometry/hyperplane_arrangement/ordered_arrangement.py new file mode 100644 index 00000000000..bb16768e13b --- /dev/null +++ b/src/sage/geometry/hyperplane_arrangement/ordered_arrangement.py @@ -0,0 +1,650 @@ +r""" +Ordered Hyperplane Arrangements + +The :class:`HyperplaneArrangements` orders the hyperplanes in a arrangement +independently of the way the hyperplanes are introduced. The class +:class:`OrderedHyperplaneArrangements` fixes an order specified by +the user. This can be needed for certain properties, e.g., fundamental group with +information about meridians, braid monodromy with information about the strands; +in the future, it may be useful for combinatorial properties. +There are no other differences with usual hyperplane arrangements. + +An ordered arrangement is an arrangement where the hyperplanes are sorted +by the user:: + + sage: H0. = HyperplaneArrangements(QQ) + sage: H0(t0 - t1, t1 - t2, t0 - t2) + Arrangement + sage: H. = OrderedHyperplaneArrangements(QQ) + sage: H(t0 - t1, t1 - t2, t0 - t2) + Arrangement + +Some methods are adapted, e.g., :meth:`~sage.geometry.hyperplane_arrangement.arrangement.HyperplaneArrangementElement.hyperplanes`, +and some new ones are created, regarding +hyperplane sections and fundamental groups:: + + sage: H. = HyperplaneArrangements(QQ) + sage: H1. = OrderedHyperplaneArrangements(QQ) + sage: A1 = H1(x, y); A = H(A1) + sage: A.hyperplanes() + (Hyperplane 0*x + y + 0, Hyperplane x + 0*y + 0) + sage: A1.hyperplanes() + (Hyperplane x + 0*y + 0, Hyperplane 0*x + y + 0) + +We see the differences in :meth:`~sage.geometry.hyperplane_arrangement.arrangement.HyperplaneArrangementElement.union`:: + + sage: H. = HyperplaneArrangements(QQ) + sage: H1. = OrderedHyperplaneArrangements(QQ) + sage: A = H([1,2,3], [0,1,1], [0,1,-1], [1,-1,0], [1,1,0]) + sage: B = H([1,1,1], [1,-1,1], [1,0,-1]) + sage: C = A.union(B) + sage: A1 = H1(A); B1 = H1(B); C1 = A1.union(B1) + sage: [C1.hyperplanes().index(h) for h in C.hyperplanes()] + [0, 5, 6, 1, 2, 3, 7, 4] + +Also in meth:`~sage.geometry.hyperplane_arrangement.arrangement.HyperplaneArrangementElement.cone`:: + + sage: # needs sage.combinat + sage: a. = hyperplane_arrangements.semiorder(3) + sage: H. = OrderedHyperplaneArrangements(QQ) + sage: a1 = H(a) + sage: b = a.cone(); b1 = a1.cone() + sage: [b1.hyperplanes().index(h) for h in b.hyperplanes()] + [0, 2, 4, 6, 1, 3, 5] + +And in :meth:`~sage.geometry.hyperplane_arrangement.arrangement.HyperplaneArrangementElement.restriction`:: + + sage: # needs sage.graphs + sage: A. = hyperplane_arrangements.braid(4) + sage: L. = OrderedHyperplaneArrangements(QQ) + sage: A1 = L(A) + sage: H = A[0]; H + Hyperplane 0*u + 0*x + y - z + 0 + sage: A.restriction(H) + Arrangement + sage: A1.restriction(H) + Arrangement + sage: A1.restriction(H, repetitions=True) + Arrangement of 5 hyperplanes of dimension 3 and rank 2 + +AUTHORS: + +- Enrique Artal (2023-12): initial version + +This module adds some features to the *unordered* one for some +properties which depend on the order. +""" + +# ***************************************************************************** +# Copyright (C) 2023 Enrique Artal +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +# ***************************************************************************** + +from sage.geometry.hyperplane_arrangement.arrangement import HyperplaneArrangementElement +from sage.geometry.hyperplane_arrangement.arrangement import HyperplaneArrangements +from sage.geometry.hyperplane_arrangement.hyperplane import Hyperplane +from sage.matrix.constructor import matrix, vector +from sage.misc.misc_c import prod +from sage.groups.free_group import FreeGroup +from sage.rings.integer_ring import ZZ +from sage.rings.qqbar import QQbar +from sage.schemes.curves.plane_curve_arrangement import AffinePlaneCurveArrangements +from sage.schemes.curves.plane_curve_arrangement import ProjectivePlaneCurveArrangements + + +class OrderedHyperplaneArrangementElement(HyperplaneArrangementElement): + """ + An ordered hyperplane arrangement. + + .. WARNING:: + + You should never create + :class:`OrderedHyperplaneArrangementElement` instances directly, + always use the parent. + """ + def __init__(self, parent, hyperplanes, check=True, backend=None): + """ + Construct an ordered hyperplane arrangement. + + INPUT: + + - ``parent`` -- the parent :class:`OrderedHyperplaneArrangements` + + - ``hyperplanes`` -- a tuple of hyperplanes + + - ``check`` -- boolean (default ``True``); whether + to check input + + - ``backend`` -- string (default: ``None``); the backend to + use for the related polyhedral objects + + EXAMPLES:: + + sage: H. = OrderedHyperplaneArrangements(QQ) + sage: elt = H(x, y); elt + Arrangement + sage: TestSuite(elt).run() + """ + super().__init__(parent, hyperplanes, check=check, backend=backend) + self._affine_fundamental_group = None + self._affine_meridians = None + self._projective_fundamental_group = None + self._projective_meridians = None + + def hyperplane_section(self, proj=True): + r""" + Compute a generic hyperplane section of ``self``. + + INPUT: + + - ``proj`` -- (default: ``True``); if the + ambient space is affine or projective + + OUTPUT: + + An arrangement `\mathcal{A}` obtained by intersecting with a + generic hyperplane + + EXAMPLES:: + + sage: L. = OrderedHyperplaneArrangements(QQ) + sage: L(x, y - 1, z).hyperplane_section() + Traceback (most recent call last): + ... + TypeError: the arrangement is not projective + + sage: # needs sage.graphs + sage: A0. = hyperplane_arrangements.braid(4); A0 + Arrangement of 6 hyperplanes of dimension 4 and rank 3 + sage: L. = OrderedHyperplaneArrangements(QQ) + sage: A = L(A0) + sage: M = A.matroid() + sage: A1 = A.hyperplane_section() + sage: A1 + Arrangement of 6 hyperplanes of dimension 3 and rank 3 + sage: M1 = A1.matroid() + sage: A2 = A1.hyperplane_section(); A2 + Arrangement of 6 hyperplanes of dimension 2 and rank 2 + sage: M2 = A2.matroid() + sage: T1 = M1.truncation() + sage: T1.is_isomorphic(M2) + True + sage: T1.isomorphism(M2) + {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5} + + sage: # needs sage.combinat + sage: a0 = hyperplane_arrangements.semiorder(3); a0 + Arrangement of 6 hyperplanes of dimension 3 and rank 2 + sage: L. = OrderedHyperplaneArrangements(QQ) + sage: a = L(a0) + sage: ca = a.cone() + sage: m = ca.matroid() + sage: a1 = a.hyperplane_section(proj=False) + sage: a1 + Arrangement of 6 hyperplanes of dimension 2 and rank 2 + sage: ca1 = a1.cone() + sage: m1 = ca1.matroid() + sage: m.isomorphism(m1) + {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6} + sage: p0 = hyperplane_arrangements.Shi(4) + sage: L. = OrderedHyperplaneArrangements(QQ) + sage: p = L(p0) + sage: a = p.hyperplane_section(proj=False); a + Arrangement of 12 hyperplanes of dimension 3 and rank 3 + sage: ca = a.cone() + sage: m = ca.matroid().truncation() + sage: a1 = a.hyperplane_section(proj=False); a1 + Arrangement of 12 hyperplanes of dimension 2 and rank 2 + sage: ca1 = a1.cone() + sage: m1 = ca1.matroid() + sage: m1.is_isomorphism(m, {j: j for j in range(13)}) + True + """ + if proj and not self.is_linear(): + raise TypeError('the arrangement is not projective') + n0 = self.dimension() + if not proj: + H = self.cone() + H1 = H.hyperplane_section() + mat = matrix(h.coefficients()[1:] for h in H1) + m = mat.nrows() + for j in range(mat.ncols()): + if mat[m - 1, j] != 0: + mat.swap_columns(0, j) + break + for j in range(1, mat.ncols()): + mat.add_multiple_of_column(j, 0, -mat[m - 1, j] / mat[m - 1, 0]) + vrs = H1.parent().variable_names()[1:] + A1 = OrderedHyperplaneArrangements(self.base_ring(), names=vrs) + mat_rows = mat.rows()[:-1] + H1b = A1(mat_rows) + return H1b + P = self.intersection_poset(element_label="subspace") + center = P.maximal_elements()[0].linear_part() + n1 = center.dimension() + U = [] + for p in P: + if p.dimension() == n1 + 1: + B = [u for u in p.linear_part().basis() if u not in center] + U.append(B[0]) + # U = [p.linear_part().basis()[0] for p in P if p.dimension() == n1 + 1] + U0 = sum(U) + # We look for a linear hyperplane with integer coefficients + # defining a transversal hyperplane + for v in ZZ**n0: + v1 = v + U0 + if 0 not in [w * v1 for w in U]: + break + h0 = self.parent()((0,) + tuple(v1)) + H1 = self.add_hyperplane(h0) + return H1.restriction(h0) + + def affine_fundamental_group(self): + r""" + Return the fundamental group of the complement of an affine + hyperplane arrangement in `\CC^n` whose equations have + coefficients in a subfield of `\QQbar`. + + OUTPUT: + + A finitely presented fundamental group. + + .. NOTE:: + + This functionality requires the ``sirocco`` package to be installed. + + EXAMPLES:: + + sage: # needs sirocco + sage: A. = OrderedHyperplaneArrangements(QQ) + sage: L = [y + x, y + x - 1] + sage: H = A(L) + sage: H.affine_fundamental_group() + Finitely presented group < x0, x1 | > + sage: L = [x, y, x + 1, y + 1, x - y] + sage: A(L).affine_fundamental_group() + Finitely presented group + < x0, x1, x2, x3, x4 | x4*x0*x4^-1*x0^-1, + x0*x2*x3*x2^-1*x0^-1*x3^-1, + x1*x2*x4*x2^-1*x1^-1*x4^-1, + x2*x3*x0*x2^-1*x0^-1*x3^-1, + x2*x4*x1*x2^-1*x1^-1*x4^-1, + x4*x1*x4^-1*x3^-1*x2^-1*x1^-1*x2*x3 > + sage: H = A(x, y, x + y) + sage: H.affine_fundamental_group() + Finitely presented group + < x0, x1, x2 | x0*x1*x2*x1^-1*x0^-1*x2^-1, x1*x2*x0*x1^-1*x0^-1*x2^-1 > + sage: H.affine_fundamental_group() # repeat to use the attribute + Finitely presented group + < x0, x1, x2 | x0*x1*x2*x1^-1*x0^-1*x2^-1, x1*x2*x0*x1^-1*x0^-1*x2^-1 > + sage: T. = QQ[] + sage: K. = NumberField(t^3 + t + 1) + sage: L. = OrderedHyperplaneArrangements(K) + sage: H = L(a*x + y -1, x + a*y + 1, x - 1, y - 1) + sage: H.affine_fundamental_group() + Traceback (most recent call last): + ... + TypeError: the base field is not in QQbar + sage: L. = OrderedHyperplaneArrangements(QQ) + sage: L([t - j for j in range(4)]).affine_fundamental_group() + Finitely presented group < x0, x1, x2, x3 | > + sage: L. = OrderedHyperplaneArrangements(QQ) + sage: L(L.gens() + (x + y + z + 1,)).affine_fundamental_group().sorted_presentation() + Finitely presented group + < x0, x1, x2, x3 | x3^-1*x2^-1*x3*x2, x3^-1*x1^-1*x3*x1, + x3^-1*x0^-1*x3*x0, x2^-1*x1^-1*x2*x1, + x2^-1*x0^-1*x2*x0, x1^-1*x0^-1*x1*x0 > + sage: A = OrderedHyperplaneArrangements(QQ, names=()) + sage: H = A(); H + Empty hyperplane arrangement of dimension 0 + sage: H.affine_fundamental_group() + Finitely presented group < | > + """ + K = self.base_ring() + if not K.is_subring(QQbar): + raise TypeError('the base field is not in QQbar') + if self._affine_fundamental_group: + return self._affine_fundamental_group + n = self.dimension() + r = len(self) + if n == 0: + return FreeGroup(0) / [] + if n == 1: + G = FreeGroup(r) / [] + dic = {j: G.gen(j) for j in range(r)} + dic[r] = [prod(G.gens()) ** -1] + self._affine_fundamental_group = G + self._affine_meridians = dic + return G + if n == 2: + S = self.parent().ambient_space().symmetric_space() + coord = vector((1,) + S.gens()) + Af = AffinePlaneCurveArrangements(K, names=self.parent().variable_names()) + L = Af([vector(line.coefficients()) * coord for line in self]) + G = L.fundamental_group() + self._affine_fundamental_group = G + self._affine_meridians = L._meridians_simpl_vertical + return G + H1 = self.hyperplane_section(proj=False) + G = H1.affine_fundamental_group() + self._affine_fundamental_group = G + self._affine_meridians = H1._affine_meridians + return G + + def affine_meridians(self): + r""" + Return the meridians of each hyperplane (including the one at infinity). + + OUTPUT: + + A dictionary + + .. NOTE:: + + This functionality requires the ``sirocco`` package to be installed. + + EXAMPLES:: + + sage: # needs sirocco + sage: A. = OrderedHyperplaneArrangements(QQ) + sage: L = [y + x, y + x - 1] + sage: H = A(L) + sage: g = H.affine_fundamental_group() + sage: g + Finitely presented group < x0, x1 | > + sage: H.affine_meridians() + {0: [x0], 1: [x1], 2: [x1^-1*x0^-1]} + sage: H1 = H.add_hyperplane(y - x) + sage: H1.affine_meridians() + {0: [x0], 1: [x1], 2: [x2], 3: [x2^-1*x1^-1*x0^-1]} + """ + if self._affine_meridians is None: + self.affine_fundamental_group() + return dict(self._affine_meridians) + + def projective_fundamental_group(self): + r""" + Return the fundamental group of the complement of a projective + hyperplane arrangement. + + OUTPUT: + + The finitely presented group of the complement + in the projective space whose equations have + coefficients in a subfield of `\QQbar`. + + .. NOTE:: + + This functionality requires the ``sirocco`` package to be installed. + + EXAMPLES:: + + sage: # needs sirocco + sage: A. = OrderedHyperplaneArrangements(QQ) + sage: H = A(x, y, x + y) + sage: H.projective_fundamental_group() + Finitely presented group < x0, x1 | > + + sage: # needs sirocco sage.graphs + sage: A3. = OrderedHyperplaneArrangements(QQ) + sage: H = A3(hyperplane_arrangements.braid(4).essentialization()) + sage: G3 = H.projective_fundamental_group(); G3.sorted_presentation() + Finitely presented group + < x0, x1, x2, x3, x4 | x4^-1*x3^-1*x2^-1*x3*x4*x0*x2*x0^-1, + x4^-1*x2^-1*x4*x2, x4^-1*x1^-1*x0^-1*x1*x4*x0, + x4^-1*x1^-1*x0^-1*x4*x0*x1, + x4^-1*x1^-1*x3*x0*x1*x3^-1*x2^-1*x4*x0^-1*x2, + x3^-1*x2^-1*x1^-1*x0^-1*x3*x0*x1*x2, + x3^-1*x1^-1*x3*x1 > + sage: G3.abelian_invariants() + (0, 0, 0, 0, 0) + sage: A4. = OrderedHyperplaneArrangements(QQ) + sage: H = A4(hyperplane_arrangements.braid(4)) + sage: G4 = H.projective_fundamental_group(); G4.sorted_presentation() + Finitely presented group + < x0, x1, x2, x3, x4 | x4^-1*x3^-1*x2^-1*x3*x4*x0*x2*x0^-1, + x4^-1*x2^-1*x4*x2, x4^-1*x1^-1*x0^-1*x1*x4*x0, + x4^-1*x1^-1*x0^-1*x4*x0*x1, + x4^-1*x1^-1*x3*x0*x1*x3^-1*x2^-1*x4*x0^-1*x2, + x3^-1*x2^-1*x1^-1*x0^-1*x3*x0*x1*x2, + x3^-1*x1^-1*x3*x1 > + sage: G4.abelian_invariants() + (0, 0, 0, 0, 0) + + sage: # needs sirocco + sage: L. = OrderedHyperplaneArrangements(QQ) + sage: H = hyperplane_arrangements.coordinate(5) + sage: H = L(H) + sage: g = H.projective_fundamental_group() + sage: g.is_abelian(), g.abelian_invariants() + (True, (0, 0, 0, 0)) + sage: L(t0, t1, t2, t3, t4, t0 - 1).projective_fundamental_group() + Traceback (most recent call last): + ... + TypeError: the arrangement is not projective + sage: T. = QQ[] + sage: K. = NumberField(t^3 + t + 1) + sage: L. = OrderedHyperplaneArrangements(K) + sage: H = L(a*x + y - z, x + a*y + z, x - z, y - z) + sage: H.projective_fundamental_group() + Traceback (most recent call last): + ... + TypeError: the base field is not in QQbar + sage: A. = OrderedHyperplaneArrangements(QQ) + sage: H = A(); H + Empty hyperplane arrangement of dimension 1 + sage: H.projective_fundamental_group() + Finitely presented group < | > + """ + K = self.base_ring() + if not K.is_subring(QQbar): + raise TypeError('the base field is not in QQbar') + if not self.is_linear(): + raise TypeError('the arrangement is not projective') + if self._projective_fundamental_group: + return self._projective_fundamental_group + n = self.dimension() + r = len(self) + if n == 1: + return FreeGroup(0) / [] + if n == 2: + G = FreeGroup(r - 1) / [] + dic = {j: G.gen(j) for j in range(r - 1)} + dic[r - 1] = [prod(G.gens()) ** -1] + self._projective_fundamental_group = G + self._projective_meridians = dic + return G + if n == 3: + S = self.parent().ambient_space().symmetric_space() + coord = vector(S.gens()) + Proj = ProjectivePlaneCurveArrangements(K, + names=self.parent().variable_names()) + L = Proj([vector(line.coefficients()[1:]) * coord for line in self]) + G = L.fundamental_group() + self._projective_fundamental_group = G + self._projective_meridians = L._meridians_simpl + return G + H1 = self.hyperplane_section() + G = H1.projective_fundamental_group() + self._projective_fundamental_group = G + self._projective_meridians = H1._projective_meridians + return G + + def projective_meridians(self): + r""" + Return the meridian of each hyperplane. + + OUTPUT: + + A dictionary + + .. NOTE:: + + This functionality requires the ``sirocco`` package to be installed. + + EXAMPLES:: + + sage: # needs sirocco + sage: A. = OrderedHyperplaneArrangements(QQ) + sage: H = A(x, y, x + y) + sage: H.projective_meridians() + {0: x0, 1: x1, 2: [x1^-1*x0^-1]} + + sage: # needs sirocco sage.graphs + sage: A3. = OrderedHyperplaneArrangements(QQ) + sage: H = A3(hyperplane_arrangements.braid(4).essentialization()) + sage: H.projective_meridians() + {0: [x2^-1*x0^-1*x4^-1*x3^-1*x1^-1], + 1: [x3], 2: [x4], 3: [x1], 4: [x2], 5: [x0]} + sage: A4. = OrderedHyperplaneArrangements(QQ) + sage: H = A4(hyperplane_arrangements.braid(4)) + sage: H.projective_meridians() + {0: [x2^-1*x0^-1*x4^-1*x3^-1*x1^-1], 1: [x3], + 2: [x4], 3: [x0], 4: [x2], 5: [x1]} + + sage: # needs sirocco + sage: L. = OrderedHyperplaneArrangements(QQ) + sage: H = hyperplane_arrangements.coordinate(5) + sage: H = L(H) + sage: H.projective_meridians() + {0: [x2], 1: [x3], 2: [x0], 3: [x3^-1*x2^-1*x1^-1*x0^-1], 4: [x1]} + """ + if self._projective_meridians is None: + self.projective_fundamental_group() + return dict(self._projective_meridians) + + +class OrderedHyperplaneArrangements(HyperplaneArrangements): + """ + Ordered Hyperplane arrangements. + + For more information on hyperplane arrangements, see + :mod:`sage.geometry.hyperplane_arrangement.arrangement`. + + INPUT: + + - ``base_ring`` -- ring; the base ring + + - ``names`` -- tuple of strings; the variable names + + EXAMPLES:: + + sage: H. = HyperplaneArrangements(QQ) + sage: x + Hyperplane x + 0*y + 0 + sage: x + y + Hyperplane x + y + 0 + sage: H(x, y, x-1, y-1) + Arrangement + """ + Element = OrderedHyperplaneArrangementElement + + def _element_constructor_(self, *args, **kwds): + """ + Construct an element of ``self``. + + INPUT: + + - ``*args`` -- positional arguments, each defining a + hyperplane; alternatively, a single polytope or a single + hyperplane arrangement + + - ``signed`` -- boolean (default: ``True``); whether to + preserve signs of hyperplane equations + + - ``check`` -- boolean (default: ``True``); whether to + perform argument checking. + + EXAMPLES:: + + sage: L. = OrderedHyperplaneArrangements(QQ) + sage: L(x) + Arrangement + sage: L(x, y) + Arrangement + sage: L([x, y]) + Arrangement + sage: L([0, 1, 0], [0, 0, 1]) + Arrangement + sage: L([[0, 0, 1], [0, 1, 0]]) + Arrangement + + sage: L(polytopes.hypercube(2)) + Arrangement <-x + 1 | -y + 1 | x + 1 | y + 1> + + sage: L(-x, x + y - 1, signed=False) + Arrangement + + TESTS:: + + sage: L() + Empty hyperplane arrangement of dimension 2 + sage: L(0) # zero is equivalent to no argument, Issue #8648 + Empty hyperplane arrangement of dimension 2 + sage: L(0*x) # degenerate hyperplane is NOT allowed + Traceback (most recent call last): + ... + ValueError: linear expression must be non-constant to define a hyperplane + sage: L(0*x, y) # ditto + Traceback (most recent call last): + ... + ValueError: linear expression must be non-constant to define a hyperplane + """ + if len(args) == 1: + arg = args[0] + if isinstance(arg, HyperplaneArrangementElement) and arg.parent() is self: + # optimization if argument is already a hyperplane arrangement + return arg + if arg == 0 and not isinstance(arg, Hyperplane): + # zero = neutral element under addition = the empty hyperplane arrangement + args = [] + # process keyword arguments + not_char2 = (self.base_ring().characteristic() != 2) + signed = kwds.pop('signed', not_char2) + check = kwds.pop('check', True) + backend = kwds.pop('backend', None) + if kwds: + raise ValueError('unknown keyword argument') + # process positional arguments + AA = self.ambient_space() + try: + hyperplanes = [AA(a) for a in args] + except (TypeError, ValueError, AttributeError): + if len(args) > 1: + raise + arg = args[0] + if hasattr(arg, 'Hrepresentation'): + hyperplanes = [AA(h) for h in arg.Hrepresentation()] + else: + hyperplanes = [AA(a) for a in arg] + hyperplanes = [h.primitive(signed) for h in hyperplanes] + if check: + if signed and not not_char2: + raise ValueError('cannot be signed in characteristic 2') + for h in hyperplanes: + if h.A() == 0: + raise ValueError('linear expression must be non-constant to define a hyperplane') + if not_char2 and -h in hyperplanes: + raise ValueError('arrangement cannot simultaneously have h and -h as hyperplane') + return self.element_class(self, tuple(hyperplanes), backend=backend) + + def _repr_(self): + """ + Return a string representation. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: L. = OrderedHyperplaneArrangements(QQ); L + Ordered hyperplane arrangements in 2-dimensional linear space + over Rational Field with coordinates x, y + """ + return 'Ordered hyperplane arrangements in {0}'.format(self.ambient_space()) diff --git a/src/sage/geometry/lattice_polytope.py b/src/sage/geometry/lattice_polytope.py index b652e68c946..666548b9f39 100644 --- a/src/sage/geometry/lattice_polytope.py +++ b/src/sage/geometry/lattice_polytope.py @@ -482,6 +482,9 @@ def is_LatticePolytope(x): sage: from sage.geometry.lattice_polytope import is_LatticePolytope sage: is_LatticePolytope(1) + doctest:warning... + DeprecationWarning: is_LatticePolytope is deprecated, use isinstance instead + See https://github.com/sagemath/sage/issues/34307 for details. False sage: p = LatticePolytope([(1,0), (0,1), (-1,-1)]) sage: p # needs palp @@ -489,6 +492,8 @@ def is_LatticePolytope(x): sage: is_LatticePolytope(p) True """ + from sage.misc.superseded import deprecation + deprecation(34307, "is_LatticePolytope is deprecated, use isinstance instead") return isinstance(x, LatticePolytopeClass) @richcmp_method @@ -989,7 +994,7 @@ def _palp(self, command, reduce_dimension=False): sage: o = lattice_polytope.cross_polytope(3) sage: o._palp("poly.x -f") # needs palp 'M:7 6 N:27 8 Pic:17 Cor:0\n' - sage: print(o._palp("nef.x -f -N -p")) # random time information # needs palp + sage: print(o._palp("nef.x -f -N -p")) # random time information # needs palp M:27 8 N:7 6 codim=2 #part=5 H:[0] P:0 V:2 4 5 0sec 0cpu H:[0] P:2 V:3 4 5 0sec 0cpu @@ -1234,7 +1239,7 @@ def _read_nef_partitions(self, data): sage: o = lattice_polytope.cross_polytope(3) sage: s = o.nef_x("-p -N -Lv") # needs palp - sage: print(s) # random time values # needs palp + sage: print(s) # random time values # needs palp M:27 8 N:7 6 codim=2 #part=5 3 6 Vertices in N-lattice: 1 0 0 -1 0 0 @@ -3139,7 +3144,7 @@ def normal_form(self, algorithm="palp_native", permutation=False): M( 12, -1, -9, -6, 6), M( 12, -1, -6, -3, 3) in 5-d lattice M - sage: P.normal_form(algorithm="palp_modified") # not tested (22s; MemoryError on 32 bit), needs sage.groups + sage: P.normal_form(algorithm="palp_modified") # not tested (22s; MemoryError on 32 bit), needs sage.groups M( 6, 0, 0, 0, 0), M( -6, 0, 0, 0, 0), M( 0, 1, 0, 0, 0), @@ -5264,7 +5269,7 @@ def _read_nef_x_partitions(data): sage: o = lattice_polytope.cross_polytope(3) sage: s = o.nef_x("-N -p") # needs palp - sage: print(s) # random # needs palp + sage: print(s) # random # needs palp M:27 8 N:7 6 codim=2 #part=5 P:0 V:2 4 5 0sec 0cpu P:2 V:3 4 5 0sec 0cpu diff --git a/src/sage/geometry/newton_polygon.py b/src/sage/geometry/newton_polygon.py index 28101e70646..c8e185d01e6 100644 --- a/src/sage/geometry/newton_polygon.py +++ b/src/sage/geometry/newton_polygon.py @@ -14,6 +14,8 @@ # https://www.gnu.org/licenses/ ############################################################################# +import sage.geometry.abc + from sage.structure.unique_representation import UniqueRepresentation from sage.structure.parent import Parent from sage.structure.element import Element @@ -22,7 +24,6 @@ from sage.rings.infinity import Infinity from sage.geometry.polyhedron.constructor import Polyhedron -from sage.geometry.polyhedron.base import is_Polyhedron class NewtonPolygon_element(Element): @@ -716,7 +717,7 @@ def _element_constructor_(self, arg, sort_slopes=True, last_slope=Infinity): sage: NewtonPolygon(1) Finite Newton polygon with 1 vertex: (0, 0) """ - if is_Polyhedron(arg): + if isinstance(arg, sage.geometry.abc.Polyhedron): return self.element_class(arg, parent=self) if arg == 0: polyhedron = Polyhedron(base_ring=self.base_ring(), ambient_dim=2) diff --git a/src/sage/geometry/polyhedral_complex.py b/src/sage/geometry/polyhedral_complex.py index 09200a60be9..981404eb174 100644 --- a/src/sage/geometry/polyhedral_complex.py +++ b/src/sage/geometry/polyhedral_complex.py @@ -110,9 +110,11 @@ # **************************************************************************** from copy import copy + +import sage.geometry.abc + from sage.topology.cell_complex import GenericCellComplex from sage.geometry.polyhedron.constructor import Polyhedron -from sage.geometry.polyhedron.base import is_Polyhedron from sage.modules.free_module_element import vector from sage.rings.integer_ring import ZZ from sage.graphs.graph import Graph @@ -308,7 +310,7 @@ def __init__(self, maximal_cells=None, backend=None, maximality_check=True, ambient_dim = next(iter(cells_dict[self._dim])).ambient_dim() self._ambient_dim = ambient_dim self._maximal_cells = cells_dict - if not all((is_Polyhedron(cell) and + if not all((isinstance(cell, sage.geometry.abc.Polyhedron) and cell.ambient_dim() == self._ambient_dim) for cell in self.maximal_cell_iterator()): raise ValueError("the given cells are not polyhedra " + @@ -959,7 +961,7 @@ def __contains__(self, x): sage: (0, 0) in pc # not a polyhedron False """ - if not is_Polyhedron(x): + if not isinstance(x, sage.geometry.abc.Polyhedron): return False dim = x.dimension() return dim in self.cells() and x in self.cells()[dim] @@ -2028,7 +2030,7 @@ def add_cell(self, cell): """ if self._is_immutable: raise ValueError("this polyhedral complex is not mutable") - if not is_Polyhedron(cell) or cell.ambient_dim() != self._ambient_dim: + if not isinstance(cell, sage.geometry.abc.Polyhedron) or cell.ambient_dim() != self._ambient_dim: raise ValueError("the given cell is not a polyhedron " + "in the same ambient space") # if cell is already in self, do nothing. @@ -2191,7 +2193,7 @@ def remove_cell(self, cell, check=False): """ if self._is_immutable: raise ValueError("this polyhedral complex is not mutable") - if not is_Polyhedron(cell) or cell.ambient_dim() != self._ambient_dim: + if not isinstance(cell, sage.geometry.abc.Polyhedron) or cell.ambient_dim() != self._ambient_dim: raise ValueError("the given cell is not a polyhedron " + "in the same ambient space") # if cell is not in self, delete nothing. diff --git a/src/sage/geometry/polyhedron/base.py b/src/sage/geometry/polyhedron/base.py index f9ce84be09d..82e92b72bd3 100644 --- a/src/sage/geometry/polyhedron/base.py +++ b/src/sage/geometry/polyhedron/base.py @@ -74,10 +74,15 @@ def is_Polyhedron(X): sage: p = polytopes.hypercube(2) sage: from sage.geometry.polyhedron.base import is_Polyhedron sage: is_Polyhedron(p) + doctest:warning... + DeprecationWarning: is_Polyhedron is deprecated, use isinstance instead + See https://github.com/sagemath/sage/issues/34307 for details. True sage: is_Polyhedron(123456) False """ + from sage.misc.superseded import deprecation + deprecation(34307, "is_Polyhedron is deprecated, use isinstance instead") return isinstance(X, Polyhedron_base) diff --git a/src/sage/geometry/polyhedron/parent.py b/src/sage/geometry/polyhedron/parent.py index 2d87ac6f202..5fad17dc55a 100644 --- a/src/sage/geometry/polyhedron/parent.py +++ b/src/sage/geometry/polyhedron/parent.py @@ -9,6 +9,8 @@ # https://www.gnu.org/licenses/ # ***************************************************************************** +import sage.geometry.abc + from sage.structure.parent import Parent from sage.structure.element import get_coercion_model from sage.structure.unique_representation import UniqueRepresentation @@ -22,8 +24,6 @@ from sage.categories.fields import Fields from sage.categories.rings import Rings from sage.categories.modules import Modules - -from sage.geometry.polyhedron.base import is_Polyhedron from .representation import Inequality, Equation, Vertex, Ray, Line @@ -480,9 +480,8 @@ def Vrepresentation_space(self): if self.base_ring() in Fields(): from sage.modules.free_module import VectorSpace return VectorSpace(self.base_ring(), self.ambient_dim()) - else: - from sage.modules.free_module import FreeModule - return FreeModule(self.base_ring(), self.ambient_dim()) + from sage.modules.free_module import FreeModule + return FreeModule(self.base_ring(), self.ambient_dim()) ambient_space = Vrepresentation_space @@ -521,7 +520,6 @@ def _repr_base_ring(self): sage: Polyhedra(K, 4)._repr_base_ring() # needs 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: @@ -535,9 +533,9 @@ def _repr_base_ring(self): else: if self.base_ring() is AA: return 'AA' - return '({0})'.format(self.base_ring()) + return f'({self.base_ring()})' - def _repr_ambient_module(self): + def _repr_ambient_module(self) -> str: """ Return an abbreviated string representation of the ambient space. @@ -693,7 +691,7 @@ def convert_base_ring_Hrep(lstlst): if convert and Vrep: Vrep = [convert_base_ring(_) for _ in Vrep] return self.element_class(self, Vrep, Hrep, **kwds) - if nargs == 1 and is_Polyhedron(args[0]): + if nargs == 1 and isinstance(args[0], sage.geometry.abc.Polyhedron): copy = kwds.pop('copy', args[0].parent() is not self) mutable = kwds.pop('mutable', False) @@ -885,7 +883,7 @@ def _coerce_base_ring(self, other): from sage.structure.element import Element if isinstance(other, Element): other = other.parent() - if hasattr(other, "is_ring") and other.is_ring(): + if other in Rings(): other_ring = other else: try: diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 4d147efcd2f..c6d429863d3 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -2126,15 +2126,23 @@ def incidence_matrix(self, oriented=None, sparse=True, vertices=None, edges=None - ``sparse`` -- boolean (default: ``True``); whether to use a sparse or a dense matrix - - ``vertices`` -- list (default: ``None``); when specified, the `i`-th - row of the matrix corresponds to the `i`-th vertex in the ordering of - ``vertices``, otherwise, the `i`-th row of the matrix corresponds to - the `i`-th vertex in the ordering given by method :meth:`vertices`. + - ``vertices`` -- list, ``None``, or ``True`` (default: ``None``); - - ``edges`` -- list (default: ``None``); when specified, the `i`-th - column of the matrix corresponds to the `i`-th edge in the ordering of - ``edges``, otherwise, the `i`-th column of the matrix corresponds to - the `i`-th edge in the ordering given by method :meth:`edge_iterator`. + - when a list, the `i`-th row of the matrix corresponds to the `i`-th + vertex in the ordering of ``vertices``, + - when ``None``, the `i`-th row of the matrix corresponds to + the `i`-th vertex in the ordering given by method :meth:`vertices`, + - when ``True``, construct a morphism of free modules instead of a matrix, + where the codomain's basis is indexed by the vertices. + + - ``edges`` -- list, ``None``, or ``True`` (default: ``None``); + + - when a list, the `i`-th column of the matrix corresponds to the `i`-th + edge in the ordering of ``edges``, + - when ``None``, the `i`-th column of the matrix corresponds to + the `i`-th edge in the ordering given by method :meth:`edge_iterator`, + - when ``True``, construct a morphism of free modules instead of a matrix, + where the domain's basis is indexed by the edges. - ``base_ring`` -- a ring (default: ``ZZ``); the base ring of the matrix space to use. @@ -2258,6 +2266,32 @@ def incidence_matrix(self, oriented=None, sparse=True, vertices=None, edges=None ValueError: matrix is immutable; please change a copy instead (i.e., use copy(M) to change a copy of M). + Creating a module morphism:: + + sage: # needs sage.modules + sage: D12 = posets.DivisorLattice(12).hasse_diagram() + sage: phi_VE = D12.incidence_matrix(vertices=True, edges=True); phi_VE + Generic morphism: + From: Free module generated by + {(1, 2), (1, 3), (2, 4), (2, 6), (3, 6), (4, 12), (6, 12)} + over Integer Ring + To: Free module generated by {1, 2, 3, 4, 6, 12} over Integer Ring + sage: print(phi_VE._unicode_art_matrix()) + (1, 2) (1, 3) (2, 4) (2, 6) (3, 6) (4, 12) (6, 12) + 1⎛ -1 -1 0 0 0 0 0⎞ + 2⎜ 1 0 -1 -1 0 0 0⎟ + 3⎜ 0 1 0 0 -1 0 0⎟ + 4⎜ 0 0 1 0 0 -1 0⎟ + 6⎜ 0 0 0 1 1 0 -1⎟ + 12⎝ 0 0 0 0 0 1 1⎠ + sage: E = phi_VE.domain() + sage: P1 = E.monomial((2, 4)) + E.monomial((4, 12)); P1 + B[(2, 4)] + B[(4, 12)] + sage: P2 = E.monomial((2, 6)) + E.monomial((6, 12)); P2 + B[(2, 6)] + B[(6, 12)] + sage: phi_VE(P1 - P2) + 0 + TESTS:: sage: P5 = graphs.PathGraph(5) @@ -2279,15 +2313,24 @@ def incidence_matrix(self, oriented=None, sparse=True, vertices=None, edges=None if oriented is None: oriented = self.is_directed() - if vertices is None: + row_keys = None + if vertices is True: + vertices = self.vertices(sort=False) + row_keys = tuple(vertices) # because a list is not hashable + elif vertices is None: vertices = self.vertices(sort=False) elif (len(vertices) != self.num_verts() or set(vertices) != set(self.vertex_iterator())): raise ValueError("parameter vertices must be a permutation of the vertices") + column_keys = None verts = {v: i for i, v in enumerate(vertices)} - if edges is None: - edges = self.edge_iterator(labels=False) + use_edge_labels = kwds.pop('use_edge_labels', False) + if edges is True: + edges = self.edges(labels=use_edge_labels) + column_keys = tuple(edges) # because an EdgesView is not hashable + elif edges is None: + edges = self.edge_iterator(labels=use_edge_labels) elif len(edges) != self.size(): raise ValueError("parameter edges must be a permutation of the edges") else: @@ -2319,8 +2362,13 @@ def reorder(u, v): m[verts[e[0]], i] += 1 m[verts[e[1]], i] += 1 + if row_keys is not None or column_keys is not None: + m.set_immutable() + return matrix(m, row_keys=row_keys, column_keys=column_keys) + if immutable: m.set_immutable() + return m def distance_matrix(self, vertices=None, *, base_ring=None, **kwds): diff --git a/src/sage/groups/abelian_gps/abelian_aut.py b/src/sage/groups/abelian_gps/abelian_aut.py index b27f9bdb220..894b54f6736 100644 --- a/src/sage/groups/abelian_gps/abelian_aut.py +++ b/src/sage/groups/abelian_gps/abelian_aut.py @@ -176,9 +176,7 @@ def matrix(self): The `i`-th row is the exponent vector of the image of the `i`-th generator. - OUTPUT: - - - a square matrix over the integers + OUTPUT: a square matrix over the integers EXAMPLES:: @@ -315,9 +313,7 @@ def _coerce_map_from_(self, S): - ``S`` -- anything - OUTPUT: - - Boolean or nothing + OUTPUT: boolean or nothing EXAMPLES:: diff --git a/src/sage/groups/abelian_gps/abelian_group.py b/src/sage/groups/abelian_gps/abelian_group.py index 90cdcaa42fd..8a0c5167b16 100644 --- a/src/sage/groups/abelian_gps/abelian_group.py +++ b/src/sage/groups/abelian_gps/abelian_group.py @@ -40,21 +40,21 @@ invariant factors of the group. You should now use :meth:`~AbelianGroup_class.gens_orders` instead:: - sage: J = AbelianGroup([2,0,3,2,4]); J - Multiplicative Abelian group isomorphic to C2 x Z x C3 x C2 x C4 - sage: J.gens_orders() # use this instead - (2, 0, 3, 2, 4) - sage: J.invariants() # deprecated - (2, 0, 3, 2, 4) - sage: J.elementary_divisors() # these are the "invariant factors" - (2, 2, 12, 0) - sage: for i in range(J.ngens()): - ....: print((i, J.gen(i), J.gen(i).order())) # or use this form - (0, f0, 2) - (1, f1, +Infinity) - (2, f2, 3) - (3, f3, 2) - (4, f4, 4) + sage: J = AbelianGroup([2,0,3,2,4]); J + Multiplicative Abelian group isomorphic to C2 x Z x C3 x C2 x C4 + sage: J.gens_orders() # use this instead + (2, 0, 3, 2, 4) + sage: J.invariants() # deprecated + (2, 0, 3, 2, 4) + sage: J.elementary_divisors() # these are the "invariant factors" + (2, 2, 12, 0) + sage: for i in range(J.ngens()): + ....: print((i, J.gen(i), J.gen(i).order())) # or use this form + (0, f0, 2) + (1, f1, +Infinity) + (2, f2, 3) + (3, f3, 2) + (4, f4, 4) Background on invariant factors and the Smith normal form (according to section 4.1 of [Cohen1]_): An abelian group is a @@ -160,7 +160,7 @@ .. [Rotman] \J. Rotman, An introduction to the theory of groups, 4th ed, Springer, 1995. -.. warning:: +.. WARNING:: Many basic properties for infinite abelian groups are not implemented. @@ -221,15 +221,17 @@ from sage.structure.unique_representation import UniqueRepresentation -# TODO: this uses perm groups - the AbelianGroupElement instance method -# uses a different implementation. +# .. TODO:: + +# this uses perm groups - the AbelianGroupElement instance method +# uses a different implementation. def word_problem(words, g, verbose=False): r""" - G and H are abelian, g in G, H is a subgroup of G generated by a - list (words) of elements of G. If g is in H, return the expression - for g as a word in the elements of (words). + `G` and `H` are abelian, `g` in `G`, `H` is a subgroup of `G` generated by + a list (words) of elements of `G`. If `g` is in `H`, return the expression + for `g` as a word in the elements of (words). - The 'word problem' for a finite abelian group G boils down to the + The 'word problem' for a finite abelian group `G` boils down to the following matrix-vector analog of the Chinese remainder theorem. Problem: Fix integers `1`. + OUTPUT: the :class:`dual abelian group ` EXAMPLES:: @@ -719,9 +717,9 @@ def dual_group(self, names="X", base_ring=None): @cached_method def elementary_divisors(self): r""" - This returns the elementary divisors of the group, using Pari. + Return the elementary divisors of the group, using Pari. - .. note:: + .. NOTE:: Here is another algorithm for computing the elementary divisors `d_1, d_2, d_3, \ldots`, of a finite abelian group (where `d_1 | d_2 | d_3 | \ldots` @@ -736,9 +734,7 @@ def elementary_divisors(self): on these "smaller invariants" to compute `d_{i-1}`, and so on. (Thanks to Robert Miller for communicating this algorithm.) - OUTPUT: - - A tuple of integers. + OUTPUT: tuple of integers EXAMPLES:: @@ -806,15 +802,13 @@ def identity(self): def _group_notation(self, eldv): """ - Return abstract group notation for generator orders ``eldv`` + Return abstract group notation for generator orders ``eldv``. INPUT: - - ``eldv`` -- iterable of integers. - - OUTPUT: + - ``eldv`` -- iterable of integers - String. + OUTPUT: string EXAMPLES:: @@ -955,9 +949,7 @@ def gens_orders(self): Use :meth:`elementary_divisors` if you are looking for an invariant of the group. - OUTPUT: - - A tuple of integers. + OUTPUT: tuple of integers EXAMPLES:: @@ -981,8 +973,8 @@ def gens_orders(self): TESTS:: sage: G, (g0, g1) = AbelianGroup(2, [48, 0]).objgens() - sage: G0 = G.subgroup([g0]) - sage: len(G0.gens()) == len(G0.gens_orders()) + sage: G0 = G.subgroup([g0]) # optional - gap_package_polycyclic + sage: len(G0.gens()) == len(G0.gens_orders()) # optional - gap_package_polycyclic True sage: F = AbelianGroup(3, [2], names='abc') sage: list(map(type, F.gens_orders())) @@ -1018,9 +1010,7 @@ def invariants(self): Use :meth:`elementary_divisors` if you are looking for an invariant of the group. - OUTPUT: - - A tuple of integers. Zero means infinite cyclic factor. + OUTPUT: tuple of integers; zero means infinite cyclic factor EXAMPLES:: @@ -1043,7 +1033,7 @@ def invariants(self): def is_cyclic(self): """ - Return True if the group is a cyclic group. + Return ``True`` if the group is a cyclic group. EXAMPLES:: @@ -1153,7 +1143,7 @@ def permutation_group(self): def is_commutative(self): """ - Return True since this group is commutative. + Return ``True`` since this group is commutative. EXAMPLES:: @@ -1432,8 +1422,8 @@ def subgroups(self, check=False): INPUT: - - check: if ``True``, performs the same computation in GAP and - checks that the number of subgroups generated is the + - ``check`` -- boolean; if ``True``, performs the same computation in + GAP and checks that the number of subgroups generated is the same. (I don't know how to convert GAP's output back into Sage, so we don't actually compare the subgroups). @@ -1528,15 +1518,15 @@ def subgroups(self, check=False): def subgroup_reduced(self, elts, verbose=False): r""" - Given a list of lists of integers (corresponding to elements of self), - find a set of independent generators for the subgroup generated by - these elements, and return the subgroup with these as generators, - forgetting the original generators. + Given a list of lists of integers (corresponding to elements of + ``self``), find a set of independent generators for the subgroup + generated by these elements, and return the subgroup with these as + generators, forgetting the original generators. This is used by the ``subgroups`` routine. An error will be raised if the elements given are not linearly - independent over QQ. + independent over `\QQ`. EXAMPLES:: @@ -1595,7 +1585,8 @@ def torsion_subgroup(self, n=None): sage: G = AbelianGroup([2, 2*3, 2*3*5, 0, 2*3*5*7, 2*3*5*7*11]) sage: G.torsion_subgroup(5) # needs sage.libs.gap # optional - gap_package_polycyclic - Multiplicative Abelian subgroup isomorphic to C5 x C5 x C5 generated by {f2^6, f4^42, f5^462} + Multiplicative Abelian subgroup isomorphic to C5 x C5 x C5 + generated by {f2^6, f4^42, f5^462} """ if n is None: torsion_generators = [g for g in self.gens() if g.order() != infinity] @@ -1615,7 +1606,7 @@ def torsion_subgroup(self, n=None): class AbelianGroup_subgroup(AbelianGroup_class): """ - Subgroup subclass of AbelianGroup_class, so instance methods are + Subgroup subclass of ``AbelianGroup_class``, so instance methods are inherited. .. TODO:: @@ -1787,11 +1778,9 @@ def __contains__(self, x): def ambient_group(self): """ - Return the ambient group related to self. + Return the ambient group related to ``self``. - OUTPUT: - - A multiplicative Abelian group. + OUTPUT: a multiplicative Abelian group EXAMPLES:: @@ -1809,11 +1798,11 @@ def equals(left, right): INPUT: - - ``right`` -- anything. + - ``right`` -- anything OUTPUT: - Boolean. If ``right`` is a subgroup, test whether ``left`` and + boolean; if ``right`` is a subgroup, test whether ``left`` and ``right`` are the same subset of the ambient group. If ``right`` is not a subgroup, test whether they are isomorphic groups, see :meth:`~AbelianGroup_class.is_isomorphic`. @@ -1855,7 +1844,7 @@ def equals(left, right): def _repr_(self): """ - Return a string representation + Return a string representation. EXAMPLES:: @@ -1879,9 +1868,7 @@ def gens(self) -> tuple: """ Return the generators for this subgroup. - OUTPUT: - - A tuple of group elements generating the subgroup. + OUTPUT: tuple of group elements generating the subgroup EXAMPLES:: diff --git a/src/sage/groups/abelian_gps/abelian_group_element.py b/src/sage/groups/abelian_gps/abelian_group_element.py index b081f1e1c4e..ac279f71011 100644 --- a/src/sage/groups/abelian_gps/abelian_group_element.py +++ b/src/sage/groups/abelian_gps/abelian_group_element.py @@ -49,8 +49,8 @@ def is_AbelianGroupElement(x): """ - Return true if x is an abelian group element, i.e., an element of - type AbelianGroupElement. + Return ``True`` if ``x`` is an abelian group element, i.e., an element of + type :class:`AbelianGroupElement`. EXAMPLES: Though the integer 3 is in the integers, and the integers have an abelian group structure, 3 is not an AbelianGroupElement:: @@ -92,8 +92,8 @@ class AbelianGroupElement(AbelianGroupElementBase): """ def as_permutation(self): r""" - Return the element of the permutation group G (isomorphic to the - abelian group A) associated to a in A. + Return the element of the permutation group ``G`` (isomorphic to the + abelian group ``A``) associated to ``a`` in ``A``. EXAMPLES:: @@ -120,22 +120,24 @@ def as_permutation(self): def word_problem(self, words): """ - TODO - this needs a rewrite - see stuff in the matrix_grp - directory. - - G and H are abelian groups, g in G, H is a subgroup of G generated - by a list (words) of elements of G. If self is in H, return the - expression for self as a word in the elements of (words). + ``G`` and ``H`` are abelian groups, ``g`` in ``G``, ``H`` is a + subgroup of ``G`` generated by a list (words) of elements of ``G``. If + ``self`` is in ``H``, return the expression for ``self`` as a word in + the elements of (words). This function does not solve the word problem in Sage. Rather it pushes it over to GAP, which has optimized (non-deterministic) algorithms for the word problem. - .. warning:: + .. WARNING:: - Don't use E (or other GAP-reserved letters) as a generator + Don't use ``E`` (or other GAP-reserved letters) as a generator name. + .. TODO:: + + This needs a rewrite - see stuff in the ``matrix_grp`` directory. + EXAMPLES:: sage: # needs sage.libs.gap diff --git a/src/sage/groups/abelian_gps/abelian_group_gap.py b/src/sage/groups/abelian_gps/abelian_group_gap.py index 9ce83a6e33c..45a6a8bdd1a 100644 --- a/src/sage/groups/abelian_gps/abelian_group_gap.py +++ b/src/sage/groups/abelian_gps/abelian_group_gap.py @@ -92,9 +92,7 @@ def __reduce__(self): r""" Implement pickling. - OUTPUT: - - - a tuple ``f`` such that this element is ``f[0](*f[1])`` + OUTPUT: tuple ``f`` such that this element is ``f[0](*f[1])`` EXAMPLES:: @@ -112,9 +110,7 @@ def exponents(self): r""" Return the tuple of exponents of this element. - OUTPUT: - - - a tuple of integers + OUTPUT: tuple of integers EXAMPLES:: @@ -172,9 +168,7 @@ def order(self): r""" Return the order of this element. - OUTPUT: - - - an integer or infinity + OUTPUT: integer or infinity EXAMPLES:: @@ -204,9 +198,7 @@ def exponents(self): r""" Return the tuple of exponents of ``self``. - OUTPUT: - - - a tuple of integers + OUTPUT: tuple of integers EXAMPLES:: @@ -272,9 +264,7 @@ def _coerce_map_from_(self, S): - ``S`` -- anything - OUTPUT: - - Boolean. + OUTPUT: boolean EXAMPLES:: @@ -293,7 +283,7 @@ def _coerce_map_from_(self, S): def _element_constructor_(self, x, check=True): r""" - Defines coercions and conversions. + Define coercions and conversions. INPUT: @@ -492,9 +482,7 @@ def gens_orders(self): Use :meth:`elementary_divisors` if you are looking for an invariant of the group. - OUTPUT: - - - a tuple of integers + OUTPUT: tuple of integers EXAMPLES:: @@ -598,9 +586,7 @@ def subgroup(self, gens): - ``gens`` -- a list of elements coercible into this group - OUTPUT: - - - a subgroup + OUTPUT: a subgroup EXAMPLES:: @@ -643,9 +629,7 @@ class AbelianGroupGap(AbelianGroup_gap): - ``generator_orders`` -- a list of nonnegative integers where `0` gives a factor isomorphic to `\ZZ` - OUTPUT: - - - an abelian group + OUTPUT: an abelian group EXAMPLES:: @@ -731,7 +715,7 @@ def _repr_(self): def __reduce__(self): r""" - Implements pickling. + Implement pickling. We have to work around the fact that gap does not provide pickling. @@ -820,7 +804,7 @@ def _repr_(self): def __reduce__(self): r""" - Implements pickling. + Implement pickling. We have to work around the fact that gap does not provide pickling. @@ -850,9 +834,7 @@ def lift(self, x): - ``x`` -- an element of this subgroup - OUTPUT: - - The corresponding element of the ambient group + OUTPUT: the corresponding element of the ambient group EXAMPLES:: @@ -881,9 +863,7 @@ def retract(self, x): - ``x`` -- an element of the ambient group that actually lies in this subgroup. - OUTPUT: - - The corresponding element of this subgroup + OUTPUT: the corresponding element of this subgroup EXAMPLES:: @@ -955,7 +935,7 @@ def _repr_(self): def __reduce__(self): r""" - Implements pickling. + Implement pickling. We have to work around the fact that gap does not provide pickling. @@ -980,9 +960,7 @@ def _coerce_map_from_(self, S): - ``S`` -- anything - OUTPUT: - - Boolean. + OUTPUT: boolean EXAMPLES:: diff --git a/src/sage/groups/abelian_gps/abelian_group_morphism.py b/src/sage/groups/abelian_gps/abelian_group_morphism.py index e4b7b82b42b..a44abb83df9 100644 --- a/src/sage/groups/abelian_gps/abelian_group_morphism.py +++ b/src/sage/groups/abelian_gps/abelian_group_morphism.py @@ -44,10 +44,10 @@ def _repr_type(self): class AbelianGroupMorphism(Morphism): """ - Some python code for wrapping GAP's GroupHomomorphismByImages - function for abelian groups. Returns "fail" if gens does not - generate self or if the map does not extend to a group - homomorphism, self - other. + Some python code for wrapping GAP's ``GroupHomomorphismByImages`` + function for abelian groups. Returns "fail" if ``gens`` does not + generate ``self`` or if the map does not extend to a group + homomorphism, ``self`` - ``other``. EXAMPLES:: diff --git a/src/sage/groups/abelian_gps/dual_abelian_group.py b/src/sage/groups/abelian_gps/dual_abelian_group.py index 4aecfd69816..86966cf74d6 100644 --- a/src/sage/groups/abelian_gps/dual_abelian_group.py +++ b/src/sage/groups/abelian_gps/dual_abelian_group.py @@ -78,7 +78,7 @@ def is_DualAbelianGroup(x): """ - Return True if `x` is the dual group of an abelian group. + Return ``True`` if `x` is the dual group of an abelian group. EXAMPLES:: @@ -117,7 +117,7 @@ class DualAbelianGroup_class(UniqueRepresentation, AbelianGroupBase): def __init__(self, G, names, base_ring): """ - The Python constructor + The Python constructor. EXAMPLES:: @@ -271,9 +271,7 @@ def gens(self) -> tuple: """ Return the generators for the group. - OUTPUT: - - A tuple of group elements generating the group. + OUTPUT: tuple of group elements generating the group EXAMPLES:: @@ -301,9 +299,7 @@ def gens_orders(self): """ The orders of the generators of the dual group. - OUTPUT: - - A tuple of integers. + OUTPUT: tuple of integers EXAMPLES:: @@ -332,7 +328,7 @@ def invariants(self): def __contains__(self, X): """ - Implements "in". + Implement "in". EXAMPLES:: @@ -361,7 +357,7 @@ def order(self): def is_commutative(self): """ - Return True since this group is commutative. + Return ``True`` since this group is commutative. EXAMPLES:: @@ -377,7 +373,7 @@ def is_commutative(self): @cached_method def list(self): """ - Return tuple of all elements of this group. + Return a tuple of all elements of this group. EXAMPLES:: diff --git a/src/sage/groups/abelian_gps/dual_abelian_group_element.py b/src/sage/groups/abelian_gps/dual_abelian_group_element.py index 6fdb8a68c4e..ecf17404bdd 100644 --- a/src/sage/groups/abelian_gps/dual_abelian_group_element.py +++ b/src/sage/groups/abelian_gps/dual_abelian_group_element.py @@ -64,9 +64,7 @@ def is_DualAbelianGroupElement(x) -> bool: - ``x`` -- anything - OUTPUT: - - Boolean + OUTPUT: boolean EXAMPLES:: @@ -89,10 +87,8 @@ def __call__(self, g): """ Evaluate ``self`` on a group element ``g``. - OUTPUT: - - An element in - :meth:`~sage.groups.abelian_gps.dual_abelian_group.DualAbelianGroup_class.base_ring`. + OUTPUT: an element in + :meth:`~sage.groups.abelian_gps.dual_abelian_group.DualAbelianGroup_class.base_ring` EXAMPLES:: diff --git a/src/sage/groups/abelian_gps/element_base.py b/src/sage/groups/abelian_gps/element_base.py index 0435c9835b9..7712049e219 100644 --- a/src/sage/groups/abelian_gps/element_base.py +++ b/src/sage/groups/abelian_gps/element_base.py @@ -165,10 +165,6 @@ def _repr_(self): """ Return a string representation of ``self``. - OUTPUT: - - String. - EXAMPLES:: sage: G = AbelianGroup([2]) @@ -199,9 +195,7 @@ def _richcmp_(self, other, op): The comparison is based on the exponents. - OUTPUT: - - boolean + OUTPUT: boolean EXAMPLES:: @@ -220,9 +214,7 @@ def order(self): """ Return the order of this element. - OUTPUT: - - An integer or ``infinity``. + OUTPUT: integer or ``infinity`` EXAMPLES:: @@ -256,7 +248,7 @@ def order(self): def _div_(left, right): """ - Divide ``left`` and ``right`` + Divide ``left`` and ``right``. TESTS:: @@ -274,7 +266,7 @@ def _div_(left, right): def _mul_(left, right): """ - Multiply ``left`` and ``right`` + Multiply ``left`` and ``right``. TESTS:: @@ -292,7 +284,7 @@ def _mul_(left, right): def __pow__(self, n): """ - Exponentiate ``self`` + Exponentiate ``self``. TESTS:: @@ -335,9 +327,7 @@ def is_trivial(self): """ Test whether ``self`` is the trivial group element ``1``. - OUTPUT: - - Boolean. + OUTPUT: boolean EXAMPLES:: diff --git a/src/sage/groups/abelian_gps/values.py b/src/sage/groups/abelian_gps/values.py index 3db117123f0..c38edcfb9f9 100644 --- a/src/sage/groups/abelian_gps/values.py +++ b/src/sage/groups/abelian_gps/values.py @@ -16,7 +16,7 @@ number field. The :func:`AbelianGroupWithValues` keeps track of these associated values. -.. warning:: +.. WARNING:: Really, this requires a group homomorphism from the abstract Abelian group to the set of values. This is only checked if you @@ -85,10 +85,10 @@ def AbelianGroupWithValues(values, n, gens_orders=None, names='f', check=False, INPUT: - ``values`` -- a list/tuple/iterable of values that you want to - associate to the generators. + associate to the generators - ``n`` -- integer (optional). If not specified, will be derived - from ``gens_orders``. + from ``gens_orders`` - ``gens_orders`` -- a list of non-negative integers in the form `[a_0, a_1, \dots, a_{n-1}]`, typically written in increasing @@ -156,7 +156,7 @@ class AbelianGroupWithValuesEmbedding(Morphism): - ``domain`` -- a :class:`AbelianGroupWithValues_class` - ``codomain`` -- the values group (need not be in the category of - groups, e.g. symbolic ring). + groups, e.g. symbolic ring) EXAMPLES:: @@ -176,7 +176,7 @@ class AbelianGroupWithValuesEmbedding(Morphism): def __init__(self, domain, codomain): """ - Construct the morphism + Construct the morphism. TESTS:: @@ -193,16 +193,12 @@ def __init__(self, domain, codomain): def _call_(self, x): """ - Return the value associated to ``x`` + Return the value associated to ``x``. INPUT: - ``x`` -- a group element - OUTPUT: - - Its value. - EXAMPLES:: sage: # needs sage.symbolic @@ -222,13 +218,13 @@ class AbelianGroupWithValuesElement(AbelianGroupElement): INPUT: - - ``exponents`` -- tuple of integers. The exponent vector defining - the group element. + - ``exponents`` -- tuple of integers; the exponent vector defining + the group element - - ``parent`` -- the parent. + - ``parent`` -- the parent - ``value`` -- the value assigned to the group element or ``None`` - (default). In the latter case, the value is computed as needed. + (default); in the latter case, the value is computed as needed EXAMPLES:: @@ -239,7 +235,7 @@ class AbelianGroupWithValuesElement(AbelianGroupElement): def __init__(self, parent, exponents, value=None): """ - Create an element + Create an element. EXAMPLES:: @@ -257,10 +253,8 @@ def value(self): """ Return the value of the group element. - OUTPUT: - - The value according to the values for generators, see - :meth:`~AbelianGroupWithValues.gens_values`. + OUTPUT: the value according to the values for generators; see + :meth:`~AbelianGroupWithValues.gens_values` EXAMPLES:: @@ -275,7 +269,7 @@ def value(self): def _div_(left, right): """ - Divide ``left`` by ``right`` + Divide ``left`` by ``right``. TESTS:: @@ -293,7 +287,7 @@ def _div_(left, right): def _mul_(left, right): """ - Multiply ``left`` and ``right`` + Multiply ``left`` and ``right``. TESTS:: @@ -311,11 +305,11 @@ def _mul_(left, right): def __pow__(self, n): """ - Exponentiate ``self`` + Exponentiate ``self``. INPUT: - - ``n`` -- integer. The exponent. + - ``n`` -- integer; the exponent TESTS:: @@ -363,15 +357,15 @@ class AbelianGroupWithValues_class(AbelianGroup_class): INPUT: - - ``generator_orders`` -- tuple of integers. The orders of the + - ``generator_orders`` -- tuple of integers; the orders of the generators. - - ``names`` -- string or list of strings. The names for the generators. + - ``names`` -- string or list of strings; the names for the generators - - ``values`` -- Tuple the same length as the number of - generators. The values assigned to the generators. + - ``values`` -- tuple the same length as the number of + generators; the values assigned to the generators. - - ``values_group`` -- the common parent of the values. + - ``values_group`` -- the common parent of the values EXAMPLES:: @@ -382,7 +376,7 @@ class AbelianGroupWithValues_class(AbelianGroup_class): def __init__(self, generator_orders, names, values, values_group): """ - The Python constructor + The Python constructor. TESTS:: @@ -412,11 +406,9 @@ def gen(self, i=0): INPUT: - - ``i`` -- integer (default: 0). The index of the generator. - - OUTPUT: + - ``i`` -- integer (default: 0); the index of the generator - A group element. + OUTPUT: a group element EXAMPLES:: @@ -442,9 +434,7 @@ def gens_values(self): """ Return the values associated to the generators. - OUTPUT: - - A tuple. + OUTPUT: tuple EXAMPLES:: @@ -465,10 +455,8 @@ def values_group(self): units in a ring then the :meth:`values_group` would be the whole ring. - OUTPUT: - - The common parent of the values, containing the group - generated by all values. + OUTPUT: the common parent of the values, containing the group + generated by all values EXAMPLES:: @@ -486,9 +474,7 @@ def values_embedding(self): """ Return the embedding of ``self`` in :meth:`values_group`. - OUTPUT: - - A morphism. + OUTPUT: a morphism EXAMPLES:: diff --git a/src/sage/groups/additive_abelian/additive_abelian_group.py b/src/sage/groups/additive_abelian/additive_abelian_group.py index 167b38a9cc0..bbf91165437 100644 --- a/src/sage/groups/additive_abelian/additive_abelian_group.py +++ b/src/sage/groups/additive_abelian/additive_abelian_group.py @@ -18,16 +18,15 @@ def AdditiveAbelianGroup(invs, remember_generators=True): INPUT: - - ``invs`` (list of integers): the invariants. + - ``invs`` -- list of integers; the invariants. These should all be greater than or equal to zero. - - ``remember_generators`` (boolean): whether or not to fix a set of - generators (corresponding to the given invariants, which need not be in - Smith form). + - ``remember_generators`` -- boolean (default: ``True``); whether or not + to fix a set of generators (corresponding to the given invariants, which + need not be in Smith form). - OUTPUT: - - The abelian group `\bigoplus_i \ZZ / n_i \ZZ`, where `n_i` are the invariants. + OUTPUT: the abelian group `\bigoplus_i \ZZ / n_i \ZZ`, where `n_i` are the + invariants EXAMPLES:: @@ -207,9 +206,9 @@ class AdditiveAbelianGroup_class(FGP_Module_class): INPUT: - - ``cover`` -- the covering group as `\ZZ`-module. + - ``cover`` -- the covering group as `\ZZ`-module - - ``relations`` -- the relations as submodule of ``cover``. + - ``relations`` -- the relations as submodule of ``cover`` """ # The element class must be overridden in derived classes @@ -246,7 +245,7 @@ def _repr_(self): def _latex_(self): r""" - Returns a Latex representation of the group, using the invariants. + Return a Latex representation of the group, using the invariants. EXAMPLES:: @@ -296,11 +295,11 @@ def _module_constructor(self, cover, relations, check=True): INPUT: - - ``cover`` -- the covering group as `\ZZ`-module. + - ``cover`` -- the covering group as `\ZZ`-module - - ``relations`` -- the relations as submodule of ``cover``. + - ``relations`` -- the relations as submodule of ``cover`` - - ``check`` -- ignored, present for compatibility with ``fg_pid`` code. + - ``check`` -- ignored, present for compatibility with ``fg_pid`` code EXAMPLES:: @@ -326,7 +325,7 @@ def _module_constructor(self, cover, relations, check=True): def order(self): r""" - Return the order of this group (an integer or infinity) + Return the order of this group (integer or infinity). EXAMPLES:: @@ -375,7 +374,7 @@ def is_multiplicative(self): def is_cyclic(self): r""" - Returns ``True`` if the group is cyclic. + Return ``True`` if the group is cyclic. EXAMPLES: @@ -422,7 +421,7 @@ def __init__(self, cover, rels, gens): def gens(self) -> tuple: r""" - Return the specified generators for self (as a tuple). Compare + Return the specified generators for ``self`` (as a tuple). Compare ``self.smithform_gens()``. EXAMPLES:: diff --git a/src/sage/groups/additive_abelian/additive_abelian_wrapper.py b/src/sage/groups/additive_abelian/additive_abelian_wrapper.py index bf16874d97f..966b3137cdb 100644 --- a/src/sage/groups/additive_abelian/additive_abelian_wrapper.py +++ b/src/sage/groups/additive_abelian/additive_abelian_wrapper.py @@ -2,7 +2,7 @@ Wrapper class for abelian groups This class is intended as a template for anything in Sage that needs the -functionality of abelian groups. One can create an AdditiveAbelianGroupWrapper +functionality of abelian groups. One can create an ``AdditiveAbelianGroupWrapper`` object from any given set of elements in some given parent, as long as an ``_add_`` method has been defined. @@ -41,10 +41,10 @@ .. TODO:: - - Think about subgroups and quotients, which probably won't work - in the current implementation -- some fiddly adjustments will be - needed in order to be able to pass extra arguments to the - subquotient's init method. + Think about subgroups and quotients, which probably won't work + in the current implementation -- some fiddly adjustments will be + needed in order to be able to pass extra arguments to the + subquotient's init method. AUTHORS: @@ -77,6 +77,7 @@ from sage.misc.superseded import deprecated_function_alias + class UnwrappingMorphism(Morphism): r""" The embedding into the ambient group. Used by the coercion framework. @@ -159,7 +160,7 @@ def element(self): def _repr_(self): r""" - String representation of self. + String representation of ``self``. EXAMPLES:: @@ -213,7 +214,7 @@ def __init__(self, universe, gens, invariants): self._universe = universe self._gen_elements = tuple(universe(x) for x in gens) self._gen_orders = invariants - cover,rels = addgp.cover_and_relations_from_invariants(invariants) + cover, rels = addgp.cover_and_relations_from_invariants(invariants) addgp.AdditiveAbelianGroup_fixed_gens.__init__(self, cover, rels, cover.gens()) self._unset_coercions_used() self.register_embedding(UnwrappingMorphism(self)) @@ -289,7 +290,7 @@ def discrete_exp(self, v): r""" Given a list (or other iterable) of length equal to the number of generators of this group, compute the element of the ambient group - with those exponents in terms of the generators of self. + with those exponents in terms of the generators of ``self``. EXAMPLES:: @@ -488,13 +489,13 @@ def torsion_subgroup(self, n=None): if n <= 0: raise ValueError('n must be a positive integer') gens, ords = [], [] - for g,o in genords: + for g, o in genords: if not o: continue d = n.gcd(o) if d == 1: continue - gens.append(o//d * g) + gens.append(o // d * g) ords.append(d) return AdditiveAbelianGroupWrapper(self.universe(), gens, ords) @@ -691,9 +692,9 @@ def _expand_basis_pgroup(p, alphas, vals, beta, h, rel): if not (isinstance(alphas, list) and isinstance(vals, list)): raise TypeError('alphas and vals must be lists for mutability') if not len(alphas) == len(vals) == k - 1: - raise ValueError(f'alphas and/or vals have incorrect length') -# assert not sum(r*a for r,a in zip(rel, alphas+[beta])) -# assert all(a.order() == p**v for a,v in zip(alphas,vals)) + raise ValueError('alphas and/or vals have incorrect length') + # assert not sum(r*a for r,a in zip(rel, alphas+[beta])) + # assert all(a.order() == p**v for a,v in zip(alphas,vals)) if rel[-1] < 0: raise ValueError('rel must have nonnegative entries') @@ -726,8 +727,8 @@ def _expand_basis_pgroup(p, alphas, vals, beta, h, rel): return # step 3 - j = next(j for j,r in enumerate(rel) if r == min_r) - alphas[j] = sum(a * (r//rel[j]) for a,r in zip(alphas+[beta], rel)) + j = next(j for j, r in enumerate(rel) if r == min_r) + alphas[j] = sum(a * (r // rel[j]) for a, r in zip(alphas + [beta], rel)) # step 4 if not alphas[j]: @@ -752,7 +753,8 @@ def _expand_basis_pgroup(p, alphas, vals, beta, h, rel): else: alphas.append(beta) vals.append(h) -# assert alphas[-1].order() == p**vals[-1] + # assert alphas[-1].order() == p**vals[-1] + def basis_from_generators(gens, ords=None): r""" @@ -803,7 +805,8 @@ def basis_from_generators(gens, ords=None): gammas = [] ms = [] for p in ps: - pgens = [(o.prime_to_m_part(p) * g, o.p_primary_part(p)) for g, o in zip(gens, ords) if not o % p] + pgens = [(o.prime_to_m_part(p) * g, o.p_primary_part(p)) + for g, o in zip(gens, ords) if not o % p] assert pgens pgens.sort(key=lambda tup: tup[1]) @@ -814,7 +817,7 @@ def basis_from_generators(gens, ords=None): while pgens: beta, ord_beta = pgens.pop() try: - dlog = _discrete_log_pgroup(p, vals, alphas, beta) + _ = _discrete_log_pgroup(p, vals, alphas, beta) except ValueError: pass else: @@ -845,8 +848,8 @@ def basis_from_generators(gens, ords=None): gammas.append(a) ms.append(p ** v) -## assert len({sum(i*g for i,g in zip(vec,gammas)) -## for vec in __import__('itertools').product(*map(range,ms))}) \ -## == __import__('sage').misc.misc_c.prod(ms) +# assert len({sum(i*g for i,g in zip(vec,gammas)) +# for vec in __import__('itertools').product(*map(range,ms))}) \ +# == __import__('sage').misc.misc_c.prod(ms) return gammas, ms diff --git a/src/sage/groups/additive_abelian/qmodnz.py b/src/sage/groups/additive_abelian/qmodnz.py index db0b23d5fe4..8ae0dd35728 100644 --- a/src/sage/groups/additive_abelian/qmodnz.py +++ b/src/sage/groups/additive_abelian/qmodnz.py @@ -51,12 +51,10 @@ class QmodnZ(Parent, UniqueRepresentation): #. ``QQ/(n*ZZ)``, where - - `n` -- an integer (including 0 or negative integers). + - `n` -- integer (including 0 or negative integers). - OUTPUT: - - The abelian group `\Q/n\Z`. + OUTPUT: the abelian group `\Q/n\Z` EXAMPLES:: diff --git a/src/sage/groups/additive_abelian/qmodnz_element.py b/src/sage/groups/additive_abelian/qmodnz_element.py index 10f92f76bf9..b3ebe8e2335 100644 --- a/src/sage/groups/additive_abelian/qmodnz_element.py +++ b/src/sage/groups/additive_abelian/qmodnz_element.py @@ -34,13 +34,11 @@ class QmodnZ_Element(AdditiveGroupElement): INPUT: - - ``q`` -- a rational number. + - ``q`` -- a rational number - - ``parent`` -- the parent abelian group `\Q/n\Z`. + - ``parent`` -- the parent abelian group `\Q/n\Z` - OUTPUT: - - The element `q` of abelian group `\Q/n\Z`, in standard form. + OUTPUT: the element `q` of abelian group `\Q/n\Z`, in standard form EXAMPLES:: @@ -330,7 +328,7 @@ def _richcmp_(self, right, op): def additive_order(self): r""" - Returns the order of this element in the abelian group `\Q/n\Z`. + Return the order of this element in the abelian group `\Q/n\Z`. EXAMPLES:: diff --git a/src/sage/groups/affine_gps/affine_group.py b/src/sage/groups/affine_gps/affine_group.py index a811640c3c0..7d27488ec59 100644 --- a/src/sage/groups/affine_gps/affine_group.py +++ b/src/sage/groups/affine_gps/affine_group.py @@ -186,11 +186,11 @@ def __init__(self, degree, ring): INPUT: - - ``degree`` -- integer. The degree of the affine group, that + - ``degree`` -- integer; the degree of the affine group, that is, the dimension of the affine space the group is acting on - naturally. + naturally - - ``ring`` -- a ring. The base ring of the affine space. + - ``ring`` -- a ring; the base ring of the affine space EXAMPLES:: @@ -308,7 +308,7 @@ def degree(self): """ Return the dimension of the affine space. - OUTPUT: An integer. + OUTPUT: integer EXAMPLES:: @@ -327,10 +327,8 @@ def matrix_space(self): Return the space of matrices representing the general linear transformations. - OUTPUT: - - The parent of the matrices `A` defining the affine group - element `Ax+b`. + OUTPUT: the parent of the matrices `A` defining the affine group + element `Ax+b` EXAMPLES:: @@ -393,7 +391,7 @@ def linear(self, A): - ``A`` -- anything that determines a matrix - OUTPUT: The affine group element `x \mapsto A x`. + OUTPUT: The affine group element `x \mapsto A x` EXAMPLES:: @@ -414,7 +412,7 @@ def translation(self, b): - ``b`` -- anything that determines a vector - OUTPUT: The affine group element `x \mapsto x + b`. + OUTPUT: The affine group element `x \mapsto x + b` EXAMPLES:: @@ -437,7 +435,7 @@ def reflection(self, v): INPUT: - - ``v`` -- a vector, or something that determines a vector. + - ``v`` -- a vector, or something that determines a vector OUTPUT: diff --git a/src/sage/groups/affine_gps/euclidean_group.py b/src/sage/groups/affine_gps/euclidean_group.py index 3154eee7a01..000210ddeab 100644 --- a/src/sage/groups/affine_gps/euclidean_group.py +++ b/src/sage/groups/affine_gps/euclidean_group.py @@ -164,9 +164,9 @@ def _element_constructor_check(self, A, b): INPUT: - - ``A`` -- an element of :meth:`matrix_space`. + - ``A`` -- an element of :meth:`matrix_space` - - ``b`` -- an element of :meth:`vector_space`. + - ``b`` -- an element of :meth:`vector_space` OUTPUT: diff --git a/src/sage/groups/affine_gps/group_element.py b/src/sage/groups/affine_gps/group_element.py index 42546194bb6..c4ffa997e94 100644 --- a/src/sage/groups/affine_gps/group_element.py +++ b/src/sage/groups/affine_gps/group_element.py @@ -53,20 +53,20 @@ class AffineGroupElement(MultiplicativeGroupElement): INPUT: - ``A`` -- an invertible matrix, or something defining a - matrix if ``convert==True``. + matrix if ``convert==True`` - ``b``-- a vector, or something defining a vector if ``convert==True`` (default: ``0``, defining the zero - vector). + vector) - - ``parent`` -- the parent affine group. + - ``parent`` -- the parent affine group - - ``convert`` - bool (default: ``True``). Whether to convert + - ``convert`` - bool (default: ``True``); whether to convert ``A`` into the correct matrix space and ``b`` into the - correct vector space. + correct vector space - - ``check`` - bool (default: ``True``). Whether to do some - checks or just accept the input as valid. + - ``check`` - bool (default: ``True``); whether to do some + checks or just accept the input as valid As a special case, ``A`` can be a matrix obtained from :meth:`matrix`, that is, one row and one column larger. In @@ -145,7 +145,7 @@ def A(self): """ Return the general linear part of an affine group element. - OUTPUT: The matrix `A` of the affine group element `Ax + b`. + OUTPUT: The matrix `A` of the affine group element `Ax + b` EXAMPLES:: @@ -162,7 +162,7 @@ def b(self): """ Return the translation part of an affine group element. - OUTPUT: The vector `b` of the affine group element `Ax + b`. + OUTPUT: The vector `b` of the affine group element `Ax + b` EXAMPLES:: @@ -326,13 +326,10 @@ def _mul_(self, other): INPUT: - - ``other`` -- another element of the same affine group. + - ``other`` -- another element of the same affine group - OUTPUT: - - The product of the affine group elements ``self`` and - ``other`` defined by the composition of the two affine - transformations. + OUTPUT: the product of the affine group elements ``self`` and + ``other`` defined by the composition of the two affine transformations EXAMPLES:: @@ -357,9 +354,9 @@ def __call__(self, v): INPUT: - ``v`` -- a polynomial, a multivariate polynomial, a polyhedron, a - vector, or anything that can be converted into a vector. + vector, or anything that can be converted into a vector - OUTPUT: The image of ``v`` under the affine group element. + OUTPUT: the image of ``v`` under the affine group element EXAMPLES:: @@ -402,7 +399,6 @@ def __call__(self, v): sage: cube = polytopes.cube() # needs sage.geometry.polyhedron sage: f(cube) # needs sage.geometry.polyhedron A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 8 vertices - """ parent = self.parent() @@ -455,7 +451,7 @@ def __invert__(self): """ Return the inverse group element. - OUTPUT: Another affine group element. + OUTPUT: another affine group element EXAMPLES:: diff --git a/src/sage/groups/artin.py b/src/sage/groups/artin.py index afb40463206..306569e546b 100644 --- a/src/sage/groups/artin.py +++ b/src/sage/groups/artin.py @@ -51,9 +51,7 @@ def _latex_(self): r""" Return a LaTeX representation of ``self``. - OUTPUT: - - String. A valid LaTeX math command sequence. + OUTPUT: a string; a valid LaTeX math command sequence TESTS:: @@ -79,9 +77,7 @@ def exponent_sum(self): """ Return the exponent sum of ``self``. - OUTPUT: - - Integer. + OUTPUT: integer EXAMPLES:: @@ -111,11 +107,10 @@ def coxeter_group_element(self, W=None): INPUT: - - ``W`` -- (default: ``self.parent().coxeter_group()``) the image Coxeter group - - OUTPUT: + - ``W`` -- (default: ``self.parent().coxeter_group()``) the image + Coxeter group - An element of the Coxeter group ``W``. + OUTPUT: an element of the Coxeter group ``W`` EXAMPLES:: @@ -263,10 +258,8 @@ def _left_normal_form_coxeter(self): Return the left normal form of the element, in the `\Delta` exponent and Coxeter group element form. - OUTPUT: - - A tuple whose first element is the power of `\Delta`, and the rest - are the Coxeter elements corresponding to the simple factors. + OUTPUT: tuple whose first element is the power of `\Delta`, and the + rest are the Coxeter elements corresponding to the simple factors EXAMPLES:: @@ -515,9 +508,7 @@ def cardinality(self): """ Return the number of elements of ``self``. - OUTPUT: - - Infinity. + OUTPUT: Infinity EXAMPLES:: @@ -605,9 +596,7 @@ def index_set(self): """ Return the index set of ``self``. - OUTPUT: - - A tuple. + OUTPUT: tuple EXAMPLES:: @@ -665,7 +654,7 @@ def _standard_lift_Tietze(self, w): INPUT: - - ``w`` -- an element of the Coxeter group of ``self``. + - ``w`` -- an element of the Coxeter group of ``self`` EXAMPLES:: @@ -683,7 +672,7 @@ def _standard_lift(self, w): INPUT: - - ``w`` -- an element of the Coxeter group of ``self``. + - ``w`` -- an element of the Coxeter group of ``self`` EXAMPLES:: diff --git a/src/sage/groups/braid.py b/src/sage/groups/braid.py index fb52b074218..7721d3d602f 100644 --- a/src/sage/groups/braid.py +++ b/src/sage/groups/braid.py @@ -170,9 +170,7 @@ def components_in_closure(self): """ Return the number of components of the trace closure of the braid. - OUTPUT: - - Positive integer. + OUTPUT: a positive integer EXAMPLES:: @@ -442,9 +440,7 @@ def permutation(self, W=None): - ``W`` -- (optional) the permutation group to project ``self`` to; the default is ``self.parent().coxeter_group()`` - OUTPUT: - - The image of ``self`` under the natural projection map to ``W``. + OUTPUT: the image of ``self`` under the natural projection map to ``W`` EXAMPLES:: @@ -676,9 +672,8 @@ def LKB_matrix(self, variables='x,y'): - ``variables`` -- string (default: ``'x,y'``). A string containing the names of the variables, separated by a comma. - OUTPUT: - - The matrix corresponding to the Lawrence-Krammer-Bigelow representation of the braid. + OUTPUT: the matrix corresponding to the Lawrence-Krammer-Bigelow + representation of the braid EXAMPLES:: @@ -811,10 +806,9 @@ def links_gould_matrix(self, symbolics=False): are elements of a quotient ring of a three variate Laurent polynomial ring. - OUTPUT: - - The representation matrix of ``self`` over the ring according to the choice - of the keyword ``symbolics`` (see the corresponding explanation). + OUTPUT: the representation matrix of ``self`` over the ring according + to the choice of the keyword ``symbolics`` (see the corresponding + explanation) EXAMPLES:: @@ -849,9 +843,7 @@ def links_gould_polynomial(self, varnames=None, use_symbolics=False): - ``varnames`` -- string (default ``t0, t1``) - OUTPUT: - - A Laurent polynomial in the given variable names. + OUTPUT: a Laurent polynomial in the given variable names EXAMPLES:: @@ -908,9 +900,7 @@ def tropical_coordinates(self): r""" Return the tropical coordinates of ``self`` in the braid group `B_n`. - OUTPUT: - - - a list of `2n` tropical integers + OUTPUT: a list of `2n` tropical integers EXAMPLES:: @@ -1325,9 +1315,7 @@ def _annular_khovanov_complex_cached(self, qagrad, ring=None): - ``ring`` -- (default: ``ZZ``) the coefficient ring - OUTPUT: - - The annular Khovanov complex of the braid in the given grading. + OUTPUT: the annular Khovanov complex of the braid in the given grading .. NOTE:: @@ -1380,8 +1368,8 @@ def annular_khovanov_complex(self, qagrad=None, ring=None): OUTPUT: The annular Khovanov complex of the braid, given as a dictionary whose - keys are tuples of quantum and annular grading. - If ``qagrad`` is specified only return the chain complex of that grading. + keys are tuples of quantum and annular grading. If ``qagrad`` is + specified only return the chain complex of that grading. EXAMPLES:: @@ -1437,9 +1425,9 @@ def annular_khovanov_homology(self, qagrad=None, ring=IntegerRing()): OUTPUT: - If ``qagrad`` is ``None``, return a dictionary of homogies in all - gradings indexed by grading. If qagrad is specified, return homology - of that grading. + If ``qagrad`` is ``None``, return a dictionary of homologies in all + gradings indexed by grading. If ``qagrad`` is specified, return the + homology of that grading. .. NOTE:: @@ -1535,10 +1523,8 @@ def _left_normal_form_coxeter(self): r""" Return the left normal form of the braid, in permutation form. - OUTPUT: - - A tuple whose first element is the power of `\Delta`, and the - rest are the permutations corresponding to the simple factors. + OUTPUT: tuple whose first element is the power of `\Delta`, and the + rest are the permutations corresponding to the simple factors EXAMPLES:: @@ -1633,7 +1619,6 @@ def centralizer(self): sage: b = B([2, 1, 3, 2]) sage: b.centralizer() [s1*s0*s2*s1, s0*s2] - """ c = centralizer(self) B = self.parent() @@ -1641,7 +1626,7 @@ def centralizer(self): def super_summit_set(self): """ - Return a list with the super summit set of the braid + Return a list with the super summit set of the braid. EXAMPLES:: @@ -1652,7 +1637,6 @@ def super_summit_set(self): (s0^-1*s1^-1*s0^-1)^2*s1^2*s0^3*s1, (s0^-1*s1^-1*s0^-1)^2*s1*s0^3*s1^2, s0^-1*s1^-1*s0^-2*s1^-1*s0*s1^3*s0] - """ sss = supersummitset(self) B = self.parent() @@ -1710,10 +1694,8 @@ def conjugating_braid(self, other): OUTPUT: - A conjugating braid. - - More precisely, if the output is `d`, `o` equals ``other``, and `s` equals ``self`` - then `o = d^{-1} \cdot s \cdot d`. + A conjugating braid. More precisely, if the output is `d`, `o` equals + ``other``, and `s` equals ``self`` then `o = d^{-1} \cdot s \cdot d`. EXAMPLES:: @@ -1798,10 +1780,9 @@ def pure_conjugating_braid(self, other): OUTPUT: - A pure conjugating braid. - - More precisely, if the output is `d`, `o` equals ``other``, and `s` equals ``self`` - then `o = d^{-1} \cdot s \cdot d`. + A pure conjugating braid. More precisely, if the output is `d`, `o` + equals ``other``, and `s` equals ``self`` then + `o = d^{-1} \cdot s \cdot d`. EXAMPLES:: @@ -1883,7 +1864,7 @@ def pure_conjugating_braid(self, other): def ultra_summit_set(self): """ - Return a list with the orbits of the ultra summit set of ``self`` + Return a list with the orbits of the ultra summit set of ``self``. EXAMPLES:: @@ -1914,9 +1895,7 @@ def thurston_type(self): """ Return the thurston_type of ``self``. - OUTPUT: - - One of ``'reducible'``, ``'periodic'`` or ``'pseudo-anosov'``. + OUTPUT: one of ``'reducible'``, ``'periodic'`` or ``'pseudo-anosov'`` EXAMPLES:: @@ -1946,7 +1925,6 @@ def is_reducible(self): sage: a = B([2, 2, -1, -1, 2, 2]) sage: a.is_reducible() False - """ return self.thurston_type() == 'reducible' @@ -2002,9 +1980,7 @@ def sliding_circuits(self): """ Return the sliding circuits of the braid. - OUTPUT: - - A list of sliding circuits. Each sliding circuit is itself + OUTPUT: a list of sliding circuits. Each sliding circuit is itself a list of braids. EXAMPLES:: @@ -2081,9 +2057,7 @@ def deformed_burau_matrix(self, variab='q'): resulting laurent polynomial, which is the base ring for the free algebra constructed - OUTPUT: - - A matrix with elements in the free algebra ``self._algebra``. + OUTPUT: a matrix with elements in the free algebra ``self._algebra`` EXAMPLES:: @@ -2333,10 +2307,8 @@ def tuples(self): This is in the reduced form as outlined in Definition 4.1 of [HL2018]_. - OUTPUT: - - A dict of tuples of ints corresponding to the exponents in the - generators with values in the algebra's base ring. + OUTPUT: a dict of tuples of ints corresponding to the exponents in the + generators with values in the algebra's base ring EXAMPLES:: @@ -2384,9 +2356,7 @@ def reduced_word(self): r""" Return the (reduced) right quantum word. - OUTPUT: - - An element in the free algebra. + OUTPUT: an element in the free algebra EXAMPLES:: @@ -2441,7 +2411,7 @@ def eps(self, N): INPUT: - - ``N`` -- an integer; the number of colors + - ``N`` -- integer; the number of colors EXAMPLES:: @@ -2590,8 +2560,8 @@ def __init__(self, names): rels = [] for i in range(1, n): rels.append(free_group([i, i + 1, i, -i - 1, -i, -i - 1])) - for j in range(i + 2, n + 1): - rels.append(free_group([i, j, -i, -j])) + rels.extend(free_group([i, j, -i, -j]) + for j in range(i + 2, n + 1)) cat = Groups().Infinite() FinitelyPresentedGroup.__init__(self, free_group, tuple(rels), category=cat) @@ -2619,11 +2589,7 @@ def __reduce__(self): def _repr_(self): """ - Return a string representation - - OUTPUT: - - String. + Return a string representation. TESTS:: @@ -2637,9 +2603,7 @@ def cardinality(self): """ Return the number of group elements. - OUTPUT: - - Infinity. + OUTPUT: Infinity TESTS:: @@ -2656,10 +2620,8 @@ def as_permutation_group(self): """ Return an isomorphic permutation group. - OUTPUT: - - This raises a :class:`ValueError` error since braid groups - are infinite. + OUTPUT: this raises a :class:`ValueError` error since braid groups + are infinite TESTS:: @@ -2675,9 +2637,7 @@ def strands(self): """ Return the number of strands. - OUTPUT: - - Integer. + OUTPUT: integer EXAMPLES:: @@ -2753,10 +2713,7 @@ def _standard_lift_Tietze(self, p): - Every two strands cross each other at most once. - OUTPUT: - - A shortest word that represents the braid, - in Tietze list form. + OUTPUT: a shortest word that represents the braid, in Tietze list form EXAMPLES:: @@ -2880,16 +2837,14 @@ def _LKB_matrix_(self, braid, variab): INPUT: - - ``braid`` -- tuple of integers. The Tietze list of the - braid. + - ``braid`` -- tuple of integers; the Tietze list of the + braid - - ``variab`` -- string. the names of the variables that will + - ``variab`` -- string. The names of the variables that will appear in the matrix. They must be given as a string, separated by a comma - OUTPUT: - - The LKB matrix of the braid, with respect to the variables. + OUTPUT: the LKB matrix of the braid, with respect to the variables TESTS:: @@ -3036,9 +2991,7 @@ def TL_basis_with_drain(self, drain_size): - ``drain_size`` -- integer between 0 and the number of strands (both inclusive) - OUTPUT: - - A list of basis elements, each of which is a list of integers. + OUTPUT: a list of basis elements, each of which is a list of integers EXAMPLES: @@ -3220,10 +3173,8 @@ def TL_representation(self, drain_size, variab=None): entries of the matrices; if ``None``, then use a default variable in `\ZZ[A,A^{-1}]` - OUTPUT: - - A list of matrices corresponding to the representations of each - of the standard generators and their inverses. + OUTPUT: a list of matrices corresponding to the representations of each + of the standard generators and their inverses EXAMPLES:: @@ -3311,7 +3262,8 @@ def TL_representation(self, drain_size, variab=None): def mapping_class_action(self, F): """ - Return the action of self in the free group F as mapping class group. + Return the action of ``self`` in the free group F as mapping class + group. This action corresponds to the action of the braid over the punctured disk, whose fundamental group is the free group on @@ -3321,9 +3273,7 @@ def mapping_class_action(self, F): element with a braid. So you generally do not have to construct this action yourself. - OUTPUT: - - A :class:`MappingClassGroupAction`. + OUTPUT: a :class:`MappingClassGroupAction` EXAMPLES:: @@ -3502,7 +3452,7 @@ def epimorphisms(self, H): def BraidGroup(n=None, names='s'): """ - Construct a Braid Group + Construct a Braid Group. INPUT: @@ -3646,13 +3596,11 @@ def _act_(self, b, x): INPUT: - - ``b`` -- a braid. - - - ``x`` -- a free group element. + - ``b`` -- a braid - OUTPUT: + - ``x`` -- a free group element - A new braid. + OUTPUT: a new braid TESTS:: diff --git a/src/sage/groups/cactus_group.py b/src/sage/groups/cactus_group.py index a4a6fe629b0..c8d6ba7241f 100644 --- a/src/sage/groups/cactus_group.py +++ b/src/sage/groups/cactus_group.py @@ -43,7 +43,7 @@ class CactusGroup(UniqueRepresentation, Group): INPUT: - - ``n`` -- an integer + - ``n`` -- integer EXAMPLES: diff --git a/src/sage/groups/class_function.py b/src/sage/groups/class_function.py index 901b0083b49..5d4dc0c1505 100644 --- a/src/sage/groups/class_function.py +++ b/src/sage/groups/class_function.py @@ -4,11 +4,13 @@ This module implements a wrapper of GAP's ClassFunction function. -NOTE: The ordering of the columns of the character table of a group -corresponds to the ordering of the list. However, in general there is -no way to canonically list (or index) the conjugacy classes of a group. -Therefore the ordering of the columns of the character table of -a group is somewhat random. +.. NOTE: + + The ordering of the columns of the character table of a group + corresponds to the ordering of the list. However, in general there is + no way to canonically list (or index) the conjugacy classes of a group. + Therefore the ordering of the columns of the character table of + a group is somewhat random. AUTHORS: @@ -48,11 +50,10 @@ def ClassFunction(group, values): INPUT: - - ``group`` -- a group. - - - ``values`` -- list/tuple/iterable of numbers. The values of the - class function on the conjugacy classes, in that order. + - ``group`` -- a group + - ``values`` -- list/tuple/iterable of numbers; the values of the + class function on the conjugacy classes, in that order EXAMPLES:: @@ -132,7 +133,7 @@ def __init__(self, G, values): def _gap_init_(self): r""" - Return a string showing how to declare / initialize self in Gap. + Return a string showing how to declare / initialize ``self`` in Gap. Stored in the \code{self._gap_string} attribute. EXAMPLES:: @@ -146,7 +147,7 @@ def _gap_init_(self): def _gap_(self, *args): r""" - Coerce self into a GAP element. + Coerce ``self`` into a GAP element. EXAMPLES:: @@ -167,10 +168,6 @@ def __repr__(self): r""" Return a string representation. - OUTPUT: - - A string. - EXAMPLES:: sage: G = SymmetricGroup(4) @@ -182,7 +179,7 @@ def __repr__(self): def __iter__(self): r""" - Iterate through the values of self evaluated on the conjugacy + Iterate through the values of ``self`` evaluated on the conjugacy classes. EXAMPLES:: @@ -296,16 +293,13 @@ def __call__(self, g): def __add__(self, other): r""" - Return the sum of the characters self and other. + Return the sum of the characters of ``self`` and other. INPUT: - - ``other`` -- a :class:`ClassFunction` of the same group as - ``self``. - - OUTPUT: + - ``other`` -- a :class:`ClassFunction` of the same group as ``self`` - A :class:`ClassFunction` + OUTPUT: a :class:`ClassFunction` EXAMPLES:: @@ -328,11 +322,9 @@ def __sub__(self, other): INPUT: - ``other`` -- a :class:`ClassFunction` of the same group as - ``self``. + ``self`` - OUTPUT: - - A :class:`ClassFunction` + OUTPUT: a :class:`ClassFunction` EXAMPLES:: @@ -357,13 +349,11 @@ def __mul__(self, other): INPUT: - ``other`` -- either a number or a :class:`ClassFunction` of - the same group as ``self``. A number can be anything that + the same group as ``self``; a number can be anything that can be converted into GAP: integers, rational, and elements - of certain number fields. + of certain number fields - OUTPUT: - - A :class:`ClassFunction` + OUTPUT: a :class:`ClassFunction` EXAMPLES:: @@ -376,7 +366,6 @@ def __mul__(self, other): sage: (3*chi1).values() [9, 3, -3, 0, -3] - sage: (1/2*chi1).values() [3/2, 1/2, -1/2, 0, -1/2] @@ -420,9 +409,7 @@ def __pos__(self): r""" Return ``self``. - OUTPUT: - - A :class:`ClassFunction` + OUTPUT: a :class:`ClassFunction` EXAMPLES:: @@ -440,9 +427,7 @@ def __neg__(self): r""" Return the additive inverse of ``self``. - OUTPUT: - - A :class:`ClassFunction` + OUTPUT: a :class:`ClassFunction` EXAMPLES:: @@ -458,7 +443,7 @@ def __neg__(self): def __pow__(self, other): r""" - Return the product of self with itself other times. + Return the product of ``self`` with itself other times. EXAMPLES:: @@ -475,16 +460,13 @@ def __pow__(self, other): def symmetric_power(self, n): r""" - Return the symmetrized product of self with itself ``n`` times. + Return the symmetrized product of ``self`` with itself ``n`` times. INPUT: - - ``n`` -- a positive integer. - - OUTPUT: + - ``n`` -- positive integer - The ``n``-th symmetrized power of ``self`` as a - :class:`ClassFunction`. + OUTPUT: the ``n``-th symmetrized power of ``self`` as a :class:`ClassFunction` EXAMPLES:: @@ -501,21 +483,19 @@ def symmetric_power(self, n): def exterior_power(self, n): r""" - Return the anti-symmetrized product of self with itself ``n`` times. + Return the antisymmetrized product of ``self`` with itself ``n`` + times. INPUT: - - ``n`` -- a positive integer. + - ``n`` -- positive integer. - OUTPUT: - - The ``n``-th anti-symmetrized power of ``self`` as a - :class:`ClassFunction`. + OUTPUT: the ``n``-th antisymmetrized power of ``self`` as a :class:`ClassFunction` EXAMPLES:: sage: chi = ClassFunction(SymmetricGroup(4), gap([3, 1, -1, 0, -1])) - sage: p = chi.exterior_power(3) # the highest anti-symmetric power for a 3-d character + sage: p = chi.exterior_power(3) # the highest antisymmetric power for a 3-d character sage: p Character of Symmetric group of order 4! as a permutation group sage: p.values() @@ -529,7 +509,7 @@ def exterior_power(self, n): def scalar_product(self, other): r""" - Return the scalar product of self with other. + Return the scalar product of ``self`` with other. EXAMPLES:: @@ -546,8 +526,8 @@ def scalar_product(self, other): def is_irreducible(self): r""" - Return True if self cannot be written as the sum of two nonzero - characters of self. + Return ``True`` if ``self`` cannot be written as the sum of two nonzero + characters of ``self``. EXAMPLES:: @@ -605,10 +585,9 @@ def irreducible_constituents(self): L = self._gap_classfunction.ConstituentsOfCharacter() return tuple(ClassFunction(self._group, list(l)) for l in L) - def decompose(self): + def decompose(self) -> tuple: r""" - Returns a list of the characters that appear in the decomposition - of chi. + Return a list of the characters appearing the decomposition of ``self``. EXAMPLES:: @@ -618,14 +597,13 @@ def decompose(self): ((3, Character of Symmetric group of order 5! as a permutation group), (2, Character of Symmetric group of order 5! as a permutation group)) """ - L = [] - for irr in self.irreducible_constituents(): - L.append((self.scalar_product(irr), irr)) + L = [(self.scalar_product(irr), irr) + for irr in self.irreducible_constituents()] return tuple(L) def norm(self): r""" - Returns the norm of self. + Return the norm of ``self``. EXAMPLES:: @@ -635,9 +613,9 @@ def norm(self): """ return self._gap_classfunction.Norm() - def values(self): + def values(self) -> list: r""" - Return the list of values of self on the conjugacy classes. + Return the list of values of ``self`` on the conjugacy classes. EXAMPLES:: @@ -665,7 +643,7 @@ def values(self): def central_character(self): r""" - Returns the central character of self. + Return the central character of ``self``. EXAMPLES:: @@ -677,7 +655,7 @@ def central_character(self): def determinant_character(self): r""" - Returns the determinant character of self. + Return the determinant character of ``self``. EXAMPLES:: @@ -704,11 +682,9 @@ def restrict(self, H): INPUT: - - ``H`` -- a subgroup of the underlying group of ``self``. - - OUTPUT: + - ``H`` -- a subgroup of the underlying group of ``self`` - A :class:`ClassFunction` of ``H`` defined by restriction. + OUTPUT: a :class:`ClassFunction` of ``H`` defined by restriction EXAMPLES:: @@ -731,7 +707,7 @@ def induct(self, G): INPUT: - - ``G`` -- A supergroup of the underlying group of ``self``. + - ``G`` -- a supergroup of the underlying group of ``self`` OUTPUT: @@ -865,10 +841,6 @@ def _repr_(self): r""" Return a string representation. - OUTPUT: - - A string. - EXAMPLES:: sage: G = SymmetricGroup(4) @@ -955,9 +927,7 @@ def domain(self): r""" Return the domain of ``self``. - OUTPUT: - - The underlying group of the class function. + OUTPUT: the underlying group of the class function EXAMPLES:: @@ -1004,12 +974,9 @@ def __add__(self, other): INPUT: - - ``other`` -- a :class:`ClassFunction` of the same group as - ``self``. - - OUTPUT: + - ``other`` -- a :class:`ClassFunction` of the same group as ``self`` - A :class:`ClassFunction` + OUTPUT: a :class:`ClassFunction` EXAMPLES:: @@ -1031,12 +998,9 @@ def __sub__(self, other): INPUT: - - ``other`` -- a :class:`ClassFunction` of the same group as - ``self``. - - OUTPUT: + - ``other`` -- a :class:`ClassFunction` of the same group as ``self`` - A :class:`ClassFunction` + OUTPUT: a :class:`ClassFunction` EXAMPLES:: @@ -1065,9 +1029,7 @@ def __mul__(self, other): can be converted into GAP: integers, rational, and elements of certain number fields. - OUTPUT: - - A :class:`ClassFunction` + OUTPUT: a :class:`ClassFunction` EXAMPLES:: @@ -1080,7 +1042,6 @@ def __mul__(self, other): sage: (3*chi1).values() [9, 3, -3, 0, -3] - sage: (1/2*chi1).values() [3/2, 1/2, -1/2, 0, -1/2] @@ -1124,9 +1085,7 @@ def __pos__(self): r""" Return ``self``. - OUTPUT: - - A :class:`ClassFunction` + OUTPUT: a :class:`ClassFunction` EXAMPLES:: @@ -1144,9 +1103,7 @@ def __neg__(self): r""" Return the additive inverse of ``self``. - OUTPUT: - - A :class:`ClassFunction` + OUTPUT: a :class:`ClassFunction` EXAMPLES:: @@ -1183,12 +1140,9 @@ def symmetric_power(self, n): INPUT: - - ``n`` -- a positive integer + - ``n`` -- positive integer - OUTPUT: - - The ``n``-th symmetrized power of ``self`` as a - :class:`ClassFunction`. + OUTPUT: the ``n``-th symmetrized power of ``self`` as a :class:`ClassFunction` EXAMPLES:: @@ -1205,21 +1159,18 @@ def symmetric_power(self, n): def exterior_power(self, n): r""" - Return the anti-symmetrized product of ``self`` with itself ``n`` times. + Return the antisymmetrized product of ``self`` with itself ``n`` times. INPUT: - - ``n`` -- a positive integer - - OUTPUT: + - ``n`` -- positive integer - The ``n``-th anti-symmetrized power of ``self`` as a - :class:`ClassFunction`. + OUTPUT: the ``n``-th antisymmetrized power of ``self`` as a :class:`ClassFunction` EXAMPLES:: sage: chi = ClassFunction(SymmetricGroup(4), [3, 1, -1, 0, -1]) - sage: p = chi.exterior_power(3) # the highest anti-symmetric power for a 3-d character + sage: p = chi.exterior_power(3) # the highest antisymmetric power for a 3-d character sage: p Character of Symmetric group of order 4! as a permutation group sage: p.values() @@ -1310,10 +1261,9 @@ def irreducible_constituents(self): L = self._gap_classfunction.ConstituentsOfCharacter() return tuple(ClassFunction_libgap(self._group, l) for l in L) - def decompose(self): + def decompose(self) -> tuple: r""" - Return a list of the characters that appear in the decomposition - of ``self``. + Return a list of the characters appearing the decomposition of ``self``. EXAMPLES:: @@ -1323,9 +1273,8 @@ def decompose(self): ((3, Character of Symmetric group of order 5! as a permutation group), (2, Character of Symmetric group of order 5! as a permutation group)) """ - L = [] - for irr in self.irreducible_constituents(): - L.append((self.scalar_product(irr), irr)) + L = [(self.scalar_product(irr), irr) + for irr in self.irreducible_constituents()] return tuple(L) def norm(self): @@ -1342,7 +1291,7 @@ def norm(self): def values(self): r""" - Return the list of values of self on the conjugacy classes. + Return the list of values of ``self`` on the conjugacy classes. EXAMPLES:: @@ -1412,11 +1361,9 @@ def restrict(self, H): INPUT: - - ``H`` -- a subgroup of the underlying group of ``self``. - - OUTPUT: + - ``H`` -- a subgroup of the underlying group of ``self`` - A :class:`ClassFunction` of ``H`` defined by restriction. + OUTPUT: a :class:`ClassFunction` of ``H`` defined by restriction EXAMPLES:: @@ -1444,7 +1391,7 @@ def induct(self, G): INPUT: - - ``G`` -- A supergroup of the underlying group of ``self``. + - ``G`` -- a supergroup of the underlying group of ``self`` OUTPUT: diff --git a/src/sage/groups/conjugacy_classes.py b/src/sage/groups/conjugacy_classes.py index 147cc803842..2208389e17d 100644 --- a/src/sage/groups/conjugacy_classes.py +++ b/src/sage/groups/conjugacy_classes.py @@ -216,7 +216,6 @@ def __iter__(self): True sage: any(x == m2 for x in C) True - """ from sage.sets.recursively_enumerated_set import RecursivelyEnumeratedSet g = self._representative @@ -517,7 +516,6 @@ def set(self): sage: S = [(1,3,2,4), (1,4,3,2), (1,3,4,2), (1,2,3,4), (1,4,2,3), (1,2,4,3)] sage: C.set() == Set(G(x) for x in S) True - """ from sage.sets.set import Set try: diff --git a/src/sage/groups/cubic_braid.py b/src/sage/groups/cubic_braid.py index 9dd59e069f9..6f357c91b58 100644 --- a/src/sage/groups/cubic_braid.py +++ b/src/sage/groups/cubic_braid.py @@ -128,7 +128,7 @@ def eliminate_item(tietze_list): return None first = tietze_list[0] second = None - for i in range(1,l): + for i in range(1, l): if tietze_list[i] in (first, -first): if i == 1: second = tietze_list[i] @@ -502,9 +502,9 @@ def find_root(domain): # -------------------------------------------------------------------- cbg_type = self.parent()._cbg_type if cbg_type == CubicBraidGroup.type.AssionS: - characteristic = 3 # making Assion type S relations vanish + characteristic = 3 # making Assion type S relations vanish elif cbg_type == CubicBraidGroup.type.AssionU: - characteristic = 2 # making Assion type U relations vanish + characteristic = 2 # making Assion type U relations vanish else: characteristic = 0 try: @@ -512,7 +512,7 @@ def find_root(domain): except ValueError: raise ValueError('characteristic must be in integer') - if not characteristic.is_zero() and not characteristic.is_prime(): + if not characteristic.is_zero() and not characteristic.is_prime(): raise ValueError('characteristic must be a prime') if characteristic.is_zero(): from sage.rings.number_field.number_field import CyclotomicField @@ -526,7 +526,7 @@ def find_root(domain): root_bur = find_root(domain) domain = root_bur.parent() - else: # domain is not None + else: # domain is not None root_bur = find_root(domain) else: # root_bur is not None @@ -550,11 +550,11 @@ def conv2domain(laur_pol): from sage.matrix.constructor import matrix d1, d2 = burau_ori.dimensions() - burau_mat = matrix(d1, d2, lambda i,j: conv2domain(burau_ori[i,j])) + burau_mat = matrix(d1, d2, lambda i, j: conv2domain(burau_ori[i, j])) if unitary: - burau_mat_adj = matrix(d1, d2, lambda i,j: conv2domain(burau_ori_adj[i,j])) - herm_form = matrix(d1, d2, lambda i,j: conv2domain(herm_form_ori[i,j])) + burau_mat_adj = matrix(d1, d2, lambda i, j: conv2domain(burau_ori_adj[i, j])) + herm_form = matrix(d1, d2, lambda i, j: conv2domain(herm_form_ori[i, j])) return burau_mat, burau_mat_adj, herm_form return burau_mat @@ -579,7 +579,7 @@ class CubicBraidGroup(FinitelyPresentedGroup): INPUT: - - ``names`` -- see the corresponding documentation of :class:`BraidGroup_class`. + - ``names`` -- see the corresponding documentation of :class:`BraidGroup_class` - ``cbg_type`` -- (default: ``CubicBraidGroup.type.Coxeter``; see explanation below) enum type :class:`CubicBraidGroup.type` @@ -764,15 +764,14 @@ def __init__(self, names, cbg_type=None): # internal naming of elements for convenience b = [free_group([i]) for i in range(1, n+1)] - t = [free_group([i, i+1]) ** 3 for i in range(1, n)] + t = [free_group([i, i+1]) ** 3 for i in range(1, n)] ti = [free_group([-i, -i-1]) ** 3 for i in range(1, n)] - # first the braid relation + # first the braid relations rels = list(self._braid_group.relations()) - # than the cubic relation - for i in range(n): - rels.append(b[i]**3) + # than the cubic relations + rels.extend(b[i]**3 for i in range(n)) # than Assion's relation Satz 2.2 for cbg_type=CubicBraidGroup.type.AssionS # and Satz 2.4 for cbg_type=CubicBraidGroup.type.AssionU @@ -808,10 +807,6 @@ def _repr_(self): r""" Return a string representation. - OUTPUT: - - String describing ``self``. - EXAMPLES:: sage: CubicBraidGroup(2) @@ -1077,18 +1072,17 @@ def set_classical_realization(self, base_group, proj_group, centralizing_matrix, This is a local function of :meth:`_create_classical_realization`. - It handles the common part of symplectic and unitary version and creates conversion maps. + It handles the common part of symplectic and unitary version and + creates conversion maps. INPUT: - - ``base_group`` -- The symplectic or unitary groups Sp(m,3) resp. GU(m,2). - - ``proj_group`` -- The corresponding projective group of base_group. - - ``centralizing_matrix`` -- The centralizing matrix according to Assion. - - ``transvec_matrices`` -- List of transvection matrices according to Assion. + - ``base_group`` -- the symplectic or unitary groups Sp(m,3) resp. GU(m,2) + - ``proj_group`` -- the corresponding projective group of base_group + - ``centralizing_matrix`` -- the centralizing matrix according to Assion + - ``transvec_matrices`` -- list of transvection matrices according to Assion - OUTPUT: - - No output, but the function sets the attributes of ``self`` described above. + OUTPUT: no output, but the function sets the attributes of ``self`` described above """ centralizing_element = None @@ -1156,10 +1150,12 @@ def create_sympl_realization(self, m): INPUT: - - ``m`` -- Integer, the dimension of the classical groups vector-space of operation. + - ``m`` -- integer; the dimension of the classical groups + vector-space of operation - The function calculates the centralizing matrix and the transvections as given by Assion - and then uses set_classical_realization to complete the construction. + The function calculates the centralizing matrix and the + transvections as given by Assion and then uses + ``set_classical_realization`` to complete the construction. """ # ----------------------------------------------------------- # getting the invariant bilinear form of the group @@ -1227,10 +1223,12 @@ def create_unitary_realization(self, m): INPUT: - - ``m`` -- Integer, the dimension of the classical groups vector-space of operation. + - ``m`` -- integer; the dimension of the classical groups + vector-space of operation - The function calculates the centralizing_matrix and the transvections as given by Assion - and then uses set_classical_realization to complete the construction. + The function calculates the centralizing_matrix and the + transvections as given by Assion and then uses + ``set_classical_realization`` to complete the construction. """ # --------------------------------------------------------------------- # getting the invariant bilinear form of the group @@ -1276,7 +1274,7 @@ def create_unitary_realization(self, m): if pos + 1 < m: transvections.append(xbas[pos-1]+xbas[pos]+xbas[pos+1]) # t_{3i+1} = x_{3i-1} + x_{3i} + x_{3i+1} if pos + 3 < m: - transvections.append(xbas[pos+1]+xbas[pos+2]+xbas[pos+3]) # t_{3i+2} = x_{3i+1} + x_{3i+2} + x_{3i+3} + transvections.append(xbas[pos+1]+xbas[pos+2]+xbas[pos+3]) # t_{3i+2} = x_{3i+1} + x_{3i+2} + x_{3i+3} # ----------------------------------------------------------- # Conversion-Map from transvection vector to transvection @@ -1299,15 +1297,14 @@ def transvec2mat(v, bas=bas, bform=bform, fact=a): set_classical_realization(self, base_group, proj_group, centralizing_matrix, transvec_matrices) return - #---------------------------------------------------------------------------------------------------------- - #---------------------------------------------------------------------------------------------------------- + # ---------------------------------------------------------------- # local functions declaration section finishes here - #---------------------------------------------------------------------------------------------------------- - #---------------------------------------------------------------------------------------------------------- + # ---------------------------------------------------------------- - # ------------------------------------------------------------------------------- + # ---------------------------------------------------------------- # initialization of constants - # ------------------------------------------------------------------------------- + # ---------------------------------------------------------------- + n = self.strands() # ------------------------------------------------------------------------------- @@ -1317,7 +1314,7 @@ def transvec2mat(v, bas=bas, bform=bform, fact=a): dim_sympl_group = n-1 # S(n-1) = Sp(n-1, 3) if n % 2 == 0: dim_sympl_group = n # S(n-1) = subgroup of PSp(n, 3) - create_sympl_realization(self, dim_sympl_group) + create_sympl_realization(self, dim_sympl_group) elif self._cbg_type == CubicBraidGroup.type.AssionU: dim_unitary_group = n-1 # U(n-1) = GU(n-1, 2) if n % 3 == 0: @@ -1425,10 +1422,8 @@ def braid_group(self): Return a :class:`BraidGroup` with identical generators, such that there exists an epimorphism to ``self``. - OUTPUT: - - A :class:`BraidGroup` having conversion maps to and from ``self`` - (which is just a section in the latter case). + OUTPUT: a :class:`BraidGroup` having conversion maps to and from + ``self`` (which is just a section in the latter case) EXAMPLES:: @@ -1467,7 +1462,7 @@ def braid_group(self): @cached_method def as_matrix_group(self, root_bur=None, domain=None, characteristic=None, var='t', reduced=False): r""" - Creates an epimorphic image of ``self`` as a matrix group by use of + Create an epimorphic image of ``self`` as a matrix group by use of the burau representation. INPUT: @@ -1543,8 +1538,9 @@ def as_matrix_group(self, root_bur=None, domain=None, characteristic=None, var=' unitary = True gen_list = [] for braid_gen in self.gens(): - bur_mat = braid_gen.burau_matrix(root_bur=root_bur, domain=domain, characteristic=characteristic, - var=var, reduced=reduced) + bur_mat = braid_gen.burau_matrix(root_bur=root_bur, domain=domain, + characteristic=characteristic, + var=var, reduced=reduced) if unitary: bur_mat, bur_mat_ad, herm_form = bur_mat @@ -1809,7 +1805,7 @@ def as_reflection_group(self): if not is_chevie_available(): raise ImportError("the GAP3 package 'CHEVIE' is needed to obtain the corresponding reflection groups") - if self._cbg_type != CubicBraidGroup.type.Coxeter or self.strands() > 5 or self.strands() < 2: + if self._cbg_type != CubicBraidGroup.type.Coxeter or self.strands() > 5 or self.strands() < 2: raise ValueError("no reflection group defined") # ------------------------------------------------------------------------------- @@ -1974,9 +1970,7 @@ def order(self): To avoid long wait-time on calculations the order will be obtained using the classical realization. - OUTPUT: - - Cardinality of the group as Integer or infinity. + OUTPUT: cardinality of the group as integer or infinity EXAMPLES:: diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index 3388c41d954..a6ef6bb7395 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -269,10 +269,6 @@ def _repr_(self): """ Return a string representation. - OUTPUT: - - String. - EXAMPLES:: sage: G. = FreeGroup() @@ -300,9 +296,7 @@ def Tietze(self): the letter corresponding to the `i`-th generator of the group. Negative integers represent the inverses of generators. - OUTPUT: - - A tuple of integers. + OUTPUT: tuple of integers EXAMPLES:: @@ -326,16 +320,14 @@ def __call__(self, *values, **kwds): INPUT: - ``*values`` -- a list/tuple/iterable of the same length as - the number of generators. - - - ``check=True`` -- boolean keyword (default: - ``True``). Whether to verify that ``values`` satisfy the - relations in the finitely presented group. + the number of generators - OUTPUT: + - ``check=True`` -- boolean keyword (default: ``True``); whether to + verify that ``values`` satisfy the relations in the finitely + presented group - The product of ``values`` in the order and with exponents - specified by ``self``. + OUTPUT: the product of ``values`` in the order and with exponents + specified by ``self`` EXAMPLES:: @@ -386,9 +378,7 @@ def wrap_FpGroup(libgap_fpgroup): - ``libgap_fpgroup`` -- a LibGAP finitely presented group - OUTPUT: - - A Sage :class:`FinitelyPresentedGroup`. + OUTPUT: a Sage :class:`FinitelyPresentedGroup` EXAMPLES: @@ -563,7 +553,7 @@ def finitely_presented_group(self): def reduce(self, element): """ - Applies the rules in the rewriting system to the element, to obtain + Apply the rules in the rewriting system to the element, to obtain a reduced form. If the rewriting system is confluent, this reduced form is unique @@ -693,7 +683,7 @@ def is_confluent(self): def make_confluent(self): """ - Applies Knuth-Bendix algorithm to try to transform the rewriting + Apply the Knuth-Bendix algorithm to try to transform the rewriting system into a confluent one. Note that this method does not return any object, just changes the @@ -813,10 +803,6 @@ def _repr_(self): """ Return a string representation. - OUTPUT: - - String. - TESTS:: sage: G. = FreeGroup() @@ -832,11 +818,9 @@ def _repr_(self): def _latex_(self): """ - Return a LaTeX representation - - OUTPUT: + Return a LaTeX representation. - String. A valid LaTeX math command sequence. + OUTPUT: string; a valid LaTeX math command sequence TESTS:: @@ -864,9 +848,7 @@ def free_group(self): """ Return the free group (without relations). - OUTPUT: - - A :func:`~sage.groups.free_group.FreeGroup`. + OUTPUT: a :func:`~sage.groups.free_group.FreeGroup` EXAMPLES:: @@ -883,9 +865,7 @@ def relations(self): """ Return the relations of the group. - OUTPUT: - - The relations as a tuple of elements of :meth:`free_group`. + OUTPUT: the relations as a tuple of elements of :meth:`free_group` EXAMPLES:: @@ -907,12 +887,10 @@ def cardinality(self, limit=4096000): INPUT: - - ``limit`` -- integer (default: 4096000). The maximal number - of cosets before the computation is aborted. - - OUTPUT: + - ``limit`` -- integer (default: 4096000); the maximal number + of cosets before the computation is aborted - Integer or ``Infinity``. The number of elements in the group. + OUTPUT: integer or ``Infinity``; the number of elements in the group EXAMPLES:: @@ -959,8 +937,8 @@ def as_permutation_group(self, limit=4096000): INPUT: - - ``limit`` -- integer (default: 4096000). The maximal number - of cosets before the computation is aborted. + - ``limit`` -- integer (default: 4096000); the maximal number + of cosets before the computation is aborted OUTPUT: @@ -1005,7 +983,7 @@ def as_permutation_group(self, limit=4096000): from sage.combinat.permutation import Permutation from sage.groups.perm_gps.permgroup import PermutationGroup return PermutationGroup([ - Permutation(coset_table[2*i]) for i in range(len(coset_table)//2)]) + Permutation(coset_table[2*i]) for i in range(len(coset_table)//2)]) def direct_product(self, H, reduced=False, new_names=True): r""" @@ -1036,10 +1014,8 @@ def direct_product(self, H, reduced=False, new_names=True): to keep the old variable names, as they may change meaning in the output group if its presentation is reduced. - OUTPUT: - - The direct product of ``self`` with ``H`` as a finitely - presented group. + OUTPUT: the direct product of ``self`` with ``H`` as a finitely + presented group EXAMPLES:: @@ -1358,9 +1334,8 @@ def abelianization_map(self): r""" Return the abelianization map of ``self``. - OUTPUT: - - The abelianization map of ``self`` as a homomorphism of finitely presented groups. + OUTPUT: the abelianization map of ``self`` as a homomorphism of + finitely presented groups EXAMPLES:: @@ -1530,11 +1505,10 @@ def simplified(self): def sorted_presentation(self): """ - Return the same presentation with the relations sorted to ensure equality. - - OUTPUT: + Return the same presentation with the relations sorted to ensure + equality. - A new finitely presented group with the relations sorted. + OUTPUT: a new finitely presented group with the relations sorted EXAMPLES:: @@ -1548,14 +1522,13 @@ def sorted_presentation(self): L1 = [] for rel in L0: C = [rel] - for j in range(len(rel) - 1): - C.append(rel[j + 1:] + rel[:j + 1]) + C.extend(rel[j + 1:] + rel[:j + 1] for j in range(len(rel) - 1)) C1 = [tuple(-j for j in reversed(l)) for l in C] C += C1 C.sort() L1.append(C[0]) L1.sort() - return F/L1 + return F / L1 def epimorphisms(self, H): r""" @@ -1563,7 +1536,7 @@ def epimorphisms(self, H): INPUT: - - `H` -- Another group + - ``H`` -- another group EXAMPLES:: @@ -1681,8 +1654,9 @@ def abelian_alexander_matrix(self, ring=QQ, simplified=True): OUTPUT: - ``A`` -- a matrix with coefficients in ``R`` - - ``ideal`` -- an list of generators of an ideal ``I`` of ``R = A.base_ring()`` such that ``R/I`` is - the group algebra of the abelianization of ``self`` + - ``ideal`` -- an list of generators of an ideal ``I`` of + ``R = A.base_ring()`` such that ``R/I`` is the group algebra of the + abelianization of ``self`` EXAMPLES:: @@ -1747,10 +1721,11 @@ def characteristic_varieties(self, ring=QQ, matrix_ideal=None, groebner=False): r""" Return the characteristic varieties of the group ``self``. - There are several definitions of the characteristic varieties of a group `G`, see e.g. [CS1999a]_. Let `\Lambda` be the - group algebra of `G/G'` and `\mathbb{T}` its associated algebraic variety (a torus). Each - element `\xi\in\mathbb{T}` defines a local system of coefficients and the `k` th-characteristic - variety is + There are several definitions of the characteristic varieties of a + group `G`, see e.g. [CS1999a]_. Let `\Lambda` be the group algebra of + `G/G'` and `\mathbb{T}` its associated algebraic variety (a torus). + Each element `\xi\in\mathbb{T}` defines a local system of coefficients + and the `k`-th characteristic variety is .. MATH:: diff --git a/src/sage/groups/finitely_presented_named.py b/src/sage/groups/finitely_presented_named.py index d800bf014d0..8b07c5af7df 100644 --- a/src/sage/groups/finitely_presented_named.py +++ b/src/sage/groups/finitely_presented_named.py @@ -77,11 +77,9 @@ def CyclicPresentation(n): INPUT: - - ``n`` -- The order of the cyclic presentation to be returned. + - ``n`` -- the order of the cyclic presentation to be returned - OUTPUT: - - The cyclic group of order `n` as finite presentation. + OUTPUT: the cyclic group of order `n` as finite presentation EXAMPLES:: @@ -112,9 +110,9 @@ def FinitelyGeneratedAbelianPresentation(int_list): INPUT: - - ``int_list`` -- List of integers defining the group to be returned, the defining list + - ``int_list`` -- list of integers defining the group to be returned, the defining list is reduced to the invariants of the input list before generating the corresponding - group. + group OUTPUT: @@ -219,10 +217,8 @@ def FinitelyGeneratedHeisenbergPresentation(n=1, p=0): - ``p`` -- (optional) a prime number, where we construct the Heisenberg group over the finite field `\ZZ/p\ZZ` - OUTPUT: - - Finitely generated Heisenberg group over the finite field - of order ``p`` or over the integers. + OUTPUT: finitely generated Heisenberg group over the finite field + of order ``p`` or over the integers .. SEEALSO:: @@ -302,11 +298,9 @@ def DihedralPresentation(n): INPUT: - - ``n`` -- The size of the set that `D_n` is acting on. + - ``n`` -- the size of the set that `D_n` is acting on - OUTPUT: - - Dihedral group of order `2n`. + OUTPUT: Dihedral group of order `2n` EXAMPLES:: @@ -341,11 +335,9 @@ def DiCyclicPresentation(n): INPUT: - ``n`` -- positive integer, 2 or greater, determining the order of - the group (`4n`). - - OUTPUT: + the group (`4n`) - The dicyclic group of order `4n` is defined by the presentation + OUTPUT: the dicyclic group of order `4n` is defined by the presentation .. MATH:: @@ -392,8 +384,8 @@ def SymmetricPresentation(n): INPUT: - - ``n`` -- The size of the underlying set of arbitrary symbols being acted - on by the Symmetric group of order `n!`. + - ``n`` -- the size of the underlying set of arbitrary symbols being acted + on by the Symmetric group of order `n!` OUTPUT: @@ -441,9 +433,7 @@ def QuaternionPresentation(): r""" Build the Quaternion group of order 8 as a finitely presented group. - OUTPUT: - - Quaternion group as a finite presentation. + OUTPUT: Quaternion group as a finite presentation EXAMPLES:: @@ -471,8 +461,8 @@ def AlternatingPresentation(n): INPUT: - - ``n`` -- The size of the underlying set of arbitrary symbols being acted - on by the Alternating group of order `n!/2`. + - ``n`` -- the size of the underlying set of arbitrary symbols being acted + on by the Alternating group of order `n!/2` OUTPUT: @@ -520,9 +510,7 @@ def KleinFourPresentation(): r""" Build the Klein group of order `4` as a finitely presented group. - OUTPUT: - - Klein four group (`C_2 \times C_2`) as a finitely presented group. + OUTPUT: Klein four group (`C_2 \times C_2`) as a finitely presented group EXAMPLES:: @@ -548,9 +536,7 @@ def BinaryDihedralPresentation(n): - ``n`` -- the value `n` - OUTPUT: - - The binary dihedral group of order `4n` as finite presentation. + OUTPUT: the binary dihedral group of order `4n` as finite presentation EXAMPLES:: @@ -579,9 +565,7 @@ def CactusPresentation(n): r""" Build the `n`-fruit cactus group as a finitely presented group. - OUTPUT: - - Cactus group `J_n` as a finitely presented group. + OUTPUT: Cactus group `J_n` as a finitely presented group EXAMPLES:: diff --git a/src/sage/groups/fqf_orthogonal.py b/src/sage/groups/fqf_orthogonal.py index 8c996ad3c6a..47dac17281b 100644 --- a/src/sage/groups/fqf_orthogonal.py +++ b/src/sage/groups/fqf_orthogonal.py @@ -5,7 +5,6 @@ consists of all linear self-maps of `T` which preserve the torsion quadratic form. - EXAMPLES:: sage: L = IntegralLattice("A2").twist(2) # needs sage.graphs @@ -125,7 +124,7 @@ class FqfOrthogonalGroup(AbelianGroupAutomorphismGroup_subgroup): INPUT: - - ``T`` -- a non degenerate torsion quadratic module. + - ``T`` -- a non degenerate torsion quadratic module EXAMPLES:: @@ -436,9 +435,7 @@ def _act_(self, g, a): - ``a`` -- an element of the invariant submodule - ``g`` -- an element of the acting group - OUTPUT: - - - an element of the invariant submodule + OUTPUT: an element of the invariant submodule EXAMPLES:: diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index 471940f6b95..09eb12d6a1c 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -80,11 +80,9 @@ def is_FreeGroup(x): INPUT: - - ``x`` -- anything. + - ``x`` -- anything - OUTPUT: - - Boolean. + OUTPUT: boolean EXAMPLES:: @@ -109,9 +107,9 @@ def _lexi_gen(zeroes=False): INPUT: - - ``zeroes`` -- Boolean defaulting as ``False``. If ``True``, the + - ``zeroes`` -- boolean (default: ``False``); if ``True``, the integers appended to the output string begin at zero at the - first iteration through the alphabet. + first iteration through the alphabet OUTPUT: @@ -164,11 +162,11 @@ class FreeGroupElement(ElementLibGAP): INPUT: - - ``x`` -- something that determines the group element. Either a + - ``x`` -- something that determines the group element; either a :class:`~sage.libs.gap.element.GapElement` or the Tietze list - (see :meth:`Tietze`) of the group element. + (see :meth:`Tietze`) of the group element - - ``parent`` -- the parent :class:`FreeGroup`. + - ``parent`` -- the parent :class:`FreeGroup` EXAMPLES:: @@ -243,11 +241,9 @@ def __hash__(self): def _latex_(self): r""" - Return a LaTeX representation - - OUTPUT: + Return a LaTeX representation. - String. A valid LaTeX math command sequence. + OUTPUT: a string; a valid LaTeX math command sequence EXAMPLES:: @@ -305,9 +301,7 @@ def Tietze(self): the letter corresponding to the `i`-th generator of the group. Negative integers represent the inverses of generators. - OUTPUT: - - A tuple of integers. + OUTPUT: tuple of integers EXAMPLES:: @@ -525,12 +519,10 @@ def __call__(self, *values): - ``*values`` -- a sequence of values, or a list/tuple/iterable of the same length as the number of - generators of the free group. + generators of the free group - OUTPUT: - - The product of ``values`` in the order and with exponents - specified by ``self``. + OUTPUT: the product of ``values`` in the order and with exponents + specified by ``self`` EXAMPLES:: @@ -605,11 +597,11 @@ def FreeGroup(n=None, names='x', index_set=None, abelian=False, **kwds): INPUT: - - ``n`` -- integer or ``None`` (default). The number of - generators. If not specified the ``names`` are counted. + - ``n`` -- integer (default: ``None``); the number of + generators (if not specified the ``names`` are counted) - ``names`` -- string or list/tuple/iterable of strings (default: - ``'x'``). The generator names or name prefix. + ``'x'``); the generator names or name prefix - ``index_set`` -- (optional) an index set for the generators; if specified then the optional keyword ``abelian`` can be used @@ -698,11 +690,9 @@ def wrap_FreeGroup(libgap_free_group): INPUT: - - ``libgap_free_group`` -- a LibGAP free group. - - OUTPUT: + - ``libgap_free_group`` -- a LibGAP free group - A Sage :class:`FreeGroup_class`. + OUTPUT: a Sage :class:`FreeGroup_class` EXAMPLES: @@ -753,12 +743,12 @@ def __init__(self, generator_names, libgap_free_group=None): INPUT: - - ``generator_names`` -- a tuple of strings. The names of the - generators. + - ``generator_names`` -- a tuple of strings; the names of the + generators - - ``libgap_free_group`` -- a LibGAP free group or ``None`` - (default). The LibGAP free group to wrap. If ``None``, a - suitable group will be constructed. + - ``libgap_free_group`` -- a LibGAP free group (default: ``None``); + the LibGAP free group to wrap (if ``None``, a suitable group will be + constructed) TESTS:: @@ -792,13 +782,11 @@ def _repr_(self): def rank(self): """ - Return the number of generators of self. + Return the number of generators of ``self``. Alias for :meth:`ngens`. - OUTPUT: - - Integer. + OUTPUT: integer EXAMPLES:: @@ -919,10 +907,10 @@ def quotient(self, relations, **kwds): INPUT: - - ``relations`` -- A list/tuple/iterable with the elements of - the free group. + - ``relations`` -- a list/tuple/iterable with the elements of + the free group - further named arguments, that are passed to the constructor - of a finitely presented group. + of a finitely presented group OUTPUT: @@ -951,7 +939,6 @@ def quotient(self, relations, **kwds): Free Group on generators {a, b} sage: F1/[r] Finitely presented group < a, b, c, d | a*b*a^-1 > - """ from sage.groups.finitely_presented import FinitelyPresentedGroup return FinitelyPresentedGroup(self, tuple(map(self, relations) ), **kwds) diff --git a/src/sage/groups/galois_group.py b/src/sage/groups/galois_group.py index a7439d18ab3..36495c846ad 100644 --- a/src/sage/groups/galois_group.py +++ b/src/sage/groups/galois_group.py @@ -25,9 +25,11 @@ def _alg_key(self, algorithm=None, recompute=False): r""" Return a key for use in cached_method calls. - If recompute is false, will cache using ``None`` as the key, so no recomputation will be done. + If recompute is false, will cache using ``None`` as the key, so no + recomputation will be done. - If recompute is true, will cache by algorithm, yielding a recomputation for each different algorithm. + If recompute is true, will cache by algorithm, yielding a recomputation + for each different algorithm. EXAMPLES:: @@ -45,10 +47,11 @@ def _alg_key(self, algorithm=None, recompute=False): class _GMixin: r""" - This class provides some methods for Galois groups to be used for both permutation groups - and abelian groups, subgroups and full Galois groups. + This class provides some methods for Galois groups to be used for both + permutation groups and abelian groups, subgroups and full Galois groups. - It is just intended to provide common functionality between various different Galois group classes. + It is just intended to provide common functionality between various + different Galois group classes. """ @lazy_attribute def _default_algorithm(self): @@ -90,7 +93,7 @@ def _gcdata(self): def _get_algorithm(self, algorithm): r""" - Allows overriding the default algorithm specified at object creation. + Allow overriding the default algorithm specified at object creation. EXAMPLES:: @@ -177,7 +180,7 @@ def _field(self): def _repr_(self): """ - String representation of this Galois group + String representation of this Galois group. EXAMPLES:: @@ -309,13 +312,14 @@ def fixed_field(self, name=None, polred=None, threshold=None): INPUT: - - ``name`` -- a variable name for the new field. + - ``name`` -- a variable name for the new field - ``polred`` -- whether to optimize the generator of the newly created field - for a simpler polynomial, using pari's polredbest. - Defaults to ``True`` when the degree of the fixed field is at most 8. + for a simpler polynomial, using Pari's :pari:`polredbest`; + defaults to ``True`` when the degree of the fixed field is at most 8 - - ``threshold`` -- positive number; polred only performed if the cost is at most this threshold + - ``threshold`` -- positive number; polred only performed if the cost + is at most this threshold EXAMPLES:: @@ -377,7 +381,7 @@ def is_galois(self): @lazy_attribute def _gcdata(self): r""" - Return the Galois closure (ie, the finite field itself) together with the identity + Return the Galois closure (i.e., the finite field itself) together with the identity. EXAMPLES:: diff --git a/src/sage/groups/galois_group_perm.py b/src/sage/groups/galois_group_perm.py index 7a640bcfe29..3b81e0d5908 100644 --- a/src/sage/groups/galois_group_perm.py +++ b/src/sage/groups/galois_group_perm.py @@ -17,11 +17,13 @@ class GaloisGroup_perm(_GaloisMixin, PermutationGroup_generic): - ``field`` -- a field, separable over its base - - ``names`` -- a string or tuple of length 1, giving a variable name for the splitting field + - ``names`` -- a string or tuple of length 1, giving a variable name for + the splitting field - - ``gc_numbering`` -- boolean, whether to express permutations in terms of the - roots of the defining polynomial of the splitting field (versus the defining polynomial - of the original extension). The default value may vary based on the type of field. + - ``gc_numbering`` -- boolean, whether to express permutations in terms of + the roots of the defining polynomial of the splitting field (versus the + defining polynomial of the original extension); the default value may + vary based on the type of field """ @abstract_method def transitive_number(self, algorithm=None, recompute=False): diff --git a/src/sage/groups/generic.py b/src/sage/groups/generic.py index cb406b444af..e69462ecc6b 100644 --- a/src/sage/groups/generic.py +++ b/src/sage/groups/generic.py @@ -15,7 +15,6 @@ multiplication_names = ('multiplication', 'times', 'product', '*') addition_names = ('addition', 'plus', 'sum', '+') - Also included are a generic function for computing multiples (or powers), and an iterator for general multiples and powers. @@ -401,10 +400,12 @@ def bsgs(a, b, bounds, operation='*', identity=None, inverse=None, op=None): An integer `n` such that `a^n = b` (or `na = b`). If no such `n` exists, this function raises a :class:`ValueError` exception. - NOTE: This is a generalization of discrete logarithm. One - situation where this version is useful is to find the order of - an element in a group where we only have bounds on the group - order (see the elliptic curve example below). + .. NOTE:: + + This is a generalization of discrete logarithm. One + situation where this version is useful is to find the order of + an element in a group where we only have bounds on the group + order (see the elliptic curve example below). ALGORITHM: Baby step giant step. Time and space are soft `O(\sqrt{n})` where `n` is the difference between upper and lower @@ -533,7 +534,7 @@ def discrete_log_rho(a, base, ord=None, operation='*', identity=None, inverse=No - ``hash_function`` -- having an efficient hash function is critical for this algorithm (see examples) - OUTPUT: an integer `n` such that `a = base^n` (or `a = n*base`) + OUTPUT: integer `n` such that `a = base^n` (or `a = n*base`) ALGORITHM: Pollard rho for discrete logarithm, adapted from the article of Edlyn Teske, 'A space efficient algorithm for group @@ -599,7 +600,6 @@ def discrete_log_rho(a, base, ord=None, operation='*', identity=None, inverse=No AUTHOR: - Yann Laigle-Chapuy (2009-09-05) - """ from sage.rings.integer import Integer from sage.rings.finite_rings.integer_mod_ring import IntegerModRing @@ -698,9 +698,10 @@ def discrete_log(a, base, ord=None, bounds=None, operation='*', identity=None, i - ``bounds`` -- a priori bounds on the log - ``operation`` -- string: ``'*'``, ``'+'``, other - ``identity`` -- the group's identity - - ``inverse()`` - function of 1 argument ``x``, returning inverse of ``x`` - - ``op()`` - function of 2 arguments ``x``, ``y``, returning ``x*y`` in the group - - ``algorithm`` -- string denoting what algorithm to use for prime-order logarithms: ``'bsgs'``, ``'rho'``, ``'lambda'`` + - ``inverse`` -- function of 1 argument ``x``, returning inverse of ``x`` + - ``op`` -- function of 2 arguments ``x``, ``y``, returning ``x*y`` in the group + - ``algorithm`` -- string denoting what algorithm to use for prime-order + logarithms: ``'bsgs'``, ``'rho'``, ``'lambda'`` ``a`` and ``base`` must be elements of some group with identity given by ``identity``, inverse of ``x`` by ``inverse(x)``, and group @@ -718,7 +719,7 @@ def discrete_log(a, base, ord=None, bounds=None, operation='*', identity=None, i If no such `n` exists, this function raises a :class:`ValueError` exception. - .. warning:: + .. WARNING:: If ``x`` has a ``log`` method, it is likely to be vastly faster than using this function. E.g., if ``x`` is an integer modulo @@ -901,7 +902,7 @@ def discrete_log(a, base, ord=None, bounds=None, operation='*', identity=None, i power = lambda x, y: multiple(x, y, operation=operation, identity=identity, inverse=inverse, op=op) if bounds: lb, ub = map(integer_ring.ZZ, bounds) - if (op is None or identity is None or inverse is None or ord is None) and operation not in addition_names+multiplication_names: + if (op is None or identity is None or inverse is None or ord is None) and operation not in addition_names + multiplication_names: raise ValueError("ord, op, identity, and inverse must all be specified for this operation") if ord is None: if operation in multiplication_names: @@ -992,7 +993,7 @@ def discrete_log_lambda(a, base, bounds, operation='*', identity=None, inverse=N - op() -- function of 2 arguments ``x``, ``y`` returning ``x*y`` in the group - hash_function -- having an efficient hash function is critical for this algorithm - OUTPUT: Returns an integer `n` such that `a=base^n` (or `a=n*base`) + OUTPUT: integer `n` such that `a=base^n` (or `a=n*base`) ALGORITHM: Pollard Lambda, if bounds are (lb,ub) it has time complexity O(sqrt(ub-lb)) and space complexity O(log(ub-lb)) @@ -1199,24 +1200,24 @@ def order_from_multiple(P, m, plist=None, factorization=None, check=True, INPUT: - - ``P`` -- a Sage object which is a group element; + - ``P`` -- a Sage object which is a group element - ``m`` -- a Sage integer which is a multiple of the order of ``P``, - i.e. we require that ``m*P=0`` (or ``P**m=1``); + i.e. we require that ``m*P=0`` (or ``P**m=1``) - ``check`` -- a Boolean (default: ``True``), indicating whether we check if ``m`` - really is a multiple of the order; + really is a multiple of the order - ``factorization`` -- the factorization of ``m``, or ``None`` in which - case this function will need to factor ``m``; + case this function will need to factor ``m`` - ``plist`` -- a list of the prime factors of ``m``, or ``None``. Kept for compatibility only, - prefer the use of ``factorization``; - - ``operation`` -- string: ``'+'`` (default), ``'*'`` or ``None``; - - ``identity`` -- the identity element of the group; - - ``inverse()`` -- function of 1 argument ``x``, returning inverse of ``x``; - - ``op()`` - function of 2 arguments ``x``, ``y`` returning ``x*y`` in the group. + prefer the use of ``factorization`` + - ``operation`` -- string: ``'+'`` (default), ``'*'`` or ``None`` + - ``identity`` -- the identity element of the group + - ``inverse`` -- function of 1 argument ``x``, returning inverse of ``x`` + - ``op`` -- function of 2 arguments ``x``, ``y`` returning ``x*y`` in the group - .. note:: + .. NOTE:: - It is more efficient for the caller to factor ``m`` and cache - the factors for subsequent calls. + It is more efficient for the caller to factor ``m`` and cache + the factors for subsequent calls. EXAMPLES:: @@ -1297,7 +1298,7 @@ def _multiple(A, B): # we use an internal recursive function to avoid unnecessary computations. def _order_from_multiple_helper(Q, L, S): """ - internal use, to minimize the number of group operations. + For internal use, to minimize the number of group operations. """ l = len(L) if l == 1: @@ -1348,10 +1349,10 @@ def order_from_bounds(P, bounds, d=None, operation='+', - ``P`` -- a Sage object which is a group element - ``bounds`` -- a 2-tuple ``(lb,ub)`` such that ``m*P=0`` (or - ``P**m=1``) for some ``m`` with ``lb<=m<=ub``. + ``P**m=1``) for some ``m`` with ``lb<=m<=ub`` - ``d`` -- (optional) a positive integer; only ``m`` which are - multiples of this will be considered. + multiples of this will be considered - ``operation`` -- string: ``'+'`` (default ) or ``'*'`` or other. If other, the following must be supplied: @@ -1362,8 +1363,7 @@ def order_from_bounds(P, bounds, d=None, operation='+', - ``op()`` -- a function of 2 arguments defining the group binary operation. - - .. note:: + .. NOTE:: Typically ``lb`` and ``ub`` will be bounds on the group order, and from previous calculation we know that the group order is @@ -1500,7 +1500,6 @@ def has_order(P, n, operation='+'): return False n = n.factor() - G = P.parent() if operation in addition_names: isid = lambda el: not el mult = lambda el, n: multiple(el, n, operation='+') @@ -1550,9 +1549,7 @@ def merge_points(P1, P2, operation='+', - ``op()`` -- a function of 2 arguments defining the group binary operation. - OUTPUT: - - A pair `(g_3,n_3)` where `g_3` has order `n_3=\hbox{lcm}(n_1,n_2)`. + OUTPUT: a pair `(g_3,n_3)` where `g_3` has order `n_3=\hbox{lcm}(n_1,n_2)` EXAMPLES:: @@ -1629,12 +1626,10 @@ def structure_description(G, latex=False): INPUT: - - ``latex`` -- a boolean (default: ``False``). If ``True``, return a - LaTeX formatted string. - - OUTPUT: + - ``latex`` -- a boolean (default: ``False``); if ``True``, return a + LaTeX formatted string - string + OUTPUT: string .. WARNING:: diff --git a/src/sage/groups/group.pyx b/src/sage/groups/group.pyx index 9ecbfde9d6f..3f1fb92e756 100644 --- a/src/sage/groups/group.pyx +++ b/src/sage/groups/group.pyx @@ -27,11 +27,9 @@ def is_Group(x): INPUT: - - ``x`` -- anything. + - ``x`` -- anything - OUTPUT: - - Boolean. + OUTPUT: boolean EXAMPLES:: @@ -50,7 +48,7 @@ def is_Group(x): cdef class Group(Parent): """ - Base class for all groups + Base class for all groups. TESTS:: @@ -78,7 +76,7 @@ cdef class Group(Parent): """ def __init__(self, base=None, category=None): """ - The Python constructor + The Python constructor. TESTS:: @@ -175,7 +173,7 @@ cdef class Group(Parent): def is_finite(self): """ - Returns True if this group is finite. + Return ``True`` if this group is finite. EXAMPLES:: @@ -223,8 +221,7 @@ cdef class Group(Parent): def is_multiplicative(self): r""" - Returns True if the group operation is given by \* (rather than - +). + Return ``True`` if the group operation is given by ``*`` (rather than ``+``). Override for additive groups. @@ -239,11 +236,9 @@ cdef class Group(Parent): def _an_element_(self): """ - Return an element - - OUTPUT: + Return an element. - An element of the group. + OUTPUT: an element of the group EXAMPLES:: diff --git a/src/sage/groups/group_exp.py b/src/sage/groups/group_exp.py index ffae5e73cb2..49f2315eae3 100644 --- a/src/sage/groups/group_exp.py +++ b/src/sage/groups/group_exp.py @@ -111,9 +111,8 @@ def _apply_functor(self, x): - A commutative additive group `x` - OUTPUT: - - - An isomorphic group whose operation is multiplication rather than addition. + OUTPUT: an isomorphic group whose operation is multiplication rather + than addition In the following example, ``self`` is the functor `GroupExp()`, `x` is the additive group `QQ^2`, and the output group is stored as `EQ2`. @@ -141,14 +140,15 @@ def _apply_functor_to_morphism(self, f): INPUT: - - A homomorphism `f` of commutative additive groups. + - A homomorphism `f` of commutative additive groups - OUTPUT: + OUTPUT: the above homomorphism, but between the corresponding + multiplicative groups - The above homomorphism, but between the corresponding multiplicative groups. - In the following example, ``self`` is the functor `GroupExp()` and `f` is an endomorphism of the - additive group of integers. + In the following example, ``self`` is the functor :class:`GroupExp` and `f` + is an endomorphism of the additive group of integers. EXAMPLES:: @@ -180,7 +180,7 @@ class GroupExpElement(ElementWrapper, MultiplicativeGroupElement): - ``self`` -- the exponentiated group element being created - ``parent`` -- the exponential group (parent of ``self``) - - ``x`` -- the commutative additive group element being wrapped to form ``self``. + - ``x`` -- the commutative additive group element being wrapped to form ``self`` EXAMPLES:: @@ -245,11 +245,9 @@ class GroupExp_Class(UniqueRepresentation, Parent): INPUT: - - `G`: a commutative additive group - - OUTPUT: + - `G` -- a commutative additive group - - The multiplicative form of `G`. + OUTPUT: the multiplicative form of `G` EXAMPLES:: @@ -263,7 +261,6 @@ def __init__(self, G): sage: EG = GroupExp()(QQ^2) sage: TestSuite(EG).run(skip = "_test_elements") - """ if G not in CommutativeAdditiveGroups(): raise TypeError("%s must be a commutative additive group" % G) @@ -307,7 +304,6 @@ def one(self): (1, 0) sage: x == x * G.one() True - """ return GroupExpElement(self, self._G.zero()) @@ -349,7 +345,6 @@ def group_generators(self): sage: GroupExp()(ZZ).group_generators() (1,) - """ if hasattr(self._G, 'gens'): additive_generators = self._G.gens() diff --git a/src/sage/groups/group_semidirect_product.py b/src/sage/groups/group_semidirect_product.py index 1805dbbc53a..7adc1868f8d 100644 --- a/src/sage/groups/group_semidirect_product.py +++ b/src/sage/groups/group_semidirect_product.py @@ -136,7 +136,8 @@ def to_opposite(self): class GroupSemidirectProduct(CartesianProduct): r""" - Return the semidirect product of the groups ``G`` and ``H`` using the homomorphism ``twist``. + Return the semidirect product of the groups ``G`` and ``H`` using the + homomorphism ``twist``. INPUT: @@ -291,8 +292,8 @@ def check_implemented_group(x): def act_to_right(self): r""" - True if the left factor acts on the right factor and - False if the right factor acts on the left factor. + Return ``True`` if the left factor acts on the right factor and + ``False`` if the right factor acts on the left factor. EXAMPLES:: diff --git a/src/sage/groups/indexed_free_group.py b/src/sage/groups/indexed_free_group.py index 79be1df663d..02a4838d77c 100644 --- a/src/sage/groups/indexed_free_group.py +++ b/src/sage/groups/indexed_free_group.py @@ -24,7 +24,8 @@ from sage.categories.poor_man_map import PoorManMap from sage.groups.group import Group, AbelianGroup from sage.monoids.indexed_free_monoid import (IndexedMonoid, - IndexedFreeMonoidElement, IndexedFreeAbelianMonoidElement) + IndexedFreeMonoidElement, + IndexedFreeAbelianMonoidElement) from sage.misc.cachefunc import cached_method import sage.data_structures.blas_dict as blas from sage.rings.integer import Integer @@ -165,7 +166,7 @@ def __init__(self, indices, prefix, category=None, **kwds): def _repr_(self): """ - Return a string representation of ``self`` + Return a string representation of ``self``. TESTS:: diff --git a/src/sage/groups/kernel_subgroup.py b/src/sage/groups/kernel_subgroup.py index dec7a0be573..6a20b673c57 100644 --- a/src/sage/groups/kernel_subgroup.py +++ b/src/sage/groups/kernel_subgroup.py @@ -21,6 +21,7 @@ from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation + class KernelSubgroup(UniqueRepresentation, Parent): r""" The kernel (normal) subgroup. diff --git a/src/sage/groups/libgap_mixin.py b/src/sage/groups/libgap_mixin.py index 50f8fdf2678..931d22ccdc5 100644 --- a/src/sage/groups/libgap_mixin.py +++ b/src/sage/groups/libgap_mixin.py @@ -56,10 +56,8 @@ def is_abelian(self): r""" Return whether the group is Abelian. - OUTPUT: - - Boolean. ``True`` if this group is an Abelian group and ``False`` - otherwise. + OUTPUT: boolean; ``True`` if this group is an Abelian group and + ``False`` otherwise EXAMPLES:: @@ -184,9 +182,7 @@ def is_finite(self): """ Test whether the matrix group is finite. - OUTPUT: - - Boolean. + OUTPUT: boolean EXAMPLES:: @@ -200,7 +196,7 @@ def is_finite(self): def cardinality(self): """ - Implements :meth:`EnumeratedSets.ParentMethods.cardinality`. + Implement :meth:`EnumeratedSets.ParentMethods.cardinality`. EXAMPLES:: @@ -272,12 +268,11 @@ def conjugacy_classes_representatives(self): Traceback (most recent call last): ... NotImplementedError: only implemented for finite groups - """ if not self.is_finite(): raise NotImplementedError("only implemented for finite groups") G = self.gap() - reps = [ cc.Representative() for cc in G.ConjugacyClasses() ] + reps = [cc.Representative() for cc in G.ConjugacyClasses()] return tuple(self(g) for g in reps) def conjugacy_classes(self): @@ -334,8 +329,8 @@ def class_function(self, values): INPUT: - - ``values`` -- list/tuple/iterable of numbers. The values of the - class function on the conjugacy classes, in that order. + - ``values`` -- list/tuple/iterable of numbers; the values of the + class function on the conjugacy classes, in that order EXAMPLES:: @@ -352,9 +347,7 @@ def center(self): """ Return the center of this group as a subgroup. - OUTPUT: - - The center as a subgroup. + OUTPUT: the center as a subgroup EXAMPLES:: @@ -654,9 +647,7 @@ def irreducible_characters(self): """ Return the irreducible characters of the group. - OUTPUT: - - A tuple containing all irreducible characters. + OUTPUT: tuple containing all irreducible characters EXAMPLES:: @@ -676,9 +667,7 @@ def irreducible_characters(self): if not self.is_finite(): raise NotImplementedError("only implemented for finite groups") Irr = self.gap().Irr() - L = [] - for irr in Irr: - L.append(ClassFunction_libgap(self, irr)) + L = [ClassFunction_libgap(self, irr) for irr in Irr] return tuple(L) def character(self, values): @@ -731,7 +720,7 @@ def trivial_character(self): """ if not self.is_finite(): raise NotImplementedError("only implemented for finite groups") - values = [1]*self._gap_().NrConjugacyClasses().sage() + values = [1] * self._gap_().NrConjugacyClasses().sage() return self.character(values) def character_table(self): @@ -763,8 +752,8 @@ def character_table(self): [ 4 0 -1 -1 2 1 0] [ 1 1 1 1 1 1 1] """ - #code from function in permgroup.py, but modified for - #how gap handles these groups. + # code from function in permgroup.py, but modified for + # how gap handles these groups. G = self._gap_() cl = self.conjugacy_classes() from sage.rings.integer import Integer @@ -786,9 +775,7 @@ def random_element(self): """ Return a random element of this group. - OUTPUT: - - A group element. + OUTPUT: a group element EXAMPLES:: @@ -866,10 +853,8 @@ def list(self): """ List all elements of this group. - OUTPUT: - - A tuple containing all group elements in a random but fixed - order. + OUTPUT: tuple containing all group elements in a random but fixed + order EXAMPLES:: @@ -941,11 +926,9 @@ def is_isomorphic(self, H): INPUT: - - ``H`` -- a group. - - OUTPUT: + - ``H`` -- a group - Boolean. + OUTPUT: boolean EXAMPLES:: diff --git a/src/sage/groups/libgap_morphism.py b/src/sage/groups/libgap_morphism.py index daac871629b..e0f75e7bd4f 100644 --- a/src/sage/groups/libgap_morphism.py +++ b/src/sage/groups/libgap_morphism.py @@ -46,7 +46,8 @@ class GroupMorphism_libgap(Morphism): INPUT: - ``homset`` -- the parent - - ``gap_hom`` -- a :class:`sage.libs.gap.element.GapElement` consisting of a group homomorphism + - ``gap_hom`` -- a :class:`sage.libs.gap.element.GapElement` consisting of + a group homomorphism - ``check`` -- (default: ``True``) check if the ``gap_hom`` is a group homomorphism; this can be expensive @@ -283,7 +284,7 @@ def __init__(self, homset, gap_hom, check=True): def __reduce__(self): r""" - Implements pickling. + Implement pickling. We have to work around the fact that GAP does not provide pickling. @@ -378,9 +379,7 @@ def pushforward(self, J, *args, **kwds): - ``J`` -- a subgroup or an element of the domain of ``self`` - OUTPUT: - - The image of ``J`` under ``self``. + OUTPUT: the image of ``J`` under ``self`` .. NOTE:: @@ -558,13 +557,12 @@ def preimage(self, S): def section(self): r""" - This method returns a section map of self by use of :meth:`lift`. - See :meth:`section` of :class:`sage.categories.map.Map`, as well. + Return a section map of ``self`` by use of :meth:`lift`. - OUTPUT: + See :meth:`section` of :class:`sage.categories.map.Map`, as well. - an instance of :class:`sage.categories.morphism.SetMorphism` - mapping an element of the codomain of self to one of its preimages + OUTPUT: an instance of :class:`sage.categories.morphism.SetMorphism` + mapping an element of the codomain of ``self`` to one of its preimages EXAMPLES:: @@ -596,9 +594,7 @@ class GroupHomset_libgap(HomsetWithBase): - ``H`` -- a libgap group - ``category`` -- a category - OUTPUT: - - The homset of two libgap groups. + OUTPUT: the homset of two libgap groups EXAMPLES:: @@ -730,9 +726,10 @@ def natural_map(self): OUTPUT: - an instance of the element class of self if there exists a group homomorphism - mapping the generators of the domain of self to the according generators of - the codomain. Else the method falls back to the default. + An instance of the element class of ``self`` if there exists a group + homomorphism mapping the generators of the domain of ``self`` to the + according generators of the codomain. Otherwise, the method falls back + to the default. EXAMPLES:: diff --git a/src/sage/groups/libgap_wrapper.pxd b/src/sage/groups/libgap_wrapper.pxd index 0c43b098140..2f76d978afd 100644 --- a/src/sage/groups/libgap_wrapper.pxd +++ b/src/sage/groups/libgap_wrapper.pxd @@ -1,7 +1,6 @@ from sage.structure.element cimport MultiplicativeGroupElement from sage.libs.gap.element cimport GapElement - cdef class ElementLibGAP(MultiplicativeGroupElement): cdef GapElement _libgap cpdef GapElement gap(self) diff --git a/src/sage/groups/libgap_wrapper.pyx b/src/sage/groups/libgap_wrapper.pyx index 3d27ec44ea0..ddba766e94d 100644 --- a/src/sage/groups/libgap_wrapper.pyx +++ b/src/sage/groups/libgap_wrapper.pyx @@ -86,11 +86,11 @@ class ParentLibGAP(SageObject): INPUT: - ``libgap_parent`` -- the libgap element that is the parent in - GAP. + GAP - ``ambient`` -- A derived class of :class:`ParentLibGAP` or - ``None`` (default). The ambient class if ``libgap_parent`` has - been defined as a subgroup. + ``None`` (default); the ambient class if ``libgap_parent`` has + been defined as a subgroup EXAMPLES:: @@ -159,9 +159,7 @@ class ParentLibGAP(SageObject): You can access the containing group with :meth:`ambient`. - OUTPUT: - - Boolean. + OUTPUT: boolean EXAMPLES:: @@ -180,9 +178,7 @@ class ParentLibGAP(SageObject): - ``G`` -- group; the codomain - ``cat`` -- category - OUTPUT: - - The set of homomorphisms from ``self`` to ``G``. + OUTPUT: the set of homomorphisms from ``self`` to ``G`` EXAMPLES:: @@ -201,10 +197,7 @@ class ParentLibGAP(SageObject): You should override this with a derived class. Its constructor must accept the same arguments as :meth:`__init__`. - OUTPUT: - - A new instance of a group (derived class of - :class:`ParentLibGAP`). + OUTPUT: a new instance of a group (derived class of :class:`ParentLibGAP`) TESTS:: @@ -225,9 +218,7 @@ class ParentLibGAP(SageObject): - ``generators`` -- a list/tuple/iterable of group elements. - OUTPUT: - - The subgroup generated by ``generators``. + OUTPUT: the subgroup generated by ``generators`` EXAMPLES:: @@ -269,11 +260,9 @@ class ParentLibGAP(SageObject): def gap(self): """ - Return the gap representation of self. - - OUTPUT: + Return the GAP representation of ``self``. - A :class:`~sage.libs.gap.element.GapElement` + OUTPUT: a :class:`~sage.libs.gap.element.GapElement` EXAMPLES:: @@ -309,11 +298,9 @@ class ParentLibGAP(SageObject): def ngens(self): """ - Return the number of generators of self. + Return the number of generators of ``self``. - OUTPUT: - - Integer. + OUTPUT: integer EXAMPLES:: @@ -332,10 +319,6 @@ class ParentLibGAP(SageObject): """ Return a string representation. - OUTPUT: - - String. - TESTS:: sage: from sage.groups.libgap_wrapper import ElementLibGAP, ParentLibGAP @@ -407,9 +390,9 @@ class ParentLibGAP(SageObject): def gen(self, i): """ - Return the `i`-th generator of self. + Return the `i`-th generator of ``self``. - .. warning:: + .. WARNING:: Indexing starts at `0` as usual in Sage/Python. Not as in GAP, where indexing starts at `1`. @@ -417,11 +400,9 @@ class ParentLibGAP(SageObject): INPUT: - ``i`` -- integer between `0` (inclusive) and :meth:`ngens` - (exclusive). The index of the generator. - - OUTPUT: + (exclusive); the index of the generator - The `i`-th generator of the group. + OUTPUT: the `i`-th generator of the group EXAMPLES:: @@ -438,7 +419,7 @@ class ParentLibGAP(SageObject): @cached_method def one(self): """ - Return the identity element of self. + Return the identity element of ``self``. EXAMPLES:: @@ -454,7 +435,7 @@ class ParentLibGAP(SageObject): def _an_element_(self): """ - Return an element of self. + Return an element of ``self``. EXAMPLES:: @@ -472,7 +453,7 @@ class ParentLibGAP(SageObject): cdef class ElementLibGAP(MultiplicativeGroupElement): """ - A class for LibGAP-based Sage group elements + A class for LibGAP-based Sage group elements. INPUT: @@ -522,9 +503,7 @@ cdef class ElementLibGAP(MultiplicativeGroupElement): """ Return a LibGAP representation of the element. - OUTPUT: - - A :class:`~sage.libs.gap.element.GapElement` + OUTPUT: a :class:`~sage.libs.gap.element.GapElement` EXAMPLES:: @@ -572,9 +551,7 @@ cdef class ElementLibGAP(MultiplicativeGroupElement): """ Test whether the group element is the trivial element. - OUTPUT: - - Boolean. + OUTPUT: boolean EXAMPLES:: @@ -591,10 +568,6 @@ cdef class ElementLibGAP(MultiplicativeGroupElement): """ Return a string representation. - OUTPUT: - - String. - EXAMPLES:: sage: G. = FreeGroup() @@ -620,29 +593,24 @@ cdef class ElementLibGAP(MultiplicativeGroupElement): def _latex_(self): r""" - Return a LaTeX representation - - OUTPUT: + Return a LaTeX representation. - String. A valid LaTeX math command sequence. + OUTPUT: a string; a valid LaTeX math command sequence EXAMPLES:: sage: from sage.groups.libgap_group import GroupLibGAP sage: G = GroupLibGAP(libgap.FreeGroup('a', 'b')) sage: g = G.gen(0) * G.gen(1) - sage: g._latex_() - "ab%\n" + sage: latex(g) + \text{\texttt{a*b}} """ - try: - return self.gap().LaTeX() - except ValueError: - from sage.misc.latex import latex - return latex(self._repr_()) + from sage.misc.latex import latex + return latex(self._repr_()) cpdef _mul_(left, right): """ - Multiplication of group elements + Multiplication of group elements. TESTS:: @@ -727,7 +695,7 @@ cdef class ElementLibGAP(MultiplicativeGroupElement): def __invert__(self): """ - Return the inverse of self. + Return the inverse of ``self``. TESTS:: diff --git a/src/sage/groups/matrix_gps/finitely_generated.py b/src/sage/groups/matrix_gps/finitely_generated.py index 6d487e5d123..1df22e6c3a6 100644 --- a/src/sage/groups/matrix_gps/finitely_generated.py +++ b/src/sage/groups/matrix_gps/finitely_generated.py @@ -83,9 +83,7 @@ def normalize_square_matrices(matrices): """ Find a common space for all matrices. - OUTPUT: - - A list of matrices, all elements of the same matrix space. + OUTPUT: a list of matrices, all elements of the same matrix space EXAMPLES:: @@ -151,7 +149,8 @@ def QuaternionMatrixGroupGF3(): is not isomorphic to the group of symmetries of a square (the dihedral group `D_4`). - .. note:: + .. NOTE:: + This group is most easily available via ``groups.matrix.QuaternionGF3()``. EXAMPLES: @@ -212,10 +211,10 @@ def MatrixGroup(*gens, **kwds): INPUT: - ``*gens`` -- matrices, or a single list/tuple/iterable of - matrices, or a matrix group. + matrices, or a matrix group - - ``check`` -- boolean keyword argument (optional, default: - ``True``). Whether to check that each matrix is invertible. + - ``check`` -- boolean keyword argument (default: ``True``); + whether to check that each matrix is invertible EXAMPLES:: @@ -411,11 +410,9 @@ def gens(self) -> tuple: def gen(self, i): """ - Return the `i`-th generator + Return the `i`-th generator. - OUTPUT: - - The `i`-th generator of the group. + OUTPUT: the `i`-th generator of the group EXAMPLES:: @@ -433,11 +430,9 @@ def gen(self, i): def ngens(self): """ - Return the number of generators - - OUTPUT: + Return the number of generators. - An integer. The number of generators. + OUTPUT: integer; the number of generators EXAMPLES:: diff --git a/src/sage/groups/matrix_gps/finitely_generated_gap.py b/src/sage/groups/matrix_gps/finitely_generated_gap.py index 14bf551789f..70c45d59162 100644 --- a/src/sage/groups/matrix_gps/finitely_generated_gap.py +++ b/src/sage/groups/matrix_gps/finitely_generated_gap.py @@ -29,7 +29,6 @@ from sage.groups.matrix_gps.finitely_generated import MatrixGroup from sage.groups.matrix_gps.matrix_group_gap import MatrixGroup_gap from sage.matrix.matrix_space import MatrixSpace -from sage.misc.cachefunc import cached_method from sage.misc.functional import cyclotomic_polynomial from sage.modules.free_module_element import vector from sage.rings.fraction_field import FractionField @@ -72,7 +71,7 @@ def __reduce__(self): ) """ return (MatrixGroup, - tuple(g.matrix() for g in self.gens()) + ({'check':False},)) + tuple(g.matrix() for g in self.gens()) + ({'check': False},)) def as_permutation_group(self, algorithm=None, seed=None): r""" @@ -86,13 +85,13 @@ def as_permutation_group(self, algorithm=None, seed=None): INPUT: - - ``algorithm`` -- ``None`` or ``'smaller'``. In the latter + - ``algorithm`` -- ``None`` or ``'smaller'``; in the latter case, try harder to find a permutation representation of - small degree. + small degree - ``seed`` -- ``None`` or an integer specifying the seed - to fix results depending on pseudo-random-numbers. Here + to fix results depending on pseudo-random-numbers; here it makes sense to be used with respect to the ``'smaller'`` - option, since GAP produces random output in that context. + option, since GAP produces random output in that context OUTPUT: @@ -321,7 +320,7 @@ def invariant_generators(self): # test if the field is admissible if F.gen() == 1: # we got the rationals or GF(prime) FieldStr = str(F.characteristic()) - elif hasattr(F,'polynomial'): # we got an algebraic extension + elif hasattr(F, 'polynomial'): # we got an algebraic extension if len(F.gens()) > 1: raise NotImplementedError("can only deal with finite fields and (simple algebraic extensions of) the rationals") FieldStr = '(%d,%s)' % (F.characteristic(), str(F.gen())) @@ -794,10 +793,7 @@ def reynolds_operator(self, poly, chi=None): elif not C.is_absolute() or not K.is_absolute() or not R.is_absolute(): raise NotImplementedError("only implemented for absolute fields") else: - fields = [] - for M in [R,K,C]: - if M.absolute_degree() != 1: - fields.append(M) + fields = [M for M in [R, K, C] if M.absolute_degree() != 1] l = len(fields) if l == 0: # all are QQ diff --git a/src/sage/groups/matrix_gps/group_element.pyx b/src/sage/groups/matrix_gps/group_element.pyx index 38bf1f03279..99c21b2589a 100644 --- a/src/sage/groups/matrix_gps/group_element.pyx +++ b/src/sage/groups/matrix_gps/group_element.pyx @@ -91,13 +91,13 @@ except ImportError: cpdef is_MatrixGroupElement(x): """ - Test whether ``x`` is a matrix group element + Test whether ``x`` is a matrix group element. INPUT: - - ``x`` -- anything. + - ``x`` -- anything - OUTPUT: Boolean. + OUTPUT: boolean EXAMPLES:: @@ -370,9 +370,9 @@ cdef class MatrixGroupElement_generic(MultiplicativeGroupElement): def __invert__(self): """ - Return the inverse group element + Return the inverse group element. - OUTPUT: A matrix group element. + OUTPUT: a matrix group element EXAMPLES:: diff --git a/src/sage/groups/matrix_gps/group_element_gap.pyx b/src/sage/groups/matrix_gps/group_element_gap.pyx index b07c0f01a44..2b363f7c303 100644 --- a/src/sage/groups/matrix_gps/group_element_gap.pyx +++ b/src/sage/groups/matrix_gps/group_element_gap.pyx @@ -306,14 +306,14 @@ cdef class MatrixGroupElement_gap(ElementLibGAP): This method writes the group element as a product of the elements of the list ``gens``, or the standard generators of - the parent of self if ``gens`` is None. + the parent of ``self`` if ``gens`` is ``None``. INPUT: - ``gens`` -- a list/tuple/iterable of elements (or objects that can be converted to group elements), or ``None`` - (default). By default, the generators of the parent group - are used. + (default); by default, the generators of the parent group + are used OUTPUT: diff --git a/src/sage/groups/matrix_gps/isometries.py b/src/sage/groups/matrix_gps/isometries.py index 424e5a0edf0..4763b13721a 100644 --- a/src/sage/groups/matrix_gps/isometries.py +++ b/src/sage/groups/matrix_gps/isometries.py @@ -46,8 +46,9 @@ class GroupOfIsometries(FinitelyGeneratedMatrixGroup_gap): r""" A base class for Orthogonal matrix groups with a gap backend. - Main difference to :class:`~sage.groups.matrix_gps.orthogonal.OrthogonalMatrixGroup_gap` is that we can - specify generators and a bilinear form. Following gap the group action is from the right. + Main difference to :class:`~sage.groups.matrix_gps.orthogonal.OrthogonalMatrixGroup_gap` + is that we can specify generators and a bilinear form. Following GAP, the group action is + from the right. INPUT: @@ -59,10 +60,10 @@ class GroupOfIsometries(FinitelyGeneratedMatrixGroup_gap): - ``check`` -- bool (default: ``True``) check if the generators preserve the bilinear form - ``invariant_submodule`` -- a submodule preserved by the group action - (default: ``None``) registers an action on this submodule. + (default: ``None``) registers an action on this submodule - ``invariant_quotient_module`` -- a quotient module preserved by the group action (default: ``None``) - registers an action on this quotient module. + registers an action on this quotient module EXAMPLES:: @@ -134,7 +135,7 @@ def _repr_(self): r""" Return the string representation of this matrix group. - OUTPUT: a string + OUTPUT: string EXAMPLES:: @@ -159,7 +160,7 @@ def _repr_(self): def __reduce__(self): r""" - Implements pickling. + Implement pickling. EXAMPLES:: @@ -358,7 +359,7 @@ class GroupActionOnQuotientModule(Action): - ``MatrixGroup`` -- the group acting :class:`GroupOfIsometries` - ``submodule`` -- an invariant quotient module - - ``is_left`` -- bool (default: ``False``) + - ``is_left`` -- boolean (default: ``False``) EXAMPLES:: @@ -376,7 +377,7 @@ class GroupActionOnQuotientModule(Action): """ def __init__(self, MatrixGroup, quotient_module, is_left=False): r""" - Initialize the action + Initialize the action. TESTS:: diff --git a/src/sage/groups/matrix_gps/linear.py b/src/sage/groups/matrix_gps/linear.py index ef2bf1aa0be..e8c7ddd08a3 100644 --- a/src/sage/groups/matrix_gps/linear.py +++ b/src/sage/groups/matrix_gps/linear.py @@ -201,19 +201,19 @@ def SL(n, R, var='a'): matrices that are invertible over the ring `R` with determinant one. - .. note:: + .. NOTE:: This group is also available via ``groups.matrix.SL()``. INPUT: - - ``n`` -- a positive integer. + - ``n`` -- positive integer - - ``R`` -- ring or an integer. If an integer is specified, the - corresponding finite field is used. + - ``R`` -- ring or integer; if an integer is specified, the + corresponding finite field is used - ``var`` -- variable used to represent generator of the finite - field, if needed. + field, if needed EXAMPLES:: diff --git a/src/sage/groups/matrix_gps/matrix_group.py b/src/sage/groups/matrix_gps/matrix_group.py index 2b87493fbc5..709a88a6d8e 100644 --- a/src/sage/groups/matrix_gps/matrix_group.py +++ b/src/sage/groups/matrix_gps/matrix_group.py @@ -126,14 +126,12 @@ def _check_matrix(self, x, *args): INPUT: - ``x`` -- a Sage matrix in the correct matrix space (degree - and base ring). + and base ring) - ``*args`` -- optional other representations of ``x``, - depending on the group implementation. Ignored by default. + depending on the group implementation. Ignored by default - OUTPUT: - - A :class:`TypeError` must be raised if ``x`` is invalid. + OUTPUT: a :class:`TypeError` must be raised if ``x`` is invalid EXAMPLES:: @@ -186,9 +184,11 @@ def subgroup(self, generators, check=True): INPUT: - ``generators`` -- a list/tuple/iterable of group elements of ``self`` - - ``check`` -- boolean (optional, default: ``True``). Whether to check that each matrix is invertible. + - ``check`` -- boolean (default: ``True``); whether to check that each + matrix is invertible - OUTPUT: The subgroup generated by ``generators`` as an instance of :class:`FinitelyGeneratedMatrixGroup_gap` + OUTPUT: the subgroup generated by ``generators`` as an instance of + :class:`FinitelyGeneratedMatrixGroup_gap` EXAMPLES:: @@ -262,10 +262,6 @@ def _repr_(self): """ Return a string representation. - OUTPUT: - - String. - EXAMPLES:: sage: F = GF(5); MS = MatrixSpace(F, 2, 2) @@ -349,7 +345,8 @@ def sign_representation(self, base_ring=None, side="twosided"): .. WARNING:: - Assumes ``self`` is a matrix group over a field which has embedding over real numbers. + Assumes ``self`` is a matrix group over a field which has + embedding over real numbers. INPUT: @@ -394,17 +391,17 @@ class MatrixGroup_generic(MatrixGroup_base): def __init__(self, degree, base_ring, category=None): """ - Base class for matrix groups over generic base rings + Base class for matrix groups over generic base rings. You should not use this class directly. Instead, use one of the more specialized derived classes. INPUT: - - ``degree`` -- integer. The degree (matrix size) of the - matrix group. + - ``degree`` -- integer; the degree (matrix size) of the + matrix group - - ``base_ring`` -- ring. The base ring of the matrices. + - ``base_ring`` -- ring; the base ring of the matrices TESTS:: @@ -429,10 +426,8 @@ def degree(self): """ Return the degree of this matrix group. - OUTPUT: - - Integer. The size (number of rows equals number of columns) of - the matrices. + OUTPUT: integer; the size (number of rows equals number of columns) + of the matrices EXAMPLES:: @@ -474,9 +469,7 @@ def __richcmp__(self, other, op): - ``op`` -- comparison operator - OUTPUT: - - boolean + OUTPUT: boolean EXAMPLES:: @@ -573,6 +566,5 @@ def is_trivial(self): 2 sage: G.is_trivial() True - """ return all(g.is_one() for g in self.gens()) diff --git a/src/sage/groups/matrix_gps/matrix_group_gap.py b/src/sage/groups/matrix_gps/matrix_group_gap.py index 84fd3d74996..d7de4c75f6e 100644 --- a/src/sage/groups/matrix_gps/matrix_group_gap.py +++ b/src/sage/groups/matrix_gps/matrix_group_gap.py @@ -37,16 +37,16 @@ def __init__(self, degree, base_ring, libgap_group, ambient=None, category=None) INPUT: - - ``degree`` -- integer. The degree (matrix size) of the - matrix group. + - ``degree`` -- integer; the degree (matrix size) of the + matrix group - - ``base_ring`` -- ring. The base ring of the matrices. + - ``base_ring`` -- ring; the base ring of the matrices - - ``libgap_group`` -- the defining libgap group. + - ``libgap_group`` -- the defining libgap group - - ``ambient`` -- A derived class of :class:`ParentLibGAP` or - ``None`` (default). The ambient class if ``libgap_group`` - has been defined as a subgroup. + - ``ambient`` -- a derived class of :class:`ParentLibGAP` or + ``None`` (default); the ambient class if ``libgap_group`` + has been defined as a subgroup TESTS: @@ -137,7 +137,6 @@ def __init__(self, degree, base_ring, libgap_group, ambient=None, category=None) Traceback (most recent call last): ... NotImplementedError: group must be finite - """ ParentLibGAP.__init__(self, libgap_group, ambient=ambient) MatrixGroup_generic.__init__(self, degree, base_ring, category=category) @@ -198,13 +197,11 @@ def _check_matrix(self, x_sage, x_gap): INPUT: - ``x_sage`` -- a Sage matrix in the correct matrix space (degree - and base ring). - - - ``x_gap`` -- the corresponding LibGAP matrix. + and base ring) - OUTPUT: + - ``x_gap`` -- the corresponding LibGAP matrix - A :class:`TypeError` must be raised if ``x`` is invalid. + OUTPUT: a :class:`TypeError` must be raised if ``x`` is invalid EXAMPLES:: @@ -232,9 +229,11 @@ def subgroup(self, generators, check=True): INPUT: - ``generators`` -- a list/tuple/iterable of group elements of ``self`` - - ``check`` -- boolean (optional, default: ``True``). Whether to check that each matrix is invertible. + - ``check`` -- boolean (default: ``True``). Whether to check that each + matrix is invertible - OUTPUT: The subgroup generated by ``generators`` as an instance of :class:`FinitelyGeneratedMatrixGroup_gap` + OUTPUT: The subgroup generated by ``generators`` as an instance of + :class:`FinitelyGeneratedMatrixGroup_gap` EXAMPLES:: diff --git a/src/sage/groups/matrix_gps/named_group.py b/src/sage/groups/matrix_gps/named_group.py index 90c01e232e7..142ea68ddfb 100644 --- a/src/sage/groups/matrix_gps/named_group.py +++ b/src/sage/groups/matrix_gps/named_group.py @@ -78,9 +78,7 @@ def normalize_args_vectorspace(*args, **kwds): field generator name in the case where ``ring`` is a prime power. - OUTPUT: - - A pair ``(degree, ring)``. + OUTPUT: a pair ``(degree, ring)`` TESTS:: @@ -190,7 +188,7 @@ class NamedMatrixGroup_generic(CachedRepresentation, MatrixGroup_generic): def __init__(self, degree, base_ring, special, sage_name, latex_string, category=None, invariant_form=None): """ - Base class for "named" matrix groups + Base class for "named" matrix groups. INPUT: @@ -237,9 +235,7 @@ def _an_element_(self): """ Return an element. - OUTPUT: - - A group element. + OUTPUT: a group element EXAMPLES:: @@ -253,10 +249,6 @@ def _repr_(self): """ Return a string representation. - OUTPUT: - - String. - EXAMPLES:: sage: GL(2, QQ)._repr_() @@ -266,11 +258,9 @@ def _repr_(self): def _latex_(self): """ - Return a LaTeX representation - - OUTPUT: + Return a LaTeX representation. - String. + OUTPUT: string EXAMPLES:: diff --git a/src/sage/groups/matrix_gps/named_group_gap.py b/src/sage/groups/matrix_gps/named_group_gap.py index b8b1f54722c..699e351f06d 100644 --- a/src/sage/groups/matrix_gps/named_group_gap.py +++ b/src/sage/groups/matrix_gps/named_group_gap.py @@ -23,22 +23,22 @@ class NamedMatrixGroup_gap(NamedMatrixGroup_generic, MatrixGroup_gap): def __init__(self, degree, base_ring, special, sage_name, latex_string, gap_command_string, category=None): """ - Base class for "named" matrix groups using LibGAP + Base class for "named" matrix groups using LibGAP. INPUT: - - ``degree`` -- integer. The degree (number of rows/columns of - matrices). + - ``degree`` -- integer; the degree (number of rows/columns of + matrices) - - ``base_ring`` -- ring. The base ring of the matrices. + - ``base_ring`` -- ring; the base ring of the matrices - - ``special`` -- boolean. Whether the matrix group is special, - that is, elements have determinant one. + - ``special`` -- boolean; whether the matrix group is special, + that is, elements have determinant one - - ``latex_string`` -- string. The latex representation. + - ``latex_string`` -- string; the latex representation - - ``gap_command_string`` -- string. The GAP command to construct - the matrix group. + - ``gap_command_string`` -- string; the GAP command to construct + the matrix group EXAMPLES:: diff --git a/src/sage/groups/matrix_gps/orthogonal.py b/src/sage/groups/matrix_gps/orthogonal.py index 3ce9915dfec..adfa3248204 100644 --- a/src/sage/groups/matrix_gps/orthogonal.py +++ b/src/sage/groups/matrix_gps/orthogonal.py @@ -21,7 +21,7 @@ of `SO(e,d,q)` in `GO(e,d,q)` is `2` if `q` is odd, but `SO(e,d,q) = GO(e,d,q)` if `q` is even.) -.. warning:: +.. WARNING:: GAP and Sage use different notations: @@ -107,18 +107,16 @@ def normalize_args_e(degree, ring, e): INPUT: - - ``degree`` -- integer. The degree of the affine group, that is, - the dimension of the affine space the group is acting on. + - ``degree`` -- integer; the degree of the affine group, that is, + the dimension of the affine space the group is acting on - - ``ring`` -- a ring. The base ring of the affine space. + - ``ring`` -- a ring; the base ring of the affine space - ``e`` -- integer, one of `+1`, `0`, `-1`. Only relevant for finite fields and if the degree is even. A parameter that distinguishes inequivalent invariant forms. - OUTPUT: - - The integer ``e`` with values required by GAP. + OUTPUT: the integer ``e`` with values required by GAP TESTS:: @@ -196,18 +194,18 @@ def _OG(n, R, special, e=0, var='a', invariant_form=None): inserted_text = "with respect to symmetric form" name = '{0} Orthogonal Group of degree {1} over {2} {3}\n{4}'.format( - prefix, degree, ring, inserted_text,invariant_form) + prefix, degree, ring, inserted_text, invariant_form) ltx = r'\text{{{0}O}}_{{{1}}}({2})\text{{ {3} }}{4}'.format( - ltx_prefix, degree, latex(ring), inserted_text, - latex(invariant_form)) + ltx_prefix, degree, latex(ring), inserted_text, + latex(invariant_form)) else: name = '{0} Orthogonal Group of degree {1} over {2}'.format(prefix, degree, ring) ltx = r'\text{{{0}O}}_{{{1}}}({2})'.format(ltx_prefix, degree, latex(ring)) else: name = '{0} Orthogonal Group of degree {1} and form parameter {2} over {3}'.format(prefix, degree, e, ring) ltx = r'\text{{{0}O}}_{{{1}}}({2}, {3})'.format(ltx_prefix, degree, - latex(ring), - '+' if e == 1 else '-') + latex(ring), + '+' if e == 1 else '-') if isinstance(ring, FiniteField): try: @@ -263,10 +261,8 @@ def GO(n, R, e=0, var='a', invariant_form=None): by the orthogonal group; the form is checked to be non-degenerate and symmetric but not to be positive definite - OUTPUT: - - The general orthogonal group of given degree, base ring, and - choice of invariant form. + OUTPUT: the general orthogonal group of given degree, base ring, and + choice of invariant form EXAMPLES:: @@ -373,10 +369,8 @@ def SO(n, R, e=None, var='a', invariant_form=None): by the orthogonal group; the form is checked to be non-degenerate and symmetric but not to be positive definite - OUTPUT: - - The special orthogonal group of given degree, base ring, and choice of - invariant form. + OUTPUT: the special orthogonal group of given degree, base ring, and choice + of invariant form EXAMPLES:: @@ -482,9 +476,7 @@ def invariant_bilinear_form(self): """ Return the symmetric bilinear form preserved by ``self``. - OUTPUT: - - A matrix. + OUTPUT: a matrix EXAMPLES:: @@ -522,11 +514,14 @@ def invariant_bilinear_form(self): m.set_immutable() return m - invariant_quadratic_form = invariant_bilinear_form # this is identical in the generic case - invariant_form = invariant_bilinear_form # alias (analogues to symplectic and unitary cases) + invariant_quadratic_form = invariant_bilinear_form + # this is identical in the generic case + + invariant_form = invariant_bilinear_form + # alias (analogues to symplectic and unitary cases) def _check_matrix(self, x, *args): - """a + """ Check whether the matrix ``x`` is orthogonal. See :meth:`~sage.groups.matrix_gps.matrix_group._check_matrix` diff --git a/src/sage/groups/matrix_gps/symplectic.py b/src/sage/groups/matrix_gps/symplectic.py index 0e378b4d153..d139034107d 100644 --- a/src/sage/groups/matrix_gps/symplectic.py +++ b/src/sage/groups/matrix_gps/symplectic.py @@ -211,7 +211,7 @@ def invariant_form(self): """ Return the quadratic form preserved by the symplectic group. - OUTPUT: A matrix. + OUTPUT: a matrix EXAMPLES:: diff --git a/src/sage/groups/matrix_gps/symplectic_gap.py b/src/sage/groups/matrix_gps/symplectic_gap.py index d348ed9f40f..b0d05c3be6e 100644 --- a/src/sage/groups/matrix_gps/symplectic_gap.py +++ b/src/sage/groups/matrix_gps/symplectic_gap.py @@ -48,7 +48,7 @@ def invariant_form(self): """ Return the quadratic form preserved by the symplectic group. - OUTPUT: A matrix. + OUTPUT: a matrix EXAMPLES:: diff --git a/src/sage/groups/matrix_gps/unitary.py b/src/sage/groups/matrix_gps/unitary.py index 43ac69c3983..e706abcfda7 100644 --- a/src/sage/groups/matrix_gps/unitary.py +++ b/src/sage/groups/matrix_gps/unitary.py @@ -68,11 +68,7 @@ def finite_field_sqrt(ring): """ Helper function. - INPUT: A ring. - - OUTPUT: - - Integer `q` such that ``ring`` is the finite field with `q^2` elements. + OUTPUT: integer `q` such that ``ring`` is the finite field with `q^2` elements EXAMPLES:: @@ -189,7 +185,7 @@ def GU(n, R, var='a', invariant_form=None): by the unitary group; the form is checked to be non-degenerate and hermitian but not to be positive definite - OUTPUT: The general unitary group. + OUTPUT: the general unitary group EXAMPLES:: @@ -301,9 +297,7 @@ def SU(n, R, var='a', invariant_form=None): by the unitary group; the form is checked to be non-degenerate and hermitian but not to be positive definite - OUTPUT: - - Return the special unitary group. + OUTPUT: the special unitary group EXAMPLES:: @@ -396,7 +390,7 @@ def invariant_form(self): Return the hermitian form preserved by the unitary group. - OUTPUT: A square matrix describing the bilinear form + OUTPUT: a square matrix describing the bilinear form EXAMPLES:: diff --git a/src/sage/groups/matrix_gps/unitary_gap.py b/src/sage/groups/matrix_gps/unitary_gap.py index ac2de20f96b..ab0512a4803 100644 --- a/src/sage/groups/matrix_gps/unitary_gap.py +++ b/src/sage/groups/matrix_gps/unitary_gap.py @@ -41,9 +41,7 @@ def invariant_form(self): """ Return the hermitian form preserved by the unitary group. - OUTPUT: - - A square matrix describing the bilinear form + OUTPUT: a square matrix describing the bilinear form EXAMPLES:: diff --git a/src/sage/groups/misc_gps/argument_groups.py b/src/sage/groups/misc_gps/argument_groups.py index 1c949a57f51..edbdb7b7bad 100644 --- a/src/sage/groups/misc_gps/argument_groups.py +++ b/src/sage/groups/misc_gps/argument_groups.py @@ -114,9 +114,7 @@ def _normalize_(element): - ``element`` -- an element of the parent's base - OUTPUT: - - An element. + OUTPUT: an element TESTS:: @@ -151,9 +149,7 @@ def _symbolic_(self, R=None): The output will be an element of ``R``. If ``None``, then the symbolic ring is used. - OUTPUT: - - A symbolic expression. + OUTPUT: a symbolic expression EXAMPLES:: @@ -285,7 +281,7 @@ def _act_on_(self, other, is_left): def __abs__(self): r""" - Return the absolute value of this argument which equals `1` + Return the absolute value of this argument which equals `1`. TESTS:: @@ -337,9 +333,7 @@ def _determine_category_(category): - ``category`` -- a category or ``None`` (in which case the output equals ``category``) - OUTPUT: - - A category. + OUTPUT: a category EXAMPLES:: @@ -414,9 +408,7 @@ def _normalize_(exponent): - ``exponent`` -- an element of the parent's base - OUTPUT: - - An element. + OUTPUT: an element TESTS:: @@ -463,9 +455,7 @@ def _symbolic_(self, R=None): The output will be an element of ``R``. If ``None``, then the symbolic ring is used. - OUTPUT: - - A symbolic expression. + OUTPUT: a symbolic expression EXAMPLES:: @@ -681,9 +671,7 @@ def _element_constructor_(self, data, exponent=None, **kwds): - ``kwds`` -- are passed on to element - OUTPUT: - - A :class:`UnitCirclePoint`. + OUTPUT: a :class:`UnitCirclePoint` TESTS:: @@ -794,11 +782,9 @@ def _create_element_in_extension_(self, exponent): INPUT: - - ``exponent`` -- the element data. + - ``exponent`` -- the element data - OUTPUT: - - An element. + OUTPUT: an element EXAMPLES:: @@ -826,11 +812,9 @@ def _coerce_map_from_(self, R): INPUT: - - ``R`` -- a parent. - - OUTPUT: + - ``R`` -- a parent - A boolean. + OUTPUT: boolean TESTS:: @@ -1088,9 +1072,7 @@ def _normalize_(element): - ``element`` -- an element of the parent's base - OUTPUT: - - An element. + OUTPUT: an element TESTS:: @@ -1123,9 +1105,7 @@ def _symbolic_(self, R=None): The output will be an element of ``R``. If ``None``, then the symbolic ring is used. - OUTPUT: - - A symbolic expression. + OUTPUT: a symbolic expression EXAMPLES:: @@ -1275,9 +1255,7 @@ def _element_constructor_(self, data, **kwds): - ``kwds`` -- are passed on to element - OUTPUT: - - A :class:`ArgumentByElement`. + OUTPUT: a :class:`ArgumentByElement` TESTS:: @@ -1343,11 +1321,9 @@ def _create_element_in_extension_(self, element): INPUT: - - ``element`` -- the element data. - - OUTPUT: + - ``element`` -- the element data - An element. + OUTPUT: an element EXAMPLES:: @@ -1377,11 +1353,9 @@ def _coerce_map_from_(self, R): INPUT: - - ``R`` -- a parent. + - ``R`` -- a parent - OUTPUT: - - A boolean. + OUTPUT: boolean TESTS:: @@ -1436,9 +1410,7 @@ def _normalize_(element): - ``element`` -- an element of the parent's base - OUTPUT: - - An element. + OUTPUT: an element TESTS:: @@ -1689,9 +1661,7 @@ def _element_constructor_(self, data): - ``data`` -- an object - OUTPUT: - - A :class:`Sign`. + OUTPUT: a :class:`Sign` TESTS:: diff --git a/src/sage/groups/misc_gps/imaginary_groups.py b/src/sage/groups/misc_gps/imaginary_groups.py index 41fe79f9959..428db810921 100644 --- a/src/sage/groups/misc_gps/imaginary_groups.py +++ b/src/sage/groups/misc_gps/imaginary_groups.py @@ -284,9 +284,7 @@ def _determine_category_(category): - ``category`` -- a category or ``None`` (in which case the output equals ``category``) - OUTPUT: - - A category. + OUTPUT: a category EXAMPLES:: @@ -376,9 +374,7 @@ def _element_constructor_(self, data, imag=None): - ``imag`` -- a number (of a subset of the reals) or ``None`` - OUTPUT: - - A :class:`ImaginaryElement`. + OUTPUT: a :class:`ImaginaryElement` TESTS:: diff --git a/src/sage/groups/old.pyx b/src/sage/groups/old.pyx index 1833b4a2b74..564dbd75c23 100644 --- a/src/sage/groups/old.pyx +++ b/src/sage/groups/old.pyx @@ -26,7 +26,7 @@ from sage.misc.superseded import deprecation cdef class Group(sage.structure.parent.Parent): """ - Generic group class + Generic group class. """ def __init__(self, category=None): """ @@ -69,7 +69,7 @@ cdef class Group(sage.structure.parent.Parent): def __contains__(self, x): r""" - True if coercion of `x` into self is defined. + Return ``True`` if coercion of ``x`` into ``self`` is defined. EXAMPLES:: @@ -88,7 +88,7 @@ cdef class Group(sage.structure.parent.Parent): def is_abelian(self): """ - Return True if this group is abelian. + Return ``True`` if this group is abelian. EXAMPLES:: @@ -103,7 +103,7 @@ cdef class Group(sage.structure.parent.Parent): def is_commutative(self): r""" - Return True if this group is commutative. This is an alias for + Return ``True`` if this group is commutative. This is an alias for is_abelian, largely to make groups work well with the Factorization class. @@ -119,7 +119,7 @@ cdef class Group(sage.structure.parent.Parent): def order(self): """ - Returns the number of elements of this group, which is either a + Return the number of elements of this group, which is either a positive integer or infinity. EXAMPLES:: @@ -135,8 +135,7 @@ cdef class Group(sage.structure.parent.Parent): def is_multiplicative(self): r""" - Returns True if the group operation is given by \* (rather than - +). + Return ``True`` if the group operation is given by \* (rather than +). Override for additive groups. @@ -166,8 +165,7 @@ cdef class Group(sage.structure.parent.Parent): def quotient(self, H, **kwds): """ - Return the quotient of this group by the normal subgroup - `H`. + Return the quotient of this group by the normal subgroup `H`. EXAMPLES:: @@ -214,7 +212,6 @@ cdef class FiniteGroup(Group): """ return True - cdef class AlgebraicGroup(Group): """ Generic algebraic group. diff --git a/src/sage/groups/pari_group.py b/src/sage/groups/pari_group.py index 62dc75cb821..fb460e5b902 100644 --- a/src/sage/groups/pari_group.py +++ b/src/sage/groups/pari_group.py @@ -29,7 +29,7 @@ def __init__(self, x, degree): def __repr__(self): """ - String representation of this group + String representation of this group. EXAMPLES:: @@ -159,7 +159,7 @@ def order(self): def permutation_group(self): """ - Return the corresponding GAP transitive group + Return the corresponding GAP transitive group. EXAMPLES:: diff --git a/src/sage/groups/perm_gps/constructor.py b/src/sage/groups/perm_gps/constructor.py index 5a130eb19be..ab912f7667d 100644 --- a/src/sage/groups/perm_gps/constructor.py +++ b/src/sage/groups/perm_gps/constructor.py @@ -170,10 +170,8 @@ def standardize_generator(g, convert_dict=None, as_cycles=False): - ``as_cycles`` -- (default: ``False``) whether the output should be as cycles or in one-line notation - OUTPUT: - - The permutation in as a list in one-line notation or a list of cycles - as tuples. + OUTPUT: the permutation in as a list in one-line notation or a list of + cycles as tuples. EXAMPLES:: diff --git a/src/sage/groups/perm_gps/partn_ref/automorphism_group_canonical_label.pyx b/src/sage/groups/perm_gps/partn_ref/automorphism_group_canonical_label.pyx index 803f2533b7d..dd2647238f2 100644 --- a/src/sage/groups/perm_gps/partn_ref/automorphism_group_canonical_label.pyx +++ b/src/sage/groups/perm_gps/partn_ref/automorphism_group_canonical_label.pyx @@ -438,12 +438,12 @@ cdef aut_gp_and_can_lab *get_aut_gp_and_can_lab(void *S, int -- 0 if gamma_1(S1) = gamma_2(S2), otherwise -1 or 1 (see docs for cmp), such that the set of all structures is well-ordered - NOTE: - The partition ``partition1`` *must* satisfy the property that in each cell, - the smallest element occurs first! + .. NOTE:: - OUTPUT: - pointer to a aut_gp_and_can_lab struct + The partition ``partition1`` *must* satisfy the property that in each + cell, the smallest element occurs first! + + OUTPUT: a pointer to a ``aut_gp_and_can_lab`` struct """ cdef PartitionStack *current_ps diff --git a/src/sage/groups/perm_gps/partn_ref/canonical_augmentation.pyx b/src/sage/groups/perm_gps/partn_ref/canonical_augmentation.pyx index 6143e509caa..245ec5f7c06 100644 --- a/src/sage/groups/perm_gps/partn_ref/canonical_augmentation.pyx +++ b/src/sage/groups/perm_gps/partn_ref/canonical_augmentation.pyx @@ -467,9 +467,7 @@ cdef iterator *setup_canonical_generator(int degree, - ``max_depth`` - maximum depth of augmentations to be made from the seed object S - OUTPUT: - - pointer to an iterator of objects + OUTPUT: a pointer to an iterator of objects """ if max_depth <= 1: diff --git a/src/sage/groups/perm_gps/partn_ref/data_structures.pxd b/src/sage/groups/perm_gps/partn_ref/data_structures.pxd index f53184bd082..a71fa3cadd8 100644 --- a/src/sage/groups/perm_gps/partn_ref/data_structures.pxd +++ b/src/sage/groups/perm_gps/partn_ref/data_structures.pxd @@ -85,8 +85,7 @@ cdef inline OrbitPartition *OP_copy(OrbitPartition *OP) noexcept: """ Allocate and return a pointer to a copy of a OrbitPartition of degree n. - Returns a - null pointer in the case of an allocation failure. + Returns a null pointer in the case of an allocation failure. """ cdef OrbitPartition *OP2 = OP_new(OP.degree) if OP is NULL: @@ -115,7 +114,7 @@ cdef inline int OP_find(OrbitPartition *OP, int n) noexcept: OP.parent[n] = OP_find(OP, OP.parent[n]) return OP.parent[n] -cdef inline int OP_join(OrbitPartition *OP, int m, int n) noexcept: +cdef inline void OP_join(OrbitPartition *OP, int m, int n) noexcept: """ Join the cells containing m and n, if they are different. """ @@ -172,7 +171,7 @@ cdef inline int PS_copy_from_to(PartitionStack *PS, PartitionStack *PS2) noexcep cdef inline bint PS_is_discrete(PartitionStack *PS) noexcept: """ - Returns whether the deepest partition consists only of singleton cells. + Return whether the deepest partition consists only of singleton cells. """ cdef int i for i from 0 <= i < PS.degree: @@ -182,7 +181,7 @@ cdef inline bint PS_is_discrete(PartitionStack *PS) noexcept: cdef inline int PS_num_cells(PartitionStack *PS) noexcept: """ - Returns the number of cells. + Return the number of cells. """ cdef int i, ncells = 0 for i from 0 <= i < PS.degree: @@ -206,13 +205,13 @@ cdef inline void PS_move_min_to_front(PartitionStack *PS, int start, int end) no cdef inline bint PS_is_mcr(PartitionStack *PS, int m) noexcept: """ - Returns whether PS.elements[m] (not m!) is the smallest element of its cell. + Return whether PS.elements[m] (not m!) is the smallest element of its cell. """ return m == 0 or PS.levels[m-1] <= PS.depth cdef inline bint PS_is_fixed(PartitionStack *PS, int m) noexcept: """ - Returns whether PS.elements[m] (not m!) is in a singleton cell, assuming + Return whether PS.elements[m] (not m!) is in a singleton cell, assuming PS_is_mcr(PS, m) is already true. """ return PS.levels[m] <= PS.depth diff --git a/src/sage/groups/perm_gps/partn_ref/data_structures.pyx b/src/sage/groups/perm_gps/partn_ref/data_structures.pyx index cb25f23a4d3..6f4d9b2b430 100644 --- a/src/sage/groups/perm_gps/partn_ref/data_structures.pyx +++ b/src/sage/groups/perm_gps/partn_ref/data_structures.pyx @@ -42,7 +42,7 @@ from sage.arith.misc import is_prime as n_is_prime cdef inline OrbitPartition *OP_new(int n) noexcept: """ - Allocate and return a pointer to a new OrbitPartition of degree n. Returns a + Allocate and return a pointer to a new OrbitPartition of degree n. Return a null pointer in the case of an allocation failure. """ cdef OrbitPartition *OP = \ @@ -181,7 +181,7 @@ def OP_represent(int n, merges, perm): cdef inline PartitionStack *PS_new(int n, bint unit_partition) noexcept: """ - Allocate and return a pointer to a new PartitionStack of degree n. Returns a + Allocate and return a pointer to a new PartitionStack of degree n. Return a null pointer in the case of an allocation failure. """ cdef PartitionStack *PS = \ @@ -213,7 +213,7 @@ cdef void PS_unit_partition(PartitionStack *PS) noexcept: cdef inline PartitionStack *PS_copy(PartitionStack *PS) noexcept: """ - Allocate and return a pointer to a copy of PartitionStack PS. Returns a null + Allocate and return a pointer to a copy of PartitionStack PS. Return a null pointer in the case of an allocation failure. """ cdef int n = PS.degree @@ -237,7 +237,7 @@ cdef inline void PS_dealloc(PartitionStack *PS) noexcept: cdef PartitionStack *PS_from_list(list L) noexcept: """ - Allocate and return a pointer to a PartitionStack representing L. Returns a + Allocate and return a pointer to a PartitionStack representing L. Return a null pointer in the case of an allocation failure. """ cdef int cell, i, num_cells = len(L), cur_start = 0, cur_len, n = 0 @@ -286,7 +286,7 @@ cdef int PS_first_smallest(PartitionStack *PS, bitset_t b, int *second_pos=NULL) """ Find the first occurrence of the smallest cell of size greater than one, which is admissible (checked by the function ``test_allowance``). - Its entries are stored to b and its minimum element is returned. + Its entries are stored to `b` and its minimum element is returned. """ cdef int i = 0, j = 0, location = 0, n = PS.degree bitset_zero(b) @@ -383,7 +383,7 @@ cdef int PS_find_element(PartitionStack *PS, bitset_t b, int x) except -1: cdef list PS_singletons(PartitionStack * part): """ - Return the list of all singletons in the PartitionStack. + Return the list of all singletons in the ``PartitionStack``. """ cdef list l = [] cdef int i @@ -552,7 +552,7 @@ cdef enum: cdef StabilizerChain *SC_new(int n, bint init_gens=True) noexcept: """ - Allocate and return a pointer to a new StabilizerChain of degree n. Returns + Allocate and return a pointer to a new StabilizerChain of degree n. Return a null pointer in the case of an allocation failure. """ cdef int i @@ -624,7 +624,7 @@ cdef inline int SC_realloc_gens(StabilizerChain *SC, int level, int size) noexce """ Reallocate generator array at level `level` to size `size`. - Returns 1 in case of an allocation failure. + Return 1 in case of an allocation failure. """ cdef int *temp cdef int n = SC.degree @@ -659,9 +659,9 @@ cdef inline void SC_dealloc(StabilizerChain *SC) noexcept: cdef StabilizerChain *SC_symmetric_group(int n) noexcept: """ - Returns a stabilizer chain for the symmetric group on {0, 1, ..., n-1}. + Return a stabilizer chain for the symmetric group on {0, 1, ..., n-1}. - Returns NULL in the case of an allocation failure. + Return ``NULL`` in the case of an allocation failure. """ cdef int i, j, b cdef StabilizerChain *SC = SC_new(n, False) @@ -700,9 +700,9 @@ cdef StabilizerChain *SC_symmetric_group(int n) noexcept: cdef StabilizerChain *SC_alternating_group(int n) noexcept: """ - Returns a stabilizer chain for the alternating group on {0, 1, ..., n-1}. + Return a stabilizer chain for the alternating group on {0, 1, ..., n-1}. - Returns NULL in the case of an allocation failure. + Return ``NULL`` in the case of an allocation failure. """ cdef int i, j, b cdef StabilizerChain *SC = SC_new(n, False) @@ -747,7 +747,7 @@ cdef int SC_realloc_bitsets(StabilizerChain *SC, unsigned long size) noexcept: If size is larger than current allocation, double the size of the bitsets until it is not. - Returns 1 in case of an allocation failure. + Return 1 in case of an allocation failure. """ cdef unsigned long size_old = SC.gen_used.size if size <= size_old: @@ -780,7 +780,7 @@ cdef StabilizerChain *SC_copy(StabilizerChain *SC, int level) noexcept: """ Creates a copy of the first `level` levels of SC. Must have 0 < level. - Returns a null pointer in case of allocation failure. + Return a null pointer in case of allocation failure. """ cdef int i, n = SC.degree cdef StabilizerChain *SCC = SC_new(n, False) @@ -847,7 +847,7 @@ cdef StabilizerChain *SC_new_base(StabilizerChain *SC, int *base, int base_len) Use SC_cleanup to remove redundant base points. - Returns a null pointer in case of an allocation failure. + Return a null pointer in case of an allocation failure. """ cdef StabilizerChain *NEW = SC_new(SC.degree) if NEW is NULL: @@ -908,7 +908,7 @@ cdef StabilizerChain *SC_insert_base_point(StabilizerChain *SC, int level, int p Use SC_cleanup to remove redundant base points. - Returns a null pointer in case of an allocation failure. + Return a null pointer in case of an allocation failure. """ cdef int i, b, n = SC.degree cdef StabilizerChain *NEW @@ -994,7 +994,7 @@ cdef int SC_sift(StabilizerChain *SC, int level, int x, int *gens, int num_gens, num_gens - how many of these there are new_gens - space of size at least num_gens*n for the sifted perms to go - Returns 1 in case of an allocation failure. + Return 1 in case of an allocation failure. """ cdef int n = SC.degree if num_gens == 0: @@ -1142,8 +1142,8 @@ cdef bint SC_is_giant(int n, int num_perms, int *perms, float p, bitset_t suppor If the group is not a giant, this routine will return False. This could also indicate an allocation failure. - If the group is a giant, this routine will return True with approximate - probability p. It will set `support' to the support of the group in this + If the group is a giant, this routine will return ``True`` with approximate + probability ``p``. It will set `support' to the support of the group in this case. Use bitset_len to get the size of support. The bitset `support' must be initialized. Must have 0 <= p < 1. @@ -1619,10 +1619,11 @@ cdef int sort_by_function(PartitionStack *PS, int start, int *degrees) noexcept: A simple counting sort, given the degrees of vertices to a certain cell. INPUT: - PS -- the partition stack to be checked - start -- beginning index of the cell to be sorted - degrees -- the values to be sorted by, must have extra scratch space for a - total of 3*n+1 + + - PS -- the partition stack to be checked + - start -- beginning index of the cell to be sorted + - degrees -- the values to be sorted by, must have extra scratch space for a + total of `3*n+1` """ cdef int n = PS.degree diff --git a/src/sage/groups/perm_gps/partn_ref/double_coset.pyx b/src/sage/groups/perm_gps/partn_ref/double_coset.pyx index 73631b395c4..051e3f78fa4 100644 --- a/src/sage/groups/perm_gps/partn_ref/double_coset.pyx +++ b/src/sage/groups/perm_gps/partn_ref/double_coset.pyx @@ -313,14 +313,13 @@ cdef int double_coset(void *S1, void *S2, PartitionStack *partition1, int *order isom -- space to store the isomorphism to, or NULL if isomorphism is not needed - NOTE: - The partition ``partition1`` and the resulting partition from ``ordering2`` - *must* satisfy the property that in each cell, the smallest element occurs - first! + .. NOTE:: - OUTPUT: - 1 if S1 and S2 are isomorphic, otherwise 0. + The partition ``partition1`` and the resulting partition from + ``ordering2`` *must* satisfy the property that in each cell, the + smallest element occurs first! + OUTPUT: ``1`` if ``S1`` and ``S2`` are isomorphic, otherwise ``0`` """ cdef PartitionStack *current_ps cdef PartitionStack *first_ps diff --git a/src/sage/groups/perm_gps/partn_ref/refinement_binary.pyx b/src/sage/groups/perm_gps/partn_ref/refinement_binary.pyx index 6a3c07ac75d..1abbf635d9c 100644 --- a/src/sage/groups/perm_gps/partn_ref/refinement_binary.pyx +++ b/src/sage/groups/perm_gps/partn_ref/refinement_binary.pyx @@ -114,11 +114,12 @@ cdef class LinearBinaryCodeStruct(BinaryCodeStruct): def run(self, partition=None): """ Perform the canonical labeling and automorphism group computation, - storing results to self. + storing results to ``self``. INPUT: - partition -- an optional list of lists partition of the columns. - default is the unit partition. + + - ``partition`` -- an optional list of lists partition of the columns; + default is the unit partition EXAMPLES:: @@ -253,17 +254,17 @@ cdef class LinearBinaryCodeStruct(BinaryCodeStruct): def automorphism_group(self): """ - Returns a list of generators of the automorphism group, along with its + Return a list of generators of the automorphism group, along with its order and a base for which the list of generators is a strong generating set. - EXAMPLES: (For more examples, see self.run()) + EXAMPLES: (For more examples, see self.run()):: + sage: from sage.groups.perm_gps.partn_ref.refinement_binary import LinearBinaryCodeStruct sage: B = LinearBinaryCodeStruct(matrix(GF(2),[[1,1,1,1]])) sage: B.automorphism_group() ([[0, 1, 3, 2], [0, 2, 1, 3], [1, 0, 2, 3]], 24, [0, 1, 2]) - """ cdef int i, j cdef list generators, base @@ -280,9 +281,10 @@ cdef class LinearBinaryCodeStruct(BinaryCodeStruct): def canonical_relabeling(self): """ - Returns a canonical relabeling (in list permutation format). + Return a canonical relabeling (in list permutation format). + + EXAMPLES: (For more examples, see self.run()):: - EXAMPLES: (For more examples, see self.run()) sage: from sage.groups.perm_gps.partn_ref.refinement_binary import LinearBinaryCodeStruct sage: B = LinearBinaryCodeStruct(matrix(GF(2), [[1,1,0]])) @@ -295,7 +297,6 @@ cdef class LinearBinaryCodeStruct(BinaryCodeStruct): ([[2, 1, 0]], 2, [0]) sage: B.canonical_relabeling() [1, 0, 2] - """ cdef int i if self.output is NULL: @@ -304,7 +305,7 @@ cdef class LinearBinaryCodeStruct(BinaryCodeStruct): def is_isomorphic(self, LinearBinaryCodeStruct other): """ - Calculate whether self is isomorphic to other. + Calculate whether ``self`` is isomorphic to ``other``. EXAMPLES:: @@ -314,7 +315,6 @@ cdef class LinearBinaryCodeStruct(BinaryCodeStruct): sage: C = LinearBinaryCodeStruct(Matrix(GF(2), [[1,1,1,0,0,1],[1,1,0,1,1,0]])) sage: B.is_isomorphic(C) [0, 1, 2, 5, 3, 4] - """ cdef int i, n = self.degree cdef int *output @@ -512,7 +512,6 @@ cdef class NonlinearBinaryCodeStruct(BinaryCodeStruct): [4, 0, 1, 2]) sage: B.canonical_relabeling() [2, 3, 4, 5, 0, 1] - """ cdef int n = self.degree cdef PartitionStack *part @@ -530,11 +529,12 @@ cdef class NonlinearBinaryCodeStruct(BinaryCodeStruct): def automorphism_group(self): """ - Returns a list of generators of the automorphism group, along with its + Return a list of generators of the automorphism group, along with its order and a base for which the list of generators is a strong generating set. - EXAMPLES: (For more examples, see self.run()) + EXAMPLES: (For more examples, see self.run()):: + sage: from sage.groups.perm_gps.partn_ref.refinement_binary import NonlinearBinaryCodeStruct sage: B = NonlinearBinaryCodeStruct(Matrix(GF(2), [[1,1,1,0,0,0],[1,1,0,1,0,0],[1,0,1,1,0,0],[0,1,1,1,0,0],[0,0,0,0,1,0],[0,0,0,0,0,1]])) @@ -546,7 +546,6 @@ cdef class NonlinearBinaryCodeStruct(BinaryCodeStruct): [0, 1, 2, 3, 5, 4]], 48, [4, 0, 1, 2]) - """ cdef int i, j cdef list generators, base @@ -563,16 +562,16 @@ cdef class NonlinearBinaryCodeStruct(BinaryCodeStruct): def canonical_relabeling(self): """ - Returns a canonical relabeling (in list permutation format). + Return a canonical relabeling (in list permutation format). + + EXAMPLES: (For more examples, see self.run()):: - EXAMPLES: (For more examples, see self.run()) sage: from sage.groups.perm_gps.partn_ref.refinement_binary import NonlinearBinaryCodeStruct sage: B = NonlinearBinaryCodeStruct(Matrix(GF(2), [[1,1,1,0,0,0],[1,1,0,1,0,0],[1,0,1,1,0,0],[0,1,1,1,0,0],[0,0,0,0,1,0],[0,0,0,0,0,1]])) sage: B.run() sage: B.canonical_relabeling() [2, 3, 4, 5, 0, 1] - """ cdef int i if self.output is NULL: @@ -581,7 +580,7 @@ cdef class NonlinearBinaryCodeStruct(BinaryCodeStruct): def is_isomorphic(self, NonlinearBinaryCodeStruct other): """ - Calculate whether self is isomorphic to other. + Calculate whether ``self`` is isomorphic to ``other``. EXAMPLES:: @@ -591,7 +590,6 @@ cdef class NonlinearBinaryCodeStruct(BinaryCodeStruct): sage: C = NonlinearBinaryCodeStruct(Matrix(GF(2), [[1,1,0,0,1,1],[1,1,1,1,0,0]])) sage: B.is_isomorphic(C) [2, 3, 0, 1, 4, 5] - """ cdef int i, n = self.degree cdef int *output @@ -628,17 +626,17 @@ cdef int ith_word_nonlinear(BinaryCodeStruct self, int i, bitset_s *word) noexce cdef int refine_by_bip_degree(PartitionStack *col_ps, void *S, int *cells_to_refine_by, int ctrb_len) noexcept: r""" - Refines the input partition by checking degrees of vertices to the given + Refine the input partition by checking degrees of vertices to the given cells in the associated bipartite graph (vertices split into columns and words). INPUT: - col_ps -- a partition stack, whose finest partition is the partition to be - refined. - S -- a binary code struct object - cells_to_refine_by -- a list of pointers to cells to check degrees against - in refining the other cells (updated in place) - ctrb_len -- how many cells in cells_to_refine_by + + - col_ps -- a partition stack, whose finest partition is the partition to be refined + - S -- a binary code struct object + - cells_to_refine_by -- a list of pointers to cells to check degrees against + in refining the other cells (updated in place) + - ctrb_len -- how many cells in cells_to_refine_by OUTPUT: @@ -914,17 +912,19 @@ cdef int compare_nonlinear_codes(int *gamma_1, int *gamma_2, void *S1, void *S2, cdef bint all_children_are_equivalent(PartitionStack *col_ps, void *S) noexcept: """ - Returns True if any refinement of the current partition results in the same - structure. - - WARNING: - Converse does not hold in general! See Lemma 2.25 of [1] for details, noting - that the binary code is interpreted as a bipartite graph (see module docs - for details). + Return ``True`` if any refinement of the current partition results in the + same structure. INPUT: - col_ps -- the partition stack to be checked - S -- a binary code struct object + + - ``col_ps`` -- the partition stack to be checked + - ``S`` -- a binary code struct object + + .. WARNING:: + + Converse does not hold in general! See Lemma 2.25 of [1] for details, noting + that the binary code is interpreted as a bipartite graph (see module docs + for details). """ cdef BinaryCodeStruct BCS = S cdef PartitionStack *word_ps = BCS.word_ps @@ -957,16 +957,16 @@ cdef bint all_children_are_equivalent(PartitionStack *col_ps, void *S) noexcept: cdef inline int word_degree(PartitionStack *word_ps, BinaryCodeStruct BCS, int entry, int cell_index, PartitionStack *col_ps) noexcept: """ - Returns the number of edges from the vertex corresponding to entry to + Return the number of edges from the vertex corresponding to entry to vertices in the cell corresponding to cell_index. INPUT: - word_ps -- the partition stack to be checked - col_ps -- corresponding partition stack on columns - BCS -- a binary code struct object - entry -- the position of the vertex in question in the entries of word_ps - cell_index -- the starting position of the cell in question in the entries - of PS + + - word_ps -- the partition stack to be checked + - col_ps -- corresponding partition stack on columns + - BCS -- a binary code struct object + - entry -- the position of the vertex in question in the entries of word_ps + - cell_index -- the starting position of the cell in question in the entries of PS """ cdef bitset_t cell, word cdef int h @@ -987,16 +987,16 @@ cdef inline int word_degree(PartitionStack *word_ps, BinaryCodeStruct BCS, int e cdef inline int col_degree(PartitionStack *col_ps, BinaryCodeStruct BCS, int entry, int cell_index, PartitionStack *word_ps) noexcept: """ - Returns the number of edges from the vertex corresponding to entry to + Return the number of edges from the vertex corresponding to entry to vertices in the cell corresponding to cell_index. INPUT: - col_ps -- the partition stack to be checked - word_ps -- corresponding partition stack on words - BCS -- a binary code struct object - entry -- the position of the vertex in question in the entries of word_ps - cell_index -- the starting position of the cell in question in the entries - of PS + + - col_ps -- the partition stack to be checked + - word_ps -- corresponding partition stack on words + - BCS -- a binary code struct object + - entry -- the position of the vertex in question in the entries of word_ps + - cell_index -- the starting position of the cell in question in the entries of PS """ cdef bitset_t word bitset_init(word, BCS.degree) @@ -1055,14 +1055,15 @@ cdef inline int sort_by_function_codes(PartitionStack *PS, int start, int *degre def random_tests(num=50, n_max=50, k_max=6, nwords_max=200, perms_per_code=10, density_range=(.1,.9)): """ - Tests to make sure that C(gamma(B)) == C(B) for random permutations gamma - and random codes B, and that is_isomorphic returns an isomorphism. + Test to make sure that ``C(gamma(B)) == C(B)`` for random permutations ``gamma`` + and random codes ``B``, and that :meth:`is_isomorphic` returns an isomorphism. INPUT: - num -- run tests for this many codes - n_max -- test codes with at most this many columns - k_max -- test codes with at most this for dimension - perms_per_code -- test each code with this many random permutations + + - ``num`` -- run tests for this many codes + - ``n_max`` -- test codes with at most this many columns + - ``k_max`` -- test codes with at most this for dimension + - ``perms_per_code`` -- test each code with this many random permutations DISCUSSION: 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 1b588487d1c..5cad4cb0a0b 100644 --- a/src/sage/groups/perm_gps/partn_ref/refinement_graphs.pyx +++ b/src/sage/groups/perm_gps/partn_ref/refinement_graphs.pyx @@ -676,12 +676,12 @@ cdef int compare_graphs(int *gamma_1, int *gamma_2, void *S1, void *S2, int degr cdef bint all_children_are_equivalent(PartitionStack *PS, void *S) noexcept: """ - Return True if every refinement of the current partition results in the + Return ``True`` if every refinement of the current partition results in the same structure. - WARNING: + .. WARNING:: - Converse does not hold in general! See Lemma 2.25 of [1] for details. + Converse does not hold in general! See Lemma 2.25 of [1] for details. INPUT: @@ -1163,7 +1163,7 @@ cdef void *apply_dg_edge_aug(void *parent, void *aug, void *child, int *degree, cdef void *allocate_dg_edge(int n, bint loops) noexcept: r""" - Allocates an object for this augmentation scheme. + Allocate an object for this augmentation scheme. """ cdef GraphStruct GS cdef DenseGraph G @@ -1187,7 +1187,7 @@ cdef void *allocate_dg_edge(int n, bint loops) noexcept: cdef void free_dg_edge(void *child) noexcept: r""" - Deallocates an object for this augmentation scheme. + Deallocate an object for this augmentation scheme. """ cdef GraphStruct GS = child sig_free(GS.scratch) @@ -1196,9 +1196,9 @@ cdef void free_dg_edge(void *child) noexcept: cdef void *canonical_dg_edge_parent(void *child, void *parent, int *permutation, int *degree, bint *mem_err) noexcept: r""" - Applies ``permutation`` to ``child``, determines an arbitrary parent by - deleting the lexicographically largest edge, applies the inverse of - ``permutation`` to the result and stores the result in ``parent``. + Apply ``permutation`` to ``child``, determine an arbitrary parent by + deleting the lexicographically largest edge, apply the inverse of + ``permutation`` to the result and store the result in ``parent``. """ cdef GraphStruct GS_par = parent, GS = child cdef DenseGraph DG_par = GS_par.G, DG = GS.G @@ -1226,7 +1226,7 @@ cdef void *canonical_dg_edge_parent(void *child, void *parent, int *permutation, cdef iterator *allocate_dg_edge_gen(int degree, int depth, bint loops) noexcept: r""" - Allocates the iterator for generating graphs. + Allocate the iterator for generating graphs. """ cdef iterator *dg_edge_gen = sig_malloc(sizeof(iterator)) cdef canonical_generator_data *cgd = allocate_cgd(depth, degree) @@ -1256,7 +1256,7 @@ cdef iterator *allocate_dg_edge_gen(int degree, int depth, bint loops) noexcept: cdef void free_dg_edge_gen(iterator *dg_edge_gen) noexcept: r""" - Deallocates the iterator for generating graphs. + Deallocate the iterator for generating graphs. """ cdef canonical_generator_data *cgd = dg_edge_gen.data deallocate_cgd(cgd) @@ -1431,7 +1431,7 @@ cdef void *apply_dg_vert_aug(void *parent, void *aug, void *child, int *degree, cdef void *allocate_dg_vert(int n, int depth) noexcept: r""" - Allocates an object for this augmentation scheme. + Allocate an object for this augmentation scheme. """ cdef GraphStruct GS cdef DenseGraph G @@ -1457,7 +1457,7 @@ cdef void *allocate_dg_vert(int n, int depth) noexcept: cdef void free_dg_vert(void *child) noexcept: r""" - Deallocates an object for this augmentation scheme. + Deallocate an object for this augmentation scheme. """ cdef GraphStruct GS = child sig_free(GS.scratch) @@ -1466,9 +1466,9 @@ cdef void free_dg_vert(void *child) noexcept: cdef void *canonical_dg_vert_parent(void *child, void *parent, int *permutation, int *degree, bint *mem_err) noexcept: r""" - Applies ``permutation`` to ``child``, determines an arbitrary parent by - deleting the lexicographically largest vertex, applies the inverse of - ``permutation`` to the result and stores the result in ``parent``. + Apply ``permutation`` to ``child``, determines an arbitrary parent by + deleting the lexicographically largest vertex, apply the inverse of + ``permutation`` to the result and store the result in ``parent``. """ cdef GraphStruct GS_par = parent, GS = child cdef DenseGraph DG_par = GS_par.G, DG = GS.G @@ -1488,7 +1488,7 @@ cdef void *canonical_dg_vert_parent(void *child, void *parent, int *permutation, cdef iterator *allocate_dg_vert_gen(int degree, int depth) noexcept: r""" - Allocates the iterator for generating graphs. + Allocate the iterator for generating graphs. """ cdef iterator *dg_vert_gen = sig_malloc(sizeof(iterator)) cdef canonical_generator_data *cgd = allocate_cgd(depth, degree) @@ -1529,7 +1529,7 @@ cdef iterator *allocate_dg_vert_gen(int degree, int depth) noexcept: cdef void free_dg_vert_gen(iterator *dg_vert_gen) noexcept: r""" - Deallocates the iterator for generating graphs. + Deallocate the iterator for generating graphs. """ cdef canonical_generator_data *cgd = dg_vert_gen.data deallocate_cgd(cgd) diff --git a/src/sage/groups/perm_gps/partn_ref/refinement_lists.pxd b/src/sage/groups/perm_gps/partn_ref/refinement_lists.pxd index e7b6cf42f6d..43cf567dc40 100644 --- a/src/sage/groups/perm_gps/partn_ref/refinement_lists.pxd +++ b/src/sage/groups/perm_gps/partn_ref/refinement_lists.pxd @@ -11,7 +11,6 @@ from sage.groups.perm_gps.partn_ref.data_structures cimport * - # name of the three functions to customize cdef int refine_list(PartitionStack *, void *, int *, int) noexcept cdef int compare_lists(int *, int *, void *, void *, int) noexcept diff --git a/src/sage/groups/perm_gps/partn_ref/refinement_matrices.pyx b/src/sage/groups/perm_gps/partn_ref/refinement_matrices.pyx index c8d4f86a8f1..7fa11c28343 100644 --- a/src/sage/groups/perm_gps/partn_ref/refinement_matrices.pyx +++ b/src/sage/groups/perm_gps/partn_ref/refinement_matrices.pyx @@ -110,7 +110,6 @@ cdef class MatrixStruct: 00011 01100 4 - """ print(self.matrix) print("") @@ -127,13 +126,12 @@ cdef class MatrixStruct: def run(self, partition=None): """ Perform the canonical labeling and automorphism group computation, - storing results to self. + storing results to ``self``. INPUT: - partition -- an optional list of lists partition of the columns. - - Default is the unit partition. + - ``partition`` -- an optional list of lists partition of the columns; + default is the unit partition. EXAMPLES:: @@ -153,7 +151,6 @@ cdef class MatrixStruct: sage: M = MatrixStruct(matrix(GF(3),[[0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2]])) sage: M.automorphism_group()[1] == factorial(14) True - """ cdef int i, n = self.degree cdef PartitionStack *part @@ -176,7 +173,7 @@ cdef class MatrixStruct: def automorphism_group(self): """ - Returns a list of generators of the automorphism group, along with its + Return a list of generators of the automorphism group, along with its order and a base for which the list of generators is a strong generating set. @@ -189,7 +186,6 @@ cdef class MatrixStruct: sage: M = MatrixStruct(matrix(GF(3),[[0,1,2],[0,2,1]])) sage: M.automorphism_group() ([[0, 2, 1]], 2, [1]) - """ cdef int i, j cdef list generators, base @@ -206,7 +202,7 @@ cdef class MatrixStruct: def canonical_relabeling(self): """ - Returns a canonical relabeling (in list permutation format). + Return a canonical relabeling (in list permutation format). For more examples, see self.run(). @@ -217,7 +213,6 @@ cdef class MatrixStruct: sage: M = MatrixStruct(matrix(GF(3),[[0,1,2],[0,2,1]])) sage: M.canonical_relabeling() [0, 1, 2] - """ cdef int i if self.output is NULL: @@ -226,7 +221,7 @@ cdef class MatrixStruct: def is_isomorphic(self, MatrixStruct other): """ - Calculate whether self is isomorphic to other. + Calculate whether ``self`` is isomorphic to ``other``. EXAMPLES:: @@ -235,7 +230,6 @@ cdef class MatrixStruct: sage: N = MatrixStruct(Matrix(GF(11), [[0,1,0,2,0,3],[1,0,2,0,3,0]])) sage: M.is_isomorphic(N) [0, 2, 4, 1, 3, 5] - """ cdef int i, n = self.degree cdef int *output @@ -304,8 +298,8 @@ cdef bint all_matrix_children_are_equivalent(PartitionStack *PS, void *S) noexce def random_tests(n=10, nrows_max=50, ncols_max=50, nsymbols_max=10, perms_per_matrix=5, density_range=(.1,.9)): """ - Tests to make sure that C(gamma(M)) == C(M) for random permutations gamma - and random matrices M, and that M.is_isomorphic(gamma(M)) returns an + Test to make sure that ``C(gamma(M)) == C(M)`` for random permutations ``gamma`` + and random matrices ``M``, and that ``M.is_isomorphic(gamma(M))`` returns an isomorphism. INPUT: diff --git a/src/sage/groups/perm_gps/partn_ref/refinement_python.pyx b/src/sage/groups/perm_gps/partn_ref/refinement_python.pyx index 23d5e7576c7..96e3497fe42 100644 --- a/src/sage/groups/perm_gps/partn_ref/refinement_python.pyx +++ b/src/sage/groups/perm_gps/partn_ref/refinement_python.pyx @@ -12,12 +12,11 @@ in pure Python, and still use the Cython algorithms. Experimentation with specific partition backtrack implementations no longer requires compilation, as the input functions can be dynamically changed at runtime. -NOTE: - -This is not intended for production quality implementations of partition -refinement, but instead for experimentation, learning, and use of the Python -debugger. +.. NOTE:: + This is not intended for production quality implementations of partition + refinement, but instead for experimentation, learning, and use of the + Python debugger. """ #***************************************************************************** @@ -53,7 +52,6 @@ cdef class PythonPartitionStack: sage: from sage.groups.perm_gps.partn_ref.refinement_python import PythonPartitionStack sage: P = PythonPartitionStack(7) # implicit doctest - """ self.c_ps = PS_new(n, 1) @@ -66,13 +64,12 @@ cdef class PythonPartitionStack: sage: from sage.groups.perm_gps.partn_ref.refinement_python import PythonPartitionStack sage: P = PythonPartitionStack(7) sage: del(P) # implicit doctest - """ PS_dealloc(self.c_ps) def __repr__(self): """ - Returns a string representing the stack. + Return a string representing the stack. EXAMPLES:: @@ -80,13 +77,12 @@ cdef class PythonPartitionStack: sage: P = PythonPartitionStack(7) sage: P # implicit doctest PythonPartitionStack of degree 7 and depth 0. - """ return "PythonPartitionStack of degree %d and depth %d."%(self.c_ps.degree, self.c_ps.depth) def display(self): """ - Prints a representation of the stack. + Print a representation of the stack. EXAMPLES:: @@ -98,13 +94,12 @@ cdef class PythonPartitionStack: sage: P.display() (0 1 2 3 4 5 6) (0 1 2|3 4 5 6) - """ PS_print(self.c_ps) def is_discrete(self): """ - Returns whether the deepest partition consists only of singleton cells. + Return whether the deepest partition consists only of singleton cells. EXAMPLES:: @@ -116,13 +111,12 @@ cdef class PythonPartitionStack: [None, None, None, None, None, None, None] sage: P.is_discrete() True - """ return PS_is_discrete(self.c_ps) def num_cells(self): """ - Returns the number of cells in the deepest partition. + Return the number of cells in the deepest partition. EXAMPLES:: @@ -130,13 +124,12 @@ cdef class PythonPartitionStack: sage: P = PythonPartitionStack(7) sage: P.num_cells() 1 - """ return PS_num_cells(self.c_ps) def move_min_to_front(self, int start, int end): """ - Makes sure that the first element of the segment of entries i with + Make sure that the first element of the segment of entries i with start <= i <= end is minimal. EXAMPLES:: @@ -150,7 +143,6 @@ cdef class PythonPartitionStack: sage: P.move_min_to_front(0,1) sage: P.display() (0 1 2 3 4 5 6) - """ PS_move_min_to_front(self.c_ps, start, end) @@ -166,7 +158,6 @@ cdef class PythonPartitionStack: (0 1 2 3 4 5 6) sage: Q.display() (0 1 2 3 4 5 6) - """ cdef PythonPartitionStack cpy cpy = PythonPartitionStack(self.c_ps.degree) @@ -175,7 +166,7 @@ cdef class PythonPartitionStack: def clear(self): """ - Sets the current partition to the first shallower one, i.e. forgets about + Set the current partition to the first shallower one, i.e. forget about boundaries between cells that are new to the current level. EXAMPLES:: @@ -192,13 +183,12 @@ cdef class PythonPartitionStack: sage: P.display() (0 1 2 3 4 5 6) (0 1 2 3 4 5 6) - """ PS_clear(self.c_ps) def entries(self): """ - Returns the entries array as a Python list of ints. + Return the entries array as a Python list of ints. EXAMPLES:: @@ -208,14 +198,13 @@ cdef class PythonPartitionStack: [0, 1, 2, 3, 4, 5, 6] sage: P.levels() [7, 7, 7, 7, 7, 7, -1] - """ cdef int i return [self.c_ps.entries[i] for i from 0 <= i < self.c_ps.degree] def set_entry(self, int i, int entry): """ - Sets the ith entry of the entries array to entry. + Set the ith entry of the entries array to entry. EXAMPLES:: @@ -225,13 +214,12 @@ cdef class PythonPartitionStack: sage: P.set_entry(0,1) sage: P.display() (1 0 2 3 4 5 6) - """ self.c_ps.entries[i] = entry def get_entry(self, int i): """ - Gets the ith entry of the entries array. + Get the ith entry of the entries array. EXAMPLES:: @@ -239,7 +227,6 @@ cdef class PythonPartitionStack: sage: P = PythonPartitionStack(7) sage: P.get_entry(0) 0 - """ return self.c_ps.entries[i] @@ -255,13 +242,12 @@ cdef class PythonPartitionStack: [0, 1, 2, 3, 4, 5, 6] sage: P.levels() [7, 7, 7, 7, 7, 7, -1] - """ return [self.c_ps.levels[i] for i from 0 <= i < self.c_ps.degree] def set_level(self, int i, int level): """ - Sets the ith entry of the levels array to entry. + Set the ith entry of the levels array to entry. EXAMPLES:: @@ -273,13 +259,12 @@ cdef class PythonPartitionStack: sage: P.display() (0 1 2 3 4 5 6) (0 1 2|3 4 5 6) - """ self.c_ps.levels[i] = level def get_level(self, int i): """ - Gets the ith entry of the levels array. + Get the ith entry of the levels array. EXAMPLES:: @@ -287,13 +272,12 @@ cdef class PythonPartitionStack: sage: P = PythonPartitionStack(7) sage: P.get_level(0) 7 - """ return self.c_ps.levels[i] def depth(self, new=None): """ - Returns the depth of the deepest partition in the stack, setting it to + Return the depth of the deepest partition in the stack, setting it to new if new is not None. EXAMPLES:: @@ -302,7 +286,6 @@ cdef class PythonPartitionStack: sage: P = PythonPartitionStack(7) sage: P.depth() 0 - """ if new is not None: self.c_ps.depth = new @@ -310,7 +293,7 @@ cdef class PythonPartitionStack: def degree(self, new=None): """ - Returns the degree of the partition stack, setting it to + Return the degree of the partition stack, setting it to new if new is not None. EXAMPLES:: @@ -319,7 +302,6 @@ cdef class PythonPartitionStack: sage: P = PythonPartitionStack(7) sage: P.degree() 7 - """ if new is not None: self.c_ps.degree = new @@ -340,7 +322,6 @@ cdef class PythonPartitionStack: [[0, 1, 2, 3, 4, 5, 6]] sage: P.partition(1) [[0, 1, 2], [3, 4, 5, 6]] - """ cdef int i cdef list partition = [], cell = [] @@ -380,7 +361,6 @@ class PythonObjectWrapper: sage: P.cs_fn - """ self.degree = degree self.obj = obj @@ -424,13 +404,13 @@ def aut_gp_and_can_lab_python(S, partition, n, compare_structures, canonical_label, base, order): """ - Calls the automorphism group and canonical label function. + Call the automorphism group and canonical label function. INPUT: - S -- the object to examine - partition -- an ordered partition, as a list of lists - n -- the degree of the automorphism group to be computed + - ``S`` -- the object to examine + - ``partition`` -- an ordered partition, as a list of lists + - ``n`` -- the degree of the automorphism group to be computed :: @@ -513,11 +493,10 @@ def double_coset_python(S1, S2, partition1, ordering2, n, INPUT: - S1, S2 -- the objects to examine - partition1 -- an ordered partition, as a list of lists - ordering2 -- represents a partition of the points of S2, - as a relabeling of partition1 - n -- the degree + - ``S1``, ``S2`` -- the objects to examine + - ``partition1`` -- an ordered partition, as a list of lists + - ``ordering2`` -- represents a partition of the points of ``S2``, as a relabeling of ``partition1`` + - ``n`` -- the degree :: 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 6ccc0b28c45..0f951e7e097 100644 --- a/src/sage/groups/perm_gps/partn_ref/refinement_sets.pyx +++ b/src/sage/groups/perm_gps/partn_ref/refinement_sets.pyx @@ -183,9 +183,9 @@ def set_stab_py(generators, sett, relab=False): cdef aut_gp_and_can_lab *set_stab(StabilizerChain *supergroup, subset *sett, bint relab) noexcept: r""" - Computes the set stabilizer of ``sett`` within ``supergroup``. (Note that + Compute the set stabilizer of ``sett`` within ``supergroup``. (Note that ``set`` is a reserved Python keyword.) If ``relab`` is specified then - computes the canonical label of the set under the action of the group. + compute the canonical label of the set under the action of the group. """ cdef aut_gp_and_can_lab *output cdef int n = supergroup.degree @@ -202,7 +202,7 @@ cdef aut_gp_and_can_lab *set_stab(StabilizerChain *supergroup, subset *sett, bin def sets_isom_py(generators, set1, set2): r""" - Computes whether ``set1`` and ``set2`` are isomorphic under the action of + Compute whether ``set1`` and ``set2`` are isomorphic under the action of the group generated by the generators given in list form. EXAMPLES:: @@ -486,7 +486,7 @@ cdef int compare_sets(int *gamma_1, int *gamma_2, void *S1, void *S2, int degree cdef void *allocate_subset(int n) noexcept: r""" - Allocates a subset struct of degree n. + Allocate a subset struct of degree n. """ cdef subset *set1 = sig_malloc(sizeof(subset)) cdef int *scratch = sig_malloc((3*n+1) * sizeof(int)) @@ -505,7 +505,7 @@ cdef void *allocate_subset(int n) noexcept: cdef void free_subset(void *child) noexcept: r""" - Deallocates a subset struct. + Deallocate a subset struct. """ cdef subset *set1 = child if set1 is not NULL: @@ -515,7 +515,7 @@ cdef void free_subset(void *child) noexcept: cdef void *allocate_sgd(int degree) noexcept: r""" - Allocates the data part of an iterator which generates augmentations, i.e., + Allocate the data part of an iterator which generates augmentations, i.e., elements to add to the set. """ cdef subset_generator_data *sgd = sig_malloc(sizeof(subset_generator_data)) @@ -527,7 +527,7 @@ cdef void *allocate_sgd(int degree) noexcept: cdef void deallocate_sgd(void *data) noexcept: r""" - Deallocates the data part of the augmentation iterator. + Deallocate the data part of the augmentation iterator. """ cdef subset_generator_data *sgd = data if sgd is not NULL: @@ -536,7 +536,7 @@ cdef void deallocate_sgd(void *data) noexcept: cdef void *subset_generator_next(void *data, int *degree, bint *mem_err) noexcept: r""" - Returns the next element to consider adding to the set. + Return the next element to consider adding to the set. """ cdef subset_generator_data *sgd = data while True: @@ -552,7 +552,7 @@ cdef void *subset_generator_next(void *data, int *degree, bint *mem_err) noexcep cdef int generate_child_subsets(void *S, aut_gp_and_can_lab *group, iterator *child_iterator) noexcept: r""" - Sets up an iterator of augmentations, i.e., elements to add to the given set. + Set up an iterator of augmentations, i.e., elements to add to the given set. """ cdef subset *subset1 = S cdef int i, j, n = group.group.degree @@ -572,7 +572,7 @@ cdef int generate_child_subsets(void *S, aut_gp_and_can_lab *group, iterator *ch cdef void *apply_subset_aug(void *parent, void *aug, void *child, int *degree, bint *mem_err) noexcept: r""" - Adds the element represented by ``aug`` to ``parent``, storing the result to + Add the element represented by ``aug`` to ``parent``, storing the result to ``child``. """ cdef subset *set1 = child @@ -589,7 +589,7 @@ cdef void free_subset_aug(void *aug) noexcept: cdef void *canonical_set_parent(void *child, void *parent, int *permutation, int *degree, bint *mem_err) noexcept: r""" - Determines the canonical parent of the set ``child`` by applying + Determine the canonical parent of the set ``child`` by applying ``permutation``, deleting the largest element in lexicographic order, and storing the result to ``parent``. """ @@ -619,7 +619,7 @@ cdef void *canonical_set_parent(void *child, void *parent, int *permutation, int cdef iterator *allocate_subset_gen(int degree, int max_size) noexcept: r""" - Allocates the generator of subsets. + Allocate the generator of subsets. """ cdef iterator *subset_gen = sig_malloc(sizeof(iterator)) if subset_gen is not NULL: @@ -630,7 +630,7 @@ cdef iterator *allocate_subset_gen(int degree, int max_size) noexcept: cdef int allocate_subset_gen_2(int degree, int max_size, iterator *it) noexcept: r""" - Given an already allocated iterator, allocates the generator of subsets. + Given an already allocated iterator, allocate the generator of subsets. """ cdef canonical_generator_data *cgd = allocate_cgd(max_size + 1, degree) if cgd is NULL: @@ -656,7 +656,7 @@ cdef int allocate_subset_gen_2(int degree, int max_size, iterator *it) noexcept: cdef void free_subset_gen(iterator *subset_gen) noexcept: r""" - Frees the iterator of subsets. + Free the iterator of subsets. """ if subset_gen is NULL: return @@ -666,7 +666,7 @@ cdef void free_subset_gen(iterator *subset_gen) noexcept: cdef iterator *setup_set_gen(iterator *subset_gen, int degree, int max_size) noexcept: r""" - Initiates the iterator of subsets. + Initiate the iterator of subsets. """ cdef subset *empty_set cdef iterator *subset_iterator = setup_canonical_generator(degree, @@ -694,11 +694,11 @@ def sets_modulo_perm_group(list generators, int max_size, INPUT: - - ``generators`` - (list of lists) list of generators in list form - - ``max_size`` - (int) maximum size of subsets to be generated - - ``indicate_mem_err`` - (bool) whether to raise an error - if we run out of memory, or simply append a MemoryError - instance to the end of the output + - ``generators`` -- (list of lists) list of generators in list form + - ``max_size`` -- (int) maximum size of subsets to be generated + - ``indicate_mem_err`` -- (bool) whether to raise an error + if we run out of memory, or simply append a :class:`MemoryError` + instance to the end of the output EXAMPLES:: 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 261d1987897..ff54ecdb023 100644 --- a/src/sage/groups/perm_gps/partn_ref2/refinement_generic.pyx +++ b/src/sage/groups/perm_gps/partn_ref2/refinement_generic.pyx @@ -1,5 +1,5 @@ r""" -Automorphism groups and canonical labels. +Automorphism groups and canonical labels For details see section 3 of [Feu2013]_. @@ -252,7 +252,6 @@ cdef tuple PS_refinement(PartitionStack * part, long *refine_vals, long *best, j += 1 - loc_begin = i + 1 i += 1 return (True, newly_fixed) @@ -455,7 +454,7 @@ cdef class LabelledBranching: cdef class PartitionRefinement_generic: r""" - Implements the partition and refinement framework for + Implement the partition and refinement framework for group actions `G \rtimes S_n` on `X^n` as described in :mod:`sage.groups.perm_gps.partn_ref2.refinement_generic`. """ @@ -495,17 +494,15 @@ cdef class PartitionRefinement_generic: ##################################################################### cdef bint _inner_min_(self, int pos, bint * inner_group_changed) noexcept: """ - Minimize the node by the action of the inner group on the i-th position. + Minimize the node by the action of the inner group on the ith position. INPUT: - - `pos` - A position in `range(self.n)` - - `inner_group_changed` - will be set to true if `G_y` got smaller - - OUTPUT: + - ``pos`` -- A position in ``range(self.n)`` + - ``inner_group_changed`` -- will be set to ``True`` if `G_y` got smaller - - `True` if and only if the actual node compares less or equal to - the candidate for the canonical form. + OUTPUT: ``True`` if and only if the actual node compares less or equal + to the candidate for the canonical form. """ raise NotImplementedError @@ -517,10 +514,8 @@ cdef class PartitionRefinement_generic: to a smaller subgroup of `S_n`. This function also has to take care on ``self._is_candidate_initialized``. - OUTPUT: - - - `False` only if the actual node compares larger than the candidate - for the canonical form. + OUTPUT: ``False`` only if the actual node compares larger than the + candidate for the canonical form """ raise NotImplementedError @@ -597,7 +592,7 @@ cdef class PartitionRefinement_generic: def get_autom_order_permutation(self): r""" - Return the order of the automorphism group we have computes + Return the order of the automorphism group we have computed. EXAMPLES:: @@ -874,7 +869,7 @@ cdef class PartitionRefinement_generic: cdef void _latex_act_node(self, str comment="", int printlvl=0) noexcept: r""" Append the actual node as a string of latex-commands to - ``self._latex_debug_string`` + ``self._latex_debug_string``. """ raise NotImplementedError # must be implemented by derived classes diff --git a/src/sage/groups/perm_gps/permgroup.py b/src/sage/groups/perm_gps/permgroup.py index f001126ed84..5b76232e336 100644 --- a/src/sage/groups/perm_gps/permgroup.py +++ b/src/sage/groups/perm_gps/permgroup.py @@ -119,12 +119,12 @@ - Dixon, J. and Mortimer, B., Permutation Groups, Springer-Verlag, Berlin/New York, 1996. -.. note:: +.. NOTE:: - Though Suzuki groups are okay, Ree groups should *not* be wrapped - as permutation groups - the construction is too slow - unless (for - small values or the parameter) they are made using explicit - generators. + Though Suzuki groups are okay, Ree groups should *not* be wrapped + as permutation groups - the construction is too slow - unless (for + small values or the parameter) they are made using explicit + generators. """ # **************************************************************************** @@ -203,6 +203,7 @@ def wrapped(self, n, p=0): return f(self, n, p=p) return wrapped + def direct_product_permgroups(P): """ Takes the direct product of the permutation groups listed in ``P``. @@ -230,6 +231,7 @@ def direct_product_permgroups(P): G = libgap.DirectProduct(*P) return PermutationGroup(gap_group=G) + def from_gap_list(G, src): r""" Convert a string giving a list of GAP permutations into a list of @@ -249,7 +251,7 @@ def from_gap_list(G, src): # src is a list of strings, each of which is a permutation of # integers in cycle notation. It may contain \n and spaces. src = [str(g)[1:].split(")(") - for g in str(src).replace(" ","").replace("\n","")[1:-2].split("),")] + for g in str(src).replace(" ", "").replace("\n", "")[1:-2].split("),")] # src is a list of list of strings. Each string is a list of # integers separated by ',' @@ -260,6 +262,7 @@ def from_gap_list(G, src): # src is now a list of group elements return src + def PermutationGroup(gens=None, *args, **kwds): """ Return the permutation group associated to `x` (typically a @@ -274,9 +277,7 @@ def PermutationGroup(gens=None, *args, **kwds): - ``canonicalize`` -- boolean (default: ``True``); if ``True``, sort generators and remove duplicates - OUTPUT: - - - a permutation group + OUTPUT: a permutation group EXAMPLES:: @@ -454,9 +455,7 @@ def __init__(self, gens=None, gap_group=None, canonicalize=True, - ``canonicalize`` -- bool (default: ``True``); if ``True``, sort generators and remove duplicates - OUTPUT: - - - A permutation group. + OUTPUT: a permutation group EXAMPLES: @@ -539,7 +538,7 @@ def __init__(self, gens=None, gap_group=None, canonicalize=True, # 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)) + domain = list(range(min([1] + domain), max([1] + domain) + 1)) if domain not in FiniteEnumeratedSets(): domain = FiniteEnumeratedSet(domain) @@ -662,9 +661,8 @@ def gap(self): compatibility and have :class:`sage.groups.libgap_morphism.GroupHomset_libgap` work for permutation groups, as well - OUTPUT: - - an instance of :class:`sage.libs.gap.element.GapElement` representing this group + OUTPUT: an instance of :class:`sage.libs.gap.element.GapElement` + representing this group EXAMPLES:: @@ -727,9 +725,7 @@ def _Hom_(self, G, category=None, check=True): - ``G`` -- group; the codomain - ``cat`` -- category - OUTPUT: - - The set of homomorphisms from ``self`` to ``G``. + OUTPUT: the set of homomorphisms from ``self`` to ``G`` EXAMPLES:: @@ -748,7 +744,7 @@ def _Hom_(self, G, category=None, check=True): def _magma_init_(self, magma): r""" - Return a string showing how to declare / initialize self in Magma. + Return a string showing how to declare / initialize ``self`` in Magma. EXAMPLES: @@ -813,7 +809,6 @@ def __richcmp__(self, right, op): False sage: G != H False - """ if not isinstance(right, PermutationGroup_generic): return NotImplemented @@ -823,7 +818,7 @@ def __richcmp__(self, right, op): gSelf = self._libgap_() gRight = right._libgap_() - if op in [op_EQ,op_NE]: + if op in [op_EQ, op_NE]: return gSelf._richcmp_(gRight, op) if gSelf.IsSubgroup(gRight): @@ -906,7 +901,7 @@ def _element_constructor_(self, x, check=True): # We check if we can lift ``x`` to ``self`` directly # so we can pass check=False for speed. if (isinstance(x_parent, PermutationGroup_subgroup) - and x_parent._ambient_group is self): + and x_parent._ambient_group is self): return self.element_class(x, self, check=False) from sage.groups.perm_gps.permgroup_named import SymmetricGroup @@ -1268,9 +1263,9 @@ def elements(SGS): else: enumeration = "depth" return iter(RecursivelyEnumeratedSet( - seeds=seeds, - successors=successors, - enumeration=enumeration)) + seeds=seeds, + successors=successors, + enumeration=enumeration)) else: raise ValueError("the input algorithm (='%s') must be 'SGS', 'BFS' or 'DFS'" % algorithm) @@ -1464,7 +1459,6 @@ def exponent(self): sage: G = AlternatingGroup(4) sage: G.exponent() 6 - """ return Integer(self._libgap_().Exponent()) @@ -1487,7 +1481,7 @@ def largest_moved_point(self): sage: G.largest_moved_point() 'e' - .. warning:: + .. WARNING:: The name of this function is not good; this function should be deprecated in term of degree:: @@ -1558,12 +1552,12 @@ def _domain_gap(self, domain=None): '[1, 2, 3, 4, 5]' """ if domain is None: - return repr(list(range(1, self.degree()+1))) - else: - try: - return repr([self._domain_to_gap[point] for point in domain]) - except KeyError: - raise ValueError("domain must be a subdomain of self.domain()") + return repr(list(range(1, self.degree() + 1))) + + try: + return repr([self._domain_to_gap[point] for point in domain]) + except KeyError: + raise ValueError("domain must be a subdomain of self.domain()") @cached_method def smallest_moved_point(self): @@ -1588,9 +1582,9 @@ def smallest_moved_point(self): p = self._libgap_().SmallestMovedPoint() return self._domain_from_gap[Integer(p)] - def representative_action(self,x,y): + def representative_action(self, x, y): r""" - Return an element of self that maps `x` to `y` if it exists. + Return an element of ``self`` that maps `x` to `y` if it exists. This method wraps the gap function ``RepresentativeAction``, which can also return elements that map a given set of points on another set of @@ -1942,7 +1936,6 @@ def base(self, seed=None): [1, 2, 3, 4, 5] sage: S.base([1,3,5,7,9,11]) # create a base for M12 with only odd integers [1, 3, 5, 7, 9] - """ if seed is None: seed = self.domain() @@ -1989,10 +1982,8 @@ def strong_generating_system(self, base_of_group=None, implementation="sage"): * ``"gap"`` -- if used, the ``base_of_group`` must be ``None`` and the computation is directly performed in GAP - OUTPUT: - - A list of lists of permutations from the group, which form a strong - generating system. + OUTPUT: a list of lists of permutations from the group, which forms a + strong generating system .. WARNING:: @@ -2430,7 +2421,6 @@ def frattini_subgroup(self): sage: G.frattini_subgroup() Subgroup generated by [()] of (Symmetric group of order 4! as a permutation group) - """ return self.subgroup(gap_group=self._libgap_().FrattiniSubgroup()) @@ -2522,7 +2512,6 @@ def intersection(self, other): Traceback (most recent call last): ... TypeError: junk is not a permutation group - """ from sage.categories.finite_permutation_groups import FinitePermutationGroups @@ -2878,7 +2867,6 @@ def semidirect_product(self, N, mapping, check=True): AUTHOR: - Kevin Halasz (2012-8-12) - """ if check: @@ -2924,10 +2912,8 @@ def holomorph(self): See :wikipedia:`Holomorph (mathematics)` - OUTPUT: - - Return the holomorph of a given group as permutation group - via a wrapping of GAP's semidirect product function. + OUTPUT: the holomorph of a given group as permutation group + via a wrapping of GAP's semidirect product function EXAMPLES: @@ -3007,9 +2993,7 @@ def _subgroup_constructor(self, libgap_group): - ``libgap_group`` -- an instance of :class:`sage.libs.gap.element.GapElement` representing a GAP group whose generators belong to ``self.gap()`` - OUTPUT: - - The corresponding subgroup of ``self`` as an instance of this class + OUTPUT: the corresponding subgroup of ``self`` as an instance of this class EXAMPLES:: @@ -3042,10 +3026,9 @@ def as_finitely_presented_group(self, reduced=False): is called, attempting to simplify the presentation of the finitely presented group to be returned. - OUTPUT: - - Finite presentation of ``self``, obtained by taking the image - of the isomorphism returned by the GAP function ``IsomorphismFpGroupByGenerators``. + OUTPUT: finite presentation of ``self``, obtained by taking the image + of the isomorphism returned by the GAP function + ``IsomorphismFpGroupByGenerators`` ALGORITHM: @@ -3192,7 +3175,7 @@ def commutator(self, other=None): The two groups need only be permutation groups, there is no notion of requiring them to explicitly be subgroups of some other group. - .. note:: + .. NOTE:: For the identical statement, the generators of the returned group can vary from one execution to the next. @@ -3508,7 +3491,6 @@ def character_table(self): AUTHORS: - David Joyner and William Stein (2006-01-04) - """ G = self._libgap_() cl = G.ConjugacyClasses() @@ -3709,12 +3691,9 @@ def subgroups(self): - Rob Beezer (2011-01-24) """ - all_sg = [] ccs = self._libgap_().ConjugacyClassesSubgroups() - for cc in ccs: - for h in cc.Elements(): - all_sg.append(self.subgroup(gap_group=h)) - return all_sg + return [self.subgroup(gap_group=h) for cc in ccs + for h in cc.Elements()] @cached_method def _regular_subgroup_gap(self): @@ -3734,7 +3713,6 @@ def _regular_subgroup_gap(self): sage: S4._regular_subgroup_gap() # random ConjugacyClassSubgroups(SymmetricGroup( [ 1 .. 4 ] ),Group( [ (1,4)(2,3), (1,3)(2,4) ] )) - """ filt = libgap.eval('x -> IsRegular(Representative(x), [1..{}])'.format( self.degree())) @@ -3774,7 +3752,6 @@ def has_regular_subgroup(self, return_group=False): sage: G = graphs.PetersenGraph().automorphism_group() sage: G.has_regular_subgroup() False - """ b = False G = None @@ -3909,7 +3886,7 @@ def cosets(self, S, side='right'): the list of cosets, due to the ordering of the representatives). See below for examples of this. - .. note:: + .. NOTE:: This is a naive implementation intended for instructional purposes, and hence is slow for larger groups. Sage and GAP @@ -4166,7 +4143,7 @@ def maximal_normal_subgroups(self): return [self.subgroup(gap_group=gap_subgroup) for gap_subgroup in self._libgap_().MaximalNormalSubgroups()] - ###################### Boolean tests ##################### + # ##################### Boolean tests ##################### def is_abelian(self): """ @@ -4237,15 +4214,11 @@ def isomorphism_to(self, right): INPUT: - - ``self`` -- this group - ``right`` -- a permutation group - - OUTPUT: - - - ``None`` or a morphism of permutation groups. + OUTPUT: ``None``, or a morphism of permutation groups EXAMPLES:: @@ -4290,16 +4263,12 @@ def is_isomorphic(self, right): INPUT: - - ``self`` -- this group - ``right`` -- a permutation group - - OUTPUT: - - - boolean; ``True`` if ``self`` and ``right`` are isomorphic groups; - ``False`` otherwise. + OUTPUT: boolean; ``True`` if ``self`` and ``right`` are isomorphic + groups; ``False`` otherwise EXAMPLES:: @@ -4392,7 +4361,6 @@ def is_pgroup(self): sage: G = PermutationGroup(['(1,2,3,4,5)']) sage: G.is_pgroup() True - """ return bool(self._libgap_().IsPGroup()) @@ -4425,7 +4393,6 @@ def is_simple(self): sage: G = PermutationGroup(['(1,2,3)(4,5)']) sage: G.is_simple() False - """ return bool(self._libgap_().IsSimpleGroup()) @@ -4610,7 +4577,6 @@ def is_primitive(self, domain=None): sage: G = PermutationGroup([[(1,2,3,4)],[(2,4)]]) sage: G.is_primitive([1,2,3]) False - """ #If the domain is not a subset of self.domain(), then the #action isn't primitive. @@ -4646,7 +4612,6 @@ def is_semi_regular(self, domain=None): True sage: G.is_semi_regular(G.non_fixed_points()) False - """ try: domain = libgap.eval(self._domain_gap(domain)) @@ -4679,7 +4644,6 @@ def is_regular(self, domain=None): True sage: G.is_regular(G.non_fixed_points()) False - """ try: domain = libgap.eval(self._domain_gap(domain)) @@ -4715,7 +4679,7 @@ def normalizes(self, other): """ return bool(self._libgap_().IsNormal(other)) - ############## Series ###################### + # ############# Series ###################### def composition_series(self): """ @@ -4857,7 +4821,8 @@ def molien_series(self): sage: PG.molien_series() == PG1.molien_series()*(1-x)^2 True """ - pi = self._libgap_().PermutationCharacter(list(self.domain()),libgap.OnPoints) + pi = self._libgap_().PermutationCharacter(list(self.domain()), + libgap.OnPoints) M = pi.MolienSeries() R = QQ['x'] @@ -4915,7 +4880,6 @@ def poincare_series(self, p=2, n=10): AUTHORS: - David Joyner and Graham Ellis - """ load_hap() from sage.arith.misc import is_prime @@ -4956,7 +4920,6 @@ def sylow_subgroup(self, p): sage: PSL(10,2).sylow_subgroup(7) Subgroup generated by... - """ return self.subgroup(gap_group=self._libgap_().SylowSubgroup(p)) @@ -5158,7 +5121,6 @@ def __richcmp__(self, other, op): True sage: G.subgroup([G((1,2,3))]) == G.subgroup([G((1,3,2))]) True - """ if self is other: return rich_to_bool(op, 0) @@ -5217,7 +5179,7 @@ def _latex_(self): gens = '\\langle ' + \ ', '.join([x._latex_() for x in self.gens()]) + ' \\rangle' return '\\hbox{Subgroup } %s \\hbox{ of } %s' % \ - (gens, self.ambient_group()._latex_()) + (gens, self.ambient_group()._latex_()) def ambient_group(self): """ @@ -5253,7 +5215,6 @@ def is_normal(self, other=None): (Symmetric group of order 3! as a permutation group) sage: H.is_normal() True - """ if other is None: other = self.ambient_group() @@ -5263,6 +5224,7 @@ def is_normal(self, other=None): # Allow for subclasses to use a different subgroup class PermutationGroup_generic.Subgroup = PermutationGroup_subgroup + class PermutationGroup_action(PermutationGroup_generic): """ A permutation group given by a finite group action. @@ -5314,9 +5276,7 @@ def __init__(self, gens, action, domain, gap_group=None, category=None, canonica - ``canonicalize`` -- bool (default: ``True``); if ``True``, sort generators and remove duplicates - OUTPUT: - - - A finite group action given as a permutation group. + OUTPUT: a finite group action given as a permutation group. EXAMPLES:: @@ -5324,7 +5284,6 @@ def __init__(self, gens, action, domain, gap_group=None, category=None, canonica sage: G = PermutationGroup(action=a, domain=range(7)) # needs sage.combinat sage: G.orbits() # needs sage.combinat ((0,), (1, 2, 4), (3, 6, 5)) - """ from sage.combinat.cyclic_sieving_phenomenon import orbit_decomposition from sage.sets.disjoint_set import DisjointSet diff --git a/src/sage/groups/perm_gps/permgroup_element.pyx b/src/sage/groups/perm_gps/permgroup_element.pyx index 0d80d04a8af..c2a462d9727 100644 --- a/src/sage/groups/perm_gps/permgroup_element.pyx +++ b/src/sage/groups/perm_gps/permgroup_element.pyx @@ -794,7 +794,7 @@ cdef class PermutationGroupElement(MultiplicativeGroupElement): def __reduce__(self): r""" - Returns a function and its arguments needed to create this + Return a function and its arguments needed to create this permutation group element. This is used in pickling. EXAMPLES:: @@ -899,7 +899,7 @@ cdef class PermutationGroupElement(MultiplicativeGroupElement): def _gap_init_(self): r""" - Returns a GAP string representation for this + Return a GAP string representation for this PermutationGroupElement. EXAMPLES:: @@ -961,7 +961,7 @@ cdef class PermutationGroupElement(MultiplicativeGroupElement): def __getitem__(self, i): r""" Return the ``i``-th permutation cycle in the disjoint cycle - representation of self. + representation of ``self``. INPUT: @@ -1028,7 +1028,7 @@ cdef class PermutationGroupElement(MultiplicativeGroupElement): def __call__(self, i): r""" - Returns the image of the integer i under this permutation. + Return the image of the integer i under this permutation. Alternately, if i is a list, tuple or string, returns the result of self acting on i. @@ -1091,7 +1091,7 @@ cdef class PermutationGroupElement(MultiplicativeGroupElement): cpdef list _act_on_list_on_position(self, list x): r""" - Returns the right action of ``self`` on the list ``x``. This is the + Return the right action of ``self`` on the list ``x``. This is the action on positions. EXAMPLES:: @@ -1119,7 +1119,7 @@ cdef class PermutationGroupElement(MultiplicativeGroupElement): cpdef ClonableIntArray _act_on_array_on_position(self, ClonableIntArray x): r""" - Returns the right action of ``self`` on the ClonableIntArray + Return the right action of ``self`` on the :class:`ClonableIntArray` ``x``. This is the action on positions. EXAMPLES:: @@ -1422,7 +1422,7 @@ cdef class PermutationGroupElement(MultiplicativeGroupElement): cpdef _gap_list(self): r""" - Returns this permutation in list notation compatible with the + Return this permutation in list notation compatible with the GAP numbering. EXAMPLES:: @@ -1446,7 +1446,7 @@ cdef class PermutationGroupElement(MultiplicativeGroupElement): def _gap_cycle_string(self): r""" - Returns a cycle string for this permutation compatible with + Return a cycle string for this permutation compatible with the GAP numbering. EXAMPLES:: @@ -1871,10 +1871,8 @@ cdef class PermutationGroupElement(MultiplicativeGroupElement): type should be returned as a :class:`list` or as a :class:`Partition` (default: ``False``) - OUTPUT: - - A :class:`Partition`, or :class:`list` if ``is_list`` is ``True``, - giving the cycle type of ``self`` + OUTPUT: a :class:`Partition`, or :class:`list` if ``is_list`` is + ``True``, giving the cycle type of ``self`` If speed is a concern, then ``as_list=True`` should be used. @@ -1905,13 +1903,7 @@ cdef class PermutationGroupElement(MultiplicativeGroupElement): def has_descent(self, i, side="right", positive=False): r""" - INPUT: - - - ``i`` -- an element of the index set - - ``side`` -- ``"left"`` or ``"right"`` (default: ``"right"``) - - ``positive`` -- a boolean (default: ``False``) - - Returns whether ``self`` has a left (resp. right) descent at + Return whether ``self`` has a left (resp. right) descent at position ``i``. If ``positive`` is ``True``, then test for a non descent instead. @@ -1920,6 +1912,12 @@ cdef class PermutationGroupElement(MultiplicativeGroupElement): convention. Hence, ``self`` has a left descent at position ``i`` if ``self(i) > self(i+1)``. + INPUT: + + - ``i`` -- an element of the index set + - ``side`` -- ``"left"`` or ``"right"`` (default: ``"right"``) + - ``positive`` -- a boolean (default: ``False``) + EXAMPLES:: sage: S = SymmetricGroup([1,2,3]) @@ -2124,10 +2122,10 @@ cdef class SymmetricGroupElement(PermutationGroupElement): cdef bint is_valid_permutation(int* perm, int n) noexcept: r""" - This is used in the __init__ method. + This is used in the ``__init__`` method. - Returns True iff the first n elements of perm are literally a - permutation of [0, ..., n-1]. + Return ``True`` iff the first ``n`` elements of perm are literally a + permutation of ``[0, ..., n-1]``. TESTS:: diff --git a/src/sage/groups/perm_gps/permgroup_named.py b/src/sage/groups/perm_gps/permgroup_named.py index 69014066364..c000d2cf105 100644 --- a/src/sage/groups/perm_gps/permgroup_named.py +++ b/src/sage/groups/perm_gps/permgroup_named.py @@ -157,7 +157,7 @@ def __classcall__(cls, domain): INPUT: - - ``n`` -- an integer or list or tuple thereof + - ``n`` -- integer or list or tuple thereof Calls the constructor with a tuple representing the set. @@ -191,7 +191,7 @@ def __classcall__(cls, domain): if domain < 0: raise ValueError("domain (={}) must be an integer >= 0 or a list".format(domain)) - domain = list(range(1, domain+1)) + domain = list(range(1, domain + 1)) v = FiniteEnumeratedSet(domain) else: v = domain @@ -469,17 +469,17 @@ def young_subgroup(self, comp): gens = [] pos = 0 for c in comp: - for i in range(c - 1): - gens.append(self((domain[pos + i], domain[pos + i + 1]))) + gens.extend(self((domain[pos + i], domain[pos + i + 1])) + for i in range(c - 1)) pos += c return self.subgroup(gens) def major_index(self, parameter=None): r""" - Return the *major index generating polynomial* of ``self``, - which is a gadget counting the elements of ``self`` by major - index. + Return the *major index generating polynomial* of ``self``. + + This is a gadget counting the elements of ``self`` by major index. INPUT: @@ -594,9 +594,7 @@ def conjugacy_class(self, g): - ``g`` -- a partition or an element of the symmetric group ``self`` - OUTPUT: - - A conjugacy class of a symmetric group. + OUTPUT: a conjugacy class of a symmetric group EXAMPLES:: @@ -678,7 +676,7 @@ def __init__(self, domain=None): - ``n`` -- a positive integer, or list or tuple thereof - .. note:: + .. NOTE:: This group is also available via ``groups.permutation.Alternating()``. @@ -753,7 +751,7 @@ def __init__(self, n): - ``n`` -- a positive integer - .. note:: + .. NOTE:: This group is also available via ``groups.permutation.Cyclic()``. @@ -874,7 +872,7 @@ class DiCyclicGroup(PermutationGroup_unique): `i+1` and code `a^{i}x` as the symbol `2n+i+1`. The two generators are then represented using a left regular representation. - .. note:: + .. NOTE:: This group is also available via ``groups.permutation.DiCyclic()``. @@ -1004,7 +1002,7 @@ def _repr_(self): def is_commutative(self): r""" - Return True if this group is commutative. + Return ``True`` if this group is commutative. EXAMPLES:: @@ -1016,7 +1014,7 @@ def is_commutative(self): def is_abelian(self): r""" - Return True if this group is abelian. + Return ``True`` if this group is abelian. EXAMPLES:: @@ -1033,13 +1031,11 @@ def __init__(self): The Klein 4 Group, which has order `4` and exponent `2`, viewed as a subgroup of `S_4`. - OUTPUT: - - the Klein 4 group of order 4, as a permutation group of degree 4. + OUTPUT: the Klein 4 group of order 4, as a permutation group of degree 4 - .. note:: + .. NOTE:: - This group is also available via ``groups.permutation.KleinFour()``. + This group is also available via ``groups.permutation.KleinFour()``. EXAMPLES:: @@ -1081,7 +1077,7 @@ def __init__(self, n): INPUT: - - ``n`` -- an integer among `\{1,2,3\}`. + - ``n`` -- integer among `\{1,2,3\}`. EXAMPLES:: @@ -1152,7 +1148,7 @@ class QuaternionGroup(DiCyclicGroup): See the :class:`DiCyclicGroup` class for a generalization of this construction. - .. note:: + .. NOTE:: This group is also available via ``groups.permutation.Quaternion()``. @@ -1395,13 +1391,12 @@ def __init__(self, factors): jumppoint = Integer(1) for a in simplified: # create one of the generators for the abelian group - gens.append([tuple(range(jumppoint, jumppoint+a))]) + gens.append([tuple(range(jumppoint, jumppoint + a))]) # make contribution to the generator that dihedralizes the # abelian group - for i in range(1, (a//2)+1): - if i != a-i: - genx.append((jumppoint+i, jumppoint+a-i)) - jumppoint = jumppoint + a + genx.extend((jumppoint + i, jumppoint + a - i) + for i in range(1, (a//2) + 1) if i != a - i) + jumppoint += a # If all of the direct factors are C2, then the action turning # each element into its inverse is trivial, and the # semi-direct product becomes a direct product, so we simply @@ -1419,9 +1414,7 @@ def _repr_(self): sage: G Generalized dihedral group generated by C2 x C4 x C8 """ - grouplist = [] - for n in self.factors: - grouplist.append('C{}'.format(n)) + grouplist = [f'C{n}' for n in self.factors] return 'Generalized dihedral group generated by ' + ' x '.join(grouplist) @@ -1434,9 +1427,7 @@ def __init__(self, n): - ``n`` -- a positive integer - OUTPUT: - - The dihedral group of order `2n`, as a permutation group + OUTPUT: the dihedral group of order `2n`, as a permutation group .. NOTE:: @@ -1733,7 +1724,6 @@ def __init__(self, m): AUTHOR: - Kevin Halasz (2012-8-7) - """ if not isinstance(m, Integer): raise TypeError('m must be an integer, not %s' % m) @@ -1779,13 +1769,11 @@ def __init__(self, n): - ``n`` -- a positive integer in {9, 10, 11, 12, 21, 22, 23, 24}. - OUTPUT: - - the Mathieu group of degree `n`, as a permutation group + OUTPUT: the Mathieu group of degree `n`, as a permutation group - .. note:: + .. NOTE:: - This group is also available via ``groups.permutation.Mathieu()``. + This group is also available via ``groups.permutation.Mathieu()``. EXAMPLES:: @@ -1833,13 +1821,11 @@ def __init__(self, d, n): - ``n`` -- positive integer; the index of the group in the GAP database, starting at 1 - OUTPUT: - - the `n`-th transitive group of degree `d` + OUTPUT: the `n`-th transitive group of degree `d` - .. note:: + .. NOTE:: - This group is also available via ``groups.permutation.Transitive()``. + This group is also available via ``groups.permutation.Transitive()``. EXAMPLES:: @@ -1855,7 +1841,7 @@ def __init__(self, d, n): sage: G.category() Category of finite enumerated permutation groups - .. warning:: this follows GAP's naming convention of indexing + .. WARNING:: this follows GAP's naming convention of indexing the transitive groups starting from ``1``:: sage: TransitiveGroup(5,0) @@ -1863,7 +1849,7 @@ def __init__(self, d, n): ... ValueError: index n must be in {1,..,5} - .. warning:: only transitive groups of "small" degree are + .. WARNING:: only transitive groups of "small" degree are available in GAP's database:: sage: TransitiveGroup(32,1) @@ -1936,7 +1922,7 @@ def TransitiveGroups(d=None): """ INPUT: - - ``d`` -- an integer (optional) + - ``d`` -- integer (optional) Return the set of all transitive groups of a given degree ``d`` up to isomorphisms. If ``d`` is not specified, it returns the set of all @@ -1954,7 +1940,7 @@ def TransitiveGroups(d=None): sage: TransitiveGroups() Transitive Groups - .. warning:: in practice, the database currently only contains + .. WARNING:: in practice, the database currently only contains transitive groups up to degree 31:: sage: TransitiveGroups(32).cardinality() @@ -2228,9 +2214,7 @@ class PrimitiveGroup(PermutationGroup_unique): - ``n`` -- positive integer. the index of the group in the GAP database, starting at 1 - OUTPUT: - - The ``n``-th primitive group of degree ``d``. + OUTPUT: the ``n``-th primitive group of degree ``d`` EXAMPLES:: @@ -2245,7 +2229,7 @@ class PrimitiveGroup(PermutationGroup_unique): sage: G.category() Category of finite enumerated permutation groups - .. warning:: + .. WARNING:: this follows GAP's naming convention of indexing the primitive groups starting from ``1``:: @@ -2305,10 +2289,6 @@ def _repr_(self): """ Return a string representation. - OUTPUT: - - String. - EXAMPLES:: sage: G = PrimitiveGroup(5,1); G @@ -2320,9 +2300,7 @@ def group_primitive_id(self): """ Return the index of this group in the GAP database of primitive groups. - OUTPUT: - - A positive integer, following GAP's conventions. + OUTPUT: a positive integer, following GAP's conventions EXAMPLES:: @@ -2338,7 +2316,7 @@ def PrimitiveGroups(d=None): INPUT: - - ``d`` -- an integer (optional) + - ``d`` -- integer (optional) OUTPUT: @@ -2420,9 +2398,7 @@ def _repr_(self): """ Return a string representation. - OUTPUT: - - String. + OUTPUT: string TESTS:: @@ -2439,9 +2415,7 @@ def __contains__(self, G): - `G` -- anything. - OUTPUT: - - Boolean. + OUTPUT: boolean EXAMPLES:: @@ -2497,9 +2471,7 @@ def _repr_(self): """ Return a string representation. - OUTPUT: - - String. + OUTPUT: string TESTS:: @@ -2516,9 +2488,7 @@ def __contains__(self, G): - `G` -- anything. - OUTPUT: - - Boolean. + OUTPUT: boolean EXAMPLES:: @@ -2544,7 +2514,7 @@ def __getitem__(self, n): sage: PrimitiveGroups(5)[3] AGL(1, 5) - .. warning:: + .. WARNING:: this follows GAP's naming convention of indexing the primitive groups starting from ``1``:: @@ -2571,10 +2541,8 @@ def cardinality(self): r""" Return the cardinality of ``self``. - OUTPUT: - - An integer. The number of primitive groups of a given degree - up to isomorphism. + OUTPUT: integer; the number of primitive groups of a given degree + up to isomorphism EXAMPLES:: @@ -2651,13 +2619,11 @@ def __init__(self, n, q, name='a'): - ``q`` -- prime power; the size of the ground field - ``name`` -- (default: ``'a'``) variable name of indeterminate of finite field `\GF(q)` - OUTPUT: - - PGL(`n`, `q`) + OUTPUT: PGL(`n`, `q`) - .. note:: + .. NOTE:: - This group is also available via ``groups.permutation.PGL()``. + This group is also available via ``groups.permutation.PGL()``. EXAMPLES:: @@ -2713,11 +2679,9 @@ def __init__(self, n, q, name='a'): - ``q`` -- either a prime power (the size of the ground field) or a finite field - ``name`` -- (default: ``'a'``) variable name of indeterminate of finite field `\GF(q)` - OUTPUT: - - the group PSL(`n`, `q`) + OUTPUT: the group PSL(`n`, `q`) - .. note:: + .. NOTE:: This group is also available via ``groups.permutation.PSL()``. @@ -2895,13 +2859,11 @@ def __init__(self, n, q, name='a'): - ``q`` -- prime power; the size of the ground field - ``name`` -- (default: ``'a'``) variable name of indeterminate of finite field `\GF(q)` - OUTPUT: - - PSp(`n`, `q`) + OUTPUT: PSp(`n`, `q`) - .. note:: + .. NOTE:: - This group is also available via ``groups.permutation.PSp()``. + This group is also available via ``groups.permutation.PSp()``. EXAMPLES:: @@ -2973,13 +2935,11 @@ def __init__(self, n, q, name='a'): - q -- prime power; the size of the ground field - name -- (default: 'a') variable name of indeterminate of finite field GF(q) - OUTPUT: - - PSU(n,q) + OUTPUT: PSU(n,q) - .. note:: + .. NOTE:: - This group is also available via ``groups.permutation.PSU()``. + This group is also available via ``groups.permutation.PSU()``. EXAMPLES:: @@ -3009,7 +2969,6 @@ def _repr_(self): sage: PSU(2,3) # needs sage.rings.finite_rings The projective special unitary group of degree 2 over Finite Field of size 3 - """ return "The projective special unitary group of degree %s over %s" % (self._n, self.base_ring()) @@ -3025,13 +2984,11 @@ def __init__(self, n, q, name='a'): - ``q`` -- prime power; the size of the ground field - ``name`` -- (default: ``'a'``) variable name of indeterminate of finite field `\GF(q)` - OUTPUT: + OUTPUT: PGU(`n`, `q`) - PGU(`n`, `q`) - - .. note:: + .. NOTE:: - This group is also available via ``groups.permutation.PGU()``. + This group is also available via ``groups.permutation.PGU()``. EXAMPLES:: @@ -3062,7 +3019,6 @@ def _repr_(self): sage: PGU(2,3) # needs sage.rings.finite_rings The projective general unitary group of degree 2 over Finite Field of size 3 - """ return "The projective general unitary group of degree %s over %s" % (self._n, self.base_ring()) @@ -3083,13 +3039,11 @@ def __init__(self, q, name='a'): - ``name`` -- (default: ``'a'``) variable name of indeterminate of finite field `\GF(q)` - OUTPUT: + OUTPUT: a Suzuki group - A Suzuki group. - - .. note:: + .. NOTE:: - This group is also available via ``groups.permutation.Suzuki()``. + This group is also available via ``groups.permutation.Suzuki()``. EXAMPLES:: @@ -3144,7 +3098,6 @@ def __str__(self): sage: G = SuzukiGroup(32, name='alpha') # needs sage.rings.finite_rings sage: print(G) # needs sage.rings.finite_rings The Suzuki group over Finite Field in alpha of size 2^5 - """ return "The Suzuki group over %s" % self.base_ring() diff --git a/src/sage/groups/perm_gps/symgp_conjugacy_class.py b/src/sage/groups/perm_gps/symgp_conjugacy_class.py index 23f5cdab15c..da051a72523 100644 --- a/src/sage/groups/perm_gps/symgp_conjugacy_class.py +++ b/src/sage/groups/perm_gps/symgp_conjugacy_class.py @@ -307,10 +307,8 @@ def conjugacy_class_iterator(part, S=None): - ``S`` -- (optional, default: `\{ 1, 2, \ldots, n \}`, where `n` is the size of ``part``) a set - OUTPUT: - - An iterator over the conjugacy class consisting of all - permutations of the set ``S`` whose cycle type is ``part``. + OUTPUT: an iterator over the conjugacy class consisting of all + permutations of the set ``S`` whose cycle type is ``part`` EXAMPLES:: diff --git a/src/sage/groups/semimonomial_transformations/semimonomial_transformation.pyx b/src/sage/groups/semimonomial_transformations/semimonomial_transformation.pyx index baaefa1950e..b59facdd3ce 100644 --- a/src/sage/groups/semimonomial_transformations/semimonomial_transformation.pyx +++ b/src/sage/groups/semimonomial_transformations/semimonomial_transformation.pyx @@ -30,8 +30,6 @@ with and an elementwisely defined multiplication of vectors. (The indexing of vectors is `0`-based here, so `\psi = (\psi_0, \psi_1, \ldots, \psi_{n-1})`.) - - The parent is :class:`~sage.groups.semimonomial_transformations.semimonomial_transformation_group.SemimonomialTransformationGroup`. @@ -77,7 +75,7 @@ def _is_id(f, R): def _inverse(f, R): """ - Returns the inverse to the automorphism `f` of a ring `R`. + Return the inverse to the automorphism `f` of a ring `R`. EXAMPLES:: @@ -267,8 +265,8 @@ cdef class SemimonomialTransformation(MultiplicativeGroupElement): def __reduce__(self): """ - Returns a function and its arguments needed to create this - semimonomial group element. This is used in pickling. + Return a function and its arguments needed to create this + semimonomial group element. This is used in pickling. EXAMPLES:: @@ -281,7 +279,7 @@ cdef class SemimonomialTransformation(MultiplicativeGroupElement): def get_v(self): """ - Returns the component corresponding to `{R^{\times}}^n` of ``self``. + Return the component corresponding to `{R^{\times}}^n` of ``self``. EXAMPLES:: @@ -293,7 +291,7 @@ cdef class SemimonomialTransformation(MultiplicativeGroupElement): def get_v_inverse(self): """ - Returns the (elementwise) inverse of the component corresponding to + Return the (elementwise) inverse of the component corresponding to `{R^{\times}}^n` of ``self``. EXAMPLES:: @@ -306,7 +304,7 @@ cdef class SemimonomialTransformation(MultiplicativeGroupElement): def get_perm(self): """ - Returns the component corresponding to `S_n` of ``self``. + Return the component corresponding to `S_n` of ``self``. EXAMPLES:: @@ -318,7 +316,7 @@ cdef class SemimonomialTransformation(MultiplicativeGroupElement): def get_autom(self): """ - Returns the component corresponding to `Aut(R)` of ``self``. + Return the component corresponding to `Aut(R)` of ``self``. EXAMPLES:: diff --git a/src/sage/groups/semimonomial_transformations/semimonomial_transformation_group.py b/src/sage/groups/semimonomial_transformations/semimonomial_transformation_group.py index e61c7bf64c7..50b5e5741d5 100644 --- a/src/sage/groups/semimonomial_transformations/semimonomial_transformation_group.py +++ b/src/sage/groups/semimonomial_transformations/semimonomial_transformation_group.py @@ -128,11 +128,9 @@ def __init__(self, R, len): - ``R`` -- a ring - - ``len`` -- the degree of the monomial group + - ``len`` -- the degree of the monomial group - OUTPUT: - - - the complete semimonomial group + OUTPUT: the complete semimonomial group EXAMPLES:: @@ -311,8 +309,8 @@ def gens(self) -> tuple: from sage.groups.perm_gps.permgroup_named import SymmetricGroup R = self.base_ring() l = [self(v=([R.primitive_element()] + [R.one()] * (self.degree() - 1)))] - for g in SymmetricGroup(self.degree()).gens(): - l.append(self(perm=Permutation(g))) + l.extend(self(perm=Permutation(g)) + for g in SymmetricGroup(self.degree()).gens()) if R.is_field() and not R.is_prime_field(): l.append(self(autom=R.hom([R.primitive_element()**R.characteristic()]))) return tuple(l) diff --git a/src/sage/homology/chain_complex.py b/src/sage/homology/chain_complex.py index 0d692df4709..7f7cfb5e27b 100644 --- a/src/sage/homology/chain_complex.py +++ b/src/sage/homology/chain_complex.py @@ -418,6 +418,15 @@ def _ascii_art_(self): d_2 d_1 d_0 [1] d_-1 0 <---- [0] <---- [4] <---- [2] <----- 0 [5] [3] + + TESTS: + + check that :issue:`37678` is fixed:: + + sage: C = ChainComplex(base_ring=ZZ) + sage: ascii_art(C()) + 0 + """ from sage.typeset.ascii_art import AsciiArt @@ -444,6 +453,8 @@ def vector_art(d): for n in ordered: result_ordered += arrow_art(n) + vector_art(n) result = [result_ordered] + result + if len(result) == 0: + return AsciiArt(['0']) concatenated = result[0] for r in result[1:]: concatenated += AsciiArt([' ... ']) + r @@ -465,6 +476,12 @@ def _unicode_art_(self): ⎛1⎞ d_2 d_1 ⎛4⎞ d_0 ⎜2⎟ d_-1 0 <──── (0) <──── ⎝5⎠ <──── ⎝3⎠ <───── 0 + sage: unicode_art(C()) + ⎛0⎞ + d_2 d_1 ⎛0⎞ d_0 ⎜0⎟ d_-1 + 0 <──── (0) <──── ⎝0⎠ <──── ⎝0⎠ <───── 0 + sage: unicode_art(ChainComplex()) + 0 """ from sage.typeset.unicode_art import UnicodeArt @@ -492,6 +509,8 @@ def vector_art(d): for n in ordered: result_ordered += arrow_art(n) + vector_art(n) result = [result_ordered] + result + if len(result) == 0: + return UnicodeArt([u'0']) concatenated = result[0] for r in result[1:]: concatenated += UnicodeArt([u' ... ']) + r @@ -1122,58 +1141,6 @@ def __ne__(self, other): """ return not self == other - def _homology_chomp(self, deg, base_ring, verbose, generators): - """ - Helper function for :meth:`homology`. - - This function is deprecated. - - INPUT: - - - ``deg`` -- integer (one specific homology group) or ``None`` - (all of those that can be non-zero) - - - ``base_ring`` -- the base ring (must be the integers - or a prime field) - - - ``verbose`` -- boolean, whether to print some messages - - - ``generators`` -- boolean, whether to also return generators - for homology - - EXAMPLES:: - - sage: C = ChainComplex({0: matrix(ZZ, 2, 3, [3, 0, 0, 0, 0, 0])}, base_ring=GF(2)) - sage: C._homology_chomp(None, GF(2), False, False) # optional - chomp, needs sage.rings.finite_rings - doctest:...: DeprecationWarning: the CHomP interface is deprecated; hence so is this function - See https://github.com/sagemath/sage/issues/33777 for details. - {0: Vector space of dimension 2 over Finite Field of size 2, 1: Vector space of dimension 1 over Finite Field of size 2} - - sage: D = ChainComplex({0: matrix(ZZ,1,0,[]), 1: matrix(ZZ,1,1,[0]), - ....: 2: matrix(ZZ,0,1,[])}) - sage: D._homology_chomp(None, GF(2), False, False) # optional - chomp, needs sage.rings.finite_rings - {1: Vector space of dimension 1 over Finite Field of size 2, - 2: Vector space of dimension 1 over Finite Field of size 2} - """ - deprecation(33777, "the CHomP interface is deprecated; hence so is this function") - from sage.interfaces.chomp import homchain - H = homchain(self, base_ring=base_ring, verbose=verbose, - generators=generators) - if H is None: - raise RuntimeError('ran CHomP, but no output') - if deg is None: - # all the homology groups that could be non-zero - # one has to complete the answer of chomp - result = H - for idx in self.nonzero_degrees(): - if idx not in H: - result[idx] = HomologyGroup(0, base_ring) - return result - if deg in H: - return H[deg] - else: - return HomologyGroup(0, base_ring) - def homology(self, deg=None, base_ring=None, generators=False, verbose=False, algorithm='pari'): r""" @@ -1204,7 +1171,6 @@ def homology(self, deg=None, base_ring=None, generators=False, * ``'auto'`` * ``'dhsw'`` * ``'pari'`` - * ``'chomp'`` (this option is deprecated) See below for descriptions. @@ -1235,12 +1201,6 @@ def homology(self, deg=None, base_ring=None, generators=False, forces the named algorithm to be used regardless of the size of the matrices. - Finally, if ``algorithm`` is set to ``'chomp'``, then use - CHomP. CHomP is available at the web page - http://chomp.rutgers.edu/, although the software has not been - tested recently in Sage. The use of this option is deprecated; - see :issue:`33777`. - As of this writing, ``'pari'`` is the fastest standard option. .. WARNING:: @@ -1300,10 +1260,8 @@ def homology(self, deg=None, base_ring=None, generators=False, if not (base_ring.is_field() or base_ring is ZZ): raise NotImplementedError('can only compute homology if the base ring is the integers or a field') - if algorithm not in ['dhsw', 'pari', 'auto', 'no_chomp', 'chomp']: + if algorithm not in ['dhsw', 'pari', 'auto', 'no_chomp']: raise NotImplementedError('algorithm not recognized') - if algorithm == 'chomp': - return self._homology_chomp(deg, base_ring, verbose, generators) if deg is None: deg = self.nonzero_degrees() @@ -1667,76 +1625,6 @@ def shift(self, n=1): return ChainComplex({k-shift: sgn * self._diff[k] for k in self._diff}, degree_of_differential=deg) - def _chomp_repr_(self): - r""" - String representation of ``self`` suitable for use by the CHomP - program. - - This function is deprecated. - - Since CHomP can only handle chain complexes, not cochain - complexes, and since it likes its complexes to start in degree - 0, flip the complex over if necessary, and shift it to start - in degree 0. Note also that CHomP only works over the - integers or a finite prime field. - - EXAMPLES:: - - sage: C = ChainComplex({-2: matrix(ZZ, 1, 3, [3, 0, 0])}, degree=-1) - sage: C._chomp_repr_() - doctest:...: DeprecationWarning: the CHomP interface is deprecated; hence so is this function - See https://github.com/sagemath/sage/issues/33777 for details. - 'chain complex\n\nmax dimension = 1\n\ndimension 0\n boundary a1 = 0\n\ndimension 1\n boundary a1 = + 3 * a1 \n boundary a2 = 0\n boundary a3 = 0\n\n' - sage: C = ChainComplex({-2: matrix(ZZ, 1, 3, [3, 0, 0])}, degree=1) - sage: C._chomp_repr_() - 'chain complex\n\nmax dimension = 1\n\ndimension 0\n boundary a1 = 0\n\ndimension 1\n boundary a1 = + 3 * a1 \n boundary a2 = 0\n boundary a3 = 0\n\n' - """ - deprecation(33777, "the CHomP interface is deprecated; hence so is this function") - deg = self.degree_of_differential() - if (self.grading_group() != ZZ or - (deg != 1 and deg != -1)): - raise ValueError('CHomP only works on Z-graded chain complexes with ' - 'differential of degree 1 or -1') - base_ring = self.base_ring() - if (base_ring == QQ) or (base_ring != ZZ and not (base_ring.is_prime_field())): - raise ValueError('CHomP doesn\'t compute over the rationals, only over Z or F_p') - if deg == -1: - diffs = self.differential() - else: - diffs = self._flip_().differential() - - if len(diffs) == 0: - diffs = {0: matrix(ZZ, 0, 0)} - - maxdim = max(diffs) - mindim = min(diffs) - # will shift chain complex by subtracting mindim from - # dimensions, so its bottom dimension is zero. - s = "chain complex\n\nmax dimension = %s\n\n" % (maxdim - mindim - 1,) - - for i in range(0, maxdim - mindim): - s += "dimension %s\n" % i - mat = diffs.get(i + mindim, matrix(base_ring, 0, 0)) - for idx in range(mat.ncols()): - s += " boundary a%s = " % (idx + 1) - # construct list of bdries - col = mat.column(idx) - nonzero_pos = col.nonzero_positions() - if nonzero_pos: - for j in nonzero_pos: - entry = col[j] - if entry > 0: - sgn = "+" - else: - sgn = "-" - entry = -entry - s += "%s %s * a%s " % (sgn, entry, j+1) - else: - s += "0" - s += "\n" - s += "\n" - return s - def _repr_(self): """ Print representation. @@ -1775,6 +1663,8 @@ def _ascii_art_(self): sage: ascii_art(D) [1] [1] [0] [1] 0 <-- C_7 <---- C_6 <-- 0 ... 0 <-- C_3 <---- C_2 <---- C_1 <---- C_0 <-- 0 + sage: ascii_art(ChainComplex(base_ring=ZZ)) + 0 """ from sage.typeset.ascii_art import AsciiArt @@ -1803,6 +1693,8 @@ def module_art(n): for n in ordered: result_ordered += arrow_art(n) + module_art(n) result = [result_ordered] + result + if len(result) == 0: + return AsciiArt(['0']) concatenated = result[0] for r in result[1:]: concatenated += AsciiArt([' ... ']) + r @@ -1828,6 +1720,14 @@ def _unicode_art_(self): sage: unicode_art(D) (1) (1) (0) (1) 0 <── C_7 <── C_6 <── 0 ... 0 <── C_3 <── C_2 <── C_1 <── C_0 <── 0 + + TESTS: + + check that :issue:`37678` is fixed:: + + sage: C = ChainComplex(base_ring=ZZ) + sage: unicode_art(C) + 0 """ from sage.typeset.unicode_art import UnicodeArt @@ -1856,6 +1756,8 @@ def module_art(n): for n in ordered: result_ordered += arrow_art(n) + module_art(n) result = [result_ordered] + result + if len(result) == 0: + return UnicodeArt([u'0']) concatenated = result[0] for r in result[1:]: concatenated += UnicodeArt([u' ... ']) + r diff --git a/src/sage/homology/tests.py b/src/sage/homology/tests.py index 1fc391376a8..206617843a9 100644 --- a/src/sage/homology/tests.py +++ b/src/sage/homology/tests.py @@ -1,33 +1,10 @@ # sage.doctest: needs sage.modules """ Tests for chain complexes, simplicial complexes, etc. - -These test whether CHomP gives the same answers as Sage's built-in -homology calculator. - -Since the CHomP interface has been deprecated --- see :issue:`33777` ---- so are many of the functions in is this module. - -TESTS:: - - sage: from sage.homology.tests import test_random_chain_complex - sage: test_random_chain_complex(trials=20) # optional - CHomP - doctest:...: DeprecationWarning: the CHomP interface is deprecated; hence so is this function - See https://github.com/sagemath/sage/issues/33777 for details. - sage: test_random_chain_complex(level=2, trials=20) # optional - CHomP - sage: test_random_chain_complex(level=3, trials=20) # long time # optional - CHomP - - sage: from sage.homology.tests import test_random_simplicial_complex - sage: test_random_simplicial_complex(level=1, trials=20) # optional - CHomP - doctest:...: DeprecationWarning: the CHomP interface is deprecated; hence so is this function - See https://github.com/sagemath/sage/issues/33777 for details. - sage: test_random_simplicial_complex(level=2, trials=20) # optional - CHomP - sage: test_random_simplicial_complex(level=5/2, trials=10) # long time # optional - CHomP """ from sage.misc.random_testing import random_testing from sage.misc.prandom import randint -from sage.misc.superseded import deprecation from sage.matrix.constructor import random_matrix from sage.homology.chain_complex import ChainComplex from sage.rings.integer_ring import ZZ @@ -66,43 +43,6 @@ def random_chain_complex(level=1): return ChainComplex({dim: mat}, degree=deg) -@random_testing -def test_random_chain_complex(level=1, trials=1, verbose=False): - """ - Compute the homology of a random chain complex with and without - CHomP, and compare the results. If they are not the same, raise - an error. - - This function is deprecated: see :issue:`33777`. - - :param level: measure of complexity of the chain complex -- see - :func:`random_chain_complex` - :type level: positive integer; optional, default 1 - :param trials: number of trials to conduct - :type trials: positive integer; optional, default 1 - :param verbose: if ``True``, print verbose messages - :type verbose: boolean; optional, default ``False`` - - EXAMPLES:: - - sage: from sage.homology.tests import test_random_chain_complex - sage: test_random_chain_complex(trials=2) # optional - CHomP - doctest:...: DeprecationWarning: the CHomP interface is deprecated; hence so is this function - See https://github.com/sagemath/sage/issues/33777 for details. - """ - deprecation(33777, 'the CHomP interface is deprecated; hence so is this function') - for i in range(trials): - C = random_chain_complex(level=level) - for d in C.differential(): - chomp = C.homology(d, verbose=verbose) - no_chomp = C.homology(d, algorithm='no_chomp', verbose=verbose) - if chomp != no_chomp: - print("Homology in dimension %s according to CHomP: %s" % (d, chomp)) - print("Homology in dimension %s according to Sage: %s" % (d, no_chomp)) - print("Chain complex: %s" % C.differential()) - raise ValueError - - def random_simplicial_complex(level=1, p=0.5): """ Return a random simplicial complex. @@ -118,7 +58,7 @@ def random_simplicial_complex(level=1, p=0.5): sage: from sage.homology.tests import random_simplicial_complex sage: X = random_simplicial_complex() - sage: X # random + sage: X # random Simplicial complex with vertex set (0, 1, 2, 3, 4, 5, 6, 7) and 31 facets sage: X.dimension() < 11 True @@ -126,40 +66,3 @@ def random_simplicial_complex(level=1, p=0.5): n = randint(2, 4 * level) dim = randint(1, n) return RandomComplex(n, dim, p) - - -@random_testing -def test_random_simplicial_complex(level=1, trials=1, verbose=False): - """ - Compute the homology of a random simplicial complex with and - without CHomP, and compare the results. If they are not the same, - raise an error. - - :param level: measure of complexity of the simplicial complex -- - see :func:`random_simplicial_complex` - :type level: positive integer; optional, default 1 - :param trials: number of trials to conduct - :type trials: positive integer; optional, default 1 - :param verbose: if ``True``, print verbose messages - :type verbose: boolean; optional, default ``False`` - - This gets pretty slow if ``level`` is more than 3. - - EXAMPLES:: - - sage: from sage.homology.tests import test_random_simplicial_complex - sage: test_random_simplicial_complex(trials=2) # optional - CHomP - doctest:...: DeprecationWarning: the CHomP interface is deprecated; hence so is this function - See https://github.com/sagemath/sage/issues/33777 for details. - """ - deprecation(33777, 'the CHomP interface is deprecated; hence so is this function') - for i in range(trials): - X = random_simplicial_complex(level=level) - chomp = X.homology(verbose=verbose) - no_chomp = X.homology(algorithm='no_chomp', verbose=verbose) - if chomp != no_chomp: - print("Homology according to CHomP: %s" % chomp) - print("Homology according to Sage: %s" % no_chomp) - print("Simplicial complex: %s" % X) - print("Its chain complex: %s" % X.chain_complex()) - raise ValueError diff --git a/src/sage/interfaces/chomp.py b/src/sage/interfaces/chomp.py deleted file mode 100644 index ad3bf9c734e..00000000000 --- a/src/sage/interfaces/chomp.py +++ /dev/null @@ -1,923 +0,0 @@ -r""" -Interface to CHomP - -This module is deprecated: see :issue:`33777`. - -CHomP stands for "Computation Homology Program", and is good at -computing homology of simplicial complexes, cubical complexes, and -chain complexes. It can also compute homomorphisms induced on -homology by maps. See the CHomP web page http://chomp.rutgers.edu/ -for more information. - -AUTHOR: - -- John H. Palmieri -""" - -import re - -from sage.misc.superseded import deprecation - -_have_chomp = {} - - -def have_chomp(program='homsimpl'): - """ - Return True if this computer has ``program`` installed. - - The first time it is run, this function caches its result in the - variable ``_have_chomp`` -- a dictionary indexed by program name - -- and any subsequent time, it just checks the value of the - variable. - - This program is used in the routine CHomP.__call__. - - If this computer doesn't have CHomP installed, you may obtain it - from http://chomp.rutgers.edu/. - - EXAMPLES:: - - sage: from sage.interfaces.chomp import have_chomp - sage: have_chomp() # random -- depends on whether CHomP is installed - doctest:...: DeprecationWarning: the CHomP interface is deprecated; hence so is this function - See https://github.com/sagemath/sage/issues/33777 for details. - True - sage: 'homsimpl' in sage.interfaces.chomp._have_chomp - True - sage: sage.interfaces.chomp._have_chomp['homsimpl'] == have_chomp() - True - """ - deprecation(33777, "the CHomP interface is deprecated; hence so is this function") - global _have_chomp - if program not in _have_chomp: - from sage.misc.sage_ostools import have_program - _have_chomp[program] = have_program(program) - return _have_chomp[program] - - -class CHomP: - r""" - Interface to the CHomP package. - - :param program: which CHomP program to use - :type program: string - :param complex: a simplicial or cubical complex - :param subcomplex: a subcomplex of ``complex`` or None (the default) - :param base_ring: ring over which to perform computations -- must be `\ZZ` or `\GF{p}`. - :type base_ring: ring; optional, default `\ZZ` - :param generators: if True, also return list of generators - :type generators: boolean; optional, default False - :param verbose: if True, print helpful messages as the computation - progresses - :type verbose: boolean; optional, default False - :param extra_opts: options passed directly to ``program`` - :type extra_opts: string - :return: homology groups as a dictionary indexed by dimension - - The programs ``homsimpl``, ``homcubes``, and ``homchain`` are - available through this interface. ``homsimpl`` computes the - relative or absolute homology groups of simplicial complexes. - ``homcubes`` computes the relative or absolute homology groups of - cubical complexes. ``homchain`` computes the homology groups of - chain complexes. For consistency with Sage's other homology - computations, the answers produced by ``homsimpl`` and - ``homcubes`` in the absolute case are converted to reduced - homology. - - Note also that CHomP can only compute over the integers or - `\GF{p}`. CHomP is fast enough, though, that if you want - rational information, you should consider using CHomP with integer - coefficients, or with mod `p` coefficients for a sufficiently - large `p`, rather than using Sage's built-in homology algorithms. - - See also the documentation for the functions :func:`homchain`, - :func:`homcubes`, and :func:`homsimpl` for more examples, - including illustrations of some of the optional parameters. - - EXAMPLES:: - - sage: from sage.interfaces.chomp import CHomP - sage: T = cubical_complexes.Torus() - sage: CHomP()('homcubes', T) # optional - CHomP - {0: 0, 1: Z x Z, 2: Z} - - Relative homology of a segment relative to its endpoints:: - - sage: edge = simplicial_complexes.Simplex(1) - sage: ends = edge.n_skeleton(0) - sage: CHomP()('homsimpl', edge) # optional - CHomP - {0: 0} - sage: CHomP()('homsimpl', edge, ends) # optional - CHomP - {0: 0, 1: Z} - - Homology of a chain complex:: - - sage: C = ChainComplex({3: 2 * identity_matrix(ZZ, 2)}, degree=-1) - sage: CHomP()('homchain', C) # optional - CHomP - {2: C2 x C2} - """ - def __repr__(self): - """ - String representation. - - EXAMPLES:: - - sage: from sage.interfaces.chomp import CHomP - sage: CHomP() # indirect doctest - CHomP interface - """ - return "CHomP interface" - - def __call__(self, program, complex, subcomplex=None, **kwds): - """ - Call a CHomP program to compute the homology of a chain - complex, simplicial complex, or cubical complex. - - Deprecated: see :issue:`33777`. - - See :class:`CHomP` for full documentation. - - EXAMPLES:: - - sage: from sage.interfaces.chomp import CHomP - sage: T = cubical_complexes.Torus() - sage: CHomP()('homcubes', T) # indirect doctest, optional - CHomP - doctest:...: DeprecationWarning: the CHomP interface is deprecated - See https://github.com/sagemath/sage/issues/33777 for details. - {0: 0, 1: Z x Z, 2: Z} - """ - from sage.misc.temporary_file import tmp_filename - from sage.topology.cubical_complex import CubicalComplex, cubical_complexes - from sage.topology.simplicial_complex import SimplicialComplex, Simplex - from sage.homology.chain_complex import HomologyGroup - from subprocess import Popen, PIPE - from sage.rings.integer_ring import ZZ - from sage.rings.rational_field import QQ - from sage.modules.free_module import VectorSpace - from sage.modules.free_module_element import free_module_element as vector - from sage.combinat.free_module import CombinatorialFreeModule - - deprecation(33777, "the CHomP interface is deprecated") - - if not have_chomp(program): - raise OSError("Program %s not found" % program) - - verbose = kwds.get('verbose', False) - generators = kwds.get('generators', False) - extra_opts = kwds.get('extra_opts', '') - base_ring = kwds.get('base_ring', ZZ) - - if extra_opts: - extra_opts = extra_opts.split() - else: - extra_opts = [] - - # type of complex: - cubical = False - simplicial = False - chain = False - # CHomP seems to have problems with cubical complexes if the - # first interval in the first cube defining the complex is - # degenerate. So replace the complex X with [0,1] x X. - if isinstance(complex, CubicalComplex): - cubical = True - edge = cubical_complexes.Cube(1) - original_complex = complex - complex = edge.product(complex) - if verbose: - print("Cubical complex") - elif isinstance(complex, SimplicialComplex): - simplicial = True - if verbose: - print("Simplicial complex") - else: - chain = True - base_ring = kwds.get('base_ring', complex.base_ring()) - if verbose: - print("Chain complex over %s" % base_ring) - - if base_ring == QQ: - raise ValueError("CHomP doesn't compute over the rationals, only over Z or F_p.") - if base_ring.is_prime_field(): - p = base_ring.characteristic() - extra_opts.append('-p%s' % p) - mod_p = True - else: - mod_p = False - - # - # complex - # - try: - data = complex._chomp_repr_() - except AttributeError: - raise AttributeError("Complex cannot be converted to use with CHomP.") - - datafile = tmp_filename() - with open(datafile, 'w') as f: - f.write(data) - - # - # subcomplex - # - if subcomplex is None: - if cubical: - subcomplex = CubicalComplex([complex.n_cells(0)[0]]) - elif simplicial: - m = re.search(r'\(([^,]*),', data) - v = int(m.group(1)) - subcomplex = SimplicialComplex([[v]]) - else: - # replace subcomplex with [0,1] x subcomplex. - if cubical: - subcomplex = edge.product(subcomplex) - # - # generators - # - if generators: - genfile = tmp_filename() - extra_opts.append('-g%s' % genfile) - - # - # call program - # - if subcomplex is not None: - try: - sub = subcomplex._chomp_repr_() - except AttributeError: - raise AttributeError("Subcomplex cannot be converted to use with CHomP.") - subfile = tmp_filename() - with open(subfile, 'w') as f: - f.write(sub) - else: - subfile = '' - if verbose: - print("Popen called with arguments", end="") - print([program, datafile, subfile] + extra_opts) - print("") - print("CHomP output:") - print("") - # output = Popen([program, datafile, subfile, extra_opts], - cmd = [program, datafile] - if subfile: - cmd.append(subfile) - if extra_opts: - cmd.extend(extra_opts) - output = Popen(cmd, stdout=PIPE).communicate()[0] - if verbose: - print(output) - print("End of CHomP output") - print("") - if generators: - with open(genfile, 'r') as f: - gens = f.read() - if verbose: - print("Generators:") - print(gens) - # - # process output - # - if output.find('ERROR') != -1: - raise RuntimeError('error inside CHomP') - # output contains substrings of one of the forms - # "H_1 = Z", "H_1 = Z_2 + Z", "H_1 = Z_2 + Z^2", - # "H_1 = Z + Z_2 + Z" - if output.find('trivial') != -1: - if mod_p: - return {0: VectorSpace(base_ring, 0)} - else: - return {0: HomologyGroup(0, ZZ)} - d = {} - h = re.compile("^H_([0-9]*) = (.*)$", re.M) - tors = re.compile("Z_([0-9]*)") - # - # homology groups - # - for m in h.finditer(output): - if verbose: - print(m.groups()) - # dim is the dimension of the homology group - dim = int(m.group(1)) - # hom_str is the right side of the equation "H_n = Z^r + Z_k + ..." - hom_str = m.group(2) - # need to read off number of summands and their invariants - if hom_str.find("0") == 0: - if mod_p: - hom = VectorSpace(base_ring, 0) - else: - hom = HomologyGroup(0, ZZ) - else: - rk = 0 - if hom_str.find("^") != -1: - rk_srch = re.search(r'\^([0-9]*)\s?', hom_str) - rk = int(rk_srch.group(1)) - rk += len(re.findall(r"(Z$)|(Z\s)", hom_str)) - if mod_p: - rk = rk if rk != 0 else 1 - if verbose: - print("dimension = %s, rank of homology = %s" % (dim, rk)) - hom = VectorSpace(base_ring, rk) - else: - n = rk - invts = [] - for t in tors.finditer(hom_str): - n += 1 - invts.append(int(t.group(1))) - for i in range(rk): - invts.append(0) - if verbose: - print("dimension = %s, number of factors = %s, invariants = %s" % (dim, n, invts)) - hom = HomologyGroup(n, ZZ, invts) - - # - # generators - # - if generators: - if cubical: - g = process_generators_cubical(gens, dim) - if verbose: - print("raw generators: %s" % g) - if g: - module = CombinatorialFreeModule(base_ring, - original_complex.n_cells(dim), - prefix="", - bracket=True) - basis = module.basis() - output = [] - for x in g: - v = module(0) - for term in x: - v += term[0] * basis[term[1]] - output.append(v) - g = output - elif simplicial: - g = process_generators_simplicial(gens, dim, complex) - if verbose: - print("raw generators: %s" % gens) - if g: - module = CombinatorialFreeModule(base_ring, - complex.n_cells(dim), - prefix="", - bracket=False) - basis = module.basis() - output = [] - for x in g: - v = module(0) - for term in x: - if complex._is_numeric(): - v += term[0] * basis[term[1]] - else: - translate = complex._translation_from_numeric() - simplex = Simplex([translate[a] for a in term[1]]) - v += term[0] * basis[simplex] - output.append(v) - g = output - elif chain: - g = process_generators_chain(gens, dim, base_ring) - if verbose: - print("raw generators: %s" % gens) - if g: - if not mod_p: - # sort generators to match up with corresponding invariant - g = [_[1] for _ in sorted(zip(invts, g), key=lambda x: x[0])] - d[dim] = (hom, g) - else: - d[dim] = hom - else: - d[dim] = hom - - if chain: - new_d = {} - diff = complex.differential() - if len(diff) == 0: - return {} - bottom = min(diff) - top = max(diff) - for dim in d: - if complex._degree_of_differential == -1: # chain complex - new_dim = bottom + dim - else: # cochain complex - new_dim = top - dim - if isinstance(d[dim], tuple): - # generators included. - group = d[dim][0] - gens = d[dim][1] - new_gens = [] - dimension = complex.differential(new_dim).ncols() - # make sure that each vector is embedded in the - # correct ambient space: pad with a zero if - # necessary. - for v in gens: - v_dict = v.dict() - if dimension - 1 not in v.dict(): - v_dict[dimension - 1] = 0 - new_gens.append(vector(base_ring, v_dict)) - else: - new_gens.append(v) - new_d[new_dim] = (group, new_gens) - else: - new_d[new_dim] = d[dim] - d = new_d - return d - - def help(self, program): - """ - Print a help message for ``program``, a program from the CHomP suite. - - :param program: which CHomP program to use - :type program: string - :return: nothing -- just print a message - - EXAMPLES:: - - sage: from sage.interfaces.chomp import CHomP - sage: CHomP().help('homcubes') # optional - CHomP - doctest:...: DeprecationWarning: the CHomP interface is deprecated - See https://github.com/sagemath/sage/issues/33777 for details. - HOMCUBES, ver. ... Copyright (C) ... by Pawel Pilarczyk... - """ - deprecation(33777, "the CHomP interface is deprecated") - from subprocess import Popen, PIPE - print(Popen([program, '-h'], stdout=PIPE).communicate()[0]) - - -def homsimpl(complex=None, subcomplex=None, **kwds): - r""" - Compute the homology of a simplicial complex using the CHomP - program ``homsimpl``. If the argument ``subcomplex`` is present, - compute homology of ``complex`` relative to ``subcomplex``. - - This function is deprecated: see :issue:`33777`. - - :param complex: a simplicial complex - :param subcomplex: a subcomplex of ``complex`` or None (the default) - :param base_ring: ring over which to perform computations -- must be `\ZZ` or `\GF{p}`. - :type base_ring: ring; optional, default `\ZZ` - :param generators: if True, also return list of generators - :type generators: boolean; optional, default False - :param verbose: if True, print helpful messages as the computation - progresses - :type verbose: boolean; optional, default False - :param help: if True, just print a help message and exit - :type help: boolean; optional, default False - :param extra_opts: options passed directly to ``program`` - :type extra_opts: string - :return: homology groups as a dictionary indexed by dimension - - EXAMPLES:: - - sage: from sage.interfaces.chomp import homsimpl - sage: T = simplicial_complexes.Torus() - sage: M8 = simplicial_complexes.MooreSpace(8) - sage: M4 = simplicial_complexes.MooreSpace(4) - sage: X = T.disjoint_union(T).disjoint_union(T).disjoint_union(M8).disjoint_union(M4) - sage: homsimpl(X)[1] # optional - CHomP - doctest:...: DeprecationWarning: the CHomP interface is deprecated - See https://github.com/sagemath/sage/issues/33777 for details. - Z^6 x C4 x C8 - - Relative homology:: - - sage: S = simplicial_complexes.Simplex(3) - sage: bdry = S.n_skeleton(2) - sage: homsimpl(S, bdry)[3] # optional - CHomP - Z - - Generators: these are given as a list after the homology group. - Each generator is specified as a linear combination of simplices:: - - sage: homsimpl(S, bdry, generators=True)[3] # optional - CHomP - (Z, [(0, 1, 2, 3)]) - - sage: homsimpl(simplicial_complexes.Sphere(1), generators=True) # optional - CHomP - {0: 0, 1: (Z, [(0, 1) - (0, 2) + (1, 2)])} - - TESTS: - - Generators for a simplicial complex whose vertices are not integers:: - - sage: S1 = simplicial_complexes.Sphere(1) - sage: homsimpl(S1.join(S1), generators=True, base_ring=GF(2))[3][1] # optional - CHomP - [('L0', 'L1', 'R0', 'R1') + ('L0', 'L1', 'R0', 'R2') + ('L0', 'L1', 'R1', 'R2') + ('L0', 'L2', 'R0', 'R1') + ('L0', 'L2', 'R0', 'R2') + ('L0', 'L2', 'R1', 'R2') + ('L1', 'L2', 'R0', 'R1') + ('L1', 'L2', 'R0', 'R2') + ('L1', 'L2', 'R1', 'R2')] - """ - deprecation(33777, "the CHomP interface is deprecated") - from sage.topology.simplicial_complex import SimplicialComplex - help = kwds.get('help', False) - if help: - return CHomP().help('homsimpl') - # Check types here, because if you pass homsimpl a cubical - # complex, it tries to compute its homology as if it were a - # simplicial complex and gets terribly wrong answers. - if (isinstance(complex, SimplicialComplex) - and (subcomplex is None or isinstance(subcomplex, SimplicialComplex))): - return CHomP()('homsimpl', complex, subcomplex=subcomplex, **kwds) - else: - raise TypeError("Complex and/or subcomplex are not simplicial complexes.") - - -def homcubes(complex=None, subcomplex=None, **kwds): - r""" - Compute the homology of a cubical complex using the CHomP program - ``homcubes``. If the argument ``subcomplex`` is present, compute - homology of ``complex`` relative to ``subcomplex``. - - This function is deprecated: see :issue:`33777`. - - :param complex: a cubical complex - :param subcomplex: a subcomplex of ``complex`` or None (the default) - :param base_ring: ring over which to perform computations -- must be `\ZZ` or `\GF{p}`. - :type base_ring: ring; optional, default `\ZZ` - :param generators: if True, also return list of generators - :type generators: boolean; optional, default False - :param verbose: if True, print helpful messages as the computation progresses - :type verbose: boolean; optional, default False - :param help: if True, just print a help message and exit - :type help: boolean; optional, default False - :param extra_opts: options passed directly to ``homcubes`` - :type extra_opts: string - :return: homology groups as a dictionary indexed by dimension - - EXAMPLES:: - - sage: from sage.interfaces.chomp import homcubes - sage: S = cubical_complexes.Sphere(3) - sage: homcubes(S)[3] # optional - CHomP - doctest:...: DeprecationWarning: the CHomP interface is deprecated - See https://github.com/sagemath/sage/issues/33777 for details. - Z - - Relative homology:: - - sage: C3 = cubical_complexes.Cube(3) - sage: bdry = C3.n_skeleton(2) - sage: homcubes(C3, bdry) # optional - CHomP - {0: 0, 1: 0, 2: 0, 3: Z} - - Generators: these are given as a list after the homology group. - Each generator is specified as a linear combination of cubes:: - - sage: homcubes(cubical_complexes.Sphere(1), generators=True, base_ring=GF(2))[1][1] # optional - CHomP - [[[1,1] x [0,1]] + [[0,1] x [1,1]] + [[0,1] x [0,0]] + [[0,0] x [0,1]]] - """ - deprecation(33777, "the CHomP interface is deprecated") - from sage.topology.cubical_complex import CubicalComplex - help = kwds.get('help', False) - if help: - return CHomP().help('homcubes') - # Type-checking is here for the same reason as in homsimpl above. - if (isinstance(complex, CubicalComplex) - and (subcomplex is None or isinstance(subcomplex, CubicalComplex))): - return CHomP()('homcubes', complex, subcomplex=subcomplex, **kwds) - else: - raise TypeError("Complex and/or subcomplex are not cubical complexes.") - - -def homchain(complex=None, **kwds): - r""" - Compute the homology of a chain complex using the CHomP program - ``homchain``. - - This function is deprecated: see :issue:`33777`. - - :param complex: a chain complex - :param generators: if True, also return list of generators - :type generators: boolean; optional, default False - :param verbose: if True, print helpful messages as the computation progresses - :type verbose: boolean; optional, default False - :param help: if True, just print a help message and exit - :type help: boolean; optional, default False - :param extra_opts: options passed directly to ``homchain`` - :type extra_opts: string - :return: homology groups as a dictionary indexed by dimension - - EXAMPLES:: - - sage: from sage.interfaces.chomp import homchain - sage: C = cubical_complexes.Sphere(3).chain_complex() - sage: homchain(C)[3] # optional - CHomP - doctest:...: DeprecationWarning: the CHomP interface is deprecated - See https://github.com/sagemath/sage/issues/33777 for details. - Z - - Generators: these are given as a list after the homology group. - Each generator is specified as a cycle, an element in the - appropriate free module over the base ring:: - - sage: C2 = delta_complexes.Sphere(2).chain_complex() - sage: homchain(C2, generators=True)[2] # optional - CHomP - (Z, [(1, -1)]) - sage: homchain(C2, generators=True, base_ring=GF(2))[2] # optional - CHomP - (Vector space of dimension 1 over Finite Field of size 2, [(1, 1)]) - - TESTS: - - Chain complexes concentrated in negative dimensions, cochain complexes, etc.:: - - sage: C = ChainComplex({-5: 4 * identity_matrix(ZZ, 2)}, degree=-1) - sage: homchain(C) # optional - CHomP - {-6: C4 x C4} - sage: C = ChainComplex({-5: 4 * identity_matrix(ZZ, 2)}, degree=1) - sage: homchain(C, generators=True) # optional - CHomP - {-4: (C4 x C4, [(1, 0), (0, 1)])} - """ - deprecation(33777, "the CHomP interface is deprecated") - from sage.homology.chain_complex import ChainComplex_class - help = kwds.get('help', False) - if help: - return CHomP().help('homchain') - # Type-checking just in case. - if isinstance(complex, ChainComplex_class): - return CHomP()('homchain', complex, **kwds) - else: - raise TypeError("Complex is not a chain complex.") - - -def process_generators_cubical(gen_string, dim): - r""" - Process CHomP generator information for cubical complexes. - - This function is deprecated: see :issue:`33777`. - - :param gen_string: generator output from CHomP - :type gen_string: string - :param dim: dimension in which to find generators - :type dim: integer - :return: list of generators in each dimension, as described below - - ``gen_string`` has the form :: - - The 2 generators of H_1 follow: - generator 1 - -1 * [(0,0,0,0,0)(0,0,0,0,1)] - 1 * [(0,0,0,0,0)(0,0,1,0,0)] - ... - generator 2 - -1 * [(0,1,0,1,1)(1,1,0,1,1)] - -1 * [(0,1,0,0,1)(0,1,0,1,1)] - ... - - Each line consists of a coefficient multiplied by a cube; the cube - is specified by its "bottom left" and "upper right" corners. - - For technical reasons, we remove the first coordinate of each - tuple, and using regular expressions, the remaining parts get - converted from a string to a pair ``(coefficient, Cube)``, with - the cube represented as a product of tuples. For example, the - first line in "generator 1" gets turned into :: - - (-1, [0,0] x [0,0] x [0,0] x [0,1]) - - representing an element in the free abelian group with basis given - by cubes. Each generator is a list of such pairs, representing - the sum of such elements. These are reassembled in - :meth:`CHomP.__call__` to actual elements in the free module - generated by the cubes of the cubical complex in the appropriate - dimension. - - Therefore the return value is a list of lists of pairs, one list - of pairs for each generator. - - EXAMPLES:: - - sage: from sage.interfaces.chomp import process_generators_cubical - sage: s = "The 2 generators of H_1 follow:\ngenerator 1:\n-1 * [(0,0,0,0,0)(0,0,0,0,1)]\n1 * [(0,0,0,0,0)(0,0,1,0,0)]" - sage: process_generators_cubical(s, 1) - doctest:...: DeprecationWarning: the CHomP interface is deprecated - See https://github.com/sagemath/sage/issues/33777 for details. - [[(-1, [0,0] x [0,0] x [0,0] x [0,1]), (1, [0,0] x [0,1] x [0,0] x [0,0])]] - sage: len(process_generators_cubical(s, 1)) # only one generator - 1 - """ - deprecation(33777, "the CHomP interface is deprecated") - from sage.topology.cubical_complex import Cube - # each dim in gen_string starts with "The generator for - # H_3 follows:". So search for "T" to find the - # end of the current list of generators. - g_srch = re.compile(r'H_%s\sfollow[s]?:\n([^T]*)(?:T|$)' % dim) - g = g_srch.search(gen_string) - output = [] - cubes_list = [] - if g: - g = g.group(1) - if g: - # project g to one end of the cylinder [0,1] x complex: - # - # drop the first coordinate and eliminate duplicates, at least - # in positive dimensions, drop any line containing a - # degenerate cube - g = re.sub(r'\([01],', '(', g) - if dim > 0: - lines = g.splitlines() - newlines = [] - for l in lines: - x = re.findall(r'(\([0-9,]*\))', l) - if x: - left, right = x - left = [int(a) for a in left.strip('()').split(',')] - right = [int(a) for a in right.strip('()').split(',')] - if sum([xx - yy for (xx, yy) in zip(right, left)]) == dim: - newlines.append(l) - else: # line like "generator 2" - newlines.append(l) - g = newlines - cubes_list = [] - for l in g: - cubes = re.search(r'([+-]?)\s?([0-9]+)?\s?[*]?\s?\[\(([-0-9,]+)\)\(([-0-9,]+)\)\]', l) - if cubes: - if cubes.group(1) and re.search("-", cubes.group(1)): - sign = -1 - else: - sign = 1 - if cubes.group(2) and len(cubes.group(2)) > 0: - coeff = sign * int(cubes.group(2)) - else: - coeff = sign * 1 - cube1 = [int(a) for a in cubes.group(3).split(',')] - cube2 = [int(a) for a in cubes.group(4).split(',')] - cube = Cube(zip(cube1, cube2)) - cubes_list.append((coeff, cube)) - else: - if cubes_list: - output.append(cubes_list) - cubes_list = [] - if cubes_list: - output.append(cubes_list) - return output - else: - return None - - -def process_generators_simplicial(gen_string, dim, complex): - r""" - Process CHomP generator information for simplicial complexes. - - This function is deprecated: see :issue:`33777` - - :param gen_string: generator output from CHomP - :type gen_string: string - :param dim: dimension in which to find generators - :type dim: integer - :param complex: simplicial complex under consideration - :return: list of generators in each dimension, as described below - - ``gen_string`` has the form :: - - The 2 generators of H_1 follow: - generator 1 - -1 * (1,6) - 1 * (1,4) - ... - generator 2 - -1 * (1,6) - 1 * (1,4) - ... - - where each line contains a coefficient and a simplex. Each line - is converted, using regular expressions, from a string to a pair - ``(coefficient, Simplex)``, like :: - - (-1, (1,6)) - - representing an element in the free abelian group with basis given - by simplices. Each generator is a list of such pairs, - representing the sum of such elements. These are reassembled in - :meth:`CHomP.__call__` to actual elements in the free module - generated by the simplices of the simplicial complex in the - appropriate dimension. - - Therefore the return value is a list of lists of pairs, one list - of pairs for each generator. - - EXAMPLES:: - - sage: from sage.interfaces.chomp import process_generators_simplicial - sage: s = "The 2 generators of H_1 follow:\ngenerator 1:\n-1 * (1,6)\n1 * (1,4)" - sage: process_generators_simplicial(s, 1, simplicial_complexes.Torus()) - doctest:...: DeprecationWarning: the CHomP interface is deprecated - See https://github.com/sagemath/sage/issues/33777 for details. - [[(-1, (1, 6)), (1, (1, 4))]] - """ - deprecation(33777, "the CHomP interface is deprecated") - from sage.topology.simplicial_complex import Simplex - # each dim in gen_string starts with "The generator for H_3 - # follows:". So search for "T" to find the end of the current - # list of generators. - g_srch = re.compile(r'H_%s\sfollow[s]?:\n([^T]*)(?:T|$)' % dim) - g = g_srch.search(gen_string) - output = [] - simplex_list = [] - if g: - g = g.group(1) - if g: - lines = g.splitlines() - for l in lines: - simplex = re.search(r'([+-]?)\s?([0-9]+)?\s?[*]?\s?(\([0-9,]*\))', l) - if simplex: - if simplex.group(1) and re.search("-", simplex.group(1)): - sign = -1 - else: - sign = 1 - if simplex.group(2) and len(simplex.group(2)) > 0: - coeff = sign * int(simplex.group(2)) - else: - coeff = sign * 1 - simplex = Simplex([int(a) for a in simplex.group(3).strip('()').split(',')]) - simplex_list.append((coeff, simplex)) - else: - if simplex_list: - output.append(simplex_list) - simplex_list = [] - if simplex_list: - output.append(simplex_list) - return output - else: - return None - - -def process_generators_chain(gen_string, dim, base_ring=None): - r""" - Process CHomP generator information for simplicial complexes. - - This function is deprecated: see :issue:`33777`. - - :param gen_string: generator output from CHomP - :type gen_string: string - :param dim: dimension in which to find generators - :type dim: integer - :param base_ring: base ring over which to do the computations - :type base_ring: optional, default ZZ - :return: list of generators in each dimension, as described below - - ``gen_string`` has the form :: - - [H_0] - a1 - - [H_1] - a2 - a3 - - [H_2] - a1 - a2 - - For each homology group, each line lists a homology generator as a - linear combination of generators ``ai`` of the group of chains in - the appropriate dimension. The elements ``ai`` are indexed - starting with `i=1`. Each generator is converted, using regular - expressions, from a string to a vector (an element in the free - module over ``base_ring``), with ``ai`` representing the unit - vector in coordinate `i-1`. For example, the string ``a1 - a2`` - gets converted to the vector ``(1, -1)``. - - Therefore the return value is a list of vectors. - - EXAMPLES:: - - sage: from sage.interfaces.chomp import process_generators_chain - sage: s = "[H_0]\na1\n\n[H_1]\na2\na3\n" - sage: process_generators_chain(s, 1) - doctest:...: DeprecationWarning: the CHomP interface is deprecated - See https://github.com/sagemath/sage/issues/33777 for details. - [(0, 1), (0, 0, 1)] - sage: s = "[H_0]\na1\n\n[H_1]\n5 * a2 - a1\na3\n" - sage: process_generators_chain(s, 1, base_ring=ZZ) - [(-1, 5), (0, 0, 1)] - sage: process_generators_chain(s, 1, base_ring=GF(2)) - [(1, 1), (0, 0, 1)] - """ - deprecation(33777, "the CHomP interface is deprecated") - from sage.modules.free_module_element import vector - from sage.rings.integer_ring import ZZ - if base_ring is None: - base_ring = ZZ - # each dim in gens starts with a string like - # "[H_3]". So search for "[" to find the end of - # the current list of generators. - g_srch = re.compile(r'\[H_%s\]\n([^]]*)(?:\[|$)' % dim) - g = g_srch.search(gen_string) - if g: - g = g.group(1) - if g: - # each line in the string g is a linear - # combination of things like "a2", "a31", etc. - # indexing on the a's starts at 1. - lines = g.splitlines() - new_gens = [] - for l in lines: - gen = re.compile(r"([+-]?)\s?([0-9]+)?\s?[*]?\s?a([0-9]*)(?:\s|$)") - v = {} - for term in gen.finditer(l): - if term.group(1) and re.search("-", term.group(1)): - sign = -1 - else: - sign = 1 - if term.group(2) and len(term.group(2)) > 0: - coeff = sign * int(term.group(2)) - else: - coeff = sign * 1 - idx = int(term.group(3)) - v[idx-1] = coeff - if v: - new_gens.append(vector(base_ring, v)) - g = new_gens - return g diff --git a/src/sage/interfaces/fricas.py b/src/sage/interfaces/fricas.py index 87feb554d16..7d4dddabba9 100644 --- a/src/sage/interfaces/fricas.py +++ b/src/sage/interfaces/fricas.py @@ -647,7 +647,8 @@ def _convert_prod(x, y): def explicitly_not_implemented(*args): raise NotImplementedError("the translation of the FriCAS Expression '%s' to sage is not yet implemented" % args) - register_symbol(lambda *args: explicitly_not_implemented("rootOfADE"), {'fricas': 'rootOfADE'}, 2) + register_symbol(lambda *args: explicitly_not_implemented("rootOfADE"), {'fricas': 'rootOfADE'}, 2) # to be removed once we fully on FriCAS 1.3.10+ + register_symbol(lambda *args: explicitly_not_implemented("FEseries"), {'fricas': 'FEseries'}, 2) register_symbol(lambda *args: explicitly_not_implemented("rootOfRec"), {'fricas': 'rootOfRec'}, 2) def set(self, var, value): @@ -802,7 +803,7 @@ def get_unparsed_InputForm(self, var): '(1..3)$Segment(PositiveInteger())' """ - return self.get_string('unparse((%s)::InputForm)' % str(var)) + return self.get_string('unparse((%s)::InputForm)' % var) def get_InputForm(self, var): """ @@ -1184,7 +1185,7 @@ def _latex_(self): \frac{{{\log \left( {{e+1}} \right)} \ {\sin \left( {{y+x}} \right)}}}{{{e} ^{z}}} sage: latex(fricas("matrix([[1,2],[3,4]])")) - \left[ \begin{array}{cc} 1 & 2 \\ 3 & 4\end{array} \right] + \left[ \begin{array}{cc} 1 & 2 \\ 3 & 4...\end{array}...\right] sage: latex(fricas("integrate(sin(x+1/x),x)")) \int ^{\displaystyle x} {{\sin \left( {{\frac{{{{ \%...} ^{2}}+1}}{ \%...}}} \right)} \ {d \%...}} @@ -1715,7 +1716,6 @@ def _sage_expression(fricas_InputForm): sage: f[1].sage() -1/2*sqrt(1/3)*sqrt((3*(1/18*I*sqrt(229)*sqrt(3) + 1/2)^(2/3) + 4)/(1/18*I*sqrt(229)*sqrt(3) + 1/2)^(1/3)) + 1/2*sqrt(-(1/18*I*sqrt(229)*sqrt(3) + 1/2)^(1/3) + 6*sqrt(1/3)/sqrt((3*(1/18*I*sqrt(229)*sqrt(3) + 1/2)^(2/3) + 4)/(1/18*I*sqrt(229)*sqrt(3) + 1/2)^(1/3)) - 4/3/(1/18*I*sqrt(229)*sqrt(3) + 1/2)^(1/3)) - """ # a FriCAS expressions may contain implicit references to a # rootOf expression within itself, as for example in the @@ -1836,7 +1836,7 @@ def _sage_(self): sage: fricas(x+3).sage() x + 3 sage: fricas(x+3).domainOf() - Polynomial(Integer()) + Polynomial(Integer...) sage: fricas(matrix([[2,3],[4,x+5]])).diagonal().sage() (2, x + 5) @@ -1904,7 +1904,7 @@ def _sage_(self): sage: s.sage() Traceback (most recent call last): ... - NotImplementedError: the translation of the FriCAS Expression 'rootOfADE' to sage is not yet implemented + NotImplementedError: the translation of the FriCAS Expression 'FEseries' to sage is not yet implemented sage: s = fricas("series(sqrt(1+x), x=0)"); s 1 1 2 1 3 5 4 7 5 21 6 33 7 429 8 @@ -1994,19 +1994,24 @@ def _sage_(self): return R([self.coefficient(i).sage() for i in range(ZZ(self.degree()) + 1)]) - # finally translate domains with InputForm - try: - unparsed_InputForm = P.get_unparsed_InputForm(self._name) - except RuntimeError as error: - raise NotImplementedError("the translation of the FriCAS object\n\n%s\n\nto sage is not yet implemented:\n%s" % (self, error)) + # finally translate domains with InputForm - we do this + # lazily, because sometimes we can use unparse, sometimes we + # need our custom sageprint + + def unparsed_InputForm(): + try: + return P.get_unparsed_InputForm(self._name) + except RuntimeError as error: + raise NotImplementedError("the translation of the FriCAS object\n\n%s\n\nto sage is not yet implemented:\n%s" % (self, error)) + if head == "Boolean": - return unparsed_InputForm == "true" + return unparsed_InputForm() == "true" if head in ["Integer", "NonNegativeInteger", "PositiveInteger"]: - return ZZ(unparsed_InputForm) + return ZZ(unparsed_InputForm()) if head == "String": - return unparsed_InputForm + return unparsed_InputForm() if head == "Float": # Warning: precision$Float gives the current precision, @@ -2014,22 +2019,28 @@ def _sage_(self): # self. prec = max(P.new("length mantissa(%s)" % self._name).sage(), 53) R = RealField(prec) - x, e, b = unparsed_InputForm.lstrip('float(').rstrip(')').split(',') + x, e, b = unparsed_InputForm().lstrip('float(').rstrip(')').split(',') return R(ZZ(x) * ZZ(b)**ZZ(e)) if head == "DoubleFloat": - return RDF(unparsed_InputForm) + return RDF(unparsed_InputForm()) if head == "AlgebraicNumber": - s = unparsed_InputForm[:-len("::AlgebraicNumber()")] + s = unparsed_InputForm()[:-len("::AlgebraicNumber()")] return sage_eval("QQbar(" + s + ")") if head == "IntegerMod" or head == "PrimeField": # one might be tempted not to go via InputForm here, but # it turns out to be safer to do it. - n = unparsed_InputForm[len("index("):] - n = n[:n.find(")")] - return self._get_sage_type(domain)(n) + s = unparsed_InputForm()[len("index("):] + s = s[:s.find(")")] + return self._get_sage_type(domain)(s) + + if head == 'DistributedMultivariatePolynomial': + base_ring = self._get_sage_type(domain[2]) + vars = domain[1].car() + R = PolynomialRing(base_ring, vars) + return R(unparsed_InputForm()) if head == "Polynomial": base_ring = self._get_sage_type(domain[1]) @@ -2039,29 +2050,24 @@ def _sage_(self): # the following is a bad hack, we should be getting a list here vars = P.get_unparsed_InputForm("variables(%s)" % self._name)[1:-1] + s = unparsed_InputForm() if vars == "": - return base_ring(unparsed_InputForm) - else: - R = PolynomialRing(base_ring, vars) - return R(unparsed_InputForm) + return base_ring(s) + + R = PolynomialRing(base_ring, vars) + return R(s) if head in ["OrderedCompletion", "OnePointCompletion"]: # it would be more correct to get the type parameter # (which might not be Expression Integer) and recurse return FriCASElement._sage_expression(P.get_InputForm(self._name)) - if head == "Expression" or head == "Pi": + if head == "Expression" or head == "Pi" or head == "PiDomain": # we treat Expression Integer and Expression Complex # Integer just the same return FriCASElement._sage_expression(P.get_InputForm(self._name)) - if head == 'DistributedMultivariatePolynomial': - base_ring = self._get_sage_type(domain[2]) - vars = domain[1].car() - R = PolynomialRing(base_ring, vars) - return R(unparsed_InputForm) - - raise NotImplementedError("the translation of the FriCAS object %s to sage is not yet implemented" % (unparsed_InputForm)) + raise NotImplementedError("the translation of the FriCAS object %s to sage is not yet implemented" % (unparsed_InputForm())) @instancedoc diff --git a/src/sage/interfaces/gap.py b/src/sage/interfaces/gap.py index ee5861ac785..2e49e4c106f 100644 --- a/src/sage/interfaces/gap.py +++ b/src/sage/interfaces/gap.py @@ -1562,16 +1562,10 @@ def _latex_(self): sage: s = gap("[[1,2], [3/4, 5/6]]") sage: latex(s) - \left(\begin{array}{rr} 1&2\\ 3/4&\frac{5}{6}\\ \end{array}\right) + \left[\left[1, 2\right], \left[\frac{3}{4}, \frac{5}{6}\right]\right] """ - P = self._check_valid() - try: - s = P.eval('LaTeXObj(%s)' % self.name()) - s = s.replace('\\\\', '\\').replace('"', '') - s = s.replace('%\\n', ' ') - return s - except RuntimeError: - return str(self) + from sage.misc.latex import latex + return latex(self._sage_()) @cached_method def _tab_completion(self): diff --git a/src/sage/interfaces/kash.py b/src/sage/interfaces/kash.py index aecddae452b..40d1b7319be 100644 --- a/src/sage/interfaces/kash.py +++ b/src/sage/interfaces/kash.py @@ -527,12 +527,7 @@ def _quit_string(self): return 'quit;' def _start(self): - try: - Expect._start(self) - except RuntimeError: - # TODO: replace this error with something more accurate. - from sage.misc.package import PackageNotFoundError - raise PackageNotFoundError("kash") + Expect._start(self) # Turn off the annoying timer. self.eval('Time(false);') diff --git a/src/sage/interfaces/singular.py b/src/sage/interfaces/singular.py index dedbebab99c..59cd9a8afcd 100644 --- a/src/sage/interfaces/singular.py +++ b/src/sage/interfaces/singular.py @@ -1211,14 +1211,14 @@ def current_ring(self): polynomial ring, over a field, global ordering // coefficients: ZZ/127 // number of vars : 3 - // block 1 : ordering rp + // block 1 : ordering ip // : names x y z // block 2 : ordering C sage: singular.current_ring() polynomial ring, over a field, global ordering // coefficients: ZZ/127 // number of vars : 3 - // block 1 : ordering rp + // block 1 : ordering ip // : names x y z // block 2 : ordering C """ @@ -1402,6 +1402,14 @@ def _repr_(self): if self._name in s: if self.get_custom_name() is None and self.type() == 'matrix': s = self.parent().eval('pmat(%s,20)' % (self.name())) + # compatibility for singular 4.3.2p10 and before + if s.startswith("polynomial ring,"): + from sage.rings.polynomial.term_order import singular_name_mapping + from sage.repl.rich_output import get_display_manager + # this is our cue that singular uses `rp` instead of `ip` + if singular_name_mapping['invlex'] == 'rp' and 'doctest' in str(get_display_manager()): + s = re.sub('^(// .*block.* : ordering )rp$', '\\1ip', + s, 0, re.MULTILINE); return s def __copy__(self): @@ -2025,6 +2033,10 @@ def _sage_(self, R=None): sage: type(singular(int(5)).sage()) + Test that bigintvec can be coerced:: + + sage: singular('hilb((ideal(x)), 1)').sage() + (1, -1, 0, 0, -1, 1, 0) """ typ = self.type() if typ == 'poly': @@ -2040,6 +2052,9 @@ def _sage_(self, R=None): elif typ == 'intvec': from sage.modules.free_module_element import vector return vector([sage.rings.integer.Integer(str(e)) for e in self]) + elif typ == 'bigintvec': + from sage.modules.free_module_element import vector + return vector([sage.rings.rational.Rational(str(e)) for e in self]) elif typ == 'intmat': from sage.matrix.constructor import matrix from sage.rings.integer_ring import ZZ diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index 7ddbe5879c3..097021e2dd4 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -3606,36 +3606,57 @@ def plot(self, gap=0.1, component_gap=0.5, solver=None, # such that the resulting regions are in fact closed regions # with straight angles, and using the minimal number of bends. regions = sorted(self.regions(), key=len) - regions = regions[:-1] edges = list(set(flatten(pd_code))) edges.sort() MLP = MixedIntegerLinearProgram(maximization=False, solver=solver) # v will be the list of variables in the MLP problem. There will be - # two variables for each edge: number of right bendings and number of - # left bendings (at the end, since we are minimizing the total, only one - # of each will be nonzero + # two variables for each edge counting the number of bendings needed. + # The one with even index corresponds to the flow of this number from + # the left-hand-side region to the right-hand-side region if the edge + # is positive oriented. The one with odd index corresponds to the + # flow in the opposite direction. For a negative oriented edge the + # same is true but with exchanged directions. At the end, since we + # are minimizing the total, only one of each will be nonzero. v = MLP.new_variable(nonnegative=True, integer=True) + + def flow_from_source(e): + r""" + Return the flow variable from the source. + """ + if e > 0: + return v[2*edges.index(e)] + else: + return v[2*edges.index(-e)+1] + + def flow_to_sink(e): + r""" + Return the flow variable to the sink. + """ + return flow_from_source(-e) + # one condition for each region - for i in range(len(regions)): - cond = 0 + lr = len(regions) + for i in range(lr): r = regions[i] - for e in r: - if e > 0: - cond = cond + v[2*edges.index(e)] - v[2*edges.index(e) + 1] - else: - cond = cond - v[2*edges.index(-e)] + v[2*edges.index(-e) + 1] - MLP.add_constraint(cond == 4 - len(r)) + if i < lr - 1: + # capacity of interior region, sink if positive, source if negative + capacity = len(r) - 4 + else: + # capacity of exterior region, only sink (added to fix :issue:`37587`). + capacity = len(r) + 4 + flow = sum(flow_to_sink(e) - flow_from_source(e) for e in r) + MLP.add_constraint(flow == capacity) # exterior region only sink + MLP.set_objective(MLP.sum(v.values())) MLP.solve() # we store the result in a vector s packing right bends as negative left ones values = MLP.get_values(v, convert=ZZ, tolerance=1e-3) - s = [values[2*i] - values[2*i + 1] - for i in range(len(edges))] + s = [values[2*i] - values[2*i + 1] for i in range(len(edges))] # segments represents the different parts of the previous edges after bending segments = {e: [(e,i) for i in range(abs(s[edges.index(e)])+1)] for e in edges} pieces = {tuple(i): [i] for j in segments.values() for i in j} nregions = [] - for r in regions: + for r in regions[:-1]: # interior regions nregion = [] for e in r: if e > 0: @@ -3688,16 +3709,16 @@ def plot(self, gap=0.1, component_gap=0.5, solver=None, pieces[tuple(badregion[b][0])].append(N2) if a < b: - r1 = badregion[:a] + [[badregion[a][0],0], [N1,1]] + badregion[b:] - r2 = badregion[a+1:b] + [[N2,1],[N1,1]] + r1 = badregion[:a] + [[badregion[a][0], 0], [N1, 1]] + badregion[b:] + r2 = badregion[a + 1:b] + [[N2, 1],[N1, 1]] else: - r1 = badregion[b:a] + [[badregion[a][0],0], [N1,1]] - r2 = badregion[:b] + [[N2,1],[N1,1]] + badregion[a+1:] + r1 = badregion[b:a] + [[badregion[a][0], 0], [N1, 1]] + r2 = badregion[:b] + [[N2, 1],[N1, 1]] + badregion[a + 1:] if otherregion: c = [x for x in otherregion if badregion[b][0] == x[0]] c = otherregion.index(c[0]) - otherregion.insert(c+1, [N2,otherregion[c][1]]) + otherregion.insert(c + 1, [N2,otherregion[c][1]]) otherregion[c][1] = 0 nregions.remove(badregion) nregions.append(r1) diff --git a/src/sage/libs/eclib/interface.py b/src/sage/libs/eclib/interface.py index 7b2b07b1f3f..6c15997c09f 100644 --- a/src/sage/libs/eclib/interface.py +++ b/src/sage/libs/eclib/interface.py @@ -728,7 +728,7 @@ class mwrank_MordellWeil(SageObject): P1 = [-3:0:1] is generator number 1 saturating up to 20...Saturation index bound (for points of good reduction) = 3 Reducing saturation bound from given value 20 to computed index bound 3 - Tamagawa index primes are [ 2 ] + Tamagawa index primes are [ 2 ]... Checking saturation at [ 2 3 ] Checking 2-saturation Points were proved 2-saturated (max q used = 7) @@ -738,7 +738,7 @@ class mwrank_MordellWeil(SageObject): P2 = [-2:3:1] is generator number 2 saturating up to 20...Saturation index bound (for points of good reduction) = 4 Reducing saturation bound from given value 20 to computed index bound 4 - Tamagawa index primes are [ 2 ] + Tamagawa index primes are [ 2 ]... Checking saturation at [ 2 3 ] Checking 2-saturation possible kernel vector = [1,1] @@ -753,7 +753,7 @@ class mwrank_MordellWeil(SageObject): P3 = [-14:25:8] is generator number 3 saturating up to 20...Saturation index bound (for points of good reduction) = 3 Reducing saturation bound from given value 20 to computed index bound 3 - Tamagawa index primes are [ 2 ] + Tamagawa index primes are [ 2 ]... Checking saturation at [ 2 3 ] Checking 2-saturation Points were proved 2-saturated (max q used = 11) @@ -908,7 +908,7 @@ def process(self, v, saturation_bound=0): saturating basis...Saturation index bound (for points of good reduction) = 93 Only p-saturating for p up to given value 2. The resulting points may not be p-saturated for p between this and the computed index bound 93 - Tamagawa index primes are [ 2 ] + Tamagawa index primes are [ 2 ]... Checking saturation at [ 2 ] Checking 2-saturation possible kernel vector = [1,0,0] @@ -930,7 +930,7 @@ def process(self, v, saturation_bound=0): saturating basis...Saturation index bound (for points of good reduction) = 46 Only p-saturating for p up to given value 3. The resulting points may not be p-saturated for p between this and the computed index bound 46 - Tamagawa index primes are [ 2 ] + Tamagawa index primes are [ 2 ]... Checking saturation at [ 2 3 ] Checking 2-saturation Points were proved 2-saturated (max q used = 11) @@ -954,7 +954,7 @@ def process(self, v, saturation_bound=0): saturating basis...Saturation index bound (for points of good reduction) = 15 Only p-saturating for p up to given value 5. The resulting points may not be p-saturated for p between this and the computed index bound 15 - Tamagawa index primes are [ 2 ] + Tamagawa index primes are [ 2 ]... Checking saturation at [ 2 3 5 ] Checking 2-saturation Points were proved 2-saturated (max q used = 11) @@ -978,7 +978,7 @@ def process(self, v, saturation_bound=0): 0.417143558758384 sage: EQ.saturate() # points are now saturated saturating basis...Saturation index bound (for points of good reduction) = 3 - Tamagawa index primes are [ 2 ] + Tamagawa index primes are [ 2 ]... Checking saturation at [ 2 3 ] Checking 2-saturation Points were proved 2-saturated (max q used = 11) @@ -1189,7 +1189,7 @@ def saturate(self, max_prime=-1, min_prime=2): sage: EQ.saturate() # points are now saturated saturating basis...Saturation index bound (for points of good reduction) = 3 - Tamagawa index primes are [ 2 ] + Tamagawa index primes are [ 2 ]... Checking saturation at [ 2 3 ] Checking 2-saturation Points were proved 2-saturated (max q used = 11) @@ -1217,7 +1217,7 @@ def saturate(self, max_prime=-1, min_prime=2): sage: EQ.saturate() saturating basis...Saturation index bound (for points of good reduction) = 3 - Tamagawa index primes are [ 2 ] + Tamagawa index primes are [ 2 ]... Checking saturation at [ 2 3 ] Checking 2-saturation Points were proved 2-saturated (max q used = 11) diff --git a/src/sage/libs/eclib/mwrank.pyx b/src/sage/libs/eclib/mwrank.pyx index bc475f907b6..c685c329926 100644 --- a/src/sage/libs/eclib/mwrank.pyx +++ b/src/sage/libs/eclib/mwrank.pyx @@ -590,7 +590,7 @@ cdef class _mw: P1 = [-3:0:1] is generator number 1 saturating up to 20...Saturation index bound (for points of good reduction) = 3 Reducing saturation bound from given value 20 to computed index bound 3 - Tamagawa index primes are [ 2 ] + Tamagawa index primes are [ 2 ]... Checking saturation at [ 2 3 ] Checking 2-saturation Points were proved 2-saturated (max q used = 7) @@ -600,7 +600,7 @@ cdef class _mw: P2 = [-2:3:1] is generator number 2 saturating up to 20...Saturation index bound (for points of good reduction) = 4 Reducing saturation bound from given value 20 to computed index bound 4 - Tamagawa index primes are [ 2 ] + Tamagawa index primes are [ 2 ]... Checking saturation at [ 2 3 ] Checking 2-saturation possible kernel vector = [1,1] @@ -615,7 +615,7 @@ cdef class _mw: P3 = [-14:25:8] is generator number 3 saturating up to 20...Saturation index bound (for points of good reduction) = 3 Reducing saturation bound from given value 20 to computed index bound 3 - Tamagawa index primes are [ 2 ] + Tamagawa index primes are [ 2 ]... Checking saturation at [ 2 3 ] Checking 2-saturation Points were proved 2-saturated (max q used = 11) diff --git a/src/sage/libs/ntl/ntl_ZZ_pX.pxd b/src/sage/libs/ntl/ntl_ZZ_pX.pxd index d02596b5128..6c0e227621f 100644 --- a/src/sage/libs/ntl/ntl_ZZ_pX.pxd +++ b/src/sage/libs/ntl/ntl_ZZ_pX.pxd @@ -1,5 +1,6 @@ from sage.libs.ntl.ZZ_pX cimport * from sage.libs.ntl.ntl_ZZ_pContext cimport ntl_ZZ_pContext_class +from sage.rings.integer cimport Integer cdef class ntl_ZZ_pX(): cdef ZZ_pX_c x @@ -7,6 +8,8 @@ cdef class ntl_ZZ_pX(): cdef void setitem_from_int(ntl_ZZ_pX self, long i, int value) noexcept cdef int getitem_as_int(ntl_ZZ_pX self, long i) noexcept cdef ntl_ZZ_pX _new(self) + cdef ntl_ZZ_pX _pow(ntl_ZZ_pX self, long exp) + cdef ntl_ZZ_pX _powmod(ntl_ZZ_pX self, Integer exp, ntl_ZZ_pX modulus) cdef class ntl_ZZ_pX_Modulus(): cdef ZZ_pX_Modulus_c x diff --git a/src/sage/libs/ntl/ntl_ZZ_pX.pyx b/src/sage/libs/ntl/ntl_ZZ_pX.pyx index c93d263cb10..e9a55b48cbc 100644 --- a/src/sage/libs/ntl/ntl_ZZ_pX.pyx +++ b/src/sage/libs/ntl/ntl_ZZ_pX.pyx @@ -29,6 +29,7 @@ include 'decl.pxi' from cpython.object cimport Py_EQ, Py_NE from sage.cpython.string cimport char_to_str from sage.rings.integer cimport Integer +from sage.libs.ntl.convert cimport PyLong_to_ZZ from sage.libs.ntl.ntl_ZZ cimport ntl_ZZ from sage.libs.ntl.ntl_ZZ_p cimport ntl_ZZ_p from sage.libs.ntl.ntl_ZZ_pContext cimport ntl_ZZ_pContext_class @@ -489,9 +490,12 @@ cdef class ntl_ZZ_pX(): sig_off() return r - def __pow__(ntl_ZZ_pX self, long n, ignored): + def __pow__(self, n, modulus): """ - Return the n-th nonnegative power of self. + Return the ``n``-th nonnegative power of ``self``. + + If ``modulus`` is not ``None``, the exponentiation is performed + modulo the polynomial ``modulus``. EXAMPLES:: @@ -499,13 +503,65 @@ cdef class ntl_ZZ_pX(): sage: g = ntl.ZZ_pX([-1,0,1],c) sage: g**10 [1 0 10 0 5 0 0 0 10 0 8 0 10 0 0 0 5 0 10 0 1] + + sage: x = ntl.ZZ_pX([0,1],c) + sage: x**10 + [0 0 0 0 0 0 0 0 0 0 1] + + Modular exponentiation:: + + sage: c = ntl.ZZ_pContext(20) + sage: f = ntl.ZZ_pX([1,0,1],c) + sage: m = ntl.ZZ_pX([1,0,0,0,0,1],c) + sage: pow(f, 123**45, m) + [1 19 3 0 3] + + Modular exponentiation of ``x``:: + + sage: f = ntl.ZZ_pX([0,1],c) + sage: f.is_x() + True + sage: m = ntl.ZZ_pX([1,1,0,0,0,1],c) + sage: pow(f, 123**45, m) + [15 5 5 11] + """ + if modulus is None: + return (self)._pow(n) + else: + return (self)._powmod(Integer(n), modulus) + + cdef ntl_ZZ_pX _pow(ntl_ZZ_pX self, long n): + """ + Compute the ``n``-th power of ``self``. """ if n < 0: raise NotImplementedError + cdef long ln = n #self.c.restore_c() # restored in _new() cdef ntl_ZZ_pX r = self._new() + if self.is_x() and n >= 1: + ZZ_pX_LeftShift(r.x, self.x, n-1) + else: + sig_on() + ZZ_pX_power(r.x, self.x, ln) + sig_off() + return r + + cdef ntl_ZZ_pX _powmod(ntl_ZZ_pX self, Integer n, ntl_ZZ_pX modulus): + r""" + Compute the ``n``-th power of ``self`` modulo a polynomial. + """ + cdef ntl_ZZ_pX r = self._new() + cdef ZZ_c n_ZZ + cdef ZZ_pX_Modulus_c mod + is_x = self.is_x() sig_on() - ZZ_pX_power(r.x, self.x, n) + mpz_to_ZZ(&n_ZZ, (n).value) + ZZ_pX_Modulus_build(mod, modulus.x) + if is_x: + ZZ_pX_PowerXMod_pre(r.x, n_ZZ, mod) + else: + ZZ_pX_PowerMod_pre(r.x, self.x, n_ZZ, mod) sig_off() return r diff --git a/src/sage/libs/singular/decl.pxd b/src/sage/libs/singular/decl.pxd index e36216d6395..3affdb34e11 100644 --- a/src/sage/libs/singular/decl.pxd +++ b/src/sage/libs/singular/decl.pxd @@ -48,6 +48,13 @@ cdef extern from "factory/factory.h": cdef int SW_USE_NTL_SORT cdef extern from "singular/Singular/libsingular.h": + """ + // compatibility for singular 4.3.2p10 and before + #if SINGULAR_VERSION <= 4330 + #define ringorder_ip ringorder_rp + #define BIGINTVEC_CMD INTVEC_CMD + #endif + """ # # OPTIONS @@ -243,7 +250,7 @@ cdef extern from "singular/Singular/libsingular.h": ringorder_s ringorder_lp ringorder_dp - ringorder_rp + ringorder_ip ringorder_Dp ringorder_wp ringorder_Wp @@ -291,6 +298,10 @@ cdef extern from "singular/Singular/libsingular.h": int row int col + cdef cppclass bigintmat: + int (*length)() + number* (*get)(int i) + # omalloc bins ctypedef struct omBin "omBin_s" @@ -921,6 +932,7 @@ cdef extern from "singular/Singular/libsingular.h": cdef int MATRIX_CMD cdef int LIST_CMD cdef int INTVEC_CMD + cdef int BIGINTVEC_CMD cdef int NONE cdef int RESOLUTION_CMD cdef int PACKAGE_CMD diff --git a/src/sage/libs/singular/function.pyx b/src/sage/libs/singular/function.pyx index 4a5ab6d78f6..5c333e8d7e6 100644 --- a/src/sage/libs/singular/function.pyx +++ b/src/sage/libs/singular/function.pyx @@ -98,7 +98,7 @@ from sage.rings.polynomial.multi_polynomial_sequence import PolynomialSequence_g from sage.libs.singular.decl cimport * from sage.libs.singular.option import opt_ctx from sage.libs.singular.polynomial cimport singular_vector_maximal_component -from sage.libs.singular.singular cimport sa2si, si2sa, si2sa_intvec +from sage.libs.singular.singular cimport sa2si, si2sa, si2sa_intvec, si2sa_bigintvec from sage.libs.singular.singular import error_messages from sage.interfaces.singular import get_docstring @@ -954,6 +954,8 @@ cdef class Converter(SageObject): return si2sa(to_convert.data, self._singular_ring, self._sage_ring.base_ring()) elif rtyp == INTVEC_CMD: return si2sa_intvec( to_convert.data) + elif rtyp == BIGINTVEC_CMD: + return si2sa_bigintvec( to_convert.data) elif rtyp == STRING_CMD: # TODO: Need to determine what kind of data can be returned by a # STRING_CMD--is it just ASCII strings or can it be an arbitrary @@ -1048,6 +1050,17 @@ cdef class LibraryCallHandler(BaseCallHandler): """ return False +# mapping int --> string for function arity +arity_dict = { + CMD_1: "CMD_1", + CMD_2: "CMD_2", + CMD_3: "CMD_3", + CMD_12: "CMD_12", + CMD_13: "CMD_13", + CMD_23: "CMD_23", + CMD_123: "CMD_123", + CMD_M: "CMD_M" +} cdef class KernelCallHandler(BaseCallHandler): """ @@ -1125,8 +1138,9 @@ cdef class KernelCallHandler(BaseCallHandler): errorreported += 1 error_messages.append( - "Wrong number of arguments (got {} arguments, arity code is {})" - .format(number_of_arguments, self.arity)) + "Wrong number of arguments (got {} arguments, arity is {})" + .format(number_of_arguments, + arity_dict.get(self.arity) or self.arity)) return NULL cdef bint free_res(self) noexcept: @@ -1231,7 +1245,7 @@ cdef class SingularFunction(SageObject): Traceback (most recent call last): ... RuntimeError: error in Singular function call 'size': - Wrong number of arguments (got 2 arguments, arity code is 302) + Wrong number of arguments (got 2 arguments, arity is CMD_1) sage: size('foobar', ring=P) 6 @@ -1634,17 +1648,17 @@ def singular_function(name): Traceback (most recent call last): ... RuntimeError: error in Singular function call 'factorize': - Wrong number of arguments (got 0 arguments, arity code is 305) + Wrong number of arguments (got 0 arguments, arity is CMD_12) sage: factorize(f, 1, 2) Traceback (most recent call last): ... RuntimeError: error in Singular function call 'factorize': - Wrong number of arguments (got 3 arguments, arity code is 305) + Wrong number of arguments (got 3 arguments, arity is CMD_12) sage: factorize(f, 1, 2, 3) Traceback (most recent call last): ... RuntimeError: error in Singular function call 'factorize': - Wrong number of arguments (got 4 arguments, arity code is 305) + Wrong number of arguments (got 4 arguments, arity is CMD_12) The Singular function ``list`` can be called with any number of arguments:: diff --git a/src/sage/libs/singular/ring.pyx b/src/sage/libs/singular/ring.pyx index 0efff45904d..b3295206e5b 100644 --- a/src/sage/libs/singular/ring.pyx +++ b/src/sage/libs/singular/ring.pyx @@ -16,7 +16,7 @@ AUTHORS: # https://www.gnu.org/licenses/ # **************************************************************************** -from sage.cpython.string cimport str_to_bytes +from sage.cpython.string cimport str_to_bytes, bytes_to_str from sage.libs.gmp.types cimport __mpz_struct from sage.libs.gmp.mpz cimport mpz_init_set_ui @@ -24,7 +24,7 @@ from sage.libs.gmp.mpz cimport mpz_init_set_ui from sage.libs.singular.decl cimport ring, currRing from sage.libs.singular.decl cimport rChangeCurrRing, rComplete, rDelete, idInit from sage.libs.singular.decl cimport omAlloc0, omStrDup, omAlloc -from sage.libs.singular.decl cimport ringorder_dp, ringorder_Dp, ringorder_lp, ringorder_rp, ringorder_ds, ringorder_Ds, ringorder_ls, ringorder_M, ringorder_c, ringorder_C, ringorder_wp, ringorder_Wp, ringorder_ws, ringorder_Ws, ringorder_a, rRingOrder_t +from sage.libs.singular.decl cimport ringorder_dp, ringorder_Dp, ringorder_lp, ringorder_ip, ringorder_ds, ringorder_Ds, ringorder_ls, ringorder_M, ringorder_c, ringorder_C, ringorder_wp, ringorder_Wp, ringorder_ws, ringorder_Ws, ringorder_a, rRingOrder_t from sage.libs.singular.decl cimport prCopyR from sage.libs.singular.decl cimport n_unknown, n_algExt, n_transExt, n_Z, n_Zn, n_Znm, n_Z2m from sage.libs.singular.decl cimport n_coeffType @@ -60,7 +60,7 @@ order_dict = { "dp": ringorder_dp, "Dp": ringorder_Dp, "lp": ringorder_lp, - "rp": ringorder_rp, + "ip": ringorder_ip, "ds": ringorder_ds, "Ds": ringorder_Ds, "ls": ringorder_ls, @@ -71,6 +71,16 @@ order_dict = { "a": ringorder_a, } +cdef extern from "singular/Singular/libsingular.h": + cdef char * rSimpleOrdStr(rRingOrder_t) + +if bytes_to_str(rSimpleOrdStr(ringorder_ip)) == "rp": + # compatibility for singular 4.3.2p10 and before + order_dict["rp"] = ringorder_ip + # also patch term_order mappings + from sage.rings.polynomial import term_order + term_order.singular_name_mapping['invlex'] = 'rp' + term_order.inv_singular_name_mapping['rp'] = 'invlex' ############################################################################# cdef ring *singular_ring_new(base_ring, n, names, term_order) except NULL: diff --git a/src/sage/libs/singular/singular.pxd b/src/sage/libs/singular/singular.pxd index 05f32b68079..ca31d02456c 100644 --- a/src/sage/libs/singular/singular.pxd +++ b/src/sage/libs/singular/singular.pxd @@ -1,4 +1,4 @@ -from sage.libs.singular.decl cimport ring, poly, number, intvec +from sage.libs.singular.decl cimport ring, poly, number, intvec, bigintmat from sage.libs.singular.function cimport Resolution from sage.rings.rational cimport Rational @@ -29,6 +29,7 @@ cdef object si2sa_ZZmod(number *n, ring *_ring, object base) cdef object si2sa_NF(number *n, ring *_ring, object base) cdef object si2sa_intvec(intvec *v) +cdef object si2sa_bigintvec(bigintmat *v) # dispatches to all the above. cdef object si2sa(number *n, ring *_ring, object base) diff --git a/src/sage/libs/singular/singular.pyx b/src/sage/libs/singular/singular.pyx index e256949298e..3074b859529 100644 --- a/src/sage/libs/singular/singular.pyx +++ b/src/sage/libs/singular/singular.pyx @@ -1544,7 +1544,6 @@ cdef inline number *sa2si_ZZmod(IntegerMod_abstract d, ring *_ring) noexcept: sage: P(3) 3 """ - nr2mModul = d.parent().characteristic() if _ring != currRing: rChangeCurrRing(_ring) cdef number *nn @@ -1699,6 +1698,25 @@ cdef object si2sa_intvec(intvec *v): l.append(v.get(r)) return tuple(l) +cdef object si2sa_bigintvec(bigintmat *v): + r""" + create a sage tuple from a singular vector of big integers + + INPUT: + + - ``v`` -- a (pointer to) singular bigintmat + + OUTPUT: + + a sage tuple + """ + cdef int r + cdef list l = list() + for r in range(v.length()): + n = v.get(r) + l.append(si2sa_QQ(n, &n, currRing)) + return tuple(l) + # ============== # Initialisation # ============== diff --git a/src/sage/manifolds/differentiable/affine_connection.py b/src/sage/manifolds/differentiable/affine_connection.py index 1a45b219872..5a3dc99ae6c 100644 --- a/src/sage/manifolds/differentiable/affine_connection.py +++ b/src/sage/manifolds/differentiable/affine_connection.py @@ -149,10 +149,10 @@ class AffineConnection(SageObject): Unset components are initialized to zero:: - sage: nab[:] # list of coefficients relative to the manifold's default vector frame + sage: nab[:] # list of coefficients relative to the manifold's default vector frame [[[0, x^2, 0], [0, 0, 0], [0, 0, 0]], - [[0, 0, 0], [0, 0, 0], [0, 0, 0]], - [[0, 0, 0], [0, 0, y*z], [0, 0, 0]]] + [[0, 0, 0], [0, 0, 0], [0, 0, 0]], + [[0, 0, 0], [0, 0, y*z], [0, 0, 0]]] The treatment of connection coefficients in a given vector frame is similar to that of tensor components; see therefore the class @@ -2377,7 +2377,7 @@ def curvature_form(self, i, j, frame=None): 2-form curvature (1,1) of connection nabla w.r.t. Vector frame (M, (e_1,e_2,e_3)) on the 3-dimensional differentiable manifold M sage: nab.curvature_form(1,1,e).display(e) # long time (if above is skipped) - curvature (1,1) of connection nabla w.r.t. Vector frame + curvature (1,1) of connection nabla w.r.t. Vector frame (M, (e_1,e_2,e_3)) = (y^3*z^4 + 2*x*y*z + (x*y^4 - x*y)*z^2) e^1∧e^2 + (x^4*y*z^2 - x^2*y^2) e^1∧e^3 + (x^5*y*z^3 - x*z^2) e^2∧e^3 diff --git a/src/sage/manifolds/differentiable/bundle_connection.py b/src/sage/manifolds/differentiable/bundle_connection.py index a3353b820cb..8870b408d35 100644 --- a/src/sage/manifolds/differentiable/bundle_connection.py +++ b/src/sage/manifolds/differentiable/bundle_connection.py @@ -98,11 +98,11 @@ class BundleConnection(SageObject, Mutability): sage: nab[e, 1, 1][:] = [x+z, y-z, x*y*z] sage: nab.display() connection (1,1) of bundle connection nabla w.r.t. Local frame - (E|_M, (e_1,e_2)) = (x + z) dx + (y - z) dy + x*y*z dz - connection (1,2) of bundle connection nabla w.r.t. Local frame - (E|_M, (e_1,e_2)) = x*z dx + y*z dy + z^2 dz - connection (2,1) of bundle connection nabla w.r.t. Local frame - (E|_M, (e_1,e_2)) = x dx + x^2 dy + x^3 dz + (E|_M, (e_1,e_2)) = (x + z) dx + (y - z) dy + x*y*z dz + connection (1,2) of bundle connection nabla w.r.t. Local frame + (E|_M, (e_1,e_2)) = x*z dx + y*z dy + z^2 dz + connection (2,1) of bundle connection nabla w.r.t. Local frame + (E|_M, (e_1,e_2)) = x dx + x^2 dy + x^3 dz Notice, when we omit the frame, the default frame of the vector bundle is assumed (in this case ``e``):: @@ -189,14 +189,14 @@ class BundleConnection(SageObject, Mutability): sage: nab.display(frame=f) connection (1,1) of bundle connection nabla w.r.t. Local frame (E|_M, (f_1,f_2)) = ((x^3 + x)*z + 2*x)/(x^2 + 1) dx + y*z dy + z^2 dz - connection (1,2) of bundle connection nabla w.r.t. Local frame - (E|_M, (f_1,f_2)) = -(x^3 + x)*z dx - (x^2 + 1)*y*z dy - + connection (1,2) of bundle connection nabla w.r.t. Local frame + (E|_M, (f_1,f_2)) = -(x^3 + x)*z dx - (x^2 + 1)*y*z dy - (x^2 + 1)*z^2 dz - connection (2,1) of bundle connection nabla w.r.t. Local frame - (E|_M, (f_1,f_2)) = (x*z - x)/(x^2 + 1) dx - + connection (2,1) of bundle connection nabla w.r.t. Local frame + (E|_M, (f_1,f_2)) = (x*z - x)/(x^2 + 1) dx - (x^2 - y*z)/(x^2 + 1) dy - (x^3 - z^2)/(x^2 + 1) dz - connection (2,2) of bundle connection nabla w.r.t. Local frame - (E|_M, (f_1,f_2)) = -x*z dx - y*z dy - z^2 dz + connection (2,2) of bundle connection nabla w.r.t. Local frame + (E|_M, (f_1,f_2)) = -x*z dx - y*z dy - z^2 dz The new connection 1-forms obey the defining formula, too:: @@ -214,14 +214,14 @@ class BundleConnection(SageObject, Mutability): ....: print(Omega(i ,j, e).display()) curvature (1,1) of bundle connection nabla w.r.t. Local frame (E|_M, (e_1,e_2)) = -(x^3 - x*y)*z dx∧dy + (-x^4*z + x*z^2) dx∧dz + - (-x^3*y*z + x^2*z^2) dy∧dz - curvature (1,2) of bundle connection nabla w.r.t. Local frame + (-x^3*y*z + x^2*z^2) dy∧dz + curvature (1,2) of bundle connection nabla w.r.t. Local frame (E|_M, (e_1,e_2)) = -x dx∧dz - y dy∧dz - curvature (2,1) of bundle connection nabla w.r.t. Local frame + curvature (2,1) of bundle connection nabla w.r.t. Local frame (E|_M, (e_1,e_2)) = 2*x dx∧dy + 3*x^2 dx∧dz - curvature (2,2) of bundle connection nabla w.r.t. Local frame + curvature (2,2) of bundle connection nabla w.r.t. Local frame (E|_M, (e_1,e_2)) = (x^3 - x*y)*z dx∧dy + (x^4*z - x*z^2) dx∧dz + - (x^3*y*z - x^2*z^2) dy∧dz + (x^3*y*z - x^2*z^2) dy∧dz The derived forms certainly obey the structure equations, see :meth:`curvature_form` for details:: @@ -483,17 +483,17 @@ def _new_forms(self, frame): sage: forms = nab._new_forms(e) sage: [forms[k] for k in sorted(forms)] [1-form connection (1,1) of bundle connection nabla w.r.t. Local - frame (E|_M, (e_1,e_2)) on the 2-dimensional differentiable - manifold M, - 1-form connection (1,2) of bundle connection nabla w.r.t. Local - frame (E|_M, (e_1,e_2)) on the 2-dimensional differentiable - manifold M, - 1-form connection (2,1) of bundle connection nabla w.r.t. Local - frame (E|_M, (e_1,e_2)) on the 2-dimensional differentiable - manifold M, - 1-form connection (2,2) of bundle connection nabla w.r.t. Local - frame (E|_M, (e_1,e_2)) on the 2-dimensional differentiable - manifold M] + frame (E|_M, (e_1,e_2)) on the 2-dimensional differentiable + manifold M, + 1-form connection (1,2) of bundle connection nabla w.r.t. Local + frame (E|_M, (e_1,e_2)) on the 2-dimensional differentiable + manifold M, + 1-form connection (2,1) of bundle connection nabla w.r.t. Local + frame (E|_M, (e_1,e_2)) on the 2-dimensional differentiable + manifold M, + 1-form connection (2,2) of bundle connection nabla w.r.t. Local + frame (E|_M, (e_1,e_2)) on the 2-dimensional differentiable + manifold M] """ dom = frame._domain @@ -1307,8 +1307,8 @@ def display(self, frame=None, vector_frame=None, chart=None, sage: nab.display() connection (1,1) of bundle connection nabla w.r.t. Local frame (E|_M, (e_1,e_2)) = x dx + y dy + z dz - connection (2,2) of bundle connection nabla w.r.t. Local frame - (E|_M, (e_1,e_2)) = x^2 dx + y^2 dy + z^2 dz + connection (2,2) of bundle connection nabla w.r.t. Local frame + (E|_M, (e_1,e_2)) = x^2 dx + y^2 dy + z^2 dz sage: latex(nab.display()) \begin{array}{lcl} \omega^1_{\ \, 1} = x \mathrm{d} x + y \mathrm{d} y + z \mathrm{d} z \\ \omega^2_{\ \, 2} = x^{2} diff --git a/src/sage/manifolds/differentiable/diff_map.py b/src/sage/manifolds/differentiable/diff_map.py index 8a64694b38b..1c2d71d23b4 100644 --- a/src/sage/manifolds/differentiable/diff_map.py +++ b/src/sage/manifolds/differentiable/diff_map.py @@ -132,9 +132,9 @@ class is sage: Phi.display() Phi: S^2 → R^3 on U: (x, y) ↦ (X, Y, Z) = (2*x/(x^2 + y^2 + 1), 2*y/(x^2 + y^2 + 1), - (x^2 + y^2 - 1)/(x^2 + y^2 + 1)) + (x^2 + y^2 - 1)/(x^2 + y^2 + 1)) on V: (u, v) ↦ (X, Y, Z) = (2*u/(u^2 + v^2 + 1), 2*v/(u^2 + v^2 + 1), - -(u^2 + v^2 - 1)/(u^2 + v^2 + 1)) + -(u^2 + v^2 - 1)/(u^2 + v^2 + 1)) It is possible to create the map via the method :meth:`~sage.manifolds.differentiable.manifold.DifferentiableManifold.diff_map` @@ -162,7 +162,7 @@ class is sage: Phi1.display() Phi: S^2 → R^3 on U: (x, y) ↦ (X, Y, Z) = (2*x/(x^2 + y^2 + 1), 2*y/(x^2 + y^2 + 1), - (x^2 + y^2 - 1)/(x^2 + y^2 + 1)) + (x^2 + y^2 - 1)/(x^2 + y^2 + 1)) The definition can be completed by means of the method :meth:`~sage.manifolds.continuous_map.ContinuousMap.add_expr`:: @@ -172,9 +172,9 @@ class is sage: Phi1.display() Phi: S^2 → R^3 on U: (x, y) ↦ (X, Y, Z) = (2*x/(x^2 + y^2 + 1), 2*y/(x^2 + y^2 + 1), - (x^2 + y^2 - 1)/(x^2 + y^2 + 1)) + (x^2 + y^2 - 1)/(x^2 + y^2 + 1)) on V: (u, v) ↦ (X, Y, Z) = (2*u/(u^2 + v^2 + 1), 2*v/(u^2 + v^2 + 1), - -(u^2 + v^2 - 1)/(u^2 + v^2 + 1)) + -(u^2 + v^2 - 1)/(u^2 + v^2 + 1)) At this stage, ``Phi1`` and ``Phi`` are fully equivalent:: @@ -226,6 +226,14 @@ class is Basis (∂/∂X,∂/∂Y,∂/∂Z) on the Tangent space at Point Phi(N) on the 3-dimensional differentiable manifold R^3 + A convenient way to display the matrix of the differential:: + + sage: Phi.differential(np).display() + ∂/∂u ∂/∂v + ∂/∂X⎛ 2 0⎞ + ∂/∂Y⎜ 0 2⎟ + ∂/∂Z⎝ 0 0⎠ + Differentiable maps can be composed by means of the operator ``*``: let us introduce the map `\RR^3\rightarrow \RR^2` corresponding to the projection from the point `(X,Y,Z)=(0,0,1)` onto the equatorial plane diff --git a/src/sage/manifolds/differentiable/differentiable_submanifold.py b/src/sage/manifolds/differentiable/differentiable_submanifold.py index 7387ea3a44e..aec0dce99e8 100644 --- a/src/sage/manifolds/differentiable/differentiable_submanifold.py +++ b/src/sage/manifolds/differentiable/differentiable_submanifold.py @@ -69,14 +69,14 @@ class DifferentiableSubmanifold(DifferentiableManifold, TopologicalSubmanifold): - ``field`` -- field `K` on which the sub manifold is defined; allowed values are - - ``'real'`` or an object of type ``RealField`` (e.g., ``RR``) for - a manifold over `\RR` - - ``'complex'`` or an object of type ``ComplexField`` (e.g., ``CC``) - for a manifold over `\CC` - - an object in the category of topological fields (see - :class:`~sage.categories.fields.Fields` and - :class:`~sage.categories.topological_spaces.TopologicalSpaces`) - for other types of manifolds + - ``'real'`` or an object of type ``RealField`` (e.g., ``RR``) for + a manifold over `\RR` + - ``'complex'`` or an object of type ``ComplexField`` (e.g., ``CC``) + for a manifold over `\CC` + - an object in the category of topological fields (see + :class:`~sage.categories.fields.Fields` and + :class:`~sage.categories.topological_spaces.TopologicalSpaces`) + for other types of manifolds - ``structure`` -- manifold structure (see :class:`~sage.manifolds.structure.TopologicalStructure` or @@ -136,7 +136,7 @@ class DifferentiableSubmanifold(DifferentiableManifold, TopologicalSubmanifold): sage: phi_inv_t = M.scalar_field({CM: z-x^2-y^2}) sage: phi_inv_t.display() M → ℝ - (x, y, z) ↦ -x^2 - y^2 + z + (x, y, z) ↦ -x^2 - y^2 + z `\phi` can then be declared as an embedding `N\to M`:: diff --git a/src/sage/manifolds/differentiable/multivectorfield.py b/src/sage/manifolds/differentiable/multivectorfield.py index 9a4b03fb369..8de30fdd711 100644 --- a/src/sage/manifolds/differentiable/multivectorfield.py +++ b/src/sage/manifolds/differentiable/multivectorfield.py @@ -1040,8 +1040,7 @@ def wedge(self, other): sage: b.display() b = y^2 ∂/∂x∧∂/∂y + (x + z) ∂/∂x∧∂/∂z + z^2 ∂/∂y∧∂/∂z sage: s = a.wedge(b); s - 3-vector field a∧b on the 3-dimensional differentiable - manifold M + 3-vector field a∧b on the 3-dimensional differentiable manifold M sage: s.display() a∧b = (-x^2 + (y^3 - x - 1)*z + 2*z^2 - x) ∂/∂x∧∂/∂y∧∂/∂z diff --git a/src/sage/manifolds/differentiable/tangent_space.py b/src/sage/manifolds/differentiable/tangent_space.py index 98a60cede2a..a938d77e237 100644 --- a/src/sage/manifolds/differentiable/tangent_space.py +++ b/src/sage/manifolds/differentiable/tangent_space.py @@ -177,28 +177,32 @@ class TangentSpace(FiniteRankFreeModule): [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 + 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) + sage: 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] + 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 + 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) + sage: 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] + 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) @@ -211,7 +215,8 @@ class TangentSpace(FiniteRankFreeModule): 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 + 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] diff --git a/src/sage/manifolds/differentiable/tensorfield.py b/src/sage/manifolds/differentiable/tensorfield.py index df54270dde0..d2135c1e5a9 100644 --- a/src/sage/manifolds/differentiable/tensorfield.py +++ b/src/sage/manifolds/differentiable/tensorfield.py @@ -4695,6 +4695,20 @@ def apply_map(self, fun, frame=None, chart=None, sage: v.display(X.frame(), X) (x + y)*(x - y) ∂/∂x + 2*pi*(pi - 1)*x ∂/∂y + TESTS: + + Check that the cached quantities derived from the components are + erased:: + + sage: w = M.vector_field(a*x, 0) + sage: diff(w[[0]]).display() + a dx + sage: w.apply_map(lambda t: t.subs(a=-2)) + sage: w.display() + -2*x ∂/∂x + sage: diff(w[[0]]).display() + -2 dx + """ # The dictionary of components w.r.t. frame: if keep_other_components: @@ -4712,3 +4726,4 @@ def apply_map(self, fun, frame=None, chart=None, for ch, fct in scalar._express.items(): cfunc_dict[ch] = ch.function(fun(fct.expr())) scalar._express = cfunc_dict + scalar._del_derived() diff --git a/src/sage/manifolds/differentiable/vector_bundle.py b/src/sage/manifolds/differentiable/vector_bundle.py index 18d1169064d..36a3c48c712 100644 --- a/src/sage/manifolds/differentiable/vector_bundle.py +++ b/src/sage/manifolds/differentiable/vector_bundle.py @@ -768,7 +768,7 @@ def set_change_of_frame(self, frame1, frame2, change_of_frame, :class:`~sage.tensor.modules.free_module_automorphism.FreeModuleAutomorphism` describing the automorphism `P` that relates the basis `(e_i)` to the basis `(f_i)` according to `f_i = P(e_i)` - - ``compute_inverse`` (default: True) -- if set to True, the inverse + - ``compute_inverse`` (default: ``True``) -- if set to ``True``, the inverse automorphism is computed and the change from basis `(f_i)` to `(e_i)` is set to it in the internal dictionary ``self._frame_changes`` @@ -1476,7 +1476,7 @@ def ambient_domain(self): sage: Phi.display() Phi: R → M t ↦ (x, y) = (cos(t), sin(t)) - sage: PhiT11 = R.tensor_bundle(1, 1, dest_map=Phi) + sage: PhiT11 = R.tensor_bundle(1, 1, dest_map=Phi) sage: PhiT11.ambient_domain() 2-dimensional differentiable manifold M @@ -1724,15 +1724,15 @@ def orientation(self): [] sage: T11.set_orientation([c_xy.frame(), c_uv.frame()]) sage: T11.orientation() - [Coordinate frame (U, (∂/∂x,∂/∂y)), Coordinate frame - (V, (∂/∂u,∂/∂v))] + [Coordinate frame (U, (∂/∂x,∂/∂y)), + Coordinate frame (V, (∂/∂u,∂/∂v))] If the destination map is the identity, the orientation is automatically set for the manifold, too:: sage: M.orientation() - [Coordinate frame (U, (∂/∂x,∂/∂y)), Coordinate frame - (V, (∂/∂u,∂/∂v))] + [Coordinate frame (U, (∂/∂x,∂/∂y)), + Coordinate frame (V, (∂/∂u,∂/∂v))] Conversely, if one sets an orientation on the manifold, the orientation on its tensor bundles is set accordingly:: @@ -1740,8 +1740,8 @@ def orientation(self): sage: c_tz. = U.chart() sage: M.set_orientation([c_tz, c_uv]) sage: T11.orientation() - [Coordinate frame (U, (∂/∂t,∂/∂z)), Coordinate frame - (V, (∂/∂u,∂/∂v))] + [Coordinate frame (U, (∂/∂t,∂/∂z)), + Coordinate frame (V, (∂/∂u,∂/∂v))] """ if self._dest_map.is_identity(): diff --git a/src/sage/matrix/args.pxd b/src/sage/matrix/args.pxd index 9ab004e1887..8deb4d434c9 100644 --- a/src/sage/matrix/args.pxd +++ b/src/sage/matrix/args.pxd @@ -47,6 +47,7 @@ cdef class MatrixArgs: cdef public Parent space # parent of matrix cdef public Parent base # parent of entries cdef public long nrows, ncols + cdef public object row_keys, column_keys cdef public object entries cdef entries_type typ cdef public bint sparse @@ -54,6 +55,7 @@ cdef class MatrixArgs: cdef bint is_finalized cpdef Matrix matrix(self, bint convert=?) + cpdef element(self, bint immutable=?) cpdef list list(self, bint convert=?) cpdef dict dict(self, bint convert=?) @@ -89,7 +91,11 @@ cdef class MatrixArgs: raise ArithmeticError("number of columns must be non-negative") cdef long p = self.ncols if p != -1 and p != n: - raise ValueError(f"inconsistent number of columns: should be {p} but got {n}") + raise ValueError(f"inconsistent number of columns: should be {p} " + f"but got {n}") + if self.column_keys is not None and n != len(self.column_keys): + raise ValueError(f"inconsistent number of columns: should be cardinality of {self.column_keys} " + f"but got {n}") self.ncols = n cdef inline int set_nrows(self, long n) except -1: @@ -102,8 +108,23 @@ cdef class MatrixArgs: cdef long p = self.nrows if p != -1 and p != n: raise ValueError(f"inconsistent number of rows: should be {p} but got {n}") + if self.row_keys is not None and n != len(self.row_keys): + raise ValueError(f"inconsistent number of rows: should be cardinality of {self.row_keys} " + f"but got {n}") self.nrows = n + cdef inline int _ensure_nrows_ncols(self) except -1: + r""" + Make sure that the number of rows and columns is set. + If ``row_keys`` or ``column_keys`` is not finite, this can raise an exception. + """ + if self.nrows == -1: + self.nrows = len(self.row_keys) + if self.ncols == -1: + self.ncols = len(self.column_keys) + + cpdef int set_column_keys(self, column_keys) except -1 + cpdef int set_row_keys(self, row_keys) except -1 cpdef int set_space(self, space) except -1 cdef int finalize(self) except -1 diff --git a/src/sage/matrix/args.pyx b/src/sage/matrix/args.pyx index 6e272858156..b01f3466998 100644 --- a/src/sage/matrix/args.pyx +++ b/src/sage/matrix/args.pyx @@ -312,7 +312,8 @@ cdef class MatrixArgs: self.sparse = -1 self.kwds = {} - def __init__(self, *args, base_ring=None, nrows=None, ncols=None, entries=None, sparse=None, space=None, **kwds): + def __init__(self, *args, base_ring=None, nrows=None, ncols=None, entries=None, + sparse=None, row_keys=None, column_keys=None, space=None, **kwds): """ Parse arguments for creating a new matrix. @@ -325,21 +326,27 @@ cdef class MatrixArgs: sage: from sage.matrix.args import MatrixArgs sage: MatrixArgs().finalized() - + sage: MatrixArgs(1).finalized() - + sage: MatrixArgs(1, 1, 3).finalized() - + sage: MatrixArgs(1, 1, 1, 1).finalized() Traceback (most recent call last): ... TypeError: too many arguments in matrix constructor sage: MatrixArgs(3, nrows=1, ncols=1).finalized() - + sage: MatrixArgs(3, nrows=1).finalized() - + sage: MatrixArgs(3, ncols=1).finalized() - + """ if "ring" in kwds.keys(): deprecation_cython(issue_number=33380, message="ring is deprecated (keyword will be removed in the future). Use base_ring instead", stacklevel=3) @@ -354,6 +361,10 @@ cdef class MatrixArgs: self.entries = entries if sparse is not None: self.sparse = sparse + if row_keys is not None: + self.set_row_keys(row_keys) + if column_keys is not None: + self.set_column_keys(column_keys) if space is not None: self.set_space(space) self.kwds.update(kwds) @@ -369,18 +380,20 @@ cdef class MatrixArgs: if self.entries is None and isinstance(arg, (list, tuple, dict)): self.entries = arg argc -= 1 - if argi == argc: return + if argi == argc: + return # check for base ring argument if self.base is None and isinstance(args[argi], Parent): self.base = args[argi] argi += 1 - if argi == argc: return + if argi == argc: + return # check nrows and ncols argument cdef int k cdef long v - if self.nrows == -1 and self.ncols == -1: + if self.nrows == -1 and self.ncols == -1 and self.row_keys is None and self.column_keys is None: for k in range(2): arg = args[argi] if is_numpy_type(type(arg)): @@ -544,12 +557,14 @@ cdef class MatrixArgs: if sparse: pass else: + self._ensure_nrows_ncols() zero = self.base.zero() for i in range(self.nrows): for j in range(self.ncols): sig_check() yield zero elif self.typ == MA_ENTRIES_SCALAR: + self._ensure_nrows_ncols() diag = self.entries if convert and self.need_to_convert(diag): diag = self.base(diag) @@ -564,6 +579,7 @@ cdef class MatrixArgs: sig_check() yield diag if (i == j) else zero elif self.typ == MA_ENTRIES_SEQ_SEQ: + self._ensure_nrows_ncols() row_iter = sized_iter(self.entries, self.nrows) for i in range(self.nrows): row = sized_iter(next(row_iter), self.ncols) @@ -577,6 +593,7 @@ cdef class MatrixArgs: else: yield x elif self.typ == MA_ENTRIES_SEQ_FLAT: + self._ensure_nrows_ncols() it = sized_iter(self.entries, self.nrows * self.ncols) for i in range(self.nrows): for j in range(self.ncols): @@ -600,8 +617,14 @@ cdef class MatrixArgs: raise TypeError("dense iteration is not supported for sparse input") elif self.typ == MA_ENTRIES_CALLABLE: f = self.entries - for i in range(self.nrows): - for j in range(self.ncols): + row_keys = self.row_keys + if row_keys is None: + row_keys = range(self.nrows) + column_keys = self.column_keys + if column_keys is None: + column_keys = range(self.ncols) + for i in row_keys: + for j in column_keys: sig_check() x = f(i, j) if convert and self.need_to_convert(x): @@ -634,6 +657,7 @@ cdef class MatrixArgs: 42 """ self.finalize() + self._ensure_nrows_ncols() return self.nrows * self.ncols cpdef Matrix matrix(self, bint convert=True): @@ -645,8 +669,8 @@ cdef class MatrixArgs: INPUT: - - ``convert`` -- if True, the matrix is guaranteed to have - the correct parent matrix space. If False, the input matrix + - ``convert`` -- if ``True``, the matrix is guaranteed to have + the correct parent matrix space. If ``False``, the input matrix may be returned even if it lies in the wrong space. .. NOTE:: @@ -720,13 +744,67 @@ cdef class MatrixArgs: M = M.__copy__() break else: - M = self.space(self, coerce=convert) + space = self.space + if not isinstance(space, MatrixSpace): + space = space.zero().matrix(side='left').parent() + M = space(self, coerce=convert) # Also store the matrix to support multiple calls of matrix() self.entries = M self.typ = MA_ENTRIES_MATRIX return M + cpdef element(self, bint immutable=False): + r""" + Return the matrix or morphism. + + INPUT: + + - ``immutable`` -- boolean; if ``True``, the result will be immutable. + + OUTPUT: an element of ``self.space`` + + .. NOTE:: + + This may change ``self.entries``, making it unsafe to access the + ``self.entries`` attribute after calling this method. + + EXAMPLES:: + + sage: from sage.matrix.args import MatrixArgs + sage: M = matrix(2, 3, range(6), sparse=True) + sage: ma = MatrixArgs(M); ma.finalized() + + sage: M2 = ma.element(immutable=True); M2.parent() + Full MatrixSpace of 2 by 3 sparse matrices over Integer Ring + sage: M2.is_immutable() + True + + sage: ma = MatrixArgs(M, row_keys=['u','v'], column_keys=['a','b','c']) + sage: ma.finalized() + + sage: phi = ma.element(); phi + Generic morphism: + From: Free module generated by {'a', 'b', 'c'} over Integer Ring + To: Free module generated by {'u', 'v'} over Integer Ring + """ + self.finalize() + cdef Matrix M = self.matrix(convert=True) + if immutable: + M.set_immutable() + if isinstance(self.space, MatrixSpace): + return M + return self.space(matrix=M, side='left') + cpdef list list(self, bint convert=True): """ Return the entries of the matrix as a flat list of scalars. @@ -736,7 +814,7 @@ cdef class MatrixArgs: INPUT: - - ``convert`` -- If True, the entries are converted to the base + - ``convert`` -- If ``True``, the entries are converted to the base ring. Otherwise, the entries are returned as given. .. NOTE:: @@ -796,11 +874,12 @@ cdef class MatrixArgs: cpdef dict dict(self, bint convert=True): """ - Return the entries of the matrix as a dict. The keys of this - dict are the non-zero positions ``(i,j)``. The corresponding - value is the entry at that position. Zero values are skipped. + Return the entries of the matrix as a :class:`dict`. + + The keys of this :class:`dict` are the non-zero positions ``(i,j)``. The + corresponding value is the entry at that position. Zero values are skipped. - If ``convert`` is True, the entries are converted to the base + If ``convert`` is ``True``, the entries are converted to the base ring. Otherwise, the entries are returned as given. EXAMPLES:: @@ -828,13 +907,84 @@ cdef class MatrixArgs: D[se.i, se.j] = x return D + cpdef int set_column_keys(self, column_keys) except -1: + """ + Set the column keys with consistency checking. + + If the value was previously set, it must remain the same. + + EXAMPLES:: + + sage: from sage.matrix.args import MatrixArgs + sage: ma = MatrixArgs(2, 4) + sage: ma.set_column_keys('xyz') + Traceback (most recent call last): + ... + ValueError: inconsistent column keys: should be of cardinality 4 but got xyz + sage: ma.set_column_keys('abcd') + 0 + sage: ma.finalized() + + """ + if self.column_keys is not None and self.column_keys != column_keys: + raise ValueError(f"inconsistent column keys: should be {self.column_keys} " + f"but got {column_keys}") + cdef long p = self.ncols + if p != -1 and p != len(column_keys): + raise ValueError(f"inconsistent column keys: should be of cardinality {self.ncols} " + f"but got {column_keys}") + self.column_keys = column_keys + + cpdef int set_row_keys(self, row_keys) except -1: + """ + Set the row keys with consistency checking. + + If the value was previously set, it must remain the same. + + EXAMPLES:: + + sage: from sage.matrix.args import MatrixArgs + sage: ma = MatrixArgs(2, 4) + sage: ma.set_row_keys('xyz') + Traceback (most recent call last): + ... + ValueError: inconsistent row keys: should be of cardinality 2 but got xyz + sage: ma.set_row_keys(['u', 'v']) + 0 + sage: ma.finalized() + + """ + if self.row_keys is not None and self.row_keys != row_keys: + raise ValueError(f"inconsistent row keys: should be {self.row_keys} " + f"but got {row_keys}") + if self.nrows != -1 and self.nrows != len(row_keys): + raise ValueError(f"inconsistent row keys: should be of cardinality {self.nrows} " + f"but got {row_keys}") + self.row_keys = row_keys + cpdef int set_space(self, space) except -1: """ Set inputs from a given matrix space. INPUT: - - ``space`` -- a :class:`MatrixSpace` + - ``space`` -- a :class:`MatrixSpace` or a homset of modules with basis EXAMPLES:: @@ -851,12 +1001,37 @@ cdef class MatrixArgs: [0 0] sage: M.parent() is S True + + From a homset:: + + sage: C = CombinatorialFreeModule(ZZ, ['a', 'b', 'c']) + sage: R = CombinatorialFreeModule(ZZ, ['u', 'v']) + sage: S = Hom(C, R); S + Set of Morphisms + from Free module generated by {'a', 'b', 'c'} over Integer Ring + to Free module generated by {'u', 'v'} over Integer Ring + in Category of finite dimensional modules with basis over Integer Ring + sage: ma = MatrixArgs() + sage: _ = ma.set_space(S) + sage: ma.finalized() + """ + if self.space is not None: + return 0 # TODO: ?????? self.space = space - self.set_nrows(space.nrows()) - self.set_ncols(space.ncols()) - self.base = space._base - self.sparse = space.is_sparse() + try: + self.set_nrows(space.nrows()) + self.set_ncols(space.ncols()) + self.base = space._base + self.sparse = space.is_sparse() + except AttributeError: + self.set_row_keys(space.codomain().basis().keys()) + self.set_column_keys(space.domain().basis().keys()) + self.base = space.base_ring() def finalized(self): """ @@ -938,6 +1113,7 @@ cdef class MatrixArgs: # Can we assume a square matrix? if self.typ & MA_FLAG_ASSUME_SQUARE: + # TODO: Handle column_keys/row_keys if self.ncols == -1: if self.nrows != -1: self.ncols = self.nrows @@ -970,7 +1146,7 @@ cdef class MatrixArgs: # Error if size is required if self.typ & MA_FLAG_DIM_REQUIRED: - if self.nrows == -1 or self.ncols == -1: + if (self.nrows == -1 and self.row_keys is None) or (self.ncols == -1 and self.column_keys is None): raise TypeError("the dimensions of the matrix must be specified") # Determine base in easy cases @@ -986,7 +1162,9 @@ cdef class MatrixArgs: if self.base is None: raise TypeError(f"unable to determine base of {self.entries!r}") - if self.nrows == -1 or self.ncols == -1 or self.base is None: + if ((self.nrows == -1 and self.row_keys is None) + or (self.ncols == -1 and self.column_keys is None) + or self.base is None): # Determine dimensions or base in the cases where we # really need to look at the entries. if self.typ == MA_ENTRIES_SEQ_SEQ: @@ -1006,9 +1184,9 @@ cdef class MatrixArgs: self.typ = MA_ENTRIES_ZERO except Exception: # "not self.entries" has failed, self.entries cannot be determined to be zero - if self.nrows != self.ncols: + if self.nrows != self.ncols or self.row_keys != self.column_keys: raise TypeError("scalar matrix must be square if the value cannot be determined to be zero") - if self.typ == MA_ENTRIES_SCALAR and self.nrows != self.ncols: + if self.typ == MA_ENTRIES_SCALAR and (self.nrows != self.ncols or self.row_keys != self.column_keys): # self.typ is still SCALAR -> "not self.entries" has successfully evaluated, to False raise TypeError("nonzero scalar matrix must be square") @@ -1019,8 +1197,17 @@ cdef class MatrixArgs: global MatrixSpace if MatrixSpace is None: from sage.matrix.matrix_space import MatrixSpace - self.space = MatrixSpace(self.base, self.nrows, self.ncols, - sparse=self.sparse, **self.kwds) + nrows = self.nrows + if nrows == -1: + nrows = None + ncols = self.ncols + if ncols == -1: + ncols = None + self.space = MatrixSpace(self.base, nrows, ncols, + sparse=self.sparse, + row_keys=self.row_keys, + column_keys=self.column_keys, + **self.kwds) self.is_finalized = True @@ -1190,10 +1377,11 @@ cdef class MatrixArgs: e = PySequence_Fast(self.entries, "not a sequence") self.set_nrows(len(e)) if self.nrows == 0: - if self.ncols == -1: self.ncols = 0 + if self.ncols == -1 and self.column_keys is None: + self.set_ncols(0) self.setdefault_base(ZZ) return 0 - elif self.ncols != -1 and self.base is not None: + elif (self.ncols != -1 or self.column_keys is not None) and self.base is not None: # Everything known => OK return 0 @@ -1239,14 +1427,20 @@ cdef class MatrixArgs: self.nrows = 0 if self.ncols == -1: self.ncols = 0 - elif self.ncols == -1: + elif self.ncols == -1 and self.column_keys is None: if self.nrows == -1: - # Assume row matrix - self.nrows = 1 - self.ncols = N + if self.row_keys is None: + # Assume row matrix + self.nrows = 1 + self.ncols = N + else: + self.nrows = len(self.row_keys) + self.ncols = N // self.nrows else: self.ncols = N // self.nrows - elif self.nrows == -1: + elif self.nrows == -1 and self.row_keys is None: + if self.ncols == -1: + self.ncols = len(self.column_keys) self.nrows = N // self.ncols self.set_seq_flat(entries) diff --git a/src/sage/matrix/constructor.pyx b/src/sage/matrix/constructor.pyx index d7b209b93ca..3e8235cb94f 100644 --- a/src/sage/matrix/constructor.pyx +++ b/src/sage/matrix/constructor.pyx @@ -37,7 +37,7 @@ def matrix(*args, **kwds): INPUT: - The matrix command takes the entries of a matrix, optionally + The :func:`matrix` command takes the entries of a matrix, optionally preceded by a ring and the dimensions of the matrix, and returns a matrix. @@ -49,11 +49,11 @@ def matrix(*args, **kwds): columns. You can create a matrix of zeros by passing an empty list or the integer zero for the entries. To construct a multiple of the identity (`cI`), you can specify square dimensions and pass in - `c`. Calling matrix() with a Sage object may return something that - makes sense. Calling matrix() with a NumPy array will convert the + `c`. Calling :func:`matrix` with a Sage object may return something that + makes sense. Calling :func:`matrix` with a NumPy array will convert the array to a matrix. - All arguments (even the positional) are optional. + All arguments (even the positional ones) are optional. Positional and keyword arguments: @@ -62,14 +62,19 @@ def matrix(*args, **kwds): determine this from the given entries, falling back to ``ZZ`` if no entries are given. - - ``nrows`` -- the number of rows in the matrix. + - ``nrows`` or ``row_keys`` -- the number of rows in the matrix, + or a finite or enumerated family of arbitrary objects + that index the rows of the matrix - - ``ncols`` -- the number of columns in the matrix. + - ``ncols`` or ``column_keys`` -- the number of columns in the + matrix, or a finite or enumerated family of arbitrary objects + that index the columns of the matrix - ``entries`` -- see examples below. - If either ``nrows`` or ``ncols`` is given as keyword argument, then - no positional arguments ``nrows`` and ``ncols`` may be given. + If any of ``nrows``, ``ncols``, ``row_keys``, ``column_keys`` is + given as keyword argument, then none of these may be given as + positional arguments. Keyword-only arguments: @@ -78,16 +83,15 @@ def matrix(*args, **kwds): defaults to ``False``. - ``space`` -- matrix space which will be the parent of the output - matrix. This determines ``base_ring``, ``nrows``, ``ncols`` and - ``sparse``. + matrix. This determines ``base_ring``, ``nrows``, ``row_keys``, + ``ncols``, ``column_keys``, and ``sparse``. - ``immutable`` -- (boolean) make the matrix immutable. By default, the output matrix is mutable. - OUTPUT: - - a matrix + OUTPUT: a matrix or, more generally, a homomorphism between free + modules EXAMPLES:: @@ -135,8 +139,8 @@ def matrix(*args, **kwds): :: - sage: v1=vector((1,2,3)) - sage: v2=vector((4,5,6)) + sage: v1 = vector((1,2,3)) + sage: v2 = vector((4,5,6)) sage: m = matrix([v1,v2]); m; m.parent() [1 2 3] [4 5 6] @@ -144,28 +148,28 @@ def matrix(*args, **kwds): :: - sage: m = matrix(QQ,2,[1,2,3,4,5,6]); m; m.parent() + sage: m = matrix(QQ, 2, [1,2,3,4,5,6]); m; m.parent() [1 2 3] [4 5 6] Full MatrixSpace of 2 by 3 dense matrices over Rational Field :: - sage: m = matrix(QQ,2,3,[1,2,3,4,5,6]); m; m.parent() + sage: m = matrix(QQ, 2, 3, [1,2,3,4,5,6]); m; m.parent() [1 2 3] [4 5 6] Full MatrixSpace of 2 by 3 dense matrices over Rational Field :: - sage: m = matrix({(0,1): 2, (1,1):2/5}); m; m.parent() + sage: m = matrix({(0,1): 2, (1,1): 2/5}); m; m.parent() [ 0 2] [ 0 2/5] Full MatrixSpace of 2 by 2 sparse matrices over Rational Field :: - sage: m = matrix(QQ,2,3,{(1,1): 2}); m; m.parent() + sage: m = matrix(QQ, 2, 3, {(1,1): 2}); m; m.parent() [0 0 0] [0 2 0] Full MatrixSpace of 2 by 3 sparse matrices over Rational Field @@ -234,13 +238,25 @@ def matrix(*args, **kwds): :: - sage: M = Matrix([[1,2,3],[4,5,6],[7,8,9]], immutable=True) + sage: M = Matrix([[1,2,3], [4,5,6], [7,8,9]], immutable=True) sage: M[0] = [9,9,9] Traceback (most recent call last): ... ValueError: matrix is immutable; please change a copy instead (i.e., use copy(M) to change a copy of M). + Using ``row_keys`` and ``column_keys``:: + + sage: M = matrix([[1,2,3], [4,5,6]], + ....: column_keys=['a','b','c'], row_keys=['u','v']); M + Generic morphism: + From: Free module generated by {'a', 'b', 'c'} over Integer Ring + To: Free module generated by {'u', 'v'} over Integer Ring + sage: print(M._unicode_art_matrix()) + a b c + u⎛1 2 3⎞ + v⎝4 5 6⎠ + TESTS: There are many ways to create an empty matrix:: @@ -645,10 +661,7 @@ def matrix(*args, **kwds): :class:`MatrixArgs`, see :issue:`24742` """ immutable = kwds.pop('immutable', False) - M = MatrixArgs(*args, **kwds).matrix() - if immutable: - M.set_immutable() - return M + return MatrixArgs(*args, **kwds).element(immutable=immutable) Matrix = matrix diff --git a/src/sage/matrix/matrix1.pyx b/src/sage/matrix/matrix1.pyx index d446030473f..082664500e9 100644 --- a/src/sage/matrix/matrix1.pyx +++ b/src/sage/matrix/matrix1.pyx @@ -1925,7 +1925,7 @@ cdef class Matrix(Matrix0): sage: D = A.augment(B) Traceback (most recent call last): ... - TypeError: not a constant polynomial + TypeError: y is not a constant polynomial sage: E = A.change_ring(R) sage: F = E.augment(B); F diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index c3f621ec8ef..9a7fdd393c5 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -9964,6 +9964,33 @@ cdef class Matrix(Matrix1): chi = self.charpoly() return chi.is_monomial() + def is_semisimple(self) -> bool: + r""" + Return if ``self`` is semisimple. + + A (square) matrix `A` is *semisimple* if the + :meth:`minimal polynomial ` of `A` is sqaure-free. + + If `A` represents a linear map from `F^n \to F^n` for some field `F`, + then this is equivalent to every `A`-invariant subspace of `F^n` + has a complementary `A`-invariant subspace. This is also equivalent + to saying the matrix is diagonalizable over `\bar{F}`, the algebraic + closure of `F`. + + EXAMPLES:: + + sage: A = matrix([[0, -1], [1, 0]]); A + [ 0 -1] + [ 1 0] + sage: A.is_semisimple() + True + sage: A.change_ring(QQ).is_diagonalizable() + False + sage: A.change_ring(CyclotomicField(4)).is_diagonalizable() + True + """ + return self.minpoly().is_squarefree() + def as_sum_of_permutations(self): r""" Returns the current matrix as a sum of permutation matrices @@ -11660,6 +11687,95 @@ cdef class Matrix(Matrix1): else: return J + def jordan_decomposition(self): + r""" + Return the Jordan decomposition of ``self``. + + The Jordan decomposition of a matrix `A` is a pair of + matrices `(S, N)` such that + + - `A = S + N`, + - `S` is semisimple, + - `N` is nilpotent. + + EXAMPLES:: + + sage: A = matrix(QQ, 5, 5, {(0,1): -1, (1,0): 1, (2,3): -1}); A + [ 0 -1 0 0 0] + [ 1 0 0 0 0] + [ 0 0 0 -1 0] + [ 0 0 0 0 0] + [ 0 0 0 0 0] + sage: S, N = A.jordan_decomposition() + sage: S + [ 0 -1 0 0 0] + [ 1 0 0 0 0] + [ 0 0 0 0 0] + [ 0 0 0 0 0] + [ 0 0 0 0 0] + sage: N + [ 0 0 0 0 0] + [ 0 0 0 0 0] + [ 0 0 0 -1 0] + [ 0 0 0 0 0] + [ 0 0 0 0 0] + sage: A == S + N + True + sage: S.is_semisimple() + True + sage: N.is_nilpotent() + True + sage: A.jordan_form() + Traceback (most recent call last): + ... + RuntimeError: Some eigenvalue does not exist in Rational Field. + + TESTS:: + + sage: X = random_matrix(QQ, 4) + sage: S, N = X.jordan_decomposition() + sage: X == S + N + True + sage: S is X.jordan_decomposition()[0] # result is cached + True + sage: N is X.jordan_decomposition()[1] # result is cached + True + sage: A = matrix(ZZ, 5, 5, {(0,1): -1, (1,0): 1, (2,3): -1}) + sage: A.jordan_decomposition() + Traceback (most recent call last): + ... + ValueError: unable to compute Jordan decomposition + sage: B = A.change_ring(RR) + sage: B.jordan_decomposition() + Traceback (most recent call last): + ... + NotImplementedError: Jordan decomposition not implemented over inexact rings + """ + if not self.base_ring().is_exact(): + raise NotImplementedError("Jordan decomposition not implemented over inexact rings") + JD = self.fetch('jordan_decomposition') + if JD is not None: + return JD + f = self.minpoly() + h = f // f.gcd(f.diff()) + o, p, q = h.xgcd(h.diff()) + if not o.is_one(): + raise ValueError("unable to compute Jordan decomposition") + A = self + hq = h * q + # very bad bound, but requires no extra computation + # a better bound is the maximum multiplicity in the minpoly, + # but this requires factoring the minpoly + for _ in range(self.nrows()): + if not h(A): + ret = (A, self - A) + ret[0].set_immutable() + ret[1].set_immutable() + self.cache('jordan_decomposition', ret) + return ret + A -= hq(A) + raise ValueError("Jordan decomposition does not exist") + def diagonalization(self, base_field=None): """ Return a diagonal matrix similar to ``self`` along with the diff --git a/src/sage/matrix/matrix_space.py b/src/sage/matrix/matrix_space.py index aa9a6fe6111..6683003b6f2 100644 --- a/src/sage/matrix/matrix_space.py +++ b/src/sage/matrix/matrix_space.py @@ -434,9 +434,73 @@ def get_matrix_class(R, nrows, ncols, sparse, implementation): class MatrixSpace(UniqueRepresentation, Parent): """ - The space of matrices of given size and base ring + The space of matrices of given size and base ring. - EXAMPLES: + INPUT: + + - ``base_ring`` -- a ring + + - ``nrows`` or ``row_keys`` -- (nonnegative integer) the number of rows, or + a finite family of arbitrary objects that index the rows of the matrix + + - ``ncols`` or ``column_keys`` -- (nonnegative integer, default ``nrows``) + the number of columns, or a finite family of arbitrary objects that index + the columns of the matrix + + - ``sparse`` -- (boolean, default ``False``) whether or not matrices + are given a sparse representation + + - ``implementation`` -- (optional, a string or a matrix class) a possible + implementation. Depending on the base ring, the string can be + + - ``'generic'`` -- on any base rings + + - ``'flint'`` -- for integers and rationals + + - ``'meataxe'`` -- finite fields using the optional package :ref:`spkg_meataxe` + + - ``'m4ri'`` -- for characteristic 2 using the :ref:`spkg_m4ri` library + + - ``'linbox-float'`` -- for integer mod rings up to `2^8 = 256` + + - ``'linbox-double'`` -- for integer mod rings up to + `floor(2^26*sqrt(2) + 1/2) = 94906266` + + - ``'numpy'`` -- for real and complex floating point numbers + + OUTPUT: a matrix space or, more generally, a homspace between free modules + + This factory function creates instances of various specialized classes + depending on the input. Not all combinations of options are + implemented. + + - If the parameters ``row_keys`` or ``column_keys`` are provided, they + must be finite families of objects. In this case, instances of + :class:`CombinatorialFreeModule` are created via the factory function + :func:`FreeModule`. Then the homspace between these modules is returned. + + EXAMPLES:: + + sage: MatrixSpace(QQ, 2) + Full MatrixSpace of 2 by 2 dense matrices over Rational Field + sage: MatrixSpace(ZZ, 3, 2) + Full MatrixSpace of 3 by 2 dense matrices over Integer Ring + sage: MatrixSpace(ZZ, 3, sparse=False) + Full MatrixSpace of 3 by 3 dense matrices over Integer Ring + + sage: MatrixSpace(ZZ, 10, 5) + Full MatrixSpace of 10 by 5 dense matrices over Integer Ring + sage: MatrixSpace(ZZ, 10, 5).category() + Category of infinite enumerated finite dimensional modules with basis over + (Dedekind domains and euclidean domains + and infinite enumerated sets and metric spaces) + sage: MatrixSpace(ZZ, 10, 10).category() + Category of infinite enumerated finite dimensional algebras with basis over + (Dedekind domains and euclidean domains + and infinite enumerated sets and metric spaces) + sage: MatrixSpace(QQ, 10).category() + Category of infinite finite dimensional algebras with basis over + (number fields and quotient fields and metric spaces) Some examples of square 2 by 2 rational matrices:: @@ -463,8 +527,7 @@ class MatrixSpace(UniqueRepresentation, Parent): sage: B[1,1] [0 0] [0 1] - sage: A = MS.matrix([1,2,3,4]) - sage: A + sage: A = MS.matrix([1,2,3,4]); A [1 2] [3 4] @@ -476,19 +539,27 @@ class MatrixSpace(UniqueRepresentation, Parent): [ 9 12 15] [19 26 33] + Using ``row_keys`` and ``column_keys``:: + + sage: MS = MatrixSpace(ZZ, ['u', 'v'], ['a', 'b', 'c']); MS + Set of Morphisms + from Free module generated by {'a', 'b', 'c'} over Integer Ring + to Free module generated by {'u', 'v'} over Integer Ring + in Category of finite dimensional modules with basis over Integer Ring + Check categories:: - sage: MatrixSpace(ZZ,10,5) + sage: MatrixSpace(ZZ, 10, 5) Full MatrixSpace of 10 by 5 dense matrices over Integer Ring - sage: MatrixSpace(ZZ,10,5).category() + sage: MatrixSpace(ZZ, 10, 5).category() Category of infinite enumerated finite dimensional modules with basis over (Dedekind domains and euclidean domains and infinite enumerated sets and metric spaces) - sage: MatrixSpace(ZZ,10,10).category() + sage: MatrixSpace(ZZ, 10, 10).category() Category of infinite enumerated finite dimensional algebras with basis over (Dedekind domains and euclidean domains and infinite enumerated sets and metric spaces) - sage: MatrixSpace(QQ,10).category() + sage: MatrixSpace(QQ, 10).category() Category of infinite finite dimensional algebras with basis over (number fields and quotient fields and metric spaces) @@ -536,14 +607,72 @@ class MatrixSpace(UniqueRepresentation, Parent): sage: M1 = MatrixSpace(GF(2), 5) sage: M1(m * m) == M1(m) * M1(m) True + + Check various combinations of dimensions and row/column keys:: + + sage: M_ab_4 = MatrixSpace(QQ, ['a','b'], 4); M_ab_4 + Set of Morphisms (Linear Transformations) + from Vector space of dimension 4 over Rational Field + to Free module generated by {'a', 'b'} over Rational Field + sage: TestSuite(M_ab_4).run() # known bug + sage: M_4_ab = MatrixSpace(QQ, 4, ['a','b']); M_4_ab + Set of Morphisms + from Free module generated by {'a', 'b'} over Rational Field + to Vector space of dimension 4 over Rational Field + in Category of finite dimensional vector spaces with basis + over (number fields and quotient fields and metric spaces) + sage: TestSuite(M_4_ab).run() # known bug + sage: M_ab_xy = MatrixSpace(QQ, ['a','b'], ['x','y'], nrows=2); M_ab_xy + Set of Morphisms + from Free module generated by {'x', 'y'} over Rational Field + to Free module generated by {'a', 'b'} over Rational Field + in Category of finite dimensional vector spaces with basis over Rational Field + sage: TestSuite(M_ab_xy).run() # known bug + sage: MatrixSpace(QQ, ['a','b'], ['x','y'], nrows=4) + Traceback (most recent call last): + ... + ValueError: inconsistent number of rows: + should be cardinality of ['a', 'b'] but got 4 + sage: MatrixSpace(QQ, ['a','b'], ['x','y'], ncols=2) + Set of Morphisms + from Free module generated by {'x', 'y'} over Rational Field + to Free module generated by {'a', 'b'} over Rational Field + in Category of finite dimensional vector spaces with basis over Rational Field + sage: MatrixSpace(QQ, ['a','b'], ['x','y'], ncols=4) + Traceback (most recent call last): + ... + ValueError: inconsistent number of columns: + should be cardinality of ['x', 'y'] but got 4 + sage: MatrixSpace(QQ, ['a','b'], ['x','y'], nrows=2, ncols=2) + Set of Morphisms + from Free module generated by {'x', 'y'} over Rational Field + to Free module generated by {'a', 'b'} over Rational Field + in Category of finite dimensional vector spaces with basis over Rational Field + sage: MatrixSpace(QQ, ['a','b'], ['x','y'], nrows=2, ncols=4) + Traceback (most recent call last): + ... + ValueError: inconsistent number of columns: + should be cardinality of ['x', 'y'] but got 4 + sage: MatrixSpace(QQ, ['a','b'], ['x','y'], nrows=4, ncols=4) + Traceback (most recent call last): + ... + ValueError: inconsistent number of columns: + should be cardinality of ['x', 'y'] but got 4 + sage: MatrixSpace(QQ, 4, ['a','b'], nrows=4, ncols=2) + Traceback (most recent call last): + ... + ValueError: duplicate values for nrows """ @staticmethod - def __classcall__(cls, base_ring, nrows, ncols=None, sparse=False, implementation=None, **kwds): + def __classcall__(cls, base_ring, + nrows_or_row_keys=None, ncols_or_column_keys=None, + sparse=False, implementation=None, *, + nrows=None, ncols=None, + row_keys=None, column_keys=None, + **kwds): """ - Normalize the arguments to call the ``__init__`` constructor. - - See the documentation in ``__init__``. + Normalize the arguments to call the ``__init__`` constructor or delegate to another class. TESTS:: @@ -588,13 +717,53 @@ def __classcall__(cls, base_ring, nrows, ncols=None, sparse=False, implementatio """ if base_ring not in _Rings: raise TypeError("base_ring (=%s) must be a ring" % base_ring) - nrows = int(nrows) - if ncols is None: + + if ncols_or_column_keys is not None: + try: + n = int(ncols_or_column_keys) + except (TypeError, ValueError): + if column_keys is not None: + raise ValueError("duplicate values for column_keys") + column_keys = ncols_or_column_keys + else: + if ncols is not None: + raise ValueError("duplicate values for ncols") + ncols = n + if column_keys is not None and ncols is not None and ncols != len(column_keys): + raise ValueError(f"inconsistent number of columns: should be cardinality of {column_keys} " + f"but got {ncols}") + + if nrows_or_row_keys is not None: + try: + n = int(nrows_or_row_keys) + except (TypeError, ValueError): + if row_keys is not None: + raise ValueError("duplicate values for row_keys") + row_keys = nrows_or_row_keys + else: + if nrows is not None: + raise ValueError("duplicate values for nrows") + nrows = n + if row_keys is not None and nrows is not None and nrows != len(row_keys): + raise ValueError(f"inconsistent number of rows: should be cardinality of {row_keys} " + f"but got {nrows}") + + if ncols is None and column_keys is None: ncols = nrows - else: - ncols = int(ncols) + column_keys = row_keys + sparse = bool(sparse) + if row_keys is not None or column_keys is not None: + from sage.categories.homset import Hom + from sage.modules.free_module import FreeModule + + domain = FreeModule(base_ring, rank=ncols, basis_keys=column_keys, + sparse=sparse, **kwds) + codomain = FreeModule(base_ring, rank=nrows, basis_keys=row_keys, + sparse=sparse, **kwds) + return Hom(domain, codomain) + if nrows < 0: raise ArithmeticError("nrows must be nonnegative") if ncols < 0: @@ -608,59 +777,6 @@ def __classcall__(cls, base_ring, nrows, ncols=None, sparse=False, implementatio def __init__(self, base_ring, nrows, ncols, sparse, implementation): r""" - INPUT: - - - ``base_ring`` - - - ``nrows`` - (positive integer) the number of rows - - - ``ncols`` - (positive integer, default nrows) the number of - columns - - - ``sparse`` - (boolean, default false) whether or not matrices - are given a sparse representation - - - ``implementation`` -- (optional, a string or a matrix class) a possible - implementation. Depending on the base ring the string can be - - - ``'generic'`` - on any base rings - - - ``'flint'`` - for integers and rationals - - - ``'meataxe'`` - finite fields, needs to install the optional package meataxe - - - ``m4ri`` - for characteristic 2 using M4RI library - - - ``linbox-float`` - for integer mod rings up to `2^8 = 256` - - - ``linbox-double`` - for integer mod rings up to - `floor(2^26*sqrt(2) + 1/2) = 94906266` - - - ``numpy`` - for real and complex floating point numbers - - EXAMPLES:: - - sage: MatrixSpace(QQ, 2) - Full MatrixSpace of 2 by 2 dense matrices over Rational Field - sage: MatrixSpace(ZZ, 3, 2) - Full MatrixSpace of 3 by 2 dense matrices over Integer Ring - sage: MatrixSpace(ZZ, 3, sparse=False) - Full MatrixSpace of 3 by 3 dense matrices over Integer Ring - - sage: MatrixSpace(ZZ,10,5) - Full MatrixSpace of 10 by 5 dense matrices over Integer Ring - sage: MatrixSpace(ZZ,10,5).category() - Category of infinite enumerated finite dimensional modules with basis over - (Dedekind domains and euclidean domains - and infinite enumerated sets and metric spaces) - sage: MatrixSpace(ZZ,10,10).category() - Category of infinite enumerated finite dimensional algebras with basis over - (Dedekind domains and euclidean domains - and infinite enumerated sets and metric spaces) - sage: MatrixSpace(QQ,10).category() - Category of infinite finite dimensional algebras with basis over - (number fields and quotient fields and metric spaces) - TESTS: We test that in the real or complex double dense case, diff --git a/src/sage/matroids/advanced.py b/src/sage/matroids/advanced.py index d290aca2060..8a9509efddc 100644 --- a/src/sage/matroids/advanced.py +++ b/src/sage/matroids/advanced.py @@ -53,6 +53,7 @@ from .minor_matroid import MinorMatroid from .dual_matroid import DualMatroid from .rank_matroid import RankMatroid +from .circuits_matroid import CircuitsMatroid from .circuit_closures_matroid import CircuitClosuresMatroid from .basis_matroid import BasisMatroid from .flats_matroid import FlatsMatroid diff --git a/src/sage/matroids/basis_exchange_matroid.pxd b/src/sage/matroids/basis_exchange_matroid.pxd index 97cc4391633..6bcc9e0236e 100644 --- a/src/sage/matroids/basis_exchange_matroid.pxd +++ b/src/sage/matroids/basis_exchange_matroid.pxd @@ -15,7 +15,7 @@ cdef class BasisExchangeMatroid(Matroid): cdef _weak_invariant_var, _strong_invariant_var, _heuristic_invariant_var cdef SetSystem _weak_partition_var, _strong_partition_var, _heuristic_partition_var - cdef _relabel(self, l) + cdef _relabel(self, mapping) cdef _pack(self, bitset_t, X) cdef __unpack(self, bitset_t) diff --git a/src/sage/matroids/basis_exchange_matroid.pyx b/src/sage/matroids/basis_exchange_matroid.pyx index 835092678e2..7e6d565a868 100644 --- a/src/sage/matroids/basis_exchange_matroid.pyx +++ b/src/sage/matroids/basis_exchange_matroid.pyx @@ -191,28 +191,31 @@ cdef class BasisExchangeMatroid(Matroid): bitset_free(self._output) bitset_free(self._temp) - cdef _relabel(self, l): + cdef _relabel(self, mapping): """ - Relabel each element `e` as `l[e]`, where `l` is a given injective map. + Relabel each element ``e`` as ``mapping[e]``, where ``mapping`` is a + given injective map. INPUT: - - `l`, a python object such that `l[e]` is the new label of e. + - ``mapping`` -- a python object such that ``mapping[e]`` is the new + label of ``e`` - OUTPUT: + OUTPUT: ``None`` - ``None``. + .. NOTE:: - NOTE: - For internal use. Matroids are immutable but this method does modify the matroid. The use this method will only - be safe in very limited circumstances, such as perhaps on a fresh copy of a matroid. + For internal use. Matroids are immutable but this method does + modify the matroid. The use of this method will only be safe in + very limited circumstances, such as perhaps on a fresh copy of a + matroid. """ cdef long i E = [] for i in range(self._groundset_size): - if self._E[i] in l: - E.append(l[self._E[i]]) - else: + try: + E.append(mapping[self._E[i]]) + except LookupError: E.append(self._E[i]) self._E = tuple(E) self._groundset = frozenset(E) @@ -222,12 +225,12 @@ cdef class BasisExchangeMatroid(Matroid): self._idx[self._E[i]] = i if self._weak_partition_var: - self._weak_partition_var._relabel(l) + self._weak_partition_var._relabel(mapping) if self._strong_partition_var: - self._strong_partition_var._relabel(l) + self._strong_partition_var._relabel(mapping) if self._heuristic_partition_var: - self._heuristic_partition_var._relabel(l) + self._heuristic_partition_var._relabel(mapping) # the engine cdef _pack(self, bitset_t I, F): @@ -1920,7 +1923,7 @@ cdef class BasisExchangeMatroid(Matroid): sage: M = matroids.catalog.N1() sage: M._characteristic_setsystem() - Iterator over a system of subsets + SetSystem of 23 sets over 10 elements sage: len(M._characteristic_setsystem()) 23 """ diff --git a/src/sage/matroids/basis_matroid.pxd b/src/sage/matroids/basis_matroid.pxd index 3782b51c5fa..98400ba26ca 100644 --- a/src/sage/matroids/basis_matroid.pxd +++ b/src/sage/matroids/basis_matroid.pxd @@ -25,7 +25,7 @@ cdef class BasisMatroid(BasisExchangeMatroid): cpdef truncation(self) cpdef _extension(self, e, H) cpdef _with_coloop(self, e) - cpdef relabel(self, l) + # cpdef relabel(self, mapping) cpdef _bases_invariant(self) cpdef _bases_partition(self) diff --git a/src/sage/matroids/basis_matroid.pyx b/src/sage/matroids/basis_matroid.pyx index 4c20102a621..da6192324bc 100644 --- a/src/sage/matroids/basis_matroid.pyx +++ b/src/sage/matroids/basis_matroid.pyx @@ -77,8 +77,9 @@ from sage.structure.richcmp cimport rich_to_bool from sage.matroids.matroid cimport Matroid from sage.matroids.basis_exchange_matroid cimport BasisExchangeMatroid from sage.matroids.set_system cimport SetSystem +from sage.matroids.utilities import cmp_elements_key from cpython.object cimport Py_EQ, Py_NE - +from sage.misc.decorators import rename_keyword from itertools import combinations # class of general matroids, represented by their list of bases @@ -189,15 +190,19 @@ cdef class BasisMatroid(BasisExchangeMatroid): if M is not None: rank = M.full_rank() nonbases = M.nonbases() - groundset = sorted(M.groundset()) + groundset = sorted(M.groundset(), key=cmp_elements_key) if groundset is None: groundset = frozenset() if rank is None: if bases is not None: - rank = len(min(bases)) + for B in bases: + rank = len(B) + break elif nonbases is not None: - rank = len(min(nonbases)) + for N in nonbases: + rank = len(N) + break else: rank = 0 @@ -258,7 +263,7 @@ cdef class BasisMatroid(BasisExchangeMatroid): sage: repr(M) # indirect doctest 'Matroid of rank 3 on 7 elements with 28 bases' """ - return Matroid._repr_(self) + " with " + str(self.bases_count()) + " bases" + return f'{Matroid._repr_(self)} with {self.bases_count()} bases' # support for parent BasisExchangeMatroid @@ -515,42 +520,48 @@ cdef class BasisMatroid(BasisExchangeMatroid): cdef frozenset se = frozenset([e]) return BasisMatroid(groundset=self._E + (e,), bases=[B | se for B in self.bases()]) - cpdef relabel(self, l): - """ + @rename_keyword(deprecation=37775, l='mapping') + def relabel(self, mapping): + r""" Return an isomorphic matroid with relabeled groundset. - The output is obtained by relabeling each element ``e`` by ``l[e]``, - where ``l`` is a given injective map. If ``e not in l`` then the - identity map is assumed. + The output is obtained by relabeling each element ``e`` by + ``mapping[e]``, where ``mapping`` is a given injective map. If + ``mapping[e]`` is not defined, then the identity map is assumed. INPUT: - - ``l`` -- a python object such that `l[e]` is the new label of `e`. - - OUTPUT: - - A matroid. - - .. TODO:: + - ``mapping`` -- a python object such that ``mapping[e]`` is the new + label of ``e`` - Write abstract ``RelabeledMatroid`` class, and add ``relabel()`` - method to the main ``Matroid`` class, together with ``_relabel()`` - method that can be replaced by subclasses. Use the code from - ``is_isomorphism()`` in ``relabel()`` to deal with a variety of - input methods for the relabeling. + OUTPUT: a matroid EXAMPLES:: - sage: from sage.matroids.advanced import * + sage: from sage.matroids.advanced import BasisMatroid sage: M = BasisMatroid(matroids.catalog.Fano()) sage: sorted(M.groundset()) ['a', 'b', 'c', 'd', 'e', 'f', 'g'] - sage: N = M.relabel({'g':'x'}) - sage: sorted(N.groundset()) - ['a', 'b', 'c', 'd', 'e', 'f', 'x'] + sage: N = M.relabel({'a': 0, 'g': 'x'}) + sage: from sage.matroids.utilities import cmp_elements_key + sage: sorted(N.groundset(), key=cmp_elements_key) + [0, 'b', 'c', 'd', 'e', 'f', 'x'] + sage: N.is_isomorphic(M) + True + + TESTS:: + + sage: from sage.matroids.advanced import BasisMatroid + sage: M = BasisMatroid(matroids.catalog.Fano()) + sage: f = {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6, 'g': 7} + sage: N = M.relabel(f) + sage: for S in powerset(M.groundset()): + ....: assert M.rank(S) == N.rank([f[x] for x in S]) """ - M = BasisMatroid(M=self) - M._relabel(l) + d = self._relabel_map(mapping) + E = [d[x] for x in self.groundset()] + B = [[d[y] for y in x] for x in self.bases()] + M = BasisMatroid(groundset=E, bases=B) return M # enumeration @@ -691,8 +702,8 @@ cdef class BasisMatroid(BasisExchangeMatroid): bi[bc[e]].append(e) else: bi[bc[e]] = [e] - self._bases_invariant_var = hash(tuple([(c, len(bi[c])) for c in sorted(bi)])) - self._bases_partition_var = SetSystem(self._E, [[self._E[e] for e in bi[c]] for c in sorted(bi)]) + self._bases_invariant_var = hash(tuple([(c, len(bi[c])) for c in sorted(bi, key=cmp_elements_key)])) + self._bases_partition_var = SetSystem(self._E, [[self._E[e] for e in bi[c]] for c in sorted(bi, key=cmp_elements_key)]) return self._bases_invariant_var cpdef _bases_partition(self): @@ -1161,46 +1172,6 @@ cdef class BasisMatroid(BasisExchangeMatroid): else: return rich_to_bool(op, 1) - def __copy__(self): - """ - Create a shallow copy. - - EXAMPLES:: - - sage: from sage.matroids.advanced import * - sage: M = BasisMatroid(matroids.catalog.Vamos()) - sage: N = copy(M) # indirect doctest - sage: M == N - True - """ - N = BasisMatroid(M=self) - N.rename(self.get_custom_name()) - return N - - def __deepcopy__(self, memo=None): - """ - Create a deep copy. - - .. NOTE:: - - Identical to shallow copy for BasisMatroid class. - - EXAMPLES:: - - sage: from sage.matroids.advanced import * - sage: M = BasisMatroid(matroids.catalog.Vamos()) - sage: N = deepcopy(M) # indirect doctest - sage: M == N - True - sage: M.groundset() is N.groundset() - False - """ - if memo is None: - memo = {} - N = BasisMatroid(M=self) - N.rename(self.get_custom_name()) - return N - def __reduce__(self): """ Save the matroid for later reloading. diff --git a/src/sage/matroids/circuit_closures_matroid.pxd b/src/sage/matroids/circuit_closures_matroid.pxd index 7c7b61ec35f..4c6ef771057 100644 --- a/src/sage/matroids/circuit_closures_matroid.pxd +++ b/src/sage/matroids/circuit_closures_matroid.pxd @@ -13,3 +13,4 @@ cdef class CircuitClosuresMatroid(Matroid): cpdef _circuit(self, F) cpdef circuit_closures(self) cpdef _is_isomorphic(self, other, certificate=*) + cpdef relabel(self, mapping) diff --git a/src/sage/matroids/circuit_closures_matroid.pyx b/src/sage/matroids/circuit_closures_matroid.pyx index 42293cc2fc3..a732ea192e2 100644 --- a/src/sage/matroids/circuit_closures_matroid.pyx +++ b/src/sage/matroids/circuit_closures_matroid.pyx @@ -46,15 +46,6 @@ Note that the class does not implement custom minor and dual operations:: AUTHORS: - Rudi Pendavingh, Stefan van Zwam (2013-04-01): initial version - -TESTS:: - - sage: from sage.matroids.advanced import * - sage: M = CircuitClosuresMatroid(matroids.catalog.Fano()) - sage: TestSuite(M).run() - -Methods -======= """ # **************************************************************************** # Copyright (C) 2013 Rudi Pendavingh @@ -131,7 +122,8 @@ cdef class CircuitClosuresMatroid(Matroid): True """ - # NECESSARY + # necessary + def __init__(self, M=None, groundset=None, circuit_closures=None): """ Initialization of the matroid. See class docstring for full @@ -154,6 +146,12 @@ cdef class CircuitClosuresMatroid(Matroid): ....: 4: ['abcdefgh']}) sage: M.equals(matroids.catalog.P8()) True + + TESTS:: + + sage: from sage.matroids.advanced import * + sage: M = CircuitClosuresMatroid(matroids.catalog.Fano()) + sage: TestSuite(M).run() """ if M is not None: self._groundset = M.groundset() @@ -206,7 +204,8 @@ cdef class CircuitClosuresMatroid(Matroid): """ return len(self._max_independent(X)) - # OPTIONAL, OPTIMIZED FOR THIS CLASS + # optional + cpdef full_rank(self): r""" Return the rank of the matroid. @@ -413,7 +412,8 @@ cdef class CircuitClosuresMatroid(Matroid): SN.append(C) return SM._isomorphism(SN) is not None - # REPRESENTATION + # representation + def _repr_(self): """ Return a string representation of the matroid. @@ -430,7 +430,7 @@ cdef class CircuitClosuresMatroid(Matroid): """ return Matroid._repr_(self) + " with circuit-closures\n" + setprint_s(self._circuit_closures) - # COMPARISON + # comparison def __hash__(self): r""" @@ -490,52 +490,7 @@ cdef class CircuitClosuresMatroid(Matroid): return rich_to_bool(op, 1) return richcmp(lt._circuit_closures, rt._circuit_closures, op) - # COPYING, LOADING, SAVING - - def __copy__(self): - """ - Create a shallow copy. - - EXAMPLES:: - - sage: M = matroids.catalog.Vamos() - sage: N = copy(M) # indirect doctest - sage: M == N - True - sage: M.groundset() is N.groundset() - True - """ - N = CircuitClosuresMatroid(groundset=[], circuit_closures={}) - N._groundset = self._groundset - N._circuit_closures = self._circuit_closures - N._matroid_rank = self._matroid_rank - N.rename(self.get_custom_name()) - return N - - def __deepcopy__(self, memo=None): - """ - Create a deep copy. - - .. NOTE:: - - Since matroids are immutable, a shallow copy normally suffices. - - EXAMPLES:: - - sage: M = matroids.catalog.Vamos() - sage: N = deepcopy(M) # indirect doctest - sage: M == N - True - sage: M.groundset() is N.groundset() - False - """ - if memo is None: - memo = {} - from copy import deepcopy - # Since matroids are immutable, N cannot reference itself in correct code, so no need to worry about the recursion. - N = CircuitClosuresMatroid(groundset=deepcopy(self._groundset, memo), circuit_closures=deepcopy(self._circuit_closures, memo)) - N.rename(deepcopy(self.get_custom_name(), memo)) - return N + # copying, loading, saving def __reduce__(self): """ @@ -568,4 +523,49 @@ cdef class CircuitClosuresMatroid(Matroid): version = 0 return sage.matroids.unpickling.unpickle_circuit_closures_matroid, (version, data) + cpdef relabel(self, mapping): + r""" + Return an isomorphic matroid with relabeled groundset. + + The output is obtained by relabeling each element ``e`` by + ``mapping[e]``, where ``mapping`` is a given injective map. If + ``mapping[e]`` is not defined, then the identity map is assumed. + + INPUT: + + - ``mapping`` -- a python object such that ``mapping[e]`` is the new + label of ``e`` + + OUTPUT: a matroid + + EXAMPLES:: + + sage: from sage.matroids.circuit_closures_matroid import CircuitClosuresMatroid + sage: M = CircuitClosuresMatroid(matroids.catalog.RelaxedNonFano()) + sage: sorted(M.groundset()) + [0, 1, 2, 3, 4, 5, 6] + sage: N = M.relabel({'g': 'x', 0: 'z'}) # 'g': 'x' is ignored + sage: from sage.matroids.utilities import cmp_elements_key + sage: sorted(N.groundset(), key=cmp_elements_key) + [1, 2, 3, 4, 5, 6, 'z'] + sage: M.is_isomorphic(N) + True + + TESTS:: + + sage: from sage.matroids.circuit_closures_matroid import CircuitClosuresMatroid + sage: M = CircuitClosuresMatroid(matroids.catalog.RelaxedNonFano()) + sage: f = {0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e', 5: 'f', 6: 'g'} + sage: N = M.relabel(f) + sage: for S in powerset(M.groundset()): + ....: assert M.rank(S) == N.rank([f[x] for x in S]) + """ + d = self._relabel_map(mapping) + E = [d[x] for x in self.groundset()] + CC = {} + for i in self.circuit_closures(): + CC[i] = [[d[y] for y in x] for x in self._circuit_closures[i]] + M = CircuitClosuresMatroid(groundset=E, circuit_closures=CC) + return M + # todo: customized minor, extend methods. diff --git a/src/sage/matroids/circuits_matroid.pxd b/src/sage/matroids/circuits_matroid.pxd index 26f86b2dac0..f495a1eff81 100644 --- a/src/sage/matroids/circuits_matroid.pxd +++ b/src/sage/matroids/circuits_matroid.pxd @@ -24,8 +24,9 @@ cdef class CircuitsMatroid(Matroid): cpdef girth(self) cpdef is_paving(self) - # isomorphism + # isomorphism and relabeling cpdef _is_isomorphic(self, other, certificate=*) + cpdef relabel(self, mapping) # verification cpdef is_valid(self) diff --git a/src/sage/matroids/circuits_matroid.pyx b/src/sage/matroids/circuits_matroid.pyx index 6c09c2dc07d..aec280a35f7 100644 --- a/src/sage/matroids/circuits_matroid.pyx +++ b/src/sage/matroids/circuits_matroid.pyx @@ -269,9 +269,9 @@ cdef class CircuitsMatroid(Matroid): NonDesargues: Matroid of rank 3 on 10 elements with 9 nonspanning circuits """ if self._nsc_defined: - return Matroid._repr_(self) + " with " + str(len(self.nonspanning_circuits())) + " nonspanning circuits" + return f'{Matroid._repr_(self)} with {len(self.nonspanning_circuits())} nonspanning circuits' else: - return Matroid._repr_(self) + " with " + str(len(self._C)) + " circuits" + return f'{Matroid._repr_(self)} with {len(self._C)} circuits' # comparison @@ -337,55 +337,6 @@ cdef class CircuitsMatroid(Matroid): # copying, loading, saving - def __copy__(self): - """ - Create a shallow copy. - - EXAMPLES:: - - sage: from sage.matroids.circuits_matroid import CircuitsMatroid - sage: M = CircuitsMatroid(matroids.catalog.Vamos()) - sage: N = copy(M) # indirect doctest - sage: M == N - True - sage: M.groundset() is N.groundset() - True - """ - N = CircuitsMatroid(groundset=[], circuits=[]) - N._groundset = self._groundset - N._C = self._C - N._k_C = self._k_C - N._nsc_defined = self._nsc_defined - N._matroid_rank = self._matroid_rank - N.rename(self.get_custom_name()) - return N - - def __deepcopy__(self, memo=None): - """ - Create a deep copy. - - .. NOTE:: - - Since matroids are immutable, a shallow copy normally suffices. - - EXAMPLES:: - - sage: from sage.matroids.circuits_matroid import CircuitsMatroid - sage: M = CircuitsMatroid(matroids.catalog.Vamos()) - sage: N = deepcopy(M) # indirect doctest - sage: M == N - True - sage: M.groundset() is N.groundset() - False - """ - if memo is None: - memo = {} - from copy import deepcopy - # Since matroids are immutable, N cannot reference itself in correct code, so no need to worry about the recursion. - N = CircuitsMatroid(groundset=deepcopy(self._groundset, memo), circuits=deepcopy(frozenset(self._C), memo)) - N.rename(deepcopy(self.get_custom_name(), memo)) - return N - def __reduce__(self): """ Save the matroid for later reloading. @@ -413,6 +364,51 @@ cdef class CircuitsMatroid(Matroid): version = 0 return sage.matroids.unpickling.unpickle_circuits_matroid, (version, data) + cpdef relabel(self, mapping): + r""" + Return an isomorphic matroid with relabeled groundset. + + The output is obtained by relabeling each element ``e`` by + ``mapping[e]``, where ``mapping`` is a given injective map. If + ``mapping[e]`` is not defined, then the identity map is assumed. + + INPUT: + + - ``mapping`` -- a python object such that ``mapping[e]`` is the new + label of ``e`` + + OUTPUT: a matroid + + EXAMPLES:: + + sage: from sage.matroids.circuits_matroid import CircuitsMatroid + sage: M = CircuitsMatroid(matroids.catalog.RelaxedNonFano()) + sage: sorted(M.groundset()) + [0, 1, 2, 3, 4, 5, 6] + sage: N = M.relabel({'g': 'x', 0: 'z'}) # 'g': 'x' is ignored + sage: from sage.matroids.utilities import cmp_elements_key + sage: sorted(N.groundset(), key=cmp_elements_key) + [1, 2, 3, 4, 5, 6, 'z'] + sage: M.is_isomorphic(N) + True + + TESTS:: + + sage: from sage.matroids.circuits_matroid import CircuitsMatroid + sage: M = CircuitsMatroid(matroids.catalog.RelaxedNonFano()) + sage: f = {0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e', 5: 'f', 6: 'g'} + sage: N = M.relabel(f) + sage: for S in powerset(M.groundset()): + ....: assert M.rank(S) == N.rank([f[x] for x in S]) + """ + d = self._relabel_map(mapping) + E = [d[x] for x in self._groundset] + C = [] + for i in self._k_C: + C += [[d[y] for y in x] for x in self._k_C[i]] + M = CircuitsMatroid(groundset=E, circuits=C) + return M + # enumeration cpdef bases(self): @@ -485,7 +481,7 @@ cdef class CircuitsMatroid(Matroid): sage: from sage.matroids.circuits_matroid import CircuitsMatroid sage: M = CircuitsMatroid(matroids.Uniform(2, 4)) sage: M.circuits() - Iterator over a system of subsets + SetSystem of 4 sets over 4 elements sage: list(M.circuits(0)) [] sage: sorted(M.circuits(3), key=str) @@ -548,7 +544,7 @@ cdef class CircuitsMatroid(Matroid): sage: from sage.matroids.circuits_matroid import CircuitsMatroid sage: M = CircuitsMatroid(matroids.Uniform(2, 4)) sage: M.nonspanning_circuits() - Iterator over a system of subsets + SetSystem of 0 sets over 4 elements """ cdef list NSC = [] for i in self._k_C: diff --git a/src/sage/matroids/database_matroids.py b/src/sage/matroids/database_matroids.py index 0017ce0627e..28a149436f3 100644 --- a/src/sage/matroids/database_matroids.py +++ b/src/sage/matroids/database_matroids.py @@ -38,7 +38,6 @@ from sage.matrix.constructor import Matrix from sage.matroids.constructor import Matroid -from sage.matroids.circuit_closures_matroid import CircuitClosuresMatroid from sage.matroids.linear_matroid import ( RegularMatroid, BinaryMatroid, @@ -59,7 +58,7 @@ # The order is the same as in Oxley. -def U24(): +def U24(groundset='abcd'): r""" The uniform matroid of rank `2` on `4` elements. @@ -70,7 +69,7 @@ def U24(): sage: M = matroids.catalog.U24(); M U(2, 4): Matroid of rank 2 on 4 elements with circuit-closures - {2: {{0, 1, 2, 3}}} + {2: {{'a', 'b', 'c', 'd'}}} sage: N = matroids.Uniform(2, 4) sage: M.is_isomorphic(N) True @@ -96,12 +95,21 @@ def U24(): REFERENCES: [Oxl2011]_, p. 639. + + TESTS:: + + sage: M = matroids.catalog.U24(range(4)) + sage: sorted(M.groundset()) + [0, 1, 2, 3] + sage: M.is_isomorphic(matroids.catalog.U24()) + True """ M = Uniform(2, 4) + M = _rename_and_relabel(M, "U(2, 4)", groundset) return M -def U25(): +def U25(groundset='abcde'): """ The uniform matroid of rank `2` on `5` elements. @@ -111,7 +119,9 @@ def U25(): sage: U25 = matroids.catalog.U25(); U25 U(2, 5): Matroid of rank 2 on 5 elements with circuit-closures - {2: {{0, 1, 2, 3, 4}}} + {2: {{'a', 'b', 'c', 'd', 'e'}}} + sage: U25.is_graphic() or U25.is_regular() + False sage: U35 = matroids.catalog.U35() sage: U25.is_isomorphic(U35.dual()) True @@ -119,12 +129,19 @@ def U25(): REFERENCES: [Oxl2011]_, p. 640. + + TESTS:: + + sage: M = matroids.catalog.U25(range(5)) + sage: sorted(M.groundset()) + [0, 1, 2, 3, 4] """ M = Uniform(2, 5) + M = _rename_and_relabel(M, "U(2, 5)", groundset) return M -def U35(): +def U35(groundset='abcde'): """ The uniform matroid of rank `3` on `5` elements. @@ -134,7 +151,9 @@ def U35(): sage: U35 = matroids.catalog.U35(); U35 U(3, 5): Matroid of rank 3 on 5 elements with circuit-closures - {3: {{0, 1, 2, 3, 4}}} + {3: {{'a', 'b', 'c', 'd', 'e'}}} + sage: U35.is_graphic() or U35.is_regular() + False sage: U25 = matroids.catalog.U25() sage: U35.is_isomorphic(U25.dual()) True @@ -142,12 +161,19 @@ def U35(): REFERENCES: [Oxl2011]_, p. 640. + + TESTS:: + + sage: M = matroids.catalog.U35(range(5)) + sage: sorted(M.groundset()) + [0, 1, 2, 3, 4] """ M = Uniform(3, 5) + M = _rename_and_relabel(M, "U(3, 5)", groundset) return M -def K4(): +def K4(groundset='abcdef'): r""" The graphic matroid of the complete graph `K_4`. @@ -155,6 +181,8 @@ def K4(): sage: M = matroids.catalog.K4(); M M(K4): Graphic matroid of rank 3 on 6 elements + sage: M.is_graphic() + True `M(K_4)` is isomorphic to `M(\mathcal{W}_3)`, the rank-`3` wheel:: @@ -176,12 +204,19 @@ def K4(): REFERENCES: [Oxl2011]_, p. 640. + + TESTS:: + + sage: M = matroids.catalog.K4(range(6)) + sage: sorted(M.groundset()) + [0, 1, 2, 3, 4, 5] """ M = CompleteGraphic(4) + M = _rename_and_relabel(M, "M(K4)", groundset) return M -def Whirl3(): +def Whirl3(groundset='abcdef'): r""" The rank-`3` whirl. @@ -211,12 +246,19 @@ def Whirl3(): REFERENCES: [Oxl2011]_, p. 641. + + TESTS:: + + sage: W = matroids.catalog.Whirl3(range(6)) + sage: sorted(W.groundset()) + [0, 1, 2, 3, 4, 5] """ M = Whirl(3) + M = _rename_and_relabel(M, "Whirl(3)", groundset) return M -def Q6(): +def Q6(groundset='abcdef'): """ Return the matroid `Q_6`, represented over `GF(4)`. @@ -245,12 +287,12 @@ def Q6(): F = GF(4, 'x') x = F.gens()[0] A = Matrix(F, [[1, 0, 0, 1, 0, 1], [0, 1, 0, 1, 1, x], [0, 0, 1, 0, 1, 1]]) - M = QuaternaryMatroid(A, 'abcdef') - M.rename("Q6: " + repr(M)) + M = QuaternaryMatroid(A, groundset) + M = _rename_and_relabel(M, "Q6") return M -def P6(): +def P6(groundset=None): """ Return the matroid `P_6`, represented as circuit closures. @@ -277,14 +319,13 @@ def P6(): [Oxl2011]_, p. 641-2. """ - E = 'abcdef' - CC = {2: ['abc'], 3: [E]} - M = CircuitClosuresMatroid(groundset=E, circuit_closures=CC) - M.rename("P6: " + repr(M)) + CC = {2: ['abc'], 3: ['abcdef']} + M = Matroid(circuit_closures=CC) + M = _rename_and_relabel(M, "P6", groundset) return M -def U36(): +def U36(groundset='abcdef'): """ The uniform matroid of rank `3` on `6` elements. @@ -295,7 +336,7 @@ def U36(): sage: U36 = matroids.catalog.U36(); U36 U(3, 6): Matroid of rank 3 on 6 elements with circuit-closures - {3: {{0, 1, 2, 3, 4, 5}}} + {3: {{'a', 'b', 'c', 'd', 'e', 'f'}}} sage: Z = matroids.Spike(3, False) sage: U36.is_isomorphic(Z) True @@ -307,12 +348,21 @@ def U36(): REFERENCES: [Oxl2011]_, p. 642. + + TESTS:: + + sage: M = matroids.catalog.U36(range(6)) + sage: sorted(M.groundset()) + [0, 1, 2, 3, 4, 5] + sage: M.is_isomorphic(matroids.catalog.U36()) + True """ M = Uniform(3, 6) + M = _rename_and_relabel(M, "U(3, 6)", groundset) return M -def R6(): +def R6(groundset='abcdef'): """ Return the matroid `R_6`, represented over `GF(3)`. @@ -340,12 +390,12 @@ def R6(): A = Matrix( GF(3), [[1, 0, 0, 1, 1, 1], [0, 1, 0, 1, 2, 1], [0, 0, 1, 1, 0, 2]] ) - M = TernaryMatroid(A, 'abcdef') - M.rename("R6: " + repr(M)) + M = TernaryMatroid(A, groundset) + M = _rename_and_relabel(M, "R6") return M -def Fano(): +def Fano(groundset='abcdefg'): r""" Return the Fano matroid, represented over `GF(2)`. @@ -390,12 +440,12 @@ def Fano(): GF(2), [[1, 0, 0, 0, 1, 1, 1], [0, 1, 0, 1, 0, 1, 1], [0, 0, 1, 1, 1, 0, 1]] ) - M = BinaryMatroid(A, 'abcdefg') - M.rename("Fano: " + repr(M)) + M = BinaryMatroid(A, groundset) + M = _rename_and_relabel(M, "Fano") return M -def FanoDual(): +def FanoDual(groundset='abcdefg'): """ Return the dual of the Fano matroid. @@ -424,13 +474,19 @@ def FanoDual(): REFERENCES: [Oxl2011]_, p. 643. + + TESTS:: + + sage: F7D = matroids.catalog.FanoDual(range(7)) + sage: sorted(F7D.groundset()) + [0, 1, 2, 3, 4, 5, 6] """ M = Fano().dual() - M.rename("F7*: " + repr(M)) + M = _rename_and_relabel(M, "F7*", groundset) return M -def NonFano(): +def NonFano(groundset='abcdefg'): """ Return the non-Fano matroid, represented over `GF(3)`. @@ -459,12 +515,12 @@ def NonFano(): GF(3), [[1, 0, 0, 0, 1, 1, 1], [0, 1, 0, 1, 0, 1, 1], [0, 0, 1, 1, 1, 0, 1]] ) - M = TernaryMatroid(A, 'abcdefg') - M.rename("NonFano: " + repr(M)) + M = TernaryMatroid(A, groundset) + M = _rename_and_relabel(M, "NonFano") return M -def NonFanoDual(): +def NonFanoDual(groundset='abcdefg'): r""" Return the dual of the non-Fano matroid. @@ -492,13 +548,21 @@ def NonFanoDual(): REFERENCES: [Oxl2011]_, p. 643-4. + + TESTS:: + + sage: M = matroids.catalog.NonFanoDual(range(7)) + sage: sorted(M.groundset()) + [0, 1, 2, 3, 4, 5, 6] + sage: M.is_isomorphic(matroids.catalog.NonFanoDual()) + True """ M = NonFano().dual() - M.rename("NonFano*: " + repr(M)) + M = _rename_and_relabel(M, "NonFano*", groundset) return M -def O7(): +def O7(groundset='abcdefg'): """ Return the matroid `O_7`, represented over `GF(3)`. @@ -523,12 +587,12 @@ def O7(): GF(3), [[1, 0, 0, 1, 1, 1, 1], [0, 1, 0, 0, 1, 2, 2], [0, 0, 1, 1, 0, 1, 0]] ) - M = TernaryMatroid(A, 'abcdefg') - M.rename("O7: " + repr(M)) + M = TernaryMatroid(A, groundset) + M = _rename_and_relabel(M, "O7") return M -def P7(): +def P7(groundset='abcdefg'): """ Return the matroid `P_7`, represented over `GF(3)`. @@ -556,12 +620,12 @@ def P7(): GF(3), [[1, 0, 0, 2, 1, 1, 0], [0, 1, 0, 1, 1, 0, 1], [0, 0, 1, 1, 0, 1, 1]] ) - M = TernaryMatroid(A, 'abcdefg') - M.rename("P7: " + repr(M)) + M = TernaryMatroid(A, groundset) + M = _rename_and_relabel(M, "P7") return M -def AG32(): +def AG32(groundset='abcdefgh'): """ Return the matroid `AG(3, 2)`. @@ -601,10 +665,11 @@ def AG32(): [Oxl2011]_, p. 645. """ M = AG(3, 2) + M = _rename_and_relabel(M, "AG(3, 2)", groundset) return M -def AG32prime(): +def AG32prime(groundset=None): """ Return the matroid `AG(3, 2)'`, represented as circuit closures. @@ -629,7 +694,7 @@ def AG32prime(): {'b', 'c', 'd', 'g'}, {'b', 'c', 'e', 'f'}, {'b', 'd', 'f', 'h'}, {'b', 'e', 'g', 'h'}, {'c', 'd', 'e', 'h'}, {'c', 'f', 'g', 'h'}, {'d', 'e', 'f', 'g'}] - sage: M.is_valid() # long time + sage: M.is_valid() True sage: M.automorphism_group().is_transitive() False @@ -665,12 +730,12 @@ def AG32prime(): ], 4: ['abcdefgh'], } - M = CircuitClosuresMatroid(groundset='abcdefgh', circuit_closures=CC) - M.rename("AG(3, 2)': " + repr(M)) + M = Matroid(circuit_closures=CC) + M = _rename_and_relabel(M, "AG(3, 2)'", groundset) return M -def R8(): +def R8(groundset='abcdefgh'): """ Return the matroid `R_8`, represented over `GF(3)`. @@ -717,12 +782,12 @@ def R8(): [0, 0, 0, 1, 1, 1, 1, 2], ], ) - M = TernaryMatroid(A, 'abcdefgh') - M.rename("R8: " + repr(M)) + M = TernaryMatroid(A, groundset) + M = _rename_and_relabel(M, "R8") return M -def F8(): +def F8(groundset=None): """ Return the matroid `F_8`, represented as circuit closures. @@ -765,12 +830,12 @@ def F8(): ], 4: ['abcdefgh'], } - M = CircuitClosuresMatroid(groundset='abcdefgh', circuit_closures=CC) - M.rename("F8: " + repr(M)) + M = Matroid(circuit_closures=CC) + M = _rename_and_relabel(M, "F8", groundset) return M -def Q8(): +def Q8(groundset=None): """ Return the matroid `Q_8`, represented as circuit closures. @@ -814,12 +879,12 @@ def Q8(): ], 4: ['abcdefgh'], } - M = CircuitClosuresMatroid(groundset='abcdefgh', circuit_closures=CC) - M.rename("Q8: " + repr(M)) + M = Matroid(circuit_closures=CC) + M = _rename_and_relabel(M, "Q8", groundset) return M -def L8(): +def L8(groundset=None): """ Return the matroid `L_8`, represented as circuit closures. @@ -846,7 +911,7 @@ def L8(): Every single-element contraction is isomorphic to the free extension of `M(K_4)`:: - sage: K4 = matroids.catalog.K4() + sage: K4 = matroids.catalog.K4(range(6)) sage: Bext = [list(b) for b in K4.bases()] + [list(I)+[6] for I in ....: K4.independent_r_sets(2)] sage: K4ext = Matroid(bases=Bext) @@ -861,12 +926,12 @@ def L8(): """ CC = {3: ['abfg', 'bcdg', 'defg', 'cdeh', 'aefh', 'abch', 'aceg', 'bdfh'], 4: ['abcdefgh']} - M = CircuitClosuresMatroid(groundset='abcdefgh', circuit_closures=CC) - M.rename("L8: " + repr(M)) + M = Matroid(circuit_closures=CC) + M = _rename_and_relabel(M, "L8", groundset) return M -def S8(): +def S8(groundset='abcdefgh'): """ Return the matroid `S_8`, represented over `GF(2)`. @@ -912,12 +977,12 @@ def S8(): [0, 0, 0, 1, 1, 1, 1, 1], ], ) - M = BinaryMatroid(A, 'abcdefgh') - M.rename("S8: " + repr(M)) + M = BinaryMatroid(A, groundset) + M = _rename_and_relabel(M, "S8") return M -def Vamos(): +def Vamos(groundset=None): r""" Return the `V\acute{a}mos` matroid, represented as circuit closures. @@ -938,7 +1003,7 @@ def Vamos(): {'c', 'd', 'e', 'f'}, {'e', 'f', 'g', 'h'}] sage: M.is_dependent(['c', 'd', 'g', 'h']) False - sage: M.is_valid() # long time + sage: M.is_valid() and M.is_paving() # long time True sage: M.is_isomorphic(M.dual()) and not M.equals(M.dual()) True @@ -950,12 +1015,12 @@ def Vamos(): [Oxl2011]_, p. 649. """ CC = {3: ['abcd', 'abef', 'cdef', 'abgh', 'efgh'], 4: ['abcdefgh']} - M = CircuitClosuresMatroid(groundset='abcdefgh', circuit_closures=CC) - M.rename("Vamos: " + repr(M)) + M = Matroid(circuit_closures=CC) + M = _rename_and_relabel(M, "Vamos", groundset) return M -def T8(): +def T8(groundset='abcdefgh'): """ Return the matroid `T_8`, represented over `GF(3)`. @@ -991,12 +1056,12 @@ def T8(): [0, 0, 0, 1, 1, 1, 1, 0], ], ) - M = TernaryMatroid(A, 'abcdefgh') - M.rename("T8: " + repr(M)) + M = TernaryMatroid(A, groundset) + M = _rename_and_relabel(M, "T8") return M -def J(): +def J(groundset='abcdefgh'): """ Return the matroid `J`, represented over `GF(3)`. @@ -1032,12 +1097,12 @@ def J(): [0, 0, 0, 1, 1, 0, 0, 1], ], ) - M = TernaryMatroid(A, 'abcdefgh') - M.rename("J: " + repr(M)) + M = TernaryMatroid(A, groundset) + M = _rename_and_relabel(M, "J") return M -def P8(): +def P8(groundset='abcdefgh'): """ Return the matroid `P_8`, represented over `GF(3)`. @@ -1070,12 +1135,12 @@ def P8(): [0, 0, 0, 1, 0, 1, 1, 2], ], ) - M = TernaryMatroid(A, 'abcdefgh') - M.rename("P8: " + repr(M)) + M = TernaryMatroid(A, groundset) + M = _rename_and_relabel(M, "P8") return M -def P8pp(): +def P8pp(groundset=None): """ Return the matroid `P_8^=`, represented as circuit closures. @@ -1101,12 +1166,12 @@ def P8pp(): [Oxl2011]_, p. 651. """ NSC = ['abfh', 'bceg', 'cdfh', 'adeg', 'acef', 'bdfg', 'acgh', 'bdeh'] - M = Matroid(groundset='abcdefgh', rank=4, nonspanning_circuits=NSC) - M.rename("P8'': " + repr(M)) + M = Matroid(rank=4, nonspanning_circuits=NSC) + M = _rename_and_relabel(M, "P8''", groundset) return M -def Wheel4(): +def Wheel4(groundset='abcdefgh'): """ Return the rank-`4` wheel. @@ -1129,10 +1194,11 @@ def Wheel4(): [Oxl2011]_, p. 651-2. """ M = Wheel(4) + M = _rename_and_relabel(M, "Wheel(4)", groundset) return M -def Whirl4(): +def Whirl4(groundset='abcdefgh'): """ Return the rank-`4` whirl. @@ -1155,10 +1221,11 @@ def Whirl4(): [Oxl2011]_, p. 652. """ M = Whirl(4) + M = _rename_and_relabel(M, "Whirl(4)", groundset) return M -def K33dual(): +def K33dual(groundset='abcdefghi'): """ Return the matroid `M*(K_{3, 3})`, represented over the regular partial field. @@ -1185,13 +1252,13 @@ def K33dual(): from sage.graphs.graph_generators import graphs G = graphs.CompleteBipartiteGraph(3, 3) - M = Matroid(groundset='abcdefghi', graph=G, regular=True) + M = Matroid(groundset=groundset, graph=G, regular=True) M = M.dual() - M.rename("M*(K3, 3): " + repr(M)) + M = _rename_and_relabel(M, "M*(K3, 3)") return M -def K33(): +def K33(groundset='abcdefghi'): r""" Return the graphic matroid `M(K_{3,3})`. @@ -1215,12 +1282,12 @@ def K33(): from sage.graphs.graph_generators import graphs G = graphs.CompleteBipartiteGraph(3, 3) - M = Matroid(groundset='abcdefghi', graph=G, regular=True) - M.rename("M(K3, 3): " + repr(M)) + M = Matroid(groundset=groundset, graph=G, regular=True) + M = _rename_and_relabel(M, "M(K3, 3)") return M -def AG23(): +def AG23(groundset='abcdefghi'): """ Return the matroid `AG(2, 3)`. @@ -1247,10 +1314,11 @@ def AG23(): [Oxl2011]_, p. 653. """ M = AG(2, 3) + M = _rename_and_relabel(M, "AG(2, 3)", groundset) return M -def TernaryDowling3(): +def TernaryDowling3(groundset='abcdefghi'): r""" Return the matroid `Q_3(GF(3)^\times)`, represented over `GF(3)`. @@ -1281,13 +1349,13 @@ def TernaryDowling3(): [0, 0, 1, 0, 0, 2, 1, 2, 1], ], ) - M = TernaryMatroid(A, 'abcdefghi') - M.rename("Q3(GF(3)x): " + repr(M)) + M = TernaryMatroid(A, groundset) + M = _rename_and_relabel(M, "Q3(GF(3)x)") return M -def R9(): - r""" +def R9(groundset=None): + """ Return the matroid `R_9`. The ternary Reid geometry. The only `9`-element rank-`3` simple ternary @@ -1314,7 +1382,7 @@ def R9(): NSC = ['abc', 'abd', 'acd', 'aef', 'agh', 'bcd', 'bfh', 'bgi', 'ceg', 'cfi', 'deh', 'dei', 'dfg', 'dhi', 'ehi'] M = Matroid(rank=3, nonspanning_circuits=NSC) - M.rename("R9: " + repr(M)) + M = _rename_and_relabel(M, "R9", groundset) return M @@ -1338,7 +1406,7 @@ def Pappus(groundset=None): {'g', 'h', 'i'}] sage: M.is_dependent(['d', 'e', 'f']) True - sage: M.is_valid() # long time + sage: M.is_valid() True sage: M.automorphism_group().is_transitive() True @@ -1348,12 +1416,12 @@ def Pappus(groundset=None): [Oxl2011]_, p. 655. """ NSC = ['abc', 'def', 'ceg', 'bfg', 'cdh', 'afh', 'bdi', 'aei', 'ghi'] - M = Matroid(groundset='abcdefghi', rank=3, nonspanning_circuits=NSC) - M.rename("Pappus: " + repr(M)) + M = Matroid(rank=3, nonspanning_circuits=NSC) + M = _rename_and_relabel(M, "Pappus", groundset) return M -def NonPappus(): +def NonPappus(groundset=None): """ Return the non-Pappus matroid. @@ -1363,15 +1431,16 @@ def NonPappus(): EXAMPLES:: - sage: from sage.matroids.advanced import setprint sage: M = matroids.catalog.NonPappus(); M NonPappus: Matroid of rank 3 on 9 elements with 8 nonspanning circuits - sage: setprint(M.nonspanning_circuits()) - [{'a', 'b', 'c'}, {'a', 'e', 'i'}, {'a', 'f', 'h'}, {'b', 'd', 'i'}, - {'b', 'f', 'g'}, {'c', 'd', 'h'}, {'c', 'e', 'g'}, {'g', 'h', 'i'}] + sage: NSC = set([('a', 'b', 'c'), ('a', 'e', 'i'), ('a', 'f', 'h'), + ....: ('b', 'd', 'i'), ('b', 'f', 'g'), ('c', 'd', 'h'), + ....: ('c', 'e', 'g'), ('g', 'h', 'i')]) + sage: NSC == set(tuple(sorted(C)) for C in M.nonspanning_circuits()) + True sage: M.is_dependent(['d', 'e', 'f']) False - sage: M.is_valid() # long time + sage: M.is_valid() and M.is_paving() True sage: M.automorphism_group().is_transitive() False @@ -1381,12 +1450,12 @@ def NonPappus(): [Oxl2011]_, p. 655. """ NSC = ['abc', 'ceg', 'bfg', 'cdh', 'afh', 'bdi', 'aei', 'ghi'] - M = Matroid(groundset='abcdefghi', rank=3, nonspanning_circuits=NSC) - M.rename("NonPappus: " + repr(M)) + M = Matroid(rank=3, nonspanning_circuits=NSC) + M = _rename_and_relabel(M, "NonPappus", groundset) return M -def K5(): +def K5(groundset='abcdefghij'): """ Return the graphic matroid `M(K_5)`. @@ -1407,10 +1476,11 @@ def K5(): [Oxl2011]_, p. 656. """ M = CompleteGraphic(5) + M = _rename_and_relabel(M, "M(K5)", groundset) return M -def K5dual(): +def K5dual(groundset='abcdefghij'): """ Return the matroid `M^*(K_5)`. @@ -1419,7 +1489,7 @@ def K5dual(): EXAMPLES:: sage: M = matroids.catalog.K5dual(); M - M*(K5): Dual of 'M(K5): Graphic matroid of rank 4 on 10 elements' + M*(K5): Matroid of rank 6 on 10 elements with 15 circuits sage: M.is_3connected() True sage: G1 = M.automorphism_group() @@ -1432,16 +1502,16 @@ def K5dual(): [Oxl2011]_, p. 656. """ M = CompleteGraphic(5).dual() - # M = Matroid(circuits=M.circuits()) - M.rename("M*(K5): " + repr(M)) + M = Matroid(circuits=list(M.circuits())) + M = _rename_and_relabel(M, "M*(K5)", groundset) return M -def R10(): +def R10(groundset='abcdefghij'): """ Return the matroid `R_{10}`, represented over the regular partial field. - The matroid `R_{10}` is a 10-element regular matroid of rank-5. It is the + The NonDesargues matroid is a `10`-element matroid of rank-5. It is the unique splitter for the class of regular matroids. It is the graft matroid of `K_{3, 3}` in which every vertex is coloured. @@ -1492,8 +1562,8 @@ def R10(): [0, 0, 0, 0, 1, 1, 0, 0, 1, -1], ], ) - M = RegularMatroid(A, 'abcdefghij') - M.rename("R10: " + repr(M)) + M = RegularMatroid(A, groundset) + M = _rename_and_relabel(M, "R10") return M @@ -1520,7 +1590,7 @@ def NonDesargues(groundset=None): """ NSC = ['acj', 'aef', 'bce', 'bfj', 'bgi', 'chi', 'dfg', 'dij', 'egh'] M = Matroid(rank=3, nonspanning_circuits=NSC) - M.rename("NonDesargues: " + repr(M)) + M = _rename_and_relabel(M, "NonDesargues", groundset) return M @@ -1539,7 +1609,7 @@ def R12(groundset='abcdefghijkl'): R12: Regular matroid of rank 6 on 12 elements with 441 bases sage: M.is_isomorphic(M.dual()) and not M.equals(M.dual()) True - sage: M.is_valid() # long time + sage: M.is_valid() True sage: M.automorphism_group().is_transitive() False @@ -1559,12 +1629,12 @@ def R12(groundset='abcdefghijkl'): [0, 0, 0, 0, 0, 1, 0, 0, 0, 1, -1, -1], ], ) - M = RegularMatroid(A, 'abcdefghijkl') - M.rename("R12: " + repr(M)) + M = RegularMatroid(A, groundset) + M = _rename_and_relabel(M, "R12") return M -def ExtendedTernaryGolayCode(): +def ExtendedTernaryGolayCode(groundset='abcdefghijkl'): """ Return the matroid of the extended ternary Golay code. @@ -1576,13 +1646,14 @@ def ExtendedTernaryGolayCode(): Extended Ternary Golay Code: Ternary matroid of rank 6 on 12 elements, type 6+ sage: C = LinearCode(M.representation()) - sage: C.is_permutation_equivalent(codes.GolayCode(GF(3))) # long time + sage: C.is_permutation_equivalent(codes.GolayCode(GF(3))) True sage: M.is_valid() True The automorphism group is the `5`-transitive Mathieu group `M12`: + sage: # long time sage: G = M.automorphism_group() sage: G.is_transitive() True @@ -1627,12 +1698,12 @@ def ExtendedTernaryGolayCode(): [0, 0, 0, 0, 1, 0, 1, 0, 2, 2, 1, 1], [0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1] ]) - M = TernaryMatroid(A, 'abcdefghijkl') - M.rename('Extended Ternary Golay Code: ' + repr(M)) + M = TernaryMatroid(A, groundset) + M = _rename_and_relabel(M, "Extended Ternary Golay Code") return M -def T12(): +def T12(groundset='abcdefghijkl'): """ Return the matroid `T_{12}`. @@ -1668,12 +1739,12 @@ def T12(): [0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1], ], ) - M = BinaryMatroid(A, 'abcdefghijkl') - M.rename("T12: " + repr(M)) + M = BinaryMatroid(A, groundset) + M = _rename_and_relabel(M, "T12") return M -def PG23(): +def PG23(groundset=None): """ Return the matroid `PG23`. @@ -1694,10 +1765,11 @@ def PG23(): [Oxl2011]_, p. 659. """ M = PG(2, 3) + M = _rename_and_relabel(M, groundset=groundset) return M -def Wheel(r, field=None, ring=None): +def Wheel(r, field=None, ring=None, groundset=None): r""" Return the rank-`r` wheel. @@ -1708,6 +1780,7 @@ def Wheel(r, field=None, ring=None): over the ring or field ``ring``. If the ring is `\ZZ`, then output will be a regular matroid. - ``field`` -- any field; same as ``ring``, but only fields are allowed + - ``groundset`` -- a string (optional); the groundset of the matroid OUTPUT: the rank-`r` wheel matroid, represented as a regular matroid @@ -1767,11 +1840,11 @@ def Wheel(r, field=None, ring=None): M = RegularMatroid(A) else: M = Matroid(A) - M.rename("Wheel(" + str(r) + "): " + repr(M)) + M = _rename_and_relabel(M, f'Wheel({r})', groundset) return M -def Whirl(r): +def Whirl(r, groundset=None): r""" Return the rank-`r` whirl. @@ -1780,6 +1853,7 @@ def Whirl(r): INPUT: - ``r`` -- a positive integer; the rank of the desired matroid. + - ``groundset`` -- a string (optional); the groundset of the matroid OUTPUT: the rank-`r` whirl matroid, represented as a ternary matroid @@ -1835,11 +1909,11 @@ def Whirl(r): else: A[i, 2 * r - 1] = 1 M = TernaryMatroid(A) - M.rename("Whirl(" + str(r) + "): " + repr(M)) + M = _rename_and_relabel(M, f'Whirl({r})', groundset) return M -def Uniform(r, n): +def Uniform(r, n, groundset=None): """ Return the uniform matroid of rank `r` on `n` elements. @@ -1852,6 +1926,7 @@ def Uniform(r, n): - ``r`` -- a nonnegative integer; the rank of the uniform matroid - ``n`` -- a nonnegative integer; the number of elements of the uniform matroid + - ``groundset`` -- a string (optional); the groundset of the matroid OUTPUT: the uniform matroid `U_{r,n}` @@ -1885,12 +1960,12 @@ def Uniform(r, n): CC = {r: [E]} else: CC = {} - M = CircuitClosuresMatroid(groundset=E, circuit_closures=CC) - M.rename("U(" + str(r) + ", " + str(n) + "): " + repr(M)) + M = Matroid(groundset=E, circuit_closures=CC) + M = _rename_and_relabel(M, f'U({r}, {n})', groundset) return M -def PG(n, q, x=None): +def PG(n, q, x=None, groundset=None): """ Return the projective geometry of dimension ``n`` over the finite field of order ``q``. @@ -1904,6 +1979,7 @@ def PG(n, q, x=None): - ``x`` -- a string (default: ``None``); the name of the generator of a non-prime field, used for non-prime fields. If not supplied, ``'x'`` is used. + - ``groundset`` -- a string (optional); the groundset of the matroid OUTPUT: a linear matroid whose elements are the points of `PG(n, q)` @@ -1928,11 +2004,11 @@ def PG(n, q, x=None): P = ProjectiveSpace(n, F) A = Matrix(F, [list(p) for p in list(P)]).transpose() M = Matroid(A) - M.rename("PG(" + str(n) + ", " + str(q) + "): " + repr(M)) + M = _rename_and_relabel(M, f'PG({n}, {q})', groundset) return M -def AG(n, q, x=None): +def AG(n, q, x=None, groundset=None): r""" Return the affine geometry of dimension ``n`` over the finite field of order ``q``. @@ -1949,6 +2025,7 @@ def AG(n, q, x=None): - ``x`` -- a string (default: ``None``); the name of the generator of a non-prime field, used for non-prime fields. If not supplied, ``'x'`` is used. + - ``groundset`` -- a string (optional); the groundset of the matroid OUTPUT: a linear matroid whose elements are the points of `AG(n, q)` @@ -1974,11 +2051,11 @@ def AG(n, q, x=None): F, [list(p) for p in list(P) if not list(p)[0] == 0] ).transpose() M = Matroid(A) - M.rename("AG(" + str(n) + ", " + str(q) + "): " + repr(M)) + M = _rename_and_relabel(M, f'AG({n}, {q})', groundset) return M -def Z(r, t=True): +def Z(r, t=True, groundset=None): r""" Return the unique rank-`r` binary spike. @@ -1987,7 +2064,8 @@ def Z(r, t=True): INPUT: - ``r`` -- an integer (`r \ge 3`); the rank of the spike - - ``t`` -- a Boolean (default: ``True``); whether the spike is tipped + - ``t`` -- boolean (default: ``True``); whether the spike is tipped + - ``groundset`` -- a string (optional); the groundset of the matroid OUTPUT: a matroid; the unique rank-`r` binary spike (tipped or tipless) @@ -2004,7 +2082,9 @@ def Z(r, t=True): sage: import random sage: Z3 = matroids.Z(3) - sage: e = random.choice(list(Z3.groundset())) + sage: E = sorted(Z3.groundset()); E + ['t', 'x1', 'x2', 'x3', 'y1', 'y2', 'y3'] + sage: e = random.choice(E) sage: Z3.delete(e).is_isomorphic(matroids.catalog.K4()) True @@ -2020,6 +2100,18 @@ def Z(r, t=True): sage: Z4.is_isomorphic(matroids.catalog.AG32()) True + and `Z_4 \setminus e \cong S_8`, for all `e \neq t`:: + + sage: Z4 = matroids.Z(4) + sage: E = sorted(Z4.groundset()) + sage: E.remove('t') + sage: e = random.choice(E) + sage: S8 = matroids.catalog.S8() + sage: Z4.delete(e).is_isomorphic(S8) + True + sage: Z4.delete('t').is_isomorphic(S8) + False + The tipless binary spike is self-dual; it is identically self-dual if and only if r is even. It also has a transitive automorphism group:: @@ -2029,7 +2121,7 @@ def Z(r, t=True): True sage: Z.equals(Z.dual()) != (r % 2 == 1) # XOR True - sage: Z.automorphism_group().is_transitive() + sage: Z.automorphism_group().is_transitive() # long time True REFERENCES: @@ -2043,17 +2135,20 @@ def Z(r, t=True): A = Id.augment(J-Id).augment(tip) M = Matroid(A) + X = [f'x{i}' for i in range(1, r + 1)] + Y = [f'y{i}' for i in range(1, r + 1)] if t: - # M = M.relabel(X+Y+['t']) - M.rename("Z_" + str(r) + ": " + repr(M)) + M = M.relabel(X + Y + ['t']) + M.rename(f'Z_{r}: ' + repr(M)) else: - M = M.delete(2*r) - # M = M.relabel(X+Y) - M.rename("Z_" + str(r) + "\\t: " + repr(M)) + M = M.delete(2 * r) + M = M.relabel(X + Y) + M.rename(f'Z_{r}\\t: ' + repr(M)) + M = _rename_and_relabel(M, groundset=groundset) return M -def Spike(r, t=True, C3=[]): +def Spike(r, t=True, C3=[], groundset=None): r""" Return a rank-`r` spike. @@ -2076,6 +2171,7 @@ def Spike(r, t=True, C3=[]): - ``t`` -- boolean (default: ``True``); whether the spike is tipped - ``C3`` -- a list (default: ``[]``); a list of extra nonspanning circuits. The default (i.e. the empty list) results in a free `r`-spike + - ``groundset`` -- a string (optional); the groundset of the matroid OUTPUT: a matroid; a rank-`r` spike (tipped or tipless) @@ -2089,7 +2185,7 @@ def Spike(r, t=True, C3=[]): sage: len(matroids.Spike(8).bases()) 4864 sage: import random - sage: r = random.choice(range(3, 8)) + sage: r = random.choice(range(3, 20)) sage: M = matroids.Spike(r) sage: M.is_3connected() True @@ -2138,18 +2234,18 @@ def Spike(r, t=True, C3=[]): E = ['t'] X, Y = [], [] for i in range(1, r + 1): - X.append('x' + str(i)) - Y.append('y' + str(i)) + X.append(f'x{i}') + Y.append(f'y{i}') E += X E += Y if C3 == [] and r > 3: # free spike (can be defined fast through circuit closures) - lines = [['t', 'x'+str(i), 'y'+str(i)] for i in range(1, r+1)] - planes = [['t', 'x'+str(i), 'y'+str(i), 'x'+str(j), 'y'+str(j)] - for i in range(1, r+1) for j in range(i+1, r+1)] + lines = [['t', f'x{i}', f'y{i}'] for i in range(1, r + 1)] + planes = [['t', f'x{i}', f'y{i}', f'x{j}', f'y{j}'] + for i in range(1, r + 1) for j in range(i + 1, r + 1)] CC = {2: lines, 3: planes, r: [E]} - M = Matroid(groundset=E, circuit_closures=CC) + M = Matroid(circuit_closures=CC) else: for S in C3: for xy in S: @@ -2166,24 +2262,24 @@ def Spike(r, t=True, C3=[]): NSC = [] # nonspanning_circuits NSC += C3 - for i in range(1, r+1): - NSC += [['t', 'x'+str(i), 'y'+str(i)]] - for j in range(i+1, r+1): - NSC += [['t', 'x'+str(i), 'y'+str(i), 'x'+str(j), 'y'+str(j)]] + for i in range(1, r + 1): + NSC += [['t', f'x{i}', f'y{i}']] + for j in range(i + 1, r + 1): + NSC += [[f'x{i}', f'y{i}', f'x{j}', f'y{j}']] - M = Matroid(groundset=E, rank=r, nonspanning_circuits=NSC) + M = Matroid(rank=r, nonspanning_circuits=NSC) free = "Free " if C3 == [] else "" tip = "" if t else "\\t" M = M if t else M.delete('t') - M.rename(free + str(r) + "-spike" + tip + ": " + repr(M)) + M = _rename_and_relabel(M, f'{free}{r}-spike{tip}', groundset) return M # Q_r(A) -def Theta(n): +def Theta(n, groundset=None): r""" Return the matroid `\Theta_n`. @@ -2193,11 +2289,14 @@ def Theta(n): INPUT: - ``n`` -- an integer (`n \ge 2`); the rank of the matroid + - ``groundset`` -- a string (optional); the groundset of the matroid OUTPUT: a matroid (`\Theta_n`) EXAMPLES:: + sage: matroids.Theta(30) + Theta_30: Matroid of rank 30 on 60 elements with 16270 circuits sage: M = matroids.Theta(2) sage: U12 = matroids.Uniform(1, 2) sage: U = U12.direct_sum(U12) @@ -2232,30 +2331,29 @@ def Theta(n): [Oxl2011]_, p. 663-4. """ - X = ['x'+str(i) for i in range(n)] - Y = ['y'+str(i) for i in range(n)] - E = X + Y + X = [f'x{i}' for i in range(n)] + Y = [f'y{i}' for i in range(n)] import itertools C = [] C += list(itertools.combinations(X, 3)) for i in range(n): Yi = [Y[j] for j in range(len(Y)) if j != i] - C += [Yi + ['x'+str(i)]] + C += [Yi + [f'x{i}']] for u in range(n): for s in range(n): - for t in range(s+1, n): + for t in range(s + 1, n): if u != s and u != t and s != t: Yu = [Y[i] for i in range(len(Y)) if i != u] - C += [Yu + ['x'+str(s)] + ['x'+str(t)]] + C += [Yu + [f'x{s}'] + [f'x{t}']] - M = Matroid(groundset=E, circuits=C) - M.rename("Theta_" + str(n) + ": " + repr(M)) + M = Matroid(circuits=C) + M = _rename_and_relabel(M, f'Theta_{n}', groundset) return M -def Psi(r): +def Psi(r, groundset=None): r""" Return the matroid `\Psi_r`. @@ -2264,6 +2362,7 @@ def Psi(r): INPUT: - ``r`` -- an integer (`r \ge 3`); the rank of the matroid + - ``groundset`` -- a string (optional); the groundset of the matroid OUTPUT: a matroid (`\Psi_r`) @@ -2294,15 +2393,15 @@ def Psi(r): sage: M = matroids.Psi(r) sage: M.equals(M.dual()) True - sage: M.automorphism_group().is_transitive() + sage: M.automorphism_group().is_transitive() # long time True REFERENCES: [Oxl2011]_, p. 664. """ - A = ['a'+str(i) for i in range(0, r)] - B = ['b'+str(i) for i in range(0, r)] + A = [f'a{i}' for i in range(0, r)] + B = [f'b{i}' for i in range(0, r)] E = A + B def generate_binary_strings(bit_count): @@ -2320,20 +2419,20 @@ def genbin(n, bs=""): NSC = [] # nonspanning circuits for i in range(0, r): - for k in range(1, r-2): - I0 = ['a'+str(i), 'b'+str(i)] - IK = ['a'+str((i+k) % r), 'b'+str((i+k) % r)] - for AB in generate_binary_strings(k-1): + for k in range(1, r - 2): + I0 = [f'a{i}', f'b{i}'] + IK = [f'a{(i+k) % r}', f'b{(i+k) % r}'] + for AB in generate_binary_strings(k - 1): C = [] C += I0 + IK j = 1 for z in AB: - C += [z+str((i+j) % r)] + C += [f'{z}{(i+j) % r}'] j += 1 NSC += [C] M = Matroid(groundset=E, rank=r, nonspanning_circuits=NSC) - M.rename("Psi_" + str(r) + ": " + repr(M)) + M = _rename_and_relabel(M, f'Psi_{r}', groundset) return M @@ -2346,7 +2445,7 @@ def genbin(n, bs=""): # 7 elements: -def RelaxedNonFano(): +def RelaxedNonFano(groundset=None): """ Return the relaxed NonFano matroid. @@ -2363,11 +2462,11 @@ def RelaxedNonFano(): w = GF4('w') A = Matrix(GF4, [[1, 1, 0, 1], [1, 0, 1, 1], [0, 1, w, 1]]) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("F7=: " + repr(M)) + M = _rename_and_relabel(M, "F7=", groundset) return M -def TippedFree3spike(): +def TippedFree3spike(groundset=None): """ Return the tipped free `3`-spike. @@ -2387,14 +2486,14 @@ def TippedFree3spike(): M = QuaternaryMatroid( reduced_matrix=A, groundset=[0, 3, 5, 1, 4, 6, 2] ) - M.rename("Tipped rank-3 free spike: " + repr(M)) + M = _rename_and_relabel(M, "Tipped rank-3 free spike", groundset) return M # 8 elements: -def AG23minusDY(): +def AG23minusDY(groundset=None): r""" Return the matroid `AG23minusDY`. @@ -2411,11 +2510,11 @@ def AG23minusDY(): """ A = Matrix(GF(3), [[1, 1, 1, 1], [1, 0, 1, 2], [2, 0, 1, 2], [2, 1, 1, 0]]) M = TernaryMatroid(reduced_matrix=A) - M.rename("Delta-Y of AG(2,3)\\e: " + repr(M)) + M = _rename_and_relabel(M, "Delta-Y of AG(2,3)\\e", groundset) return M -def TQ8(): +def TQ8(groundset=None): """ Return the matroid `TQ8`. @@ -2436,11 +2535,11 @@ def TQ8(): M = QuaternaryMatroid( reduced_matrix=A, groundset=[1, 7, 5, 3, 8, 6, 4, 2] ) - M.rename("TQ8: " + repr(M)) + M = _rename_and_relabel(M, "TQ8", groundset) return M -def P8p(): +def P8p(groundset=None): """ Return the matroid `P8^-`. @@ -2463,11 +2562,11 @@ def P8p(): M = QuaternaryMatroid( reduced_matrix=A, groundset=['a', 'c', 'b', 'f', 'd', 'e', 'g', 'h'] ) - M.rename("P8-: " + repr(M)) + M = _rename_and_relabel(M, "P8-", groundset) return M -def KP8(): +def KP8(groundset=None): """ Return the matroid `KP8`. @@ -2490,11 +2589,11 @@ def KP8(): M = QuaternaryMatroid( reduced_matrix=A, groundset=[1, 4, 3, 5, 6, 7, 0, 2] ) - M.rename("KP8: " + repr(M)) + M = _rename_and_relabel(M, "KP8", groundset) return M -def Sp8(): +def Sp8(groundset=None): """ Return the matroid `Sp8`. @@ -2517,11 +2616,11 @@ def Sp8(): M = QuaternaryMatroid( reduced_matrix=A, groundset=[1, 2, 3, 5, 4, 6, 7, 8] ) - M.rename("Sp8: " + repr(M)) + M = _rename_and_relabel(M, "Sp8", groundset) return M -def Sp8pp(): +def Sp8pp(groundset=None): """ Return the matroid `Sp8=`. @@ -2542,11 +2641,11 @@ def Sp8pp(): M = QuaternaryMatroid( reduced_matrix=A, groundset=[1, 5, 6, 7, 2, 3, 4, 8] ) - M.rename("Sp8=: " + repr(M)) + M = _rename_and_relabel(M, "Sp8=", groundset) return M -def LP8(): +def LP8(groundset=None): """ Return the matroid `LP8`. @@ -2569,11 +2668,11 @@ def LP8(): M = QuaternaryMatroid( reduced_matrix=A, groundset=['a', 'b', 'd', 'e', 'c', 'f', 'g', 'h'] ) - M.rename("LP8: " + repr(M)) + M = _rename_and_relabel(M, "LP8", groundset) return M -def WQ8(): +def WQ8(groundset=None): r""" Return the matroid `WQ8`. @@ -2596,14 +2695,14 @@ def WQ8(): M = QuaternaryMatroid( reduced_matrix=A, groundset=[0, 1, 3, 4, 2, 5, 6, 7] ) - M.rename("WQ8: " + repr(M)) + M = _rename_and_relabel(M, "WQ8", groundset) return M # 9 elements: -def BB9(): +def BB9(groundset=None): """ Return the matroid `BB9`. @@ -2638,11 +2737,11 @@ def BB9(): reduced_matrix=A, groundset=['i', 'b', 'd', 'j', 'h', 'f', 'c', 'a', 'k'] ) - M.rename("BB9: " + repr(M)) + M = _rename_and_relabel(M, "BB9", groundset) return M -def TQ9(): +def TQ9(groundset=None): """ Return the matroid `TQ9`. @@ -2673,11 +2772,11 @@ def TQ9(): M = QuaternaryMatroid( reduced_matrix=A, groundset=[1, 4, 6, 0, 2, 5, 3, 7, 8] ) - M.rename("TQ9: " + repr(M)) + M = _rename_and_relabel(M, "TQ9", groundset) return M -def TQ9p(): +def TQ9p(groundset=None): """ Return the matroid `TQ9^-`. @@ -2712,11 +2811,11 @@ def TQ9p(): M = QuaternaryMatroid( reduced_matrix=A, groundset=[1, 4, 7, 8, 0, 6, 5, 2, 3] ) - M.rename("TQ9': " + repr(M)) + M = _rename_and_relabel(M, "TQ9'", groundset) return M -def M8591(): +def M8591(groundset=None): r""" Return the matroid `M8591`. @@ -2740,11 +2839,11 @@ def M8591(): [0, 0, 1, 1, 0]] ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("M8591: " + repr(M)) + M = _rename_and_relabel(M, "M8591", groundset) return M -def PP9(): +def PP9(groundset=None): """ Return the matroid `PP9`. @@ -2780,11 +2879,11 @@ def PP9(): reduced_matrix=A, groundset=['a', 'c', 'b', 'f', 'd', 'e', 'g', 'h', 'z'] ) - M.rename("PP9: " + repr(M)) + M = _rename_and_relabel(M, "PP9", groundset) return M -def BB9gDY(): +def BB9gDY(groundset=None): r""" Return the matroid `BB9gDY`. @@ -2818,11 +2917,11 @@ def BB9gDY(): reduced_matrix=A, groundset=['c', 'd', 'i', 'f', 'h', 'a', 'j', 'k', 'b'] ) - M.rename("Segment cosegment exchange on BB9: " + repr(M)) + M = _rename_and_relabel(M, "Segment cosegment exchange on BB9", groundset) return M -def A9(): +def A9(groundset=None): """ Return the matroid `A9`. @@ -2847,11 +2946,11 @@ def A9(): M = QuaternaryMatroid( reduced_matrix=A, groundset=[6, 5, 4, 1, 2, 3, 7, 8, 0] ) - M.rename("A9: " + repr(M)) + M = _rename_and_relabel(M, "A9", groundset) return M -def FN9(): +def FN9(groundset=None): """ Return the matroid `FN9`. @@ -2881,11 +2980,11 @@ def FN9(): reduced_matrix=A, groundset=['b0', 'a', 'y', 'z', 'x', "c0", 'b', 'c', 'a0'] ) - M.rename("FN9: " + repr(M)) + M = _rename_and_relabel(M, "FN9", groundset) return M -def FX9(): +def FX9(groundset=None): """ Return the matroid `FX9`. @@ -2913,11 +3012,11 @@ def FX9(): ) # M48806 M = QuaternaryMatroid(reduced_matrix=A) - M.rename("FX9: " + repr(M)) + M = _rename_and_relabel(M, "FX9", groundset) return M -def KR9(): +def KR9(groundset=None): """ Return the matroid `KR9`. @@ -2948,11 +3047,11 @@ def KR9(): M = QuaternaryMatroid( reduced_matrix=A, groundset=[2, 4, 0, 6, 1, 5, 3, 7, 8] ) - M.rename("KR9: " + repr(M)) + M = _rename_and_relabel(M, "KR9", groundset) return M -def KQ9(): +def KQ9(groundset=None): """ Return the matroid `KQ9`. @@ -2987,14 +3086,14 @@ def KQ9(): M = QuaternaryMatroid( reduced_matrix=A, groundset=[5, 0, 4, 3, 2, 6, 8, 7, 1] ) - M.rename("KQ9: " + repr(M)) + M = _rename_and_relabel(M, "KQ9", groundset) return M # 10 elements: -def UG10(): +def UG10(groundset=None): """ Return the matroid `UG10`. @@ -3024,11 +3123,11 @@ def UG10(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("UG10: " + repr(M)) + M = _rename_and_relabel(M, "UG10", groundset) return M -def FF10(): +def FF10(groundset=None): """ Return the matroid `FF10`. @@ -3057,11 +3156,11 @@ def FF10(): M = QuaternaryMatroid( reduced_matrix=A, groundset=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] ) - M.rename("FF10: " + repr(M)) + M = _rename_and_relabel(M, "FF10", groundset) return M -def GP10(): +def GP10(groundset=None): """ Return the matroid `GP10`. @@ -3087,11 +3186,11 @@ def GP10(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("GP10: " + repr(M)) + M = _rename_and_relabel(M, "GP10", groundset) return M -def FZ10(): +def FZ10(groundset=None): """ Return the matroid `FZ10`. @@ -3118,11 +3217,11 @@ def FZ10(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("FZ10: " + repr(M)) + M = _rename_and_relabel(M, "FZ10", groundset) return M -def UQ10(): +def UQ10(groundset=None): """ Return the matroid `UQ10`. @@ -3150,11 +3249,11 @@ def UQ10(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("UQ10: " + repr(M)) + M = _rename_and_relabel(M, "UQ10", groundset) return M -def FP10(): +def FP10(groundset=None): """ Return the matroid `FP10`. @@ -3181,11 +3280,11 @@ def FP10(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("FP10: " + repr(M)) + M = _rename_and_relabel(M, "FP10", groundset) return M -def TQ10(): +def TQ10(groundset=None): """ Return the matroid `TQ10`. @@ -3218,11 +3317,11 @@ def TQ10(): M = QuaternaryMatroid( reduced_matrix=A, groundset=[1, 6, 8, 'c', 3, 7, 'd', 2, 5, 4] ) - M.rename("TQ10: " + repr(M)) + M = _rename_and_relabel(M, "TQ10", groundset) return M -def FY10(): +def FY10(groundset=None): """ Return the matroid `FY10`. @@ -3249,11 +3348,11 @@ def FY10(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("FY10: " + repr(M)) + M = _rename_and_relabel(M, "FY10", groundset) return M -def PP10(): +def PP10(groundset=None): """ Return the matroid `PP10`. @@ -3290,11 +3389,11 @@ def PP10(): reduced_matrix=A, groundset=['z', 'f', 'c', 'g', 'e', 'b', 'a', 'h', 'd', 'x'] ) - M.rename("PP10: " + repr(M)) + M = _rename_and_relabel(M, "PP10", groundset) return M -def FU10(): +def FU10(groundset=None): """ Return the matroid `FU10`. @@ -3320,11 +3419,11 @@ def FU10(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("FU10: " + repr(M)) + M = _rename_and_relabel(M, "FU10", groundset) return M -def D10(): +def D10(groundset=None): """ Return the matroid `D10`. @@ -3351,11 +3450,11 @@ def D10(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("D10: " + repr(M)) + M = _rename_and_relabel(M, "D10", groundset) return M -def UK10(): +def UK10(groundset=None): """ Return the matroid `UK10`. @@ -3382,11 +3481,11 @@ def UK10(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("UK10: " + repr(M)) + M = _rename_and_relabel(M, "UK10", groundset) return M -def PK10(): +def PK10(groundset=None): """ Return the matroid `PK10`. @@ -3413,11 +3512,11 @@ def PK10(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("PK10: " + repr(M)) + M = _rename_and_relabel(M, "PK10", groundset) return M -def GK10(): +def GK10(groundset=None): """ Return the matroid `GK10`. @@ -3444,11 +3543,11 @@ def GK10(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("GK10: " + repr(M)) + M = _rename_and_relabel(M, "GK10", groundset) return M -def FT10(): +def FT10(groundset=None): """ Return the matroid `FT10`. @@ -3475,11 +3574,11 @@ def FT10(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("FT10: " + repr(M)) + M = _rename_and_relabel(M, "FT10", groundset) return M -def TK10(): +def TK10(groundset=None): """ Return the matroid `TK10`. @@ -3506,11 +3605,11 @@ def TK10(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("TK10: " + repr(M)) + M = _rename_and_relabel(M, "TK10", groundset) return M -def KT10(): +def KT10(groundset=None): """ Return the matroid `KT10`. @@ -3537,11 +3636,11 @@ def KT10(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("KT10: " + repr(M)) + M = _rename_and_relabel(M, "KT10", groundset) return M -def TU10(): +def TU10(groundset=None): """ Return the matroid `TU10`. @@ -3568,11 +3667,11 @@ def TU10(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("TU10: " + repr(M)) + M = _rename_and_relabel(M, "TU10", groundset) return M -def UT10(): +def UT10(groundset=None): """ Return the matroid `UT10`. @@ -3599,11 +3698,11 @@ def UT10(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("UT10: " + repr(M)) + M = _rename_and_relabel(M, "UT10", groundset) return M -def FK10(): +def FK10(groundset=None): """ Return the matroid `FK10`. @@ -3630,11 +3729,11 @@ def FK10(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("FK10: " + repr(M)) + M = _rename_and_relabel(M, "FK10", groundset) return M -def KF10(): +def KF10(groundset=None): """ Return the matroid `KF10`. @@ -3661,14 +3760,14 @@ def KF10(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("KF10: " + repr(M)) + M = _rename_and_relabel(M, "KF10", groundset) return M # 11 elements: -def FA11(): +def FA11(groundset=None): """ Return the matroid `FA11`. @@ -3699,14 +3798,14 @@ def FA11(): M = QuaternaryMatroid( reduced_matrix=A, groundset=[1, 3, 4, 2, 8, 7, 9, 0, 5, 10, 6] ) - M.rename("FA11: " + repr(M)) + M = _rename_and_relabel(M, "FA11", groundset) return M # 12 elements: -def FR12(): +def FR12(groundset=None): """ Return the matroid `FR12`. @@ -3734,11 +3833,11 @@ def FR12(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("FR12: " + repr(M)) + M = _rename_and_relabel(M, "FR12", groundset) return M -def GP12(): +def GP12(groundset=None): """ Return the matroid `GP12`. @@ -3766,11 +3865,11 @@ def GP12(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("GP12: " + repr(M)) + M = _rename_and_relabel(M, "GP12", groundset) return M -def FQ12(): +def FQ12(groundset=None): """ Return the matroid `FQ12`. @@ -3807,11 +3906,11 @@ def FQ12(): M = QuaternaryMatroid( reduced_matrix=A, groundset=[7, 4, 5, 9, 2, 1, 0, 6, 'd', 'c', 8, 3] ) - M.rename("FQ12: " + repr(M)) + M = _rename_and_relabel(M, "FQ12", groundset) return M -def FF12(): +def FF12(groundset=None): """ Return the matroid `FF12`. @@ -3846,11 +3945,11 @@ def FF12(): M = QuaternaryMatroid( reduced_matrix=A, groundset=[0, 4, 'c', 3, 5, 'd', 8, 9, 2, 7, 1, 6] ) - M.rename("FF12: " + repr(M)) + M = _rename_and_relabel(M, "FF12", groundset) return M -def FZ12(): +def FZ12(groundset=None): """ Return the matroid `FZ12`. @@ -3878,11 +3977,11 @@ def FZ12(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("FZ12: " + repr(M)) + M = _rename_and_relabel(M, "FZ12", groundset) return M -def UQ12(): +def UQ12(groundset=None): """ Return the matroid `UQ12`. @@ -3910,11 +4009,11 @@ def UQ12(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("UQ12: " + repr(M)) + M = _rename_and_relabel(M, "UQ12", groundset) return M -def FP12(): +def FP12(groundset=None): """ Return the matroid `FP12`. @@ -3942,11 +4041,11 @@ def FP12(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("FP12: " + repr(M)) + M = _rename_and_relabel(M, "FP12", groundset) return M -def FS12(): +def FS12(groundset=None): """ Return the matroid `FS12`. @@ -3973,11 +4072,11 @@ def FS12(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("FS12: " + repr(M)) + M = _rename_and_relabel(M, "FS12", groundset) return M -def UK12(): +def UK12(groundset=None): """ Return the matroid `UK12`. @@ -4005,11 +4104,11 @@ def UK12(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("UK12: " + repr(M)) + M = _rename_and_relabel(M, "UK12", groundset) return M -def UA12(): +def UA12(groundset=None): """ Return the matroid `UA12`. @@ -4037,11 +4136,11 @@ def UA12(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("UA12: " + repr(M)) + M = _rename_and_relabel(M, "UA12", groundset) return M -def AK12(): +def AK12(groundset=None): """ Return the matroid `AK12`. @@ -4069,11 +4168,11 @@ def AK12(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("AK12: " + repr(M)) + M = _rename_and_relabel(M, "AK12", groundset) return M -def FK12(): +def FK12(groundset=None): """ Return the matroid `FK12`. @@ -4101,11 +4200,11 @@ def FK12(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("FK12: " + repr(M)) + M = _rename_and_relabel(M, "FK12", groundset) return M -def KB12(): +def KB12(groundset=None): """ Return the matroid `KB12`. @@ -4133,11 +4232,11 @@ def KB12(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("KB12: " + repr(M)) + M = _rename_and_relabel(M, "KB12", groundset) return M -def AF12(): +def AF12(groundset=None): """ Return the matroid `AF12`. @@ -4165,11 +4264,11 @@ def AF12(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("AF12: " + repr(M)) + M = _rename_and_relabel(M, "AF12", groundset) return M -def NestOfTwistedCubes(): +def NestOfTwistedCubes(groundset=None): r""" Return the NestOfTwistedCubes matroid. @@ -4178,7 +4277,8 @@ def NestOfTwistedCubes(): EXAMPLES:: - sage: M = matroids.catalog.NestOfTwistedCubes() + sage: M = matroids.catalog.NestOfTwistedCubes(); M + NestOfTwistedCubes: Matroid of rank 6 on 12 elements with 57 circuits sage: M.is_3connected() True """ @@ -4188,7 +4288,7 @@ def complement(groundset, subset): gs = ["e1", "e2", "e3", "e4", "e5", "e6", "f1", "f2", "f3", "f4", "f5", "f6"] - M = CircuitClosuresMatroid( + M = Matroid( groundset=gs, circuit_closures={ 3: [ @@ -4228,15 +4328,15 @@ def complement(groundset, subset): 6: [gs], }, ) - M = Matroid(circuits=M.circuits()) - M.rename("NestOfTwistedCubes: " + repr(M)) + M = Matroid(circuits=list(M.circuits())) + M = _rename_and_relabel(M, "NestOfTwistedCubes", groundset) return M # 13 elements: -def XY13(): +def XY13(groundset=None): """ Return the matroid `XY13`. @@ -4264,14 +4364,14 @@ def XY13(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("XY13: " + repr(M)) + M = _rename_and_relabel(M, "XY13", groundset) return M # 14 elements: -def N3(): +def N3(groundset=None): """ Return the matroid `N3`. @@ -4302,11 +4402,11 @@ def N3(): ], ) M = TernaryMatroid(reduced_matrix=A) - M.rename("N3: " + repr(M)) + M = _rename_and_relabel(M, "N3", groundset) return M -def N3pp(): +def N3pp(groundset=None): """ Return the matroid `N3pp`. @@ -4338,11 +4438,11 @@ def N3pp(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("N3=: " + repr(M)) + M = _rename_and_relabel(M, "N3=", groundset) return M -def UP14(): +def UP14(groundset=None): """ Return the matroid `UP14`. @@ -4371,11 +4471,11 @@ def UP14(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("UP14: " + repr(M)) + M = _rename_and_relabel(M, "UP14", groundset) return M -def VP14(): +def VP14(groundset=None): """ Return the matroid `VP14`. @@ -4404,11 +4504,11 @@ def VP14(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("VP14: " + repr(M)) + M = _rename_and_relabel(M, "VP14", groundset) return M -def FV14(): +def FV14(groundset=None): """ Return the matroid `FV14` @@ -4437,11 +4537,11 @@ def FV14(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("FV14: " + repr(M)) + M = _rename_and_relabel(M, "FV14", groundset) return M -def OW14(): +def OW14(groundset=None): """ Return the matroid `OW14`. @@ -4470,11 +4570,11 @@ def OW14(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("OW14: " + repr(M)) + M = _rename_and_relabel(M, "OW14", groundset) return M -def FM14(): +def FM14(groundset=None): """ Return the matroid `FM14`. @@ -4502,14 +4602,14 @@ def FM14(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("FM14: " + repr(M)) + M = _rename_and_relabel(M, "FM14", groundset) return M # 15 elements: -def FA15(): +def FA15(groundset=None): """ Return the matroid `FA15`. @@ -4539,14 +4639,14 @@ def FA15(): ], ) M = QuaternaryMatroid(reduced_matrix=A) - M.rename("FA15: " + repr(M)) + M = _rename_and_relabel(M, "FA15", groundset) return M # 16 elements: -def N4(): +def N4(groundset=None): """ Return the matroid `N4`. @@ -4578,7 +4678,7 @@ def N4(): ], ) M = TernaryMatroid(reduced_matrix=A) - M.rename("N4: " + repr(M)) + M = _rename_and_relabel(M, "N4", groundset) return M @@ -4588,7 +4688,7 @@ def N4(): # ******************************** # -def NonVamos(): +def NonVamos(groundset=None): r""" Return the non-`V\acute{a}mos` matroid. @@ -4616,17 +4716,16 @@ def NonVamos(): [Oxl2011]_, p. 72, 84. """ - E = 'abcdefgh' CC = { 3: ['abcd', 'abef', 'cdef', 'abgh', 'cdgh', 'efgh'], - 4: [E] + 4: ['abcdefgh'] } - M = CircuitClosuresMatroid(groundset=E, circuit_closures=CC) - M.rename('NonVamos: ' + repr(M)) + M = Matroid(circuit_closures=CC) + M = _rename_and_relabel(M, "NonVamos", groundset) return M -def NotP8(): +def NotP8(groundset='abcdefgh'): """ Return the matroid ``NotP8``. @@ -4650,12 +4749,12 @@ def NotP8(): [0, 0, 1, 0, 1, 1, 0, 1], [0, 0, 0, 1, -1, 1, 1, 1] ]) - M = TernaryMatroid(A, "abcdefgh") - M.rename('NotP8: ' + repr(M)) + M = TernaryMatroid(A, groundset) + M = _rename_and_relabel(M, "NotP8") return M -def AG23minus(): +def AG23minus(groundset=None): """ Return the ternary affine plane minus a point. @@ -4677,15 +4776,14 @@ class of near-regular matroids. [Oxl2011]_, p. 653. """ - E = 'abcdefgh' CC = {2: ['abc', 'ceh', 'fgh', 'adf', 'aeg', 'cdg', 'bdh', 'bef'], - 3: [E]} - M = CircuitClosuresMatroid(groundset=E, circuit_closures=CC) - M.rename('AG23minus: ' + repr(M)) + 3: ['abcdefgh']} + M = Matroid(circuit_closures=CC) + M = _rename_and_relabel(M, "AG23minus", groundset) return M -def P9(): +def P9(groundset='abcdefghi'): """ Return the matroid `P_9`. @@ -4707,12 +4805,12 @@ def P9(): [0, 0, 1, 0, 0, 1, 1, 0, 1], [0, 0, 0, 1, 0, 0, 1, 1, 0] ]) - M = BinaryMatroid(A, "abcdefghi") - M.rename('P9: ' + repr(M)) + M = BinaryMatroid(A, groundset) + M = _rename_and_relabel(M, "P9") return M -def R9A(): +def R9A(groundset=None): """ Return the matroid `R_9^A`. @@ -4730,12 +4828,12 @@ def R9A(): """ NSC = ['abch', 'abde', 'abfi', 'acdi', 'aceg', 'adgh', 'aefh', 'bcdf', 'bdhi', 'begi', 'cehi', 'defi', 'fghi'] - M = Matroid(groundset='abcdefghi', rank=4, nonspanning_circuits=NSC) - M.rename("R9A: " + repr(M)) + M = Matroid(rank=4, nonspanning_circuits=NSC) + M = _rename_and_relabel(M, "R9A", groundset) return M -def R9B(): +def R9B(groundset=None): """ Return the matroid `R_9^B`. @@ -4753,12 +4851,12 @@ def R9B(): """ NSC = ['abde', 'bcdf', 'aceg', 'abch', 'befh', 'cdgh', 'bcei', 'adfi', 'abgi', 'degi', 'bdhi', 'aehi', 'fghi'] - M = Matroid(groundset='abcdefghi', rank=4, nonspanning_circuits=NSC) - M.rename("R9B: " + repr(M)) + M = Matroid(rank=4, nonspanning_circuits=NSC) + M = _rename_and_relabel(M, "R9B", groundset) return M -def Block_9_4(): +def Block_9_4(groundset=None): """ Return the paving matroid whose nonspanning circuits form the blocks of a `2-(9, 4, 3)` design. @@ -4770,19 +4868,19 @@ def Block_9_4(): circuits sage: M.is_valid() and M.is_paving() True - sage: BD = BlockDesign(M.groundset(), M.nonspanning_circuits()) + sage: BD = BlockDesign(M.groundset(), list(M.nonspanning_circuits())) sage: BD.is_t_design(return_parameters=True) (True, (2, 9, 4, 3)) """ NSC = ['abcd', 'acef', 'bdef', 'cdeg', 'abfg', 'adeh', 'bcfh', 'acgh', 'begh', 'dfgh', 'abei', 'cdfi', 'bcgi', 'adgi', 'efgi', 'bdhi', 'cehi', 'afhi'] - M = Matroid(groundset='abcdefghi', rank=4, nonspanning_circuits=NSC) - M.rename("Block(9, 4): " + repr(M)) + M = Matroid(rank=4, nonspanning_circuits=NSC) + M = _rename_and_relabel(M, "Block(9, 4)", groundset) return M -def TicTacToe(): +def TicTacToe(groundset=None): """ Return the TicTacToe matroid. @@ -4802,12 +4900,12 @@ def TicTacToe(): """ NSC = ['abcdg', 'adefg', 'abceh', 'abcfi', 'cdefi', 'adghi', 'beghi', 'cfghi'] - M = Matroid(groundset='abcdefghi', rank=5, nonspanning_circuits=NSC) - M.rename("TicTacToe: " + repr(M)) + M = Matroid(rank=5, nonspanning_circuits=NSC) + M = _rename_and_relabel(M, "TicTacToe", groundset) return M -def N1(): +def N1(groundset='abcdefghij'): r""" Return the matroid `N_1`, represented over `\GF{3}`. @@ -4833,12 +4931,12 @@ def N1(): [0, 0, 0, 1, 0, 0, 0, 1, 2, 2], [0, 0, 0, 0, 1, 1, 1, 1, 2, 0] ]) - M = TernaryMatroid(A, 'abcdefghij') - M.rename('N1: ' + repr(M)) + M = TernaryMatroid(A, groundset) + M = _rename_and_relabel(M, "N1") return M -def Block_10_5(): +def Block_10_5(groundset=None): """ Return the paving matroid whose nonspanning circuits form the blocks of a `3-(10, 5, 3)` design. @@ -4850,7 +4948,7 @@ def Block_10_5(): circuits sage: M.is_valid() and M.is_paving() True - sage: BD = BlockDesign(M.groundset(), M.nonspanning_circuits()) + sage: BD = BlockDesign(M.groundset(), list(M.nonspanning_circuits())) sage: BD.is_t_design(return_parameters=True) (True, (3, 10, 5, 3)) """ @@ -4860,12 +4958,12 @@ def Block_10_5(): 'cdegj', 'bcfgj', 'acdhj', 'bcehj', 'defhj', 'bdghj', 'afghj', 'abcij', 'bdeij', 'cdfij', 'adgij', 'efgij', 'aehij', 'bfhij', 'cghij'] - M = Matroid(groundset='abcdefghij', rank=5, nonspanning_circuits=NSC) - M.rename("Block(10, 5): " + repr(M)) + M = Matroid(rank=5, nonspanning_circuits=NSC) + M = _rename_and_relabel(M, "Block(10, 5)", groundset) return M -def Q10(): +def Q10(groundset='abcdefghij'): r""" Return the matroid `Q_{10}`, represented over `\GF{4}`. @@ -4876,7 +4974,8 @@ def Q10(): EXAMPLES:: - sage: M = matroids.catalog.Q10() + sage: M = matroids.catalog.Q10(); M + Q10: Quaternary matroid of rank 5 on 10 elements sage: M.is_isomorphic(M.dual()) True sage: M.is_valid() @@ -4901,12 +5000,12 @@ def Q10(): [0, 0, 0, 1, 0, 0, 0, x + 1, 1, x], [0, 0, 0, 0, 1, x, 0, 0, x + 1, 1] ]) - M = QuaternaryMatroid(A, "abcdefghij") - M.rename('Q10: ' + repr(M)) + M = QuaternaryMatroid(A, groundset) + M = _rename_and_relabel(M, "Q10") return M -def BetsyRoss(): +def BetsyRoss(groundset=None): """ Return the Betsy Ross matroid, represented by circuit closures. @@ -4927,12 +5026,12 @@ def BetsyRoss(): NSC = ['acf', 'acg', 'adi', 'adj', 'afg', 'ahk', 'aij', 'bdg', 'bdh', 'bef', 'bej', 'bfj', 'bgh', 'bik', 'ceh', 'cei', 'cfg', 'chi', 'cjk', 'dfk', 'dgh', 'dij', 'efj', 'egk', 'ehi'] - M = Matroid(groundset='abcdefghijk', rank=3, nonspanning_circuits=NSC) - M.rename("BetsyRoss: " + repr(M)) + M = Matroid(rank=3, nonspanning_circuits=NSC) + M = _rename_and_relabel(M, "BetsyRoss", groundset) return M -def N2(): +def N2(groundset='abcdefghijkl'): r""" Return the matroid `N_2`, represented over `\GF{3}`. @@ -4959,8 +5058,8 @@ def N2(): [0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1], [0, 0, 0, 0, 0, 1, 1, 2, 2, 1, 0, 1] ]) - M = TernaryMatroid(A, "abcdefghijkl") - M.rename('N2: ' + repr(M)) + M = TernaryMatroid(A, groundset) + M = _rename_and_relabel(M, "N2") return M @@ -4994,12 +5093,12 @@ def D16(groundset='abcdefghijklmnop'): # A.K.A. the Carolyn Chun Matroid [0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0] ]) - M = BinaryMatroid(A, "abcdefghijklmnop") - M.rename('D16: ' + repr(M)) + M = BinaryMatroid(A, groundset) + M = _rename_and_relabel(M, "D16") return M -def Terrahawk(): # A.K.A. the Dillon Mayhew Matroid +def Terrahawk(groundset='abcdefghijklmnop'): # aka the Dillon Mayhew Matroid """ Return the Terrahawk matroid. @@ -5027,12 +5126,12 @@ def Terrahawk(): # A.K.A. the Dillon Mayhew Matroid [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0] ]) - M = BinaryMatroid(A, "abcdefghijklmnop") - M.rename('Terrahawk: ' + repr(M)) + M = BinaryMatroid(A, groundset) + M = _rename_and_relabel(M, "Terrahawk") return M -def ExtendedBinaryGolayCode(): +def ExtendedBinaryGolayCode(groundset='abcdefghijklmnopqrstuvwx'): """ Return the matroid of the extended binary Golay code. @@ -5076,12 +5175,12 @@ def ExtendedBinaryGolayCode(): [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] ]) - M = BinaryMatroid(A, "abcdefghijklmnopqrstuvwx") - M.rename('Extended Binary Golay Code: ' + repr(M)) + M = BinaryMatroid(A, groundset) + M = _rename_and_relabel(M, "Extended Binary Golay Code") return M -def CompleteGraphic(n): +def CompleteGraphic(n, groundset=None): """ Return the cycle matroid of the complete graph on `n` vertices. @@ -5115,5 +5214,37 @@ def CompleteGraphic(n): groundset=list(range((n * (n - 1)) // 2)), graph=graphs.CompleteGraph(n) ) - M.rename("M(K" + str(n) + "): " + repr(M)) + M = _rename_and_relabel(M, f'M(K{n})', groundset) + return M + + +# helper function + + +def _rename_and_relabel(M, name=None, groundset=None): + """ + Return a renamed and relabeled matroid. + + This is a helper function for easily renaming and relabeling matroids upon + definition in the context of the database of matroids. + + INPUT: + + - ``M`` -- a matroid + - ``name`` -- a string (optional) + - ``groundset`` -- a string (optional) + + OUTPUT: a matroid + """ + if groundset is not None: + if len(groundset) != len(M.groundset()): + raise ValueError( + "The groundset should be of size %s (%s given)." % + (len(M.groundset()), len(groundset)) + ) + M = M.relabel(dict(zip(M.groundset(), groundset))) + + if name is not None: + M.rename(name+": " + repr(M)) + return M diff --git a/src/sage/matroids/dual_matroid.py b/src/sage/matroids/dual_matroid.py index f3cabe0dc42..37b9e6c046b 100644 --- a/src/sage/matroids/dual_matroid.py +++ b/src/sage/matroids/dual_matroid.py @@ -386,7 +386,8 @@ def dual(self): """ return self._matroid - # REPRESENTATION + # representation + def _repr_(self): """ Return a string representation of the matroid. @@ -404,7 +405,7 @@ def _repr_(self): """ return "Dual of '" + repr(self._matroid) + "'" - # COMPARISON + # comparison def __hash__(self): r""" @@ -494,46 +495,7 @@ def __ne__(self, other): """ return not self == other - # COPYING, LOADING, SAVING - - def __copy__(self): - """ - Create a shallow copy. - - EXAMPLES:: - - sage: M = matroids.catalog.Vamos() - sage: N = copy(M) # indirect doctest - sage: M == N - True - sage: M.groundset() is N.groundset() - True - """ - N = DualMatroid(self._matroid) - N.rename(self.get_custom_name()) - return N - - def __deepcopy__(self, memo={}): - """ - Create a deep copy. - - .. NOTE:: - - Since matroids are immutable, a shallow copy normally suffices. - - EXAMPLES:: - - sage: M = matroids.catalog.Vamos().dual() - sage: N = deepcopy(M) # indirect doctest - sage: M == N - True - sage: M.groundset() is N.groundset() - False - """ - from copy import deepcopy - N = DualMatroid(deepcopy(self._matroid, memo)) - N.rename(deepcopy(self.get_custom_name(), memo)) - return N + # copying, loading, saving def __reduce__(self): """ diff --git a/src/sage/matroids/flats_matroid.pxd b/src/sage/matroids/flats_matroid.pxd index e97b929429b..440dac7012c 100644 --- a/src/sage/matroids/flats_matroid.pxd +++ b/src/sage/matroids/flats_matroid.pxd @@ -13,8 +13,9 @@ cdef class FlatsMatroid(Matroid): cpdef flats(self, k) cpdef whitney_numbers2(self) - # isomorphism + # isomorphism and relabeling cpdef _is_isomorphic(self, other, certificate=*) + cpdef relabel(self, mapping) # verification cpdef is_valid(self) diff --git a/src/sage/matroids/flats_matroid.pyx b/src/sage/matroids/flats_matroid.pyx index ae95455bb2f..74f64621dd1 100644 --- a/src/sage/matroids/flats_matroid.pyx +++ b/src/sage/matroids/flats_matroid.pyx @@ -205,7 +205,7 @@ cdef class FlatsMatroid(Matroid): Matroid of rank 6 on 6 elements with 64 flats """ flats_num = sum(1 for i in self._F for F in self._F[i]) - return Matroid._repr_(self) + " with " + str(flats_num) + " flats" + return f'{Matroid._repr_(self)} with {flats_num} flats' # comparison @@ -300,6 +300,52 @@ cdef class FlatsMatroid(Matroid): version = 0 return sage.matroids.unpickling.unpickle_flats_matroid, (version, data) + cpdef relabel(self, mapping): + r""" + Return an isomorphic matroid with relabeled groundset. + + The output is obtained by relabeling each element ``e`` by + ``mapping[e]``, where ``mapping`` is a given injective map. If + ``mapping[e]`` is not defined, then the identity map is assumed. + + INPUT: + + - ``mapping`` -- a python object such that ``mapping[e]`` is the new + label of ``e`` + + OUTPUT: a matroid + + EXAMPLES:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(matroids.catalog.RelaxedNonFano()) + sage: sorted(M.groundset()) + [0, 1, 2, 3, 4, 5, 6] + sage: N = M.relabel({'g': 'x', 0: 'z'}) # 'g': 'x' is ignored + sage: from sage.matroids.utilities import cmp_elements_key + sage: sorted(N.groundset(), key=cmp_elements_key) + [1, 2, 3, 4, 5, 6, 'z'] + sage: M.is_isomorphic(N) + True + + TESTS:: + + sage: from sage.matroids.flats_matroid import FlatsMatroid + sage: M = FlatsMatroid(matroids.catalog.RelaxedNonFano()) + sage: f = {0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e', 5: 'f', 6: 'g'} + sage: N = M.relabel(f) + sage: for S in powerset(M.groundset()): + ....: assert M.rank(S) == N.rank([f[x] for x in S]) + """ + d = self._relabel_map(mapping) + E = [d[x] for x in self._groundset] + F = {} + for i in self._F: + F[i] = [] + F[i] += [[d[y] for y in x] for x in self._F[i]] + M = FlatsMatroid(groundset=E, flats=F) + return M + # enumeration cpdef flats(self, k): diff --git a/src/sage/matroids/graphic_matroid.py b/src/sage/matroids/graphic_matroid.py index 57bbae15f22..76447037974 100644 --- a/src/sage/matroids/graphic_matroid.py +++ b/src/sage/matroids/graphic_matroid.py @@ -306,8 +306,7 @@ def _repr_(self): """ self._mrank = str(self._rank(self._groundset)) self._elts = str(len(self._groundset)) - - return "Graphic matroid of rank " + self._mrank + " on " + self._elts + " elements" + return f'Graphic matroid of rank {self._mrank} on {self._elts} elements' # Comparison: @@ -450,46 +449,6 @@ def __ne__(self, other): # Copying, loading, saving: - def __copy__(self): - """ - Create a shallow copy. - - Creating a ``GraphicMatroid`` instance will build a new graph, so - the copies have no attributes in common. - - EXAMPLES:: - - sage: M = Matroid(graphs.PappusGraph()) - sage: N = copy(M) - sage: M == N - True - sage: M._G is N._G - False - """ - N = GraphicMatroid(self._G) - N.rename(self.get_custom_name()) - return N - - def __deepcopy__(self, memo={}): - """ - Create a deep copy. - - .. NOTE:: - - Since matroids are immutable, a shallow copy normally suffices. - - EXAMPLES:: - - sage: M = Matroid(graphs.PetersenGraph()) - sage: N = deepcopy(M) - sage: N == M - True - """ - # The only real difference between this and __copy__() is the memo - N = GraphicMatroid(deepcopy(self._G, memo)) - N.rename(deepcopy(self.get_custom_name(), memo)) - return N - def __reduce__(self): """ Save the matroid for later reloading. @@ -1161,6 +1120,34 @@ def is_valid(self): """ return True + def is_graphic(self): + r""" + Return if ``self`` is graphic. + + This is trivially ``True`` for a class:`GraphicMatroid`. + + EXAMPLES:: + + sage: M = Matroid(graphs.PetersenGraph()) + sage: M.is_graphic() + True + """ + return True + + def is_regular(self): + r""" + Return if ``self`` is regular. + + This is always ``True`` for a class:`GraphicMatroid`. + + EXAMPLES:: + + sage: M = Matroid(graphs.DesarguesGraph()) + sage: M.is_regular() + True + """ + return True + # Graphic methods: def graph(self): @@ -2001,3 +1988,42 @@ def regular_matroid(self): from sage.matroids.constructor import Matroid as ConstructorMatroid X = [l for u, v, l in self._G.edge_iterator()] return ConstructorMatroid(groundset=X, graph=self._G, regular=True) + + def relabel(self, mapping): + r""" + Return an isomorphic matroid with relabeled groundset. + + The output is obtained by relabeling each element ``e`` by + ``mapping[e]``, where ``mapping`` is a given injective map. If + ``mapping[e]`` is not defined, then the identity map is assumed. + + INPUT: + + - ``mapping`` -- a python object such that ``mapping[e]`` is the new + label of ``e`` + + OUTPUT: a matroid + + EXAMPLES:: + + sage: M = matroids.CompleteGraphic(4) + sage: sorted(M.groundset()) + [0, 1, 2, 3, 4, 5] + sage: N = M.relabel({0: 6, 5: 'e'}) + sage: sorted(N.groundset(), key=str) + [1, 2, 3, 4, 6, 'e'] + sage: N.is_isomorphic(M) + True + + TESTS:: + + sage: M = matroids.CompleteGraphic(4) + sage: f = {0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e', 5: 'f', 6: 'g'} + sage: N = M.relabel(f) + sage: for S in powerset(M.groundset()): + ....: assert M.rank(S) == N.rank([f[x] for x in S]) + """ + d = self._relabel_map(mapping) + E = [d[x] for x in self.groundset()] + M = GraphicMatroid(self.graph(), groundset=E) + return M diff --git a/src/sage/matroids/lean_matrix.pyx b/src/sage/matroids/lean_matrix.pyx index 34c498b016a..ff511148392 100644 --- a/src/sage/matroids/lean_matrix.pyx +++ b/src/sage/matroids/lean_matrix.pyx @@ -476,32 +476,6 @@ cdef class LeanMatrix: # Copying, loading, saving: - def __copy__(self): - """ - Return a copy of ``self``. - - EXAMPLES:: - - sage: from sage.matroids.lean_matrix import * - sage: A = GenericMatrix(2, 5, Matrix(GF(5), [[1, 0, 1, 1, 1], [0, 1, 1, 2, 3]])) - sage: A == copy(A) # indirect doctest - True - """ - return self.copy() - - def __deepcopy__(self, memo=None): - """ - Return a deep copy of ``self``. - - EXAMPLES:: - - sage: from sage.matroids.lean_matrix import * - sage: A = GenericMatrix(2, 5, Matrix(GF(5), [[1, 0, 1, 1, 1], [0, 1, 1, 2, 3]])) - sage: A == deepcopy(A) # indirect doctest - True - """ - return self.copy() - def __reduce__(self): """ Save the object. diff --git a/src/sage/matroids/linear_matroid.pxd b/src/sage/matroids/linear_matroid.pxd index 4902ccef6ef..76027b61a9f 100644 --- a/src/sage/matroids/linear_matroid.pxd +++ b/src/sage/matroids/linear_matroid.pxd @@ -28,6 +28,7 @@ cdef class LinearMatroid(BasisExchangeMatroid): cpdef is_field_isomorphism(self, other, morphism) # cpdef is_field_isomorphic(self, other) # TODO: currently only works as ``def`` cpdef _fast_isom_test(self, other) + cpdef relabel(self, mapping) cpdef _minor(self, contractions, deletions) cpdef dual(self) @@ -88,6 +89,7 @@ cdef class BinaryMatroid(LinearMatroid): cpdef BinaryMatrix _projection(self) cpdef BinaryMatrix _projection_partition(self) cpdef _fast_isom_test(self, other) + cpdef relabel(self, mapping) cpdef is_graphic(self) cpdef is_valid(self) @@ -118,6 +120,7 @@ cdef class TernaryMatroid(LinearMatroid): cpdef _principal_quadripartition(self) cpdef TernaryMatrix _projection(self) cpdef _fast_isom_test(self, other) + cpdef relabel(self, mapping) cpdef is_valid(self) @@ -144,6 +147,7 @@ cdef class QuaternaryMatroid(LinearMatroid): cpdef bicycle_dimension(self) cpdef _principal_tripartition(self) cpdef _fast_isom_test(self, other) + cpdef relabel(self, mapping) cpdef is_valid(self) @@ -159,6 +163,7 @@ cdef class RegularMatroid(LinearMatroid): cpdef _invariant(self) cpdef _fast_isom_test(self, other) + cpdef relabel(self, mapping) cpdef bases_count(self) cpdef _projection(self) diff --git a/src/sage/matroids/linear_matroid.pyx b/src/sage/matroids/linear_matroid.pyx index 7b6eb9bb711..0027d85073f 100644 --- a/src/sage/matroids/linear_matroid.pyx +++ b/src/sage/matroids/linear_matroid.pyx @@ -466,8 +466,7 @@ cdef class LinearMatroid(BasisExchangeMatroid): 'Linear matroid of rank 2 on 5 elements represented over the Finite Field of size 5' """ - S = "Linear matroid of rank " + str(self.rank()) + " on " + str(self.size()) + " elements represented over the " + repr(self.base_ring()) - return S + return f'Linear matroid of rank {self.rank()} on {self.size()} elements represented over the {self.base_ring()!r}' # representations @@ -496,17 +495,26 @@ cdef class LinearMatroid(BasisExchangeMatroid): - ``B`` -- (default: ``None``) a subset of elements. When provided, the representation is such that a basis `B'` that maximally intersects `B` is an identity matrix. + - ``reduced`` -- (default: ``False``) when ``True``, return a reduced matrix `D` (so `[I\ \ D]` is a representation of the matroid). Otherwise return a full representation matrix. + - ``labels`` -- (default: ``None``) when ``True``, return additionally a list of column labels (if ``reduced=False``) or a list of row labels and a list of column labels (if ``reduced=True``). The default setting, ``None``, will not return the labels for a full matrix, but will return the labels for a reduced matrix. - - ``order`` -- (default: ``None``) an ordering of the groundset - elements. If provided, the columns (and, in case of a reduced - representation, rows) will be presented in the given order. + + - ``order`` -- sequence or ``None`` or ``True`` (default: ``None``); + + - when a sequence, it should be an ordering of the groundset + elements, and the columns (and, in case of a reduced + representation, rows) will be presented in the given order, + - when ``None``, use the same ordering that :meth:`groundset_list` + uses, + - when ``True``, return a morphism of free modules instead of a matrix. + - ``lift_map`` -- (default: ``None``) a dictionary containing the cross ratios of the representing matrix in its domain. If provided, the representation will be transformed by mapping its cross ratios according @@ -579,9 +587,47 @@ cdef class LinearMatroid(BasisExchangeMatroid): [ 1 0 0 1 0 1 1 1] [ 0 1 0 -z + 1 1 0 0 1] [ 0 0 1 0 1 -1 z - 1 0] + + As morphisms:: + + sage: M = matroids.catalog.Fano() + sage: A = M.representation(order=True); A + Generic morphism: + From: Free module generated by {'a', 'b', 'c', 'd', 'e', 'f', 'g'} + over Finite Field of size 2 + To: Free module generated by {0, 1, 2} over Finite Field of size 2 + sage: print(A._unicode_art_matrix()) + a b c d e f g + 0⎛1 0 0 0 1 1 1⎞ + 1⎜0 1 0 1 0 1 1⎟ + 2⎝0 0 1 1 1 0 1⎠ + sage: A = M.representation(B='efg', order=True); A + Generic morphism: + From: Free module generated by {'a', 'b', 'c', 'd', 'e', 'f', 'g'} + over Finite Field of size 2 + To: Free module generated by {0, 1, 2} over Finite Field of size 2 + sage: print(A._unicode_art_matrix()) + a b c d e f g + 0⎛1 1 0 1 1 0 0⎞ + 1⎜1 0 1 1 0 1 0⎟ + 2⎝1 1 1 0 0 0 1⎠ + sage: A = M.representation(B='abc', order=True, reduced=True); A + Generic morphism: + From: Free module generated by {'d', 'e', 'f', 'g'} + over Finite Field of size 2 + To: Free module generated by {'a', 'b', 'c'} over Finite Field of size 2 + sage: print(A._unicode_art_matrix()) + d e f g + a⎛0 1 1 1⎞ + b⎜1 0 1 1⎟ + c⎝1 1 0 1⎠ """ cdef LeanMatrix A - if order is None: + column_keys = None + if order is True: + order = self.groundset_list() + column_keys = tuple(order) + elif order is None: order = self.groundset_list() else: if not frozenset(order) == self.groundset(): @@ -608,16 +654,14 @@ cdef class LinearMatroid(BasisExchangeMatroid): B = self._subset_internal(B) A = self._basic_representation(B) A = A.matrix_from_rows_and_columns(range(A.nrows()), order_idx) - if lift_map is None: - if labels: - return (A._matrix_(), order) - else: - return A._matrix_() - else: - if labels: - return (lift_cross_ratios(A._matrix_(), lift_map), order) - else: - return lift_cross_ratios(A._matrix_(), lift_map) + Am = A._matrix_() + if lift_map is not None: + Am = lift_cross_ratios(Am, lift_map) + if column_keys is not None: + Am = matrix(Am, row_keys=range(A.nrows()), column_keys=column_keys) + if labels: + return Am, order + return Am else: if B is None: B = frozenset(self.basis()) @@ -638,16 +682,14 @@ cdef class LinearMatroid(BasisExchangeMatroid): Ci.append(C.index(e)) Cl.append(e) A = A.matrix_from_rows_and_columns(Ri, Ci) - if lift_map is None: - if labels or labels is None: - return (A._matrix_(), Rl, Cl) - else: - return A._matrix_() - else: - if labels or labels is None: - return (lift_cross_ratios(A._matrix_(), lift_map), Rl, Cl) - else: - return lift_cross_ratios(A._matrix_(), lift_map) + Am = A._matrix_() + if lift_map is not None: + Am = lift_cross_ratios(Am, lift_map) + if column_keys is not None: + Am = matrix(Am, row_keys=tuple(Rl), column_keys=tuple(Cl)) + if labels or (labels is None and column_keys is None): + return Am, Rl, Cl + return Am cpdef _current_rows_cols(self, B=None): """ @@ -2882,48 +2924,6 @@ cdef class LinearMatroid(BasisExchangeMatroid): # Copying, loading, saving - def __copy__(self): - """ - Create a shallow copy. - - EXAMPLES:: - - sage: M = Matroid(Matrix(GF(7), [[1, 0, 0, 1, 1], [0, 1, 0, 1, 2], - ....: [0, 0, 1, 1, 3]])) - sage: N = copy(M) # indirect doctest - sage: M == N - True - """ - cdef LinearMatroid N - if self._representation is not None: - N = LinearMatroid(groundset=self._E, matrix=self._representation, keep_initial_representation=True) - else: - rows, cols = self._current_rows_cols() - N = LinearMatroid(groundset=rows + cols, reduced_matrix=self._A) - N.rename(self.get_custom_name()) - return N - - def __deepcopy__(self, memo): - """ - Create a deep copy. - - EXAMPLES:: - - sage: M = Matroid(Matrix(GF(7), [[1, 0, 0, 1, 1], [0, 1, 0, 1, 2], - ....: [0, 0, 1, 1, 3]])) - sage: N = deepcopy(M) # indirect doctest - sage: M == N - True - """ - cdef LinearMatroid N - if self._representation is not None: - N = LinearMatroid(groundset=deepcopy(self._E, memo), matrix=deepcopy(self._representation, memo), keep_initial_representation=True) - else: - rows, cols = self._current_rows_cols() - N = LinearMatroid(groundset=deepcopy(rows + cols, memo), reduced_matrix=deepcopy(self._A, memo)) - N.rename(deepcopy(self.get_custom_name(), memo)) - return N - def __reduce__(self): """ Save the matroid for later reloading. @@ -2973,6 +2973,43 @@ cdef class LinearMatroid(BasisExchangeMatroid): data = (A, gs, reduced, self.get_custom_name()) return sage.matroids.unpickling.unpickle_linear_matroid, (version, data) + cpdef relabel(self, mapping): + r""" + Return an isomorphic matroid with relabeled groundset. + + The output is obtained by relabeling each element ``e`` by + ``mapping[e]``, where ``mapping`` is a given injective map. If + ``mapping[e]`` is not defined, then the identity map is assumed. + + INPUT: + + - ``mapping`` -- a python object such that ``mapping[e]`` is the new + label of ``e`` + + OUTPUT: a matroid + + EXAMPLES:: + + sage: M = matroids.catalog.Fano() + sage: sorted(M.groundset()) + ['a', 'b', 'c', 'd', 'e', 'f', 'g'] + sage: N = M.relabel({'g': 'x'}) + sage: sorted(N.groundset()) + ['a', 'b', 'c', 'd', 'e', 'f', 'x'] + + TESTS:: + + sage: M = matroids.catalog.Fano() + sage: f = {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6, 'g': 7} + sage: N = M.relabel(f) + sage: for S in powerset(M.groundset()): + ....: assert M.rank(S) == N.rank([f[x] for x in S]) + """ + d = self._relabel_map(mapping) + E = [d[x] for x in self.groundset_list()] + M = LinearMatroid(groundset=E, matrix=self._matrix_()) + return M + # Binary matroid cdef class BinaryMatroid(LinearMatroid): @@ -3210,8 +3247,7 @@ cdef class BinaryMatroid(LinearMatroid): sage: repr(M) # indirect doctest 'Binary matroid of rank 3 on 7 elements, type (3, 0)' """ - S = "Binary matroid of rank " + str(self.rank()) + " on " + str(self.size()) + " elements, type (" + str(self.bicycle_dimension()) + ', ' + str(self.brown_invariant()) + ')' - return S + return f'Binary matroid of rank {self.rank()} on {self.size()} elements, type ({self.bicycle_dimension()}, {self.brown_invariant()})' cpdef _current_rows_cols(self, B=None): """ @@ -3931,55 +3967,6 @@ cdef class BinaryMatroid(LinearMatroid): """ return True - def __copy__(self): - """ - Create a shallow copy. - - EXAMPLES:: - - sage: M = Matroid(Matrix(GF(2), [[1, 0, 0, 1, 1], [0, 1, 0, 1, 2], - ....: [0, 0, 1, 1, 3]])) - sage: N = copy(M) # indirect doctest - sage: M == N - True - """ - cdef BinaryMatroid N - cdef list basis - if self._representation is not None: - N = BinaryMatroid(groundset=self._E, matrix=self._representation, keep_initial_representation=True) - else: - basis = [0] * self.full_rank() - for e in self.basis(): - basis[self._prow[self._idx[e]]] = e - N = BinaryMatroid(groundset=self._E, matrix=self._A, basis=basis) - N.rename(self.get_custom_name()) - return N - - def __deepcopy__(self, memo): - """ - Create a deep copy. - - EXAMPLES:: - - sage: M = Matroid(Matrix(GF(2), [[1, 0, 0, 1, 1], [0, 1, 0, 1, 2], - ....: [0, 0, 1, 1, 3]])) - sage: N = deepcopy(M) # indirect doctest - sage: M == N - True - """ - from copy import deepcopy - cdef BinaryMatroid N - cdef list basis - if self._representation is not None: - N = BinaryMatroid(groundset=deepcopy(self._E, memo), matrix=deepcopy(self._representation, memo), keep_initial_representation=True) - else: - basis = [0] * self.full_rank() - for e in self.basis(): - basis[self._prow[self._idx[e]]] = e - N = BinaryMatroid(groundset=deepcopy(self._E, memo), matrix=deepcopy(self._A, memo), basis=deepcopy(basis, memo)) - N.rename(deepcopy(self.get_custom_name(), memo)) - return N - def __reduce__(self): """ Save the matroid for later reloading. @@ -4038,6 +4025,43 @@ cdef class BinaryMatroid(LinearMatroid): data = (A, gs, basis, self.get_custom_name()) return sage.matroids.unpickling.unpickle_binary_matroid, (version, data) + cpdef relabel(self, mapping): + r""" + Return an isomorphic matroid with relabeled groundset. + + The output is obtained by relabeling each element ``e`` by + ``mapping[e]``, where ``mapping`` is a given injective map. If + ``mapping[e]`` is not defined, then the identity map is assumed. + + INPUT: + + - ``mapping`` -- a python object such that ``mapping[e]`` is the new + label of ``e`` + + OUTPUT: a matroid + + EXAMPLES:: + + sage: M = matroids.catalog.Fano() + sage: sorted(M.groundset()) + ['a', 'b', 'c', 'd', 'e', 'f', 'g'] + sage: N = M.relabel({'g': 'x'}) + sage: sorted(N.groundset()) + ['a', 'b', 'c', 'd', 'e', 'f', 'x'] + + TESTS:: + + sage: M = matroids.catalog.Fano() + sage: f = {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6, 'g': 7} + sage: N = M.relabel(f) + sage: for S in powerset(M.groundset()): + ....: assert M.rank(S) == N.rank([f[x] for x in S]) + """ + d = self._relabel_map(mapping) + E = [d[x] for x in self.groundset_list()] + M = BinaryMatroid(groundset=E, matrix=self._matrix_()) + return M + cdef class TernaryMatroid(LinearMatroid): r""" Ternary matroids. @@ -4277,7 +4301,7 @@ cdef class TernaryMatroid(LinearMatroid): sage: repr(M) # indirect doctest 'Ternary matroid of rank 3 on 7 elements, type 0-' """ - S = "Ternary matroid of rank " + str(self.rank()) + " on " + str(self.size()) + " elements, type " + str(self.bicycle_dimension()) + S = f'Ternary matroid of rank {self.rank()} on {self.size()} elements, type {self.bicycle_dimension()}' if self.character() == 1: S = S + '+' else: @@ -4821,55 +4845,6 @@ cdef class TernaryMatroid(LinearMatroid): """ return True - def __copy__(self): - """ - Create a shallow copy. - - EXAMPLES:: - - sage: M = Matroid(Matrix(GF(3), [[1, 0, 0, 1, 1], [0, 1, 0, 1, 2], - ....: [0, 0, 1, 1, 3]])) - sage: N = copy(M) # indirect doctest - sage: M == N - True - """ - cdef TernaryMatroid N - cdef list basis - if self._representation is not None: - N = TernaryMatroid(groundset=self._E, matrix=self._representation, keep_initial_representation=True) - else: - basis = [0] * self.full_rank() - for e in self.basis(): - basis[self._prow[self._idx[e]]] = e - N = TernaryMatroid(groundset=self._E, matrix=self._A, basis=basis) - N.rename(self.get_custom_name()) - return N - - def __deepcopy__(self, memo): - """ - Create a deep copy. - - EXAMPLES:: - - sage: M = Matroid(Matrix(GF(3), [[1, 0, 0, 1, 1], [0, 1, 0, 1, 2], - ....: [0, 0, 1, 1, -1]])) - sage: N = deepcopy(M) # indirect doctest - sage: M == N - True - """ - from copy import deepcopy - cdef TernaryMatroid N - cdef list basis - if self._representation is not None: - N = TernaryMatroid(groundset=deepcopy(self._E, memo), matrix=deepcopy(self._representation, memo), keep_initial_representation=True) - else: - basis = [0] * self.full_rank() - for e in self.basis(): - basis[self._prow[self._idx[e]]] = e - N = TernaryMatroid(groundset=deepcopy(self._E, memo), matrix=deepcopy(self._A, memo), basis=deepcopy(basis, memo)) - N.rename(deepcopy(self.get_custom_name(), memo)) - return N - def __reduce__(self): """ Save the matroid for later reloading. @@ -4932,6 +4907,43 @@ cdef class TernaryMatroid(LinearMatroid): data = (A, gs, basis, self.get_custom_name()) return sage.matroids.unpickling.unpickle_ternary_matroid, (version, data) + cpdef relabel(self, mapping): + r""" + Return an isomorphic matroid with relabeled groundset. + + The output is obtained by relabeling each element ``e`` by + ``mapping[e]``, where ``mapping`` is a given injective map. If + ``mapping[e]`` is not defined, then the identity map is assumed. + + INPUT: + + - ``mapping`` -- a python object such that ``mapping[e]`` is the new + label of ``e`` + + OUTPUT: a matroid + + EXAMPLES:: + + sage: M = matroids.catalog.NonFano() + sage: sorted(M.groundset()) + ['a', 'b', 'c', 'd', 'e', 'f', 'g'] + sage: N = M.relabel({'g': 'x'}) + sage: sorted(N.groundset()) + ['a', 'b', 'c', 'd', 'e', 'f', 'x'] + + TESTS:: + + sage: M = matroids.catalog.NonFano() + sage: f = {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6, 'g': 7} + sage: N = M.relabel(f) + sage: for S in powerset(M.groundset()): + ....: assert M.rank(S) == N.rank([f[x] for x in S]) + """ + d = self._relabel_map(mapping) + E = [d[x] for x in self.groundset_list()] + M = TernaryMatroid(groundset=E, matrix=self._matrix_()) + return M + # Quaternary Matroids cdef class QuaternaryMatroid(LinearMatroid): @@ -5167,8 +5179,7 @@ cdef class QuaternaryMatroid(LinearMatroid): sage: repr(M) # indirect doctest # needs sage.rings.finite_rings 'Quaternary matroid of rank 2 on 3 elements' """ - S = "Quaternary matroid of rank " + str(self.rank()) + " on " + str(self.size()) + " elements" - return S + return f'Quaternary matroid of rank {self.rank()} on {self.size()} elements' cpdef _current_rows_cols(self, B=None): """ @@ -5550,55 +5561,6 @@ cdef class QuaternaryMatroid(LinearMatroid): """ return True - def __copy__(self): - """ - Create a shallow copy. - - EXAMPLES:: - - sage: M = Matroid(Matrix(GF(4, 'x'), [[1, 0, 0, 1, 1], # needs sage.rings.finite_rings - ....: [0, 1, 0, 1, 2], [0, 0, 1, 1, 3]])) - sage: N = copy(M) # indirect doctest # needs sage.rings.finite_rings - sage: M == N # needs sage.rings.finite_rings - True - """ - cdef QuaternaryMatroid N - cdef list basis - if self._representation is not None: - N = QuaternaryMatroid(groundset=self._E, matrix=self._representation, keep_initial_representation=True) - else: - basis = [0] * self.full_rank() - for e in self.basis(): - basis[self._prow[self._idx[e]]] = e - N = QuaternaryMatroid(groundset=self._E, matrix=self._A, basis=basis) - N.rename(self.get_custom_name()) - return N - - def __deepcopy__(self, memo): - """ - Create a deep copy. - - EXAMPLES:: - - sage: M = Matroid(Matrix(GF(4, 'x'), [[1, 0, 0, 1, 1], # needs sage.rings.finite_rings - ....: [0, 1, 0, 1, 2], [0, 0, 1, 1, -1]])) - sage: N = deepcopy(M) # indirect doctest # needs sage.rings.finite_rings - sage: M == N # needs sage.rings.finite_rings - True - """ - from copy import deepcopy - cdef QuaternaryMatroid N - cdef list basis - if self._representation is not None: - N = QuaternaryMatroid(groundset=deepcopy(self._E, memo), matrix=deepcopy(self._representation, memo), keep_initial_representation=True) - else: - basis = [0] * self.full_rank() - for e in self.basis(): - basis[self._prow[self._idx[e]]] = e - N = QuaternaryMatroid(groundset=deepcopy(self._E, memo), matrix=deepcopy(self._A, memo), basis=deepcopy(basis, memo)) - N.rename(deepcopy(self.get_custom_name(), memo)) - return N - def __reduce__(self): """ Save the matroid for later reloading. @@ -5657,6 +5619,43 @@ cdef class QuaternaryMatroid(LinearMatroid): data = (A, gs, basis, self.get_custom_name()) return sage.matroids.unpickling.unpickle_quaternary_matroid, (version, data) + cpdef relabel(self, mapping): + r""" + Return an isomorphic matroid with relabeled groundset. + + The output is obtained by relabeling each element ``e`` by + ``mapping[e]``, where ``mapping`` is a given injective map. If + ``mapping[e]`` is not defined, then the identity map is assumed. + + INPUT: + + - ``mapping`` -- a python object such that ``mapping[e]`` is the new + label of ``e`` + + OUTPUT: a matroid + + EXAMPLES:: + + sage: M = matroids.catalog.RelaxedNonFano("abcdefg") + sage: sorted(M.groundset()) + ['a', 'b', 'c', 'd', 'e', 'f', 'g'] + sage: N = M.relabel({'g':'x'}) + sage: sorted(N.groundset()) + ['a', 'b', 'c', 'd', 'e', 'f', 'x'] + + TESTS:: + + sage: M = matroids.catalog.RelaxedNonFano("abcdefg") + sage: f = {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6, 'g': 7} + sage: N = M.relabel(f) + sage: for S in powerset(M.groundset()): + ....: assert M.rank(S) == N.rank([f[x] for x in S]) + """ + d = self._relabel_map(mapping) + E = [d[x] for x in self.groundset_list()] + M = QuaternaryMatroid(groundset=E, matrix=self._matrix_()) + return M + # Regular Matroids cdef class RegularMatroid(LinearMatroid): @@ -5875,8 +5874,7 @@ cdef class RegularMatroid(LinearMatroid): sage: repr(M) # indirect doctest 'Regular matroid of rank 5 on 10 elements with 162 bases' """ - S = "Regular matroid of rank " + str(self.rank()) + " on " + str(self.size()) + " elements with " + str(self.bases_count()) + " bases" - return S + return f'Regular matroid of rank {self.rank()} on {self.size()} elements with {self.bases_count()} bases' cpdef bases_count(self): """ @@ -6367,6 +6365,20 @@ cdef class RegularMatroid(LinearMatroid): # representation + def is_regular(self): + r""" + Return if ``self`` is regular. + + This is trivially ``True`` for a class:`RegularMatroid`. + + EXAMPLES:: + + sage: M = matroids.catalog.R10() + sage: M.is_regular() + True + """ + return True + cpdef binary_matroid(self, randomized_tests=1, verify = True): r""" Return a binary matroid representing ``self``. @@ -6487,46 +6499,6 @@ cdef class RegularMatroid(LinearMatroid): # Copying, loading, saving - def __copy__(self): - """ - Create a shallow copy. - - EXAMPLES:: - - sage: M = matroids.catalog.R10() - sage: N = copy(M) # indirect doctest - sage: M == N - True - """ - cdef RegularMatroid N - if self._representation is not None: - N = RegularMatroid(groundset=self._E, matrix=self._representation, keep_initial_representation=True) - else: - rows, cols = self._current_rows_cols() - N = RegularMatroid(groundset=rows + cols, reduced_matrix=self._A) - N.rename(self.get_custom_name()) - return N - - def __deepcopy__(self, memo): - """ - Create a deep copy. - - EXAMPLES:: - - sage: M = matroids.catalog.R10() - sage: N = deepcopy(M) # indirect doctest - sage: M == N - True - """ - cdef RegularMatroid N - if self._representation is not None: - N = RegularMatroid(groundset=deepcopy(self._E, memo), matrix=deepcopy(self._representation, memo), keep_initial_representation=True) - else: - rows, cols = self._current_rows_cols() - N = RegularMatroid(groundset=deepcopy(rows + cols, memo), reduced_matrix=deepcopy(self._A, memo)) - N.rename(deepcopy(self.get_custom_name(), memo)) - return N - def __reduce__(self): """ Save the matroid for later reloading. @@ -6575,3 +6547,40 @@ cdef class RegularMatroid(LinearMatroid): reduced = True data = (A, gs, reduced, self.get_custom_name()) return sage.matroids.unpickling.unpickle_regular_matroid, (version, data) + + cpdef relabel(self, mapping): + r""" + Return an isomorphic matroid with relabeled groundset. + + The output is obtained by relabeling each element ``e`` by + ``mapping[e]``, where ``mapping`` is a given injective map. If + ``mapping[e]`` is not defined, then the identity map is assumed. + + INPUT: + + - ``mapping`` -- a python object such that ``mapping[e]`` is the new + label of ``e`` + + OUTPUT: a matroid + + EXAMPLES:: + + sage: M = matroids.catalog.R10() + sage: sorted(M.groundset()) + ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'] + sage: N = M.relabel({'g': 'x'}) + sage: sorted(N.groundset()) + ['a', 'b', 'c', 'd', 'e', 'f', 'h', 'i', 'j', 'x'] + + TESTS:: + + sage: M = matroids.catalog.R10() + sage: f = {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6, 'g': 7} + sage: N = M.relabel(f) + sage: for S in powerset(M.groundset()): + ....: assert M.rank(S) == N.rank([M._relabel_map(f)[x] for x in S]) + """ + d = self._relabel_map(mapping) + E = [d[x] for x in self.groundset_list()] + M = RegularMatroid(groundset=E, matrix=self._matrix_()) + return M diff --git a/src/sage/matroids/matroid.pxd b/src/sage/matroids/matroid.pxd index 5103bcf2a4b..e5ebe0505fa 100644 --- a/src/sage/matroids/matroid.pxd +++ b/src/sage/matroids/matroid.pxd @@ -141,6 +141,7 @@ cdef class Matroid(SageObject): cpdef equals(self, other) cpdef is_isomorphism(self, other, morphism) cpdef _is_isomorphism(self, other, morphism) + cpdef _relabel_map(self, mapping) # minors, dual, truncation cpdef minor(self, contractions=*, deletions=*) @@ -193,6 +194,8 @@ cdef class Matroid(SageObject): cpdef _local_ternary_matroid(self, basis=*) cpdef ternary_matroid(self, randomized_tests=*, verify=*) cpdef is_ternary(self, randomized_tests=*) + cpdef is_regular(self) + cpdef is_graphic(self) # matroid k-closed cpdef is_k_closed(self, int k) diff --git a/src/sage/matroids/matroid.pyx b/src/sage/matroids/matroid.pyx index 781f72592ac..331470622de 100644 --- a/src/sage/matroids/matroid.pyx +++ b/src/sage/matroids/matroid.pyx @@ -116,10 +116,13 @@ additional functionality (e.g. linear extensions). - :meth:`girth() ` - Representation + - :meth:`is_graphic() ` + - :meth:`is_regular() ` - :meth:`binary_matroid() ` - :meth:`is_binary() ` - :meth:`ternary_matroid() ` - :meth:`is_ternary() ` + - :meth:`relabel() ` - Optimization - :meth:`max_weight_independent() ` @@ -517,6 +520,98 @@ cdef class Matroid(SageObject): """ raise NotImplementedError("subclasses need to implement this.") + # copying + + def __copy__(self): + """ + Create a shallow copy. + + EXAMPLES:: + + sage: from sage.matroids.advanced import * + sage: matroids_lst = [ + ....: BasisMatroid(matroids.catalog.Vamos()), + ....: CircuitsMatroid(matroids.catalog.Vamos()), + ....: CircuitClosuresMatroid(matroids.catalog.Vamos()), + ....: Matroid(groundset=range(10), rank_function=lambda X: min(len(X), 4)), + ....: Matroid(Matrix(GF(7), [[1,0,0,1,1],[0,1,0,1,2],[0,0,1,1,3]])), + ....: Matroid(Matrix(GF(2), [[1,0,0,1,1],[0,1,0,1,2],[0,0,1,1,3]])), + ....: Matroid(Matrix(GF(3), [[1,0,0,1,1],[0,1,0,1,2],[0,0,1,1,3]])), + ....: Matroid(Matrix(GF(4, 'x'), [[1,0,0,1,1],[0,1,0,1,2],[0,0,1,1,3]])), + ....: matroids.catalog.R10() + ....: ] + sage: for M in matroids_lst: # indirect doctest + ....: N = copy(M) + ....: assert M == N + ....: assert M.groundset() is N.groundset() + + sage: M = Matroid(graphs.PappusGraph()) + sage: N = copy(M) + sage: M == N + True + sage: M._G is N._G + True + + sage: M = MinorMatroid(matroid=matroids.catalog.Vamos(), + ....: contractions={'a', 'b'}, deletions={'f'}) + sage: N = copy(M) # indirect doctest + sage: M == N + True + sage: M._matroid is N._matroid + True + + sage: from sage.matroids.lean_matrix import * + sage: A = GenericMatrix(2, 5, Matrix(GF(5), [[1, 0, 1, 1, 1], [0, 1, 1, 2, 3]])) + sage: A == copy(A) # indirect doctest + True + """ + return self + + def __deepcopy__(self, memo=None): + """ + Create a deep copy. + + EXAMPLES:: + + sage: from sage.matroids.advanced import * + sage: matroids_lst = [ + ....: BasisMatroid(matroids.catalog.Vamos()), + ....: CircuitsMatroid(matroids.catalog.Vamos()), + ....: CircuitClosuresMatroid(matroids.catalog.Vamos()), + ....: Matroid(groundset=range(10), rank_function=lambda X: min(len(X), 4)), + ....: Matroid(Matrix(GF(7), [[1,0,0,1,1],[0,1,0,1,2],[0,0,1,1,3]])), + ....: Matroid(Matrix(GF(2), [[1,0,0,1,1],[0,1,0,1,2],[0,0,1,1,3]])), + ....: Matroid(Matrix(GF(3), [[1,0,0,1,1],[0,1,0,1,2],[0,0,1,1,3]])), + ....: Matroid(Matrix(GF(4, 'x'), [[1,0,0,1,1],[0,1,0,1,2],[0,0,1,1,3]])), + ....: matroids.catalog.R10() + ....: ] + sage: for M in matroids_lst: # indirect doctest + ....: N = deepcopy(M) + ....: assert M == N + ....: assert M.groundset() is N.groundset() + + sage: M = Matroid(graphs.PappusGraph()) + sage: N = deepcopy(M) + sage: M == N + True + sage: M._G is N._G + True + + sage: M = MinorMatroid(matroid=matroids.catalog.Vamos(), + ....: contractions={'a', 'b'}, deletions={'f'}) + sage: N = deepcopy(M) # indirect doctest + sage: M == N + True + sage: M._matroid is N._matroid + True + + sage: from sage.matroids.lean_matrix import * + sage: A = GenericMatrix(2, 5, Matrix(GF(5), [[1, 0, 1, 1, 1], [0, 1, 1, 2, 3]])) + sage: A == deepcopy(A) # indirect doctest + True + """ + return self + # internal methods, assuming verified input # for better efficiency, its best to override the following methods in @@ -1196,9 +1291,7 @@ cdef class Matroid(SageObject): sage: sage.matroids.matroid.Matroid._repr_(M) 'Matroid of rank 4 on 8 elements' """ - S = "Matroid of rank " - S = S + str(self.rank()) + " on " + str(self.size()) + " elements" - return S + return f'Matroid of rank {self.rank()} on {self.size()} elements' # cpdef show(self): # Show either the graph, or the matrix with labels, or the lattice, @@ -6465,6 +6558,71 @@ cdef class Matroid(SageObject): """ return self.ternary_matroid(randomized_tests=randomized_tests, verify=True) is not None + cpdef is_graphic(self): + r""" + Return if ``self`` is graphic. + + A matroid is graphic if and only if it has no minor isomorphic to any + of the matroids `U_{2, 4}`, `F_7`, `F_7^*`, `M^*(K_5)`, and + `M^*(K_{3, 3})`. + + EXAMPLES:: + + sage: M = matroids.catalog.Wheel4() + sage: M.is_graphic() + True + sage: M = matroids.catalog.U24() + sage: M.is_graphic() + False + + REFERENCES: + + [Oxl2011]_, p. 385. + """ + from sage.matroids.database_matroids import ( + U24, + Fano, + FanoDual, + K5dual, + K33dual + ) + excluded_minors = [U24(), Fano(), FanoDual(), K5dual(), K33dual()] + for M in excluded_minors: + if self.has_minor(M): + return False + return True + + cpdef is_regular(self): + r""" + Return if ``self`` is regular. + + A regular matroid is one that can be represented by a totally + unimodular matrix, the latter being a matrix over `\mathbb{R}` for + which every square submatrix has determinant in `\{0, 1, -1\}`. A + matroid is regular if and only if it is representable over every field. + Alternatively, a matroid is regular if and only if it has no minor + isomorphic to `U_{2, 4}`, `F_7`, or `F_7^*`. + + EXAMPLES:: + + sage: M = matroids.catalog.Wheel4() + sage: M.is_regular() + True + sage: M = matroids.catalog.R9() + sage: M.is_regular() + False + + REFERENCES: + + [Oxl2011]_, p. 373. + """ + if not self.is_binary(): # equivalent to checking for a U24 minor + return False + from sage.matroids.database_matroids import Fano, FanoDual + if self.has_minor(Fano()) or self.has_minor(FanoDual()): + return False + return True + # matroid k-closed cpdef is_k_closed(self, int k): @@ -8389,3 +8547,87 @@ cdef class Matroid(SageObject): # place this matroid at the beginning of the list matroids.insert(0, self) return union_matroid.MatroidSum(iter(matroids)) + + cpdef _relabel_map(self, mapping): + """ + Return a dictionary from the groundset to the relabeled groundset + and check that the mapping defined by ``mapping`` is valid. + + INPUT: + + - ``mapping`` -- a python object such that ``mapping[e]`` is the new + label of ``e``; if ``mapping[e]`` is not defined then the identity + map is assumed + + EXAMPLES:: + + sage: M = matroids.catalog.Vamos([1, 2, 3, 4, 5, 6, 7, 8]) + sage: M._relabel_map({1: 'a', 8: 'h', 9: 'i'}) + {1: 'a', 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 'h'} + sage: M._relabel_map({1: 2}) + Traceback (most recent call last): + ... + ValueError: given map doesn't relabel the groundset properly + """ + E = set() + d = {} + for x in self.groundset(): + try: + E.add(mapping[x]) + d[x] = mapping[x] + except LookupError: + E.add(x) + d[x] = x + if len(E) != len(self.groundset()): + raise ValueError("given map doesn't relabel the groundset properly") + return d + + def relabel(self, mapping): + r""" + Return an isomorphic matroid with relabeled groundset. + + The output is obtained by relabeling each element ``e`` by + ``mapping[e]``, where ``mapping`` is a given injective map. If + ``mapping[e]`` is not defined, then the identity map is assumed. + + INPUT: + + - ``mapping`` -- a python object such that ``mapping[e]`` is the new + label of ``e`` + + OUTPUT: a matroid + + EXAMPLES:: + + sage: from sage.matroids.rank_matroid import RankMatroid + sage: N = matroids.catalog.Sp8pp() + sage: M = RankMatroid(groundset=N.groundset(), rank_function=N.rank) + sage: sorted(M.groundset()) + [1, 2, 3, 4, 5, 6, 7, 8] + sage: N = M.relabel({8: 0}) + sage: sorted(N.groundset()) + [0, 1, 2, 3, 4, 5, 6, 7] + sage: M.is_isomorphic(N) + True + + TESTS:: + + sage: from sage.matroids.rank_matroid import RankMatroid + sage: N = matroids.catalog.Sp8pp() + sage: M = RankMatroid(groundset=N.groundset(), rank_function=N.rank) + sage: f = {1: 'a', 2: 'b', 3: 'c', 4: 'd', 5: 'e', 6: 'f', 7: 'g', 8: 'h'} + sage: N = M.relabel(f) + sage: for S in powerset(M.groundset()): + ....: assert M.rank(S) == N.rank([f[x] for x in S]) + """ + from sage.matroids.rank_matroid import RankMatroid + d = self._relabel_map(mapping) + E = [d[x] for x in self.groundset()] + + def f_relabel(X): + d_inv = {d[x]: x for x in self.groundset()} + X_inv = [d_inv[x] for x in X] + return self.rank(X_inv) + + M = RankMatroid(groundset=E, rank_function=f_relabel) + return M diff --git a/src/sage/matroids/minor_matroid.py b/src/sage/matroids/minor_matroid.py index 6c7fbeb8ef9..721dd653d8d 100644 --- a/src/sage/matroids/minor_matroid.py +++ b/src/sage/matroids/minor_matroid.py @@ -470,50 +470,6 @@ def __ne__(self, other): # Copying, loading, saving: - def __copy__(self): - """ - Create a shallow copy. - - EXAMPLES:: - - sage: from sage.matroids.advanced import * - sage: M = MinorMatroid(matroid=matroids.catalog.Vamos(), - ....: contractions={'a', 'b'}, deletions={'f'}) - sage: N = copy(M) # indirect doctest - sage: M == N - True - sage: M._matroid is N._matroid - True - """ - N = MinorMatroid(self._matroid, self._contractions, self._deletions) - N.rename(self.get_custom_name()) - return N - - def __deepcopy__(self, memo={}): - """ - Create a deep copy. - - .. NOTE:: - - Since matroids are immutable, a shallow copy normally suffices. - - EXAMPLES:: - - sage: from sage.matroids.advanced import * - sage: M = MinorMatroid(matroid=matroids.catalog.Vamos(), - ....: contractions={'a', 'b'}, deletions={'f'}) - sage: N = deepcopy(M) # indirect doctest - sage: M == N - True - sage: M._matroid is N._matroid - False - """ - from copy import deepcopy - # Since matroids are immutable, N cannot reference itself in correct code, so no need to worry about the recursion. - N = MinorMatroid(deepcopy(self._matroid, memo), deepcopy(self._contractions, memo), deepcopy(self._deletions, memo)) - N.rename(deepcopy(self.get_custom_name(), memo)) - return N - def __reduce__(self): r""" Save the matroid for later reloading. diff --git a/src/sage/matroids/rank_matroid.py b/src/sage/matroids/rank_matroid.py index 9dd359c14a9..c2ca7ccb980 100644 --- a/src/sage/matroids/rank_matroid.py +++ b/src/sage/matroids/rank_matroid.py @@ -247,51 +247,6 @@ def __ne__(self, other): # Copying, loading, saving: - def __copy__(self): - """ - Create a shallow copy. - - EXAMPLES:: - - sage: from sage.matroids.advanced import * - sage: M = Matroid(groundset=range(10), - ....: rank_function=lambda X: min(len(X), 4)) - sage: N = copy(M) # indirect doctest - sage: M == N - True - sage: M.groundset() is N.groundset() - True - """ - N = RankMatroid(groundset=[], rank_function=None) - N._groundset = self._groundset - N._rank_function = self._rank_function - N.rename(self.get_custom_name()) - return N - - def __deepcopy__(self, memo={}): - """ - Create a deep copy. - - .. NOTE:: - - Since matroids are immutable, a shallow copy normally suffices. - - EXAMPLES:: - - sage: M = Matroid(groundset=range(10), - ....: rank_function=lambda X: min(len(X), 4)) - sage: N = deepcopy(M) # indirect doctest - sage: M == N - True - sage: M.groundset() is N.groundset() - False - """ - from copy import deepcopy - # Since matroids are immutable, N cannot reference itself in correct code, so no need to worry about the recursion. - N = RankMatroid(groundset=deepcopy(self._groundset), rank_function=deepcopy(self._rank_function)) - N.rename(deepcopy(self.get_custom_name(), memo)) - return N - def __reduce__(self): """ Save the matroid for later reloading. diff --git a/src/sage/matroids/set_system.pxd b/src/sage/matroids/set_system.pxd index a283244b53b..3b3a4902a22 100644 --- a/src/sage/matroids/set_system.pxd +++ b/src/sage/matroids/set_system.pxd @@ -9,7 +9,7 @@ cdef class SetSystem: cdef bitset_t _temp cdef copy(self) - cdef _relabel(self, l) + cdef _relabel(self, mapping) cpdef _complements(self) cdef resize(self, k=*) diff --git a/src/sage/matroids/set_system.pyx b/src/sage/matroids/set_system.pyx index 606231a1734..1fc0ed4546a 100644 --- a/src/sage/matroids/set_system.pyx +++ b/src/sage/matroids/set_system.pyx @@ -40,7 +40,7 @@ cdef class SetSystem: sage: M = matroids.catalog.Fano() sage: M.circuits() - Iterator over a system of subsets + SetSystem of 14 sets over 7 elements To access the sets in this structure, simply iterate over them. The simplest way must be:: @@ -75,7 +75,7 @@ cdef class SetSystem: sage: from sage.matroids.set_system import SetSystem sage: S = SetSystem([1, 2, 3, 4], [[1, 2], [3, 4], [1, 2, 4]]) sage: S - Iterator over a system of subsets + SetSystem of 3 sets over 4 elements """ cdef long i if not isinstance(groundset, tuple): @@ -110,7 +110,7 @@ cdef class SetSystem: sage: from sage.matroids.set_system import SetSystem sage: S = SetSystem([1, 2, 3, 4], [[1, 2], [3, 4], [1, 2, 4]]) sage: S - Iterator over a system of subsets + SetSystem of 3 sets over 4 elements sage: sorted(S[1]) [3, 4] sage: for s in S: print(sorted(s)) @@ -138,7 +138,7 @@ cdef class SetSystem: sage: from sage.matroids.set_system import SetSystem sage: S = SetSystem([1, 2, 3, 4], [[1, 2], [3, 4], [1, 2, 4]]) sage: S - Iterator over a system of subsets + SetSystem of 3 sets over 4 elements sage: len(S) 3 """ @@ -196,9 +196,9 @@ cdef class SetSystem: sage: from sage.matroids.set_system import SetSystem sage: S = SetSystem([1, 2, 3, 4], [[1, 2], [3, 4], [1, 2, 4]]) sage: repr(S) # indirect doctest - 'Iterator over a system of subsets' + 'SetSystem of 3 sets over 4 elements' """ - return "Iterator over a system of subsets" + return f'SetSystem of {self._len} sets over {self._groundset_size} elements' cdef copy(self): cdef SetSystem S @@ -207,24 +207,23 @@ cdef class SetSystem: S._append(self._subsets[i]) return S - cdef _relabel(self, l): + cdef _relabel(self, mapping): """ - Relabel each element `e` of the ground set as `l(e)`, where `l` is a - given injective map. + Relabel each element ``e`` of the ground set as ``mapping[e]``, where + ``mapping`` is a given injective map. INPUT: - - ``l`` -- a python object such that `l[e]` is the new label of e. + - ``mapping`` -- a python object such that ``mapping[e]`` is the new + label of ``e`` - OUTPUT: - - ``None``. + OUTPUT: ``None`` """ cdef long i E = [] for i in range(self._groundset_size): - if self._groundset[i] in l: - E.append(l[self._E[i]]) + if self._groundset[i] in mapping: + E.append(mapping[self._E[i]]) else: E.append(self._E[i]) self._groundset = E diff --git a/src/sage/matroids/union_matroid.pyx b/src/sage/matroids/union_matroid.pyx index 817f9c4a222..12e9d1e7718 100644 --- a/src/sage/matroids/union_matroid.pyx +++ b/src/sage/matroids/union_matroid.pyx @@ -21,7 +21,7 @@ cdef class MatroidUnion(Matroid): {} Matroid of rank 1 on 2 elements with 2 bases sage: M.bases() - Iterator over a system of subsets + SetSystem of 2 sets over 5 elements sage: list(M.circuits()) [frozenset({3, 4})] diff --git a/src/sage/matroids/utilities.py b/src/sage/matroids/utilities.py index a0cf14f5276..d5e680fc6b9 100644 --- a/src/sage/matroids/utilities.py +++ b/src/sage/matroids/utilities.py @@ -70,7 +70,7 @@ def setprint(X): sage: from sage.matroids.advanced import setprint sage: M = matroids.catalog.Fano().delete('efg') sage: M.bases() - Iterator over a system of subsets + SetSystem of 3 sets over 4 elements sage: setprint(M.bases()) [{'a', 'b', 'c'}, {'a', 'b', 'd'}, {'a', 'c', 'd'}] diff --git a/src/sage/misc/all.py b/src/sage/misc/all.py index 8aee092368c..585db5a05eb 100644 --- a/src/sage/misc/all.py +++ b/src/sage/misc/all.py @@ -131,11 +131,9 @@ lazy_import('sage.misc.dev_tools', 'runsnake', deprecation=34259) lazy_import('sage.misc.edit_module', 'set_edit_template', deprecation=34259) lazy_import('sage.misc.profiler', 'Profiler', deprecation=34259) -lazy_import('sage.misc.dist', 'install_scripts', deprecation=34259) lazy_import('sage.misc.trace', 'trace', deprecation=34259) lazy_import('sage.misc.package', ('installed_packages', 'is_package_installed', - 'standard_packages', 'optional_packages', - 'experimental_packages', 'package_versions'), + 'package_versions'), deprecation=34259) lazy_import('sage.misc.benchmark', 'benchmark', deprecation=34259) lazy_import('sage.repl.interpreter', 'logstr', deprecation=34259) diff --git a/src/sage/misc/dist.py b/src/sage/misc/dist.py deleted file mode 100644 index b7c59ee77f3..00000000000 --- a/src/sage/misc/dist.py +++ /dev/null @@ -1,167 +0,0 @@ -""" -Installing shortcut scripts -""" - -import os - -from sage.misc.superseded import deprecation - -def install_scripts(directory=None, ignore_existing=False): - r""" - This function has been deprecated. - - Running ``install_scripts(directory)`` creates scripts in the - given directory that run various software components included with - Sage. Each of these scripts essentially just runs ``sage --CMD`` - where ``CMD`` is also the name of the script: - - - 'gap' runs GAP - - 'gp' runs the PARI/GP interpreter - - 'ipython' runs IPython - - 'maxima' runs Maxima - - 'mwrank' runs mwrank - - 'R' runs R - - 'singular' runs Singular - - 'sqlite3' runs SQLite version 3 - - This command: - - - verbosely tells you which scripts it adds, and - - - will *not* overwrite any scripts you already have in the given - directory. - - INPUT: - - - ``directory`` - string; the directory into which to put the - scripts. This directory must exist and the user must have write - and execute permissions. - - - ``ignore_existing`` - bool (optional, default False): if True, - install script even if another version of the program is in your - path. - - OUTPUT: Verbosely prints what it is doing and creates files in - ``directory`` that are world executable and readable. - - .. note:: - - You may need to run ``sage`` as ``root`` in order to run - ``install_scripts`` successfully, since the user running - ``sage`` needs write permissions on ``directory``. Note - that one good candidate for ``directory`` is - ``'/usr/local/bin'``, so from the shell prompt, you could run :: - - sudo sage -c "install_scripts('/usr/local/bin')" - - .. note:: - - Running ``install_scripts(directory)`` will be most helpful if - ``directory`` is in your path. - - AUTHORS: - - - William Stein: code / design - - - Arthur Gaer: design - - - John Palmieri: revision, 2011-07 (:issue:`11602`) - - EXAMPLES:: - - sage: import tempfile - sage: from sage.misc.dist import install_scripts - sage: with tempfile.TemporaryDirectory() as d: - ....: install_scripts(d, ignore_existing=True) - doctest:warning... - the function install_scripts has been deprecated and will be removed in a future version of Sage - See https://github.com/sagemath/sage/issues/30207 for details. - Checking that Sage has the command 'gap' installed - ... - """ - deprecation(30207, 'the function install_scripts has been deprecated and ' - 'will be removed in a future version of Sage') - - if directory is None: - # We do this since the intended user of install_scripts - # will likely be pretty clueless about how to use Sage or - # its help system. - from . import sagedoc - print(sagedoc.format(install_scripts.__doc__)) - print("USAGE: install_scripts('directory')") - return - - if not os.path.exists(directory): - print(f"Error: '{directory}' does not exist.") - return - - if not os.path.isdir(directory): - print(f"Error: '{directory}' is not a directory.") - return - - if not (os.access(directory, os.W_OK) and os.access(directory, os.X_OK)): - print(f"Error: you do not have write permission for '{directory}'.") - return - - from sage.misc.sage_ostools import have_program - from sage.env import SAGE_LOCAL - - if not SAGE_LOCAL: - print(f"Error: This installation of Sage does not use SAGE_LOCAL, so install_scripts makes no sense.") - return - - script_created = False - SAGE_BIN = os.path.join(SAGE_LOCAL, 'bin') - # See if 'directory' is already in PATH, and then remove - # SAGE_LOCAL/bin from PATH so that we can later check whether - # cmd is available outside of Sage. - PATH = os.environ['PATH'].split(os.pathsep) - PATH = [d for d in PATH if os.path.exists(d)] - dir_in_path = any(os.path.samefile(directory, d) for d in PATH) - PATH = os.pathsep.join(d for d in PATH - if not os.path.samefile(d, SAGE_BIN)) - for cmd in ['gap', 'gp', 'ipython', 'maxima', - 'mwrank', 'R', 'singular', 'sqlite3']: - print(f"Checking that Sage has the command '{cmd}' installed") - # Check to see if Sage includes cmd. - cmd_inside_sage = have_program(cmd, path=SAGE_BIN) - if not cmd_inside_sage: - print(f"The command '{cmd}' is not available as part of Sage; " + - "not creating script.") - print() - continue - cmd_outside_sage = have_program(cmd, path=PATH) - if cmd_outside_sage: - print(f"The command '{cmd}' is installed outside of Sage;", end=' ') - if not ignore_existing: - print("not creating script.") - print() - continue - print("trying to create script anyway...") - else: - print(f"Creating script for '{cmd}'...") - # Install shortcut. - target = os.path.join(directory, cmd) - if os.path.exists(target): - print(f"The file '{target}' already exists; not adding script.") - else: - with open(target, 'w') as o: - o.write('#!/bin/sh\n') - o.write('exec sage --%s "$@"\n' % cmd) - print(f"Created script '{target}'") - os.system(f'chmod a+rx {target}') - script_created = True - print() - - if script_created: - print("Finished creating scripts.") - print() - print("You need not do this again even if you upgrade or move Sage.") - print("The only requirement is that your PATH contains both") - print("'{}' and the directory containing the command 'sage'.".format(directory)) - if not dir_in_path: - print() - print("Warning: '{}' is not currently in your PATH.".format(directory)) - print() - else: - print("No scripts created.") diff --git a/src/sage/misc/misc.py b/src/sage/misc/misc.py index 39a6ecffd14..a1f19a37a86 100644 --- a/src/sage/misc/misc.py +++ b/src/sage/misc/misc.py @@ -52,16 +52,6 @@ lazy_import("sage.combinat.subset", ["powerset", "subsets", "uniq"], deprecation=35564) -lazy_import("sage.misc.call", ["AttrCallObject", "attrcall", "call_method"], - deprecation=29869) - -lazy_import("sage.misc.verbose", ["verbose", "set_verbose", "set_verbose_files", - "get_verbose_files", "unset_verbose_files", "get_verbose"], - deprecation=17815) - -lazy_import("sage.misc.repr", ["coeff_repr", "repr_lincomb"], - deprecation=29892) - lazy_import("sage.misc.timing", ["cputime", "GlobalCputime", "walltime"], deprecation=35816) @@ -71,44 +61,6 @@ # File and directory utilities ################################################################# - -def sage_makedirs(dirname, mode=0o777): - """ - Python version of ``mkdir -p``: try to create a directory, and also - create all intermediate directories as necessary. Succeed silently - if the directory already exists (unlike ``os.makedirs()``). - Raise other errors (like permission errors) normally. - - This function is deprecated; use ``os.makedirs(..., exist_ok=True)`` - instead. - - EXAMPLES:: - - sage: from sage.misc.misc import sage_makedirs - sage: sage_makedirs(DOT_SAGE) # no output - doctest:warning... - DeprecationWarning: sage_makedirs is deprecated; use os.makedirs(..., exist_ok=True) instead - See https://github.com/sagemath/sage/issues/32987 for details. - - The following fails because we are trying to create a directory in - place of an ordinary file:: - - sage: filename = tmp_filename() - sage: sage_makedirs(filename) - Traceback (most recent call last): - ... - FileExistsError: [Errno ...] File exists: ... - """ - from sage.misc.superseded import deprecation - deprecation(32987, - 'sage_makedirs is deprecated; use os.makedirs(..., exist_ok=True) instead') - try: - os.makedirs(dirname) - except OSError: - if not os.path.isdir(dirname): - raise - - # We create the DOT_SAGE directory (if it does not exist yet; note in particular # that it may already have been created by the bin/sage script) with # restrictive permissions, since otherwise possibly just anybody can easily see @@ -216,79 +168,6 @@ def try_read(obj, splitlines=False): return data -################################################# -# Next we create the Sage temporary directory. -################################################# - - -@lazy_string -def SAGE_TMP(): - """ - EXAMPLES:: - - sage: from sage.misc.misc import SAGE_TMP - sage: SAGE_TMP - doctest:warning... - DeprecationWarning: SAGE_TMP is deprecated; please use python's - "tempfile" module instead. - See https://github.com/sagemath/sage/issues/33213 for details. - l'.../temp/...' - - """ - from sage.misc.superseded import deprecation - deprecation(33213, "SAGE_TMP is deprecated; please use python's \"tempfile\" module instead.") - d = os.path.join(DOT_SAGE, 'temp', HOSTNAME, str(os.getpid())) - os.makedirs(d, exist_ok=True) - return d - - -@lazy_string -def ECL_TMP(): - """ - Temporary directory that should be used by ECL interfaces launched from - Sage. - - EXAMPLES:: - - sage: from sage.misc.misc import ECL_TMP - sage: ECL_TMP - doctest:warning... - DeprecationWarning: ECL_TMP is deprecated and is no longer used - by the ECL interface in sage - See https://github.com/sagemath/sage/issues/33213 for details. - ... - - """ - from sage.misc.superseded import deprecation - deprecation(33213, "ECL_TMP is deprecated and is no longer used by the ECL interface in sage") - import atexit - import tempfile - d = tempfile.TemporaryDirectory() - result = os.path.join(d.name, 'ecl') - atexit.register(lambda: d.cleanup()) - return result - - -@lazy_string -def SPYX_TMP(): - r""" - EXAMPLES:: - - sage: from sage.misc.misc import SPYX_TMP - sage: SPYX_TMP - doctest:warning... - DeprecationWarning: SPYX_TMP is deprecated; - use sage.misc.temporary_file.spyx_tmp instead - See https://github.com/sagemath/sage/issues/33213 for details. - ... - - """ - from sage.misc.temporary_file import spyx_tmp - from sage.misc.superseded import deprecation - deprecation(33213, "SPYX_TMP is deprecated; use sage.misc.temporary_file.spyx_tmp instead") - return spyx_tmp() - - SAGE_DB = os.path.join(DOT_SAGE, 'db') os.makedirs(SAGE_DB, exist_ok=True) @@ -299,41 +178,6 @@ def SPYX_TMP(): pass -def union(x, y=None): - """ - Return the union of x and y, as a list. The resulting list need not - be sorted and can change from call to call. - - INPUT: - - - - ``x`` - iterable - - - ``y`` - iterable (may optionally omitted) - - - OUTPUT: list - - EXAMPLES:: - - sage: answer = union([1,2,3,4], [5,6]); answer - doctest:...: DeprecationWarning: sage.misc.misc.union is deprecated... - See https://github.com/sagemath/sage/issues/32096 for details. - [1, 2, 3, 4, 5, 6] - sage: union([1,2,3,4,5,6], [5,6]) == answer - True - sage: union((1,2,3,4,5,6), [5,6]) == answer - True - sage: union((1,2,3,4,5,6), set([5,6])) == answer - 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") - if y is None: - return list(set(x)) - return list(set(x).union(y)) - - def exactly_one_is_true(iterable): r""" Return whether exactly one element of ``iterable`` evaluates ``True``. diff --git a/src/sage/misc/package.py b/src/sage/misc/package.py index cc7ac5fdfb2..a97ad023590 100644 --- a/src/sage/misc/package.py +++ b/src/sage/misc/package.py @@ -178,16 +178,16 @@ def pip_installed_packages(normalization=None): sage: # optional - sage_spkg sage: from sage.misc.package import pip_installed_packages sage: d = pip_installed_packages() - sage: 'scipy' in d or 'SciPy' in d + sage: 'scipy' in d or 'SciPy' in d # needs scipy + True + sage: 'beautifulsoup4' in d # needs beautifulsoup4 + True + sage: 'prompt-toolkit' in d or 'prompt_toolkit' in d # whether - or _ appears in the name depends on the setuptools version used for building the package True - sage: d['beautifulsoup4'] # optional - beautifulsoup4 - '...' - sage: d['prompt-toolkit'] - '...' sage: d = pip_installed_packages(normalization='spkg') sage: d['prompt_toolkit'] '...' - sage: d['scipy'] + sage: d['scipy'] # needs scipy '...' """ with open(os.devnull, 'w') as devnull: @@ -228,34 +228,6 @@ def is_installed(self) -> bool: """ return self.installed_version is not None - def __getitem__(self, key: Union[int, str]): - r""" - Only for backwards compatibility to allow dict-like access. - - TESTS:: - - sage: from sage.misc.package import PackageInfo - sage: package = PackageInfo("test_package") - sage: package["name"] - doctest:warning... - dict-like access is deprecated, use pkg.name instead of pkg['name'], for example - See https://github.com/sagemath/sage/issues/31013 for details. - 'test_package' - sage: package[0] - 'test_package' - """ - if isinstance(key, str): - from sage.misc.superseded import deprecation - - if key == "installed": - deprecation(31013, "dict-like access via 'installed' is deprecated, use method is_installed instead") - return self.is_installed() - else: - deprecation(31013, "dict-like access is deprecated, use pkg.name instead of pkg['name'], for example") - return self.__getattribute__(key) - else: - return tuple.__getitem__(self, key) - def list_packages(*pkg_types: str, pkg_sources: List[str] = ['normal', 'pip', 'script'], local: bool = False, ignore_URLError: bool = False, exclude_pip: bool = False) -> Dict[str, PackageInfo]: @@ -547,109 +519,6 @@ def package_versions(package_type, local=False): return {pkg.name: (pkg.installed_version, pkg.remote_version) for pkg in list_packages(package_type, local=local).values()} -def standard_packages(): - """ - Return two lists. The first contains the installed and the second - contains the not-installed standard packages that are available - from the Sage repository. - - OUTPUT: - - - installed standard packages (as a list) - - - NOT installed standard packages (as a list) - - Run ``sage -i package_name`` from a shell to install a given - package or ``sage -f package_name`` to re-install it. - - .. SEEALSO:: :func:`sage.misc.package.list_packages` - - EXAMPLES:: - - sage: from sage.misc.package import standard_packages - sage: installed, not_installed = standard_packages() # optional - sage_spkg - doctest:...: DeprecationWarning: ... - sage: 'numpy' in installed # optional - sage_spkg - True - """ - from sage.misc.superseded import deprecation - deprecation(30747, - 'the functions standard_packages, optional_packages, experimental_packages ' - 'are deprecated, use sage.features instead') - pkgs = list_packages('standard', local=True).values() - return (sorted(pkg.name for pkg in pkgs if pkg.is_installed()), - sorted(pkg.name for pkg in pkgs if not pkg.is_installed())) - - -def optional_packages(): - """ - Return two lists. The first contains the installed and the second - contains the not-installed optional packages that are available - from the Sage repository. - - OUTPUT: - - - installed optional packages (as a list) - - - NOT installed optional packages (as a list) - - Run ``sage -i package_name`` from a shell to install a given - package or ``sage -f package_name`` to re-install it. - - .. SEEALSO:: :func:`sage.misc.package.list_packages` - - EXAMPLES:: - - sage: # optional - sage_spkg - sage: from sage.misc.package import optional_packages - sage: installed, not_installed = optional_packages() - doctest:...: DeprecationWarning: ... - sage: 'biopython' in installed + not_installed - True - sage: 'biopython' in installed # optional - biopython - True - """ - from sage.misc.superseded import deprecation - deprecation(30747, - 'the functions standard_packages, optional_packages, experimental_packages ' - 'are deprecated, use sage.features instead') - pkgs = list_packages('optional', local=True) - pkgs = pkgs.values() - return (sorted(pkg.name for pkg in pkgs if pkg.is_installed()), - sorted(pkg.name for pkg in pkgs if not pkg.is_installed())) - - -def experimental_packages(): - """ - Return two lists. The first contains the installed and the second - contains the not-installed experimental packages that are available - from the Sage repository. - - OUTPUT: - - - installed experimental packages (as a list) - - - NOT installed experimental packages (as a list) - - Run ``sage -i package_name`` from a shell to install a given - package or ``sage -f package_name`` to re-install it. - - .. SEEALSO:: :func:`sage.misc.package.list_packages` - - EXAMPLES:: - - sage: from sage.misc.package import experimental_packages - sage: installed, not_installed = experimental_packages() # optional - sage_spkg - doctest:...: DeprecationWarning: ... - """ - from sage.misc.superseded import deprecation - deprecation(30747, - 'the functions standard_packages, optional_packages, experimental_packages ' - 'are deprecated, use sage.features instead') - pkgs = list_packages('experimental', local=True).values() - return (sorted(pkg.name for pkg in pkgs if pkg.is_installed()), - sorted(pkg.name for pkg in pkgs if not pkg.is_installed())) - def package_manifest(package): """ Return the manifest for ``package``. @@ -691,68 +560,10 @@ def package_manifest(package): pass raise RuntimeError('package manifest directory changed at runtime') -class PackageNotFoundError(RuntimeError): - """ - This class defines the exception that should be raised when a - function, method, or class cannot detect a Sage package that it - depends on. - This exception should be raised with a single argument, namely - the name of the package. - - When a ``PackageNotFoundError`` is raised, this means one of the - following: - - - The required optional package is not installed. - - - The required optional package is installed, but the relevant - interface to that package is unable to detect the package. - - Raising a ``PackageNotFoundError`` is deprecated. Use - :class:`sage.features.FeatureNotPresentError` instead. - - User code can continue to catch ``PackageNotFoundError`` exceptions - for compatibility with older versions of the Sage library. - This does not cause deprecation warnings. - - EXAMPLES:: - - sage: from sage.misc.package import PackageNotFoundError - sage: try: - ....: pass - ....: except PackageNotFoundError: - ....: pass - - """ - - def __init__(self, *args): - """ - TESTS:: - - sage: from sage.misc.package import PackageNotFoundError - sage: raise PackageNotFoundError("my_package") - Traceback (most recent call last): - ... - PackageNotFoundError: the package 'my_package' was not found. You can install it by running 'sage -i my_package' in a shell - """ - super().__init__(*args) - # We do not deprecate the whole class because we want - # to allow user code to handle this exception without causing - # a deprecation warning. - from sage.misc.superseded import deprecation - deprecation(30607, "Instead of raising PackageNotFoundError, raise sage.features.FeatureNotPresentError") - - def __str__(self): - """ - Return the actual error message. - - EXAMPLES:: - - sage: from sage.misc.package import PackageNotFoundError - sage: str(PackageNotFoundError("my_package")) - doctest:warning... - "the package 'my_package' was not found. You can install it by running 'sage -i my_package' in a shell" - """ - return ("the package {0!r} was not found. " - "You can install it by running 'sage -i {0}' in a shell" - .format(self.args[0])) +# PackageNotFoundError used to be an exception class. +# It was deprecated in #30607 and removed afterwards. +# User code can continue to use PackageNotFoundError in +# try...except statements using this definition, which +# catches no exception. +PackageNotFoundError = () diff --git a/src/sage/misc/package_dir.py b/src/sage/misc/package_dir.py index 16c78d18462..adfb62331fb 100644 --- a/src/sage/misc/package_dir.py +++ b/src/sage/misc/package_dir.py @@ -111,6 +111,7 @@ def read_distribution(src_file): EXAMPLES:: + sage: # needs SAGE_SRC sage: from sage.env import SAGE_SRC sage: from sage.misc.package_dir import read_distribution sage: read_distribution(os.path.join(SAGE_SRC, 'sage', 'graphs', 'graph_decompositions', 'tdlib.pyx')) diff --git a/src/sage/misc/replace_dot_all.py b/src/sage/misc/replace_dot_all.py index 117b7f6892b..aa1b1ff593d 100644 --- a/src/sage/misc/replace_dot_all.py +++ b/src/sage/misc/replace_dot_all.py @@ -111,8 +111,9 @@ def find_replacements(location, package_regex=None, verbose=False): EXAMPLES:: + sage: # needs SAGE_SRC sage: from sage.misc.replace_dot_all import * - sage: location = os.path.join(sage.env.SAGE_SRC, 'sage/plot/arc.py') + sage: location = os.path.join(sage.env.SAGE_SRC, 'sage', 'plot', 'arc.py') sage: find_replacements(location, package_regex='sage[.]plot[.]all', verbose=True) [[..., ..., 'from sage.plot.graphics import Graphics']] """ @@ -295,8 +296,9 @@ def process_line(location, line, replacements, row_index, verbose=False): Replacing the first line which needs a replacement in the source file with filepath ``src/sage/plot/arc.py``:: + sage: # needs SAGE_SRC sage: from sage.misc.replace_dot_all import * - sage: location = os.path.join(sage.env.SAGE_SRC, 'sage/plot/arc.py') + sage: location = os.path.join(sage.env.SAGE_SRC, 'sage', 'plot', 'arc.py') sage: replacements = find_replacements(location, package_regex='sage[.]plot[.]all', verbose=True); replacements [[477, 24, 'from sage.plot.graphics import Graphics']] sage: with open(location, "r") as file: @@ -401,6 +403,7 @@ def walkdir_replace_dot_all(dir, file_regex=r'.*[.](py|pyx|pxi)$', package_regex EXAMPLES:: + sage: # needs SAGE_SRC sage: from sage.misc.replace_dot_all import * sage: walkdir_replace_dot_all(os.path.join(sage.env.SAGE_SRC, 'sage')) # not tested """ diff --git a/src/sage/modular/abvar/morphism.py b/src/sage/modular/abvar/morphism.py index cc7e5a22ab2..d0eae78b6ed 100644 --- a/src/sage/modular/abvar/morphism.py +++ b/src/sage/modular/abvar/morphism.py @@ -869,6 +869,20 @@ def charpoly(self, var='x'): """ return self.characteristic_polynomial(var) + def fcp(self, var='x'): + """ + Return the factorization of the characteristic polynomial. + + EXAMPLES:: + + sage: t2 = J0(33).hecke_operator(2) + sage: t2.charpoly() + x^3 + 3*x^2 - 4 + sage: t2.fcp() + (x - 1) * (x + 2)^2 + """ + return self.charpoly(var).factor() + def action_on_homology(self, R=ZZ): r""" Return the action of this Hecke operator on the homology diff --git a/src/sage/modular/arithgroup/arithgroup_element.pyx b/src/sage/modular/arithgroup/arithgroup_element.pyx index 1b3db34a04c..9ab2a274e2b 100644 --- a/src/sage/modular/arithgroup/arithgroup_element.pyx +++ b/src/sage/modular/arithgroup/arithgroup_element.pyx @@ -39,12 +39,11 @@ cdef class ArithmeticSubgroupElement(MultiplicativeGroupElement): - ``parent`` -- an arithmetic subgroup - - `x` -- data defining a 2x2 matrix over ZZ - which lives in parent + - ``x`` -- data defining a 2x2 matrix over ZZ + which lives in ``parent`` - ``check`` -- if ``True``, check that parent is an arithmetic - subgroup, and that `x` defines a matrix of - determinant `1`. + subgroup, and that `x` defines a matrix of determinant `1`. We tend not to create elements of arithmetic subgroups that are not SL2Z, in order to avoid coercion issues (that is, the other arithmetic diff --git a/src/sage/modular/arithgroup/farey_symbol.pyx b/src/sage/modular/arithgroup/farey_symbol.pyx index 61c2446a16b..c9cc8059c65 100644 --- a/src/sage/modular/arithgroup/farey_symbol.pyx +++ b/src/sage/modular/arithgroup/farey_symbol.pyx @@ -897,7 +897,6 @@ cdef class Farey: sig_off() return result - @rename_keyword(rgbcolor='color') @options(alpha=1, fill=True, thickness=1, color='lightgray', color_even='white', zorder=2, linestyle='solid', show_pairing=True, diff --git a/src/sage/modular/cusps_nf.py b/src/sage/modular/cusps_nf.py index 8d8b580a74e..d2a7eb8942e 100644 --- a/src/sage/modular/cusps_nf.py +++ b/src/sage/modular/cusps_nf.py @@ -944,7 +944,7 @@ def is_Gamma0_equivalent(self, other, N, Transformation=False): :: - sage: k. = NumberField(x^2+23) + sage: k. = NumberField(x^2 + 23) sage: N = k.ideal(3) sage: alpha1 = NFCusp(k, a+1, 4) sage: alpha2 = NFCusp(k, a-8, 29) @@ -1188,7 +1188,7 @@ def NFCusps_ideal_reps_for_levelN(N, nlists=1): :: - sage: k. = NumberField(x^4 - x^3 -21*x^2 + 17*x + 133) + sage: k. = NumberField(x^4 - x^3 - 21*x^2 + 17*x + 133) sage: N = k.ideal(6) sage: from sage.modular.cusps_nf import NFCusps_ideal_reps_for_levelN sage: NFCusps_ideal_reps_for_levelN(N) diff --git a/src/sage/modular/dirichlet.py b/src/sage/modular/dirichlet.py index 7b0c28c5c32..3f871dadf91 100644 --- a/src/sage/modular/dirichlet.py +++ b/src/sage/modular/dirichlet.py @@ -16,14 +16,15 @@ sage: G = DirichletGroup(35) sage: x = G.gens() sage: e = x[0]*x[1]^2; e - Dirichlet character modulo 35 of conductor 35 mapping 22 |--> zeta12^3, 31 |--> zeta12^2 - 1 + Dirichlet character modulo 35 of conductor 35 + mapping 22 |--> zeta12^3, 31 |--> zeta12^2 - 1 sage: e.order() 12 This illustrates a canonical coercion:: sage: e = DirichletGroup(5, QQ).0 - sage: f = DirichletGroup(5,CyclotomicField(4)).0 + sage: f = DirichletGroup(5, CyclotomicField(4)).0 sage: e*f Dirichlet character modulo 5 of conductor 5 mapping 2 |--> -zeta4 @@ -118,7 +119,8 @@ def kronecker_character(d): EXAMPLES:: sage: kronecker_character(97*389*997^2) - Dirichlet character modulo 37733 of conductor 37733 mapping 1557 |--> -1, 37346 |--> -1 + Dirichlet character modulo 37733 of conductor 37733 + mapping 1557 |--> -1, 37346 |--> -1 :: @@ -148,7 +150,8 @@ def kronecker_character_upside_down(d): EXAMPLES:: sage: kronecker_character_upside_down(97*389*997^2) - Dirichlet character modulo 37506941597 of conductor 37733 mapping 13533432536 |--> -1, 22369178537 |--> -1, 14266017175 |--> 1 + Dirichlet character modulo 37506941597 of conductor 37733 + mapping 13533432536 |--> -1, 22369178537 |--> -1, 14266017175 |--> 1 AUTHORS: @@ -213,7 +216,8 @@ def __init__(self, parent, x, check=True): sage: G. = DirichletGroup(13) sage: G - Group of Dirichlet characters modulo 13 with values in Cyclotomic Field of order 12 and degree 4 + Group of Dirichlet characters modulo 13 with values + in Cyclotomic Field of order 12 and degree 4 sage: e Dirichlet character modulo 13 of conductor 13 mapping 2 |--> zeta12 sage: loads(e.dumps()) == e @@ -223,7 +227,8 @@ def __init__(self, parent, x, check=True): sage: G, x = DirichletGroup(35).objgens() sage: e = x[0]*x[1]; e - Dirichlet character modulo 35 of conductor 35 mapping 22 |--> zeta12^3, 31 |--> zeta12^2 + Dirichlet character modulo 35 of conductor 35 + mapping 22 |--> zeta12^3, 31 |--> zeta12^2 sage: e.order() 12 sage: loads(e.dumps()) == e @@ -242,14 +247,16 @@ def __init__(self, parent, x, check=True): sage: G([i, -1, -1]) Traceback (most recent call last): ... - ValueError: values (= (zeta16^4, -1, -1)) must have multiplicative orders dividing (2, 16, 2), respectively + ValueError: values (= (zeta16^4, -1, -1)) must have + multiplicative orders dividing (2, 16, 2), respectively sage: from sage.modular.dirichlet import DirichletCharacter sage: M = FreeModule(Zmod(16), 3) sage: DirichletCharacter(G, M([4, 8, 8])) Traceback (most recent call last): ... - ValueError: values (= (4, 8, 8) modulo 16) must have additive orders dividing (2, 16, 2), respectively + ValueError: values (= (4, 8, 8) modulo 16) must have + additive orders dividing (2, 16, 2), respectively """ MultiplicativeGroupElement.__init__(self, parent) if check: @@ -360,7 +367,8 @@ def change_ring(self, R): sage: e = DirichletGroup(7, QQ).0 sage: f = e.change_ring(QuadraticField(3, 'a')) sage: f.parent() - Group of Dirichlet characters modulo 7 with values in Number Field in a with defining polynomial x^2 - 3 with a = 1.732050807568878? + Group of Dirichlet characters modulo 7 with values in Number Field in a + with defining polynomial x^2 - 3 with a = 1.732050807568878? :: @@ -1054,11 +1062,14 @@ def decomposition(self): sage: G. = DirichletGroup(20) sage: c = a*b sage: d = c.decomposition(); d - [Dirichlet character modulo 4 of conductor 4 mapping 3 |--> -1, Dirichlet character modulo 5 of conductor 5 mapping 2 |--> zeta4] + [Dirichlet character modulo 4 of conductor 4 mapping 3 |--> -1, + Dirichlet character modulo 5 of conductor 5 mapping 2 |--> zeta4] sage: d[0].parent() - Group of Dirichlet characters modulo 4 with values in Cyclotomic Field of order 4 and degree 2 + Group of Dirichlet characters modulo 4 with values + in Cyclotomic Field of order 4 and degree 2 sage: d[1].parent() - Group of Dirichlet characters modulo 5 with values in Cyclotomic Field of order 4 and degree 2 + Group of Dirichlet characters modulo 5 with values + in Cyclotomic Field of order 4 and degree 2 We cannot multiply directly, since coercion of one element into the other parent fails in both cases:: @@ -1066,7 +1077,10 @@ def decomposition(self): sage: d[0]*d[1] == c Traceback (most recent call last): ... - TypeError: unsupported operand parent(s) for *: 'Group of Dirichlet characters modulo 4 with values in Cyclotomic Field of order 4 and degree 2' and 'Group of Dirichlet characters modulo 5 with values in Cyclotomic Field of order 4 and degree 2' + TypeError: unsupported operand parent(s) for *: 'Group of Dirichlet + characters modulo 4 with values in Cyclotomic Field of order 4 and + degree 2' and 'Group of Dirichlet characters modulo 5 with values + in Cyclotomic Field of order 4 and degree 2' We can multiply if we are explicit about where we want the multiplication to take place. @@ -1081,11 +1095,14 @@ def decomposition(self): trivial for `k = 1` and non-cyclic for `k \ge 3`:: sage: (DirichletGroup(18).0).decomposition() - [Dirichlet character modulo 2 of conductor 1, Dirichlet character modulo 9 of conductor 9 mapping 2 |--> zeta6] + [Dirichlet character modulo 2 of conductor 1, + Dirichlet character modulo 9 of conductor 9 mapping 2 |--> zeta6] sage: (DirichletGroup(36).0).decomposition() - [Dirichlet character modulo 4 of conductor 4 mapping 3 |--> -1, Dirichlet character modulo 9 of conductor 1 mapping 2 |--> 1] + [Dirichlet character modulo 4 of conductor 4 mapping 3 |--> -1, + Dirichlet character modulo 9 of conductor 1 mapping 2 |--> 1] sage: (DirichletGroup(72).0).decomposition() - [Dirichlet character modulo 8 of conductor 4 mapping 7 |--> -1, 5 |--> 1, Dirichlet character modulo 9 of conductor 1 mapping 2 |--> 1] + [Dirichlet character modulo 8 of conductor 4 mapping 7 |--> -1, 5 |--> 1, + Dirichlet character modulo 9 of conductor 1 mapping 2 |--> 1] """ D = self.parent().decomposition() vals = [[z] for z in self.values_on_gens()] @@ -1264,7 +1281,8 @@ def galois_orbit(self, sort=True): sage: e = G.0^2; e Dirichlet character modulo 13 of conductor 13 mapping 2 |--> zeta12^2 sage: e.galois_orbit() - [Dirichlet character modulo 13 of conductor 13 mapping 2 |--> zeta12^2, Dirichlet character modulo 13 of conductor 13 mapping 2 |--> -zeta12^2 + 1] + [Dirichlet character modulo 13 of conductor 13 mapping 2 |--> zeta12^2, + Dirichlet character modulo 13 of conductor 13 mapping 2 |--> -zeta12^2 + 1] A non-example:: @@ -1322,7 +1340,12 @@ def gauss_sum(self, a=1): sage: G = DirichletGroup(13) sage: e = G.0 sage: e.gauss_sum() - -zeta156^46 + zeta156^45 + zeta156^42 + zeta156^41 + 2*zeta156^40 + zeta156^37 - zeta156^36 - zeta156^34 - zeta156^33 - zeta156^31 + 2*zeta156^30 + zeta156^28 - zeta156^24 - zeta156^22 + zeta156^21 + zeta156^20 - zeta156^19 + zeta156^18 - zeta156^16 - zeta156^15 - 2*zeta156^14 - zeta156^10 + zeta156^8 + zeta156^7 + zeta156^6 + zeta156^5 - zeta156^4 - zeta156^2 - 1 + -zeta156^46 + zeta156^45 + zeta156^42 + zeta156^41 + 2*zeta156^40 + + zeta156^37 - zeta156^36 - zeta156^34 - zeta156^33 - zeta156^31 + + 2*zeta156^30 + zeta156^28 - zeta156^24 - zeta156^22 + zeta156^21 + + zeta156^20 - zeta156^19 + zeta156^18 - zeta156^16 - zeta156^15 + - 2*zeta156^14 - zeta156^10 + zeta156^8 + zeta156^7 + zeta156^6 + + zeta156^5 - zeta156^4 - zeta156^2 - 1 sage: factor(norm(e.gauss_sum())) 13^24 @@ -1340,7 +1363,8 @@ def gauss_sum(self, a=1): sage: G = DirichletGroup(13, K) sage: chi = G([z^2]) sage: chi.gauss_sum() - zeta52^22 + zeta52^21 + zeta52^19 - zeta52^16 + zeta52^15 + zeta52^14 + zeta52^12 - zeta52^11 - zeta52^10 - zeta52^7 - zeta52^5 + zeta52^4 + zeta52^22 + zeta52^21 + zeta52^19 - zeta52^16 + zeta52^15 + zeta52^14 + + zeta52^12 - zeta52^11 - zeta52^10 - zeta52^7 - zeta52^5 + zeta52^4 Check that :issue:`25127` is fixed:: @@ -1487,8 +1511,10 @@ def jacobi_sum(self, char, check=True): ... NotImplementedError: Characters must be from the same Dirichlet Group. - sage: all_jacobi_sums = [(DP[i].values_on_gens(),DP[j].values_on_gens(),DP[i].jacobi_sum(DP[j])) - ....: for i in range(p-1) for j in range(i, p-1)] + sage: all_jacobi_sums = [(DP[i].values_on_gens(), + ....: DP[j].values_on_gens(), + ....: DP[i].jacobi_sum(DP[j])) + ....: for i in range(p - 1) for j in range(i, p - 1)] sage: for s in all_jacobi_sums: ....: print(s) ((1,), (1,), 5) @@ -1898,6 +1924,7 @@ def minimize_base_ring(self): A related bug (see :issue:`18086`):: + sage: x = polygen(ZZ, 'x') sage: K. = NumberField([x^2 + 1, x^2 - 3]) sage: chi = DirichletGroup(7, K).0 sage: chi.minimize_base_ring() @@ -2256,7 +2283,8 @@ class DirichletGroupFactory(UniqueFactory): of `(\ZZ/N\ZZ)^*`:: sage: DirichletGroup(20) - Group of Dirichlet characters modulo 20 with values in Cyclotomic Field of order 4 and degree 2 + Group of Dirichlet characters modulo 20 with values + in Cyclotomic Field of order 4 and degree 2 We create the group of Dirichlet character mod 20 with values in the rational numbers:: @@ -2272,7 +2300,10 @@ class DirichletGroupFactory(UniqueFactory): on the generators of `(Z/NZ)^*`:: sage: list(G) - [Dirichlet character modulo 20 of conductor 1 mapping 11 |--> 1, 17 |--> 1, Dirichlet character modulo 20 of conductor 4 mapping 11 |--> -1, 17 |--> 1, Dirichlet character modulo 20 of conductor 5 mapping 11 |--> 1, 17 |--> -1, Dirichlet character modulo 20 of conductor 20 mapping 11 |--> -1, 17 |--> -1] + [Dirichlet character modulo 20 of conductor 1 mapping 11 |--> 1, 17 |--> 1, + Dirichlet character modulo 20 of conductor 4 mapping 11 |--> -1, 17 |--> 1, + Dirichlet character modulo 20 of conductor 5 mapping 11 |--> 1, 17 |--> -1, + Dirichlet character modulo 20 of conductor 20 mapping 11 |--> -1, 17 |--> -1] Next we construct the group of Dirichlet character mod 20, but with values in `\QQ(\zeta_n)`:: @@ -2284,7 +2315,8 @@ class DirichletGroupFactory(UniqueFactory): We next compute several invariants of ``G``:: sage: G.gens() - (Dirichlet character modulo 20 of conductor 4 mapping 11 |--> -1, 17 |--> 1, Dirichlet character modulo 20 of conductor 5 mapping 11 |--> 1, 17 |--> zeta4) + (Dirichlet character modulo 20 of conductor 4 mapping 11 |--> -1, 17 |--> 1, + Dirichlet character modulo 20 of conductor 5 mapping 11 |--> 1, 17 |--> zeta4) sage: G.unit_gens() (11, 17) sage: G.zeta() @@ -2298,22 +2330,29 @@ class DirichletGroupFactory(UniqueFactory): sage: R. = PolynomialRing(QQ) sage: K. = NumberField(x^4 + 1) sage: DirichletGroup(5, K) - Group of Dirichlet characters modulo 5 with values in Number Field in a with defining polynomial x^4 + 1 + Group of Dirichlet characters modulo 5 with values + in Number Field in a with defining polynomial x^4 + 1 An example where we give ``zeta``, but not its order:: sage: G = DirichletGroup(5, K, a); G - Group of Dirichlet characters modulo 5 with values in the group of order 8 generated by a in Number Field in a with defining polynomial x^4 + 1 + Group of Dirichlet characters modulo 5 with values in the group of order 8 + generated by a in Number Field in a with defining polynomial x^4 + 1 sage: G.list() - [Dirichlet character modulo 5 of conductor 1 mapping 2 |--> 1, Dirichlet character modulo 5 of conductor 5 mapping 2 |--> a^2, Dirichlet character modulo 5 of conductor 5 mapping 2 |--> -1, Dirichlet character modulo 5 of conductor 5 mapping 2 |--> -a^2] + [Dirichlet character modulo 5 of conductor 1 mapping 2 |--> 1, + Dirichlet character modulo 5 of conductor 5 mapping 2 |--> a^2, + Dirichlet character modulo 5 of conductor 5 mapping 2 |--> -1, + Dirichlet character modulo 5 of conductor 5 mapping 2 |--> -a^2] We can also restrict the order of the characters, either with or without specifying a root of unity:: sage: DirichletGroup(5, K, zeta=-1, zeta_order=2) - Group of Dirichlet characters modulo 5 with values in the group of order 2 generated by -1 in Number Field in a with defining polynomial x^4 + 1 + Group of Dirichlet characters modulo 5 with values in the group of order 2 + generated by -1 in Number Field in a with defining polynomial x^4 + 1 sage: DirichletGroup(5, K, zeta_order=2) - Group of Dirichlet characters modulo 5 with values in the group of order 2 generated by -1 in Number Field in a with defining polynomial x^4 + 1 + Group of Dirichlet characters modulo 5 with values in the group of order 2 + generated by -1 in Number Field in a with defining polynomial x^4 + 1 :: @@ -2331,7 +2370,8 @@ class DirichletGroupFactory(UniqueFactory): sage: p = next_prime(10^40) sage: g = DirichletGroup(19, GF(p)); g - Group of Dirichlet characters modulo 19 with values in Finite Field of size 10000000000000000000000000000000000000121 + Group of Dirichlet characters modulo 19 with values + in Finite Field of size 10000000000000000000000000000000000000121 Note that the root of unity has small order, i.e., it is not the largest order root of unity in the field:: @@ -2344,19 +2384,24 @@ class DirichletGroupFactory(UniqueFactory): sage: r4 = CyclotomicField(4).ring_of_integers() sage: G = DirichletGroup(60, r4) sage: G.gens() - (Dirichlet character modulo 60 of conductor 4 mapping 31 |--> -1, 41 |--> 1, 37 |--> 1, Dirichlet character modulo 60 of conductor 3 mapping 31 |--> 1, 41 |--> -1, 37 |--> 1, Dirichlet character modulo 60 of conductor 5 mapping 31 |--> 1, 41 |--> 1, 37 |--> zeta4) + (Dirichlet character modulo 60 of conductor 4 + mapping 31 |--> -1, 41 |--> 1, 37 |--> 1, + Dirichlet character modulo 60 of conductor 3 + mapping 31 |--> 1, 41 |--> -1, 37 |--> 1, + Dirichlet character modulo 60 of conductor 5 + mapping 31 |--> 1, 41 |--> 1, 37 |--> zeta4) sage: val = G.gens()[2].values_on_gens()[2] ; val zeta4 sage: parent(val) Gaussian Integers generated by zeta4 in Cyclotomic Field of order 4 and degree 2 - sage: r4.residue_field(r4.ideal(29).factor()[0][0])(val) + sage: r4_29_0 = r4.residue_field(r4.ideal(29).factor()[0][0]); r4_29_0(val) doctest:warning ... DeprecationWarning: ... 17 - sage: r4.residue_field(r4.ideal(29).factor()[0][0])(val) * GF(29)(3) + sage: r4_29_0(val) * GF(29)(3) 22 - sage: r4.residue_field(r4.ideal(29).factor()[0][0])(G.gens()[2].values_on_gens()[2]) * 3 + sage: r4_29_0(G.gens()[2].values_on_gens()[2]) * 3 22 - sage: parent(r4.residue_field(r4.ideal(29).factor()[0][0])(G.gens()[2].values_on_gens()[2]) * 3) + sage: parent(r4_29_0(G.gens()[2].values_on_gens()[2]) * 3) Residue field of Fractional ideal (-2*zeta4 + 5) :: @@ -2398,9 +2443,11 @@ class DirichletGroupFactory(UniqueFactory): sage: G.gens() Traceback (most recent call last): ... - NotImplementedError: factorization of polynomials over rings with composite characteristic is not implemented + NotImplementedError: factorization of polynomials over rings + with composite characteristic is not implemented sage: G = DirichletGroup(5, Zmod(15), zeta=2); G - Group of Dirichlet characters modulo 5 with values in the group of order 4 generated by 2 in Ring of integers modulo 15 + Group of Dirichlet characters modulo 5 with values in the group of order 4 + generated by 2 in Ring of integers modulo 15 sage: G.gens() (Dirichlet character modulo 5 of conductor 5 mapping 2 |--> 2,) diff --git a/src/sage/modular/hypergeometric_misc.pyx b/src/sage/modular/hypergeometric_misc.pyx index b8c39aeefa7..3be8e4dd545 100644 --- a/src/sage/modular/hypergeometric_misc.pyx +++ b/src/sage/modular/hypergeometric_misc.pyx @@ -4,7 +4,7 @@ significantly from Cythonization. """ from cpython cimport array from cysignals.signals cimport sig_check - +from sage.rings.integer cimport Integer cpdef hgm_coeffs(long long p, int f, int prec, gamma, m, int D, gtable, int gtable_prec, bint use_longs): @@ -37,7 +37,7 @@ cpdef hgm_coeffs(long long p, int f, int prec, gamma, m, int D, cdef int gl, j, k, l, v, gv cdef long long i, q1, w, w1, w2, q2, r, r1 - cdef bint flip + cdef bint flip, use_longlongs q1 = p ** f - 1 gl = len(gamma) @@ -55,6 +55,7 @@ cpdef hgm_coeffs(long long p, int f, int prec, gamma, m, int D, # for efficiency. flip = (f == 1 and prec == 1 and gtable_prec == 1) ans = [] + Rz = R.zero() if use_longs: q2 = p ** prec try: @@ -64,13 +65,15 @@ cpdef hgm_coeffs(long long p, int f, int prec, gamma, m, int D, for r in range(q1): gtab2[r] = gtable[r].lift() % q2 else: - gtab2 = array.array('q', [0]) * q1 - try: - for r in range(q1): - gtab2[r] = gtable[r] - except TypeError: - for r in range(q1): - gtab2[r] = gtable[r].lift() + use_longlongs = (Integer(p) ** prec < 2 ** 63) + if use_longlongs: + gtab2 = array.array('q', [0]) * q1 + try: + for r in range(q1): + gtab2[r] = gtable[r] + except TypeError: + for r in range(q1): + gtab2[r] = gtable[r].lift() if f == 1: for r in range(q1): digit_count[r] = r @@ -82,7 +85,6 @@ cpdef hgm_coeffs(long long p, int f, int prec, gamma, m, int D, w += r1 % p r1 //= p digit_count[r] = w - Rz = R.zero() ans = [None] * q1 for r in range(q1): sig_check() @@ -123,7 +125,7 @@ cpdef hgm_coeffs(long long p, int f, int prec, gamma, m, int D, else: for j in range(-gv): w1 = w1 * w2 % q2 - else: + elif use_longlongs: w2 = gtab2[r1] if gv > 0: for j in range(gv): @@ -131,6 +133,13 @@ cpdef hgm_coeffs(long long p, int f, int prec, gamma, m, int D, else: for j in range(-gv): u1 *= w2 + else: + if gv > 0: + for j in range(gv): + u *= gtable[r1] + else: + for j in range(-gv): + u1 *= gtable[r1] if use_longs: u = R(w) u1 = R(w1) diff --git a/src/sage/modular/hypergeometric_motive.py b/src/sage/modular/hypergeometric_motive.py index 26a9ff00c7c..347b1b4d44c 100644 --- a/src/sage/modular/hypergeometric_motive.py +++ b/src/sage/modular/hypergeometric_motive.py @@ -20,7 +20,7 @@ sage: H = Hyp(cyclotomic=([30], [1,2,3,5])) sage: H.alpha_beta() ([1/30, 7/30, 11/30, 13/30, 17/30, 19/30, 23/30, 29/30], - [0, 1/5, 1/3, 2/5, 1/2, 3/5, 2/3, 4/5]) + [0, 1/5, 1/3, 2/5, 1/2, 3/5, 2/3, 4/5]) sage: H.M_value() == 30**30 / (15**15 * 10**10 * 6**6) True sage: H.euler_factor(2, 7) @@ -1389,6 +1389,12 @@ def padic_H_value(self, p, f, t, prec=None, cache_p=False): ....: print(s) p is tame + Check that :issue:`37910` is resolved:: + + sage: H = Hyp(alpha_beta=[[1/2,1/2,1/2,1/2,1/2,1/3,2/3,1/6,5/6], [0,0,0,0,0,0,0,0,0]]) + sage: H.padic_H_value(151, 2, -512000) + 50178940126155881 + REFERENCES: - [MagmaHGM]_ @@ -1457,7 +1463,7 @@ def H_value(self, p, f, t, ring=None): - `t` -- a rational parameter - - ``ring`` -- optional (default ``UniversalCyclotomicfield``) + - ``ring`` -- optional (default :class:`UniversalCyclotomicfield`) The ring could be also ``ComplexField(n)`` or ``QQbar``. @@ -1468,9 +1474,9 @@ def H_value(self, p, f, t, ring=None): .. WARNING:: This is apparently working correctly as can be tested - using ComplexField(70) as value ring. + using ``ComplexField(70)`` as the value ring. - Using instead UniversalCyclotomicfield, this is much + Using instead :class:`UniversalCyclotomicfield`, this is much slower than the `p`-adic version :meth:`padic_H_value`. EXAMPLES: diff --git a/src/sage/modular/modform/ambient_R.py b/src/sage/modular/modform/ambient_R.py index 9f416f49169..0d19eb36cd3 100644 --- a/src/sage/modular/modform/ambient_R.py +++ b/src/sage/modular/modform/ambient_R.py @@ -23,7 +23,7 @@ def __init__(self, M, base_ring): EXAMPLES:: - sage: M = ModularForms(23,2,base_ring=GF(7)) # indirect doctest + sage: M = ModularForms(23, 2, base_ring=GF(7)) # indirect doctest sage: M Modular Forms space of dimension 3 for Congruence Subgroup Gamma0(23) of weight 2 over Finite Field of size 7 @@ -46,9 +46,10 @@ def modular_symbols(self,sign=0): sage: # needs sage.rings.number_field sage: K. = QuadraticField(-1) - sage: chi = DirichletGroup(5, base_ring = K).0 + sage: chi = DirichletGroup(5, base_ring=K).0 + sage: x = polygen(ZZ, 'x') sage: L. = K.extension(x^2 - 402*i) - sage: M = ModularForms(chi, 7, base_ring = L) + sage: M = ModularForms(chi, 7, base_ring=L) sage: symbs = M.modular_symbols() sage: symbs.character() == chi True diff --git a/src/sage/modular/modform/ring.py b/src/sage/modular/modform/ring.py index c0a69717cfa..d16ba084dbb 100644 --- a/src/sage/modular/modform/ring.py +++ b/src/sage/modular/modform/ring.py @@ -212,7 +212,7 @@ def __init__(self, group, base_ring=QQ): Traceback (most recent call last): ... ValueError: group (=3.40000000000000) should be a congruence subgroup - sage: ModularFormsRing(Gamma0(2), base_ring=PolynomialRing(ZZ,x)) + sage: ModularFormsRing(Gamma0(2), base_ring=PolynomialRing(ZZ, 'x')) Traceback (most recent call last): ... ValueError: base ring (=Univariate Polynomial Ring in x over Integer Ring) should be QQ, ZZ or a finite prime field diff --git a/src/sage/modular/modform_hecketriangle/graded_ring_element.py b/src/sage/modular/modform_hecketriangle/graded_ring_element.py index 0de1562677b..bd6d2a03a2c 100644 --- a/src/sage/modular/modform_hecketriangle/graded_ring_element.py +++ b/src/sage/modular/modform_hecketriangle/graded_ring_element.py @@ -143,7 +143,7 @@ def _richcmp_(self, other, op): EXAMPLES:: sage: from sage.modular.modform_hecketriangle.graded_ring import MeromorphicModularFormsRing - sage: (x,y,z,d) = MeromorphicModularFormsRing().pol_ring().gens() + sage: x, y, z, d = MeromorphicModularFormsRing().pol_ring().gens() sage: MeromorphicModularFormsRing(n=3)(x) == MeromorphicModularFormsRing(n=4)(x) False sage: MeromorphicModularFormsRing()(-1/x) is MeromorphicModularFormsRing()(1/(-x)) @@ -1249,7 +1249,7 @@ def order_at(self, tau=infinity): Return the (overall) order of ``self`` at ``tau`` if easily possible: Namely if ``tau`` is ``infinity`` or congruent to ``i`` resp. ``rho``. - It is possible to determine the order of points from ``HyperbolicPlane()``. + It is possible to determine the order of points from :class:`HyperbolicPlane`. In this case the coordinates of the upper half plane model are used. If ``self`` is homogeneous and modular then the rational function @@ -1834,7 +1834,7 @@ def evaluate(self, tau, prec=None, num_prec=None, check=False): (and fail) for certain (many) choices of (``base_ring``, ``tau.parent()``). - It is possible to evaluate at points of ``HyperbolicPlane()``. + It is possible to evaluate at points of :class:`HyperbolicPlane`. In this case the coordinates of the upper half plane model are used. To obtain a precise and fast result the parameters @@ -2123,7 +2123,7 @@ def evaluate(self, tau, prec=None, num_prec=None, check=False): sage: (f.q_expansion_fixed_d().polynomial())(exp((2*pi*i).n(1000)*az/G.lam())) # long time -140.471170232432551196978... + 469.079369280804086032719...*I - It is possible to evaluate at points of ``HyperbolicPlane()``:: + It is possible to evaluate at points of :class:`HyperbolicPlane`:: sage: # needs sage.symbolic sage: p = HyperbolicPlane().PD().get_point(-I/2) diff --git a/src/sage/modular/modform_hecketriangle/hecke_triangle_group_element.py b/src/sage/modular/modform_hecketriangle/hecke_triangle_group_element.py index 2d52c934adf..0e48b78e008 100644 --- a/src/sage/modular/modform_hecketriangle/hecke_triangle_group_element.py +++ b/src/sage/modular/modform_hecketriangle/hecke_triangle_group_element.py @@ -2888,9 +2888,9 @@ def root_extension_embedding(self, K=None): INPUT: - ``K`` -- A field to which we want the (correct) embedding. - If ``K=None`` (default) then ``AlgebraicField()`` is - used for elliptic elements and ``AlgebraicRealField()`` - otherwise. + If ``K=None`` (default), then ``AlgebraicField()`` is + used for elliptic elements and ``AlgebraicRealField()`` + otherwise. EXAMPLES:: @@ -2955,26 +2955,25 @@ def fixed_points(self, embedded=False, order="default"): INPUT: - - ``embedded`` -- If ``True`` the fixed points are embedded into - ``AlgebraicRealField`` resp. ``AlgebraicField``. - Default: ``False``. + - ``embedded`` -- If ``True``, the fixed points are embedded into + ``AlgebraicRealField`` resp. ``AlgebraicField``. Default: ``False``. - - ``order`` -- If ``order="none"`` the fixed points are choosen - and ordered according to a fixed formula. + - ``order`` -- If ``order="none"`` the fixed points are choosen + and ordered according to a fixed formula. - If ``order="sign"`` the fixed points are always ordered - according to the sign in front of the square root. + If ``order="sign"`` the fixed points are always ordered + according to the sign in front of the square root. - If ``order="default"`` (default) then in case the fixed - points are hyperbolic they are ordered according to the - sign of the trace of ``self`` instead, such that the - attracting fixed point comes first. + If ``order="default"`` (default) then in case the fixed + points are hyperbolic they are ordered according to the + sign of the trace of ``self`` instead, such that the + attracting fixed point comes first. - If ``order="trace"`` the fixed points are always ordered - according to the sign of the trace of ``self``. - If the trace is zero they are ordered by the sign in - front of the square root. In particular the fixed_points - in this case remain the same for ``-self``. + If ``order="trace"`` the fixed points are always ordered + according to the sign of the trace of ``self``. + If the trace is zero they are ordered by the sign in + front of the square root. In particular the fixed_points + in this case remain the same for ``-self``. OUTPUT: @@ -3108,16 +3107,15 @@ def acton(self, tau): INPUT: - - ``tau`` -- Either an element of ``self`` or any - element to which a linear fractional - transformation can be applied in - the usual way. + - ``tau`` -- Either an element of ``self`` or any + element to which a linear fractional + transformation can be applied in the usual way. - In particular ``infinity`` is a possible - argument and a possible return value. + In particular ``infinity`` is a possible + argument and a possible return value. - As mentioned it is also possible to use - points of ``HyperbolicPlane()``. + As mentioned it is also possible to use + points of ``HyperbolicPlane()``. EXAMPLES:: @@ -3152,8 +3150,10 @@ def acton(self, tau): sage: p = HyperbolicPlane().PD().get_point(-I/2+1/8) sage: G.V(2).acton(p) - Point in PD -((-(47*I + 161)*sqrt(5) - 47*I - 161)/(145*sqrt(5) + 94*I + 177) + I)/(I*(-(47*I + 161)*sqrt(5) - 47*I - 161)/(145*sqrt(5) + 94*I + 177) + 1) - sage: bool(G.V(2).acton(p).to_model('UHP').coordinates() == G.V(2).acton(p.to_model('UHP').coordinates())) + Point in PD -((-(47*I + 161)*sqrt(5) - 47*I - 161)/(145*sqrt(5) + 94*I + 177) + + I)/(I*(-(47*I + 161)*sqrt(5) - 47*I - 161)/(145*sqrt(5) + 94*I + 177) + 1) + sage: bool(G.V(2).acton(p).to_model('UHP').coordinates() + ....: == G.V(2).acton(p.to_model('UHP').coordinates())) True sage: p = HyperbolicPlane().PD().get_point(I) @@ -3334,9 +3334,15 @@ def as_hyperbolic_plane_isometry(self, model="UHP"): [lam^2 - 1 lam] [lam^2 - 1 lam^2 - 1] sage: el.as_hyperbolic_plane_isometry().parent() - Set of Morphisms from Hyperbolic plane in the Upper Half Plane Model to Hyperbolic plane in the Upper Half Plane Model in Category of hyperbolic models of Hyperbolic plane + Set of Morphisms + from Hyperbolic plane in the Upper Half Plane Model + to Hyperbolic plane in the Upper Half Plane Model + in Category of hyperbolic models of Hyperbolic plane sage: el.as_hyperbolic_plane_isometry("KM").parent() - Set of Morphisms from Hyperbolic plane in the Klein Disk Model to Hyperbolic plane in the Klein Disk Model in Category of hyperbolic models of Hyperbolic plane + Set of Morphisms + from Hyperbolic plane in the Klein Disk Model + to Hyperbolic plane in the Klein Disk Model + in Category of hyperbolic models of Hyperbolic plane """ from sage.geometry.hyperbolic_space.hyperbolic_interface import HyperbolicPlane return HyperbolicPlane().UHP().get_isometry(self._matrix).to_model(model) diff --git a/src/sage/modular/modsym/p1list_nf.py b/src/sage/modular/modsym/p1list_nf.py index 25c68137e0c..edb8248b6da 100644 --- a/src/sage/modular/modsym/p1list_nf.py +++ b/src/sage/modular/modsym/p1list_nf.py @@ -19,7 +19,8 @@ sage: k. = NumberField(x^3 + 11) sage: N = k.ideal(5, a^2 - a + 1) sage: P = P1NFList(N); P - The projective line over the ring of integers modulo the Fractional ideal (5, a^2 - a + 1) + The projective line over + the ring of integers modulo the Fractional ideal (5, a^2 - a + 1) List operations with the P1NFList: @@ -116,7 +117,7 @@ def P1NFList_clear_level_cache(): @richcmp_method class MSymbol(SageObject): - """ + r""" The constructor for an M-symbol over a number field. INPUT: @@ -127,15 +128,15 @@ class MSymbol(SageObject): level N. - ``d`` -- (optional) when present, it must be an integral element such - that + + N = R, where R is the corresponding ring of integers. + that `\langle c\rangle + \langle d\rangle + N = R`, where `R` is the corresponding ring of integers. - - ``check`` -- bool (default True). If ``check=False`` the constructor does - not check the condition + + N = R. + - ``check`` -- bool (default ``True``). If ``check=False`` the constructor does + not check the condition `\langle c\rangle + \langle d\rangle + N = R`. OUTPUT: - An M-symbol modulo the given ideal N, i.e. an element of the - projective line `\\mathbb{P}^1(R/N)`, where R is the ring of integers of + An M-symbol modulo the given ideal `N`, i.e. an element of the + projective line `\mathbb{P}^1(R/N)`, where `R` is the ring of integers of the underlying number field. EXAMPLES:: @@ -153,7 +154,7 @@ class MSymbol(SageObject): sage: MSymbol(N, (1, 0)) M-symbol (1: 0) of level Fractional ideal (2, a + 1) - We get an error if , and N are not coprime: + We get an error if `\langle c\rangle`, `\langle d\rangle` and `N` are not coprime: :: @@ -216,7 +217,7 @@ def __init__(self, N, c, d=None, check=True): self.__c, self.__d = (c1, d1) def __repr__(self): - """ + r""" Return the string representation of this MSymbol. EXAMPLES:: @@ -245,7 +246,7 @@ def _latex_(self): return r"\(%s: %s\)" % (self.c._latex_(), self.d._latex_()) def __richcmp__(self, other, op): - """ + r""" Comparison function for objects of the class MSymbol. The order is the same as for the underlying lists of lists. @@ -269,7 +270,7 @@ def __richcmp__(self, other, op): [other.__c.list(), other.__d.list()], op) def N(self): - """ + r""" Return the level or modulus of this MSymbol. EXAMPLES:: @@ -284,8 +285,8 @@ def N(self): return self.__N def tuple(self): - """ - Return the MSymbol as a list (c, d). + r""" + Return the :class:`MSymbol` as a list `(c, d)`. EXAMPLES:: @@ -300,7 +301,7 @@ def tuple(self): return self.__c, self.__d def __getitem__(self, n): - """ + r""" Indexing function for the list defined by an M-symbol. INPUT: @@ -323,7 +324,7 @@ def __getitem__(self, n): return self.tuple()[n] def __get_c(self): - """ + r""" Return the first coefficient of the M-symbol. EXAMPLES:: @@ -339,7 +340,7 @@ def __get_c(self): c = property(__get_c) def __get_d(self): - """ + r""" Return the second coefficient of the M-symbol. EXAMPLES:: @@ -355,15 +356,15 @@ def __get_d(self): d = property(__get_d) def lift_to_sl2_Ok(self): - """ - Lift the MSymbol to an element of `SL(2, Ok)`, where `Ok` is the ring + r""" + Lift the :class:`MSymbol` to an element of `SL(2, O_k)`, where `O_k` is the ring of integers of the corresponding number field. OUTPUT: A list of integral elements `[a, b, c', d']` that are the entries of - a 2x2 matrix with determinant 1. The lower two entries are congruent - (modulo the level) to the coefficients `c, d` of the MSymbol self. + a `2\times 2` matrix with determinant 1. The lower two entries are congruent + (modulo the level) to the coefficients `c`, `d` of the :class:`MSymbol` ``self``. EXAMPLES:: @@ -378,12 +379,12 @@ def lift_to_sl2_Ok(self): def normalize(self, with_scalar=False): r""" - Return a normalized MSymbol (a canonical representative of an element - of `\mathbb{P}^1(R/N)` ) equivalent to ``self``. + Return a normalized :class:`MSymbol` (a canonical representative of an element + of `\mathbb{P}^1(R/N)`) equivalent to ``self``. INPUT: - - ``with_scalar`` -- bool (default False) + - ``with_scalar`` -- bool (default ``False``) OUTPUT: @@ -391,7 +392,7 @@ def normalize(self, with_scalar=False): `(u*c', u*d')` is congruent to `(c: d)` (mod `N`), where `(c: d)` are the coefficients of ``self`` and `N` is the level. - - a normalized MSymbol (c': d') equivalent to ``self``. + - a normalized :class:`MSymbol` `(c': d')` equivalent to ``self``. EXAMPLES:: @@ -487,7 +488,7 @@ class P1NFList(SageObject): OUTPUT: - A P1NFList object representing `\mathbb{P}^1(R/N)`. + A :class:`P1NFList` object representing `\mathbb{P}^1(R/N)`. EXAMPLES:: @@ -505,7 +506,7 @@ class P1NFList(SageObject): True """ def __init__(self, N): - """ + r""" The constructor for the class P1NFList. See ``P1NFList`` for full documentation. @@ -522,7 +523,7 @@ def __init__(self, N): self.__list.sort() def __richcmp__(self, other, op): - """ + r""" Comparison function for objects of the class P1NFList. The order is the same as for the underlying modulus. @@ -547,7 +548,7 @@ def __richcmp__(self, other, op): return richcmp(self.__N, other.__N, op) def __getitem__(self, n): - """ + r""" Standard indexing function for the class P1NFList. EXAMPLES:: @@ -565,7 +566,7 @@ def __getitem__(self, n): return self.__list[n] def __len__(self): - """ + r""" Return the length of this P1NFList. EXAMPLES:: @@ -580,22 +581,22 @@ def __len__(self): return len(self.__list) def __repr__(self): - """ + r""" Return the string representation of this P1NFList. EXAMPLES:: sage: x = polygen(QQ, 'x') sage: k. = NumberField(x^3 + 11) - sage: N = k.ideal(5, a+1) + sage: N = k.ideal(5, a + 1) sage: P = P1NFList(N); P The projective line over the ring of integers modulo the Fractional ideal (5, a + 1) """ return "The projective line over the ring of integers modulo the %s" % self.__N def list(self): - """ - Return the underlying list of this P1NFList object. + r""" + Return the underlying list of this :class:`P1NFList` object. EXAMPLES:: @@ -622,14 +623,14 @@ def normalize(self, c, d=None, with_scalar=False): - ``d`` -- (optional) when present, it must be an integral element of the number field such that `(c, d)` defines an M-symbol of level `N`. - - ``with_scalar`` -- bool (default False) + - ``with_scalar`` -- bool (default ``False``) OUTPUT: - (only if ``with_scalar=True``) a transforming scalar `u`, such that `(u*c', u*d')` is congruent to `(c: d)` (mod `N`). - - a normalized MSymbol (c': d') equivalent to `(c: d)`. + - a normalized :class:`MSymbol` `(c': d')` equivalent to `(c: d)`. EXAMPLES:: @@ -640,7 +641,7 @@ def normalize(self, c, d=None, with_scalar=False): sage: P.normalize(3, a) M-symbol (1: 2*a) of level Fractional ideal (5, 1/2*a + 3/2) - We can use an MSymbol as input: + We can use an :class:`MSymbol` as input: :: @@ -667,8 +668,8 @@ def normalize(self, c, d=None, with_scalar=False): return MSymbol(self.N(), c, d).normalize(with_scalar) def N(self): - """ - Return the level or modulus of this P1NFList. + r""" + Return the level or modulus of this :class:`P1NFList`. EXAMPLES:: @@ -689,12 +690,12 @@ def index(self, c, d=None, with_scalar=False): INPUT: - ``c`` -- integral element of the corresponding number field, or an - MSymbol. + :class:`MSymbol`. - ``d`` -- (optional) when present, it must be an integral element of the number field such that `(c, d)` defines an M-symbol of level `N`. - - ``with_scalar`` -- bool (default False) + - ``with_scalar`` -- bool (default ``False``) OUTPUT: @@ -713,7 +714,7 @@ def index(self, c, d=None, with_scalar=False): sage: P[5]==MSymbol(N, 3, a).normalize() True - We can give an MSymbol as input: + We can give an :class:`MSymbol` as input: :: @@ -721,7 +722,7 @@ def index(self, c, d=None, with_scalar=False): sage: P.index(alpha) 5 - We cannot look for the class of an MSymbol of a different level: + We cannot look for the class of an :class:`MSymbol` of a different level: :: @@ -773,7 +774,7 @@ def index_of_normalized_pair(self, c, d=None): INPUT: - ``c`` -- integral element of the corresponding number field, or a - normalized MSymbol. + normalized :class:`MSymbol`. - ``d`` -- (optional) when present, it must be an integral element of the number field such that `(c, d)` defines a normalized M-symbol of @@ -808,18 +809,18 @@ def index_of_normalized_pair(self, c, d=None): return False def lift_to_sl2_Ok(self, i): - """ - Lift the `i`-th element of this P1NFList to an element of `SL(2, R)`, + r""" + Lift the `i`-th element of this :class:`P1NFList` to an element of `SL(2, R)`, where `R` is the ring of integers of the corresponding number field. INPUT: - - ``i`` - integer (index of the element to lift) + - ``i`` -- integer (index of the element to lift) OUTPUT: If the `i`-th element is `(c : d)`, the function returns a list of - integral elements `[a, b, c', d']` that defines a 2x2 matrix with + integral elements `[a, b, c', d']` that defines a `2\times 2` matrix with determinant 1 and such that `c=c'` (mod `N`) and `d=d'` (mod `N`). EXAMPLES:: @@ -845,8 +846,8 @@ def lift_to_sl2_Ok(self, i): return self[i].lift_to_sl2_Ok() def apply_S(self, i): - """ - Applies the matrix S = [0, -1, 1, 0] to the i-th M-Symbol of the list. + r""" + Applies the matrix `S` = [0, -1, 1, 0] to the `i`-th M-Symbol of the list. INPUT: @@ -855,7 +856,7 @@ def apply_S(self, i): OUTPUT: integer -- the index of the M-Symbol obtained by the right action of - the matrix S = [0, -1, 1, 0] on the i-th M-Symbol. + the matrix `S` = [0, -1, 1, 0] on the `i`-th M-Symbol. EXAMPLES:: @@ -880,8 +881,8 @@ def apply_S(self, i): return j def apply_TS(self, i): - """ - Applies the matrix TS = [1, -1, 0, 1] to the i-th M-Symbol of the list. + r""" + Applies the matrix `TS` = [1, -1, 0, 1] to the `i`-th M-Symbol of the list. INPUT: @@ -890,7 +891,7 @@ def apply_TS(self, i): OUTPUT: integer -- the index of the M-Symbol obtained by the right action of - the matrix TS = [1, -1, 0, 1] on the i-th M-Symbol. + the matrix `TS` = [1, -1, 0, 1] on the `i`-th M-Symbol. EXAMPLES:: @@ -901,7 +902,7 @@ def apply_TS(self, i): sage: P.apply_TS(3) 2 - We test that TS has order 3: + We test that `TS` has order 3: :: @@ -914,20 +915,20 @@ def apply_TS(self, i): return j def apply_T_alpha(self, i, alpha=1): - """ - Applies the matrix T_alpha = [1, alpha, 0, 1] to the i-th M-Symbol of + r""" + Applies the matrix `T_{alpha}` = [1, `alpha`, 0, 1] to the `i`-th M-Symbol of the list. INPUT: - ``i`` -- integer - - ``alpha`` -- element of the corresponding ring of integers(default 1) + - ``alpha`` -- (default 1) element of the corresponding ring of integers OUTPUT: integer -- the index of the M-Symbol obtained by the right action of - the matrix T_alpha = [1, alpha, 0, 1] on the i-th M-Symbol. + the matrix `T_{alpha}` = [1, `alpha`, 0, 1] on the i-th M-Symbol. EXAMPLES:: @@ -938,7 +939,7 @@ def apply_T_alpha(self, i, alpha=1): sage: P.apply_T_alpha(4, a^ 2 - 2) 3 - We test that T_a*T_b = T_(a+b): + We test that `T_a*T_b = T_{(a+b)}`: :: @@ -951,7 +952,7 @@ def apply_T_alpha(self, i, alpha=1): def apply_J_epsilon(self, i, e1, e2=1): r""" - Apply the matrix `J_{\epsilon}` = [e1, 0, 0, e2] to the i-th + Apply the matrix `J_{\epsilon}` = [e1, 0, 0, e2] to the `i`-th M-Symbol of the list. e1, e2 are units of the underlying number field. @@ -967,7 +968,7 @@ def apply_J_epsilon(self, i, e1, e2=1): OUTPUT: integer -- the index of the M-Symbol obtained by the right action of - the matrix `J_{\epsilon}` = [e1, 0, 0, e2] on the i-th M-Symbol. + the matrix `J_{\epsilon}` = [e1, 0, 0, e2] on the `i`-th M-Symbol. EXAMPLES:: @@ -1006,8 +1007,8 @@ def apply_J_epsilon(self, i, e1, e2=1): # ************************************************************************* def p1NFlist(N): - """ - Return a list of the normalized elements of `\\mathbb{P}^1(R/N)`, where + r""" + Return a list of the normalized elements of `\mathbb{P}^1(R/N)`, where `N` is an integral ideal. INPUT: @@ -1054,7 +1055,7 @@ def p1NFlist(N): def lift_to_sl2_Ok(N, c, d): - """ + r""" Lift a pair (c, d) to an element of `SL(2, O_k)`, where `O_k` is the ring of integers of the corresponding number field. @@ -1068,9 +1069,9 @@ def lift_to_sl2_Ok(N, c, d): OUTPUT: - A list [a, b, c', d'] of integral elements that are the entries of - a 2x2 matrix with determinant 1. The lower two entries are congruent to - c, d modulo the ideal `N`. + A list `[a, b, c', d']` of integral elements that are the entries of + a `2\times 2` matrix with determinant 1. The lower two entries are congruent to + `c`, `d` modulo the ideal `N`. EXAMPLES:: @@ -1161,9 +1162,9 @@ def lift_to_sl2_Ok(N, c, d): def make_coprime(N, c, d): - """ + r""" Return (c, d') so d' is congruent to d modulo N, and such that c and d' are - coprime ( + = R). + coprime (`\langle c\rangle + \langle d'\rangle = R`). INPUT: @@ -1175,9 +1176,9 @@ def make_coprime(N, c, d): OUTPUT: - A pair (c, d') where c, d' are integral elements of the corresponding - number field, with d' congruent to d mod N, and such that + = R - (R being the corresponding ring of integers). + A pair `(c, d')` where `c`, `d'` are integral elements of the corresponding + number field, with `d'` congruent to `d` mod `N`, and such that `\langle c\rangle + \langle d'\rangle = R` + (`R` being the corresponding ring of integers). EXAMPLES:: diff --git a/src/sage/modular/overconvergent/genus0.py b/src/sage/modular/overconvergent/genus0.py index 58f31f4af0c..d6359e53007 100644 --- a/src/sage/modular/overconvergent/genus0.py +++ b/src/sage/modular/overconvergent/genus0.py @@ -39,7 +39,7 @@ (This is Theorem 1 of [Loe2007]_.) Hence if we fix an element `c` with `|c| = p^{-\frac{12r}{p-1}}`, the space -`S_k^\dag(1, r)` of overconvergent `p`-adic modular forms has an orthonormal +`S_k^\dagger(1, r)` of overconvergent `p`-adic modular forms has an orthonormal basis given by the functions `(cf)^n`. So any element can be written in the form `E_k \times \sum_{n \ge 0} a_n (cf)^n`, where `a_n \to 0` as `N \to \infty`, and any such sequence `a_n` defines a unique overconvergent form. @@ -73,7 +73,8 @@ :: sage: f2 = M(CuspForms(3, 8).0); f2 - 3-adic overconvergent modular form of weight-character 8 with q-expansion q + 6*q^2 - 27*q^3 - 92*q^4 + 390*q^5 - 162*q^6 ... + 3-adic overconvergent modular form of weight-character 8 with q-expansion + q + 6*q^2 - 27*q^3 - 92*q^4 + 390*q^5 - 162*q^6 ... We express this in a basis, and see that the coefficients go to zero very fast: @@ -82,7 +83,10 @@ :: sage: [x.valuation(3) for x in f2.coordinates(60)] - [+Infinity, -1, 3, 6, 10, 13, 18, 20, 24, 27, 31, 34, 39, 41, 45, 48, 52, 55, 61, 62, 66, 69, 73, 76, 81, 83, 87, 90, 94, 97, 102, 104, 108, 111, 115, 118, 124, 125, 129, 132, 136, 139, 144, 146, 150, 153, 157, 160, 165, 167, 171, 174, 178, 181, 188, 188, 192, 195, 199, 202] + [+Infinity, -1, 3, 6, 10, 13, 18, 20, 24, 27, 31, 34, 39, 41, 45, 48, 52, 55, 61, + 62, 66, 69, 73, 76, 81, 83, 87, 90, 94, 97, 102, 104, 108, 111, 115, 118, 124, 125, + 129, 132, 136, 139, 144, 146, 150, 153, 157, 160, 165, 167, 171, 174, 178, 181, + 188, 188, 192, 195, 199, 202] This form has more level at `p`, and hence is less overconvergent: @@ -91,7 +95,9 @@ :: sage: f3 = M(CuspForms(9, 8).0); [x.valuation(3) for x in f3.coordinates(60)] - [+Infinity, -1, -1, 0, -4, -4, -2, -3, 0, 0, -1, -1, 1, 0, 3, 3, 3, 3, 5, 3, 7, 7, 6, 6, 8, 7, 10, 10, 8, 8, 10, 9, 12, 12, 12, 12, 14, 12, 17, 16, 15, 15, 17, 16, 19, 19, 18, 18, 20, 19, 22, 22, 22, 22, 24, 21, 25, 26, 24, 24] + [+Infinity, -1, -1, 0, -4, -4, -2, -3, 0, 0, -1, -1, 1, 0, 3, 3, 3, 3, 5, 3, 7, 7, + 6, 6, 8, 7, 10, 10, 8, 8, 10, 9, 12, 12, 12, 12, 14, 12, 17, 16, 15, 15, 17, 16, + 19, 19, 18, 18, 20, 19, 22, 22, 22, 22, 24, 21, 25, 26, 24, 24] An error will be raised for forms which are not sufficiently overconvergent: @@ -110,7 +116,7 @@ :: - sage: M.hecke_matrix(3, 4).change_ring(Qp(3,prec=1)) + sage: M.hecke_matrix(3, 4).change_ring(Qp(3, prec=1)) [ 1 + O(3) 0 0 0] [ 0 2*3^3 + O(3^4) 2*3^3 + O(3^4) 3^2 + O(3^3)] [ 0 2*3^7 + O(3^8) 2*3^8 + O(3^9) 3^6 + O(3^7)] @@ -124,10 +130,25 @@ sage: efuncs = M.eigenfunctions(4) sage: for i in [1..3]: - ....: print(efuncs[i].q_expansion(prec=4).change_ring(Qp(3,prec=20))) - (1 + O(3^20))*q + (2*3 + 3^15 + 3^16 + 3^17 + 2*3^19 + 2*3^20 + O(3^21))*q^2 + (2*3^3 + 2*3^4 + 2*3^5 + 2*3^6 + 2*3^7 + 2*3^8 + 2*3^9 + 2*3^10 + 2*3^11 + 2*3^12 + 2*3^13 + 2*3^14 + 2*3^15 + 2*3^16 + 3^17 + 2*3^18 + 2*3^19 + 3^21 + 3^22 + O(3^23))*q^3 + O(q^4) - (1 + O(3^20))*q + (3 + 2*3^2 + 3^3 + 3^4 + 3^12 + 3^13 + 2*3^14 + 3^15 + 2*3^17 + 3^18 + 3^19 + 3^20 + O(3^21))*q^2 + (3^7 + 3^13 + 2*3^14 + 2*3^15 + 3^16 + 3^17 + 2*3^18 + 3^20 + 2*3^21 + 2*3^22 + 2*3^23 + 2*3^25 + O(3^27))*q^3 + O(q^4) - (1 + O(3^20))*q + (2*3 + 3^3 + 2*3^4 + 3^6 + 2*3^8 + 3^9 + 3^10 + 2*3^11 + 2*3^13 + 3^16 + 3^18 + 3^19 + 3^20 + O(3^21))*q^2 + (3^9 + 2*3^12 + 3^15 + 3^17 + 3^18 + 3^19 + 3^20 + 2*3^22 + 2*3^23 + 2*3^27 + 2*3^28 + O(3^29))*q^3 + O(q^4) + ....: print(efuncs[i].q_expansion(prec=4).change_ring(Qp(3, prec=20))) + (1 + O(3^20))*q + + (2*3 + 3^15 + 3^16 + 3^17 + 2*3^19 + 2*3^20 + O(3^21))*q^2 + + (2*3^3 + 2*3^4 + 2*3^5 + 2*3^6 + 2*3^7 + 2*3^8 + 2*3^9 + + 2*3^10 + 2*3^11 + 2*3^12 + 2*3^13 + 2*3^14 + 2*3^15 + + 2*3^16 + 3^17 + 2*3^18 + 2*3^19 + 3^21 + 3^22 + O(3^23))*q^3 + + O(q^4) + (1 + O(3^20))*q + + (3 + 2*3^2 + 3^3 + 3^4 + 3^12 + 3^13 + 2*3^14 + + 3^15 + 2*3^17 + 3^18 + 3^19 + 3^20 + O(3^21))*q^2 + + (3^7 + 3^13 + 2*3^14 + 2*3^15 + 3^16 + 3^17 + 2*3^18 + + 3^20 + 2*3^21 + 2*3^22 + 2*3^23 + 2*3^25 + O(3^27))*q^3 + + O(q^4) + (1 + O(3^20))*q + + (2*3 + 3^3 + 2*3^4 + 3^6 + 2*3^8 + 3^9 + 3^10 + + 2*3^11 + 2*3^13 + 3^16 + 3^18 + 3^19 + 3^20 + O(3^21))*q^2 + + (3^9 + 2*3^12 + 3^15 + 3^17 + 3^18 + 3^19 + 3^20 + + 2*3^22 + 2*3^23 + 2*3^27 + 2*3^28 + O(3^29))*q^3 + + O(q^4) The first eigenfunction is a classical cusp form of level 3: @@ -157,7 +178,12 @@ :: sage: a3 = efuncs[3].q_expansion()[3]; a3 - 3^9 + 2*3^12 + 3^15 + 3^17 + 3^18 + 3^19 + 3^20 + 2*3^22 + 2*3^23 + 2*3^27 + 2*3^28 + 3^32 + 3^33 + 2*3^34 + 3^38 + 2*3^39 + 3^40 + 2*3^41 + 3^44 + 3^45 + 3^46 + 2*3^47 + 2*3^48 + 3^49 + 3^50 + 2*3^51 + 2*3^52 + 3^53 + 2*3^54 + 3^55 + 3^56 + 3^57 + 2*3^58 + 2*3^59 + 3^60 + 2*3^61 + 2*3^63 + 2*3^64 + 3^65 + 2*3^67 + 3^68 + 2*3^69 + 2*3^71 + 3^72 + 2*3^74 + 3^75 + 3^76 + 3^79 + 3^80 + 2*3^83 + 2*3^84 + 3^85 + 2*3^87 + 3^88 + 2*3^89 + 2*3^90 + 2*3^91 + 3^92 + O(3^98) + 3^9 + 2*3^12 + 3^15 + 3^17 + 3^18 + 3^19 + 3^20 + 2*3^22 + 2*3^23 + 2*3^27 + + 2*3^28 + 3^32 + 3^33 + 2*3^34 + 3^38 + 2*3^39 + 3^40 + 2*3^41 + 3^44 + 3^45 + + 3^46 + 2*3^47 + 2*3^48 + 3^49 + 3^50 + 2*3^51 + 2*3^52 + 3^53 + 2*3^54 + 3^55 + + 3^56 + 3^57 + 2*3^58 + 2*3^59 + 3^60 + 2*3^61 + 2*3^63 + 2*3^64 + 3^65 + 2*3^67 + + 3^68 + 2*3^69 + 2*3^71 + 3^72 + 2*3^74 + 3^75 + 3^76 + 3^79 + 3^80 + 2*3^83 + + 2*3^84 + 3^85 + 2*3^87 + 3^88 + 2*3^89 + 2*3^90 + 2*3^91 + 3^92 + O(3^98) sage: efuncs[3].slope() 9 """ @@ -216,23 +242,23 @@ def OverconvergentModularForms(prime, weight, radius, base_ring=QQ, prec=20, cha INPUT: - - ``prime`` - a prime number `p`, which must be one of the primes `\{2, 3, + - ``prime`` -- a prime number `p`, which must be one of the primes `\{2, 3, 5, 7, 13\}`, or the congruence subgroup `\Gamma_0(p)` where `p` is one of these primes. - - ``weight`` - an integer (which at present must be 0 or `\ge 2`), the + - ``weight`` -- an integer (which at present must be 0 or `\ge 2`), the weight. - - ``radius`` - a rational number in the interval `\left( 0, \frac{p}{p+1} + - ``radius`` -- a rational number in the interval `\left( 0, \frac{p}{p+1} \right)`, the radius of overconvergence. - - ``base_ring`` (default: `\QQ`), a ring over which to compute. This + - ``base_ring`` -- (default: `\QQ`), a ring over which to compute. This need not be a `p`-adic ring. - - ``prec`` - an integer (default: 20), the number of `q`-expansion terms to + - ``prec`` -- an integer (default: 20), the number of `q`-expansion terms to compute. - - ``char`` - a Dirichlet character modulo `p` or ``None`` (the default). + - ``char`` -- a Dirichlet character modulo `p` or ``None`` (the default). Here ``None`` is interpreted as the trivial character modulo `p`. The character `\chi` and weight `k` must satisfy `(-1)^k = \chi(-1)`, and @@ -243,11 +269,14 @@ def OverconvergentModularForms(prime, weight, radius, base_ring=QQ, prec=20, cha EXAMPLES:: sage: OverconvergentModularForms(3, 0, 1/2) - Space of 3-adic 1/2-overconvergent modular forms of weight-character 0 over Rational Field + Space of 3-adic 1/2-overconvergent modular forms + of weight-character 0 over Rational Field sage: OverconvergentModularForms(3, 16, 1/2) - Space of 3-adic 1/2-overconvergent modular forms of weight-character 16 over Rational Field - sage: OverconvergentModularForms(3, 3, 1/2, char = DirichletGroup(3,QQ).0) - Space of 3-adic 1/2-overconvergent modular forms of weight-character (3, 3, [-1]) over Rational Field + Space of 3-adic 1/2-overconvergent modular forms + of weight-character 16 over Rational Field + sage: OverconvergentModularForms(3, 3, 1/2, char=DirichletGroup(3,QQ).0) + Space of 3-adic 1/2-overconvergent modular forms + of weight-character (3, 3, [-1]) over Rational Field """ if is_Gamma0(prime) or is_Gamma1(prime): prime = prime.level() @@ -360,17 +389,19 @@ def _set_radius(self, radius): sage: M = OverconvergentModularForms(3, 2, 1/2) # indirect doctest sage: M._set_radius(1/3); M - Space of 3-adic 1/3-overconvergent modular forms of weight-character 2 over Rational Field + Space of 3-adic 1/3-overconvergent modular forms of weight-character 2 + over Rational Field sage: x = polygen(ZZ, 'x') sage: L. = Qp(3).extension(x^5 - 3) - sage: OverconvergentModularForms(3, 2, 1/30, base_ring=L).normalising_factor() # indirect doctest + sage: OverconvergentModularForms(3, 2, 1/30, base_ring=L).normalising_factor() # indirect doctest w + O(w^101) sage: OverconvergentModularForms(3, 2, 1/40, base_ring=L) Traceback (most recent call last): ... - ValueError: no element of base ring (=3-adic Eisenstein Extension ...) has normalised valuation 3/20 + ValueError: no element of base ring (=3-adic Eisenstein Extension ...) + has normalised valuation 3/20 """ p = ZZ(self.prime()) @@ -399,9 +430,11 @@ def _set_radius(self, radius): def is_exact(self): r""" - True if elements of this space are represented exactly, i.e., there is - no precision loss when doing arithmetic. As this is never true for - overconvergent modular forms spaces, this returns False. + Return ``True`` if elements of this space are represented exactly. + + This would mean that there is no precision loss when doing arithmetic. + As this is never true for overconvergent modular forms spaces, + this method returns ``False``. EXAMPLES:: @@ -412,13 +445,14 @@ def is_exact(self): def change_ring(self, ring): r""" - Return the space corresponding to self but over the given base ring. + Return the space corresponding to ``self`` but over the given base ring. EXAMPLES:: sage: M = OverconvergentModularForms(2, 0, 1/2) sage: M.change_ring(Qp(2)) - Space of 2-adic 1/2-overconvergent modular forms of weight-character 0 over 2-adic Field with ... + Space of 2-adic 1/2-overconvergent modular forms of weight-character 0 + over 2-adic Field with ... """ return OverconvergentModularForms(self.prime(), self.weight(), self.radius(), ring, self.prec(), self.character()) @@ -434,11 +468,13 @@ def base_extend(self, ring): sage: M = OverconvergentModularForms(2, 0, 1/2, base_ring=Qp(2)) sage: x = polygen(ZZ, 'x') sage: M.base_extend(Qp(2).extension(x^2 - 2, names="w")) - Space of 2-adic 1/2-overconvergent modular forms of weight-character 0 over 2-adic Eisenstein Extension ... + Space of 2-adic 1/2-overconvergent modular forms of weight-character 0 + over 2-adic Eisenstein Extension ... sage: M.base_extend(QQ) Traceback (most recent call last): ... - TypeError: Base extension of self (over '2-adic Field with capped relative precision 20') to ring 'Rational Field' not defined. + TypeError: Base extension of self (over '2-adic Field with capped + relative precision 20') to ring 'Rational Field' not defined. """ if ring.has_coerce_map_from(self.base_ring()): return self.change_ring(ring) @@ -451,16 +487,19 @@ def _an_element_(self): EXAMPLES:: - sage: OverconvergentModularForms(3, 2, 1/3, prec=4).an_element() # indirect doctest - 3-adic overconvergent modular form of weight-character 2 with q-expansion 9*q + 216*q^2 + 2430*q^3 + O(q^4) + sage: OverconvergentModularForms(3, 2, 1/3, prec=4).an_element() # indirect doctest + 3-adic overconvergent modular form of weight-character 2 + with q-expansion 9*q + 216*q^2 + 2430*q^3 + O(q^4) """ return self.element_class(self, self._gsr.an_element()) def character(self): r""" - Return the character of self. For overconvergent forms, the weight and - the character are unified into the concept of a weight-character, so - this returns exactly the same thing as self.weight(). + Return the character of ``self``. + + For overconvergent forms, the weight and the character are unified into + the concept of a weight-character, so this returns exactly the same + thing as :meth:`weight`. EXAMPLES:: @@ -475,9 +514,11 @@ def character(self): def weight(self): r""" - Return the character of self. For overconvergent forms, the weight and - the character are unified into the concept of a weight-character, so - this returns exactly the same thing as self.character(). + Return the weight of ``self``. + + For overconvergent forms, the weight and the character are unified into + the concept of a weight-character, so this returns exactly the same + thing as :meth:`character`. EXAMPLES:: @@ -492,6 +533,8 @@ def weight(self): def normalising_factor(self): r""" + Return the normalising factor of ``self``. + The normalising factor `c` such that `g = c f` is a parameter for the `r`-overconvergent disc in `X_0(p)`, where `f` is the standard uniformiser. @@ -515,7 +558,7 @@ def __eq__(self, other): False sage: OverconvergentModularForms(3, 0, 1/2) == OverconvergentModularForms(3, 0, 1/3) False - sage: OverconvergentModularForms(3, 0, 1/2) == OverconvergentModularForms(3, 0, 1/2, base_ring = Qp(3)) + sage: OverconvergentModularForms(3, 0, 1/2) == OverconvergentModularForms(3, 0, 1/2, base_ring=Qp(3)) False sage: OverconvergentModularForms(3, 0, 1/2) == OverconvergentModularForms(3, 0, 1/2) True @@ -535,7 +578,7 @@ def __ne__(self, other): True sage: OverconvergentModularForms(3, 0, 1/2) != OverconvergentModularForms(3, 0, 1/3) True - sage: OverconvergentModularForms(3, 0, 1/2) != OverconvergentModularForms(3, 0, 1/2, base_ring = Qp(3)) + sage: OverconvergentModularForms(3, 0, 1/2) != OverconvergentModularForms(3, 0, 1/2, base_ring=Qp(3)) True sage: OverconvergentModularForms(3, 0, 1/2) != OverconvergentModularForms(3, 0, 1/2) False @@ -579,7 +622,7 @@ def _params(self): def __reduce__(self): r""" - Return the function and arguments used to construct self. Used for pickling. + Return the function and arguments used to construct ``self``. Used for pickling. EXAMPLES:: @@ -599,24 +642,27 @@ def __reduce__(self): def gen(self, i): r""" - Return the ith module generator of self. + Return the `i`-th module generator of ``self``. EXAMPLES:: sage: M = OverconvergentModularForms(3, 2, 1/2, prec=4) sage: M.gen(0) - 3-adic overconvergent modular form of weight-character 2 with q-expansion 1 + 12*q + 36*q^2 + 12*q^3 + O(q^4) + 3-adic overconvergent modular form of weight-character 2 + with q-expansion 1 + 12*q + 36*q^2 + 12*q^3 + O(q^4) sage: M.gen(1) - 3-adic overconvergent modular form of weight-character 2 with q-expansion 27*q + 648*q^2 + 7290*q^3 + O(q^4) + 3-adic overconvergent modular form of weight-character 2 + with q-expansion 27*q + 648*q^2 + 7290*q^3 + O(q^4) sage: M.gen(30) - 3-adic overconvergent modular form of weight-character 2 with q-expansion O(q^4) + 3-adic overconvergent modular form of weight-character 2 + with q-expansion O(q^4) """ return OverconvergentModularFormElement(self, gexp=self._gsr.gen()**i) def _repr_(self): r""" - Return a string representation of self. + Return a string representation of ``self``. EXAMPLES:: @@ -627,8 +673,9 @@ def _repr_(self): def prime(self): r""" - Return the residue characteristic of self, i.e. the prime `p` such that - this is a `p`-adic space. + Return the residue characteristic of ``self``. + + This is the prime `p` such that this is a `p`-adic space. EXAMPLES:: @@ -651,16 +698,18 @@ def radius(self): def gens(self): r""" Return a generator object that iterates over the (infinite) set of - basis vectors of self. + basis vectors of ``self``. EXAMPLES:: sage: o = OverconvergentModularForms(3, 12, 1/2) sage: t = o.gens() sage: next(t) - 3-adic overconvergent modular form of weight-character 12 with q-expansion 1 - 32760/61203943*q - 67125240/61203943*q^2 - ... + 3-adic overconvergent modular form of weight-character 12 with q-expansion + 1 - 32760/61203943*q - 67125240/61203943*q^2 - ... sage: next(t) - 3-adic overconvergent modular form of weight-character 12 with q-expansion 27*q + 19829193012/61203943*q^2 + 146902585770/61203943*q^3 + ... + 3-adic overconvergent modular form of weight-character 12 with q-expansion + 27*q + 19829193012/61203943*q^2 + 146902585770/61203943*q^3 + ... """ i = 0 while True: @@ -669,14 +718,15 @@ def gens(self): def prec(self): r""" - Return the series precision of self. Note that this is different from - the `p`-adic precision of the base ring. + Return the series precision of ``self``. + + Note that this is different from the `p`-adic precision of the base ring. EXAMPLES:: sage: OverconvergentModularForms(3, 0, 1/2).prec() 20 - sage: OverconvergentModularForms(3, 0, 1/2,prec=40).prec() + sage: OverconvergentModularForms(3, 0, 1/2, prec=40).prec() 40 """ return self._prec @@ -695,7 +745,7 @@ def _element_constructor_(self, input): - arbitrary power series in `q` - lists of elements of the base ring (interpreted as vectors in the - basis given by self.gens()). + basis given by :meth:`gens`). Precision may be specified by padding lists at the end with zeros; inputs with a higher precision than the set precision of this space @@ -799,7 +849,7 @@ def zero(self): def _coerce_from_ocmf(self, f): r""" - Try to convert the overconvergent modular form `f` into an element of self. + Try to convert the overconvergent modular form `f` into an element of ``self``. An error will be raised if this is obviously nonsense. @@ -821,8 +871,9 @@ def _coerce_from_ocmf(self, f): def _coerce_map_from_(self, other): r""" - Canonical coercion of x into self. Here the possibilities for x are - more restricted. + Canonical coercion of ``x`` into ``self``. + + Here the possibilities for ``x`` are more restricted. TESTS:: @@ -844,10 +895,11 @@ def _coerce_map_from_(self, other): def coordinate_vector(self, x): r""" - Write x as a vector with respect to the basis given by self.basis(). - Here x must be an element of this space or something that can be - converted into one. If x has precision less than the default precision - of self, then the returned vector will be shorter. + Write ``x`` as a vector with respect to the basis given by ``self.basis()``. + + Here ``x`` must be an element of this space or something that can be + converted into one. If ``x`` has precision less than the default precision + of ``self``, then the returned vector will be shorter. EXAMPLES:: @@ -873,7 +925,7 @@ def coordinate_vector(self, x): def ngens(self): r""" - The number of generators of self (as a module over its base ring), i.e. infinity. + The number of generators of ``self`` (as a module over its base ring), i.e. infinity. EXAMPLES:: @@ -893,7 +945,7 @@ def hecke_operator(self, f, m): `T_m` acting on `f`. The input may be either a "bare" power series, or an - OverconvergentModularFormElement object; the return value will be of + :class:`OverconvergentModularFormElement` object; the return value will be of the same type. EXAMPLES:: @@ -901,9 +953,12 @@ def hecke_operator(self, f, m): sage: M = OverconvergentModularForms(3, 0, 1/2) sage: f = M.1 sage: M.hecke_operator(f, 3) - 3-adic overconvergent modular form of weight-character 0 with q-expansion 2430*q + 265356*q^2 + 10670373*q^3 + 249948828*q^4 + 4113612864*q^5 + 52494114852*q^6 + O(q^7) + 3-adic overconvergent modular form of weight-character 0 with q-expansion + 2430*q + 265356*q^2 + 10670373*q^3 + 249948828*q^4 + 4113612864*q^5 + + 52494114852*q^6 + O(q^7) sage: M.hecke_operator(f.q_expansion(), 3) - 2430*q + 265356*q^2 + 10670373*q^3 + 249948828*q^4 + 4113612864*q^5 + 52494114852*q^6 + O(q^7) + 2430*q + 265356*q^2 + 10670373*q^3 + 249948828*q^4 + 4113612864*q^5 + + 52494114852*q^6 + O(q^7) """ # This should just be an instance of hecke_operator_on_qexp but that @@ -943,19 +998,30 @@ 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, side='left'): 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 - on the left on column vectors (this is the opposite of the conventions - of the sage.modules.matrix_morphism class!) Uses naive `q`-expansion - arguments if use_recurrence=False and uses the Kolberg style - recurrences if use_recurrence=True. - - The argument "exact_arith" causes the computation to be done with - rational arithmetic, even if the base ring is an inexact `p`-adic ring. - This is useful as there can be precision loss issues (particularly with - use_recurrence=False). + Calculate the matrix of the `T_m` operator, truncated to `n \times n`. + + INPUT: + + - ``m`` -- integer; determines the operator `T_m` + + - ``n`` -- integer; truncate the matrix in the basis of this space + to an `n \times n` matrix + + - ``use_recurrence`` -- boolean (default: ``False``); whether to use + Kolberg style recurrences. If ``False``, use naive `q`-expansion + arguments. + + - ``exact_arith`` -- boolean (default: ``True``); whether to do the + computation to be done with rational arithmetic, even if the base ring + is an inexact `p`-adic ring. + + This is useful as there can be precision loss issues (particularly + with ``use_recurrence=False``). + + - ``side`` -- ``'left'`` (default) or ``'right'``; if ``'left'``, the + operator acts on the left on column vectors EXAMPLES:: @@ -964,18 +1030,27 @@ def hecke_matrix(self, m, n, use_recurrence=False, exact_arith=False): [ 0 24 64 0] [ 0 32 1152 4608] [ 0 0 3072 61440] - sage: OverconvergentModularForms(2, 12, 1/2, base_ring=pAdicField(2)).hecke_matrix(2, 3) * (1 + O(2^2)) + sage: o = OverconvergentModularForms(2, 12, 1/2, base_ring=pAdicField(2)) + sage: o.hecke_matrix(2, 3) * (1 + O(2^2)) [ 1 + O(2^2) 0 0] [ 0 2^3 + O(2^5) 2^6 + O(2^8)] [ 0 2^4 + O(2^6) 2^7 + 2^8 + O(2^9)] - sage: OverconvergentModularForms(2, 12, 1/2, base_ring=pAdicField(2)).hecke_matrix(2, 3, exact_arith=True) + sage: o = OverconvergentModularForms(2, 12, 1/2, base_ring=pAdicField(2)) + sage: o.hecke_matrix(2, 3, exact_arith=True) [ 1 0 0] [ 0 33881928/1414477 64] [ 0 -192898739923312/2000745183529 1626332544/1414477] - """ + Side switch:: + + sage: OverconvergentModularForms(2, 0, 1/2).hecke_matrix(2, 4, side='right') + [ 1 0 0 0] + [ 0 24 32 0] + [ 0 64 1152 3072] + [ 0 0 4608 61440] + """ if exact_arith and not self.base_ring().is_exact(): - return self.change_ring(QQ).hecke_matrix(m, n, use_recurrence) + return self.change_ring(QQ).hecke_matrix(m, n, use_recurrence, side=side) M = MatrixSpace(self.base_ring(), n) mat = M(0) @@ -1022,17 +1097,21 @@ def hecke_matrix(self, m, n, use_recurrence=False, exact_arith=False): l = self._convert_to_basis(self.hecke_operator(self._basis_cache[j], m)) for i in range(n): mat[i, j] = l[i] + + if side == 'right': + return mat.transpose() + return mat def slopes(self, n, use_recurrence=False): r""" - Compute the slopes of the `U_p` operator acting on self, using an n x n matrix. + Compute the slopes of the `U_p` operator acting on ``self``, using an `n\times n` matrix. EXAMPLES:: - sage: OverconvergentModularForms(5,2,1/3,base_ring=Qp(5),prec=100).slopes(5) + sage: OverconvergentModularForms(5, 2, 1/3, base_ring=Qp(5), prec=100).slopes(5) [0, 2, 5, 6, 9] - sage: OverconvergentModularForms(2,1,1/3,char=DirichletGroup(4,QQ).0).slopes(5) + sage: OverconvergentModularForms(2, 1, 1/3, char=DirichletGroup(4,QQ).0).slopes(5) [0, 2, 4, 6, 8] """ if self.base_ring() == QQ: @@ -1045,22 +1124,22 @@ def slopes(self, n, use_recurrence=False): def eigenfunctions(self, n, F=None, exact_arith=True): """ - Calculate approximations to eigenfunctions of self. + Calculate approximations to eigenfunctions of ``self``. - These are the eigenfunctions of self.hecke_matrix(p, n), which + 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 + of :class:`OverconvergentModularFormElement` objects, in increasing order of slope. INPUT: - - ``n`` - integer. The size of the matrix to use. + - ``n`` -- integer. The size of the matrix to use. - - ``F`` - None, or a field over which to calculate eigenvalues. If the - field is None, the current base ring is used. If the base ring is not + - ``F`` -- either ``None`` or a field over which to calculate eigenvalues. If the + field is ``None``, the current base ring is used. If the base ring is not a `p`-adic ring, an error will be raised. - - ``exact_arith`` - True or False (default True). If True, use exact + - ``exact_arith`` -- ``True`` or ``False`` (default ``True``). If ``True``, use exact rational arithmetic to calculate the matrix of the `U` operator and its characteristic power series, even when the base ring is an inexact `p`-adic ring. This is typically slower, but more numerically stable. @@ -1073,7 +1152,35 @@ def eigenfunctions(self, n, F=None, exact_arith=True): sage: X = OverconvergentModularForms(2, 2, 1/6).eigenfunctions(8, Qp(2, 100)) sage: X[1] - 2-adic overconvergent modular form of weight-character 2 with q-expansion (1 + O(2^74))*q + (2^4 + 2^5 + 2^9 + 2^10 + 2^12 + 2^13 + 2^15 + 2^17 + 2^19 + 2^20 + 2^21 + 2^23 + 2^28 + 2^30 + 2^31 + 2^32 + 2^34 + 2^36 + 2^37 + 2^39 + 2^40 + 2^43 + 2^44 + 2^45 + 2^47 + 2^48 + 2^52 + 2^53 + 2^54 + 2^55 + 2^56 + 2^58 + 2^59 + 2^60 + 2^61 + 2^67 + 2^68 + 2^70 + 2^71 + 2^72 + 2^74 + 2^76 + O(2^78))*q^2 + (2^2 + 2^7 + 2^8 + 2^9 + 2^12 + 2^13 + 2^16 + 2^17 + 2^21 + 2^23 + 2^25 + 2^28 + 2^33 + 2^34 + 2^36 + 2^37 + 2^42 + 2^45 + 2^47 + 2^49 + 2^50 + 2^51 + 2^54 + 2^55 + 2^58 + 2^60 + 2^61 + 2^67 + 2^71 + 2^72 + O(2^76))*q^3 + (2^8 + 2^11 + 2^14 + 2^19 + 2^21 + 2^22 + 2^24 + 2^25 + 2^26 + 2^27 + 2^28 + 2^29 + 2^32 + 2^33 + 2^35 + 2^36 + 2^44 + 2^45 + 2^46 + 2^47 + 2^49 + 2^50 + 2^53 + 2^54 + 2^55 + 2^56 + 2^57 + 2^60 + 2^63 + 2^66 + 2^67 + 2^69 + 2^74 + 2^76 + 2^79 + 2^80 + 2^81 + O(2^82))*q^4 + (2 + 2^2 + 2^9 + 2^13 + 2^15 + 2^17 + 2^19 + 2^21 + 2^23 + 2^26 + 2^27 + 2^28 + 2^30 + 2^33 + 2^34 + 2^35 + 2^36 + 2^37 + 2^38 + 2^39 + 2^41 + 2^42 + 2^43 + 2^45 + 2^58 + 2^59 + 2^60 + 2^61 + 2^62 + 2^63 + 2^65 + 2^66 + 2^68 + 2^69 + 2^71 + 2^72 + O(2^75))*q^5 + (2^6 + 2^7 + 2^15 + 2^16 + 2^21 + 2^24 + 2^25 + 2^28 + 2^29 + 2^33 + 2^34 + 2^37 + 2^44 + 2^45 + 2^48 + 2^50 + 2^51 + 2^54 + 2^55 + 2^57 + 2^58 + 2^59 + 2^60 + 2^64 + 2^69 + 2^71 + 2^73 + 2^75 + 2^78 + O(2^80))*q^6 + (2^3 + 2^8 + 2^9 + 2^10 + 2^11 + 2^12 + 2^14 + 2^15 + 2^17 + 2^19 + 2^20 + 2^21 + 2^23 + 2^25 + 2^26 + 2^34 + 2^37 + 2^38 + 2^39 + 2^40 + 2^41 + 2^45 + 2^47 + 2^49 + 2^51 + 2^53 + 2^54 + 2^55 + 2^57 + 2^58 + 2^59 + 2^60 + 2^61 + 2^66 + 2^69 + 2^70 + 2^71 + 2^74 + 2^76 + O(2^77))*q^7 + O(q^8) + 2-adic overconvergent modular form of weight-character 2 with q-expansion + (1 + O(2^74))*q + + (2^4 + 2^5 + 2^9 + 2^10 + 2^12 + 2^13 + 2^15 + 2^17 + 2^19 + 2^20 + + 2^21 + 2^23 + 2^28 + 2^30 + 2^31 + 2^32 + 2^34 + 2^36 + 2^37 + + 2^39 + 2^40 + 2^43 + 2^44 + 2^45 + 2^47 + 2^48 + 2^52 + 2^53 + + 2^54 + 2^55 + 2^56 + 2^58 + 2^59 + 2^60 + 2^61 + 2^67 + 2^68 + + 2^70 + 2^71 + 2^72 + 2^74 + 2^76 + O(2^78))*q^2 + + (2^2 + 2^7 + 2^8 + 2^9 + 2^12 + 2^13 + 2^16 + 2^17 + 2^21 + 2^23 + + 2^25 + 2^28 + 2^33 + 2^34 + 2^36 + 2^37 + 2^42 + 2^45 + 2^47 + + 2^49 + 2^50 + 2^51 + 2^54 + 2^55 + 2^58 + 2^60 + 2^61 + 2^67 + + 2^71 + 2^72 + O(2^76))*q^3 + + (2^8 + 2^11 + 2^14 + 2^19 + 2^21 + 2^22 + 2^24 + 2^25 + 2^26 + + 2^27 + 2^28 + 2^29 + 2^32 + 2^33 + 2^35 + 2^36 + 2^44 + 2^45 + + 2^46 + 2^47 + 2^49 + 2^50 + 2^53 + 2^54 + 2^55 + 2^56 + 2^57 + + 2^60 + 2^63 + 2^66 + 2^67 + 2^69 + 2^74 + 2^76 + 2^79 + 2^80 + + 2^81 + O(2^82))*q^4 + + (2 + 2^2 + 2^9 + 2^13 + 2^15 + 2^17 + 2^19 + 2^21 + 2^23 + 2^26 + + 2^27 + 2^28 + 2^30 + 2^33 + 2^34 + 2^35 + 2^36 + 2^37 + 2^38 + + 2^39 + 2^41 + 2^42 + 2^43 + 2^45 + 2^58 + 2^59 + 2^60 + 2^61 + + 2^62 + 2^63 + 2^65 + 2^66 + 2^68 + 2^69 + 2^71 + 2^72 + O(2^75))*q^5 + + (2^6 + 2^7 + 2^15 + 2^16 + 2^21 + 2^24 + 2^25 + 2^28 + 2^29 + 2^33 + + 2^34 + 2^37 + 2^44 + 2^45 + 2^48 + 2^50 + 2^51 + 2^54 + 2^55 + + 2^57 + 2^58 + 2^59 + 2^60 + 2^64 + 2^69 + 2^71 + 2^73 + 2^75 + + 2^78 + O(2^80))*q^6 + (2^3 + 2^8 + 2^9 + 2^10 + 2^11 + 2^12 + + 2^14 + 2^15 + 2^17 + 2^19 + 2^20 + 2^21 + 2^23 + 2^25 + 2^26 + + 2^34 + 2^37 + 2^38 + 2^39 + 2^40 + 2^41 + 2^45 + 2^47 + 2^49 + + 2^51 + 2^53 + 2^54 + 2^55 + 2^57 + 2^58 + 2^59 + 2^60 + 2^61 + + 2^66 + 2^69 + 2^70 + 2^71 + 2^74 + 2^76 + O(2^77))*q^7 + + O(q^8) sage: [x.slope() for x in X] [0, 4, 8, 14, 16, 18, 26, 30] """ @@ -1155,10 +1262,13 @@ def eigenfunctions(self, n, F=None, exact_arith=True): def recurrence_matrix(self, use_smithline=True): r""" - Return the recurrence matrix satisfied by the coefficients of `U`, - that is a matrix `R =(r_{rs})_{r,s=1 \dots p}` such that `u_{ij} = - \sum_{r,s=1}^p r_{rs} u_{i-r, j-s}`. Uses an elegant construction which - I believe is due to Smithline. See [Loe2007]_. + Return the recurrence matrix satisfied by the coefficients of `U`. + + This is a matrix `R =(r_{rs})_{r,s=1,\dots,p}` such that `u_{ij} = + \sum_{r,s=1}^p r_{rs} u_{i-r, j-s}`. + + Uses an elegant construction which the author believes to be due + to Smithline. See [Loe2007]_. EXAMPLES:: @@ -1248,15 +1358,30 @@ def _discover_recurrence_matrix(self, use_smithline=True): def cps_u(self, n, use_recurrence=False): r""" - Compute the characteristic power series of `U_p` acting on self, using - an n x n matrix. + Compute the characteristic power series of `U_p` acting on ``self``, using + an `n\times n` matrix. EXAMPLES:: sage: OverconvergentModularForms(3, 16, 1/2, base_ring=Qp(3)).cps_u(4) - 1 + O(3^20) + (2 + 2*3 + 2*3^2 + 2*3^4 + 3^5 + 3^6 + 3^7 + 3^11 + 3^12 + 2*3^14 + 3^16 + 3^18 + O(3^19))*T + (2*3^3 + 3^5 + 3^6 + 3^7 + 2*3^8 + 2*3^9 + 2*3^10 + 3^11 + 3^12 + 2*3^13 + 2*3^16 + 2*3^18 + O(3^19))*T^2 + (2*3^15 + 2*3^16 + 2*3^19 + 2*3^20 + 2*3^21 + O(3^22))*T^3 + (3^17 + 2*3^18 + 3^19 + 3^20 + 3^22 + 2*3^23 + 2*3^25 + 3^26 + O(3^27))*T^4 + 1 + O(3^20) + + (2 + 2*3 + 2*3^2 + 2*3^4 + 3^5 + 3^6 + 3^7 + + 3^11 + 3^12 + 2*3^14 + 3^16 + 3^18 + O(3^19))*T + + (2*3^3 + 3^5 + 3^6 + 3^7 + 2*3^8 + 2*3^9 + 2*3^10 + + 3^11 + 3^12 + 2*3^13 + 2*3^16 + 2*3^18 + O(3^19))*T^2 + + (2*3^15 + 2*3^16 + 2*3^19 + 2*3^20 + 2*3^21 + O(3^22))*T^3 + + (3^17 + 2*3^18 + 3^19 + 3^20 + 3^22 + 2*3^23 + 2*3^25 + 3^26 + O(3^27))*T^4 sage: OverconvergentModularForms(3, 16, 1/2, base_ring=Qp(3), prec=30).cps_u(10) - 1 + O(3^20) + (2 + 2*3 + 2*3^2 + 2*3^4 + 3^5 + 3^6 + 3^7 + 2*3^15 + O(3^16))*T + (2*3^3 + 3^5 + 3^6 + 3^7 + 2*3^8 + 2*3^9 + 2*3^10 + 2*3^11 + 2*3^12 + 2*3^13 + 3^14 + 3^15 + O(3^16))*T^2 + (3^14 + 2*3^15 + 2*3^16 + 3^17 + 3^18 + O(3^19))*T^3 + (3^17 + 2*3^18 + 3^19 + 3^20 + 3^21 + O(3^24))*T^4 + (3^29 + 2*3^32 + O(3^33))*T^5 + (2*3^44 + O(3^45))*T^6 + (2*3^59 + O(3^60))*T^7 + (2*3^78 + O(3^79))*T^8 + 1 + O(3^20) + + (2 + 2*3 + 2*3^2 + 2*3^4 + 3^5 + 3^6 + 3^7 + 2*3^15 + O(3^16))*T + + (2*3^3 + 3^5 + 3^6 + 3^7 + 2*3^8 + 2*3^9 + 2*3^10 + + 2*3^11 + 2*3^12 + 2*3^13 + 3^14 + 3^15 + O(3^16))*T^2 + + (3^14 + 2*3^15 + 2*3^16 + 3^17 + 3^18 + O(3^19))*T^3 + + (3^17 + 2*3^18 + 3^19 + 3^20 + 3^21 + O(3^24))*T^4 + + (3^29 + 2*3^32 + O(3^33))*T^5 + + (2*3^44 + O(3^45))*T^6 + + (2*3^59 + O(3^60))*T^7 + + (2*3^78 + O(3^79))*T^8 .. NOTE:: @@ -1301,7 +1426,8 @@ def __init__(self, parent, gexp=None, qexp=None): EXAMPLES:: sage: OverconvergentModularForms(3, 2, 1/6,prec=5).an_element() # indirect doctest - 3-adic overconvergent modular form of weight-character 2 with q-expansion 3*q + 72*q^2 + 810*q^3 + 6096*q^4 + O(q^5) + 3-adic overconvergent modular form of weight-character 2 + with q-expansion 3*q + 72*q^2 + 810*q^3 + 6096*q^4 + O(q^5) """ ModuleElement.__init__(self, parent) @@ -1366,22 +1492,23 @@ def _rmul_(self, x): def prec(self): r""" - Return the series expansion precision of this overconvergent modular - form. (This is not the same as the `p`-adic precision of the - coefficients.) + Return the series expansion precision of this overconvergent modular form. + + This is not the same as the `p`-adic precision of the coefficients. EXAMPLES:: - sage: OverconvergentModularForms(5, 6, 1/3,prec=15).gen(1).prec() + sage: OverconvergentModularForms(5, 6, 1/3, prec=15).gen(1).prec() 15 """ return self.gexp().prec() def is_eigenform(self): r""" - Return True if this is an eigenform. At present this returns False - unless this element was explicitly flagged as an eigenform, using the - _notify_eigen function. + Return ``True`` if this is an eigenform. + + At present this returns ``False`` unless this element was explicitly + flagged as an eigenform, using the method :meth:`_notify_eigen`. EXAMPLES:: @@ -1396,9 +1523,12 @@ def is_eigenform(self): def slope(self): r""" - Return the slope of this eigenform, i.e. the valuation of its - `U_p`-eigenvalue. Raises an error unless this element was explicitly - flagged as an eigenform, using the _notify_eigen function. + Return the slope of this eigenform. + + This is the valuation of its `U_p`-eigenvalue. + + Raises an error unless this element was explicitly + flagged as an eigenform, using the method :meth:`_notify_eigen`. EXAMPLES:: @@ -1419,16 +1549,22 @@ def eigenvalue(self): r""" Return the `U_p`-eigenvalue of this eigenform. - This raises an error unless - this element was explicitly flagged as an eigenform, using the - _notify_eigen function. + This raises an error unless this element was explicitly flagged + as an eigenform, using the method :meth:`_notify_eigen`. EXAMPLES:: sage: M = OverconvergentModularForms(3, 0, 1/2) sage: f = M.eigenfunctions(3)[1] sage: f.eigenvalue() - 3^2 + 3^4 + 2*3^6 + 3^7 + 3^8 + 2*3^9 + 2*3^10 + 3^12 + 3^16 + 2*3^17 + 3^18 + 3^20 + 2*3^21 + 3^22 + 2*3^23 + 3^25 + 3^26 + 2*3^27 + 2*3^29 + 3^30 + 3^31 + 3^32 + 3^33 + 3^34 + 3^36 + 3^40 + 2*3^41 + 3^43 + 3^44 + 3^45 + 3^46 + 3^48 + 3^49 + 3^50 + 2*3^51 + 3^52 + 3^54 + 2*3^57 + 2*3^59 + 3^60 + 3^61 + 2*3^63 + 2*3^66 + 2*3^67 + 3^69 + 2*3^72 + 3^74 + 2*3^75 + 3^76 + 2*3^77 + 2*3^78 + 2*3^80 + 3^81 + 2*3^82 + 3^84 + 2*3^85 + 2*3^86 + 3^87 + 3^88 + 2*3^89 + 2*3^91 + 3^93 + 3^94 + 3^95 + 3^96 + 3^98 + 2*3^99 + O(3^100) + 3^2 + 3^4 + 2*3^6 + 3^7 + 3^8 + 2*3^9 + 2*3^10 + 3^12 + 3^16 + 2*3^17 + + 3^18 + 3^20 + 2*3^21 + 3^22 + 2*3^23 + 3^25 + 3^26 + 2*3^27 + 2*3^29 + + 3^30 + 3^31 + 3^32 + 3^33 + 3^34 + 3^36 + 3^40 + 2*3^41 + 3^43 + 3^44 + + 3^45 + 3^46 + 3^48 + 3^49 + 3^50 + 2*3^51 + 3^52 + 3^54 + 2*3^57 + + 2*3^59 + 3^60 + 3^61 + 2*3^63 + 2*3^66 + 2*3^67 + 3^69 + 2*3^72 + + 3^74 + 2*3^75 + 3^76 + 2*3^77 + 2*3^78 + 2*3^80 + 3^81 + 2*3^82 + + 3^84 + 2*3^85 + 2*3^86 + 3^87 + 3^88 + 2*3^89 + 2*3^91 + 3^93 + 3^94 + + 3^95 + 3^96 + 3^98 + 2*3^99 + O(3^100) sage: M.gen(4).eigenvalue() Traceback (most recent call last): ... @@ -1440,12 +1576,16 @@ def eigenvalue(self): def q_expansion(self, prec=None): r""" - Return the `q`-expansion of self, to as high precision as it is known. + Return the `q`-expansion of ``self``, to as high precision as it is known. EXAMPLES:: sage: OverconvergentModularForms(3, 4, 1/2).gen(0).q_expansion() - 1 - 120/13*q - 1080/13*q^2 - 120/13*q^3 - 8760/13*q^4 - 15120/13*q^5 - 1080/13*q^6 - 41280/13*q^7 - 5400*q^8 - 120/13*q^9 - 136080/13*q^10 - 159840/13*q^11 - 8760/13*q^12 - 263760/13*q^13 - 371520/13*q^14 - 15120/13*q^15 - 561720/13*q^16 - 45360*q^17 - 1080/13*q^18 - 823200/13*q^19 + O(q^20) + 1 - 120/13*q - 1080/13*q^2 - 120/13*q^3 - 8760/13*q^4 - 15120/13*q^5 + - 1080/13*q^6 - 41280/13*q^7 - 5400*q^8 - 120/13*q^9 - 136080/13*q^10 + - 159840/13*q^11 - 8760/13*q^12 - 263760/13*q^13 - 371520/13*q^14 + - 15120/13*q^15 - 561720/13*q^16 - 45360*q^17 - 1080/13*q^18 + - 823200/13*q^19 + O(q^20) """ if prec is None: return self._qexp @@ -1456,17 +1596,27 @@ def q_expansion(self, prec=None): def gexp(self): r""" - Return the formal power series in `g` corresponding to this - overconvergent modular form (so the result is `F` where this modular form - is `E_k^\ast \times F(g)`, where `g` is the appropriately normalised - parameter of `X_0(p)`). + Return the formal power series in `g` corresponding to ``self``. + + If this overconvergent modular form is `E_k^\ast \times F(g)` + where `g` is the appropriately normalised parameter of `X_0(p)`, + the result is `F`. EXAMPLES:: sage: M = OverconvergentModularForms(3, 0, 1/2) sage: f = M.eigenfunctions(3)[1] sage: f.gexp() - (3^-3 + O(3^95))*g + (3^-1 + 1 + 2*3 + 3^2 + 2*3^3 + 3^5 + 3^7 + 3^10 + 3^11 + 3^14 + 3^15 + 3^16 + 2*3^19 + 3^21 + 3^22 + 2*3^23 + 2*3^24 + 3^26 + 2*3^27 + 3^29 + 3^31 + 3^34 + 2*3^35 + 2*3^36 + 3^38 + 2*3^39 + 3^41 + 2*3^42 + 2*3^43 + 2*3^44 + 2*3^46 + 2*3^47 + 3^48 + 2*3^49 + 2*3^50 + 3^51 + 2*3^54 + 2*3^55 + 2*3^56 + 3^57 + 2*3^58 + 2*3^59 + 2*3^60 + 3^61 + 3^62 + 3^63 + 3^64 + 2*3^65 + 3^67 + 3^68 + 2*3^69 + 3^70 + 2*3^71 + 2*3^74 + 3^76 + 2*3^77 + 3^78 + 2*3^79 + 2*3^80 + 3^84 + 2*3^85 + 2*3^86 + 3^88 + 2*3^89 + 3^91 + 3^92 + 2*3^94 + 3^95 + O(3^97))*g^2 + O(g^3) + (3^-3 + O(3^95))*g + + (3^-1 + 1 + 2*3 + 3^2 + 2*3^3 + 3^5 + 3^7 + 3^10 + 3^11 + 3^14 + 3^15 + + 3^16 + 2*3^19 + 3^21 + 3^22 + 2*3^23 + 2*3^24 + 3^26 + 2*3^27 + + 3^29 + 3^31 + 3^34 + 2*3^35 + 2*3^36 + 3^38 + 2*3^39 + 3^41 + 2*3^42 + + 2*3^43 + 2*3^44 + 2*3^46 + 2*3^47 + 3^48 + 2*3^49 + 2*3^50 + 3^51 + + 2*3^54 + 2*3^55 + 2*3^56 + 3^57 + 2*3^58 + 2*3^59 + 2*3^60 + 3^61 + + 3^62 + 3^63 + 3^64 + 2*3^65 + 3^67 + 3^68 + 2*3^69 + 3^70 + 2*3^71 + + 2*3^74 + 3^76 + 2*3^77 + 3^78 + 2*3^79 + 2*3^80 + 3^84 + 2*3^85 + + 2*3^86 + 3^88 + 2*3^89 + 3^91 + 3^92 + 2*3^94 + 3^95 + O(3^97))*g^2 + + O(g^3) """ return self._gexp @@ -1507,11 +1657,13 @@ def prime(self): def _notify_eigen(self, eigenvalue): """ - Flags this element as an eigenform. It then remembers some extra data. + Flag this element as an eigenform. + + It then remembers some extra data. EXAMPLES:: - sage: OverconvergentModularForms(3, 16, 1/3).eigenfunctions(4) # indirect doctest + sage: OverconvergentModularForms(3, 16, 1/3).eigenfunctions(4) # indirect doctest [...] """ self._is_eigen = True @@ -1520,9 +1672,10 @@ def _notify_eigen(self, eigenvalue): def is_integral(self): r""" - Test whether or not this element has `q`-expansion coefficients that - are `p`-adically integral. This should always be the case with eigenfunctions, but sometimes - if n is very large this breaks down for unknown reasons! + Test whether this element has `q`-expansion coefficients that are `p`-adically integral. + + This should always be the case with eigenfunctions, but sometimes + if `n` is very large this breaks down for unknown reasons! EXAMPLES:: @@ -1552,7 +1705,7 @@ def _repr_(self) -> str: def _richcmp_(self, other, op): r""" - Compare self to other. + Compare ``self`` to ``other``. EXAMPLES:: @@ -1566,7 +1719,7 @@ def _richcmp_(self, other, op): def r_ord(self, r): r""" - The `p`-adic valuation of the norm of self on the `r`-overconvergent region. + The `p`-adic valuation of the norm of ``self`` on the `r`-overconvergent region. EXAMPLES:: @@ -1592,8 +1745,9 @@ def r_ord(self, r): def valuation(self): r""" - Return the `p`-adic valuation of this form (i.e. the minimum of the - `p`-adic valuations of its coordinates). + Return the `p`-adic valuation of this form. + + This is the minimum of the `p`-adic valuations of its coordinates. EXAMPLES:: @@ -1667,8 +1821,10 @@ def weight(self): def additive_order(self): r""" - Return the additive order of this element (required attribute for all - elements deriving from sage.modules.ModuleElement). + Return the additive order of this element. + + This implements a required method for all + elements deriving from :class:`sage.modules.ModuleElement`. EXAMPLES:: @@ -1688,22 +1844,26 @@ def additive_order(self): def base_extend(self, R): r""" - Return a copy of self but with coefficients in the given ring. + Return a copy of ``self`` but with coefficients in the given ring. EXAMPLES:: sage: M = OverconvergentModularForms(7, 10, 1/2, prec=5) sage: f = M.1 sage: f.base_extend(Qp(7, 4)) - 7-adic overconvergent modular form of weight-character 10 with q-expansion (7 + O(7^5))*q + (6*7 + 4*7^2 + 7^3 + 6*7^4 + O(7^5))*q^2 + (5*7 + 5*7^2 + 7^4 + O(7^5))*q^3 + (7^2 + 4*7^3 + 3*7^4 + 2*7^5 + O(7^6))*q^4 + O(q^5) + 7-adic overconvergent modular form of weight-character 10 with q-expansion + (7 + O(7^5))*q + (6*7 + 4*7^2 + 7^3 + 6*7^4 + O(7^5))*q^2 + + (5*7 + 5*7^2 + 7^4 + O(7^5))*q^3 + (7^2 + 4*7^3 + 3*7^4 + 2*7^5 + + O(7^6))*q^4 + O(q^5) """ S = self.parent().base_extend(R) return S(self) def __pari__(self): r""" - Return the Pari object corresponding to self, which is just the - `q`-expansion of self as a formal power series. + Return the Pari object corresponding to ``self``. + + This is just the `q`-expansion of ``self`` as a formal power series. EXAMPLES:: diff --git a/src/sage/modular/pollack_stevens/dist.pyx b/src/sage/modular/pollack_stevens/dist.pyx index 1f79133f2f5..cd1ce2a722f 100644 --- a/src/sage/modular/pollack_stevens/dist.pyx +++ b/src/sage/modular/pollack_stevens/dist.pyx @@ -26,26 +26,26 @@ REFERENCES: # the License, or (at your option) any later version. # https://www.gnu.org/licenses/ # **************************************************************************** +import operator -from sage.structure.richcmp cimport richcmp_not_equal, rich_to_bool -from sage.rings.integer_ring import ZZ -from sage.rings.rational_field import QQ -from sage.rings.power_series_ring import PowerSeriesRing -from sage.rings.finite_rings.integer_mod_ring import Zmod from sage.arith.misc import binomial, bernoulli -from sage.matrix.matrix cimport Matrix +from sage.categories.fields import Fields from sage.matrix.constructor import matrix -from sage.structure.element cimport Element -import operator -from sage.rings.padics.padic_generic import pAdicGeneric -from sage.rings.integer cimport Integer +from sage.matrix.matrix cimport Matrix from sage.misc.verbose import verbose +from sage.modular.pollack_stevens.sigma0 import Sigma0 +from sage.rings.finite_rings.integer_mod_ring import Zmod from sage.rings.infinity import Infinity +from sage.rings.integer cimport Integer +from sage.rings.integer_ring import ZZ +from sage.rings.padics.padic_generic import pAdicGeneric +from sage.rings.power_series_ring import PowerSeriesRing +from sage.rings.rational_field import QQ +from sage.structure.element cimport Element +from sage.structure.richcmp cimport richcmp_not_equal, rich_to_bool #from sage.libs.flint.ulong_extras cimport * -from sage.modular.pollack_stevens.sigma0 import Sigma0 - cdef long overflow = 1 << (4 * sizeof(long) - 1) cdef long underflow = -overflow cdef long maxordp = (1L << (sizeof(long) * 8 - 2)) - 1 @@ -1167,7 +1167,7 @@ cdef class Dist_vector(Dist): p = self.parent().prime() cdef Dist_vector ans if p == 0: - if R.is_field(): + if R in Fields(): ans = self._new_c() ans.ordp = 0 ans._moments = V(v) diff --git a/src/sage/modular/pollack_stevens/distributions.py b/src/sage/modular/pollack_stevens/distributions.py index f33b4022842..422f319ed6e 100644 --- a/src/sage/modular/pollack_stevens/distributions.py +++ b/src/sage/modular/pollack_stevens/distributions.py @@ -31,32 +31,31 @@ (1 + O(11^5), 2 + O(11^4), 3 + O(11^3), 4 + O(11^2), 5 + O(11)) """ -# **************************************************************************** +# ************************************************************************* # Copyright (C) 2012 Robert Pollack # # 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.categories.fields import Fields +from sage.categories.modules import Modules +from sage.misc.cachefunc import cached_method from sage.misc.lazy_import import lazy_import from sage.modules.module import Module -from sage.structure.parent import Parent -from sage.rings.rational_field import QQ from sage.rings.integer_ring import ZZ -from sage.misc.cachefunc import cached_method -from sage.categories.modules import Modules -from sage.structure.factory import UniqueFactory - +from sage.rings.rational_field import QQ from sage.rings.ring import Ring +from sage.structure.factory import UniqueFactory +from sage.structure.parent import Parent +from .sigma0 import _default_adjuster lazy_import('sage.modular.pollack_stevens.dist', 'get_dist_classes') lazy_import('sage.rings.padics.factory', ['ZpCA', 'QpCR']) lazy_import('sage.rings.padics.padic_generic', 'pAdicGeneric') -from .sigma0 import _default_adjuster - class OverconvergentDistributions_factory(UniqueFactory): """ @@ -283,7 +282,7 @@ def __init__(self, k, p=None, prec_cap=None, base=None, character=None, """ if not isinstance(base, Ring): raise TypeError("base must be a ring") - #from sage.rings.padics.pow_computer import PowComputer + # from sage.rings.padics.pow_computer import PowComputer # should eventually be the PowComputer on ZpCA once that uses longs. Dist, WeightKAction = get_dist_classes(p, prec_cap, base, self.is_symk(), implementation) @@ -317,9 +316,9 @@ def _element_constructor_(self, val, **kwargs): sage: v = V([1,2,3,4,5,6,7]); v (1, 2, 3, 4, 5, 6, 7) """ - ordp = kwargs.get('ord',0) - check = kwargs.get('check',True) - normalize = kwargs.get('normalize',True) + ordp = kwargs.get('ord', 0) + check = kwargs.get('check', True) + normalize = kwargs.get('normalize', True) return self.Element(val, self, ordp, check, normalize) def _coerce_map_from_(self, other): @@ -409,13 +408,14 @@ def prime(self): def weight(self): """ - Return the weight of this distribution space. The standard - caveat applies, namely that the weight of `Sym^k` is - defined to be `k`, not `k+2`. + Return the weight of this distribution space. + + The standard caveat applies, namely that the weight of `Sym^k` + is defined to be `k`, not `k+2`. OUTPUT: - - nonnegative integer + nonnegative integer EXAMPLES:: @@ -472,7 +472,7 @@ def lift(self, p=None, M=None, new_base_ring=None): """ if self._character is not None: if self._character.base_ring() != QQ: - # need to change coefficient ring for character + # need to change coefficient ring for character raise NotImplementedError if M is None: M = self._prec_cap + 1 @@ -689,7 +689,7 @@ def _repr_(self): elif self.base_ring() is ZZ: V = 'Z^2' elif isinstance(self.base_ring(), pAdicGeneric) and self.base_ring().degree() == 1: - if self.base_ring().is_field(): + if self.base_ring() in Fields(): V = 'Q_%s^2' % self._p else: V = 'Z_%s^2' % self._p diff --git a/src/sage/modular/quasimodform/ring.py b/src/sage/modular/quasimodform/ring.py index dcdf8e470c2..5a91a837ecd 100644 --- a/src/sage/modular/quasimodform/ring.py +++ b/src/sage/modular/quasimodform/ring.py @@ -42,6 +42,8 @@ sage: QM = QuasiModularForms(1); QM Ring of Quasimodular Forms for Modular Group SL(2,Z) over Rational Field + sage: QM.category() + Category of commutative graded algebras over Rational Field sage: QM.gens() [1 - 24*q - 72*q^2 - 96*q^3 - 168*q^4 - 144*q^5 + O(q^6), 1 + 240*q + 2160*q^2 + 6720*q^3 + 17520*q^4 + 30240*q^5 + O(q^6), @@ -251,20 +253,21 @@ def __init__(self, group=1, base_ring=QQ, name='E2'): """ if not isinstance(name, str): raise TypeError("`name` must be a string") - #check if the group is SL2(Z) + # check if the group is SL2(Z) if isinstance(group, (int, Integer)): group = Gamma0(group) elif not is_CongruenceSubgroup(group): raise ValueError("Group (=%s) should be a congruence subgroup" % group) - #Check if the base ring is the rationnal field + # Check if the base ring is the rational field if base_ring != QQ: raise NotImplementedError("base ring other than Q are not yet supported for quasimodular forms ring") self.__group = group self.__modular_forms_subring = ModularFormsRing(group, base_ring) self.__polynomial_subring = self.__modular_forms_subring[name] - Parent.__init__(self, base=base_ring, category=GradedAlgebras(base_ring)) + cat = GradedAlgebras(base_ring).Commutative() + Parent.__init__(self, base=base_ring, category=cat) def group(self): r""" @@ -421,15 +424,15 @@ def _element_constructor_(self, datum): NotImplementedError: conversion from q-expansion not yet implemented """ if isinstance(datum, list): - if len(datum) == 0: + if not datum: raise ValueError("the given list should be non-empty") for idx, f in enumerate(datum): if not isinstance(f, (GradedModularFormElement, ModularFormElement)): raise ValueError("one list element is not a modular form") - datum[idx] = self.__modular_forms_subring(f) #to ensure that every forms is a GradedModularFormElement + datum[idx] = self.__modular_forms_subring(f) # to ensure that every form is a GradedModularFormElement datum = self.__polynomial_subring(datum) elif isinstance(datum, (GradedModularFormElement, ModularFormElement)): - datum = self.__modular_forms_subring(datum) # GradedModularFormElement + datum = self.__modular_forms_subring(datum) # GradedModularFormElement datum = self.__polynomial_subring(datum) elif isinstance(datum, Polynomial): datum = self.__polynomial_subring(datum.coefficients(sparse=False)) @@ -489,7 +492,7 @@ def gens(self): gen_list.append(self(f)) return gen_list - generators = gens # alias + generators = gens # alias def ngens(self): r""" @@ -690,7 +693,7 @@ def polynomial_ring(self, names=None): # the letters E and S are reserved for basis elements of the # Eisenstein subspaces and cuspidal subspaces respectively. iter_names = (product(letters, repeat=r) - for r in range(1, len(same_weights)//len(letters) + 2)) + for r in range(1, len(same_weights)//len(letters) + 2)) iter_names = chain(*iter_names) for k in same_weights: form = next(gens) @@ -714,7 +717,7 @@ def polynomial_ring(self, names=None): else: name = "".join(next(iter_names)) + str(k) names.append(name) - weights.insert(0, 2) # add the weight 2 Eisenstein series + weights.insert(0, 2) # add the weight 2 Eisenstein series return PolynomialRing(self.base_ring(), len(weights), names, order=TermOrder('wdeglex', weights)) @@ -777,7 +780,7 @@ def from_polynomial(self, polynomial): nb_var = poly_parent.ngens() if nb_var > self.ngens(): raise ValueError("the number of variables (%s) of the given polynomial cannot exceed the number of generators (%s) of the quasimodular forms ring" % (nb_var, self.ngens())) - gens_dict = {poly_parent.gen(i):self.gen(i) for i in range(0, nb_var)} + gens_dict = {poly_parent.gen(i): self.gen(i) for i in range(nb_var)} return self(polynomial.subs(gens_dict)) def basis_of_weight(self, weight): @@ -818,9 +821,9 @@ def basis_of_weight(self, weight): E2 = self.weight_2_eisenstein_series() M = self.__modular_forms_subring E2_pow = self.one() - for j in range(weight//2): - basis += [f*E2_pow for f - in M.modular_forms_of_weight(weight - 2*j).basis()] + for j in range(weight // 2): + basis.extend(f * E2_pow + for f in M.modular_forms_of_weight(weight - 2*j).basis()) E2_pow *= E2 if not weight % 2: basis.append(E2_pow) diff --git a/src/sage/modular/ssmod/ssmod.py b/src/sage/modular/ssmod/ssmod.py index d2d2d717cbc..e00da45a87e 100644 --- a/src/sage/modular/ssmod/ssmod.py +++ b/src/sage/modular/ssmod/ssmod.py @@ -69,6 +69,7 @@ # **************************************************************************** from sage.arith.misc import kronecker, next_prime +from sage.categories.fields import Fields from sage.matrix.matrix_space import MatrixSpace from sage.misc.lazy_import import lazy_import from sage.modular.arithgroup.all import Gamma0 @@ -333,7 +334,7 @@ def supersingular_j(FF): - Iftikhar Burhanuddin -- burhanud@usc.edu """ - if not FF.is_field() or not FF.is_finite(): + if FF not in Fields().Finite(): raise ValueError("%s is not a finite field" % FF) prime = FF.characteristic() if not Integer(prime).is_prime(): @@ -417,7 +418,7 @@ def __init__(self, prime=2, level=1, base_ring=ZZ): HeckeModule_free_module.__init__(self, base_ring, prime * level, weight=2) - def _repr_(self): + def _repr_(self) -> str: """ String representation of self. @@ -429,7 +430,7 @@ def _repr_(self): return "Module of supersingular points on X_0(%s)/F_%s over %s" % ( self.__level, self.__prime, self.base_ring()) - def __richcmp__(self, other, op): + def __richcmp__(self, other, op) -> bool: r""" Compare ``self`` to ``other``. @@ -675,7 +676,7 @@ def supersingular_points(self): dim = dimension_supersingular_module(prime, level) - pos = int(0) + pos = 0 # using list to keep track of explored nodes using pos ss_points = [jinv] @@ -697,7 +698,7 @@ def supersingular_points(self): # root finding (??) neighbors = Phi2_quad(X, ss_points[j_prev], ss_points[pos]).roots() - for (xj, ej) in neighbors: + for xj, ej in neighbors: if xj not in ss_points_dic: j = len(ss_points) ss_points += [xj] @@ -710,7 +711,7 @@ def supersingular_points(self): if pos != 0: # also record the root from j_prev T2_matrix[pos, j_prev] += 1 - pos += int(1) + pos += 1 self.__hecke_matrices[2] = T2_matrix return (ss_points, ss_points_dic) @@ -836,7 +837,7 @@ def hecke_matrix(self, L): Fp2 = self.__finite_field h = len(SS) R = self.base_ring() - T_L = MatrixSpace(R, h)(0) + T_L = MatrixSpace(R, h)(0) # mutable S, X = Fp2['x'].objgen() for i in range(len(SS)): diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index 6e2c9e4dc1d..f901b28ba3c 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -509,17 +509,43 @@ def FreeModule(base_ring, rank_or_basis_keys=None, sparse=False, inner_product_m sage: FreeModule(QQ, ['a', 2, 3, 4], with_basis=None) Traceback (most recent call last): ... - NotImplementedError: FiniteRankFreeModule only supports integer ranges as basis_keys, got ['a', 2, 3, 4] + NotImplementedError: FiniteRankFreeModule only supports integer ranges + as basis_keys, got ['a', 2, 3, 4] sage: FreeModule(QQ, [1, 3, 5], with_basis=None) Traceback (most recent call last): ... - NotImplementedError: FiniteRankFreeModule only supports integer ranges as basis_keys, got [1, 3, 5] + NotImplementedError: FiniteRankFreeModule only supports integer ranges + as basis_keys, got [1, 3, 5] + + sage: FreeModule(ZZ, rank=3, basis_keys=['c','d']) + Traceback (most recent call last): + ... + ValueError: inconsistent rank: should be cardinality of ['c', 'd'] but got 3 + sage: FreeModule(QQ, ['a', 'b'], rank=2) + Free module generated by {'a', 'b'} over Rational Field + sage: FreeModule(QQ, 2, basis_keys=['a', 'b']) + Free module generated by {'a', 'b'} over Rational Field + sage: FreeModule(QQ, ['a', 'b'], rank=3) + Traceback (most recent call last): + ... + ValueError: inconsistent rank: should be cardinality of ['a', 'b'] but got 3 """ if rank_or_basis_keys is not None: try: - rank = sage.rings.integer_ring.ZZ(rank_or_basis_keys) + n = sage.rings.integer_ring.ZZ(rank_or_basis_keys) except (TypeError, ValueError): + if basis_keys is not None: + raise ValueError("duplicate values for basis_keys") basis_keys = rank_or_basis_keys + else: + if rank is not None: + raise ValueError("duplicate values for rank") + rank = n + + if rank is not None and basis_keys is not None and rank != len(basis_keys): + raise ValueError(f"inconsistent rank: should be cardinality of {basis_keys} " + f"but got {rank}") + if not with_basis: if inner_product_matrix is not None: raise NotImplementedError @@ -539,12 +565,15 @@ def FreeModule(base_ring, rank_or_basis_keys=None, sparse=False, inner_product_m return FiniteRankFreeModule(base_ring, rank, start_index=start_index, **args) return FiniteRankFreeModule(base_ring, rank, **args) elif with_basis == 'standard': - if rank is not None: + if rank is not None and basis_keys is None: return FreeModuleFactory_with_standard_basis(base_ring, rank, sparse, inner_product_matrix, **args) else: if inner_product_matrix is not None: raise NotImplementedError + if rank is not None and rank != len(basis_keys): + raise ValueError(f'inconsistent basis_keys: should be of cardinality {rank}, ' + f'got {basis_keys}') from sage.combinat.free_module import CombinatorialFreeModule return CombinatorialFreeModule(base_ring, basis_keys, **args) else: diff --git a/src/sage/modules/free_module_morphism.py b/src/sage/modules/free_module_morphism.py index 147dbe5b57a..6974b31f18c 100644 --- a/src/sage/modules/free_module_morphism.py +++ b/src/sage/modules/free_module_morphism.py @@ -41,6 +41,7 @@ # be coercible into vector space of appropriate dimension. import sage.modules.free_module as free_module + from sage.categories.morphism import Morphism from sage.modules import free_module_homspace, matrix_morphism from sage.structure.richcmp import rich_to_bool, richcmp @@ -49,18 +50,29 @@ def is_FreeModuleMorphism(x): """ + This function is deprecated. + EXAMPLES:: sage: V = ZZ^2; f = V.hom([V.1, -2*V.0]) sage: sage.modules.free_module_morphism.is_FreeModuleMorphism(f) + doctest:warning... + DeprecationWarning: is_FreeModuleMorphism is deprecated; + use isinstance(..., FreeModuleMorphism) or categories instead + See https://github.com/sagemath/sage/issues/37731 for details. True sage: sage.modules.free_module_morphism.is_FreeModuleMorphism(0) False """ + from sage.misc.superseded import deprecation + deprecation(37731, + "is_FreeModuleMorphism is deprecated; " + "use isinstance(..., FreeModuleMorphism) or categories instead") return isinstance(x, FreeModuleMorphism) class FreeModuleMorphism(matrix_morphism.MatrixMorphism): + def __init__(self, parent, A, side="left"): """ INPUT: @@ -635,55 +647,6 @@ def eigenspaces(self, extend=True): return [(vec[0], Sequence(vec[1]).universe().subspace(vec[1])) for vec in ev] - def minimal_polynomial(self, var='x'): - r""" - Computes the minimal polynomial. - - ``minpoly()`` and ``minimal_polynomial()`` are the same method. - - INPUT: - - - ``var`` - string (default: 'x') a variable name - - OUTPUT: - - polynomial in var - the minimal polynomial of the endomorphism. - - EXAMPLES: - - Compute the minimal polynomial, and check it. :: - - sage: V = GF(7)^3 - sage: H = V.Hom(V)([[0,1,2], [-1,0,3], [2,4,1]]) - sage: H - Vector space morphism represented by the matrix: - [0 1 2] - [6 0 3] - [2 4 1] - Domain: Vector space of dimension 3 over Finite Field of size 7 - Codomain: Vector space of dimension 3 over Finite Field of size 7 - - sage: H.minpoly() # needs sage.libs.pari - x^3 + 6*x^2 + 6*x + 1 - - sage: H.minimal_polynomial() # needs sage.libs.pari - x^3 + 6*x^2 + 6*x + 1 - - sage: H^3 + (H^2)*6 + H*6 + 1 - Vector space morphism represented by the matrix: - [0 0 0] - [0 0 0] - [0 0 0] - Domain: Vector space of dimension 3 over Finite Field of size 7 - Codomain: Vector space of dimension 3 over Finite Field of size 7 - """ - if self.is_endomorphism(): - return self.matrix().minpoly(var) - else: - raise TypeError("not an endomorphism") - - minpoly = minimal_polynomial - class BaseIsomorphism1D(Morphism): """ diff --git a/src/sage/modules/matrix_morphism.py b/src/sage/modules/matrix_morphism.py index dc4b3817eca..ce2b88ede82 100644 --- a/src/sage/modules/matrix_morphism.py +++ b/src/sage/modules/matrix_morphism.py @@ -19,8 +19,6 @@ [1 0 0] [0 1 0] [0 0 1] - sage: is_MatrixMorphism(m) - True sage: m.charpoly('x') # needs sage.libs.pari x^3 - 3*x^2 + 3*x - 1 sage: m.base_ring() @@ -53,6 +51,7 @@ import sage.categories.morphism import sage.categories.homset +from sage.categories.finite_dimensional_modules_with_basis import FiniteDimensionalModulesWithBasis from sage.structure.all import Sequence, parent from sage.structure.richcmp import richcmp, op_NE, op_EQ @@ -61,18 +60,39 @@ def is_MatrixMorphism(x): """ Return True if x is a Matrix morphism of free modules. + This function is deprecated. + EXAMPLES:: sage: V = ZZ^2; phi = V.hom([3*V.0, 2*V.1]) sage: sage.modules.matrix_morphism.is_MatrixMorphism(phi) + doctest:warning... + DeprecationWarning: is_MatrixMorphism is deprecated; + use isinstance(..., MatrixMorphism_abstract) or categories instead + See https://github.com/sagemath/sage/issues/37731 for details. True sage: sage.modules.matrix_morphism.is_MatrixMorphism(3) False """ + from sage.misc.superseded import deprecation + deprecation(37731, + "is_MatrixMorphism is deprecated; " + "use isinstance(..., MatrixMorphism_abstract) or categories instead") return isinstance(x, MatrixMorphism_abstract) class MatrixMorphism_abstract(sage.categories.morphism.Morphism): + + # Copy in methods that delegate to self.matrix. + # This is needed because MatrixMorphism_abstract is subclassed + # for use with parents that are merely set up as additive abelian groups, + # but not as ZZ-modules; see sage.modular.abvar. + + characteristic_polynomial = charpoly = FiniteDimensionalModulesWithBasis.Homsets.Endset.ElementMethods.characteristic_polynomial + det = determinant = FiniteDimensionalModulesWithBasis.Homsets.Endset.ElementMethods.determinant + fcp = FiniteDimensionalModulesWithBasis.Homsets.Endset.ElementMethods.fcp + trace = FiniteDimensionalModulesWithBasis.Homsets.Endset.ElementMethods.trace + def __init__(self, parent, side='left'): """ INPUT: @@ -799,35 +819,6 @@ def base_ring(self): """ return self.domain().base_ring() - def characteristic_polynomial(self, var='x'): - r""" - Return the characteristic polynomial of this endomorphism. - - ``characteristic_polynomial`` and ``char_poly`` are the same method. - - INPUT: - - - var -- variable - - EXAMPLES:: - - sage: V = ZZ^2; phi = V.hom([V.0+V.1, 2*V.1]) - sage: phi.characteristic_polynomial() - x^2 - 3*x + 2 - sage: phi.charpoly() - x^2 - 3*x + 2 - sage: phi.matrix().charpoly() - x^2 - 3*x + 2 - sage: phi.charpoly('T') - T^2 - 3*T + 2 - """ - if not self.is_endomorphism(): - raise ArithmeticError("charpoly only defined for endomorphisms " - "(i.e., domain = range)") - return self.matrix().charpoly(var) - - charpoly = characteristic_polynomial - def decomposition(self, *args, **kwds): """ Return decomposition of this endomorphism, i.e., sequence of @@ -877,46 +868,6 @@ def decomposition(self, *args, **kwds): check=False) for V, _ in E], cr=True, check=False) - def trace(self): - r""" - Return the trace of this endomorphism. - - EXAMPLES:: - - sage: V = ZZ^2; phi = V.hom([V.0 + V.1, 2*V.1]) - sage: phi.trace() - 3 - """ - return self._matrix.trace() - - def det(self): - """ - Return the determinant of this endomorphism. - - EXAMPLES:: - - sage: V = ZZ^2; phi = V.hom([V.0 + V.1, 2*V.1]) - sage: phi.det() - 2 - """ - if not self.is_endomorphism(): - raise ArithmeticError("matrix morphism must be an endomorphism") - return self.matrix().determinant() - - def fcp(self, var='x'): - """ - Return the factorization of the characteristic polynomial. - - EXAMPLES:: - - sage: V = ZZ^2; phi = V.hom([V.0 + V.1, 2*V.1]) - sage: phi.fcp() # needs sage.libs.pari - (x - 2) * (x - 1) - sage: phi.fcp('T') # needs sage.libs.pari - (T - 2) * (T - 1) - """ - return self.charpoly(var).factor() - def kernel(self): """ Compute the kernel of this morphism. @@ -1366,7 +1317,7 @@ def is_equal_function(self, other): - Rob Beezer (2011-07-15) """ - if not is_MatrixMorphism(other): + if not isinstance(other, MatrixMorphism_abstract): msg = 'can only compare to a matrix morphism, not {0}' raise TypeError(msg.format(other)) if self.domain() != other.domain(): @@ -1656,7 +1607,7 @@ def matrix(self, side=None): INPUT: - - ``side`` -- (default: ``'None'``) the side of the matrix + - ``side`` -- (default: ``None``) the side of the matrix where a vector is placed to effect the morphism (function) OUTPUT: diff --git a/src/sage/modules/vector_space_homspace.py b/src/sage/modules/vector_space_homspace.py index b815051171a..475e70ed00f 100644 --- a/src/sage/modules/vector_space_homspace.py +++ b/src/sage/modules/vector_space_homspace.py @@ -368,14 +368,14 @@ def __call__(self, A, check=True, **kwds): Previously the above code resulted in a :class:`TypeError` because the dimensions of the matrix were incorrect. """ - from .vector_space_morphism import is_VectorSpaceMorphism, VectorSpaceMorphism + from .vector_space_morphism import VectorSpaceMorphism D = self.domain() C = self.codomain() side = kwds.get("side", "left") from sage.structure.element import is_Matrix if is_Matrix(A): pass - elif is_VectorSpaceMorphism(A): + elif isinstance(A, VectorSpaceMorphism): A = A.matrix() elif callable(A): try: diff --git a/src/sage/modules/vector_space_morphism.py b/src/sage/modules/vector_space_morphism.py index 6585da0b047..dba263de586 100644 --- a/src/sage/modules/vector_space_morphism.py +++ b/src/sage/modules/vector_space_morphism.py @@ -784,6 +784,8 @@ def is_VectorSpaceMorphism(x) -> bool: r""" Returns ``True`` if ``x`` is a vector space morphism (a linear transformation). + This function is deprecated. + INPUT: ``x`` - anything @@ -797,10 +799,18 @@ def is_VectorSpaceMorphism(x) -> bool: sage: V = QQ^2; f = V.hom([V.1,-2*V.0]) sage: sage.modules.vector_space_morphism.is_VectorSpaceMorphism(f) + doctest:warning... + DeprecationWarning: is_VectorSpaceMorphism is deprecated; + use isinstance(..., VectorSpaceMorphism) or categories instead + See https://github.com/sagemath/sage/issues/37731 for details. True sage: sage.modules.vector_space_morphism.is_VectorSpaceMorphism('junk') False """ + from sage.misc.superseded import deprecation + deprecation(37731, + "is_VectorSpaceMorphism is deprecated; " + "use isinstance(..., VectorSpaceMorphism) or categories instead") return isinstance(x, VectorSpaceMorphism) diff --git a/src/sage/modules/with_basis/indexed_element.pyx b/src/sage/modules/with_basis/indexed_element.pyx index c71024d2079..1e8c8d9fb75 100644 --- a/src/sage/modules/with_basis/indexed_element.pyx +++ b/src/sage/modules/with_basis/indexed_element.pyx @@ -372,6 +372,16 @@ cdef class IndexedFreeModuleElement(ModuleElement): sage: E2 = L.exterior_power(2) sage: ascii_art(E2.an_element()) 2*()/\(5,6,7) + 2*()/\(5,7,6) + 3*()/\(1,2)(3,4) + + We can also get the ascii art when ``one_basis`` + is ``NotImplemented``:: + + sage: TL = TemperleyLiebAlgebra(8, -1, QQ) + sage: C = TL.cellular_basis() + sage: ascii_art(list(C.basis())[0]) + C + ( .-. .-. .-. .-. .-. .-. .-. .-. ) + ( 0, o o o o o o o o, o o o o o o o o ) """ from sage.misc.repr import coeff_repr terms = self._sorted_items_for_printing() @@ -390,8 +400,8 @@ cdef class IndexedFreeModuleElement(ModuleElement): one_basis = None try: - if self.parent().one_basis is not NotImplemented: - one_basis = self.parent().one_basis() + if self._parent.one_basis is not NotImplemented: + one_basis = self._parent.one_basis() except (AttributeError, NotImplementedError, ValueError, TypeError): pass @@ -457,6 +467,16 @@ cdef class IndexedFreeModuleElement(ModuleElement): sage: E2 = L.exterior_power(2) sage: unicode_art(E2.an_element()) 2*()∧(5,6,7) + 2*()∧(5,7,6) + 3*()∧(1,2)(3,4) + + We can also get the unicode art when ``one_basis`` + is ``NotImplemented``:: + + sage: TL = TemperleyLiebAlgebra(8, -1, QQ) + sage: C = TL.cellular_basis() + sage: unicode_art(list(C.basis())[0]) + C + ⎛ ╭─╮ ╭─╮ ╭─╮ ╭─╮ ╭─╮ ╭─╮ ╭─╮ ╭─╮ ⎞ + ⎝ 0, ⚬ ⚬ ⚬ ⚬ ⚬ ⚬ ⚬ ⚬, ⚬ ⚬ ⚬ ⚬ ⚬ ⚬ ⚬ ⚬ ⎠ """ from sage.misc.repr import coeff_repr terms = self._sorted_items_for_printing() @@ -475,8 +495,8 @@ cdef class IndexedFreeModuleElement(ModuleElement): one_basis = None try: - if self.parent().one_basis is not NotImplemented: - one_basis = self.parent().one_basis() + if self._parent.one_basis is not NotImplemented: + one_basis = self._parent.one_basis() except (AttributeError, NotImplementedError, ValueError, TypeError): pass diff --git a/src/sage/modules/with_basis/representation.py b/src/sage/modules/with_basis/representation.py index 0e1e081c671..c484cd657d6 100644 --- a/src/sage/modules/with_basis/representation.py +++ b/src/sage/modules/with_basis/representation.py @@ -1420,7 +1420,6 @@ def _action_on_basis(self, g, b, vec_on_left): return self.element_class(self, data) - class RegularRepresentation(Representation): r""" The regular representation of a semigroup. @@ -1503,7 +1502,6 @@ def _right_on_basis(self, g, m): return self.monomial(m * g) - class TrivialRepresentation(Representation_abstract): """ The trivial representation of a semigroup. diff --git a/src/sage/numerical/backends/scip_backend.pyx b/src/sage/numerical/backends/scip_backend.pyx index ad53659857b..4b9fb79928f 100644 --- a/src/sage/numerical/backends/scip_backend.pyx +++ b/src/sage/numerical/backends/scip_backend.pyx @@ -1274,7 +1274,7 @@ cdef class SCIPBackend(GenericBackend): EXAMPLES: sage: from sage.numerical.backends.generic_backend import get_solver - sage: lp = get_solver(solver="SCIP") + sage: p = get_solver(solver="SCIP") sage: p.solver_parameter("limits/time", 1) sage: p.solver_parameter("limits/time") 1.0 diff --git a/src/sage/numerical/mip.pyx b/src/sage/numerical/mip.pyx index 368b9e4b0e3..c8aecbf6482 100644 --- a/src/sage/numerical/mip.pyx +++ b/src/sage/numerical/mip.pyx @@ -2852,7 +2852,7 @@ cdef class MixedIntegerLinearProgram(SageObject): The command :: - sage: p = MixedIntegerLinearProgram(solver="CPLEX") # optional - CPLEX + sage: p = MixedIntegerLinearProgram(solver="CPLEX") # optional - CPLEX sage: p.solver_parameter("CPX_PARAM_TILIM", 60) # optional - CPLEX works as intended. diff --git a/src/sage/numerical/optimize.py b/src/sage/numerical/optimize.py index 54262183b1b..1d8e9fafd57 100644 --- a/src/sage/numerical/optimize.py +++ b/src/sage/numerical/optimize.py @@ -346,7 +346,7 @@ def minimize(func, x0, gradient=None, hessian=None, algorithm="default", sage: vars = var('x y z') # needs sage.symbolic sage: f = 100*(y-x^2)^2 + (1-x)^2 + 100*(z-y^2)^2 + (1-y)^2 # needs sage.symbolic - sage: minimize(f, [.1,.3,.4]) # abs tol 1e-6 # needs sage.symbolic + sage: minimize(f, [.1,.3,.4]) # abs tol 1e-6 # needs sage.symbolic (1.0, 1.0, 1.0) Try the newton-conjugate gradient method; the gradient and hessian are diff --git a/src/sage/plot/animate.py b/src/sage/plot/animate.py index 071bcd92622..b4b20e83754 100644 --- a/src/sage/plot/animate.py +++ b/src/sage/plot/animate.py @@ -154,6 +154,7 @@ def animate(frames, **kwds): """ return Animation(frames, **kwds) + class Animation(WithEqualityById, SageObject): r""" Return an animation of a sequence of plots of objects. @@ -1000,7 +1001,7 @@ def ffmpeg(self, savefile=None, show_path=False, output_format=None, # -loop_output option can be added with the # ffmpeg_options argument. if iterations is not None: - loop_cmd = '-loop {0} '.format(iterations) + loop_cmd = f'-loop {iterations} ' else: loop_cmd = '' # A pix_fmt value is required for some but not all @@ -1008,10 +1009,10 @@ def ffmpeg(self, savefile=None, show_path=False, output_format=None, # prevent sage from adding this option, and it may be # controlled separately through ffmpeg_options. if pix_fmt is not None: - pix_fmt_cmd = '-pix_fmt {0} '.format(pix_fmt) + pix_fmt_cmd = f'-pix_fmt {pix_fmt} ' else: pix_fmt_cmd = '' - ffmpeg_options += ' {0}{1}'.format(pix_fmt_cmd,loop_cmd) + ffmpeg_options += f' {pix_fmt_cmd}{loop_cmd}' if delay is not None and output_format != '.mpeg' and output_format != '.mpg': early_options += ' -r %s ' % int(100/delay) savefile = os.path.abspath(savefile) @@ -1023,7 +1024,7 @@ def ffmpeg(self, savefile=None, show_path=False, output_format=None, # afterwards. Hence 'early_options' and 'ffmpeg_options' # The `-nostdin` is needed to avoid the command to hang, see # https://stackoverflow.com/questions/16523746/ffmpeg-hangs-when-run-in-background - cmd = 'cd %s; ffmpeg -nostdin -y -f image2 %s -i %s %s %s' % ( + cmd = 'cd {}; ffmpeg -nostdin -y -f image2 {} -i {} {} {}'.format( shlex.quote(pngdir), early_options, shlex.quote(pngs), ffmpeg_options, shlex.quote(savefile)) from subprocess import check_call, CalledProcessError, PIPE try: @@ -1254,7 +1255,7 @@ def interactive(self, **kwds): except (AttributeError, TypeError): frame = None if not isinstance(frame, Graphics3d): - raise TypeError("Could not convert frame {} to Graphics3d".format(i)) + raise TypeError(f"Could not convert frame {i} to Graphics3d") g3d_frames.append(frame) # Give preference to this method's keyword arguments over those provided # to animate or the constructor. @@ -1265,7 +1266,7 @@ def interactive(self, **kwds): return KeyframeAnimationGroup(g3d_frames, **kwds) -class APngAssembler(): +class APngAssembler: r""" Builds an APNG_ (Animated PNG) from a sequence of PNG files. This is used by the :meth:`sage.plot.animate.Animation.apng` method. @@ -1322,7 +1323,7 @@ def __init__(self, out, num_frames, self.num_plays = num_plays self.default_delay_numerator = delay self.default_delay_denominator = delay_denominator - self._matchref = dict() + self._matchref = {} self.out.write(self.magic) def add_frame(self, pngfile, delay=None, delay_denominator=None): @@ -1449,7 +1450,7 @@ def _add_png(self, pngfile): """ with open(pngfile, 'rb') as png: if png.read(8) != self.magic: - raise ValueError("{} is not a PNG file".format(pngfile)) + raise ValueError(f"{pngfile} is not a PNG file") while True: chead = png.read(8) if len(chead) == 0: @@ -1466,7 +1467,7 @@ def _add_png(self, pngfile): self._copy() else: if cdata != ref: - raise ValueError("Chunk {} mismatch".format(utype)) + raise ValueError(f"Chunk {utype} mismatch") met = ("_first_" if self._first else "_next_") + utype try: met = getattr(self, met) diff --git a/src/sage/plot/arc.py b/src/sage/plot/arc.py index 8da333ee12b..c2a0f69ed1c 100644 --- a/src/sage/plot/arc.py +++ b/src/sage/plot/arc.py @@ -1,7 +1,7 @@ """ Arcs of circles and ellipses """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2010 Vincent Delecroix <20100.delecroix@gmail.com>, # # Distributed under the terms of the GNU General Public License (GPL) @@ -13,8 +13,8 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from sage.plot.primitive import GraphicPrimitive from sage.plot.colors import to_mpl_color @@ -336,7 +336,7 @@ def theta_stretch(theta, scale): g.add_primitive(BezierPath(cutlist, opt)) return g - def _repr_(self): + def _repr_(self) -> str: """ String representation of ``Arc`` primitive. @@ -346,7 +346,7 @@ def _repr_(self): sage: print(Arc(2,3,2.2,2.2,0,2,3,{})) Arc with center (2.0,3.0) radii (2.2,2.2) angle 0.0 inside the sector (2.0,3.0) """ - return "Arc with center (%s,%s) radii (%s,%s) angle %s inside the sector (%s,%s)" % (self.x, self.y, self.r1, self.r2, self.angle, self.s1, self.s2) + return f"Arc with center ({self.x},{self.y}) radii ({self.r1},{self.r2}) angle {self.angle} inside the sector ({self.s1},{self.s2})" def _render_on_subplot(self, subplot): """ diff --git a/src/sage/plot/arrow.py b/src/sage/plot/arrow.py index 74da7b4b4f7..25fcf381c92 100644 --- a/src/sage/plot/arrow.py +++ b/src/sage/plot/arrow.py @@ -114,7 +114,7 @@ def _repr_(self): sage: CurveArrow(path=[[(0,0),(1,4),(2,3)]],options={})._repr_() 'CurveArrow from (0, 0) to (2, 3)' """ - return "CurveArrow from %s to %s" % (self.path[0][0], self.path[-1][-1]) + return f"CurveArrow from {self.path[0][0]} to {self.path[-1][-1]}" def _render_on_subplot(self, subplot): """ @@ -152,7 +152,7 @@ def _render_on_subplot(self, subplot): from matplotlib.path import Path bpath = Path(self.vertices, self.codes) p = FancyArrowPatch(path=bpath, - lw=width, arrowstyle='%s,head_width=%s,head_length=%s' % (style, head_width, head_length), + lw=width, arrowstyle='{},head_width={},head_length={}'.format(style, head_width, head_length), fc=color, ec=color, linestyle=get_matplotlib_linestyle(options['linestyle'], return_type='long')) p.set_zorder(options['zorder']) @@ -319,7 +319,7 @@ def _repr_(self): sage: Arrow(0,0,2,3,{})._repr_() 'Arrow from (0.0,0.0) to (2.0,3.0)' """ - return "Arrow from (%s,%s) to (%s,%s)" % (self.xtail, self.ytail, self.xhead, self.yhead) + return f"Arrow from ({self.xtail},{self.ytail}) to ({self.xhead},{self.yhead})" def _render_on_subplot(self, subplot): r""" @@ -378,7 +378,7 @@ def _render_on_subplot(self, subplot): from matplotlib.patches import FancyArrowPatch p = FancyArrowPatch((self.xtail, self.ytail), (self.xhead, self.yhead), lw=width, - arrowstyle='%s,head_width=%s,head_length=%s' % (style, head_width, head_length), + arrowstyle='{},head_width={},head_length={}'.format(style, head_width, head_length), shrinkA=arrowshorten_end, shrinkB=arrowshorten_end, fc=color, ec=color, linestyle=get_matplotlib_linestyle(options['linestyle'], return_type='long')) @@ -395,7 +395,7 @@ def _render_on_subplot(self, subplot): import matplotlib.patheffects as pe - class CheckNthSubPath(): + class CheckNthSubPath: def __init__(self, patch, n): """ creates a callable object that returns True if the diff --git a/src/sage/plot/bezier_path.py b/src/sage/plot/bezier_path.py index 14958fbbba8..16053ad0e7b 100644 --- a/src/sage/plot/bezier_path.py +++ b/src/sage/plot/bezier_path.py @@ -208,7 +208,7 @@ def _repr_(self): """ x0, y0 = self.vertices[0] x1, y1 = self.vertices[-1] - return "Bezier path from (%s, %s) to (%s, %s)" % (x0, y0, x1, y1) + return f"Bezier path from ({x0}, {y0}) to ({x1}, {y1})" def _render_on_subplot(self, subplot): """ diff --git a/src/sage/plot/circle.py b/src/sage/plot/circle.py index c7fd3d9dfc0..7d5f04f2f9c 100644 --- a/src/sage/plot/circle.py +++ b/src/sage/plot/circle.py @@ -133,7 +133,7 @@ def _repr_(self): sage: c = C[0]; c Circle defined by (2.0,3.0) with r=5.0 """ - return "Circle defined by (%s,%s) with r=%s" % (self.x, self.y, self.r) + return f"Circle defined by ({self.x},{self.y}) with r={self.r}" def _render_on_subplot(self, subplot): """ diff --git a/src/sage/plot/colors.py b/src/sage/plot/colors.py index 3322c6cfdbc..573750498d7 100644 --- a/src/sage/plot/colors.py +++ b/src/sage/plot/colors.py @@ -254,7 +254,7 @@ def html_to_float(c): raise ValueError("'%s' must be a valid HTML hex color (e.g., '#f07' or '#d6e7da')" % c) h = c[1:] if len(h) == 3: - h = '%s%s%s%s%s%s' % (h[0], h[0], h[1], h[1], h[2], h[2]) + h = f'{h[0]}{h[0]}{h[1]}{h[1]}{h[2]}{h[2]}' elif len(h) != 6: raise ValueError("color hex string (= '%s') must have length 3 or 6" % h) return tuple([int(h[i:i + 2], base=16) / 255 for i in [0, 2, 4]]) @@ -338,8 +338,8 @@ def rgbcolor(c, space='rgb'): elif isinstance(c, (list, tuple)): if len(c) != 3: - raise ValueError("color list or tuple '%s' must have 3 entries, one for each RGB, HSV, HLS, or HSL channel" % (c, )) - c = [mod_one(_) for _ in list(c)] + raise ValueError(f"color list or tuple '{c}' must have 3 entries, one for each RGB, HSV, HLS, or HSL channel") + c = [mod_one(comp) for comp in c] if space == 'rgb': return tuple(c) elif space == 'hsv': @@ -358,7 +358,7 @@ def rgbcolor(c, space='rgb'): to_mpl_color = rgbcolor -class Color(): +class Color: def __init__(self, r='#0000ff', g=None, b=None, space='rgb'): """ A Red-Green-Blue (RGB) color model color object. For most @@ -421,7 +421,7 @@ def __repr__(self): sage: Color(1, 0.5, 1/16, space='hsl').__repr__() 'RGB color (0.09375, 0.03125, 0.03125)' """ - return "RGB color %s" % (self._rgb, ) + return f"RGB color {self._rgb}" def __lt__(self, right): """ @@ -645,7 +645,7 @@ def blend(self, color, fraction=0.5): color = [float(_) for _ in color] return Color(rgbcolor([(1 - fraction) * a + fraction * b for a, b in zip(self._rgb, color)])) - raise TypeError("%s must be a Color or float-convertible 3-tuple/list" % (color, )) + raise TypeError(f"{color} must be a Color or float-convertible 3-tuple/list") def __add__(self, right): """ @@ -1104,7 +1104,7 @@ def __getattr__(self, name): try: return self[name] except KeyError: - raise AttributeError("'%s' has no attribute or colormap %s" % (type(self).__name__, name)) + raise AttributeError("'{}' has no attribute or colormap {}".format(type(self).__name__, name)) def __dir__(self): """ @@ -1611,7 +1611,7 @@ def __getattr__(self, name): try: return self[name] except KeyError: - raise AttributeError("'%s' has no attribute or colormap %s" % (type(self).__name__, name)) + raise AttributeError("'{}' has no attribute or colormap {}".format(type(self).__name__, name)) def __repr__(self): """ diff --git a/src/sage/plot/density_plot.py b/src/sage/plot/density_plot.py index ff27f2d126b..a69da22344a 100644 --- a/src/sage/plot/density_plot.py +++ b/src/sage/plot/density_plot.py @@ -127,7 +127,7 @@ def _repr_(self): sage: d = D[0]; d DensityPlot defined by a 25 x 25 data grid """ - return "DensityPlot defined by a %s x %s data grid" % (self.xy_array_row, self.xy_array_col) + return "DensityPlot defined by a {} x {} data grid".format(self.xy_array_row, self.xy_array_col) def _render_on_subplot(self, subplot): """ diff --git a/src/sage/plot/disk.py b/src/sage/plot/disk.py index 4219cf35155..20831caf61f 100644 --- a/src/sage/plot/disk.py +++ b/src/sage/plot/disk.py @@ -149,7 +149,7 @@ def _repr_(self): sage: p = P[0]; p Disk defined by (3.0,3.0) with r=1.0 spanning (0.0, 1.5707963267...) radians """ - return "Disk defined by (%s,%s) with r=%s spanning (%s, %s) radians" % (self.x, self.y, self.r, self.rad1, self.rad2) + return "Disk defined by ({},{}) with r={} spanning ({}, {}) radians".format(self.x, self.y, self.r, self.rad1, self.rad2) def _render_on_subplot(self, subplot): """ diff --git a/src/sage/plot/ellipse.py b/src/sage/plot/ellipse.py index 788db0fd76b..c606d8360db 100644 --- a/src/sage/plot/ellipse.py +++ b/src/sage/plot/ellipse.py @@ -170,7 +170,7 @@ def _repr_(self): sage: Ellipse(0,0,2,1,0,{})._repr_() 'Ellipse centered at (0.0, 0.0) with radii (2.0, 1.0) and angle 0.0' """ - return "Ellipse centered at (%s, %s) with radii (%s, %s) and angle %s" % (self.x, self.y, self.r1, self.r2, self.angle) + return "Ellipse centered at ({}, {}) with radii ({}, {}) and angle {}".format(self.x, self.y, self.r1, self.r2, self.angle) def _render_on_subplot(self, subplot): """ @@ -227,6 +227,7 @@ def plot3d(self): """ raise NotImplementedError + @rename_keyword(color='rgbcolor') @options(alpha=1, fill=False, thickness=1, edgecolor='blue', facecolor='blue', linestyle='solid', zorder=5, aspect_ratio=1.0, legend_label=None, legend_color=None) diff --git a/src/sage/plot/graphics.py b/src/sage/plot/graphics.py index 8a61068887f..f22ae6756f8 100644 --- a/src/sage/plot/graphics.py +++ b/src/sage/plot/graphics.py @@ -100,23 +100,23 @@ def _parse_figsize(figsize): # figsize should be a pair of positive numbers if len(figsize) != 2: raise ValueError("figsize should be a positive number or a list " - "of two positive numbers, not {0}".format(figsize)) + f"of two positive numbers, not {figsize}") figsize = (float(figsize[0]), float(figsize[1])) # floats for mpl if not (figsize[0] > 0 and figsize[1] > 0): raise ValueError("figsize should be positive numbers, " - "not {0} and {1}".format(figsize[0], figsize[1])) + f"not {figsize[0]} and {figsize[1]}") else: # in this case, figsize is a single number representing the width and # should be positive try: figsize = float(figsize) # to pass to mpl except TypeError: - raise TypeError("figsize should be a positive number, not {0}".format(figsize)) + raise TypeError(f"figsize should be a positive number, not {figsize}") if figsize > 0: default_width, default_height = rcParams['figure.figsize'] figsize = (figsize, default_height * figsize / default_width) else: - raise ValueError("figsize should be positive, not {0}".format(figsize)) + raise ValueError(f"figsize should be positive, not {figsize}") return figsize @@ -1389,7 +1389,7 @@ def _set_scale(self, subplot, scale=None, base=None): if isinstance(scale, (list, tuple)): if len(scale) != 2 and len(scale) != 3: raise ValueError("If the input is a tuple, it must be of " - "the form (scale, base) or (scale, basex, basey)") + "the form (scale, base) or (scale, basex, basey)") if len(scale) == 2: base = scale[1] else: @@ -1398,7 +1398,7 @@ def _set_scale(self, subplot, scale=None, base=None): if scale not in ('linear', 'loglog', 'semilogx', 'semilogy'): raise ValueError("The scale must be one of 'linear', 'loglog'," - " 'semilogx' or 'semilogy' -- got '{0}'".format(scale)) + f" 'semilogx' or 'semilogy' -- got '{scale}'") if isinstance(base, (list, tuple)): basex, basey = base @@ -1437,37 +1437,37 @@ def _set_scale(self, subplot, scale=None, base=None): # NOTE: If you intend to use a new parameter in show(), you should update # this dictionary to contain the default value for that parameter. - SHOW_OPTIONS = dict(# axes options - axes=None, axes_labels=None, axes_labels_size=None, - axes_pad=None, base=None, scale=None, - xmin=None, xmax=None, ymin=None, ymax=None, - flip_x=False, flip_y=False, - # Figure options - aspect_ratio=None, dpi=DEFAULT_DPI, fig_tight=True, - figsize=None, fontsize=None, frame=False, - title=None, title_pos=None, transparent=False, - # Grid options - gridlines=None, gridlinesstyle=None, - hgridlinesstyle=None, vgridlinesstyle=None, - # Legend options - legend_options={}, show_legend=None, - # Ticks options - ticks=None, tick_formatter=None, ticks_integer=False, - # Text options - typeset='default') + SHOW_OPTIONS = { # axes options + 'axes': None, 'axes_labels': None, 'axes_labels_size': None, + 'axes_pad': None, 'base': None, 'scale': None, + 'xmin': None, 'xmax': None, 'ymin': None, 'ymax': None, + 'flip_x': False, 'flip_y': False, + # Figure options + 'aspect_ratio': None, 'dpi': DEFAULT_DPI, 'fig_tight': True, + 'figsize': None, 'fontsize': None, 'frame': False, + 'title': None, 'title_pos': None, 'transparent': False, + # Grid options + 'gridlines': None, 'gridlinesstyle': None, + 'hgridlinesstyle': None, 'vgridlinesstyle': None, + # Legend options + 'legend_options': {}, 'show_legend': None, + # Ticks options + 'ticks': None, 'tick_formatter': None, 'ticks_integer': False, + # Text options + 'typeset': 'default'} # Default options for the legends: - LEGEND_OPTIONS = dict(back_color='white', borderpad=0.6, - borderaxespad=None, - columnspacing=None, - fancybox=False, font_family='sans-serif', - font_size='medium', font_style='normal', - font_variant='normal', font_weight='medium', - handlelength=0.05, handletextpad=0.5, - labelspacing=0.02, loc='best', - markerscale=0.6, ncol=1, numpoints=2, - shadow=True, title=None) + LEGEND_OPTIONS = {'back_color': 'white', 'borderpad': 0.6, + 'borderaxespad': None, + 'columnspacing': None, + 'fancybox': False, 'font_family': 'sans-serif', + 'font_size': 'medium', 'font_style': 'normal', + 'font_variant': 'normal', 'font_weight': 'medium', + 'handlelength': 0.05, 'handletextpad': 0.5, + 'labelspacing': 0.02, 'loc': 'best', + 'markerscale': 0.6, 'ncol': 1, 'numpoints': 2, + 'shadow': True, 'title': None} @suboptions('legend', **LEGEND_OPTIONS) def show(self, **kwds): @@ -2360,9 +2360,9 @@ def _limit_output_aspect_ratio(self, xmin, xmax, ymin, ymax): return {'xmin': xmin, 'xmax': xmax, 'ymin': ymin, 'ymax': ymax} def _matplotlib_tick_formatter(self, subplot, base=(10, 10), - locator_options={}, scale=('linear', 'linear'), - tick_formatter=(None, None), ticks=(None, None), - xmax=None, xmin=None, ymax=None, ymin=None): + locator_options={}, scale=('linear', 'linear'), + tick_formatter=(None, None), ticks=(None, None), + xmax=None, xmin=None, ymax=None, ymin=None): r""" Take a matplotlib subplot instance representing the graphic and set the ticks formatting. This function is only for internal use. @@ -2388,8 +2388,10 @@ def _matplotlib_tick_formatter(self, subplot, base=(10, 10), # This function is created to refactor some code that is repeated # in the matplotlib function from matplotlib.ticker import (FixedLocator, Locator, - LogFormatterMathtext, LogLocator, MaxNLocator, - MultipleLocator, NullLocator, ScalarFormatter) + LogFormatterMathtext, + LogLocator, MaxNLocator, + MultipleLocator, + NullLocator, ScalarFormatter) x_locator, y_locator = ticks # ---------------------- Location of x-ticks --------------------- @@ -2411,8 +2413,8 @@ def _matplotlib_tick_formatter(self, subplot, base=(10, 10), x_locator = MultipleLocator(float(x_locator)) else: # not enough room for two major ticks raise ValueError('Expand the range of the independent ' - 'variable to allow two multiples of your tick locator ' - '(option `ticks`).') + 'variable to allow two multiples of your tick locator ' + '(option `ticks`).') # ---------------------- Location of y-ticks --------------------- if y_locator is None: @@ -2432,8 +2434,8 @@ def _matplotlib_tick_formatter(self, subplot, base=(10, 10), y_locator = MultipleLocator(float(y_locator)) else: # not enough room for two major ticks raise ValueError('Expand the range of the dependent ' - 'variable to allow two multiples of your tick locator ' - '(option `ticks`).') + 'variable to allow two multiples of your tick locator ' + '(option `ticks`).') x_formatter, y_formatter = tick_formatter from matplotlib.ticker import FuncFormatter, FixedFormatter @@ -2454,8 +2456,8 @@ def _matplotlib_tick_formatter(self, subplot, base=(10, 10), if scale[0] == 'log': # We need to strip out '\\mathdefault' from the string x_formatter = FuncFormatter(lambda n, pos: - LogFormatterMathtext(base=base[0])(n, pos).replace( - "\\mathdefault", "")) + LogFormatterMathtext(base=base[0])(n, pos).replace( + "\\mathdefault", "")) else: # circumvent the problem of symbolic tick values (trac #34693) if isinstance(x_locator, FixedLocator): @@ -2466,8 +2468,8 @@ def _matplotlib_tick_formatter(self, subplot, base=(10, 10), if (not isinstance(ticks[0], (list, tuple)) or len(ticks[0]) != len(x_formatter)): raise ValueError("If the first component of the list " - "`tick_formatter` is a list then the first component " - "of `ticks` must also be a list of equal length.") + "`tick_formatter` is a list then the first component " + "of `ticks` must also be a list of equal length.") x_formatter = FixedFormatter(x_formatter) # ---------------------- Formatting y-ticks ---------------------- if y_formatter is None: @@ -2483,8 +2485,8 @@ def _matplotlib_tick_formatter(self, subplot, base=(10, 10), if scale[1] == 'log': # We need to strip out '\\mathdefault' from the string y_formatter = FuncFormatter(lambda n, pos: - LogFormatterMathtext(base=base[1])(n, pos).replace( - "\\mathdefault", "")) + LogFormatterMathtext(base=base[1])(n, pos).replace( + "\\mathdefault", "")) else: # circumvent the problem of symbolic tick values (trac #34693) if isinstance(y_locator, FixedLocator): @@ -2495,8 +2497,8 @@ def _matplotlib_tick_formatter(self, subplot, base=(10, 10), if (not isinstance(ticks[1], (list, tuple)) or len(ticks[1]) != len(y_formatter)): raise ValueError("If the second component of the list " - "`tick_formatter` is a list then the second component " - "of `ticks` must also be a list of equal length.") + "`tick_formatter` is a list then the second component " + "of `ticks` must also be a list of equal length.") y_formatter = FixedFormatter(y_formatter) subplot.xaxis.set_major_locator(x_locator) @@ -2788,7 +2790,7 @@ def matplotlib(self, filename=None, rcParams['text.usetex'] = True elif typeset != 'default': # We won't change (maybe user-set) defaults raise ValueError("typeset must be set to one of 'default', 'latex'," - " or 'type1'; got '{}'.".format(typeset)) + f" or 'type1'; got '{typeset}'.") self.fontsize(fontsize) self.axes_labels(l=axes_labels) @@ -2805,7 +2807,7 @@ def matplotlib(self, filename=None, if not subplot: subplot = figure.add_subplot(111) # Add all the primitives to the subplot - old_opts = dict() + old_opts = {} for g in self._objects: opts, old_opts[g] = g.options(), g.options() for k, v in opts.items(): @@ -2867,7 +2869,7 @@ def matplotlib(self, filename=None, if show_legend: from matplotlib.font_manager import FontProperties - lopts = dict() + lopts = {} lopts.update(legend_options) lopts.update(self._legend_opts) prop = FontProperties( @@ -2898,8 +2900,8 @@ def matplotlib(self, filename=None, subplot.set_xlim([xmin, xmax]) subplot.set_ylim([ymin, ymax]) - locator_options = dict(nbins=9, steps=[1, 2, 5, 10], - integer=ticks_integer) + locator_options = {'nbins': 9, 'steps': [1, 2, 5, 10], + 'integer': ticks_integer} if axes is None: axes = self._show_axes @@ -3055,7 +3057,7 @@ def matplotlib(self, filename=None, # We do this change only on linear scale, otherwise matplotlib # errors out with a memory error. from matplotlib.ticker import (AutoMinorLocator, FixedLocator, - LogLocator, NullLocator) + LogLocator, NullLocator) if isinstance(x_locator, (NullLocator, FixedLocator)): subplot.xaxis.set_minor_locator(NullLocator()) elif xscale == 'linear': @@ -3090,8 +3092,8 @@ def matplotlib(self, filename=None, if gridlinesstyle is None: # Set up the default grid style - gridlinesstyle = dict(color='black', linestyle=':', - linewidth=0.5) + gridlinesstyle = {'color': 'black', 'linestyle': ':', + 'linewidth': 0.5} vgridstyle = gridlinesstyle.copy() if vgridlinesstyle is not None: @@ -3180,7 +3182,7 @@ def matplotlib(self, filename=None, labeltrans = offset_copy(trans, figure, x=xaxis_labeloffset, y=0, units='points') subplot.xaxis.set_label_coords(x=xaxis_labelx, - y=xaxis_labely, transform=labeltrans) + y=xaxis_labely, transform=labeltrans) ylabel = subplot.yaxis.get_label() ylabel.set_horizontalalignment('center') @@ -3190,7 +3192,7 @@ def matplotlib(self, filename=None, labeltrans = offset_copy(trans, figure, x=0, y=yaxis_labeloffset, units='points') subplot.yaxis.set_label_coords(x=yaxis_labelx, - y=yaxis_labely, transform=labeltrans) + y=yaxis_labely, transform=labeltrans) # This option makes the xlim and ylim limits not take effect # todo: figure out which limits were specified, and let the @@ -3341,7 +3343,7 @@ def save(self, filename, **kwds): Graphics object consisting of 1 graphics primitive """ - options = dict() + options = {} options.update(self.SHOW_OPTIONS) options.update(self._extra_kwds) options.update(kwds) @@ -3406,7 +3408,7 @@ def save(self, filename, **kwds): # tight_layout adjusts the *subplot* parameters so ticks aren't cut off, etc. figure.tight_layout() - opts = dict(dpi=dpi, transparent=transparent) + opts = {'dpi': dpi, 'transparent': transparent} if fig_tight is True: opts['bbox_inches'] = 'tight' if self._bbox_extra_artists: @@ -3438,7 +3440,7 @@ def _latex_(self, **kwds): """ tmpfilename = tmp_filename(ext='.pgf') self.save(filename=tmpfilename, **kwds) - with open(tmpfilename, "r") as tmpfile: + with open(tmpfilename) as tmpfile: latex_list = tmpfile.readlines() from sage.misc.latex import latex latex.add_package_to_preamble_if_available('pgf') @@ -3464,7 +3466,7 @@ def description(self): for g in self: g_zorder = g.options().get('zorder', 0) if hasattr(g, 'xdata'): - g_str = '{0}:\t{1}'.format(g, list(zip(g.xdata, g.ydata))) + g_str = f'{g}:\t{list(zip(g.xdata, g.ydata))}' else: g_str = repr(g) data.append([g_zorder, g_str, g]) diff --git a/src/sage/plot/histogram.py b/src/sage/plot/histogram.py index e10229417fc..959284fa2e1 100644 --- a/src/sage/plot/histogram.py +++ b/src/sage/plot/histogram.py @@ -180,9 +180,9 @@ def _repr_(self): """ L = len(self.datalist) if not hasattr(self.datalist[0], '__contains__'): - return "Histogram defined by a data list of size {}".format(L) + return f"Histogram defined by a data list of size {L}" else: - return "Histogram defined by {} data lists".format(L) + return f"Histogram defined by {L} data lists" def _render_on_subplot(self, subplot): """ diff --git a/src/sage/plot/hyperbolic_arc.py b/src/sage/plot/hyperbolic_arc.py index 4e7fda8350d..9b89198c68f 100644 --- a/src/sage/plot/hyperbolic_arc.py +++ b/src/sage/plot/hyperbolic_arc.py @@ -227,7 +227,8 @@ def _repr_(self): sage: HyperbolicArc(0, 1/2+I*sqrt(3)/2, "UHP", {}) Hyperbolic arc (0.000000000000000, 0.500000000000000 + 0.866025403784439*I) """ - return "Hyperbolic arc (%s, %s)" % (self.A, self.B) + return f"Hyperbolic arc ({self.A}, {self.B})" + @rename_keyword(color='rgbcolor') @options(alpha=1, fill=False, thickness=1, rgbcolor="blue", zorder=2, linestyle='solid') @@ -389,9 +390,9 @@ def hyperbolic_arc(a, b, model="UHP", **options): # Check for valid points if a[2] < 0 or a[0]**2+a[1]**2-a[2]**2 + 1 > EPSILON: - raise ValueError("%s is not a valid point in the HM model" % (a,)) + raise ValueError(f"{a} is not a valid point in the HM model") if b[2] < 0 or b[0]**2+b[1]**2-b[2]**2 + 1 > EPSILON: - raise ValueError("%s is not a valid point in the HM model" % (b,)) + raise ValueError(f"{b} is not a valid point in the HM model") HM = HyperbolicPlane().HM() geodesic = HM.get_geodesic(a, b) diff --git a/src/sage/plot/hyperbolic_polygon.py b/src/sage/plot/hyperbolic_polygon.py index efe39697829..ba09c3de97c 100644 --- a/src/sage/plot/hyperbolic_polygon.py +++ b/src/sage/plot/hyperbolic_polygon.py @@ -109,6 +109,7 @@ def _repr_(self): """ return "Hyperbolic polygon ({})".format(", ".join(map(str, self._pts))) + def _winding_number(vertices, point): r""" Compute the winding number of the given point in the plane `z = 0`. diff --git a/src/sage/plot/hyperbolic_regular_polygon.py b/src/sage/plot/hyperbolic_regular_polygon.py index 9be2169ab63..17397dce3f4 100644 --- a/src/sage/plot/hyperbolic_regular_polygon.py +++ b/src/sage/plot/hyperbolic_regular_polygon.py @@ -117,7 +117,7 @@ def __init__(self, sides, i_angle, center, options): raise ValueError("interior angle %s must be in (0, pi) interval" % (i_angle)) if pi*(sides-2) - sides*i_angle <= 0: raise ValueError("there exists no hyperbolic regular compact polygon," - " for sides=%s the interior angle must be less than %s" % (sides, pi * (sides-2) / sides)) + " for sides={} the interior angle must be less than {}".format(sides, pi * (sides-2) / sides)) self.sides = sides self.i_angle = i_angle beta = 2 * pi / self.sides # compute the rotation angle to be used ahead @@ -168,8 +168,7 @@ def _repr_(self): sage: HyperbolicRegularPolygon(5,pi/2,I, {}) Hyperbolic regular polygon (sides=5, i_angle=1/2*pi, center=1.00000000000000*I) """ - return ("Hyperbolic regular polygon (sides=%s, i_angle=%s, center=%s)" - % (self.sides, self.i_angle, self.center)) + return ("Hyperbolic regular polygon (sides={}, i_angle={}, center={})".format(self.sides, self.i_angle, self.center)) def _i_rotation(self, z, alpha): r""" @@ -200,6 +199,7 @@ def _i_rotation(self, z, alpha): G = matrix([[_c, _s], [-_s, _c]]) return (G[0][0] * z + G[0][1]) / (G[1][0] * z + G[1][1]) + @rename_keyword(color='rgbcolor') @options(alpha=1, fill=False, thickness=1, rgbcolor="blue", zorder=2, linestyle='solid') diff --git a/src/sage/plot/matrix_plot.py b/src/sage/plot/matrix_plot.py index 96e966a6513..3e3e5c7dd24 100644 --- a/src/sage/plot/matrix_plot.py +++ b/src/sage/plot/matrix_plot.py @@ -1,7 +1,7 @@ """ Matrix plots """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2006 Alex Clemesha , # William Stein , # 2008 Mike Hansen , @@ -15,8 +15,8 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from sage.plot.primitive import GraphicPrimitive from sage.misc.decorators import options, suboptions @@ -162,7 +162,7 @@ def _repr_(self): sage: m = M[0]; m # needs sage.symbolic MatrixPlot defined by a 5 x 5 data grid """ - return "MatrixPlot defined by a %s x %s data grid" % (self.xy_array_row, self.xy_array_col) + return "MatrixPlot defined by a {} x {} data grid".format(self.xy_array_row, self.xy_array_col) def _render_on_subplot(self, subplot): """ @@ -195,9 +195,9 @@ def _render_on_subplot(self, subplot): rowstyle = subdiv_options['style'] colstyle = subdiv_options['style'] if rowstyle is None: - rowstyle = dict() + rowstyle = {} if colstyle is None: - colstyle = dict() + colstyle = {} # Make line objects for subdivisions from .line import line2d @@ -224,10 +224,11 @@ def _render_on_subplot(self, subplot): extent = (lim['xmin'], lim['xmax'], lim['ymax' if flip_y else 'ymin'], lim['ymin' if flip_y else 'ymax']) - opts = dict(cmap=cmap, interpolation='nearest', aspect='equal', - norm=norm, vmin=options['vmin'], vmax=options['vmax'], - origin=('upper' if flip_y else 'lower'), - extent=extent, zorder=options.get('zorder')) + opts = {'cmap': cmap, 'interpolation': 'nearest', + 'aspect': 'equal', 'norm': norm, + 'vmin': options['vmin'], 'vmax': options['vmax'], + 'origin': ('upper' if flip_y else 'lower'), + 'extent': extent, 'zorder': options.get('zorder')} image = subplot.imshow(self.xy_data_array, **opts) if options.get('colorbar', False): @@ -240,7 +241,7 @@ def _render_on_subplot(self, subplot): subplot.xaxis.tick_top() else: subplot.xaxis.tick_bottom() - subplot.xaxis.set_ticks_position('both') #only tick marks, not tick labels + subplot.xaxis.set_ticks_position('both') # only tick marks, not tick labels @suboptions('colorbar', orientation='vertical', format=None) diff --git a/src/sage/plot/multigraphics.py b/src/sage/plot/multigraphics.py index f22febca152..f04755a7d09 100644 --- a/src/sage/plot/multigraphics.py +++ b/src/sage/plot/multigraphics.py @@ -151,7 +151,7 @@ def __init__(self, graphics_list): else: if not isinstance(ins, (list, tuple)) or len(ins) != 2: raise TypeError("a pair (Graphics, position) is " - "expected, not {}".format(ins)) + f"expected, not {ins}") self.append(ins[0], pos=ins[1]) def _repr_(self): @@ -490,7 +490,7 @@ def save(self, filename, figsize=None, **kwds): # tight_layout adjusts the *subplot* parameters so ticks aren't # cut off, etc. figure.tight_layout() - opts = dict(dpi=dpi, transparent=transparent) + opts = {"dpi": dpi, "transparent": transparent} if fig_tight is True: opts['bbox_inches'] = 'tight' figure.savefig(filename, **opts) @@ -545,7 +545,7 @@ def _latex_(self, **kwds): """ tmpfilename = tmp_filename(ext='.pgf') self.save(filename=tmpfilename, **kwds) - with open(tmpfilename, "r") as tmpfile: + with open(tmpfilename) as tmpfile: latex_list = tmpfile.readlines() return ''.join(latex_list) @@ -741,8 +741,8 @@ def __str__(self): """ n = len(self._glist) if n <= 1: - return "Multigraphics with {} element".format(n) - return "Multigraphics with {} elements".format(n) + return f"Multigraphics with {n} element" + return f"Multigraphics with {n} elements" def _add_subplot(self, figure, index, **options): r""" @@ -910,7 +910,7 @@ def append(self, graphics, pos=None): from matplotlib import rcParams if not isinstance(graphics, Graphics): raise TypeError("a Graphics object is expected, " - "not {}".format(graphics)) + f"not {graphics}") if pos is None: # Default position: left = rcParams['figure.subplot.left'] @@ -919,7 +919,7 @@ def append(self, graphics, pos=None): height = rcParams['figure.subplot.top'] - bottom pos = (left, bottom, width, height) elif not isinstance(pos, (list, tuple)) or len(pos) != 4: - raise TypeError("pos must be a 4-tuple, not {}".format(pos)) + raise TypeError(f"pos must be a 4-tuple, not {pos}") pos = tuple(float(p) for p in pos) self._glist.append(graphics) self._positions.append(pos) @@ -1142,7 +1142,7 @@ def __init__(self, array): MultiGraphics.__init__(self, []) if not isinstance(array, (list, tuple)): raise TypeError("array must be a list of lists of Graphics " - "objects, not {}".format(array)) + f"objects, not {array}") array = list(array) self._rows = len(array) if self._rows > 0: @@ -1155,7 +1155,7 @@ def __init__(self, array): for row in array: # basically flatten the list if not isinstance(row, (list, tuple)) or len(row) != self._cols: raise TypeError("array must be a list of equal-size lists of " - "Graphics objects, not {}".format(array)) + f"Graphics objects, not {array}") for g in row: if not isinstance(g, Graphics): raise TypeError("every element of array must be a " @@ -1179,7 +1179,7 @@ def __str__(self): 'Graphics Array of size 2 x 3' """ - return "Graphics Array of size {} x {}".format(self._rows, self._cols) + return f"Graphics Array of size {self._rows} x {self._cols}" def _add_subplot(self, figure, index, **options): r""" diff --git a/src/sage/plot/plot.py b/src/sage/plot/plot.py index 20a71ef8bd1..4f650fa4613 100644 --- a/src/sage/plot/plot.py +++ b/src/sage/plot/plot.py @@ -580,17 +580,17 @@ def f(x): return (x-3)*(x-5)*(x-7)+40 from functools import reduce -## IMPORTANT: Do *not* import matplotlib at module scope. It takes a -## surprisingly long time to initialize itself. It's better if it is -## imported in functions, so it only gets started if it is actually -## going to be used. +# IMPORTANT: Do *not* import matplotlib at module scope. It takes a +# surprisingly long time to initialize itself. It's better if it is +# imported in functions, so it only gets started if it is actually +# going to be used. -#DEFAULT_FIGSIZE=(6, 3.70820393249937) +# DEFAULT_FIGSIZE=(6, 3.70820393249937) import sage.misc.verbose from sage.arith.srange import srange -from sage.misc.randstate import current_randstate #for plot adaptive refinement -from math import sin, cos, pi, log, exp #for polar_plot and log scaling +from sage.misc.randstate import current_randstate # for plot adaptive refinement +from math import sin, cos, pi, log, exp # for polar_plot and log scaling from sage.ext.fast_eval import fast_float, is_fast_float from sage.structure.element import Expression @@ -605,10 +605,10 @@ def f(x): return (x-3)*(x-5)*(x-7)+40 from sage.misc.lazy_import import lazy_import lazy_import('sage.plot.line', 'line2d', deprecation=28717) -#Currently not used - see comment immediately above about -#figure.canvas.mpl_connect('draw_event', pad_for_tick_labels) +# Currently not used - see comment immediately above about +# figure.canvas.mpl_connect('draw_event', pad_for_tick_labels) # TODO - figure out how to use this, add documentation -#def pad_for_tick_labels(event): +# def pad_for_tick_labels(event): # import matplotlib.transforms as mtransforms # figure=event.canvas.figure # bboxes = [] @@ -638,10 +638,10 @@ def f(x): return (x-3)*(x-5)*(x-7)+40 # figure.canvas.draw() # return False # -#Currently not used - see comment above about -#figure.canvas.mpl_connect('draw_event', pad_for_tick_labels) +# Currently not used - see comment above about +# figure.canvas.mpl_connect('draw_event', pad_for_tick_labels) # TODO - figure out how to use this, add documentation -#def adjust_figure_to_contain_bbox(fig, bbox,pad=1.1): +# def adjust_figure_to_contain_bbox(fig, bbox,pad=1.1): # """ # For each amount we are over (in axes coordinates), we adjust by over*pad # to give ourselves a bit of padding. @@ -673,6 +673,7 @@ def f(x): return (x-3)*(x-5)*(x-7)+40 _SelectiveFormatterClass = None + def SelectiveFormatter(formatter, skip_values): """ This matplotlib formatter selectively omits some tick values and @@ -773,6 +774,7 @@ def __call__(self, x, *args, **kwds): return _SelectiveFormatterClass(formatter, skip_values) + def xydata_from_point_list(points): r""" Return two lists (xdata, ydata), each coerced to a list of floats, @@ -837,6 +839,7 @@ def xydata_from_point_list(points): ydata.append(float(y)) return xdata, ydata + @options(alpha=1, thickness=1, fill=False, fillcolor='automatic', fillalpha=0.5, plot_points=200, adaptive_tolerance=0.01, adaptive_recursion=5, detect_poles=False, exclude=None, @@ -2066,15 +2069,16 @@ def f(x): return (floor(x)+0.5) / (1-(x-0.5)**2) xmax = kwds.pop('xmax', 1) G = _plot(funcs, (xmin, xmax), *args, **kwds) else: - sage.misc.verbose.verbose("there were %s extra arguments (besides %s)" % (n, funcs), level=0) + sage.misc.verbose.verbose(f"there were {n} extra arguments (besides {funcs})", level=0) G._set_extra_kwds(G_kwds) if do_show: G.show() return G + def _plot(funcs, xrange, parametric=False, - polar=False, fill=False, label='', randomize=True, **options): + polar=False, fill=False, label='', randomize=True, **options): """ Internal function which does the actual plotting. @@ -2427,7 +2431,7 @@ def golden_rainbow(i,lightness=0.4): fstr = 'max' else: fstr = 'min' - msg = "WARNING: You use the built-in function %s for filling. You probably wanted the string '%s'." % (fstr, fstr) + msg = "WARNING: You use the built-in function {} for filling. You probably wanted the string '{}'.".format(fstr, fstr) sage.misc.verbose.verbose(msg, level=0) if not is_fast_float(fill): fill_f = fast_float(fill, expect_one_var=True) @@ -2760,6 +2764,7 @@ def parametric_plot(funcs, *args, **kwargs): else: raise ValueError("the number of functions and the number of variable ranges is not a supported combination for a 2d or 3d parametric plots") + @options(aspect_ratio=1.0) def polar_plot(funcs, *args, **kwds): r""" @@ -2865,6 +2870,7 @@ def polar_plot(funcs, *args, **kwds): kwds['polar'] = True return plot(funcs, *args, **kwds) + @options(aspect_ratio='automatic') def list_plot(data, plotjoined=False, **kwargs): r""" @@ -3154,6 +3160,8 @@ def list_plot(data, plotjoined=False, **kwargs): return point(data, **kwargs) #------------------------ Graphs on log scale ---------------------------# + + @options(base=10) def plot_loglog(funcs, *args, **kwds): """ @@ -3204,6 +3212,7 @@ def plot_loglog(funcs, *args, **kwds): """ return plot(funcs, *args, scale='loglog', **kwds) + @options(base=10) def plot_semilogx(funcs, *args, **kwds): """ @@ -3256,6 +3265,7 @@ def plot_semilogx(funcs, *args, **kwds): """ return plot(funcs, *args, scale='semilogx', **kwds) + @options(base=10) def plot_semilogy(funcs, *args, **kwds): """ @@ -3294,6 +3304,7 @@ def plot_semilogy(funcs, *args, **kwds): """ return plot(funcs, *args, scale='semilogy', **kwds) + @options(base=10) def list_plot_loglog(data, plotjoined=False, **kwds): """ @@ -3361,6 +3372,7 @@ def list_plot_loglog(data, plotjoined=False, **kwds): """ return list_plot(data, plotjoined=plotjoined, scale='loglog', **kwds) + @options(base=10) def list_plot_semilogx(data, plotjoined=False, **kwds): """ @@ -3416,6 +3428,7 @@ def list_plot_semilogx(data, plotjoined=False, **kwds): """ return list_plot(data, plotjoined=plotjoined, scale='semilogx', **kwds) + @options(base=10) def list_plot_semilogy(data, plotjoined=False, **kwds): """ @@ -3474,6 +3487,7 @@ def list_plot_semilogy(data, plotjoined=False, **kwds): """ return list_plot(data, plotjoined=plotjoined, scale='semilogy', **kwds) + def to_float_list(v): """ Given a list or tuple or iterable v, coerce each element of v to a @@ -3487,6 +3501,7 @@ def to_float_list(v): """ return [float(x) for x in v] + def reshape(v, n, m): """ Helper function for creating graphics arrays. @@ -3553,6 +3568,7 @@ def reshape(v, n, m): return L + def graphics_array(array, nrows=None, ncols=None): r""" Plot a list of lists (or tuples) of graphics objects on one canvas, @@ -3723,6 +3739,7 @@ def h(x): return sin(4*x) array = reshape(array, nrows, ncols) return GraphicsArray(array) + def multi_graphics(graphics_list): r""" Plot a list of graphics at specified positions on a single canvas. @@ -3797,6 +3814,7 @@ def multi_graphics(graphics_list): """ return MultiGraphics(graphics_list) + def minmax_data(xdata, ydata, dict=False): """ Return the minimums and maximums of ``xdata`` and ``ydata``. @@ -3832,6 +3850,7 @@ def minmax_data(xdata, ydata, dict=False): else: return xmin, xmax, ymin, ymax + def adaptive_refinement(f, p1, p2, adaptive_tolerance=0.01, adaptive_recursion=5, level=0, *, excluded=False): r""" @@ -3913,14 +3932,14 @@ def adaptive_refinement(f, p1, p2, adaptive_tolerance=0.01, try: y = float(f(x)) if str(y) in ['nan', 'NaN', 'inf', '-inf']: - sage.misc.verbose.verbose("%s\nUnable to compute f(%s)" % (msg, x),1) + sage.misc.verbose.verbose(f"{msg}\nUnable to compute f({x})",1) # give up for this branch if excluded: return [(x, 'NaN')] return [] except (ZeroDivisionError, TypeError, ValueError, OverflowError) as msg: - sage.misc.verbose.verbose("%s\nUnable to compute f(%s)" % (msg, x), 1) + sage.misc.verbose.verbose(f"{msg}\nUnable to compute f({x})", 1) # give up for this branch if excluded: return [(x, 'NaN')] @@ -3942,6 +3961,7 @@ def adaptive_refinement(f, p1, p2, adaptive_tolerance=0.01, else: return [] + def generate_plot_points(f, xrange, plot_points=5, adaptive_tolerance=0.01, adaptive_recursion=5, randomize=True, initial_points=None, *, excluded=False, @@ -4073,7 +4093,7 @@ def generate_plot_points(f, xrange, plot_points=5, adaptive_tolerance=0.01, exception_indices.append(i) except (ArithmeticError, TypeError, ValueError) as m: - sage.misc.verbose.verbose("%s\nUnable to compute f(%s)" % (m, xi), 1) + sage.misc.verbose.verbose(f"{m}\nUnable to compute f({xi})", 1) if i == 0: # Given an error for left endpoint, try to move it in slightly for j in range(1, 99): diff --git a/src/sage/plot/plot3d/list_plot3d.py b/src/sage/plot/plot3d/list_plot3d.py index 7bfd84f76a4..a2b0781796c 100644 --- a/src/sage/plot/plot3d/list_plot3d.py +++ b/src/sage/plot/plot3d/list_plot3d.py @@ -264,7 +264,7 @@ def list_plot3d(v, interpolation_type='default', point_list=None, **kwds): kwds['color'] = txtr if is_Matrix(v): if (interpolation_type == 'default' or - interpolation_type == 'linear' and 'num_points' not in kwds): + interpolation_type == 'linear' and 'num_points' not in kwds): return list_plot3d_matrix(v, **kwds) else: data = [(i, j, v[i, j]) @@ -563,7 +563,7 @@ def list_plot3d_tuples(v, interpolation_type, **kwds): # noise to avoid the problem if needed. corr_matrix = numpy.corrcoef(x, y) if not (-0.9 <= corr_matrix[0, 1] <= 0.9): - ep = float(.000001) + ep = .000001 x = [float(p[0]) + random() * ep for p in v] y = [float(p[1]) + random() * ep for p in v] @@ -607,6 +607,7 @@ def list_plot3d_tuples(v, interpolation_type, **kwds): def g(x, y): z = f(x, y) return (x, y, z) + G = ParametricSurface(g, (list(numpy.r_[xmin:xmax:num_points * j]), list(numpy.r_[ymin:ymax:num_points * j])), **kwds) @@ -623,6 +624,7 @@ def g(x, y): def g(x, y): z = f([x, y]).item() return (x, y, z) + G = ParametricSurface(g, (list(numpy.r_[xmin:xmax:num_points * j]), list(numpy.r_[ymin:ymax:num_points * j])), **kwds) @@ -636,10 +638,11 @@ def g(x, y): kx = kwds['degree'] ky = kwds['degree'] s = kwds.get('smoothing', len(x) - numpy.sqrt(2 * len(x))) - s = interpolate.bisplrep(x, y, z, [int(1)] * len(x), xmin, xmax, + s = interpolate.bisplrep(x, y, z, [1] * len(x), xmin, xmax, ymin, ymax, kx=kx, ky=ky, s=s) def f(x, y): return interpolate.bisplev(x, y, s) + return plot3d(f, (xmin, xmax), (ymin, ymax), plot_points=[num_points, num_points], **kwds) diff --git a/src/sage/plot/plot3d/plot3d.py b/src/sage/plot/plot3d/plot3d.py index 2e027e87dd2..094ea89f254 100644 --- a/src/sage/plot/plot3d/plot3d.py +++ b/src/sage/plot/plot3d/plot3d.py @@ -158,7 +158,7 @@ def f(x,y): return math.exp(x/5)*math.cos(y) from sage.misc.sageinspect import sage_getargspec, is_function_or_cython_function -class _Coordinates(): +class _Coordinates: """ This abstract class encapsulates a new coordinate system for plotting. Sub-classes must implement the :meth:`transform` method which, given @@ -348,14 +348,14 @@ def to_cartesian(self, func, params=None): def subs_func(t): # We use eval so that the lambda function has the same # variable names as the original function - ll = """lambda {x},{y}: t.subs({{ - dep_var_dummy: float(func({x}, {y})), - indep_var_dummies[0]: float({x}), - indep_var_dummies[1]: float({y}) - }})""".format(x=params[0], y=params[1]) - return eval(ll, dict(t=t, func=func, - dep_var_dummy=dep_var_dummy, - indep_var_dummies=indep_var_dummies)) + ll = f"""lambda {params[0]},{params[1]}: t.subs({{ + dep_var_dummy: float(func({params[0]}, {params[1]})), + indep_var_dummies[0]: float({params[0]}), + indep_var_dummies[1]: float({params[1]}) + }})""" + return eval(ll, {'t': t, 'func': func, + 'dep_var_dummy': dep_var_dummy, + 'indep_var_dummies': indep_var_dummies}) return [subs_func(m) for m in transformation] def __repr__(self): @@ -373,8 +373,7 @@ def __repr__(self): sage: c My Special Coordinates coordinate transform (z in terms of x, y) """ - return '%s coordinate transform (%s in terms of %s)' % \ - (self._name, self.dep_var, ', '.join(self.indep_vars)) + return '{} coordinate transform ({} in terms of {})'.format(self._name, self.dep_var, ', '.join(self.indep_vars)) def _find_arguments_for_callable(func): @@ -471,6 +470,7 @@ def transform(self, **kwds): """ return tuple(t.subs(**kwds) for t in self.custom_trans) + class Spherical(_Coordinates): """ A spherical coordinate system for use with ``plot3d(transformation=...)`` @@ -542,6 +542,7 @@ def transform(self, radius=None, azimuth=None, inclination=None): radius * sin(inclination) * sin(azimuth), radius * cos(inclination)) + class SphericalElevation(_Coordinates): """ A spherical coordinate system for use with ``plot3d(transformation=...)`` @@ -660,6 +661,7 @@ def transform(self, radius=None, azimuth=None, elevation=None): radius * cos(elevation) * sin(azimuth), radius * sin(elevation)) + class Cylindrical(_Coordinates): """ A cylindrical coordinate system for use with ``plot3d(transformation=...)`` @@ -730,6 +732,7 @@ def transform(self, radius=None, azimuth=None, height=None): radius * sin(azimuth), height) + class TrivialTriangleFactory: """ Class emulating behavior of :class:`~sage.plot.plot3d.tri_plot.TriangleFactory` @@ -794,6 +797,8 @@ def smooth_triangle(self, a, b, c, da, db, dc, color=None): from . import parametric_plot3d + + def plot3d(f, urange, vrange, adaptive=False, transformation=None, **kwds): """ Plots a function in 3d. @@ -1109,15 +1114,16 @@ def plot3d(f, urange, vrange, adaptive=False, transformation=None, **kwds): elif adaptive: P = plot3d_adaptive(f, urange, vrange, **kwds) else: - arg1 = lambda u,v: u - arg2 = lambda u,v: v - P = parametric_plot3d.parametric_plot3d((arg1,arg2,f), + arg1 = lambda u, v: u + arg2 = lambda u, v: v + P = parametric_plot3d.parametric_plot3d((arg1, arg2, f), urange, vrange, **kwds) - P.frame_aspect_ratio([1.0,1.0,0.5]) + P.frame_aspect_ratio([1.0, 1.0, 0.5]) return P + def plot3d_adaptive(f, x_range, y_range, color="automatic", grad_f=None, max_bend=.5, max_depth=5, initial_depth=4, num_colors=128, **kwds): @@ -1323,6 +1329,7 @@ def spherical_plot3d(f, urange, vrange, **kwds): """ return plot3d(f, urange, vrange, transformation=Spherical('radius', ['azimuth', 'inclination']), **kwds) + def cylindrical_plot3d(f, urange, vrange, **kwds): """ Plots a function in cylindrical coordinates. This function is @@ -1397,6 +1404,7 @@ def cylindrical_plot3d(f, urange, vrange, **kwds): """ return plot3d(f, urange, vrange, transformation=Cylindrical('radius', ['azimuth', 'height']), **kwds) + def axes(scale=1, radius=None, **kwds): """ Creates basic axes in three dimensions. Each axis is a three @@ -1433,7 +1441,7 @@ def axes(scale=1, radius=None, **kwds): sphinx_plot(T) """ if radius is None: - radius = scale/100.0 - return Graphics3dGroup([arrow3d((0,0,0),(scale,0,0), radius, **kwds), - arrow3d((0,0,0),(0,scale,0), radius, **kwds), - arrow3d((0,0,0),(0,0,scale), radius, **kwds)]) + radius = scale / 100.0 + return Graphics3dGroup([arrow3d((0, 0, 0), (scale, 0, 0), radius, **kwds), + arrow3d((0, 0, 0), (0, scale, 0), radius, **kwds), + arrow3d((0, 0, 0), (0, 0, scale), radius, **kwds)]) diff --git a/src/sage/plot/plot3d/revolution_plot3d.py b/src/sage/plot/plot3d/revolution_plot3d.py index f3220dcd1a3..6349fabbdd5 100644 --- a/src/sage/plot/plot3d/revolution_plot3d.py +++ b/src/sage/plot/plot3d/revolution_plot3d.py @@ -25,6 +25,7 @@ from sage.plot.plot3d.parametric_plot3d import parametric_plot3d + @rename_keyword(alpha='opacity') def revolution_plot3d(curve,trange,phirange=None,parallel_axis='z',axis=(0,0),print_vector=False,show_curve=False,**kwds): r""" diff --git a/src/sage/plot/plot3d/shapes2.py b/src/sage/plot/plot3d/shapes2.py index 96e4646bbc4..7fe8aeb18a2 100644 --- a/src/sage/plot/plot3d/shapes2.py +++ b/src/sage/plot/plot3d/shapes2.py @@ -942,9 +942,8 @@ def tachyon_repr(self, render_params): radius = self.size * TACHYON_PIXEL texture = self.texture.id - return ("Sphere center {center[0]!r} {center[1]!r} {center[2]!r} " - "Rad {radius!r} {texture}").format(center=cen, radius=radius, - texture=texture) + return (f"Sphere center {cen[0]!r} {cen[1]!r} {cen[2]!r} " + f"Rad {radius!r} {texture}") def obj_repr(self, render_params): """ @@ -980,7 +979,7 @@ def jmol_repr(self, render_params): name = render_params.unique_name('point') transform = render_params.transform cen = self.loc if transform is None else transform(self.loc) - return ["draw %s DIAMETER %s {%s %s %s}\n%s" % (name, int(self.size), cen[0], cen[1], cen[2], self.texture.jmol_str('$' + name))] + return ["draw {} DIAMETER {} {{{} {} {}}}\n{}".format(name, int(self.size), cen[0], cen[1], cen[2], self.texture.jmol_str('$' + name))] def threejs_repr(self, render_params): r""" @@ -1010,7 +1009,7 @@ def threejs_repr(self, render_params): color = '#' + str(self.texture.hex_rgb()) opacity = float(self.texture.opacity) size = float(self.size) - point = dict(point=center, size=size, color=color, opacity=opacity) + point = {'point': center, 'size': size, 'color': color, 'opacity': opacity} return [('point', point)] def stl_binary_repr(self, render_params): @@ -1198,13 +1197,13 @@ def jmol_repr(self, render_params): TP = P if T is None else T(P) if P in corners: if cmd: - cmds.append(cmd + " {%s %s %s} " % TP) + cmds.append(cmd + " {{{} {} {}}} ".format(*TP)) cmds.append(self.texture.jmol_str('$' + name)) type = 'arrow' if self.arrow_head and P is last_corner else 'curve' name = render_params.unique_name('line') - cmd = "draw %s diameter %s %s {%s %s %s} " % (name, int(self.thickness), type, TP[0], TP[1], TP[2]) + cmd = "draw {} diameter {} {} {{{} {} {}}} ".format(name, int(self.thickness), type, TP[0], TP[1], TP[2]) else: - cmd += " {%s %s %s} " % TP + cmd += " {{{} {} {}}} ".format(*TP) cmds.append(cmd) cmds.append(self.texture.jmol_str('$' + name)) return cmds @@ -1392,7 +1391,7 @@ def threejs_repr(self, render_params): transform = render_params.transform if transform is not None: points = [transform(p) for p in points] - line = dict(points=points, color=color, opacity=opacity, linewidth=thickness) + line = {'points': points, 'color': color, 'opacity': opacity, 'linewidth': thickness} reprs.append(('line', line)) return reprs diff --git a/src/sage/plot/plot3d/tachyon.py b/src/sage/plot/plot3d/tachyon.py index 4e8184a7816..21a53787610 100644 --- a/src/sage/plot/plot3d/tachyon.py +++ b/src/sage/plot/plot3d/tachyon.py @@ -629,7 +629,7 @@ def _res(self): sage: t._res() '\nresolution 300 700\n' """ - return '\nresolution %s %s\n' % (self._xres, self._yres) + return f'\nresolution {self._xres} {self._yres}\n' def _camera(self): r""" @@ -651,20 +651,14 @@ def _camera(self): if self._aperture != '': camera_out = camera_out + r""" aperture %s""" % (float(self._aperture)) - camera_out = camera_out + r""" - zoom %s - aspectratio %s - antialiasing %s - raydepth %s - center %s - viewdir %s - updir %s""" % (float(self._zoom), - float(self._aspectratio), - int(self._antialiasing), - int(self._raydepth), - tostr(self._camera_position), - tostr(self._viewdir), - tostr(self._updir)) + camera_out = camera_out + fr""" + zoom {float(self._zoom)} + aspectratio {float(self._aspectratio)} + antialiasing {int(self._antialiasing)} + raydepth {int(self._raydepth)} + center {tostr(self._camera_position)} + viewdir {tostr(self._viewdir)} + updir {tostr(self._updir)}""" if self._frustum != '': camera_out = camera_out + r""" frustum %s""" % (tostr(self._frustum)) @@ -691,10 +685,10 @@ def str(self): """ return r""" begin_scene - %s - %s - %s - end_scene""" % (self._res(), + {} + {} + {} + end_scene""".format(self._res(), self._camera(), '\n'.join(x.str() for x in self._objects)) @@ -1083,7 +1077,7 @@ def parametric_plot(self, f, t_0, t_f, tex, r=.1, cylinders=True, e_rel=.01, e_abs=.01)) -class Light(): +class Light: r""" Represent lighting objects. @@ -1125,15 +1119,14 @@ def str(self): color 1.0 1.0 1.0 """ - return r""" - light center %s - rad %s - color %s - """ % (tostr(self._center), self._radius, - tostr(self._color)) + return fr""" + light center {tostr(self._center)} + rad {self._radius} + color {tostr(self._color)} + """ -class Texfunc(): +class Texfunc: def __init__(self, ttype=0, center=(0, 0, 0), rotate=(0, 0, 0), scale=(1, 1, 1), imagefile=''): @@ -1195,7 +1188,7 @@ def str(self): raise ValueError -class Texture(): +class Texture: def __init__(self, name, ambient=0.2, diffuse=0.8, specular=0.0, opacity=1.0, @@ -1255,10 +1248,10 @@ def str(self): ['ambient', '0.2', 'diffuse', '0.8'] """ return r""" - texdef %s ambient %s diffuse %s specular %s opacity %s - phong %s %s phong_size %s - color %s texfunc %s - """ % (self._name, + texdef {} ambient {} diffuse {} specular {} opacity {} + phong {} {} phong_size {} + color {} texfunc {} + """.format(self._name, self._ambient, self._diffuse, self._specular, @@ -1270,7 +1263,7 @@ def str(self): self._texfunc) -class Sphere(): +class Sphere: r""" A class for creating spheres in tachyon. """ @@ -1305,12 +1298,12 @@ def str(self): sage: s.str() '\n sphere center 1.0 1.0 1.0 rad 1.0 r\n ' """ - return r""" - sphere center %s rad %s %s - """ % (tostr(self._center), self._radius, self._texture) + return fr""" + sphere center {tostr(self._center)} rad {self._radius} {self._texture} + """ -class Ring(): +class Ring: r""" An annulus of zero thickness. """ @@ -1346,12 +1339,12 @@ def str(self): '\n ring center 0.0 0.0 0.0 normal 1.0 1.0 0.0 inner 1.0 outer 2.0 s\n ' """ return r""" - ring center %s normal %s inner %s outer %s %s - """ % (tostr(self._center), tostr(self._normal), + ring center {} normal {} inner {} outer {} {} + """.format(tostr(self._center), tostr(self._normal), self._inner, self._outer, self._texture) -class FractalLandscape(): +class FractalLandscape: r""" Axis-aligned fractal landscape. @@ -1388,12 +1381,12 @@ def str(self): '\n scape res 20 20 scale 30 30 center 1.0 2.0 3.0 s\n ' """ return r""" - scape res %s scale %s center %s %s - """ % (tostr(self._res, 2, int), tostr(self._scale, 2, int), + scape res {} scale {} center {} {} + """.format(tostr(self._res, 2, int), tostr(self._scale, 2, int), tostr(self._center), self._texture) -class Cylinder(): +class Cylinder: r""" An infinite cylinder. """ @@ -1429,11 +1422,11 @@ def str(self): '\n cylinder center 0.0 0.0 0.0 axis 1.0 1.0 1.0 rad 0.1 s\n ' """ return r""" - cylinder center %s axis %s rad %s %s - """ % (tostr(self._center), tostr(self._axis), self._radius, self._texture) + cylinder center {} axis {} rad {} {} + """.format(tostr(self._center), tostr(self._axis), self._radius, self._texture) -class Plane(): +class Plane: r""" An infinite plane. """ @@ -1465,12 +1458,12 @@ def str(self): sage: p.str() '\n plane center 1.0 2.0 3.0 normal 1.0 2.0 4.0 s\n ' """ - return r""" - plane center %s normal %s %s - """ % (tostr(self._center), tostr(self._normal), self._texture) + return fr""" + plane center {tostr(self._center)} normal {tostr(self._normal)} {self._texture} + """ -class FCylinder(): +class FCylinder: r""" A finite cylinder. """ @@ -1504,11 +1497,11 @@ def str(self): '\n fcylinder base 0.0 0.0 0.0 apex 1.0 1.0 1.0 rad 0.1 s\n ' """ return r""" - fcylinder base %s apex %s rad %s %s - """ % (tostr(self._center), tostr(self._axis), self._radius, self._texture) + fcylinder base {} apex {} rad {} {} + """.format(tostr(self._center), tostr(self._axis), self._radius, self._texture) -class Axis_aligned_box(): +class Axis_aligned_box: r""" Box with axis-aligned edges with the given min and max coordinates. """ @@ -1540,9 +1533,9 @@ def str(self): sage: aab.str() '\n box min 0.0 0.0 0.0 max 1.0 1.0 1.0 s\n ' """ - return r""" - box min %s max %s %s - """ % (tostr(self._min_p), tostr(self._max_p), self._texture) + return fr""" + box min {tostr(self._min_p)} max {tostr(self._max_p)} {self._texture} + """ class TachyonTriangle(Triangle): @@ -1560,10 +1553,10 @@ def str(self): sage: t.str() '\n TRI V0 -1.0 -1.0 -1.0 V1 0.0 0.0 0.0 V2 1.0 2.0 3.0 \n 0\n ' """ - return r""" - TRI V0 %s V1 %s V2 %s - %s - """ % (tostr(self._a), tostr(self._b), tostr(self._c), self._color) + return fr""" + TRI V0 {tostr(self._a)} V1 {tostr(self._b)} V2 {tostr(self._c)} + {self._color} + """ class TachyonSmoothTriangle(SmoothTriangle): @@ -1581,12 +1574,11 @@ def str(self): sage: t.str() '\n STRI V0 ... 1.0 0.0 0.0 N1 0.0 1.0 0.0 N2 0.0 0.0 1.0 \n 0\n ' """ - return r""" - STRI V0 %s V1 %s V2 %s - N0 %s N1 %s N2 %s - %s - """ % (tostr(self._a), tostr(self._b), tostr(self._c), - tostr(self._da), tostr(self._db), tostr(self._dc), self._color) + return fr""" + STRI V0 {tostr(self._a)} V1 {tostr(self._b)} V2 {tostr(self._c)} + N0 {tostr(self._da)} N1 {tostr(self._db)} N2 {tostr(self._dc)} + {self._color} + """ class TachyonTriangleFactory(TriangleFactory): @@ -1663,7 +1655,7 @@ def get_colors(self, list): return self._tachyon.texture_recolor(self._texture, list) -class ParametricPlot(): +class ParametricPlot: r""" Parametric plotting routines. """ diff --git a/src/sage/plot/plot3d/texture.py b/src/sage/plot/plot3d/texture.py index c4dee5d3ad5..7e67a08d590 100644 --- a/src/sage/plot/plot3d/texture.py +++ b/src/sage/plot/plot3d/texture.py @@ -339,9 +339,9 @@ def _repr_(self): Texture(texture..., ffff00) """ if self.name is not None: - return "Texture(%s, %s, %s)" % (self.id, self.name, self.hex_rgb()) + return f"Texture({self.id}, {self.name}, {self.hex_rgb()})" else: - return "Texture(%s, %s)" % (self.id, self.hex_rgb()) + return f"Texture({self.id}, {self.hex_rgb()})" def hex_rgb(self): """ @@ -353,7 +353,7 @@ def hex_rgb(self): sage: Texture((1, .5, 0)).hex_rgb() 'ff7f00' """ - return "%02x%02x%02x" % tuple(int(255 * s) for s in self.color) + return "{:02x}{:02x}{:02x}".format(*tuple(int(255 * s) for s in self.color)) def tachyon_str(self): r""" @@ -451,7 +451,7 @@ def jmol_str(self, obj): sage: sum([dodecahedron(center=[2.5*x, 0, 0], color=(1, 0, 0, x/10)) for x in range(11)]).show(aspect_ratio=[1,1,1], frame=False, zoom=2) """ translucent = "translucent %s" % float(1 - self.opacity) if self.opacity < 1 else "" - return "color %s %s [%s,%s,%s]" % (obj, translucent, + return "color {} {} [{},{},{}]".format(obj, translucent, int(255 * self.color[0]), int(255 * self.color[1]), int(255 * self.color[2])) diff --git a/src/sage/plot/plot3d/tri_plot.py b/src/sage/plot/plot3d/tri_plot.py index 256b0d348c3..5eb15299fe5 100644 --- a/src/sage/plot/plot3d/tri_plot.py +++ b/src/sage/plot/plot3d/tri_plot.py @@ -25,6 +25,7 @@ from math import sqrt import random + class Triangle: """ A graphical triangle class. @@ -64,7 +65,7 @@ class of the form sage: print(tri.str()) [0, 0, 0] [-1, 2, 3] [0, 2, 0] 0 """ - return "%s %s %s %s" % (self._a, self._b, self._c, self._color) + return f"{self._a} {self._b} {self._c} {self._color}" def set_color(self, color): """ @@ -95,6 +96,7 @@ def get_vertices(self): """ return (self._a, self._b, self._c) + class SmoothTriangle(Triangle): """ A class for smoothed triangles. @@ -135,7 +137,7 @@ def str(self): sage: print(t.str()) [1, 2, 3] [2, 3, 4] [0, 0, 0] 0 [0, 0, 1] [0, 1, 0] [1, 0, 0] """ - return "%s %s %s %s %s %s %s" % (self._a, self._b, self._c, self._color, self._da, self._db, self._dc) + return "{} {} {} {} {} {} {}".format(self._a, self._b, self._c, self._color, self._da, self._db, self._dc) def get_normals(self): """ diff --git a/src/sage/plot/plot_field.py b/src/sage/plot/plot_field.py index a54a6db185b..b1a9505eb81 100644 --- a/src/sage/plot/plot_field.py +++ b/src/sage/plot/plot_field.py @@ -2,7 +2,7 @@ """ Plotting fields """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2006 Alex Clemesha , # William Stein , # 2008 Mike Hansen , @@ -16,8 +16,8 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from sage.plot.primitive import GraphicPrimitive from sage.misc.decorators import options from sage.arith.srange import xsrange @@ -139,7 +139,7 @@ def _repr_(self): 20 """ - return "PlotField defined by a %s x %s vector grid" % ( + return "PlotField defined by a {} x {} vector grid".format( self._options['plot_points'], self._options['plot_points']) def _render_on_subplot(self, subplot): @@ -256,12 +256,12 @@ def plot_vector_field(f_g, xrange, yrange, **options): x,y = var('x,y') g = plot_vector_field((x,y), (x,-2,2), (y,-2,2), xmax=10) sphinx_plot(g) - """ - (f,g) = f_g + f, g = f_g from sage.plot.all import Graphics from sage.plot.misc import setup_for_eval_on_grid - z, ranges = setup_for_eval_on_grid([f,g], [xrange,yrange], options['plot_points']) + z, ranges = setup_for_eval_on_grid([f, g], [xrange, yrange], + options['plot_points']) f, g = z xpos_array, ypos_array, xvec_array, yvec_array = [], [], [], [] @@ -352,9 +352,12 @@ def plot_slope_field(f, xrange, yrange, **kwds): from sage.misc.functional import sqrt from sage.misc.sageinspect import is_function_or_cython_function if is_function_or_cython_function(f): - norm_inverse = lambda x,y: 1/sqrt(f(x, y)**2+1) - f_normalized = lambda x,y: f(x, y)*norm_inverse(x, y) + def norm_inverse(x, y): + return 1 / sqrt(f(x, y)**2 + 1) + + def f_normalized(x, y): + return f(x, y) * norm_inverse(x, y) else: - norm_inverse = 1 / sqrt((f**2+1)) + norm_inverse = 1 / sqrt(f**2 + 1) f_normalized = f * norm_inverse return plot_vector_field((norm_inverse, f_normalized), xrange, yrange, **slope_options) diff --git a/src/sage/plot/primitive.py b/src/sage/plot/primitive.py index 20c2987142f..ee9966846b1 100644 --- a/src/sage/plot/primitive.py +++ b/src/sage/plot/primitive.py @@ -21,6 +21,7 @@ from sage.structure.sage_object import SageObject from sage.misc.verbose import verbose + class GraphicPrimitive(WithEqualityById, SageObject): """ Base class for graphics primitives, e.g., things that knows how to draw @@ -183,7 +184,7 @@ def options(self): for k in O.keys(): if k not in K: do_verify = False - verbose("WARNING: Ignoring option '%s'=%s" % (k, O[k]), + verbose(f"WARNING: Ignoring option '{k}'={O[k]}", level=0) t = True if t: diff --git a/src/sage/plot/text.py b/src/sage/plot/text.py index 602cdfebddf..1c49c102b62 100644 --- a/src/sage/plot/text.py +++ b/src/sage/plot/text.py @@ -85,7 +85,7 @@ def _repr_(self): sage: t = T[0];t # needs sage.symbolic Text 'I like cool constants' at the point (3.1415926535...,2.7182818284...) """ - return "Text '%s' at the point (%s,%s)" % (self.string, self.x, self.y) + return f"Text '{self.string}' at the point ({self.x},{self.y})" def _allowed_options(self): """ diff --git a/src/sage/quadratic_forms/binary_qf.py b/src/sage/quadratic_forms/binary_qf.py index 1bd4e6db030..69a5f3be93b 100755 --- a/src/sage/quadratic_forms/binary_qf.py +++ b/src/sage/quadratic_forms/binary_qf.py @@ -1608,17 +1608,21 @@ def small_prime_value(self, Bmax=1000): raise ValueError("Unable to find a prime value of %s" % self) B += 10 - def solve_integer(self, n, *, algorithm="general"): + def solve_integer(self, n, *, algorithm="general", _flag=2): r""" Solve `Q(x, y) = n` in integers `x` and `y` where `Q` is this quadratic form. INPUT: - - ``n`` -- a positive integer + - ``n`` -- a positive integer or a + `:sage:`~sage.structure.factorization.Factorization` object - ``algorithm`` -- ``"general"`` (default) or ``"cornacchia"`` + - ``_flag`` -- ``1``, ``2`` (default) or ``3``; passed onto the pari + function``qfbsolve``. For internal use only. + To use the Cornacchia algorithm, the quadratic form must have `a=1` and `b=0` and `c>0`, and ``n`` must be a prime or four times a prime (but this is not checked). @@ -1630,6 +1634,8 @@ def solve_integer(self, n, *, algorithm="general"): ALGORITHM: :pari:`qfbsolve` or :pari:`qfbcornacchia` + TODO:: Replace `_flag` with human-readable parameters c.f. :issue:`37119` + EXAMPLES:: sage: Q = BinaryQF([1, 0, 419]) @@ -1665,6 +1671,14 @@ def solve_integer(self, n, *, algorithm="general"): sage: [Q.solve_integer(6) for Q in Qs] [(1, -1), (1, -1), (-1, -1)] + :: + + sage: # needs sage.libs.pari + sage: n = factor(126) + sage: Q = BinaryQF([1, 0, 5]) + sage: Q.solve_integer(n) + (11, -1) + TESTS: The returned solutions are correct (random inputs):: @@ -1714,22 +1728,39 @@ def solve_integer(self, n, *, algorithm="general"): sage: Q = Q.matrix_action_right(U) sage: Q.discriminant().is_square() True - sage: xy = Q.solve_integer(n) # needs sage.libs.pari - sage: Q(*xy) == n # needs sage.libs.pari + sage: # needs sage.libs.pari + sage: xy = Q.solve_integer(n) + sage: Q(*xy) == n True Also test the `n=0` special case separately:: - sage: xy = Q.solve_integer(0) # needs sage.libs.pari - sage: Q(*xy) # needs sage.libs.pari + sage: # needs sage.libs.pari + sage: xy = Q.solve_integer(0) + sage: Q(*xy) 0 - """ - n = ZZ(n) + Test for different `_flag` values:: + + sage: # needs sage.libs.pari + sage: Q = BinaryQF([1, 0, 5]) + sage: Q.solve_integer(126, _flag=1) + [(11, -1), (-1, -5), (-1, 5), (-11, -1)] + sage: Q.solve_integer(126, _flag=2) + (11, -1) + sage: Q.solve_integer(126, _flag=3) + [(11, -1), (-1, -5), (-1, 5), (-11, -1), (-9, -3), (9, -3)] + """ if self.is_negative_definite(): # not supported by PARI return (-self).solve_integer(-n) if self.is_reducible(): # square discriminant; not supported by PARI + from sage.structure.factorization import Factorization + if isinstance(n, Factorization): + n = ZZ(n.value()) + else: + n = ZZ(n) + if self._a: # https://math.stackexchange.com/a/980075 w = self.discriminant().sqrt() @@ -1772,9 +1803,10 @@ def solve_integer(self, n, *, algorithm="general"): if algorithm != 'general': raise ValueError(f'algorithm {algorithm!r} is not a valid algorithm') - flag = 2 # single solution, possibly imprimitive - sol = self.__pari__().qfbsolve(n, flag) - return tuple(map(ZZ, sol)) if sol else None + sol = self.__pari__().qfbsolve(n, _flag) + if _flag == 2: + return tuple(map(ZZ, sol)) if sol else None + return [tuple(map(ZZ, tup)) for tup in sol] def form_class(self): r""" diff --git a/src/sage/quadratic_forms/bqf_class_group.py b/src/sage/quadratic_forms/bqf_class_group.py index 6d3aefed881..4dc8d588110 100644 --- a/src/sage/quadratic_forms/bqf_class_group.py +++ b/src/sage/quadratic_forms/bqf_class_group.py @@ -140,7 +140,7 @@ def __init__(self, D, *, check=True): """ self._disc = ZZ(D) if check: - if not self._disc or self._disc % 4 not in (0,1): + if not self._disc or self._disc % 4 not in (0, 1): raise ValueError('not a discriminant') if self._disc > 0: raise NotImplementedError('positive discriminants are not yet supported') @@ -217,7 +217,7 @@ def random_element(self): c = (b**2 - self._disc) // (4*a) if randrange(2): b = -b - return self(BinaryQF([a,b,c])) + return self(BinaryQF([a, b, c])) def __hash__(self): r""" @@ -447,8 +447,8 @@ def _neg_(self): sage: cl + (-cl) == cl.parent().zero() # indirect doctest True """ - a,b,c = self._form - F = BinaryQF([a,-b,c]) + a, b, c = self._form + F = BinaryQF([a, -b, c]) return BQFClassGroup_element(F, parent=self.parent()) def _add_(self, other): @@ -677,14 +677,14 @@ def _project_bqf(bqf, q): """ q2 = q**2 disc = bqf.discriminant() - if not q2.divides(disc) or disc//q2 % 4 not in (0,1): + if not q2.divides(disc) or disc//q2 % 4 not in (0, 1): raise ValueError('discriminant not divisible by q^2') - a,b,c = bqf + a, b, c = bqf # lucky case: q^2|c (and q|b) if q2.divides(c): - a,b,c = c,-b,a + a, b, c = c, -b, a # general case: neither q^2|a nor q^2|c elif not q2.divides(a): @@ -704,21 +704,22 @@ def _project_bqf(bqf, q): assert False # find equivalent form with q^2|a (and q|b) - u,v = map(ZZ, (u,v)) - assert q2.divides(bqf(u,v)) + u, v = map(ZZ, (u, v)) + assert q2.divides(bqf(u, v)) if not v: v += q - g,r,s = u.xgcd(v) + g, r, s = u.xgcd(v) assert g.is_one() - M = matrix(ZZ, [[u,-v],[s,r]]) + M = matrix(ZZ, [[u, -v], [s, r]]) assert M.det().is_one() - a,b,c = bqf * M + a, b, c = bqf * M # remaining case: q^2|a (and q|b) assert q2.divides(a) assert q.divides(b) return BinaryQF(a//q2, b//q, c) + class BQFClassGroupQuotientMorphism(Morphism): r""" Let `D` be a discriminant and `f > 0` an integer. @@ -801,7 +802,7 @@ def _call_(self, elt): ALGORITHM: Repeated application of :func:`_project_bqf` for the prime factors in `f`. """ bqf = elt.form() - for q,m in self.f: + for q, m in self.f: for _ in range(m): bqf = _project_bqf(bqf, q) return self.codomain()(bqf) diff --git a/src/sage/quadratic_forms/constructions.py b/src/sage/quadratic_forms/constructions.py index 2fe117d2e29..da51beb1c2e 100644 --- a/src/sage/quadratic_forms/constructions.py +++ b/src/sage/quadratic_forms/constructions.py @@ -50,7 +50,7 @@ def BezoutianQuadraticForm(f, g): # Initialize the quadratic form R = f.base_ring() - P = PolynomialRing(R, ['x','y']) + P = PolynomialRing(R, ['x', 'y']) a, b = P.gens() n = max(f.degree(), g.degree()) Q = QuadraticForm(R, n) @@ -60,9 +60,9 @@ def BezoutianQuadraticForm(f, g): for i in range(n): for j in range(i, n): if i == j: - Q[i,j] = bez_poly.coefficient({a:i,b:j}) + Q[i, j] = bez_poly.coefficient({a: i, b: j}) else: - Q[i,j] = bez_poly.coefficient({a:i,b:j}) * 2 + Q[i, j] = bez_poly.coefficient({a: i, b: j}) * 2 return Q diff --git a/src/sage/quadratic_forms/genera/normal_form.py b/src/sage/quadratic_forms/genera/normal_form.py index 829b4783226..7d739ab9eb1 100644 --- a/src/sage/quadratic_forms/genera/normal_form.py +++ b/src/sage/quadratic_forms/genera/normal_form.py @@ -539,7 +539,7 @@ def _homogeneous_normal_form(G, w): e1 = D[-2, -2].unit_part() e2 = D[-1, -1].unit_part() e = {e1, e2} - E = [{3, 3}, {3, 5}, {5, 5}, {5, 7}] + E = [{3}, {3, 5}, {5}, {5, 7}] if e in E: B[-2:, :] = _relations(D[-2:, -2:], 1) * B[-2:, :] D = B * G * B.T diff --git a/src/sage/quadratic_forms/quadratic_form__local_density_interfaces.py b/src/sage/quadratic_forms/quadratic_form__local_density_interfaces.py index ec39e489494..2e5be4f59de 100644 --- a/src/sage/quadratic_forms/quadratic_form__local_density_interfaces.py +++ b/src/sage/quadratic_forms/quadratic_form__local_density_interfaces.py @@ -47,12 +47,13 @@ def local_density(self, p, m): # TO DO: Write a separate p-scale and p-norm routines! Q_local = self.local_normal_form(p) if n == 1: - p_valuation = valuation(Q_local[0,0], p) + p_valuation = valuation(Q_local[0, 0], p) else: - p_valuation = min(valuation(Q_local[0,0], p), valuation(Q_local[0,1], p)) + p_valuation = min(valuation(Q_local[0, 0], p), + valuation(Q_local[0, 1], p)) # If m is less p-divisible than the matrix, return zero - if ((m != 0) and (valuation(m,p) < p_valuation)): # Note: The (m != 0) condition protects taking the valuation of zero. + if ((m != 0) and (valuation(m, p) < p_valuation)): # Note: The (m != 0) condition protects taking the valuation of zero. return QQ(0) # If the form is imprimitive, rescale it and call the local density routine @@ -119,16 +120,17 @@ def local_primitive_density(self, p, m): # TO DO: Write a separate p-scale and p-norm routines! Q_local = self.local_normal_form(p) if n == 1: - p_valuation = valuation(Q_local[0,0], p) + p_valuation = valuation(Q_local[0, 0], p) else: - p_valuation = min(valuation(Q_local[0,0], p), valuation(Q_local[0,1], p)) + p_valuation = min(valuation(Q_local[0, 0], p), + valuation(Q_local[0, 1], p)) # If m is less p-divisible than the matrix, return zero - if ((m != 0) and (valuation(m,p) < p_valuation)): # Note: The (m != 0) condition protects taking the valuation of zero. - return QQ(0) + if m != 0 and valuation(m, p) < p_valuation: # Note: The (m != 0) condition protects taking the valuation of zero. + return QQ.zero() # If the form is imprimitive, rescale it and call the local density routine - p_adjustment = QQ(1) / p**p_valuation + p_adjustment = QQ.one() / p**p_valuation m_prim = QQ(m) / p**p_valuation Q_prim = Q_local.scale_by_factor(p_adjustment) diff --git a/src/sage/quadratic_forms/quadratic_form__local_field_invariants.py b/src/sage/quadratic_forms/quadratic_form__local_field_invariants.py index aff8dad2d62..4bfbfa3bb6a 100644 --- a/src/sage/quadratic_forms/quadratic_form__local_field_invariants.py +++ b/src/sage/quadratic_forms/quadratic_form__local_field_invariants.py @@ -237,12 +237,12 @@ def _rational_diagonal_form_and_transformation(self): # Diagonal matrix D = MS() for i in range(n): - D[i,i] = R[i,i] + D[i, i] = R[i, i] Q = Q.parent()(D) # Transformation matrix (inverted) T = MS(R.sage()) for i in range(n): - T[i,i] = K.one() + T[i, i] = K.one() try: return Q, ~T except ZeroDivisionError: @@ -256,13 +256,13 @@ def _rational_diagonal_form_and_transformation(self): for i in range(n): # Deal with rows where the diagonal entry is zero. - if Q[i,i] == 0: + if Q[i, i] == 0: # Look for a non-zero entry and use it to make the diagonal non-zero (if it exists) - for j in range(i+1, n): - if Q[i,j] != 0: + for j in range(i + 1, n): + if Q[i, j] != 0: temp = MS(1) - if Q[i,j] + Q[j,j] == 0: + if Q[i, j] + Q[j, j] == 0: temp[j, i] = -1 else: temp[j, i] = 1 @@ -274,9 +274,9 @@ def _rational_diagonal_form_and_transformation(self): # Create a matrix which deals with off-diagonal entries (all at once for each row) temp = MS(1) - for j in range(i+1, n): - if Q[i,j] != 0: - temp[i,j] = -Q[i,j] / (Q[i,i] * 2) # This should only occur when Q[i,i] != 0, which the above step guarantees. + for j in range(i + 1, n): + if Q[i, j] != 0: + temp[i, j] = -Q[i, j] / (Q[i, i] * 2) # This should only occur when Q[i,i] != 0, which the above step guarantees. Q = Q(temp) T = T * temp @@ -325,9 +325,9 @@ def signature_vector(self): n = 0 z = 0 for i in range(diag.dim()): - if diag[i,i] > 0: + if diag[i, i] > 0: p += 1 - elif diag[i,i] < 0: + elif diag[i, i] < 0: n += 1 else: z += 1 @@ -448,14 +448,16 @@ def hasse_invariant(self, p): n = Diag.dim() if R == QQ: - for j in range(n-1): - for k in range(j+1, n): - hasse_temp = hasse_temp * hilbert_symbol(Diag[j,j], Diag[k,k], p) + for j in range(n - 1): + for k in range(j + 1, n): + hasse_temp = hasse_temp * hilbert_symbol(Diag[j, j], + Diag[k, k], p) else: - for j in range(n-1): - for k in range(j+1, n): - hasse_temp = hasse_temp * R.hilbert_symbol(Diag[j,j], Diag[k,k], p) + for j in range(n - 1): + for k in range(j + 1, n): + hasse_temp = hasse_temp * R.hilbert_symbol(Diag[j, j], + Diag[k, k], p) return hasse_temp @@ -532,17 +534,19 @@ def hasse_invariant__OMeara(self, p): if R == QQ: for j in range(n): for k in range(j, n): - hasse_temp = hasse_temp * hilbert_symbol(Diag[j,j], Diag[k,k], p) + hasse_temp = hasse_temp * hilbert_symbol(Diag[j, j], + Diag[k, k], p) else: for j in range(n): for k in range(j, n): - hasse_temp = hasse_temp * R.hilbert_symbol(Diag[j,j], Diag[k,k], p) + hasse_temp = hasse_temp * R.hilbert_symbol(Diag[j, j], + Diag[k, k], p) return hasse_temp -def is_hyperbolic(self, p): +def is_hyperbolic(self, p) -> bool: r""" Check if the quadratic form is a sum of hyperbolic planes over the `p`-adic numbers `\QQ_p` or over the real numbers `\RR`. @@ -895,8 +899,8 @@ def compute_definiteness_string_by_determinants(self): return "degenerate" # Check the sign of the ratios of consecutive determinants of the upper triangular r x r submatrices - first_coeff = self[0,0] - for r in range(1,n+1): + first_coeff = self[0, 0] + for r in range(1, n + 1): I = list(range(r)) new_det = M.matrix_from_rows_and_columns(I, I).det() diff --git a/src/sage/quadratic_forms/quadratic_form__neighbors.py b/src/sage/quadratic_forms/quadratic_form__neighbors.py index 0486e0a4acb..25566313812 100644 --- a/src/sage/quadratic_forms/quadratic_form__neighbors.py +++ b/src/sage/quadratic_forms/quadratic_form__neighbors.py @@ -249,7 +249,7 @@ def find_p_neighbor_from_vec(self, p, y, return_matrix=False): return QF(Gnew) -def neighbor_iteration(seeds, p, mass=None, max_classes=ZZ(10)**3, +def neighbor_iteration(seeds, p, mass=None, max_classes=None, algorithm=None, max_neighbors=1000, verbose=False): r""" Return all classes in the `p`-neighbor graph of ``self``. @@ -308,9 +308,11 @@ def neighbor_iteration(seeds, p, mass=None, max_classes=ZZ(10)**3, Warning: not all classes in the genus were found [] """ - p = ZZ(p) from sage.quadratic_forms.quadratic_form import QuadraticForm from warnings import warn + p = ZZ(p) + if max_classes is None: + max_classes = 1000 if not all(isinstance(s, QuadraticForm) for s in seeds): raise ValueError("seeds must be a list of quadratic forms") if algorithm is None: diff --git a/src/sage/quadratic_forms/random_quadraticform.py b/src/sage/quadratic_forms/random_quadraticform.py index e372c625f57..0d751e1f584 100644 --- a/src/sage/quadratic_forms/random_quadraticform.py +++ b/src/sage/quadratic_forms/random_quadraticform.py @@ -13,7 +13,7 @@ # Routines to create a random quadratic form ## ################################################ -def random_quadraticform(R, n, rand_arg_list=[]): +def random_quadraticform(R, n, rand_arg_list=None): r""" Create a random quadratic form in `n` variables defined over the ring `R`. @@ -57,6 +57,8 @@ def random_quadraticform(R, n, rand_arg_list=[]): ... TypeError: the list of randomness arguments can have at most 3 elements """ + if rand_arg_list is None: + rand_arg_list = [] if len(rand_arg_list) > 3: raise TypeError("the list of randomness arguments can have " "at most 3 elements") @@ -72,7 +74,7 @@ def random_quadraticform(R, n, rand_arg_list=[]): def random_quadraticform_with_conditions(R, n, condition_list=[], - rand_arg_list=[]): + rand_arg_list=None): """ Create a random quadratic form in `n` variables defined over the ring `R` satisfying a list of boolean (i.e. True/False) conditions. @@ -95,6 +97,9 @@ def random_quadraticform_with_conditions(R, n, condition_list=[], [ * 2 2 ] [ * * 3 ] """ + if rand_arg_list is None: + rand_arg_list = [] + Q = random_quadraticform(R, n, rand_arg_list) done_flag = True @@ -119,7 +124,7 @@ def random_quadraticform_with_conditions(R, n, condition_list=[], return Q -def random_ternaryqf(rand_arg_list=[]): +def random_ternaryqf(rand_arg_list=None): """ Create a random ternary quadratic form. @@ -149,15 +154,16 @@ def random_ternaryqf(rand_arg_list=[]): [7 -8 2] [0 3 -6] """ - R = ZZ + if rand_arg_list is None: + rand_arg_list = [] if not rand_arg_list: - rand_list = [R.random_element() for _ in range(6)] + rand_list = [ZZ.random_element() for _ in range(6)] else: - rand_list = [R.random_element(*rand_arg_list) for _ in range(6)] + rand_list = [ZZ.random_element(*rand_arg_list) for _ in range(6)] return TernaryQF(rand_list) -def random_ternaryqf_with_conditions(condition_list=[], rand_arg_list=[]): +def random_ternaryqf_with_conditions(condition_list=[], rand_arg_list=None): """ Create a random ternary quadratic form satisfying a list of boolean (i.e. True/False) conditions. @@ -179,6 +185,8 @@ def random_ternaryqf_with_conditions(condition_list=[], rand_arg_list=[]): [3 4 2] [2 -2 -1] """ + if rand_arg_list is None: + rand_arg_list = [] Q = random_ternaryqf(rand_arg_list) done_flag = True diff --git a/src/sage/quadratic_forms/ternary_qf.py b/src/sage/quadratic_forms/ternary_qf.py index d5384632694..cb7eeb179c8 100644 --- a/src/sage/quadratic_forms/ternary_qf.py +++ b/src/sage/quadratic_forms/ternary_qf.py @@ -837,8 +837,8 @@ def pseudorandom_primitive_zero_mod_p(self, p): a, b, c, r, s, t = self.coefficients() while True: - r1 = randint(0,p-1) - r2 = randint(0,p-1) + r1 = randint(0, p-1) + r2 = randint(0, p-1) alpha = (b*r1**2+t*r1+a) % p if alpha != 0: diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 02f2d0be604..958347113c0 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -2066,7 +2066,7 @@ def scalar_multiplication(self, x): sage: phi = DrinfeldModule(A, [z, 0, 1, z]) sage: phi Drinfeld module defined by T |--> z*t^3 + t^2 + z - sage: phi.hom(T) + sage: phi.hom(T) # indirect doctest Endomorphism of Drinfeld module defined by T |--> z*t^3 + t^2 + z Defn: z*t^3 + t^2 + z diff --git a/src/sage/rings/function_field/function_field.py b/src/sage/rings/function_field/function_field.py index 16f83aaea84..53d53c3c2d4 100644 --- a/src/sage/rings/function_field/function_field.py +++ b/src/sage/rings/function_field/function_field.py @@ -1366,3 +1366,102 @@ def extension_constant_field(self, k): """ from .extensions import ConstantFieldExtension return ConstantFieldExtension(self, k) + + @cached_method + def jacobian(self, model=None, base_div=None, **kwds): + """ + Return the Jacobian of the function field. + + INPUT: + + - ``model`` -- (default: ``'hess'``) model to use for arithmetic + + - ``base_div`` -- an effective divisor + + The degree of the base divisor should satisfy certain degree condition + corresponding to the model used. The following table lists these + conditions. Let `g` be the genus of the function field. + + - ``hess``: ideal-based arithmetic; requires base divisor of degree `g` + + - ``km_large``: Khuri-Makdisi's large model; requires base divisor of + degree at least `2g + 1` + + - ``km_medium``: Khuri-Makdisi's medium model; requires base divisor of + degree at least `2g + 1` + + - ``km_small``: Khuri-Makdisi's small model requires base divisor of + degree at least `g + 1` + + We assume the function field has a rational place. If a base divisor is + not given, one is constructed using an arbitrary rational place. + + EXAMPLES:: + + sage: A. = AffineSpace(GF(5), 2) + sage: C = Curve(y^2*(x^3 - 1) - (x^3 - 2)) + sage: F = C.function_field() + sage: F.jacobian() + Jacobian of Function field in y defined by (x^3 + 4)*y^2 + 4*x^3 + 2 (Hess model) + + TESTS: + + sage: A. = AffineSpace(QQ, 2) + sage: C = Curve(y^2 - x^3 - 1, A).projective_closure() + sage: C.jacobian(model='hess') + Traceback (most recent call last): + ... + ValueError: failed to obtain a rational place; provide a base divisor + """ + from .place import FunctionFieldPlace + + if model is None: + model = 'hess' + + if base_div is None: + try: + base_place = self.get_place(1) + except AttributeError: + raise ValueError('failed to obtain a rational place; provide a base divisor') + if base_place is None: + raise ValueError('the function field has no rational place') + # appropriate base divisor is constructed below. + else: + if isinstance(base_div, FunctionFieldPlace): + base_div = base_div.divisor() + + g = self.genus() + curve = kwds.get('curve') + + if model.startswith('km'): + from .jacobian_khuri_makdisi import Jacobian + if model == 'km' or model.endswith('large'): + if base_div is None: + base_div = (2*g + 1) * base_place + if not base_div.degree() >= 2*g + 1: + raise ValueError("Khuri-Makdisi large model requires base divisor of degree " + "at least 2*g + 1 for genus g") + return Jacobian(self, base_div, model='large', curve=curve) + elif model.endswith('medium'): + if base_div is None: + base_div = (2*g + 1) * base_place + if not base_div.degree() >= 2*g + 1: + raise ValueError("Khuri-Makdisi medium model requires base divisor of degree " + "at least 2*g + 1 for genus g") + return Jacobian(self, base_div, model='medium', curve=curve) + elif model.endswith('small'): + if base_div is None: + base_div = (g + 1) * base_place + if not base_div.degree() >= g + 1: + raise ValueError("Khuri-Makdisi small model requires base divisor of degree " + "at least g + 1 for genus g") + return Jacobian(self, base_div, model='small', curve=curve) + elif model == 'hess': + from .jacobian_hess import Jacobian + if base_div is None: + base_div = g * base_place + if base_div.degree() != g: + raise ValueError("Hess model requires base divisor of degree g for genus g") + return Jacobian(self, base_div, curve=curve) + + raise ValueError("unknown model") diff --git a/src/sage/rings/function_field/function_field_polymod.py b/src/sage/rings/function_field/function_field_polymod.py index 9c44f7cb908..ef72935eba6 100644 --- a/src/sage/rings/function_field/function_field_polymod.py +++ b/src/sage/rings/function_field/function_field_polymod.py @@ -1925,6 +1925,50 @@ def residue_field(self, place, name=None): """ return place.residue_field(name=name) + def places_infinite(self, degree=1): + """ + Return a list of the infinite places with ``degree``. + + INPUT: + + - ``degree`` -- positive integer (default: `1`) + + EXAMPLES:: + + sage: # needs sage.rings.finite_rings + sage: F. = GF(2) + sage: K. = FunctionField(F) + sage: R. = PolynomialRing(K) + sage: L. = K.extension(t^4 + t - x^5) + sage: L.places_infinite(1) + [Place (1/x, 1/x^4*y^3)] + """ + return list(self._places_infinite(degree)) + + def _places_infinite(self, degree): + """ + Return a generator of *infinite* places with ``degree``. + + INPUT: + + - ``degree`` -- positive integer + + EXAMPLES:: + + sage: # needs sage.rings.finite_rings + sage: F. = GF(2) + sage: K. = FunctionField(F) + sage: R. = PolynomialRing(K) + sage: L. = K.extension(t^4 + t - x^5) + sage: L._places_infinite(1) + + """ + Oinf = self.maximal_order_infinite() + for prime, _, _ in Oinf.decomposition(): + place = prime.place() + if place.degree() == degree: + yield place + class FunctionField_char_zero(FunctionField_simple): """ @@ -2158,50 +2202,6 @@ def _places_finite(self, degree): if place.degree() == degree: yield place - def places_infinite(self, degree=1): - """ - Return a list of the infinite places with ``degree``. - - INPUT: - - - ``degree`` -- positive integer (default: `1`) - - EXAMPLES:: - - sage: # needs sage.rings.finite_rings - sage: F. = GF(2) - sage: K. = FunctionField(F) - sage: R. = PolynomialRing(K) - sage: L. = K.extension(t^4 + t - x^5) - sage: L.places_infinite(1) - [Place (1/x, 1/x^4*y^3)] - """ - return list(self._places_infinite(degree)) - - def _places_infinite(self, degree): - """ - Return a generator of *infinite* places with ``degree``. - - INPUT: - - - ``degree`` -- positive integer - - EXAMPLES:: - - sage: # needs sage.rings.finite_rings - sage: F. = GF(2) - sage: K. = FunctionField(F) - sage: R. = PolynomialRing(K) - sage: L. = K.extension(t^4 + t - x^5) - sage: L._places_infinite(1) - - """ - Oinf = self.maximal_order_infinite() - for prime, _, _ in Oinf.decomposition(): - place = prime.place() - if place.degree() == degree: - yield place - def gaps(self): """ Return the gaps of the function field. diff --git a/src/sage/rings/function_field/jacobian_base.py b/src/sage/rings/function_field/jacobian_base.py new file mode 100644 index 00000000000..0f58eb5817f --- /dev/null +++ b/src/sage/rings/function_field/jacobian_base.py @@ -0,0 +1,806 @@ +r""" +Jacobians of function fields + +This module provides base classes for Jacobians of function fields. + +Jacobian +-------- + +The Jacobian of a function field is created by default in the Hess model, with +a base divisor of degree `g` the genus of the function field. The base divisor +is automatically chosen if not given. :: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: F = C.function_field() + sage: J = F.jacobian() + sage: J + Jacobian of Function field in z defined by z^3 + 23*y^2*z + 6 (Hess model) + sage: J.base_divisor().degree() + 1 + +Explicitly specify a model if you want Jacobians in different models. :: + + sage: J_km = F.jacobian(model='km_large') + sage: J_km + Jacobian of Function field in z defined by z^3 + 23*y^2*z + 6 (Khuri-Makdisi large model) + +Group of rational points +------------------------ + +The group of rational points of a Jacobian is created from the Jacobian. A +point of the Jacobian group is determined by a divisor of degree zero. To +represent the point, a divisor of the form `D-B` is selected where `D` is an +effective divisor of the same degree with the base divisor `B`. Hence the point +is simply represented by the divisor `D`. :: + + sage: G = J.group() + sage: G.order() + 30 + sage: pl1 = C([1,8,1]).place() + sage: pl2 = C([2,10,1]).place() + sage: p1 = G.point(pl1 - pl2) + sage: p1 + [Place (y + 1, z + 6)] + sage: p2 = G.point(pl2 - pl1) + sage: p2 + [Place (y + 28, z + 6)] + sage: p1 + p2 == G.zero() + True + sage: p1.order() + 5 + +We can get the corresponding point in the Jacobian in a different model. :: + + sage: p1km = J_km(p1) + sage: p1km.order() + 5 + sage: p1km + Point of Jacobian determined by + [ 1 0 0 0 0 0 11 0 0] + [ 0 1 0 0 0 0 18 0 0] + [ 0 0 1 0 0 0 11 0 0] + [ 0 0 0 1 0 0 18 1 0] + [ 0 0 0 0 1 0 25 0 19] + [ 0 0 0 0 0 1 8 8 0] + +AUTHORS: + +- Kwankyu Lee (2022-01-24): initial version + +""" + +# **************************************************************************** +# Copyright (C) 2022 Kwankyu Lee +# +# 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 math + +from sage.arith.misc import integer_floor, integer_ceil + +from sage.structure.parent import Parent +from sage.structure.element import ModuleElement + +from sage.categories.commutative_additive_groups import CommutativeAdditiveGroups +from sage.categories.schemes import Jacobians +from sage.categories.pushout import ConstructionFunctor, pushout + +from sage.rings.integer_ring import IntegerRing +from sage.rings.integer import Integer + + +class JacobianPoint_base(ModuleElement): + """ + Abstract base class of points of Jacobian groups. + """ + pass + + +class JacobianPoint_finite_field_base(JacobianPoint_base): + """ + Points of Jacobians over finite fields. + """ + def order(self): + """ + Return the order of this point. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: F = C.function_field() + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: b = F.get_place(1) + sage: pl = C([-1,2,1]).place() + sage: p = G.point(pl - b) + sage: p.order() + 15 + + ALGORITHM: Shanks' Baby Step Giant Step + """ + G = self.parent() + B = G._bound_on_order() + q = integer_ceil(B.sqrt()) + zero = G.zero() + + # baby steps + b = [zero] + g = self + for i in range(q - 1): + if g == zero: + return i + 1 + b.append(g) + g = g + self + + # giant steps + g0 = self.multiple(-q) + g = g0 + for i in range(q - 1): + for r in range(q): + if g == b[r]: + return q * (i + 1) + r + g = g + g0 + + # order is neither smaller or nor larger than this + return q**2 + + def frobenius(self): + """ + Return the image of the point acted by the Frobenius automorphism. + + EXAMPLES:: + + sage: k = GF(7) + sage: A. = AffineSpace(k,2) + sage: C = Curve(y^2 + x^3 + 2*x + 1).projective_closure() + sage: J = C.jacobian(model='hess') + sage: G1 = J.group() + sage: G1.order() + 11 + sage: K = k.extension(3) + sage: G3 = J.group(K) + sage: pts1 = G1.get_points(11) + sage: pts3 = G3.get_points(12) + sage: pt = next(pt for pt in pts3 if pt not in pts1) + sage: pt.frobenius() == pt + False + sage: pt.frobenius().frobenius().frobenius() == pt + True + """ + G = self.parent() + return G._frobenius_on(self) + + +class JacobianGroupFunctor(ConstructionFunctor): + """ + A construction functor for Jacobian groups. + + EXAMPLES:: + + sage: k = GF(7) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: J = C.jacobian(model='hess') + sage: G = J.group() + sage: F, obj = G.construction() + sage: F + JacobianGroupFunctor + """ + rank = 20 + + def __init__(self, base_field, field): + """ + Initialize. + + TESTS:: + + sage: k = GF(7) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: J = C.jacobian(model='hess') + sage: K = k.extension(2) + sage: G = J.group(K) + sage: F, obj = G.construction() + sage: TestSuite(F).run() + """ + super().__init__(Jacobians(base_field), CommutativeAdditiveGroups()) + + self._field = field + + def _apply_functor(self, jacobian): + """ + Apply this functor to ``jacobian``. + + INPUT: + + - ``jacobian`` -- a Jacobian + + EXAMPLES:: + + sage: k = GF(7) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: J = C.jacobian(model='hess') + sage: K = k.extension(2) + sage: G = J.group(K) + sage: F, obj = G.construction() + sage: F(obj) is G # indirect doctest + True + """ + return jacobian.group(self._field) + + def merge(self, other): + """ + Return the functor merging ``self`` and ``other`` + + INPUT: + + - ``other`` -- a functor + + EXAMPLES:: + + sage: k = GF(7) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: J = C.jacobian(model='hess') + sage: K2 = k.extension(2) + sage: G2 = J.group(K2) + sage: K3 = k.extension(3) + sage: G3 = J.group(K3) + sage: sage.categories.pushout.pushout(G2, G3) # indirect doctest + Group of rational points of Jacobian over Finite Field in z6 of size 7^6 (Hess model) + """ + if not isinstance(other, JacobianGroupFunctor): + return None + if not self.domain() == other.domain(): + return None + K = pushout(self._field, other._field) + return JacobianGroupFunctor(self.domain().base(), K) + + +class JacobianGroup_base(Parent): + """ + Groups of rational points of Jacobians. + + INPUT: + + - ``parent`` -- a Jacobian + + - ``function_field`` -- a function field + + - ``base_div`` -- an effective divisor of the function field + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: J = C.jacobian(model='hess') + sage: J.group() + Group of rational points of Jacobian over Finite Field of size 7 (Hess model) + """ + _embedding_map_class = None + + def __init__(self, parent, function_field, base_div): + """ + Initialize. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: J = C.jacobian(model='hess') + sage: G = J.group() + sage: TestSuite(G).run(skip=['_test_elements', '_test_pickling']) + """ + super().__init__(base=IntegerRing(), category=CommutativeAdditiveGroups()) + + self._parent = parent + self._function_field = function_field + self._genus = parent._function_field.genus() # equals function_field.genus() + self._base_div = base_div + + def _repr_(self): + """ + Return the string representation of ``self``. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: J.group() + Group of rational points of Jacobian over Finite Field of size 7 (Hess model) + """ + F = self._function_field + k = F.constant_base_field() + return f'Group of rational points of Jacobian over {k}' + + def _coerce_map_from_(self, S): + """ + Return the coerce map from ``S`` if ``S`` is embedded to ``self``. + + EXAMPLES:: + + sage: k = GF(7) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: G1 = J.group() + sage: K = k.extension(3) + sage: G3 = J.group(K) + sage: G3.has_coerce_map_from(G1) + True + """ + if isinstance(S, JacobianGroup_base) and S.parent() is self.parent(): + K = self._function_field.constant_base_field() + k = S._function_field.constant_base_field() + if K.has_coerce_map_from(k): + return self._embedding_map_class(S, self) + return None + + def construction(self): + """ + Return the data for a functorial construction of this Jacobian group. + + EXAMPLES:: + + sage: k = GF(7) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: J = C.jacobian(model='hess') + sage: K2 = k.extension(2) + sage: G2 = J.group(K2) + sage: K3= k.extension(3) + sage: G3 = J.group(K3) + sage: p1, p2 = G2.get_points(2) + sage: q1, q2 = G3.get_points(2) + sage: (p1 + q1).parent() is (p2 + q2).parent() + True + """ + k = self._parent._function_field.constant_base_field() + K = self._function_field.constant_base_field() + return (JacobianGroupFunctor(k, K), self._parent) + + def parent(self): + """ + Return the Jacobian to which this Jacobian group belongs. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: J = C.jacobian(model='hess') + sage: G = J.group() + sage: G.parent() + Jacobian of Projective Plane Curve over Finite Field of size 7 + defined by x^3 - y^2*z - 2*z^3 (Hess model) + """ + return self._parent + + def function_field(self): + """ + Return the function field to which this Jacobian group attached. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: J = C.jacobian(model='hess') + sage: G = J.group() + sage: G.function_field() + Function field in z defined by z^3 + 4*y^2*z + 3 + """ + return self._function_field + + def base_divisor(self): + """ + Return the base divisor that is used to represent points of this group. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: G = J.group() + sage: G.base_divisor() + Place (1/y, 1/y*z) + sage: _ == 1*b + True + + The base divisor is the denominator (negative part) of the divisor of + degree zero that represents a point. :: + + sage: p = C([-1,2,1]).place() + sage: G.point(p - b).divisor() + - Place (1/y, 1/y*z) + + Place (y + 2, z + 1) + """ + return self._base_div + + +class JacobianGroup_finite_field_base(JacobianGroup_base): + """ + Jacobian groups of function fields over finite fields. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: J.group() + Group of rational points of Jacobian over Finite Field of size 7 (Hess model) + """ + def _bound_on_order(self): + """ + Return an upper bound on the order of the abelian group. + + This bound depends on the genus and the order of the constant field + of the function field. This simple bound is from [Hes2004]_. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: G = J.group() + sage: G._bound_on_order() + 23 + """ + F = self._function_field + q = F.constant_base_field().order() + g = self._genus + + c = 2*g/(q.sqrt() - 1) + return integer_floor(math.exp(c)*q**g) + + def order(self, algorithm='numeric'): + """ + Return the order of the Jacobian group. + + INPUT: + + - ``algorithm`` -- ``'numeric'`` (default) or ``'algebraic'`` + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: G = J.group() + sage: G.order() + 7 + """ + F = self._parent._function_field + g = F.genus() + b = self._function_field.constant_base_field().degree() // F.constant_base_field().degree() + + f = F.L_polynomial() + + if algorithm == 'numeric': + # numeric method - fast but might be inaccurate by numerical noise + from sage.rings.qqbar import AlgebraicField + h = Integer(math.prod([(1-a**(-b))**m for a, m in f.change_ring(AlgebraicField()).roots()])) + return h + + # algebraic method - slow + + es = [] + s = -1 + for i in range(1, 2*g + 1): + es.append(s*f[i]) + s = -s + es + + ps = [es[0]] + for i in range(1, 2*g): + p = 0 + s = 1 + for j in range(i): + p = p + s*es[j]*ps[-j-1] + s = -s + ps.append(p + s*(i + 1)*es[i]) + + while len(ps) < b*2*g: + p = 0 + s = 1 + for j in range(2*g): + p = p + s*es[j]*ps[-j-1] + s = -s + ps.append(p) + + qs = [ps[b*(i + 1) - 1] for i in range(2*g)] + + fs = [qs[0]] + for i in range(1, 2*g): + k = qs[i] + s = -1 + for j in range(i): + k = k + s*fs[j]*qs[i - j - 1] + s = -s + fs.append(-s*k // (i + 1)) + + bs = [1] + s = -1 + for i in range(2*g): + bs.append(s*fs[i]) + s = -s + + return sum(bs) + + def get_points(self, n): + """ + Return `n` points of the Jacobian group. + + If `n` is greater than the order of the group, then returns + all points of the group. + + INPUT: + + - ``n`` -- an integer + + EXAMPLES:: + + sage: k = GF(7) + sage: A. = AffineSpace(k,2) + sage: C = Curve(y^2 + x^3 + 2*x + 1).projective_closure() + sage: J = C.jacobian(model='hess') + sage: G = J.group() + sage: pts = G.get_points(G.order()) + sage: len(pts) + 11 + """ + lst = [] + S = iter(self) + try: + for i in range(n): + lst.append(next(S)) + except StopIteration: + pass + + return lst + + +class Jacobian_base(Parent): + """ + Jacobians of function fields. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(Y^2 + Y + x + 1/x) + sage: F.jacobian() + Jacobian of Function field in y defined by y^2 + y + (x^2 + 1)/x (Hess model) + """ + def __init__(self, function_field, base_div, **kwds): + """ + Initialize. + + TESTS:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(Y^2 + Y + x + 1/x) + sage: J = F.jacobian() + sage: TestSuite(J).run() + """ + self._function_field = function_field + self._base_div = base_div + self._system = {} + self._base_place = None + self._curve = kwds.get('curve') + super().__init__(category=Jacobians(function_field.constant_base_field()), + base=function_field.constant_base_field(), + facade=True) + + def _repr_(self): + """ + Return the string representation of ``self``. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(Y^2 + Y + x + 1/x) + sage: F.jacobian() + Jacobian of Function field in y defined by y^2 + y + (x^2 + 1)/x (Hess model) + """ + return f'Jacobian of {self.base_curve()}' + + def _an_element_(self): + """ + Return an element of ``self``. + + TESTS:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(Y^2 + Y + x + 1/x) + sage: J = F.jacobian() + sage: J.an_element() + [Place (1/x, 1/x*y)] + """ + return next(iter(self.group())) + + def __call__(self, x): + """ + Return the point of ``self`` constructed from ``x`` + + It is assumed that ``self`` and ``x`` are points of the Jacobians + attached to the same function field. + + TESTS:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(Y^2 + Y + x + 1/x) + sage: J_hess = F.jacobian(model='hess') + sage: G = J_hess.group() + sage: p = G.get_points(3)[2] + sage: Jkm = F.jacobian(model='km_large') + sage: q = Jkm(p) + sage: p.order() == q.order() + True + sage: J_hess(q) == p + True + """ + F = self._function_field + if isinstance(x, JacobianPoint_base): + Gx = x.parent() + Jx = Gx.parent() + if Jx._function_field is F: + k = Gx._function_field.constant_base_field() + G = self.group(k) + K = G._function_field + return G.point(K.divisor_group()(x.divisor())) + if x in F.place_set(): + return self(x - x.degree()*self._base_place) + if x == 0: + return self.group().zero() + if x in F.divisor_group(): + G = self.group() + return G.point(x) + raise ValueError(f"Cannot create a point of the Jacobian from {x}") + + def curve(self): + """ + Return the projective curve to which this Jacobian is attached. + + If the Jacobian was constructed from a function field, then returns nothing. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(Y^2 + Y + x + 1/x) + sage: J = F.jacobian() + sage: J.curve() + """ + return self._curve + + def base_curve(self): + """ + Return the base curve or the function field that abstractly defines a curve. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(Y^2 + Y + x + 1/x) + sage: J = F.jacobian() + sage: J.base_curve() + Function field in y defined by y^2 + y + (x^2 + 1)/x + """ + return self._function_field if self._curve is None else self._curve + + def facade_for(self): + """ + Return the system of groups that this Jacobian is a facade for. + + The Jacobian can be seen as a facade for all groups of rational points + over field extensions of the base constant field of the function field. + This method returns only the internally constructed system of such + groups. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(Y^2 + Y + x + 1/x) + sage: J = F.jacobian() + sage: J.facade_for() + [Group of rational points of Jacobian over Finite Field of size 2 (Hess model)] + """ + if not self._system: + return [self.group()] + return list(self.group(k) for k in self._system) + + def base_divisor(self): + """ + Return the base divisor used to construct the Jacobian. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(Y^2 + Y + x + 1/x) + sage: b = F.get_place(1) + sage: J = F.jacobian(base_div=b) + sage: J.base_divisor() == b + True + """ + return self._base_div + + def group(self, k_ext=None): + """ + Return the group of rational points of the Jacobian. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(Y^2 + Y + x + 1/x) + sage: b = F.get_place(1) + sage: J = F.jacobian(base_div=b) + sage: J.group() + Group of rational points of Jacobian over Finite Field of size 2 (Hess model) + """ + F = self._function_field + k = F.constant_base_field() + + if k_ext in self._system: + return self._system[k_ext][0] + + if k_ext is None or k_ext is k: + ext = F.extension_constant_field(k) + grp = self._group_class(self, F, self._base_div) + if self._base_place is not None: + grp._base_place = self._base_place + self._system[k] = (grp, ext) + else: + ext = F.extension_constant_field(k_ext) + base_div = ext.conorm_divisor(self._base_div) + grp = self._group_class(self, ext.top(), base_div) + if self._base_place is not None: + grp._base_place = ext.conorm_place(self._base_place) + self._system[k_ext] = (grp, ext) + + return grp + + def set_base_place(self, place): + """ + Set ``place`` as the base place. + + INPUT: + + - ``place`` -- a rational place of the function field. + + The base place `B` is used to map a rational place `P` of the function + field to the point of the Jacobian defined by the divisor `P - B`. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(Y^2 + Y + x + 1/x) + sage: J = F.jacobian() + sage: B = F.get_place(1) + sage: J.set_base_place(B) + sage: Q = F.places()[-1] + sage: J(Q) + [Place (x + 1, x*y + 1)] + sage: J(Q).parent() + Group of rational points of Jacobian over Finite Field of size 2 (Hess model) + sage: J(B) + [Place (x, x*y)] + sage: J(B).is_zero() + True + """ + self._base_place = place + + for k in self._system: + grp, ext = self._system[k] + grp._base_place = ext.conorm_place(place) diff --git a/src/sage/rings/function_field/jacobian_hess.py b/src/sage/rings/function_field/jacobian_hess.py new file mode 100644 index 00000000000..54980870b37 --- /dev/null +++ b/src/sage/rings/function_field/jacobian_hess.py @@ -0,0 +1,1046 @@ +r""" +Jacobians in Hess model + +This module implements Jacobian arithmetic based on divisor representation by +ideals. This approach to Jacobian arithmetic implementation is attributed to +Hess [Hes2002]_. + +Jacobian +-------- + +To create a Jacobian in Hess model, specify ``'hess'`` model and provide a base divisor +of degree `g`, which is the genus of the function field:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: C.geometric_genus() + 1 + sage: B = C([0,1,0]).place() + sage: B.degree() + 1 + sage: J = C.jacobian(model='hess', base_div=B) + sage: J + Jacobian of Projective Plane Curve over Finite Field of size 29 + defined by x^3 - y^2*z + 5*z^3 (Hess model) + +Group of rational points +------------------------ + +The group of rational points of a Jacobian is created from the Jacobian. A +point of the Jacobian group is determined by a divisor (of degree zero) of the +form `D - B` where `D` is an effective divisor of degree `g` and `B` is the base +divisor. Hence a point of the Jacobian group is represented by `D`. + +:: + + sage: G = J.group() + sage: P1 = C([1,8,1]).place() + sage: P2 = C([2,10,1]).place() + sage: p1 = G(P1) + sage: p2 = G(P2) + sage: p1 + [Place (y + 21, z + 28)] + sage: p2 + [Place (y + 24, z + 14)] + sage: p1 + p2 + [Place (y + 8, z + 28)] + +AUTHORS: + +- Kwankyu Lee (2022-01-24): initial version + +""" + +# **************************************************************************** +# Copyright (C) 2022 Kwankyu Lee +# +# 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.structure.unique_representation import UniqueRepresentation +from sage.structure.richcmp import op_EQ, richcmp + +from sage.categories.map import Map +from sage.categories.commutative_additive_groups import CommutativeAdditiveGroups +from sage.categories.homset import Hom + +from sage.arith.misc import integer_ceil +from sage.arith.functions import lcm + +from sage.rings.integer import Integer +from sage.matrix.constructor import matrix + +from sage.combinat.integer_vector_weighted import WeightedIntegerVectors + +from .place import FunctionFieldPlace +from .divisor import FunctionFieldDivisor + +from .jacobian_base import (Jacobian_base, + JacobianGroup_base, + JacobianGroup_finite_field_base, + JacobianPoint_base, + JacobianPoint_finite_field_base) + + +class JacobianPoint(JacobianPoint_base): + """ + Points of Jacobians represented by a pair of ideals. + + If a point of Jacobian is determined by `D`, then the divisor `D` is + represented by a pair of ideals in the finite maximal order and the + infinite maximal order of the function field. + + For efficiency reasons, the actual ideals stored are the inverted ideals + of the ideals representing the divisor `D`. + + INPUT: + + - ``parent`` -- Jacobian group + + - ``dS`` -- an ideal of the finite maximal order of a function field + + - ``ds`` -- an ideal of infinite maximal order of a function field + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: G = C.jacobian(model='hess', base_div=b).group() + sage: pl = C([1,8,1]).place() + sage: p = G.point(pl - b) + sage: dS, ds = p._data + sage: -(dS.divisor() + ds.divisor()) == pl + True + """ + def __init__(self, parent, dS, ds): + """ + Initialize. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: G = C.jacobian(model='hess', base_div=b).group() + sage: pl = C([1,8,1]).place() + sage: p = G.point(pl - b) + sage: TestSuite(p).run(skip=['_test_category','_test_pickling']) + """ + super().__init__(parent) + self._data = (dS, ds) + + def _repr_(self): + """ + Return the string representation of ``self``. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: G = C.jacobian(model='hess', base_div=b).group() + sage: G.zero() + [Place (1/y, 1/y*z)] + """ + dS, ds = self._data + divisor = (~dS).divisor() + (~ds).divisor() + return f'[{divisor}]' + + def __hash__(self): + """ + Return the hash of ``self``. + + EXAMPLES:: + + sage: K. = FunctionField(GF(2)); _. = K[] + sage: F. = K.extension(Y^3 - x^2*(x^2 + x + 1)^2) + sage: f = x/(y + 1) + sage: d = f.divisor() + sage: {d: 1} + {Place (1/x, 1/x^4*y^2 + 1/x^2*y + 1) + + Place (1/x, 1/x^2*y + 1) + + 3*Place (x, (1/(x^3 + x^2 + x))*y^2) + - 6*Place (x + 1, y + 1): 1} + """ + return hash(self._data) + + def _richcmp_(self, other, op): + """ + Compare ``self`` with ``other`` with respect to operator ``op``. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: pl1 = C([-1,2,1]).place() + sage: pl2 = C([2,10,1]).place() + sage: G = C.jacobian(model='hess', base_div=b).group() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: p1 == p1 + True + sage: p1 != p2 + True + sage: p1 > p1 + False + sage: p1 > p2 + False + sage: p1 < p2 + True + """ + if op is op_EQ: + J = self.parent() + idS, ids = self._data + jdS, jds = other._data + return J._normalize(idS / jdS, ids / jds) is not None + else: + return richcmp(self._data, other._data, op) + + def _add_(self, other): + """ + Return the sum of ``self`` and ``other``. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: pl1 = C([-1,2,1]).place() + sage: pl2 = C([2,10,1]).place() + sage: G = C.jacobian(model='hess', base_div=b).group() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: p1 + p2 + [Place (y + 8, z + 3)] + sage: p1 + p2 == p2 + p1 + True + """ + G = self.parent() + idS, ids = self._data + jdS, jds = other._data + bdS, bds = G._base_point + dS, ds = G._normalize(idS * jdS * bdS, ids * jds * bds) + return G.element_class(self.parent(), dS, ds) + + def _neg_(self): + """ + Return the negative of this point. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: G = C.jacobian(model='hess', base_div=b).group() + sage: pl = C([-1,2,1]).place() + sage: p = G.point(pl - b) + sage: -p + [Place (y + 27, z + 1)] + sage: -(-p) == p + True + """ + G = self.parent() + idS, ids = self._data + bdS2, bds2 = G._base_point_double + dS, ds = G._normalize(~(idS * bdS2), ~(ids * bds2)) + return G.element_class(self.parent(), dS, ds) + + def multiple(self, n): + """ + Return the ``n``-th multiple of this point. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: G = C.jacobian(model='hess', base_div=b).group() + sage: pl = C([-1,2,1]).place() + sage: p = G.point(pl - b) + sage: p.multiple(100) + [Place (1/y, 1/y*z + 8)] + """ + if n == 0: + return self.parent().zero() + + G = self.parent() + idS, ids = self._data + bdS, bds = G._base_point + bdS2, bds2 = G._base_point_double + idSbdS2 = idS * bdS2 + idsbds2 = ids * bds2 + + if n < 0: + bits = Integer(-n).digits(2) + else: + bits = Integer(n).digits(2) + bits.pop() + + dS = idS + ds = ids + for i in range(len(bits)): + b = bits.pop() + if b > 0: + dS, ds = G._normalize(dS * dS * idSbdS2 , ds * ds * idsbds2) + else: + dS, ds = G._normalize(dS * dS * bdS, ds * ds * bds) + if n < 0: + dS, ds = G._normalize(~(dS * bdS2), ~(ds * bds2)) + + return G.element_class(self.parent(), dS, ds) + + def addflip(self, other): + """ + Return the addflip of this and ``other`` point. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: G = C.jacobian(model='hess', base_div=b).group() + sage: pl1 = C([-1,2,1]).place() + sage: pl2 = C([2,19,1]).place() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: p1.addflip(p2) + [Place (y + 8, z + 27)] + sage: _ == -(p1 + p2) + True + """ + return -(self + other) + + def defining_divisor(self): + """ + Return the effective divisor that defines this point. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: G = C.jacobian(model='hess', base_div=b).group() + sage: pl = C([-1,2,1]).place() + sage: p = G.point(pl - b) + sage: p.defining_divisor() == pl + True + """ + dS, ds = self._data + return (~dS).divisor() + (~ds).divisor() + + def order(self, bound=None): + """ + Return the order of this point. + + ALGORITHM: Shanks' Baby Step Giant Step + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: G = C.jacobian(model='hess', base_div=b).group() + sage: p = C([-1,2,1]).place() + sage: pt = G.point(p - b) + sage: pt.order() + 30 + """ + if bound is None: # naive + J = self.parent() + zero = J.zero() + + m = self + r = 1 + while m != zero: + m = m + self + r += 1 + return r + + # if bound is given, deploy Shanks' Baby Step Giant Step + + J = self.parent() + B = J.bound_on_order() + q = integer_ceil(B.sqrt()) + zero = J.zero() + + # baby steps + b = [zero] + g = self + for i in range(q - 1): + if g == zero: + return i + 1 + b.append(g) + g = g + self + + # giant steps + g0 = (-q)*(self) + g = g0 + for i in range(q - 1): + for r in range(q): + if g == b[r]: + return q * (i + 1) + r + g = g + g0 + + # order is neither smaller or nor larger than this + return q**2 + + def divisor(self): + """ + Return the divisor representing this point. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: G = C.jacobian(model='hess', base_div=b).group() + sage: pl = C([-1,2,1]).place() + sage: p = G.point(pl - b) + sage: G.point(p.divisor()) == p + True + """ + J = self.parent() + dS, ds = self._data + return (~dS).divisor() + (~ds).divisor() - J._base_div + + +class JacobianPoint_finite_field(JacobianPoint, JacobianPoint_finite_field_base): + """ + Points of Jacobians over finite fields + """ + pass + + +class JacobianGroupEmbedding(Map): + """ + Embeddings between Jacobian groups. + + INPUT: + + - ``base_group`` -- Jacobian group over a base field + + - ``extension_group`` -- Jacobian group over an extension field + + EXAMPLES:: + + sage: k = GF(17) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: G1 = J.group() + sage: K = k.extension(3) + sage: G3 = J.group(K) + sage: G3.coerce_map_from(G1) + Jacobian group embedding map: + From: Group of rational points of Jacobian + over Finite Field of size 17 (Hess model) + To: Group of rational points of Jacobian + over Finite Field in z3 of size 17^3 (Hess model) + """ + def __init__(self, base_group, extension_group): + """ + Initialize. + + TESTS:: + + sage: k = GF(17) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: G1 = J.group() + sage: K = k.extension(3) + sage: G3 = J.group(K) + sage: map = G3.coerce_map_from(G1) + sage: TestSuite(map).run(skip=['_test_category', '_test_pickling']) + """ + F = base_group._function_field + F_base = F.base_field() + K = F.constant_base_field() + + F_ext = extension_group._function_field + F_ext_base = F_ext.base_field() + K_ext = F_ext.constant_base_field() + + # construct embedding of F into F_ext + embedK = K_ext.coerce_map_from(K) + embedF_base = F_base.hom(F_ext_base.gen(), embedK) + if F.degree() > 1: + embedF = F.hom(F_ext.gen(), embedF_base) + else: + embedF = embedF_base + + self._embedF = embedF + self._O_ext = F_ext.maximal_order() + self._Oinf_ext = F_ext.maximal_order_infinite() + + Map.__init__(self, Hom(base_group, extension_group, CommutativeAdditiveGroups())) + + def _repr_type(self): + """ + Return string representation of ``self``. + + TESTS:: + + sage: k = GF(17) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: G1 = J.group() + sage: K = k.extension(3) + sage: G3 = J.group(K) + sage: G3.coerce_map_from(G1) # indirect doctest + Jacobian group embedding map: + From: Group of rational points of Jacobian + over Finite Field of size 17 (Hess model) + To: Group of rational points of Jacobian + over Finite Field in z3 of size 17^3 (Hess model) + """ + return 'Jacobian group embedding' + + def _call_(self, x): + """ + Conorm map from F to F_ext. + + TESTS:: + + sage: k = GF(7) + sage: A. = AffineSpace(k, 2) + sage: C = Curve(y^5 - x^3 - 2*x - 1).projective_closure() + sage: J = C.jacobian(model='hess') + sage: G1 = J.group() + sage: K = k.extension(3) + sage: G3 = J.group(K) + sage: m = G3.coerce_map_from(G1) + sage: m(G1.zero()) == G3.zero() + True + """ + embedF = self._embedF + O_ext = self._O_ext + Oinf_ext = self._Oinf_ext + + idS,ids = x._data + dS = O_ext.ideal([embedF(g) for g in idS.gens()]) + ds = Oinf_ext.ideal([embedF(g) for g in ids.gens()]) + return self.codomain().element_class(self.codomain(), dS, ds) + + +class JacobianGroup(UniqueRepresentation, JacobianGroup_base): + """ + Groups of rational points of a Jacobian. + + INPUT: + + - ``parent`` -- a Jacobian + + - ``function_field`` -- a function field + + - ``base_div`` -- an effective divisor of the function field + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: J.group() + Group of rational points of Jacobian + over Finite Field of size 17 (Hess model) + """ + Element = JacobianPoint + _embedding_map_class = JacobianGroupEmbedding + + def __init__(self, parent, function_field, base_div): + """ + Initialize. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: G = J.group() + sage: TestSuite(G).run(skip=['_test_elements', '_test_pickling']) + """ + super().__init__(parent, function_field, base_div) + + bdS, bds = self._get_dS_ds(-base_div) + try: + bdS._gens_two() # speed up multiplication with these ideals + bds._ideal._gens_two() # by storing vector forms of two generators + except AttributeError: + pass + self._base_point = (bdS, bds) + self._base_point_double = (bdS * bdS, bds * bds) + + self._base_place = None + + def _repr_(self): + """ + Return the string representation of ``self``. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: J.group() + Group of rational points of Jacobian + over Finite Field of size 17 (Hess model) + """ + r = super()._repr_() + return r + ' (Hess model)' + + def _element_constructor_(self, x): + """ + Construct an element of ``self`` from ``x``. + + If ``x`` is an effective divisor, then it is assumed to of + degree `g`, the genus of the function field. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: G = C.jacobian(model='hess', base_div=b).group() + sage: G(0) + [Place (1/y, 1/y*z)] + """ + if x == 0: + return self.zero() + + if isinstance(x, FunctionFieldPlace): + if (self._base_place is not None + and x in self._function_field.place_set() + and x.degree() == 1): + x = x - self._base_place + else: + x = x.divisor() + + if (isinstance(x, FunctionFieldDivisor) + and x in self._function_field.divisor_group()): + if x.degree() == 0: + return self.point(x) + if x.is_effective(): + return self.element_class(self, *self._get_dS_ds(x)) + + raise ValueError(f"Cannot construct a point from {x}") + + def _get_dS_ds(self, divisor): + """ + Return (dS,ds) representation of the divisor. + + TESTS:: + + sage: k = GF(17) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: G = J.group() + sage: pl = C([2,8,1]).place() + sage: dS, ds = G._get_dS_ds(2*pl) + sage: (~dS).divisor() + (~ds).divisor() == 2*pl + True + """ + F = self._function_field + O = F.maximal_order() + Oinf = F.maximal_order_infinite() + + I = O.ideal(1) + J = Oinf.ideal(1) + for p in divisor._data: + m = divisor._data[p] + if p.is_infinite_place(): + J *= p.prime_ideal() ** (-m) + else: + I *= p.prime_ideal() ** (-m) + + return I, J + + def _normalize(self, I, J): + """ + Return a pair of normalized ideals from `I` and `J`. + + INPUT: + + - ``I`` -- an ideal of the finite maximal order + + - ``J`` -- an ideal of the infinite maximal order + + The output represents an effective divisor linearly equivalent to the + divisor represented by the given ideals `I` and `J`. + + ALGORITHM: + + Computes a function `f` in the Riemann-Roch space of the divisor `D` + represented by the (inverted) ideals `I` and `J`. The output is the + pair of the (inverted) ideals representing the effective divisor `(f) + D`, + which is linearly equivalent to `D`. + + TESTS:: + + sage: k = GF(17) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: G = J.group() + sage: pl = C([2,8,1]).place() + sage: p = G.point(pl - b) + sage: dS, ds = (p + p)._data # indirect doctest + sage: G.point((~dS).divisor() + (~ds).divisor() - b) == p + p + True + """ + F = self._function_field + n = F.degree() + + O = F.maximal_order() + Oinf = F.maximal_order_infinite() + + # Step 1: construct matrix M of rational functions in x such that + # M * B == C where B = [b1,b1,...,bn], C =[v1,v2,...,vn] + V,fr,to = F.free_module(map=True) + B = matrix([to(b) for b in J.gens_over_base()]) + C = matrix([to(v) for v in I.gens_over_base()]) + M = C * B.inverse() + + # Step 2: get the denominator d of M and set mat = d * M + den = lcm([e.denominator() for e in M.list()]) + R = den.parent() # polynomial ring + one = R.one() + mat = matrix(R, n, [e.numerator() for e in (den*M).list()]) + gens = list(I.gens_over_base()) + + # Step 3: transform mat to a weak Popov form, together with gens + + # initialise pivot_row and conflicts list + found = None + pivot_row = [[] for i in range(n)] + conflicts = [] + for i in range(n): + bestp = -1 + best = -1 + for c in range(n): + d = mat[i,c].degree() + if d >= best: + bestp = c + best = d + + if best <= den.degree(): + found = i + break + + if best >= 0: + pivot_row[bestp].append((i,best)) + if len(pivot_row[bestp]) > 1: + conflicts.append(bestp) + + if found is None: + # while there is a conflict, do a simple transformation + while conflicts: + c = conflicts.pop() + row = pivot_row[c] + i,ideg = row.pop() + j,jdeg = row.pop() + + if jdeg > ideg: + i,j = j,i + ideg,jdeg = jdeg,ideg + + coeff = - mat[i,c].lc() / mat[j,c].lc() + s = coeff * one.shift(ideg - jdeg) + + mat.add_multiple_of_row(i, j, s) + gens[i] += s * gens[j] + + row.append((j,jdeg)) + + bestp = -1 + best = -1 + for c in range(n): + d = mat[i,c].degree() + if d >= best: + bestp = c + best = d + + if best <= den.degree(): + found = i + break + + if best >= 0: + pivot_row[bestp].append((i,best)) + if len(pivot_row[bestp]) > 1: + conflicts.append(bestp) + else: + return None + + f = gens[found] + return (O.ideal(~f) * I, Oinf.ideal(~f) * J) + + def point(self, divisor): + """ + Return the point represented by the divisor of degree zero. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: G = J.group() + sage: p = C([-1,2,1]).place() + sage: G.point(p - b) + [Place (y + 2, z + 1)] + """ + c = divisor + self._base_div + f = c.basis_function_space()[0] + d = f.divisor() + c + dS, ds = self._get_dS_ds(d) + return self.element_class(self, dS, ds) + + @cached_method + def zero(self): + """ + Return the zero element of this group. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: G = J.group() + sage: G.zero() + [Place (1/y, 1/y*z)] + """ + bdS,bds = self._base_point + return self.element_class(self, ~bdS, ~bds) + + +class JacobianGroup_finite_field(JacobianGroup, JacobianGroup_finite_field_base): + """ + Jacobian groups of function fields over finite fields + + INPUT: + + - ``parent`` -- a Jacobian + + - ``function_field`` -- a function field + + - ``base_div`` -- an effective divisor of the function field + + EXAMPLES:: + + sage: k = GF(17) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: G1 = J.group() + sage: K = k.extension(3) + sage: G3 = J.group(K) + sage: G3.coerce_map_from(G1) + Jacobian group embedding map: + From: Group of rational points of Jacobian + over Finite Field of size 17 (Hess model) + To: Group of rational points of Jacobian + over Finite Field in z3 of size 17^3 (Hess model) + """ + Element = JacobianPoint_finite_field + + def __init__(self, parent, function_field, base_div): + """ + Initialize. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: G = J.group() + sage: TestSuite(G).run(skip=['_test_elements','_test_pickling']) + """ + super().__init__(parent, function_field, base_div) + + F = self._function_field + K = F.constant_base_field() + + R = F.base_field() # base rational function field + x = R.gen() + y = F.gen() + + r = self._parent._function_field.constant_base_field().degree() + frob_K = K.frobenius_endomorphism(r) + frob_R = R.hom(x, base_morphism=frob_K) + frob_F = F.hom(y, base_morphism=frob_R) + + self._frobenius = frob_F + + def __iter__(self): + """ + Return generator of points of this group. + + TESTS:: + + sage: k = GF(7) + sage: A. = AffineSpace(k,2) + sage: C = Curve(y^2 + x^3 + 2*x + 1).projective_closure() + sage: J = C.jacobian(model='hess') + sage: G = J.group() + sage: len([pt for pt in G]) + 11 + """ + g = self._parent._function_field.genus() + F = self._function_field + O = F.maximal_order() + Oinf = F.maximal_order_infinite() + + deg = 1 + support = [] + degrees = [] + multiples = [] + lst = [] + + places_infinite = F.places_infinite() + generators = [iter(places_infinite)] + num_of_infinite_places = len(places_infinite) + while True: + while True: + try: + new_pl = next(generators[-1]) + break + except StopIteration: + if deg > g: + return + generators.append(F._places_finite(deg)) + deg += 1 + multiples.append((g + 1)*[None]) + P = ~new_pl.prime_ideal() + dn = new_pl.degree() + I0 = O.ideal(1) + J0 = Oinf.ideal(1) + dr = 0 + for r in range(1, g // new_pl.degree() + 1): + if new_pl.is_infinite_place(): + J0 = J0 * P + else: + I0 = I0 * P + multiples[-1][r] = (I0, J0) + dr = dr + dn + for weights in WeightedIntegerVectors(g - dr, degrees): + I = I0 + J = J0 + for i in range(len(support)): + w = weights[i] + if w > 0: + dS, ds = multiples[i][w] + if i < num_of_infinite_places: + J *= ds # dS is the unit ideal + else: + I *= dS # ds is the unit ideal + pt = self.element_class(self, I, J) + if pt not in lst: + lst.append(pt) + yield pt + support.append(new_pl) + degrees.append(new_pl.degree()) + + def _frobenius_on(self, pt): + """ + Return the image of ``pt`` acted by the Frobenius automorphism. + + EXAMPLES:: + + sage: k = GF(7) + sage: A. = AffineSpace(k,2) + sage: C = Curve(y^2 + x^3 + 2*x + 1).projective_closure() + sage: J = C.jacobian(model='hess') + sage: G1 = J.group() + sage: G1.order() + 11 + sage: K = k.extension(3) + sage: G3 = J.group(K) + sage: pts1 = G1.get_points(11) + sage: pts3 = G3.get_points(12) + sage: pt = next(pt for pt in pts3 if pt not in pts1) + sage: pt.frobenius().frobenius().frobenius() == pt # indirect doctest + True + sage: pt.frobenius() == pt + False + """ + frob_F = self._frobenius + + F = self._function_field + O = F.maximal_order() + Oinf = F.maximal_order_infinite() + + idS,ids = pt._data + dS = O.ideal([frob_F(g) for g in idS.gens()]) + ds = Oinf.ideal([frob_F(g) for g in ids.gens()]) + return self.element_class(self, dS, ds) + + +class Jacobian(Jacobian_base, UniqueRepresentation): + """ + Jacobians of function fields. + + EXAMPLES:: + + sage: k = GF(17) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: C.jacobian(model='hess', base_div=b) + Jacobian of Projective Plane Curve over Finite Field of size 17 + defined by x^3 - y^2*z + 5*z^3 (Hess model) + """ + def __init__(self, function_field, base_div, **kwds): + """ + Initialize. + + TESTS:: + + sage: k = GF(17) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: J = C.jacobian(model='hess', base_div=b) + sage: TestSuite(J).run(skip=['_test_elements','_test_pickling']) + """ + super().__init__(function_field, base_div, **kwds) + + if function_field.constant_base_field().is_finite(): + self._group_class = JacobianGroup_finite_field + else: + self._group_class = JacobianGroup + + def _repr_(self): + """ + Return the string representation of ``self``. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: C.jacobian(model='hess', base_div=b) + Jacobian of Projective Plane Curve over Finite Field of size 17 + defined by x^3 - y^2*z + 5*z^3 (Hess model) + """ + r = super()._repr_() + return r + ' (Hess model)' diff --git a/src/sage/rings/function_field/jacobian_khuri_makdisi.py b/src/sage/rings/function_field/jacobian_khuri_makdisi.py new file mode 100644 index 00000000000..436ddb6ad32 --- /dev/null +++ b/src/sage/rings/function_field/jacobian_khuri_makdisi.py @@ -0,0 +1,1029 @@ +r""" +Jacobians in Khuri-Makdisi model + +This module implements Jacobian arithmetic by Khuri-Makdisi's algorithms +[Khu2004]_ based on divisor representation by linear spaces. + +Jacobian +-------- + +There are three models for Jacobian arithmetic by Khuri-Makdisi's algorithms. +For each of the models, one should provide a base divisor satisfying certain +degree condition. The following lists the names of the three models and the +corresponding conditions on base divisors. Let `g` be the genus of the function +field. + +- ``km_large``: large model; requires an effective divisor of degree at least `2g + 1` + +- ``km_medium``: medium model; requires an effective divisor of degree at least `2g + 1` + +- ``km_small``: small model; requires an effective divisor of degree at least `g + 1` + +To create a Jacobian in this model, specify ``'km_[large|medium|small]'`` as ``model`` and +provide a base divisor satisfying the degree condition. + +EXAMPLES: + +We construct a function field (of a projective curve) over a finite field:: + + sage: P2. = ProjectiveSpace(GF(29), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: C.geometric_genus() + 1 + sage: H = C.function(y/x).divisor_of_poles() + sage: H.degree() + 3 + +Now we use `H` as base divisor for the large and medium models:: + + sage: J_large = C.jacobian(model='km_large', base_div=H) + sage: J_large + Jacobian of Projective Plane Curve over Finite Field of size 29 + defined by x^3 - y^2*z + 5*z^3 (Khuri-Makdisi large model) + sage: J_medium = C.jacobian(model='km_medium', base_div=H) + sage: J_medium + Jacobian of Projective Plane Curve over Finite Field of size 29 + defined by x^3 - y^2*z + 5*z^3 (Khuri-Makdisi medium model) + +and for the small model, we construct an effective divisor of degree 2:: + + sage: B = sum(H.support()[:2]) + sage: B.degree() + 2 + sage: J_small = C.jacobian(model='km_small', base_div=B) + sage: J_small + Jacobian of Projective Plane Curve over Finite Field of size 29 + defined by x^3 - y^2*z + 5*z^3 (Khuri-Makdisi small model) + +Group of rational points +------------------------ + +The group of rational points of a Jacobian is created from the Jacobian. A +point of the Jacobian group is represented by a divisor `D - B` where `D` is +an effective divisor of the same degree with the base divisor `B`. The +divisor `D` in turn is determined by a linear subspace of the Riemann-Roch +space associated with certain multiple of `B` (depending on the model). This +allows representing points of Jacobian as matrices once we fix a basis of the +Riemann-Roch space. + + +EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: F = C.function_field() + sage: H = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=H) + sage: G = J.group() + sage: D = C([0,1,0]).place() + sage: P1 = C([-1,2,1]).place() + sage: P2 = C([3,7,1]).place() + sage: p1 = G.point(P1 - D) + sage: p2 = G.point(P2 - D) + sage: p1 + Point of Jacobian determined by + [ 1 0 0 0 0 0 0 12 15] + [ 0 1 0 0 0 0 0 0 13] + [ 0 0 1 0 0 0 0 0 2] + [ 0 0 0 1 0 0 0 0 16] + [ 0 0 0 0 0 1 0 0 15] + [ 0 0 0 0 0 0 1 0 1] + sage: p2 + Point of Jacobian determined by + [ 1 0 0 0 0 0 0 12 5] + [ 0 1 0 0 0 0 0 0 2] + [ 0 0 1 0 0 0 0 0 13] + [ 0 0 0 1 0 0 0 0 8] + [ 0 0 0 0 0 1 0 0 10] + [ 0 0 0 0 0 0 1 0 14] + sage: p1 + p2 + Point of Jacobian determined by + [ 1 0 0 0 0 16 0 5 3] + [ 0 1 0 0 0 6 0 8 16] + [ 0 0 1 0 0 15 0 3 10] + [ 0 0 0 1 0 3 0 0 0] + [ 0 0 0 0 1 12 0 16 8] + [ 0 0 0 0 0 0 1 3 0] + +AUTHORS: + +- Kwankyu Lee (2022-01-24): initial version + +""" + +# **************************************************************************** +# Copyright (C) 2022 Kwankyu Lee +# +# 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.structure.unique_representation import UniqueRepresentation +from sage.structure.richcmp import op_EQ, richcmp + +from sage.categories.map import Map +from sage.categories.commutative_additive_groups import CommutativeAdditiveGroups +from sage.categories.homset import Hom + +from sage.matrix.constructor import matrix + +from sage.combinat.integer_vector_weighted import WeightedIntegerVectors + +from .place import FunctionFieldPlace +from .divisor import FunctionFieldDivisor + +from .jacobian_base import (Jacobian_base, + JacobianGroup_base, + JacobianGroup_finite_field_base, + JacobianPoint_base, + JacobianPoint_finite_field_base) + + +class JacobianPoint(JacobianPoint_base): + """ + Points of a Jacobian group. + + INPUT: + + - ``parent`` -- Jacobian group + + - ``w`` -- matrix + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: pl = C([3,2,1]).place() + sage: G.point(pl - b) + Point of Jacobian determined by + [1 0 0 0 0 0 0 2 3] + [0 1 0 0 0 0 0 0 3] + [0 0 1 0 0 0 0 0 1] + [0 0 0 1 0 0 0 0 5] + [0 0 0 0 0 1 0 0 5] + [0 0 0 0 0 0 1 0 4] + + """ + def __init__(self, parent, w): + """ + Initialize. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: pl = C([3,2,1]).place() + sage: p = G.point(pl - b) + sage: TestSuite(p).run(skip=['_test_category','_test_pickling']) + """ + super().__init__(parent) + w.set_immutable() + self._w = w + + def _repr_(self): + """ + Return the string representation of ``self``. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: G.zero() + Point of Jacobian determined by + [1 0 0 0 0 0 0 0 0] + [0 1 0 0 0 0 0 0 0] + [0 0 1 0 0 0 0 0 0] + [0 0 0 0 1 0 0 0 0] + [0 0 0 0 0 1 0 0 0] + [0 0 0 0 0 0 0 1 0] + """ + return f'Point of Jacobian determined by \n{self._w}' + + def __hash__(self): + """ + Return the hash of ``self``. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: F = C.function_field() + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: zero = G.zero() + sage: {zero: 1} + {Point of Jacobian determined by + [1 0 0 0 0 0 0 0 0] + [0 1 0 0 0 0 0 0 0] + [0 0 1 0 0 0 0 0 0] + [0 0 0 0 1 0 0 0 0] + [0 0 0 0 0 1 0 0 0] + [0 0 0 0 0 0 0 1 0]: 1} + """ + return hash(self._w) + + def _richcmp_(self, other, op): + """ + Compare ``self`` with ``other`` with respect to operator ``op``. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: b = C([0,1,0]).place() + sage: pl1 = C([-1,2,1]).place() + sage: pl2 = C([3,2,1]).place() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: p1 == p1 + True + sage: p1 != p2 + True + sage: p1 > p1 + False + sage: p1 > p2 + True + sage: p1 < p2 + False + """ + if op is op_EQ: + km = self.parent()._km + return km.equal(self._w, other._w) + else: + return richcmp(self._w, other._w, op) + + def _add_(self, other): + """ + Return the sum of ``self`` and ``other``. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: b = C([0,1,0]).place() + sage: pl1 = C([-1,2,1]).place() + sage: pl2 = C([3,2,1]).place() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: p1 + p2 + Point of Jacobian determined by + [1 0 0 0 0 0 3 1 1] + [0 1 0 0 0 0 3 0 2] + [0 0 1 0 0 0 0 3 0] + [0 0 0 1 0 0 0 0 3] + [0 0 0 0 1 0 4 0 3] + [0 0 0 0 0 1 6 5 2] + sage: p1 + p2 == p2 + p1 + True + """ + G = self.parent() + km = G._km + return G.element_class(self.parent(), km.add(self._w, other._w)) + + def _neg_(self): + """ + Return the negative of this point. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: F = C.function_field() + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: b = F.get_place(1) + sage: p = C([-1,2,1]).place() + sage: pt = G.point(p - b) + sage: -pt + Point of Jacobian determined by + [1 0 0 0 0 0 1 6 0] + [0 1 0 0 0 0 2 6 3] + [0 0 1 0 0 0 4 1 1] + [0 0 0 1 0 0 1 4 1] + [0 0 0 0 1 0 5 2 6] + [0 0 0 0 0 1 3 1 3] + sage: -(-pt) == pt + True + """ + G = self.parent() + km = G._km + return G.element_class(self.parent(), km.negate(self._w)) + + def _rmul_(self, n): + """ + Return the ``n``-th multiple of this point. + + INPUT: + + - ``n`` -- an integer + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: b = C([0,1,0]).place() + sage: pl = C([-1,2,1]).place() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: p = G.point(pl - b) + sage: 10*(10*p) == 100*p + True + """ + return self.multiple(n) + + def multiple(self, n): + """ + Return the ``n``-th multiple of this point. + + INPUT: + + - ``n`` -- an integer + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: b = C([0,1,0]).place() + sage: pl = C([-1,2,1]).place() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: p = G.point(pl - b) + sage: p.multiple(100) + Point of Jacobian determined by + [1 0 0 0 0 2 0 1 1] + [0 1 0 0 0 5 0 1 6] + [0 0 1 0 0 2 0 6 3] + [0 0 0 1 0 1 0 0 0] + [0 0 0 0 1 5 0 1 4] + [0 0 0 0 0 0 1 1 0] + """ + G = self.parent() + km = G._km + return G.element_class(self.parent(), km.multiple(self._w, n)) + + def addflip(self, other): + """ + Return the addflip of this and ``other`` point. + + The addflip of two points is by definition the negative of the sum of + the points. This operation is faster than addition in Jacobian + arithmetic by Khuri-Makdisi algorithms. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: b = C([0,1,0]).place() + sage: pl1 = C([-1,2,1]).place() + sage: pl2 = C([3,2,1]).place() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: p1.addflip(p2) + Point of Jacobian determined by + [1 0 0 0 0 0 0 2 6] + [0 1 0 0 0 0 0 0 3] + [0 0 1 0 0 0 0 0 4] + [0 0 0 1 0 0 0 0 3] + [0 0 0 0 0 1 0 0 5] + [0 0 0 0 0 0 1 0 2] + sage: _ == -(p1 + p2) + True + """ + G = self.parent() + km = G._km + return G.element_class(self.parent(), km.addflip(self._w, other._w)) + + def defining_matrix(self): + """ + Return the matrix whose row span determines the effective divisor + representing this point. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: b = C([0,1,0]).place() + sage: pl = C([-1,2,1]).place() + sage: p = G.point(pl - b) + sage: p.defining_matrix() + [1 0 0 0 0 0 0 2 5] + [0 1 0 0 0 0 0 0 3] + [0 0 1 0 0 0 0 0 2] + [0 0 0 1 0 0 0 0 6] + [0 0 0 0 0 1 0 0 5] + [0 0 0 0 0 0 1 0 1] + """ + return self._w + + def divisor(self): + """ + Return the divisor representing this point. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: F = C.function_field() + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: b = F.get_place(1) + sage: p = C([-1,2,1]).place() + sage: pt = G.point(p - b) + sage: G.point(pt.divisor()) == pt + True + + ALGORITHM: Lemma 2.1 of [Khu2004]_. + """ + G = self.parent() + F = G._function_field + data = [G._from_L(f).divisor() for f in self._w.rows()] + supp = set() + for d in data: + supp.update(d.support()) + supp = list(supp) + d = F.divisor_group().zero() + for p in supp: + d += min(d.valuation(p) for d in data) * p.divisor() + return d + G._div_L - G._base_div + + +class JacobianPoint_finite_field(JacobianPoint, JacobianPoint_finite_field_base): + pass + + +class JacobianGroupEmbedding(Map): + """ + Embeddings between Jacobian groups. + + INPUT: + + - ``base_group`` -- Jacobian group over a base field + + - ``extension_group`` -- Jacobian group over an extension field + + EXAMPLES:: + + sage: k = GF(5) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + z^3 - y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G1 = J.group() + sage: K = k.extension(2) + sage: G2 = J.group(K) + sage: G2.coerce_map_from(G1) + Jacobian group embedding map: + From: Group of rational points of Jacobian + over Finite Field of size 5 (Khuri-Makdisi large model) + To: Group of rational points of Jacobian + over Finite Field in z2 of size 5^2 (Khuri-Makdisi large model) + """ + def __init__(self, base_group, extension_group): + """ + Initialize. + + TESTS:: + + sage: k = GF(5) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + z^3 - y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G1 = J.group() + sage: K = k.extension(2) + sage: G2 = J.group(K) + sage: map = G2.coerce_map_from(G1) + sage: TestSuite(map).run(skip=['_test_category', '_test_pickling']) + """ + F_ext = extension_group._function_field + K_ext = F_ext.constant_base_field() + + self._K_ext = K_ext + + Map.__init__(self, Hom(base_group, extension_group, CommutativeAdditiveGroups())) + + def _repr_type(self): + """ + Return string representation of ``self``. + + TESTS:: + + sage: k = GF(5) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + z^3 - y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G1 = J.group() + sage: K = k.extension(2) + sage: G2 = J.group(K) + sage: G2.coerce_map_from(G1) # indirect doctest + Jacobian group embedding map: + From: Group of rational points of Jacobian + over Finite Field of size 5 (Khuri-Makdisi large model) + To: Group of rational points of Jacobian + over Finite Field in z2 of size 5^2 (Khuri-Makdisi large model) + """ + return 'Jacobian group embedding' + + def _call_(self, x): + """ + Conorm map from F to F_ext. + + TESTS:: + + sage: k = GF(5) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + z^3 - y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G1 = J.group() + sage: K = k.extension(2) + sage: G2 = J.group(K) + sage: m = G2.coerce_map_from(G1) + sage: m(G1.zero()) == G2.zero() + True + """ + w = (x._w).change_ring(self._K_ext) + + return self.codomain().element_class(self.codomain(), w) + + +class JacobianGroup(UniqueRepresentation, JacobianGroup_base): + """ + Groups of rational points of a Jacobian. + + INPUT: + + - ``parent`` -- a Jacobian + + - ``function_field`` -- a function field + + - ``base_div`` -- an effective divisor of the function field + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: J.group() + Group of rational points of Jacobian + over Finite Field of size 7 (Khuri-Makdisi large model) + """ + Element = JacobianPoint + _embedding_map_class = JacobianGroupEmbedding + + def __init__(self, parent, function_field, base_div): + """ + Initialize. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + """ + super().__init__(parent, function_field, base_div) + + D0 = base_div + + self._V_cache = 10*[None] + + V_cache = self._V_cache + + def V(n): + if n in V_cache: + return V_cache[n] + + Vn, from_Vn, to_Vn = (n * D0).function_space() + V_cache[n] = (Vn, from_Vn, to_Vn) + + return Vn, from_Vn, to_Vn + + def mu(n, m, i, j): + Vn, from_Vn, to_Vn = V(n) + Vm, from_Vm, to_Vm = V(m) + Vnm, from_Vnm, to_Vnm = V(n + m) + return to_Vnm(from_Vn(Vn.gen(i)) * from_Vm(Vm.gen(j))) + + model = parent._model + + if model == 'large': + div_L = 3 * D0 + L, from_L, to_L = V(3) + from sage.rings.function_field.khuri_makdisi import KhuriMakdisi_large as KM + elif model == 'medium': + div_L = 2 * D0 + L, from_L, to_L = V(2) + from sage.rings.function_field.khuri_makdisi import KhuriMakdisi_medium as KM + elif model == 'small': + div_L = 3 * D0 + L, from_L, to_L = V(3) + from sage.rings.function_field.khuri_makdisi import KhuriMakdisi_small as KM + + self._div_L = div_L + self._L = L + self._from_L = from_L + self._to_L = to_L + + w0 = self.point(function_field.divisor_group().zero()).defining_matrix() + km = KM(lambda n: V(n)[0], mu, w0, D0.degree(), self._genus) + + self._km = km + self._w0 = w0 + + self._base_place = None + + def _repr_(self): + """ + Return the string representation of ``self``. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: J.group() + Group of rational points of Jacobian + over Finite Field of size 7 (Khuri-Makdisi large model) + """ + r = super()._repr_() + return r + f' (Khuri-Makdisi {self._parent._model} model)' + + def _wd_from_divisor(self, x): + """ + Return the matrix representing the divisor ``x``. + + INPUT: + + - ``x`` -- an effective divisor + + TESTS: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_medium', base_div=h) + sage: G = J.group() + sage: P = C([-1,2,1]).place() + sage: G._wd_from_divisor(2*P) + [1 0 0 0 4 0] + [0 1 0 0 4 6] + [0 0 1 0 2 1] + [0 0 0 1 1 6] + """ + WD = (self._div_L - x).basis_function_space() + wd = matrix([self._to_L(f) for f in WD]) + wd.echelonize() + return wd + + def _element_constructor_(self, x): + """ + Construct an element of ``self`` from ``x``. + + If ``x`` is an effective divisor, then it is assumed to have the same + degree with the base divisor. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: G(0) + Point of Jacobian determined by + [1 0 0 0 0 0 0 0 0] + [0 1 0 0 0 0 0 0 0] + [0 0 1 0 0 0 0 0 0] + [0 0 0 0 1 0 0 0 0] + [0 0 0 0 0 1 0 0 0] + [0 0 0 0 0 0 0 1 0] + sage: b = C([0,1,0]).place() + sage: J.set_base_place(b) + sage: p = C([-1,2,1]).place() + sage: G(p) + Point of Jacobian determined by + [1 0 0 0 0 0 0 2 5] + [0 1 0 0 0 0 0 0 3] + [0 0 1 0 0 0 0 0 2] + [0 0 0 1 0 0 0 0 6] + [0 0 0 0 0 1 0 0 5] + [0 0 0 0 0 0 1 0 1] + """ + if x == 0: + return self.zero() + + if isinstance(x, FunctionFieldPlace): + if (self._base_place is not None + and x in self._function_field.place_set() + and x.degree() == 1): + x = x - self._base_place + else: + x = x.divisor() + + if (isinstance(x, FunctionFieldDivisor) + and x in self._function_field.divisor_group()): + if x.degree() == 0: + return self.point(x) + if x.is_effective(): + wd = self._wd_from_divisor(x) + return self.element_class(self, wd) + + raise ValueError(f"Cannot construct a point from {x}") + + def point(self, divisor): + """ + Return the point represented by the divisor. + + INPUT: + + - ``divisor`` -- a divisor of degree zero + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: b = C([0,1,0]).place() + sage: p = C([-1,2,1]).place() + sage: G.point(p - b) + Point of Jacobian determined by + [1 0 0 0 0 0 0 2 5] + [0 1 0 0 0 0 0 0 3] + [0 0 1 0 0 0 0 0 2] + [0 0 0 1 0 0 0 0 6] + [0 0 0 0 0 1 0 0 5] + [0 0 0 0 0 0 1 0 1] + """ + if divisor.degree() != 0: + raise ValueError('divisor not of degree zero') + + c = divisor + self._base_div + f = c.basis_function_space()[0] + d = f.divisor() + c + + wd = self._wd_from_divisor(d) + return self.element_class(self, wd) + + @cached_method + def zero(self): + """ + Return the zero element of this group. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: G.zero() + Point of Jacobian determined by + [1 0 0 0 0 0 0 0 0] + [0 1 0 0 0 0 0 0 0] + [0 0 1 0 0 0 0 0 0] + [0 0 0 0 1 0 0 0 0] + [0 0 0 0 0 1 0 0 0] + [0 0 0 0 0 0 0 1 0] + """ + return self.element_class(self, self._w0) + + +class JacobianGroup_finite_field(JacobianGroup, JacobianGroup_finite_field_base): + """ + Jacobian groups of function fields over finite fields. + + INPUT: + + - ``parent`` -- a Jacobian + + - ``function_field`` -- a function field + + - ``base_div`` -- an effective divisor of the function field + + EXAMPLES:: + + sage: k = GF(7) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G1 = J.group() + sage: K = k.extension(2) + sage: G2 = J.group(K) + sage: G2.coerce_map_from(G1) + Jacobian group embedding map: + From: Group of rational points of Jacobian + over Finite Field of size 7 (Khuri-Makdisi large model) + To: Group of rational points of Jacobian + over Finite Field in z2 of size 7^2 (Khuri-Makdisi large model) + """ + Element = JacobianPoint_finite_field + + def __init__(self, parent, function_field, base_div): + """ + Initialize. + + TESTS:: + + sage: k = GF(7) + sage: P2. = ProjectiveSpace(k, 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: TestSuite(G).run(skip=['_test_elements', '_test_pickling']) + """ + super().__init__(parent, function_field, base_div) + + F = self._function_field + K = F.constant_base_field() + + r = self._parent._function_field.constant_base_field().degree() + frob_K = K.frobenius_endomorphism(r) + + self._frobenius_of_constant_field = frob_K + + def __iter__(self): + """ + Return generator of points of this group. + + TESTS:: + + sage: k = GF(7) + sage: A. = AffineSpace(k,2) + sage: C = Curve(y^2 + x^3 + 2*x + 1).projective_closure() + sage: b = C([0,0,1]).place() + sage: J = C.jacobian(model='km_large', base_div=3*b) + sage: G = J.group() + sage: len([pt for pt in G]) # long time + 11 + """ + d0 = self._base_div.degree() + F = self._function_field + + zero_divisor = self._km.zero_divisor() + deg = 1 + support = [] + degrees = [] + multiples = [] + lst = [] + + places_infinite = F.places_infinite() + generators = [iter(places_infinite)] + while True: + while True: + try: + new_pl = next(generators[-1]) + break + except StopIteration: + if deg > d0: + return + generators.append(F._places_finite(deg)) + deg += 1 + multiples.append((d0 + 1)*[None]) + wn = self._wd_from_divisor(new_pl.divisor()) + dn = new_pl.degree() + wr = zero_divisor + dr = 0 + for r in range(1, d0 // dn + 1): + wr = self._km.add_divisor(wr, wn, dr, dn) + multiples[-1][r] = wr + dr += dn + for weights in WeightedIntegerVectors(d0 - dr, degrees): + d = dr + wD = wr + for i in range(len(support)): + w = weights[i] + if w > 0: + m = w * degrees[i] + wD = self._km.add_divisor(wD, multiples[i][w], d, m) + d += m + pt = self.element_class(self, wD) + if pt not in lst: + lst.append(pt) + yield pt + support.append(new_pl) + degrees.append(new_pl.degree()) + + def _frobenius_on(self, pt): + """ + Return the image of ``pt`` acted by the Frobenius automorphism. + + INPUT: + + - ``pt`` -- a point of ``self`` + + TESTS:: + + sage: k = GF(7) + sage: A. = AffineSpace(k,2) + sage: C = Curve(y^2 + x^3 + 2*x + 1).projective_closure() + sage: b = C([0,0,1]).place() + sage: J = C.jacobian(model='km_large', base_div=3*b) + sage: G1 = J.group() + sage: K = k.extension(3) + sage: G3 = J.group(K) + sage: pt = G3.get_points(12)[-2] # expected to be a point rational over K + sage: pt.frobenius().frobenius().frobenius() == pt # indirect doctest + True + """ + w = pt._w.apply_morphism(self._frobenius_of_constant_field) + return self.element_class(self, w) + + +class Jacobian(UniqueRepresentation, Jacobian_base): + """ + Jacobians implemented by Khuri-Makdisi's algorithms. + + EXAMPLES:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: C.jacobian(model='km') + Jacobian of Projective Plane Curve over Finite Field of size 7 + defined by x^3 - y^2*z - 2*z^3 (Khuri-Makdisi large model) + """ + def __init__(self, function_field, base_div, model, **kwds): + """ + Initialize. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: J = C.jacobian(model='km_large') + sage: TestSuite(J).run(skip=['_test_elements', '_test_pickling']) + + :: + + sage: J = C.jacobian(model='km_unknown') + Traceback (most recent call last): + ... + ValueError: unknown model + """ + super().__init__(function_field, base_div, **kwds) + + if model not in ['large', 'medium', 'small']: + raise ValueError('unknown model') + + self._model = model + + if function_field.constant_base_field().is_finite(): + self._group_class = JacobianGroup_finite_field + else: + self._group_class = JacobianGroup + + def _repr_(self): + """ + Return the string representation of ``self``. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: C.jacobian(model='km_large') + Jacobian of Projective Plane Curve over Finite Field of size 7 + defined by x^3 - y^2*z - 2*z^3 (Khuri-Makdisi large model) + """ + r = super()._repr_() + return r + f' (Khuri-Makdisi {self._model} model)' diff --git a/src/sage/rings/function_field/khuri_makdisi.pyx b/src/sage/rings/function_field/khuri_makdisi.pyx new file mode 100644 index 00000000000..f0cac80a1f2 --- /dev/null +++ b/src/sage/rings/function_field/khuri_makdisi.pyx @@ -0,0 +1,893 @@ +r""" +Khuri-Makdisi algorithms for arithmetic in Jacobians + +This module implements Khuri-Makdisi's algorithms of [Khu2004]_. + +In the implementation, we use notations close to the ones used by +Khuri-Makdisi. We describe them below for readers of the code. + +Let `D_0` be the base divisor of the Jacobian in Khuri-Makdisi model. So `D_0` +is an effective divisor of appropriate degree `d_0` depending on the model. Let +`g` be the genus of the underlying function field. For large and medium models, +`d_0\ge 2g+1`. For small model `d_0\ge g+1`. A point of the Jacobian is a +divisor class containing a divisor `D - D_0` of degree `0` with an effective +divisor `D` of degree `d_0`. + +Let `V_n` denote the vector space `H^0(O(nD_0))` with a chosen +basis, and let `\mu_{n,m}` is a bilinear map from `V_n\times V_m\to V_{n+m}` +defined by `(f,g)\mapsto fg`. The map `\mu_{n,m}` can be represented by a +3-dimensional array as depicted below:: + + f + *------* + d /|e /| + *-|----* | + | *----|-* + |/ |/ + *------* + +where `d=\dim V_n`, `e=\dim V_m`, `f=\dim V_{n+m}`. In the implementation, we +instead use a matrix of size `d\times ef`. Each row of the matrix denotes a +matrix of size `e\times f`. + +A point of the Jacobian is represented by an effective divisor `D`. In +Khuri-Makdisi algorithms, the divisor `D` is represented by a subspace `W_D = +H^0(O(n_0D_0 - D))` of `V_{n_0}` with fixed `n_0` depending on the model. For +large and small models, `n_0=3` and `L = O(3D_0)`, and for medium model, +`n_0=2` and `L = O(2D_0)`. + +The subspace `W_D` is the row space of a matrix `w_D`. Thus in the +implementation, the matrix `w_D` represents a point of the Jacobian. The row +space of the matrix `w_L` is `V_{n_0}=H^0(O(n_0D_0))`. + +The function ``mu_image(w_D, w_E, mu_mat_n_m, expected_dim)`` computes the image +`\mu_{n,m}(W_D,W_E)` of the expected dimension. + +The function ``mu_preimage(w_E, w_F, mu_mat_n_m, expected_codim)`` computes the +preimage `W_D` such that `\mu_{n,m}(W_D,W_E)=W_F` of the expected codimension +`\dim V_n - \dim W_D`, which is a multiple of `d_0`. + +AUTHORS: + +- Kwankyu Lee (2022-01): initial version + +""" + +# **************************************************************************** +# Copyright (C) 2022 Kwankyu Lee +# +# 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.matrix.constructor import matrix +from sage.matrix.matrix cimport Matrix +from sage.modules.free_module_element cimport FreeModuleElement +from sage.rings.integer import Integer + + +cdef inline list listcat(list l): + flat_list = [] + for sublist in l: + flat_list.extend(sublist) + return flat_list + + +cdef class KhuriMakdisi_base(object): + cdef Matrix wL + cdef Matrix w0 + cdef int d0, g + + cdef Matrix mu_image(self, Matrix wd, Matrix we, Matrix mu_mat, int expected_dim=0): + """ + Lemma 2.2. + + TESTS:: + + sage: k = GF(7) + sage: A. = AffineSpace(k,2) + sage: C = Curve(y^2 + x^3 + 2*x + 1).projective_closure() + sage: J = C.jacobian(model='km_large') + sage: G = J.group() + sage: b = C([0,0,1]).place() + sage: pl1 = C([1,2,1]).place() + sage: pl2 = C([3,1,1]).place() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: w1 = p1._w + sage: w2 = p2._w + sage: w1 + [0 1 0 0 0 0 0 0 0] + [0 0 1 0 0 0 0 0 0] + [0 0 0 1 0 0 1 0 2] + [0 0 0 0 1 0 6 0 6] + [0 0 0 0 0 1 4 0 1] + [0 0 0 0 0 0 0 1 0] + sage: w2 + [0 1 0 0 0 0 0 2 2] + [0 0 1 0 0 0 0 4 6] + [0 0 0 1 0 0 0 5 6] + [0 0 0 0 1 0 0 3 1] + [0 0 0 0 0 1 0 3 5] + [0 0 0 0 0 0 1 6 3] + sage: (p1 + p2)._w # indirect doctest + [1 0 0 0 0 0 2 2 4] + [0 1 0 0 0 0 3 5 0] + [0 0 1 0 0 0 0 3 6] + [0 0 0 1 0 0 1 1 3] + [0 0 0 0 1 0 5 6 3] + [0 0 0 0 0 1 5 5 3] + """ + cdef Matrix mat + cdef FreeModuleElement v + cdef Py_ssize_t n, c, r + + n = we.ncols() + c = mu_mat.ncols() // n + mat = matrix(0, c) + for v in wd: + mat = mat.stack(we * matrix(n, v * mu_mat)) + mat.echelonize() + r = mat.rank() + mat = mat.matrix_from_rows(range(r)) + if expected_dim and r == expected_dim: + break + + assert not expected_dim or r == expected_dim + + return mat + + cdef Matrix mu_preimage(self, Matrix we, Matrix wde, Matrix mu_mat, int expected_codim=0): + """ + Lemma 2.3 (division). + + This computes + + {s: mu(s*E) subset F} = {s: s*M*E*F_perp^ == 0} + = {s: s*M*v*F_perp^ == 0 for v in E} + = {s: F_perp*(v*M^)*s == 0 for v in E} + + for E = we, F = wde, M^ = mu_mat_reversed + """ + cdef Matrix mat, perp, vmu + cdef FreeModuleElement v + cdef Py_ssize_t nd, ne, nde, r + + ne = we.ncols() + nde = wde.ncols() + nd = nde - ne + + perp = wde.right_kernel_matrix() + mat = matrix(0, mu_mat.nrows()) + for v in we: + vmu = matrix([v * matrix(ne, row) for row in mu_mat]) + mat = mat.stack(perp * vmu.transpose()) + mat.echelonize() + r = mat.rank() + mat = mat.matrix_from_rows(range(r)) + if expected_codim and r == expected_codim: + break + + assert not expected_codim or r == expected_codim + + return mat.right_kernel_matrix() + + cpdef Matrix negate(self, Matrix wd): + """ + Theorem 4.4 (negation), first method. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: b = C([0,1,0]).place() + sage: pl = C([-1,2,1]).place() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: p = G.point(pl - b) + sage: -p # indirect doctest + Point of Jacobian determined by + [ 1 0 0 0 0 0 15 11 2] + [ 0 1 0 0 0 0 16 0 12] + [ 0 0 1 0 0 0 0 16 0] + [ 0 0 0 1 0 0 0 0 16] + [ 0 0 0 0 1 0 12 0 16] + [ 0 0 0 0 0 1 15 16 2] + """ + return self.addflip(wd, self.w0) + + cpdef Matrix add(self, Matrix wd1, Matrix wd2): + """ + Theorem 4.5 (addition). + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: b = C([0,1,0]).place() + sage: pl = C([-1,2,1]).place() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: p = G.point(pl - b) + sage: p + p # indirect doctest + Point of Jacobian determined by + [ 1 0 0 0 0 0 0 10 0] + [ 0 1 0 0 0 0 5 1 4] + [ 0 0 1 0 0 0 15 7 12] + [ 0 0 0 1 0 0 14 8 16] + [ 0 0 0 0 1 0 3 12 16] + [ 0 0 0 0 0 1 13 5 7] + """ + return self.negate(self.addflip(wd1, wd2)) + + cpdef Matrix subtract(self, Matrix wd1, Matrix wd2): + """ + Theorem 4.6 (subtraction), first method. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: b = C([0,1,0]).place() + sage: pl = C([-1,2,1]).place() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: p = G.point(pl - b) + sage: p - p # indirect doctest + Point of Jacobian determined by + [1 0 0 0 0 0 0 0 0] + [0 1 0 0 0 0 0 0 0] + [0 0 1 0 0 0 0 0 0] + [0 0 0 0 1 0 0 0 0] + [0 0 0 0 0 1 0 0 0] + [0 0 0 0 0 0 0 1 0] + """ + return self.addflip(self.negate(wd1), wd2) + + cpdef Matrix multiple(self, Matrix wd, n): + """ + Compute multiple by additive square-and-multiply method. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: b = C([0,1,0]).place() + sage: pl = C([-1,2,1]).place() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: p = G.point(pl - b) + sage: 10*p + Point of Jacobian determined by + [ 1 0 0 0 0 0 5 2 2] + [ 0 1 0 0 0 0 13 6 11] + [ 0 0 1 0 0 0 1 11 4] + [ 0 0 0 1 0 0 1 13 7] + [ 0 0 0 0 1 0 12 16 2] + [ 0 0 0 0 0 1 6 9 10] + sage: (-10)*p + Point of Jacobian determined by + [ 1 0 0 0 0 13 0 10 6] + [ 0 1 0 0 0 5 0 4 16] + [ 0 0 1 0 0 2 0 0 4] + [ 0 0 0 1 0 9 0 6 9] + [ 0 0 0 0 1 6 0 0 9] + [ 0 0 0 0 0 0 1 9 5] + sage: 0*p + Point of Jacobian determined by + [1 0 0 0 0 0 0 0 0] + [0 1 0 0 0 0 0 0 0] + [0 0 1 0 0 0 0 0 0] + [0 0 0 0 1 0 0 0 0] + [0 0 0 0 0 1 0 0 0] + [0 0 0 0 0 0 0 1 0] + """ + cdef Matrix w + cdef int sign, b + cdef list bits + + if n == 0: + return self.w0 + if n < 0: + bits = Integer(-n).digits(2) + else: + bits = Integer(n).digits(2) + bits.pop() + mwd = None + w = wd + sign = 1 + for i in range(len(bits)): + w = self.addflip(w, w) + sign = -sign + b = bits.pop() + if b > 0: + if sign < 0: + if mwd is None: + mwd = self.addflip(wd, self.w0) + w = self.addflip(w, mwd) + else: + w = self.addflip(w, wd) + sign = -sign + if sign < 0 and n > 0 or sign > 0 and n < 0: + w = self.addflip(w, self.w0) # negate + return w + + cpdef Matrix zero_divisor(self): + """ + Return the matrix `w_L` representing zero divisor. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: b = C([0,1,0]).place() + sage: pl = C([-1,2,1]).place() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: G._km.zero_divisor() + [1 0 0 0 0 0 0 0 0] + [0 1 0 0 0 0 0 0 0] + [0 0 1 0 0 0 0 0 0] + [0 0 0 1 0 0 0 0 0] + [0 0 0 0 1 0 0 0 0] + [0 0 0 0 0 1 0 0 0] + [0 0 0 0 0 0 1 0 0] + [0 0 0 0 0 0 0 1 0] + [0 0 0 0 0 0 0 0 1] + """ + return self.wL + + +cdef class KhuriMakdisi_large(KhuriMakdisi_base): + r""" + Khuri-Makdisi's large model. + """ + cdef Matrix mu_mat33 + + def __init__(self, V, mu, w0, d0, g): + """ + Initialize. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: b = C([0,1,0]).place() + sage: pl1 = C([-1,2,1]).place() + sage: pl2 = C([3,7,1]).place() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: p1 + Point of Jacobian determined by + [ 1 0 0 0 0 0 0 12 15] + [ 0 1 0 0 0 0 0 0 13] + [ 0 0 1 0 0 0 0 0 2] + [ 0 0 0 1 0 0 0 0 16] + [ 0 0 0 0 0 1 0 0 15] + [ 0 0 0 0 0 0 1 0 1] + sage: p2 + Point of Jacobian determined by + [ 1 0 0 0 0 0 0 12 5] + [ 0 1 0 0 0 0 0 0 2] + [ 0 0 1 0 0 0 0 0 13] + [ 0 0 0 1 0 0 0 0 8] + [ 0 0 0 0 0 1 0 0 10] + [ 0 0 0 0 0 0 1 0 14] + sage: p1 + p2 + Point of Jacobian determined by + [ 1 0 0 0 0 16 0 5 3] + [ 0 1 0 0 0 6 0 8 16] + [ 0 0 1 0 0 15 0 3 10] + [ 0 0 0 1 0 3 0 0 0] + [ 0 0 0 0 1 12 0 16 8] + [ 0 0 0 0 0 0 1 3 0] + sage: p1 - p2 + Point of Jacobian determined by + [ 1 0 0 0 0 0 13 9 5] + [ 0 1 0 0 0 0 2 5 8] + [ 0 0 1 0 0 0 6 7 5] + [ 0 0 0 1 0 0 11 3 16] + [ 0 0 0 0 1 0 9 7 10] + [ 0 0 0 0 0 1 4 10 5] + sage: p1.addflip(p2) == -(p1 + p2) + True + """ + self.wL = V(3).basis_matrix() + self.w0 = w0 + self.d0 = d0 + self.g = g + self.mu_mat33 = matrix(listcat([list(mu(3, 3, i, j)) for j in range(3*d0-g+1)]) for i in range(3*d0-g+1)) + + def equal(self, wd, we): + """ + Theorem 4.1, second method. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: J = C.jacobian(model='km_large') + sage: b = C([0,1,0]).place() + sage: J.set_base_place(b) + sage: G = J.group() + sage: pl1 = C([3,2,1]).place() + sage: pl2 = C([5,5,1]).place() + sage: p1 = G(pl1) + sage: p2 = G(pl2) + sage: p1 + p2 == p2 + p1 # indirect doctest + True + sage: p1 - p2 == -(p2 - p1) + True + sage: zero = G.zero() + sage: p1 + zero == p1 + True + sage: p1 - p1 == zero + True + """ + cdef int d0 = self.d0 + cdef int g = self.g + cdef Matrix wf, w1, w2 + + wf = matrix(wd[0]) + w1 = self.mu_image(wf, we, self.mu_mat33, 2*d0 - g + 1) + w2 = self.mu_preimage(wd, w1, self.mu_mat33) + return w2.rank() > 0 + + cdef Matrix _add(self, Matrix wd, Matrix we): + """ + Theorem 3.6 (addition of divisors, first method). + """ + cdef int d0 = self.d0 + cdef int g = self.g + cdef Matrix w1, w2 + + w1 = self.mu_image(wd, we, self.mu_mat33, 4*d0 - g + 1) + w2 = self.mu_preimage(self.wL, w1, self.mu_mat33, 2*d0) + return w2 + + cdef Matrix _flip(self, Matrix wd): + """ + Theorem 3.10 (flipping) + """ + cdef int d0 = self.d0 + cdef int g = self.g + cdef Matrix w1, w2 + + # efficient than + # wf = matrix(wd[0]) + # w1 = self.mu_image(wf, self.wL, mu_mat, 3*d0 - g + 1) + w1 = matrix(3*d0 - g + 1, wd[0] * self.mu_mat33) + w2 = self.mu_preimage(wd, w1, self.mu_mat33, d0) + return w2 + + cpdef Matrix addflip(self, Matrix wd1, Matrix wd2): + """ + Theorem 4.3 (addflip) + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: F = C.function_field() + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: b = C([0,1,0]).place() + sage: pl1 = C([-1,2,1]).place() + sage: pl2 = C([3,7,1]).place() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: p1.addflip(p2) + Point of Jacobian determined by + [ 1 0 0 0 0 0 7 10 9] + [ 0 1 0 0 0 0 4 14 10] + [ 0 0 1 0 0 0 7 0 9] + [ 0 0 0 1 0 0 10 10 6] + [ 0 0 0 0 1 0 6 5 15] + [ 0 0 0 0 0 1 14 9 1] + """ + return self._flip(self._add(wd1, wd2)) + + cpdef Matrix add_divisor(self, Matrix wd1, Matrix wd2, int d1, int d2): + """ + Theorem 3.6 (addition of divisors, first method). + + We assume that `w_{D_1}`, `w_{D_2}` represent divisors of degree at most + `3d_0 - 2g - 1`. + + TESTS:: + + sage: k = GF(7) + sage: A. = AffineSpace(k,2) + sage: C = Curve(y^2 + x^3 + 2*x + 1).projective_closure() + sage: J = C.jacobian(model='km_large') + sage: G = J.group() + sage: pts = G.get_points(G.order()) # indirect doctest + sage: len(pts) + 11 + """ + cdef int d0 = self.d0 + cdef int g = self.g + cdef Matrix w1, w2 + + w1 = self.mu_image(wd1, wd2, self.mu_mat33, 6*d0 - d1 - d2 - g + 1) + w2 = self.mu_preimage(self.wL, w1, self.mu_mat33, d1 + d2) + return w2 + + +cdef class KhuriMakdisi_medium(KhuriMakdisi_base): + """ + Khuri-Makdisi's *medium* model + """ + cdef Matrix wV1, wV2, wV3, mu_mat22, mu_mat23, mu_mat31, mu_mat32 + + def __init__(self, V, mu, w0, d0, g): + """ + Initialize. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: b = C([0,1,0]).place() + sage: pl1 = C([-1,2,1]).place() + sage: pl2 = C([3,7,1]).place() + sage: J = C.jacobian(model='km_medium', base_div=h) + sage: G = J.group() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: p1 + Point of Jacobian determined by + [ 1 0 0 0 16 12] + [ 0 1 0 0 15 0] + [ 0 0 1 0 1 0] + sage: p2 + Point of Jacobian determined by + [ 1 0 0 0 8 12] + [ 0 1 0 0 10 0] + [ 0 0 1 0 14 0] + sage: p1 + p2 + Point of Jacobian determined by + [ 1 0 0 6 3 16] + [ 0 1 0 15 16 10] + [ 0 0 1 3 0 0] + sage: p1 - p2 + Point of Jacobian determined by + [ 1 0 0 8 0 14] + [ 0 1 0 1 10 10] + [ 0 0 1 15 3 6] + sage: p1.addflip(p2) == -(p1 + p2) + True + """ + self.wL = V(2).basis_matrix() + self.w0 = w0 + self.d0 = d0 + self.g = g + self.wV1 = V(1).basis_matrix() + self.wV2 = V(2).basis_matrix() + self.wV3 = V(3).basis_matrix() + self.mu_mat22 = matrix(listcat([list(mu(2, 2, i, j)) for j in range(2*d0-g+1)]) for i in range(2*d0-g+1)) + self.mu_mat23 = matrix(listcat([list(mu(2, 3, i, j)) for j in range(3*d0-g+1)]) for i in range(2*d0-g+1)) + self.mu_mat31 = matrix(listcat([list(mu(3, 1, i, j)) for j in range(1*d0-g+1)]) for i in range(3*d0-g+1)) + self.mu_mat32 = matrix(listcat([list(mu(3, 2, i, j)) for j in range(2*d0-g+1)]) for i in range(3*d0-g+1)) + + def equal(self, wd, we): + """ + Theorem 4.1, second method. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: J = C.jacobian(model='km_medium', base_div=h) + sage: G = J.group() + sage: b = C([0,1,0]).place() + sage: pl1 = C([-1,2,1]).place() + sage: pl2 = C([3,7,1]).place() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: p1 + p2 == p2 + p1 # indirect doctest + True + sage: p1 - p2 == -(p2 - p1) + True + sage: zero = G.zero() + sage: p1 + zero == p1 + True + sage: p1 - p1 == zero + True + """ + cdef int d0 = self.d0 + cdef int g = self.g + cdef Matrix wf, w1, w2 + + wf = matrix(wd[0]) + w1 = self.mu_image(wf, we, self.mu_mat22, d0 - g + 1) + w2 = self.mu_preimage(wd, w1, self.mu_mat22) + return w2.rank() > 0 + + cpdef Matrix addflip(self, Matrix wd1, Matrix wd2): + """ + Theorem 5.1 (addflip in medium model). + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: h = C.function(y/x).divisor_of_poles() + sage: b = C([0,1,0]).place() + sage: pl1 = C([-1,2,1]).place() + sage: pl2 = C([3,7,1]).place() + sage: J = C.jacobian(model='km_medium', base_div=h) + sage: G = J.group() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: af = p1.addflip(p2) + sage: af + Point of Jacobian determined by + [ 1 0 0 6 3 16] + [ 0 1 0 0 7 9] + [ 0 0 1 14 2 3] + + We check the computation in other model:: + + sage: J = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: G.point(af.divisor()) == p1.addflip(p2) + True + """ + cdef int d0 = self.d0 + cdef int g = self.g + cdef Matrix w1, w2, w3, w4 + + w1 = self.mu_image(wd1, wd2, self.mu_mat22, 2*d0 - g + 1) + w2 = self.mu_preimage(self.wV1, w1, self.mu_mat31, 2*d0) + # efficient than + # wf = matrix(w2[0]) + # w3 = self.mu_image(wf, self.wV2, self.mu_mat32, 2*d0 - g + 1) + w3 = matrix(2*d0 - g + 1, w2[0] * self.mu_mat32) + w4 = self.mu_preimage(w2, w3, self.mu_mat23, d0) + return w4 + + cpdef Matrix add_divisor(self, Matrix wd1, Matrix wd2, int d1, int d2): + """ + Theorem 3.6 (addition of divisors, first method). + + We assume that `w_{D_1}`, `w_{D_2}` represent divisors of degree at + most `4d_0 - 2g - 1`. + + TESTS:: + + sage: k = GF(7) + sage: A. = AffineSpace(k,2) + sage: C = Curve(y^2 + x^3 + 2*x + 1).projective_closure() + sage: J = C.jacobian(model='km_medium') + sage: G = J.group() + sage: pts = G.get_points(G.order()) # indirect doctest + sage: len(pts) + 11 + """ + cdef int d0 = self.d0 + cdef int g = self.g + cdef Matrix w1, w2 + + w1 = self.mu_image(wd1, wd2, self.mu_mat22, 4*d0 - d1 - d2 - g + 1) + w2 = self.mu_preimage(self.wL, w1, self.mu_mat22, d1 + d2) + return w2 + + +cdef class KhuriMakdisi_small(KhuriMakdisi_base): + """ + Khuri-Makdisi's *small* model + """ + cdef Matrix wV2, wV3, wV4, mu_mat22 + cdef Matrix mu_mat23, mu_mat24, mu_mat32, mu_mat33, mu_mat34, mu_mat42, mu_mat43 + + def __init__(self, V, mu, w0, d0, g): + """ + Initialize. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: pl1 = C([-1,2,1]).place() + sage: pl2 = C([3,7,1]).place() + sage: J = C.jacobian(model='km_small', base_div=2*b) + sage: G = J.group() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: p1 + Point of Jacobian determined by + [ 1 0 0 0 0 11] + [ 0 1 0 0 2 0] + [ 0 0 1 0 16 10] + [ 0 0 0 1 0 3] + sage: p2 + Point of Jacobian determined by + [1 0 0 0 0 3] + [0 1 0 0 7 0] + [0 0 1 0 3 5] + [0 0 0 1 0 2] + sage: p1 + p2 + Point of Jacobian determined by + [ 1 0 0 0 10 9] + [ 0 1 0 0 7 5] + [ 0 0 1 0 15 4] + [ 0 0 0 1 3 10] + sage: p1 - p2 + Point of Jacobian determined by + [ 1 0 0 0 10 9] + [ 0 1 0 0 9 8] + [ 0 0 1 0 15 4] + [ 0 0 0 1 15 11] + sage: p1.addflip(p2) == -(p1 + p2) + True + """ + self.wL = V(3).basis_matrix() + self.w0 = w0 + self.d0 = d0 + self.g = g + self.wV2 = V(2).basis_matrix() + self.wV3 = V(3).basis_matrix() + self.wV4 = V(4).basis_matrix() + self.mu_mat23 = matrix(listcat([list(mu(2, 3, i, j)) for j in range(3*d0-g+1)]) for i in range(2*d0-g+1)) + self.mu_mat24 = matrix(listcat([list(mu(2, 4, i, j)) for j in range(4*d0-g+1)]) for i in range(2*d0-g+1)) + self.mu_mat32 = matrix(listcat([list(mu(3, 2, i, j)) for j in range(2*d0-g+1)]) for i in range(3*d0-g+1)) + self.mu_mat33 = matrix(listcat([list(mu(3, 3, i, j)) for j in range(3*d0-g+1)]) for i in range(3*d0-g+1)) + self.mu_mat34 = matrix(listcat([list(mu(3, 4, i, j)) for j in range(4*d0-g+1)]) for i in range(3*d0-g+1)) + self.mu_mat42 = matrix(listcat([list(mu(4, 2, i, j)) for j in range(2*d0-g+1)]) for i in range(4*d0-g+1)) + self.mu_mat43 = matrix(listcat([list(mu(4, 3, i, j)) for j in range(3*d0-g+1)]) for i in range(4*d0-g+1)) + + def equal(self, wd, we): + """ + Theorem 4.1, second method. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: pl1 = C([-1,2,1]).place() + sage: pl2 = C([3,7,1]).place() + sage: J = C.jacobian(model='km_small', base_div=2*b) + sage: G = J.group() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: p1 + p2 == p2 + p1 # indirect doctest + True + sage: p1 - p2 == -(p2 - p1) + True + sage: zero = G.zero() + sage: p1 + zero == p1 + True + sage: p1 - p1 == zero + True + """ + cdef int d0 = self.d0 + cdef int g = self.g + cdef Matrix wf, w1, w2 + + wf = matrix(wd[0]) + w1 = self.mu_image(wf, we, self.mu_mat33, 2*d0 - g + 1) + w2 = self.mu_preimage(wd, w1, self.mu_mat33) + return w2.rank() > 0 + + cpdef Matrix addflip(self, Matrix wd1, Matrix wd2): + """ + Theorem 5.3 (addflip in small model), second method. + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(17), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: pl1 = C([-1,2,1]).place() + sage: pl2 = C([3,7,1]).place() + sage: J = C.jacobian(model='km_small', base_div=2*b) + sage: G = J.group() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: af = p1.addflip(p2) + sage: af + Point of Jacobian determined by + [ 1 0 0 0 10 9] + [ 0 1 0 0 10 12] + [ 0 0 1 0 15 4] + [ 0 0 0 1 14 7] + + We check the computation in other model:: + + sage: h = C.function(y/x).divisor_of_poles() + sage: Jl = C.jacobian(model='km_large', base_div=h) + sage: G = J.group() + sage: q1 = G.point(pl1 - b) + sage: q2 = G.point(pl2 - b) + sage: G.point(af.divisor()) == q1.addflip(p2) + True + """ + cdef int d0 = self.d0 + cdef int g = self.g + cdef Matrix w1, w2, w3, w4, w5 + + w1 = self.mu_image(wd1, wd2, self.mu_mat33, 4*d0 - g + 1) + w2 = self.mu_preimage(self.wV3, w1, self.mu_mat33, 2*d0) + w3 = self.mu_preimage(self.wV2, w1, self.mu_mat42, 2*d0) + # efficient than + # wf = matrix(w2[0]) + # w4 = self.mu_image(wf, self.wV4, self.mu_mat34, 4*d0 - g + 1) + w4 = matrix(4*d0 - g + 1, w2[0] * self.mu_mat34) + w5 = self.mu_preimage(w3, w4, self.mu_mat34, d0) + return w5 + + cpdef Matrix negate(self, Matrix wd): + """ + Theorem 5.4 (negation in small model). + + TESTS:: + + sage: P2. = ProjectiveSpace(GF(7), 2) + sage: C = Curve(x^3 + 5*z^3 - y^2*z, P2) + sage: b = C([0,1,0]).place() + sage: pl1 = C([3,2,1]).place() + sage: pl2 = C([5,5,1]).place() + sage: J = C.jacobian(model='km_small', base_div=2*b) + sage: G = J.group() + sage: p1 = G.point(pl1 - b) + sage: p2 = G.point(pl2 - b) + sage: -(-p1) == p1 # indirect doctest + True + """ + cdef int d0 = self.d0 + cdef int g = self.g + cdef Matrix w1, w2, w3, w4 + + w1 = self.mu_image(self.wV2, wd, self.mu_mat23, 4*d0 - g + 1) + w2 = self.mu_preimage(self.wV3, w1, self.mu_mat23, d0) + # efficient than + # wf = matrix(w2[0]) + # w3 = self.mu_image(wf, self.wV4, self.mu_mat24, 4*d0 - g + 1) + w3 = matrix(4*d0 - g + 1, w2[0] * self.mu_mat24) + w4 = self.mu_preimage(wd, w3, self.mu_mat33, d0) + return w4 + + cpdef Matrix add_divisor(self, Matrix wd1, Matrix wd2, int d1, int d2): + """ + Theorem 3.6 (addition of divisors, first method). + + We assume that `w_{D_1}`, `w_{D_2}` represent divisors of degree at most + `6d_0 - 2g - 1`. + + TESTS:: + + sage: k = GF(7) + sage: A. = AffineSpace(k,2) + sage: C = Curve(y^2 + x^3 + 2*x + 1).projective_closure() + sage: J = C.jacobian(model='km_small') + sage: G = J.group() + sage: pts = G.get_points(G.order()) # indirect doctest + sage: len(pts) + 11 + """ + cdef int d0 = self.d0 + cdef int g = self.g + cdef Matrix w1, w2 + + w1 = self.mu_image(wd1, wd2, self.mu_mat33, 6*d0 - d1 - d2 - g + 1) + w2 = self.mu_preimage(self.wL, w1, self.mu_mat33, d1 + d2) + return w2 diff --git a/src/sage/rings/polynomial/laurent_polynomial.pyx b/src/sage/rings/polynomial/laurent_polynomial.pyx index a830ef7f510..650388e7f0f 100644 --- a/src/sage/rings/polynomial/laurent_polynomial.pyx +++ b/src/sage/rings/polynomial/laurent_polynomial.pyx @@ -15,6 +15,7 @@ from sage.misc.derivative import multi_derivative from sage.rings.polynomial.polynomial_element import Polynomial from sage.rings.polynomial.polynomial_ring import is_PolynomialRing from sage.structure.richcmp cimport richcmp, rich_to_bool +from sage.rings.infinity import minus_infinity cdef class LaurentPolynomial(CommutativeAlgebraElement): @@ -1018,7 +1019,7 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): return ret def degree(self): - """ + r""" Return the degree of ``self``. EXAMPLES:: @@ -1030,7 +1031,16 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): sage: g = -10/x^5 + x^2 - x^7 sage: g.degree() 7 + + The zero polynomial is defined to have degree `-\infty`:: + + sage: R. = LaurentPolynomialRing(ZZ) + sage: R.zero().degree() + -Infinity """ + # The zero polynomial is defined to have degree -Infinity + if self.is_zero(): + return minus_infinity return self.__u.degree() + self.__n def __neg__(self): @@ -1318,22 +1328,59 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): return ~self raise ArithmeticError("element is not a unit") + @coerce_binop def xgcd(self, other): - """ - Extended `gcd` for univariate Laurent polynomial rings over a field. + r""" + Extended :meth:`gcd` for univariate Laurent polynomial rings over a field. + + OUTPUT: + + A triple ``(g, p, q)`` such that ``g`` is the :meth:`gcd` of + ``self`` (`= a`) and ``other`` (`= b`), and ``p`` and ``q`` are + cofactors satisfying the Bezout identity + + .. MATH:: + + g = p \cdot a + q \cdot b. EXAMPLES:: sage: S. = LaurentPolynomialRing(QQ) - sage: (t^-2 + 1).xgcd(t^-3 + 1) - (1, 1/2*t^2 - 1/2*t^3 - 1/2*t^4, 1/2*t^3 + 1/2*t^4) + sage: a = t^-2 + 1 + sage: b = t^-3 + 1 + sage: g, p, q = a.xgcd(b); (g, p, q) + (t^-3, 1/2*t^-1 - 1/2 - 1/2*t, 1/2 + 1/2*t) + sage: g == p * a + q * b + True + sage: g == a.gcd(b) + True + sage: t.xgcd(t) + (t, 0, 1) + sage: t.xgcd(5) + (1, 0, 1/5) """ - R = self.parent() - S = R.polynomial_ring() - f, df = self.monomial_reduction() - g, dg = other.monomial_reduction() - h, p, q = f.xgcd(g) - return R(h), p / df, q / dg + cdef LaurentPolynomial_univariate elt = other + cdef LaurentPolynomial_univariate ret_gcd, ret_p, ret_q + cdef long n = min(self.__n, elt.__n) + + h, p, q = self.__u.xgcd(elt.__u) + + ret_gcd = self._new_c() + ret_gcd.__u = h + ret_gcd.__n = n + ret_gcd._normalize() + + ret_p = self._new_c() + ret_p.__u = p + ret_p.__n = n - self.__n + ret_p._normalize() + + ret_q = self._new_c() + ret_q.__u = q + ret_q.__n = n - elt.__n + ret_q._normalize() + + return (ret_gcd, ret_p, ret_q) def inverse_mod(a, m): """ @@ -1424,6 +1471,26 @@ cdef class LaurentPolynomial_univariate(LaurentPolynomial): ret._normalize() return ret + def euclidean_degree(self): + r""" + Return the degree of ``self`` as an element of an Euclidean domain. + + This is the Euclidean degree of the underlying polynomial. + + EXAMPLES:: + + sage: R. = LaurentPolynomialRing(QQ) + sage: (x^-5 + x^2).euclidean_degree() + 7 + + sage: R. = LaurentPolynomialRing(ZZ) + sage: (x^-5 + x^2).euclidean_degree() + Traceback (most recent call last): + ... + NotImplementedError + """ + return self.__u.euclidean_degree() + @coerce_binop def quo_rem(self, other): r""" diff --git a/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx b/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx index c03239cebde..6a2dc18204a 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx +++ b/src/sage/rings/polynomial/laurent_polynomial_mpair.pyx @@ -16,7 +16,8 @@ from sage.structure.factorization import Factorization from sage.misc.derivative import multi_derivative from sage.rings.polynomial.polydict cimport monomial_exponent from sage.matrix.matrix0 cimport Matrix -from sage.rings.infinity import Infinity +from sage.rings.infinity import Infinity, minus_infinity + cdef class LaurentPolynomial_mpair(LaurentPolynomial): """ @@ -1155,21 +1156,56 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): return [a.eadd(self._mon) for a in self._poly.exponents()] def degree(self, x=None): - """ - Return the degree of ``x`` in ``self``. + r""" + Return the degree of ``self``. + + INPUT: + + - ``x`` -- (default: ``None``) a generator of the parent ring + + OUTPUT: + + If ``x`` is ``None``, return the total degree of ``self``. + If ``x`` is a given generator of the parent ring, + the output is the maximum degree of ``x`` in ``self``. EXAMPLES:: sage: R. = LaurentPolynomialRing(QQ) sage: f = 4*x^7*z^-1 + 3*x^3*y + 2*x^4*z^-2 + x^6*y^-7 + sage: f.degree() + 6 sage: f.degree(x) 7 sage: f.degree(y) 1 sage: f.degree(z) 0 + + The zero polynomial is defined to have degree `-\infty`:: + + sage: R. = LaurentPolynomialRing(ZZ) + sage: R.zero().degree() + -Infinity + sage: R.zero().degree(x) + -Infinity + sage: R.zero().degree(x) == R.zero().degree(y) == R.zero().degree(z) + True + + TESTS:: + + sage: R. = LaurentPolynomialRing(ZZ) + sage: f = x + y + z + sage: f.degree(1) + Traceback (most recent call last): + ... + TypeError: 1 is not a generator of parent """ - if not x: + # The zero polynomial is defined to have degree -Infinity + if self.is_zero(): + return minus_infinity + + if x is None: return self._poly.total_degree() + sum(self._mon) # Get the index of the generator or error diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring.py b/src/sage/rings/polynomial/laurent_polynomial_ring.py index 6d24de9f96f..808e897b49c 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring.py @@ -436,9 +436,7 @@ def __init__(self, R): sage: L = LaurentPolynomialRing(QQ,'x') sage: type(L) - sage: L == loads(dumps(L)) - True - + sage: TestSuite(L).run() TESTS:: diff --git a/src/sage/rings/polynomial/multi_polynomial_ideal.py b/src/sage/rings/polynomial/multi_polynomial_ideal.py index f80f7b141a7..eeb24026d9d 100644 --- a/src/sage/rings/polynomial/multi_polynomial_ideal.py +++ b/src/sage/rings/polynomial/multi_polynomial_ideal.py @@ -1383,11 +1383,7 @@ def _groebner_basis_ginv(self, algorithm="TQ", criteria='CritPartially', divisio T = P.term_order() K = P.base_ring() - try: - import ginv - except ImportError: - from sage.misc.package import PackageNotFoundError - raise PackageNotFoundError("ginv") + import ginv st = ginv.SystemType("Polynomial") diff --git a/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx b/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx index 40fbc8c69aa..d1f17f7f899 100644 --- a/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx @@ -1285,7 +1285,7 @@ cdef class MPolynomialRing_libsingular(MPolynomialRing_base): polynomial ring, over a field, global ordering // coefficients: ZZ/2[a]/(a^8+a^4+a^3+a^2+1) // number of vars : 10 - // block 1 : ordering rp + // block 1 : ordering ip // : names x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 // block 2 : ordering C @@ -1294,7 +1294,7 @@ cdef class MPolynomialRing_libsingular(MPolynomialRing_base): polynomial ring, over a field, global ordering // coefficients: ZZ/127 // number of vars : 2 - // block 1 : ordering rp + // block 1 : ordering ip // : names x0 x1 // block 2 : ordering C @@ -1303,7 +1303,7 @@ cdef class MPolynomialRing_libsingular(MPolynomialRing_base): polynomial ring, over a field, global ordering // coefficients: QQ // number of vars : 2 - // block 1 : ordering rp + // block 1 : ordering ip // : names x0 x1 // block 2 : ordering C diff --git a/src/sage/rings/polynomial/multi_polynomial_sequence.py b/src/sage/rings/polynomial/multi_polynomial_sequence.py index e9476061b57..8797a84efd8 100644 --- a/src/sage/rings/polynomial/multi_polynomial_sequence.py +++ b/src/sage/rings/polynomial/multi_polynomial_sequence.py @@ -1724,9 +1724,24 @@ def coefficients_monomials(self, order=None, sparse=True): (x*y, y, z, 1) sage: A*v (x*y + y + 1, z + 1) + + TESTS: + + Check that :issue:`37837` has been fixed:: + + sage: R. = PolynomialRing(GF(2), ['a', 'b', 'c']) + sage: A, v = Sequence([a+b+c]).coefficients_monomials() + sage: A + [1 1 1] + sage: v + (a, b, c) + sage: A*v + (a + b + c) """ from sage.modules.free_module_element import vector from sage.matrix.constructor import Matrix + from sage.rings.polynomial.multi_polynomial_ring_base import \ + BooleanPolynomialRing_base if order is None: v = sorted(self.monomials(), reverse=True) @@ -1736,16 +1751,27 @@ def coefficients_monomials(self, order=None, sparse=True): else: raise ValueError("order argument can only accept list or tuple") - R = self.ring().base_ring() - one = R.one() + R = self.ring() + K = R.base_ring() y = dict(zip(v, range(len(v)))) # construct dictionary for fast lookups - A = Matrix(R, len(self), len(v), sparse=sparse) - for x, poly in enumerate(self): - for m in poly: - try: - A[x, y[m]] = one - except KeyError: - raise ValueError("order argument does not contain all monomials") + A = Matrix(K, len(self), len(v), sparse=sparse) + + if isinstance(R, BooleanPolynomialRing_base): + one = K.one() + for x, poly in enumerate(self): + for m in poly: + try: + A[x, y[m]] = one + except KeyError: + raise ValueError("order argument does not contain all monomials") + else: + for x, poly in enumerate(self): + for c, m in poly: + try: + A[x, y[m]] = c + except KeyError: + raise ValueError("order argument does not contain all monomials") + return A, vector(v) diff --git a/src/sage/rings/polynomial/ore_function_element.py b/src/sage/rings/polynomial/ore_function_element.py index f44dd8ad880..0c1a919351f 100644 --- a/src/sage/rings/polynomial/ore_function_element.py +++ b/src/sage/rings/polynomial/ore_function_element.py @@ -708,13 +708,13 @@ def _call_(self, x): sage: F(g) # needs sage.rings.function_field Traceback (most recent call last): ... - TypeError: not a constant function + TypeError: (x + t^2)^(-1) * x is not a constant function """ numerator = x._numerator denominator = x._denominator if numerator.degree() == denominator.degree() and denominator.right_divides(numerator): return numerator.leading_coefficient() / denominator.leading_coefficient() - raise TypeError("not a constant function") + raise TypeError(f"{x} is not a constant function") class OreFunctionBaseringInjection(Morphism): r""" diff --git a/src/sage/rings/polynomial/ore_polynomial_element.pyx b/src/sage/rings/polynomial/ore_polynomial_element.pyx index 47b5b7e6ad7..dc6fbab0ab0 100644 --- a/src/sage/rings/polynomial/ore_polynomial_element.pyx +++ b/src/sage/rings/polynomial/ore_polynomial_element.pyx @@ -3010,7 +3010,7 @@ cdef class ConstantOrePolynomialSection(Map): sage: m(S([0,1])-S([0,t])) Traceback (most recent call last): ... - TypeError: not a constant polynomial + TypeError: (-t + 1)*x is not a constant polynomial """ if x.degree() <= 0: try: @@ -3018,7 +3018,7 @@ cdef class ConstantOrePolynomialSection(Map): except AttributeError: return ((x).constant_coefficient()) else: - raise TypeError("not a constant polynomial") + raise TypeError(f"{x} is not a constant polynomial") cdef class OrePolynomialBaseringInjection(Morphism): diff --git a/src/sage/rings/polynomial/polynomial_element.pyx b/src/sage/rings/polynomial/polynomial_element.pyx index e9b09e8f2b7..f08f810cfbf 100644 --- a/src/sage/rings/polynomial/polynomial_element.pyx +++ b/src/sage/rings/polynomial/polynomial_element.pyx @@ -1476,7 +1476,7 @@ cdef class Polynomial(CommutativePolynomial): sage: QQ(3*x + 45) Traceback (most recent call last): ... - TypeError: not a constant polynomial + TypeError: 3*x + 45 is not a constant polynomial """ return self._scalar_conversion(sage.rings.rational.Rational) @@ -12894,7 +12894,7 @@ cdef class ConstantPolynomialSection(Map): sage: phi(y_1) Traceback (most recent call last): ... - TypeError: not a constant polynomial + TypeError: y_1 is not a constant polynomial """ cpdef Element _call_(self, x): """ @@ -12913,7 +12913,7 @@ cdef class ConstantPolynomialSection(Map): sage: m(x) Traceback (most recent call last): ... - TypeError: not a constant polynomial + TypeError: x is not a constant polynomial """ if x.degree() <= 0: try: @@ -12921,7 +12921,7 @@ cdef class ConstantPolynomialSection(Map): except AttributeError: return ((x).constant_coefficient()) else: - raise TypeError("not a constant polynomial") + raise TypeError(f"{x} is not a constant polynomial") cdef class PolynomialBaseringInjection(Morphism): """ diff --git a/src/sage/rings/polynomial/polynomial_modn_dense_ntl.pyx b/src/sage/rings/polynomial/polynomial_modn_dense_ntl.pyx index 31dc850e982..04e677ee078 100644 --- a/src/sage/rings/polynomial/polynomial_modn_dense_ntl.pyx +++ b/src/sage/rings/polynomial/polynomial_modn_dense_ntl.pyx @@ -49,7 +49,7 @@ from sage.rings.infinity import infinity from sage.interfaces.singular import singular as singular_default -from sage.structure.element import coerce_binop +from sage.structure.element import canonical_coercion, coerce_binop, have_same_parent from sage.libs.ntl.types cimport NTL_SP_BOUND from sage.libs.ntl.ZZ_p cimport * @@ -213,7 +213,6 @@ cdef class Polynomial_dense_mod_n(Polynomial): def _pow(self, n): n = int(n) - if self.degree() <= 0: return self.parent()(self[0]**n) if n < 0: @@ -845,7 +844,7 @@ cdef class Polynomial_dense_modn_ntl_zz(Polynomial_dense_mod_n): return r def __pow__(Polynomial_dense_modn_ntl_zz self, ee, modulus): - """ + r""" TESTS:: sage: R. = PolynomialRing(Integers(100), implementation='NTL') @@ -1800,6 +1799,87 @@ cdef class Polynomial_dense_mod_p(Polynomial_dense_mod_n): A dense polynomial over the integers modulo `p`, where `p` is prime. """ + def __pow__(self, n, modulus): + """ + Exponentiation of ``self``. + + If ``modulus`` is not ``None``, the exponentiation is performed + modulo the polynomial ``modulus``. + + EXAMPLES:: + + sage: R. = PolynomialRing(Integers(101), implementation='NTL') + sage: (x-1)^5 + x^5 + 96*x^4 + 10*x^3 + 91*x^2 + 5*x + 100 + sage: pow(x-1, 15, x^3+x+1) + 55*x^2 + 6*x + 46 + + TESTS: + + Negative powers work but use the generic + implementation of fraction fields:: + + sage: R. = PolynomialRing(Integers(101), implementation='NTL') + sage: f = (x-1)^(-5) + sage: type(f) + + sage: (f + 2).numerator() + 2*x^5 + 91*x^4 + 20*x^3 + 81*x^2 + 10*x + 100 + + We define ``0^0`` to be unity, :issue:`13895`:: + + sage: R. = PolynomialRing(Integers(101), implementation='NTL') + sage: R(0)^0 + 1 + + The value returned from ``0^0`` should belong to our ring:: + + sage: R. = PolynomialRing(Integers(101), implementation='NTL') + sage: type(R(0)^0) == type(R(0)) + True + + The modulus can have smaller degree than ``self``:: + + sage: R. = PolynomialRing(GF(101), implementation="NTL") + sage: pow(x^4 + 1, 100, x^2 + x + 1) + 100*x + 100 + + Canonical coercion should apply:: + + sage: R. = PolynomialRing(GF(101), implementation="FLINT") + sage: x_ZZ = ZZ["x"].gen() + sage: pow(x+1, 100, 2) + 0 + sage: pow(x+1, 100, x_ZZ^2 + x_ZZ + 1) + 100*x + 100 + sage: pow(x+1, int(100), x_ZZ^2 + x_ZZ + 1) + 100*x + 100 + sage: xx = polygen(GF(97)) + sage: _ = pow(x + 1, 3, xx^3 + xx + 1) + Traceback (most recent call last): + ... + TypeError: no common canonical parent for objects with parents: ... + """ + n = Integer(n) + parent = (self)._parent + + if modulus is not None: + # Similar to coerce_binop + if not have_same_parent(self, modulus): + a, m = canonical_coercion(self, modulus) + if a is not self: + return pow(a, n, m) + modulus = m + self = self % modulus + return parent(pow(self.ntl_ZZ_pX(), n, modulus.ntl_ZZ_pX()), construct=True) + else: + if n < 0: + return ~(self**(-n)) + elif self.degree() <= 0: + return parent(self[0]**n) + else: + return parent(self.ntl_ZZ_pX()**n, construct=True) + @coerce_binop def gcd(self, right): """ diff --git a/src/sage/rings/polynomial/polynomial_zmod_flint.pxd b/src/sage/rings/polynomial/polynomial_zmod_flint.pxd index c6a92f3df6c..dfab67882fc 100644 --- a/src/sage/rings/polynomial/polynomial_zmod_flint.pxd +++ b/src/sage/rings/polynomial/polynomial_zmod_flint.pxd @@ -1,5 +1,6 @@ from sage.libs.flint.types cimport nmod_poly_t, nmod_poly_struct, fmpz_poly_t from sage.structure.parent cimport Parent +from sage.rings.integer cimport Integer from sage.rings.polynomial.polynomial_integer_dense_flint cimport Polynomial_integer_dense_flint ctypedef nmod_poly_struct celement @@ -14,4 +15,5 @@ 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) + cdef Polynomial _powmod_bigexp(self, Integer exp, Polynomial_zmod_flint modulus) 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 d08395dc8e8..1b37d0b848e 100644 --- a/src/sage/rings/polynomial/polynomial_zmod_flint.pyx +++ b/src/sage/rings/polynomial/polynomial_zmod_flint.pyx @@ -41,7 +41,7 @@ AUTHORS: from sage.libs.ntl.ntl_lzz_pX import ntl_zz_pX from sage.structure.element cimport parent -from sage.structure.element import coerce_binop +from sage.structure.element import coerce_binop, canonical_coercion, have_same_parent from sage.rings.polynomial.polynomial_integer_dense_flint cimport Polynomial_integer_dense_flint from sage.misc.superseded import deprecated_function_alias @@ -62,6 +62,7 @@ include "sage/libs/flint/nmod_poly_linkage.pxi" # and then the interface include "polynomial_template.pxi" +from sage.libs.flint.fmpz cimport * from sage.libs.flint.fmpz_poly cimport * from sage.libs.flint.nmod_poly cimport * @@ -482,6 +483,95 @@ cdef class Polynomial_zmod_flint(Polynomial_template): _mul_short_opposite = _mul_trunc_opposite + def __pow__(self, exp, modulus): + r""" + Exponentiation of ``self``. + + If ``modulus`` is not ``None``, the exponentiation is performed + modulo the polynomial ``modulus``. + + EXAMPLES:: + + sage: R. = GF(5)[] + sage: pow(x+1, 5**50, x^5 + 4*x + 3) + x + 1 + sage: pow(x+1, 5**64, x^5 + 4*x + 3) + x + 4 + sage: pow(x, 5**64, x^5 + 4*x + 3) + x + 3 + + The modulus can have smaller degree than ``self``:: + + sage: R. = PolynomialRing(GF(5), implementation="FLINT") + sage: pow(x^4, 6, x^2 + x + 1) + 1 + + TESTS: + + Canonical coercion applies:: + + sage: R. = PolynomialRing(GF(5), implementation="FLINT") + sage: x_ZZ = ZZ["x"].gen() + sage: pow(x+1, 25, 2) + 0 + sage: pow(x+1, 4, x_ZZ^2 + x_ZZ + 1) + 4*x + 4 + sage: pow(x+1, int(4), x_ZZ^2 + x_ZZ + 1) + 4*x + 4 + sage: xx = polygen(GF(97)) + sage: pow(x + 1, 3, xx^3 + xx + 1) + Traceback (most recent call last): + ... + TypeError: no common canonical parent for objects with parents: ... + """ + exp = Integer(exp) + if modulus is not None: + # Similar to coerce_binop + if not have_same_parent(self, modulus): + a, m = canonical_coercion(self, modulus) + if a is not self: + return pow(a, exp, m) + modulus = m + self = self % modulus + if exp > 0 and exp.bit_length() >= 32: + return (self)._powmod_bigexp(exp, modulus) + + return Polynomial_template.__pow__(self, exp, modulus) + + cdef Polynomial _powmod_bigexp(Polynomial_zmod_flint self, Integer exp, Polynomial_zmod_flint m): + r""" + Modular exponentiation with a large integer exponent. + + It is assumed that checks and coercions have been already performed on arguments. + + TESTS:: + + sage: R. = PolynomialRing(GF(5), implementation="FLINT") + sage: f = x+1 + sage: pow(f, 5**50, x^5 + 4*x + 3) # indirect doctest + x + 1 + sage: pow(x, 5**64, x^5 + 4*x + 3) # indirect doctest + x + 3 + """ + cdef Polynomial_zmod_flint ans = self._new() + # Preconditioning is useful for large exponents: the inverse + # power series helps computing fast quotients. + cdef Polynomial_zmod_flint minv = self._new() + cdef fmpz_t exp_fmpz + + fmpz_init(exp_fmpz) + fmpz_set_mpz(exp_fmpz, (exp).value) + nmod_poly_reverse(&minv.x, &m.x, nmod_poly_length(&m.x)) + nmod_poly_inv_series(&minv.x, &minv.x, nmod_poly_length(&m.x)) + + if self == self._parent.gen(): + nmod_poly_powmod_x_fmpz_preinv(&ans.x, exp_fmpz, &m.x, &minv.x) + else: + nmod_poly_powmod_fmpz_binexp_preinv(&ans.x, &self.x, exp_fmpz, &m.x, &minv.x) + + fmpz_clear(exp_fmpz) + return ans + cpdef Polynomial _power_trunc(self, unsigned long n, long prec): r""" TESTS:: diff --git a/src/sage/rings/polynomial/polynomial_zz_pex.pxd b/src/sage/rings/polynomial/polynomial_zz_pex.pxd index ca60398ef68..b3cd775542e 100644 --- a/src/sage/rings/polynomial/polynomial_zz_pex.pxd +++ b/src/sage/rings/polynomial/polynomial_zz_pex.pxd @@ -1,5 +1,6 @@ from sage.libs.ntl.ZZ_pEX cimport ZZ_pEX_c from sage.libs.ntl.ntl_ZZ_pEContext cimport ZZ_pEContext_ptrs +from sage.rings.integer cimport Integer ctypedef ZZ_pEX_c celement ctypedef ZZ_pEContext_ptrs *cparent @@ -7,5 +8,4 @@ ctypedef ZZ_pEContext_ptrs *cparent include "polynomial_template_header.pxi" cdef class Polynomial_ZZ_pEX(Polynomial_template): - pass - + cdef _powmod_bigexp(Polynomial_ZZ_pEX self, Integer exp, Polynomial_ZZ_pEX modulus) diff --git a/src/sage/rings/polynomial/polynomial_zz_pex.pyx b/src/sage/rings/polynomial/polynomial_zz_pex.pyx index b8c9d0bcbd8..f38f80ee49b 100644 --- a/src/sage/rings/polynomial/polynomial_zz_pex.pyx +++ b/src/sage/rings/polynomial/polynomial_zz_pex.pyx @@ -20,7 +20,9 @@ from sage.libs.ntl.ntl_ZZ_pEContext cimport ntl_ZZ_pEContext_class from sage.libs.ntl.ZZ_pE cimport ZZ_pE_to_ZZ_pX from sage.libs.ntl.ZZ_pX cimport ZZ_pX_deg, ZZ_pX_coeff from sage.libs.ntl.ZZ_p cimport ZZ_p_rep -from sage.libs.ntl.convert cimport ZZ_to_mpz +from sage.libs.ntl.convert cimport ZZ_to_mpz, mpz_to_ZZ + +from sage.structure.element import have_same_parent, canonical_coercion # 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 @@ -99,7 +101,7 @@ cdef class Polynomial_ZZ_pEX(Polynomial_template): sage: R([3,x]) Traceback (most recent call last): ... - TypeError: not a constant polynomial + TypeError: x is not a constant polynomial Check that NTL contexts are correctly restored and that :issue:`9524` has been fixed:: @@ -616,3 +618,97 @@ cdef class Polynomial_ZZ_pEX(Polynomial_template): ZZ_pEX_InvTrunc(r.x, self.x, prec) sig_off() return r + + def __pow__(self, exp, modulus): + r""" + Exponentiation of ``self``. + + If ``modulus`` is not ``None``, the exponentiation is performed + modulo the polynomial ``modulus``. + + EXAMPLES:: + + sage: K. = GF(101^2, 'a', modulus=[1,1,1]) + sage: R. = PolynomialRing(K, implementation="NTL") + sage: pow(x, 100) + x^100 + sage: pow(x + 3, 5) + x^5 + 15*x^4 + 90*x^3 + 68*x^2 + x + 41 + + If modulus is not ``None``, performs modular exponentiation:: + + sage: K. = GF(101^2, 'a', modulus=[1,1,1]) + sage: R. = PolynomialRing(K, implementation="NTL") + sage: pow(x, 100, x^2 + x + a) + (19*a + 64)*x + 30*a + 2 + sage: pow(x, 100 * 101**200, x^2 + x + a) + (19*a + 64)*x + 30*a + 2 + + The modulus can have smaller degree than ``self``:: + + sage: K. = GF(101^2, 'a', modulus=[1,1,1]) + sage: R. = PolynomialRing(K, implementation="NTL") + sage: pow(x^4, 25, x^2 + x + a) + (19*a + 64)*x + 30*a + 2 + + TESTS: + + Canonical coercion should apply:: + + sage: xx = GF(101)["x"].gen() + sage: pow(x+1, 25, 2) + 0 + sage: pow(x + a, 101**2, xx^3 + xx + 1) + 4*x^2 + 44*x + a + 70 + sage: pow(x + a, int(101**2), xx^3 + xx + 1) + 4*x^2 + 44*x + a + 70 + sage: xx = polygen(GF(97)) + sage: _ = pow(x + a, 101**2, xx^3 + xx + 1) + Traceback (most recent call last): + ... + TypeError: no common canonical parent for objects with parents: ... + """ + exp = Integer(exp) + if modulus is not None: + # Handle when modulus is zero + if modulus.is_zero(): + raise ZeroDivisionError("modulus must be nonzero") + + # Similar to coerce_binop + if not have_same_parent(self, modulus): + a, m = canonical_coercion(self, modulus) + if a is not self: + return pow(a, exp, m) + modulus = m + self = self % modulus + if exp > 0 and exp.bit_length() >= 32: + return (self)._powmod_bigexp(Integer(exp), modulus) + return Polynomial_template.__pow__(self, exp, modulus) + + cdef _powmod_bigexp(Polynomial_ZZ_pEX self, Integer exp, Polynomial_ZZ_pEX modulus): + """ + Modular exponentiation for large exponents. + """ + self._parent._modulus.restore() + cdef Polynomial_ZZ_pEX r + cdef ZZ_c e_ZZ + cdef ZZ_pEX_c y + cdef ZZ_pEX_Modulus_c mod + + mpz_to_ZZ(&e_ZZ, exp.value) + r = Polynomial_ZZ_pEX.__new__(Polynomial_ZZ_pEX) + celement_construct(&r.x, (self)._cparent) + r._parent = (self)._parent + r._cparent = (self)._cparent + ZZ_pEX_Modulus_build(mod, modulus.x) + + sig_on() + if ZZ_pEX_IsX(self.x): + ZZ_pEX_PowerXMod_ZZ_pre(r.x, e_ZZ, mod) + elif ZZ_pEX_deg(self.x) < ZZ_pEX_deg(modulus.x): + ZZ_pEX_PowerMod_ZZ_pre(r.x, self.x, e_ZZ, mod) + else: + ZZ_pEX_rem_pre(y, self.x, mod) + ZZ_pEX_PowerMod_ZZ_pre(r.x, y, e_ZZ, mod) + sig_off() + return r diff --git a/src/sage/rings/polynomial/term_order.py b/src/sage/rings/polynomial/term_order.py index 4a3e78fe879..ce2faa2e41b 100644 --- a/src/sage/rings/polynomial/term_order.py +++ b/src/sage/rings/polynomial/term_order.py @@ -388,7 +388,7 @@ singular_name_mapping = { 'lex' : 'lp', - 'invlex' : 'rp', + 'invlex' : 'ip', 'degrevlex' : 'dp', 'deglex' : 'Dp', 'neglex' : 'ls', diff --git a/src/sage/rings/real_double.pyx b/src/sage/rings/real_double.pyx index 17842e7e80e..de1f843ce87 100644 --- a/src/sage/rings/real_double.pyx +++ b/src/sage/rings/real_double.pyx @@ -250,7 +250,7 @@ cdef class RealDoubleField_class(sage.rings.abc.RealDoubleField): return (CompletionFunctor(sage.rings.infinity.Infinity, 53, {'type': 'RDF'}), - sage.rings.rational_field.QQ) + sage.rings.rational_field.QQ) def complex_field(self): """ @@ -431,10 +431,8 @@ cdef class RealDoubleField_class(sage.rings.abc.RealDoubleField): """ if prec == 53: return self - else: - from sage.rings.real_mpfr import RealField - return RealField(prec) - + from sage.rings.real_mpfr import RealField + return RealField(prec) def gen(self, n=0): """ @@ -901,7 +899,7 @@ cdef class RealDoubleElement(FieldElement): sage: complex(RDF(a)) (2303+0j) """ - return complex(self._value,0) + return complex(self._value, 0) def _integer_(self, ZZ=None): """ @@ -1154,7 +1152,7 @@ cdef class RealDoubleElement(FieldElement): sage: RDF(2.1)._im_gens_(R, [R(1)]) # needs sage.rings.real_mpfr 2.1000 """ - return codomain(self) # since 1 |--> 1 + return codomain(self) # since 1 |--> 1 def str(self): """ @@ -1931,7 +1929,8 @@ cdef class RealDoubleElement(FieldElement): while True: a1 = (a+b)/2 b1 = libc.math.sqrt(a*b) - if abs((b1/a1)-1) < eps: return self._new_c(a1) + if abs((b1/a1)-1) < eps: + return self._new_c(a1) a, b = a1, b1 def algebraic_dependency(self, n): @@ -1956,7 +1955,7 @@ cdef class RealDoubleElement(FieldElement): sage: r.algebraic_dependency(5) # needs sage.libs.pari x^2 - 2 """ - return sage.arith.misc.algdep(self,n) + return sage.arith.misc.algdep(self, n) algdep = algebraic_dependency @@ -2033,6 +2032,7 @@ _RDF = RealDoubleField_class() RDF = _RDF # external interface + def RealDoubleField(): """ Return the unique instance of the @@ -2046,6 +2046,7 @@ def RealDoubleField(): global _RDF return _RDF + def is_RealDoubleElement(x): """ Check if ``x`` is an element of the real double field. @@ -2061,9 +2062,9 @@ def is_RealDoubleElement(x): return isinstance(x, RealDoubleElement) -################# FAST CREATION CODE ###################### -########### Based on fast integer creation code ######### -######## There is nothing to see here, move along ####### +# ################ FAST CREATION CODE ###################### +# Based on fast integer creation code +# There is nothing to see here, move along # We use a global element to steal all the references # from. DO NOT INITIALIZE IT AGAIN and DO NOT REFERENCE IT! @@ -2125,12 +2126,12 @@ cdef PyObject* fast_tp_new(type t, args, kwds) noexcept: # objects (As indicated by the Py_TPFLAGS_HAVE_GC flag). # See below for a more detailed description. - new = PyObject_Malloc( sizeof(RealDoubleElement) ) + new = PyObject_Malloc(sizeof(RealDoubleElement)) # Now set every member as set in z, the global dummy RealDoubleElement # created before this tp_new started to operate. - memcpy(new, (global_dummy_element), sizeof(RealDoubleElement) ) + memcpy(new, (global_dummy_element), sizeof(RealDoubleElement)) # This line is only needed if Python is compiled in debugging mode # './configure --with-pydebug' or SAGE_DEBUG=yes. If that is the diff --git a/src/sage/rings/real_double_element_gsl.pyx b/src/sage/rings/real_double_element_gsl.pyx index 9d5f2d9a93b..cf2be375b78 100644 --- a/src/sage/rings/real_double_element_gsl.pyx +++ b/src/sage/rings/real_double_element_gsl.pyx @@ -15,14 +15,13 @@ gsl_set_error_handler_off() cdef class RealDoubleElement_gsl(RealDoubleElement): - def nth_root(self, int n): """ Return the `n^{th}` root of ``self``. INPUT: - - ``n`` -- an integer + - ``n`` -- an integer OUTPUT: @@ -53,9 +52,9 @@ cdef class RealDoubleElement_gsl(RealDoubleElement): from sage.rings.complex_double import CDF return self._complex_double_(CDF).nth_root(n) else: - return - ( (-self) ** (float(1)/n) ) - else: - return self ** (float(1)/n) + return - ((-self) ** (float(1)/n)) + + return self ** (float(1)/n) cdef __pow_double(self, double exponent, double sign): """ @@ -325,7 +324,6 @@ cdef class RealDoubleElement_gsl(RealDoubleElement): sig_off() return a - def log10(self): """ Return log to the base 10 of ``self``. @@ -530,7 +528,6 @@ cdef class RealDoubleElement_gsl(RealDoubleElement): sage: q.tan() 0.5773502691896256 """ - cdef double denom cos = gsl_sf_cos(self._value) a = self._new_c(gsl_sf_sin(self._value) / cos) return a @@ -604,7 +601,6 @@ cdef class RealDoubleElement_gsl(RealDoubleElement): """ return self._new_c(libc.math.atan(self._value)) - def cosh(self): """ Return the hyperbolic cosine of ``self``. @@ -615,7 +611,7 @@ cdef class RealDoubleElement_gsl(RealDoubleElement): sage: q.cosh() 1.0344656400955106 """ - return self._new_c(gsl_ldexp( gsl_sf_exp(self._value) + gsl_sf_exp(-self._value), -1)) # (e^x + e^-x)/2 + return self._new_c(gsl_ldexp(gsl_sf_exp(self._value) + gsl_sf_exp(-self._value), -1)) # (e^x + e^-x)/2 def sinh(self): """ @@ -627,7 +623,7 @@ cdef class RealDoubleElement_gsl(RealDoubleElement): sage: q.sinh() 0.26480022760227073 """ - return self._new_c(gsl_ldexp( gsl_sf_expm1(self._value) - gsl_sf_expm1(-self._value), -1)) # (e^x - e^-x)/2 + return self._new_c(gsl_ldexp(gsl_sf_expm1(self._value) - gsl_sf_expm1(-self._value), -1)) # (e^x - e^-x)/2 def tanh(self): """ diff --git a/src/sage/rings/real_interval_absolute.pyx b/src/sage/rings/real_interval_absolute.pyx index d190329872e..e1c60c049c7 100644 --- a/src/sage/rings/real_interval_absolute.pyx +++ b/src/sage/rings/real_interval_absolute.pyx @@ -14,7 +14,7 @@ from sage.rings.integer cimport Integer import sage.rings.abc from sage.structure.parent cimport Parent -from sage.structure.element cimport parent +from sage.structure.element cimport parent as parent_of from sage.rings.real_mpfr import RR_min_prec from sage.rings.real_mpfi import RealIntervalField, RealIntervalFieldElement @@ -61,6 +61,7 @@ cpdef inline Integer shift_ceil(Integer x, long shift): mpz_cdiv_q_2exp(z.value, x.value, shift) return z + class Factory(UniqueFactory): def create_key(self, prec): """ @@ -86,9 +87,11 @@ class Factory(UniqueFactory): """ return RealIntervalAbsoluteField_class(prec) + RealIntervalAbsoluteField = Factory('sage.rings.real_interval_absolute.RealIntervalAbsoluteField') RealIntervalAbsoluteField.__doc__ = RealIntervalAbsoluteField_class.__doc__ + cdef class RealIntervalAbsoluteField_class(Field): """ This field is similar to the :class:`RealIntervalField` except instead of @@ -239,7 +242,7 @@ cdef inline shift_left(value, shift): cdef class RealIntervalAbsoluteElement(FieldElement): # This could be optimized by letting these be raw mpz_t. - cdef Integer _mantissa # left endpoint + cdef Integer _mantissa # left endpoint cdef Integer _diameter def __init__(self, RealIntervalAbsoluteField_class parent, value): @@ -420,7 +423,7 @@ cdef class RealIntervalAbsoluteElement(FieldElement): sage: RIF(a) 0.3333333333333334? """ - return R(self._mantissa, self._mantissa+self._diameter) >> (self._parent)._absprec + return R(self._mantissa, self._mantissa + self._diameter) >> (self._parent)._absprec cpdef long mpfi_prec(self) noexcept: """ @@ -757,12 +760,12 @@ cdef class RealIntervalAbsoluteElement(FieldElement): return None if neg is None else -neg if type(x) in (int, Integer): return self._new_c(self._mantissa * x, self._diameter * x) - P = parent(x) + P = parent_of(x) if isinstance(P, Parent): if P.is_exact(): left = (self._mantissa * x).floor() right = ((self._mantissa + self._diameter) * x).ceil() - return self._new_c(left, right-left) + return self._new_c(left, right - left) elif isinstance(x, RealIntervalFieldElement): if x.contains_zero() or self.contains_zero(): return self * RealIntervalAbsoluteElement(self._parent, (x.lower(), x.upper())) @@ -773,7 +776,7 @@ cdef class RealIntervalAbsoluteElement(FieldElement): else: left = (self._mantissa * x.upper()).floor() right = ((self._mantissa + self._diameter) * x.lower()).ceil() - return self._new_c(left, right-left) + return self._new_c(left, right - left) def __invert__(self): """ @@ -821,7 +824,7 @@ cdef class RealIntervalAbsoluteElement(FieldElement): mpz_init_set_ui(scaling_factor, 1) try: mpz_set_ui(scaling_factor, 1) - mpz_mul_2exp(scaling_factor, scaling_factor, 2*absprec) + mpz_mul_2exp(scaling_factor, scaling_factor, 2 * absprec) # Use diameter as temp value for right endpoint... mpz_add(diameter.value, self._mantissa.value, self._diameter.value) mpz_fdiv_q(mantissa.value, scaling_factor, diameter.value) @@ -963,8 +966,7 @@ cdef class RealIntervalAbsoluteElement(FieldElement): import math cdef double height base_height = max(abs(base.lower()), abs(base.upper())) - midpoint = base.upper() - height = (math.log(base_height)/math.log(2)) + height = math.log(base_height) / math.log(2) height *= (exponent.midpoint() if isinstance(exponent, RealIntervalAbsoluteElement) else exponent) relprec = max(height + parent._absprec, 10) RIF = RealIntervalField(relprec) diff --git a/src/sage/rings/real_mpfi.pyx b/src/sage/rings/real_mpfi.pyx index da282aceb3b..2ddd9135878 100644 --- a/src/sage/rings/real_mpfi.pyx +++ b/src/sage/rings/real_mpfi.pyx @@ -288,21 +288,21 @@ from sage.cpython.string cimport char_to_str, bytes_to_str from sage.misc.superseded import deprecation import sage.rings.infinity -#***************************************************************************** +# **************************************************************************** # # Implementation # -#***************************************************************************** +# **************************************************************************** # Global settings printing_style = 'question' printing_error_digits = 0 -#***************************************************************************** +# **************************************************************************** # # Real Interval Field # -#***************************************************************************** +# **************************************************************************** cdef dict RealIntervalField_cache = {} cpdef RealIntervalField_class RealIntervalField(prec=53, sci_not=False): @@ -602,12 +602,11 @@ cdef class RealIntervalField_class(sage.rings.abc.RealIntervalField): """ if rnd == "RNDD": return self.lower_field() - elif rnd == "RNDN": + if rnd == "RNDN": return self.middle_field() - elif rnd == "RNDU": + if rnd == "RNDU": return self.upper_field() - else: - return RealField(self._prec, self.sci_not, rnd) + return RealField(self._prec, self.sci_not, rnd) def _repr_(self): """ @@ -620,7 +619,7 @@ cdef class RealIntervalField_class(sage.rings.abc.RealIntervalField): sage: RealIntervalField(200) # indirect doctest Real Interval Field with 200 bits of precision """ - s = "Real Interval Field with %s bits of precision"%self._prec + s = "Real Interval Field with %s bits of precision" % self._prec return s def _latex_(self): @@ -753,8 +752,9 @@ cdef class RealIntervalField_class(sage.rings.abc.RealIntervalField): from sage.categories.pushout import CompletionFunctor return (CompletionFunctor(sage.rings.infinity.Infinity, self.prec(), - {'sci_not': self.scientific_notation(), 'type': 'Interval'}), - sage.rings.rational_field.QQ) + {'sci_not': self.scientific_notation(), + 'type': 'Interval'}), + sage.rings.rational_field.QQ) cpdef _coerce_map_from_(self, S): """ @@ -1006,7 +1006,7 @@ cdef class RealIntervalField_class(sage.rings.abc.RealIntervalField): sage: RealIntervalField(200).name() 'IntervalRealIntervalField200' """ - return "IntervalRealIntervalField%s"%(self._prec) + return "IntervalRealIntervalField%s" % (self._prec) def __hash__(self): """ @@ -1163,11 +1163,11 @@ cdef class RealIntervalField_class(sage.rings.abc.RealIntervalField): raise ValueError("No %sth root of unity in self" % n) -#***************************************************************************** +# **************************************************************************** # # RealIntervalFieldElement -- element of Real Interval Field # -#***************************************************************************** +# **************************************************************************** cdef class RealIntervalFieldElement(RingElement): """ A real number interval. @@ -1372,7 +1372,6 @@ cdef class RealIntervalFieldElement(RingElement): """ raise TypeError - def _sage_input_(self, sib, coerce): r""" Produce an expression which will reproduce this value when evaluated. @@ -1408,7 +1407,7 @@ cdef class RealIntervalFieldElement(RingElement): # The following line would also be correct, but even though it # uses coerced=2, that doesn't help because RealNumber doesn't # print pretty for directed-rounding fields. - #return sib(self.parent())(sib(self.lower(), 2), sib(self.upper(), 2)) + # return sib(self.parent())(sib(self.lower(), 2), sib(self.upper(), 2)) return sib(self.parent())(sib(self.lower(rnd='RNDN')), sib(self.upper(rnd='RNDN'))) def __hash__(self): @@ -1437,7 +1436,7 @@ cdef class RealIntervalFieldElement(RingElement): sage: RIF(2.1)._im_gens_(R, [R(1)]) 2.10000? """ - return codomain(self) # since 1 |--> 1 + return codomain(self) # since 1 |--> 1 def real(self): """ @@ -1687,7 +1686,7 @@ cdef class RealIntervalFieldElement(RingElement): t1 = self.lower().str(base=base, no_sci=no_sci, e=e) t2 = self.upper().str(base=base, no_sci=no_sci, e=e) - return "[%s .. %s]"%(t1, t2) + return "[%s .. %s]" % (t1, t2) elif style == 'question': if no_sci is None: @@ -1931,9 +1930,9 @@ cdef class RealIntervalFieldElement(RingElement): sig_on() lower_s = mpfr_get_str(0, &lower_expo, base, 0, - &self.value.left, MPFR_RNDD) + &self.value.left, MPFR_RNDD) upper_s = mpfr_get_str(0, &upper_expo, base, 0, - &self.value.right, MPFR_RNDU) + &self.value.right, MPFR_RNDU) sig_off() if lower_s == 0: @@ -2165,24 +2164,25 @@ cdef class RealIntervalFieldElement(RingElement): scientific = True if scientific: - return '%s%s.%s?%s%s%s'%(sign_string, - mant_string[0], mant_string[1:], - error_string, e, sci_expo) + return '%s%s.%s?%s%s%s' % (sign_string, + mant_string[0], mant_string[1:], + error_string, e, sci_expo) if expo + digits <= 0: - return '%s0.%s%s?%s'%(sign_string, - '0' * -(expo + digits), mant_string, - error_string) + return '%s0.%s%s?%s' % (sign_string, + '0' * -(expo + digits), mant_string, + error_string) - return '%s%s.%s?%s'%(sign_string, - mant_string[:expo+digits], - mant_string[expo+digits:], - error_string) + return '%s%s.%s?%s' % (sign_string, + mant_string[:expo+digits], + mant_string[expo+digits:], + error_string) def __copy__(self): """ - Return copy of ``self`` - since ``self`` is immutable, we just return - ``self`` again. + Return copy of ``self``. + + Since ``self`` is immutable, we just return ``self`` again. EXAMPLES:: @@ -2790,7 +2790,6 @@ cdef class RealIntervalFieldElement(RingElement): mpfi_mul(x.value, self.value, (right).value) return x - cpdef _div_(self, right): """ Divide ``self`` by ``right``, where both are real intervals with the @@ -2957,7 +2956,7 @@ cdef class RealIntervalFieldElement(RingElement): 0.062500000000000000? """ if isinstance(x, RealIntervalFieldElement) and \ - isinstance(y, (int, Integer)): + isinstance(y, (int, Integer)): return x._rshift_(y) return sage.structure.element.bin_op(x, y, operator.rshift) @@ -3209,9 +3208,9 @@ cdef class RealIntervalFieldElement(RingElement): b = self.upper() P = self.parent() r = P(a.frac(), b.frac()) - if b.floor() > max(a,0): + if b.floor() > max(a, 0): r = r.union(P(0, 1)) - if a.ceil() < min(b,0): + if a.ceil() < min(b, 0): r = r.union(P(-1, 0)) return r @@ -3270,7 +3269,7 @@ cdef class RealIntervalFieldElement(RingElement): else: mpfi_get_left(x.value, self.value) else: - raise AssertionError("%s has unknown rounding mode"%field) + raise AssertionError("%s has unknown rounding mode" % field) return x def __float__(self): @@ -4052,7 +4051,7 @@ cdef class RealIntervalFieldElement(RingElement): True """ return mpfr_greaterequal_p(&self.value.right, &other.value.left) \ - and mpfr_greaterequal_p(&other.value.right, &self.value.left) + and mpfr_greaterequal_p(&other.value.right, &self.value.left) def intersection(self, other): """ @@ -4381,7 +4380,6 @@ cdef class RealIntervalFieldElement(RingElement): raise ValueError("self (=%s) is not >= 0" % self) return self.square_root() - def square_root(self): """ Return a square root of ``self``. An interval will always be returned @@ -5027,12 +5025,11 @@ cdef class RealIntervalFieldElement(RingElement): sage: RIF(-1, 1).algdep(5) x """ - # If 0 is in the interval, then we have no known bits! But # fortunately, there's a perfectly valid answer we can # return anyway. if 0 in self: - #import sage.rings.polynomial.polynomial_ring + # import sage.rings.polynomial.polynomial_ring return sage.rings.polynomial.polynomial_ring.polygen( sage.rings.integer_ring.IntegerRing()) @@ -5176,6 +5173,7 @@ cdef class RealIntervalFieldElement(RingElement): return RealBallField(self.precision())(self).zeta(a).\ _real_mpfi_(self._parent) + def _simplest_rational_test_helper(low, high, low_open=False, high_open=False): """ Call ``_simplest_rational_exact()``. Only used to allow doctests on @@ -5189,6 +5187,7 @@ def _simplest_rational_test_helper(low, high, low_open=False, high_open=False): """ return _simplest_rational_exact(low, high, low_open, high_open) + cdef _simplest_rational_exact(Rational low, Rational high, int low_open, int high_open): """ Return the simplest rational between ``low`` and ``high``. May return @@ -5313,10 +5312,10 @@ def RealInterval(s, upper=None, int base=10, int pad=0, min_prec=53): s = str(s) if base == 10: # hard-code the common case - bits = int(LOG_TEN_TWO_PLUS_EPSILON*len(s)) + bits = int(LOG_TEN_TWO_PLUS_EPSILON * len(s)) else: - bits = int(math.log(base,2)*1.00001*len(s)) - R = RealIntervalField(prec=max(bits+pad, min_prec)) + bits = int(math.log(base, 2) * 1.00001 * len(s)) + R = RealIntervalField(prec=max(bits + pad, min_prec)) if upper is not None: s = (s, upper) return RealIntervalFieldElement(R, s, base) @@ -5325,6 +5324,7 @@ def RealInterval(s, upper=None, int base=10, int pad=0, min_prec=53): # The default real interval field, with precision 53 bits RIF = RealIntervalField() + def is_RealIntervalField(x): """ Check if ``x`` is a :class:`RealIntervalField_class`. @@ -5338,6 +5338,7 @@ def is_RealIntervalField(x): """ return isinstance(x, RealIntervalField_class) + def is_RealIntervalFieldElement(x): """ Check if ``x`` is a :class:`RealIntervalFieldElement`. @@ -5352,7 +5353,7 @@ def is_RealIntervalFieldElement(x): return isinstance(x, RealIntervalFieldElement) -#### pickle functions +# pickle functions def __create__RealIntervalField_version0(prec, sci_not): """ For pickling. diff --git a/src/sage/rings/real_mpfr.pyx b/src/sage/rings/real_mpfr.pyx index 1100a63e3fe..885003ed7f8 100644 --- a/src/sage/rings/real_mpfr.pyx +++ b/src/sage/rings/real_mpfr.pyx @@ -142,7 +142,6 @@ from sage.libs.gmp.pylong cimport mpz_set_pylong from sage.libs.mpfr cimport * from sage.libs.mpmath.utils cimport mpfr_to_mpfval from sage.misc.randstate cimport randstate, current_randstate -from sage.misc.superseded import deprecation_cython as deprecation from sage.structure.element cimport Element from sage.structure.parent cimport Parent @@ -183,11 +182,12 @@ cdef object numpy_object_interface = {'typestr': '|O'} cdef enum: SIG_PREC_THRESHOLD = 1000 -#***************************************************************************** + +# **************************************************************************** # # External Python access to constants # -#***************************************************************************** +# **************************************************************************** def mpfr_prec_min(): """ @@ -211,6 +211,7 @@ def mpfr_prec_min(): """ return MPFR_PREC_MIN + # see Issue #11666 for the origin of this magical constant def mpfr_prec_max(): """ @@ -252,6 +253,7 @@ def mpfr_get_exp_min(): """ return mpfr_get_emin() + def mpfr_get_exp_max(): """ Return the current maximal exponent for MPFR numbers. @@ -270,6 +272,7 @@ def mpfr_get_exp_max(): """ return mpfr_get_emax() + def mpfr_set_exp_min(mp_exp_t e): """ Set the minimal exponent for MPFR numbers. @@ -290,6 +293,7 @@ def mpfr_set_exp_min(mp_exp_t e): if mpfr_set_emin(e) != 0: raise OverflowError("bad value for mpfr_set_exp_min()") + def mpfr_set_exp_max(mp_exp_t e): """ Set the maximal exponent for MPFR numbers. @@ -310,6 +314,7 @@ def mpfr_set_exp_max(mp_exp_t e): if mpfr_set_emax(e) != 0: raise OverflowError("bad value for mpfr_set_exp_max()") + def mpfr_get_exp_min_min(): """ Get the minimal value allowed for :func:`mpfr_set_exp_min`. @@ -330,6 +335,7 @@ def mpfr_get_exp_min_min(): """ return mpfr_get_emin_min() + def mpfr_get_exp_max_max(): """ Get the maximal value allowed for :func:`mpfr_set_exp_max`. @@ -350,6 +356,7 @@ def mpfr_get_exp_max_max(): """ return mpfr_get_emax_max() + # On Sage startup, set the exponent range to the maximum allowed mpfr_set_exp_min(mpfr_get_emin_min()) mpfr_set_exp_max(mpfr_get_emax_max()) @@ -365,9 +372,11 @@ mpfr_set_exp_max(mpfr_get_emax_max()) from sage.arith.long cimport (pyobject_to_long, integer_check_long_py, ERR_OVERFLOW) cdef dict rounding_modes = dict(RNDN=MPFR_RNDN, RNDZ=MPFR_RNDZ, - RNDD=MPFR_RNDD, RNDU=MPFR_RNDU, RNDA=MPFR_RNDA, RNDF=MPFR_RNDF) + RNDD=MPFR_RNDD, RNDU=MPFR_RNDU, + RNDA=MPFR_RNDA, RNDF=MPFR_RNDF) -cdef double LOG_TEN_TWO_PLUS_EPSILON = 3.321928094887363 # a small overestimate of log(10,2) +cdef double LOG_TEN_TWO_PLUS_EPSILON = 3.321928094887363 +# a small overestimate of log(10,2) cdef object RealField_cache = sage.misc.weak_dict.WeakValueDictionary() @@ -443,8 +452,8 @@ cpdef RealField(mpfr_prec_t prec=53, int sci_not=0, rnd=MPFR_RNDN): try: r = rounding_modes[rnd] except KeyError: - raise ValueError("rounding mode (={!r}) must be one of {}".format(rnd, - sorted(rounding_modes))) + msg = "rounding mode (={!r}) must be one of {}" + raise ValueError(msg.format(rnd, sorted(rounding_modes))) try: return RealField_cache[prec, sci_not, r] @@ -570,9 +579,9 @@ cdef class RealField_class(sage.rings.abc.RealField): sage: RealField(17,rnd='RNDD') # indirect doctest Real Field with 17 bits of precision and rounding RNDD """ - s = "Real Field with %s bits of precision"%self._prec + s = "Real Field with %s bits of precision" % self._prec if self.rnd != MPFR_RNDN: - s = s + " and rounding %s"%(self.rnd_str) + s = s + " and rounding %s" % (self.rnd_str) return s def _latex_(self): @@ -829,8 +838,10 @@ cdef class RealField_class(sage.rings.abc.RealField): from sage.categories.pushout import CompletionFunctor return (CompletionFunctor(sage.rings.infinity.Infinity, self.prec(), - {'type': 'MPFR', 'sci_not': self.scientific_notation(), 'rnd': self.rnd}), - sage.rings.rational_field.QQ) + {'type': 'MPFR', + 'sci_not': self.scientific_notation(), + 'rnd': self.rnd}), + sage.rings.rational_field.QQ) def gen(self, i=0): """ @@ -976,7 +987,7 @@ cdef class RealField_class(sage.rings.abc.RealField): sage: RealField(100,rnd='RNDU').name() 'RealField100_2' """ - return "RealField%s_%s"%(self._prec,self.rnd) + return "RealField%s_%s" % (self._prec, self.rnd) def __hash__(self): """ @@ -1005,7 +1016,7 @@ cdef class RealField_class(sage.rings.abc.RealField): """ return Integer(self._prec) - prec=precision # an alias + prec = precision # an alias def _magma_init_(self, magma): r""" @@ -1060,7 +1071,8 @@ cdef class RealField_class(sage.rings.abc.RealField): 0.88622692545275801364908374167057259139877473 """ cdef RealNumber x = self._new() - if self._prec > SIG_PREC_THRESHOLD: sig_on() + if self._prec > SIG_PREC_THRESHOLD: + sig_on() # The docs for mpfr_free_cache say "Free the cache used by # the functions computing constants if needed (currently # mpfr_const_log2, mpfr_const_pi and mpfr_const_euler)", so @@ -1070,7 +1082,8 @@ cdef class RealField_class(sage.rings.abc.RealField): # functions, but this free is needed for them too! mpfr_free_cache() mpfr_const_pi(x.value, self.rnd) - if self._prec > SIG_PREC_THRESHOLD: sig_off() + if self._prec > SIG_PREC_THRESHOLD: + sig_off() return x def euler_constant(self): @@ -1099,10 +1112,12 @@ cdef class RealField_class(sage.rings.abc.RealField): 0.91596559417721901505460351493 """ cdef RealNumber x = self._new() - if self._prec > SIG_PREC_THRESHOLD: sig_on() + if self._prec > SIG_PREC_THRESHOLD: + sig_on() mpfr_free_cache() mpfr_const_catalan(x.value, self.rnd) - if self._prec > SIG_PREC_THRESHOLD: sig_off() + if self._prec > SIG_PREC_THRESHOLD: + sig_off() return x def log2(self): @@ -1119,10 +1134,12 @@ cdef class RealField_class(sage.rings.abc.RealField): 0.69314718055994530941723212146 """ cdef RealNumber x = self._new() - if self._prec > SIG_PREC_THRESHOLD: sig_on() + if self._prec > SIG_PREC_THRESHOLD: + sig_on() mpfr_free_cache() mpfr_const_log2(x.value, self.rnd) - if self._prec > SIG_PREC_THRESHOLD: sig_off() + if self._prec > SIG_PREC_THRESHOLD: + sig_off() return x def random_element(self, min=-1, max=1, distribution=None): @@ -1189,9 +1206,11 @@ cdef class RealField_class(sage.rings.abc.RealField): if n < 0: raise ArithmeticError("n must be nonnegative") x = self._new() - if self._prec > SIG_PREC_THRESHOLD and n < SIG_PREC_THRESHOLD: sig_on() + if self._prec > SIG_PREC_THRESHOLD and n < SIG_PREC_THRESHOLD: + sig_on() mpfr_fac_ui(x.value, n, self.rnd) - if self._prec > SIG_PREC_THRESHOLD and n < SIG_PREC_THRESHOLD: sig_off() + if self._prec > SIG_PREC_THRESHOLD and n < SIG_PREC_THRESHOLD: + sig_off() return x def rounding_mode(self): @@ -1314,13 +1333,14 @@ cdef class RealField_class(sage.rings.abc.RealField): F = list(f._pari_with_name().factor()) from sage.structure.factorization import Factorization - return Factorization([(R(g).monic(),e) for g,e in zip(*F)], f.leading_coefficient()) + return Factorization([(R(g).monic(), e) for g, e in zip(*F)], + f.leading_coefficient()) -#***************************************************************************** +# **************************************************************************** # # RealNumber -- element of Real Field # -#***************************************************************************** +# **************************************************************************** cdef class RealLiteral(RealNumber) @@ -1496,7 +1516,7 @@ cdef class RealNumber(sage.structure.element.RingElement): elif type(x) is gmpy2.mpz: mpfr_set_z(self.value, (x).z, parent.rnd) else: - s = str(x).replace(' ','').replace('_', '') + s = str(x).replace(' ', '').replace('_', '') s_lower = s.lower() if s_lower == 'infinity': raise ValueError('can only convert signed infinity to RR') @@ -1742,8 +1762,8 @@ cdef class RealNumber(sage.structure.element.RingElement): # and "-1.3*x".) cdef bint can_use_float_literal = \ rnd == MPFR_RNDN and (sib.preparse() or - ((will_convert or self.prec() <= 53) and - self._parent(float(self_str)) == self)) + ((will_convert or self.prec() <= 53) and + self._parent(float(self_str)) == self)) if can_use_int_literal or can_use_float_literal: if can_use_int_literal: @@ -1809,7 +1829,7 @@ cdef class RealNumber(sage.structure.element.RingElement): sage: RR(2.1)._im_gens_(R, [R(1)]) 2.1000 """ - return codomain(self) # since 1 |--> 1 + return codomain(self) # since 1 |--> 1 def real(self): """ @@ -2537,7 +2557,6 @@ cdef class RealNumber(sage.structure.element.RingElement): mpfr_mul(x.value, self.value, (right).value, (self._parent).rnd) return x - cpdef _div_(self, right): """ Divide ``self`` by other, where both are real numbers with the same @@ -2687,7 +2706,6 @@ cdef class RealNumber(sage.structure.element.RingElement): except TypeError: raise TypeError("unsupported operands for >>") - def multiplicative_order(self): """ Return the multiplicative order of ``self``. @@ -2738,7 +2756,7 @@ cdef class RealNumber(sage.structure.element.RingElement): """ return Integer((self._parent)._prec) - prec = precision # alias + prec = precision # alias def conjugate(self): """ @@ -2861,7 +2879,7 @@ cdef class RealNumber(sage.structure.element.RingElement): mpfr_nextabove(x.value) elif mpfr_inf_p(self.value): mpfr_set_inf(x.value, 1) - else: # NaN + else: # NaN mpfr_set_nan(x.value) return x @@ -2940,7 +2958,6 @@ cdef class RealNumber(sage.structure.element.RingElement): mpfr_abs(x.value, x.value, _parent.rnd) return x - ################### # Rounding etc ################### @@ -2965,8 +2982,8 @@ cdef class RealNumber(sage.structure.element.RingElement): cdef RealNumber x x = (left)._new() mpfr_remainder (x.value, (left).value, - (right).value, - ((left)._parent).rnd) + (right).value, + ((left)._parent).rnd) return x def round(self): @@ -3049,7 +3066,7 @@ cdef class RealNumber(sage.structure.element.RingElement): mpfr_ceil(x.value, self.value) return x.integer_part() - ceiling = ceil # alias + ceiling = ceil # alias def trunc(self): """ @@ -3273,10 +3290,10 @@ cdef class RealNumber(sage.structure.element.RingElement): """ prec = self.parent().prec() - #Set the precision in Axiom - old_prec = axiom('precision(%s)$Float'%prec) - res = axiom('%s :: Float'%self.exact_rational()) - axiom.eval('precision(%s)$Float'%old_prec) + # Set the precision in Axiom + old_prec = axiom('precision(%s)$Float' % prec) + res = axiom('%s :: Float' % self.exact_rational()) + axiom.eval('precision(%s)$Float' % old_prec) return res @@ -3412,15 +3429,14 @@ cdef class RealNumber(sage.structure.element.RingElement): cdef Integer sign cdef mp_exp_t exponent - - if mpfr_signbit(self.value)==0: - sign=Integer(1) + if mpfr_signbit(self.value) == 0: + sign = Integer(1) else: - sign=Integer(-1) + sign = Integer(-1) if mpfr_zero_p(self.value): - mantissa=Integer(0) - exponent=Integer(0) + mantissa = Integer(0) + exponent = Integer(0) else: mantissa = Integer() exponent = mpfr_get_z_exp(mantissa.value, self.value) @@ -3752,7 +3768,7 @@ cdef class RealNumber(sage.structure.element.RingElement): raise ValueError('cannot convert NaN or infinity to rational number') if ((max_error is None and max_denominator is None) or - (max_error is not None and max_denominator is not None)): + (max_error is not None and max_denominator is not None)): raise ValueError('Must specify exactly one of max_error or max_denominator in nearby_rational()') if max_error is not None: @@ -4192,9 +4208,11 @@ cdef class RealNumber(sage.structure.element.RingElement): cdef RealNumber x if mpfr_cmp_ui(self.value, 0) >= 0: x = self._new() - if (self._parent)._prec > 10*SIG_PREC_THRESHOLD: sig_on() + if (self._parent)._prec > 10*SIG_PREC_THRESHOLD: + sig_on() mpfr_sqrt(x.value, self.value, (self._parent).rnd) - if (self._parent)._prec > 10*SIG_PREC_THRESHOLD: sig_off() + if (self._parent)._prec > 10*SIG_PREC_THRESHOLD: + sig_off() if all: if x.is_zero(): return [x] @@ -4238,9 +4256,11 @@ cdef class RealNumber(sage.structure.element.RingElement): -1.42108547152020e-14 """ cdef RealNumber x = self._new() - if (self._parent)._prec > 10*SIG_PREC_THRESHOLD: sig_on() + if (self._parent)._prec > 10*SIG_PREC_THRESHOLD: + sig_on() mpfr_cbrt(x.value, self.value, (self._parent).rnd) - if (self._parent)._prec > 10*SIG_PREC_THRESHOLD: sig_off() + if (self._parent)._prec > 10*SIG_PREC_THRESHOLD: + sig_off() return x def _pow(self, RealNumber exponent): @@ -4380,9 +4400,11 @@ cdef class RealNumber(sage.structure.element.RingElement): return self._complex_number_().log(base) if base is None or base == 'e': x = self._new() - if (self._parent)._prec > SIG_PREC_THRESHOLD: sig_on() + if (self._parent)._prec > SIG_PREC_THRESHOLD: + sig_on() mpfr_log(x.value, self.value, (self._parent).rnd) - if (self._parent)._prec > SIG_PREC_THRESHOLD: sig_off() + if (self._parent)._prec > SIG_PREC_THRESHOLD: + sig_off() return x elif base == 10: return self.log10() @@ -4421,9 +4443,11 @@ cdef class RealNumber(sage.structure.element.RingElement): if self < 0: return self._complex_number_().log(2) x = self._new() - if (self._parent)._prec > SIG_PREC_THRESHOLD: sig_on() + if (self._parent)._prec > SIG_PREC_THRESHOLD: + sig_on() mpfr_log2(x.value, self.value, (self._parent).rnd) - if (self._parent)._prec > SIG_PREC_THRESHOLD: sig_off() + if (self._parent)._prec > SIG_PREC_THRESHOLD: + sig_off() return x def log10(self): @@ -4458,9 +4482,11 @@ cdef class RealNumber(sage.structure.element.RingElement): if self < 0: return self._complex_number_().log(10) x = self._new() - if (self._parent)._prec > SIG_PREC_THRESHOLD: sig_on() + if (self._parent)._prec > SIG_PREC_THRESHOLD: + sig_on() mpfr_log10(x.value, self.value, (self._parent).rnd) - if (self._parent)._prec > SIG_PREC_THRESHOLD: sig_off() + if (self._parent)._prec > SIG_PREC_THRESHOLD: + sig_off() return x def log1p(self): @@ -4507,9 +4533,11 @@ cdef class RealNumber(sage.structure.element.RingElement): if self < -1: return (self+1.0)._complex_number_().log() x = self._new() - if (self._parent)._prec > SIG_PREC_THRESHOLD: sig_on() + if (self._parent)._prec > SIG_PREC_THRESHOLD: + sig_on() mpfr_log1p(x.value, self.value, (self._parent).rnd) - if (self._parent)._prec > SIG_PREC_THRESHOLD: sig_off() + if (self._parent)._prec > SIG_PREC_THRESHOLD: + sig_off() return x def exp(self): @@ -4565,9 +4593,11 @@ cdef class RealNumber(sage.structure.element.RingElement): 1.89117248253021e-10 """ cdef RealNumber x = self._new() - if (self._parent)._prec > SIG_PREC_THRESHOLD: sig_on() + if (self._parent)._prec > SIG_PREC_THRESHOLD: + sig_on() mpfr_exp2(x.value, self.value, (self._parent).rnd) - if (self._parent)._prec > SIG_PREC_THRESHOLD: sig_off() + if (self._parent)._prec > SIG_PREC_THRESHOLD: + sig_off() return x def exp10(self): @@ -4593,9 +4623,11 @@ cdef class RealNumber(sage.structure.element.RingElement): 5.01187233627276e-33 """ cdef RealNumber x = self._new() - if (self._parent)._prec > SIG_PREC_THRESHOLD: sig_on() + if (self._parent)._prec > SIG_PREC_THRESHOLD: + sig_on() mpfr_exp10(x.value, self.value, (self._parent).rnd) - if (self._parent)._prec > SIG_PREC_THRESHOLD: sig_off() + if (self._parent)._prec > SIG_PREC_THRESHOLD: + sig_off() return x def expm1(self): @@ -4617,9 +4649,11 @@ cdef class RealNumber(sage.structure.element.RingElement): 1.00000000000000e-16 """ cdef RealNumber x = self._new() - if (self._parent)._prec > SIG_PREC_THRESHOLD: sig_on() + if (self._parent)._prec > SIG_PREC_THRESHOLD: + sig_on() mpfr_expm1(x.value, self.value, (self._parent).rnd) - if (self._parent)._prec > SIG_PREC_THRESHOLD: sig_off() + if (self._parent)._prec > SIG_PREC_THRESHOLD: + sig_off() return x def eint(self): @@ -4639,9 +4673,11 @@ cdef class RealNumber(sage.structure.element.RingElement): -0.219383934395520 """ cdef RealNumber x = self._new() - if (self._parent)._prec > SIG_PREC_THRESHOLD: sig_on() + if (self._parent)._prec > SIG_PREC_THRESHOLD: + sig_on() mpfr_eint(x.value, self.value, (self._parent).rnd) - if (self._parent)._prec > SIG_PREC_THRESHOLD: sig_on() + if (self._parent)._prec > SIG_PREC_THRESHOLD: + sig_on() return x def cos(self): @@ -4706,13 +4742,13 @@ cdef class RealNumber(sage.structure.element.RingElement): sage: t.sincos() (0.500000000000000, 0.866025403784439) """ - cdef RealNumber x,y + cdef RealNumber x, y x = self._new() y = self._new() sig_on() mpfr_sin_cos(x.value, y.value, self.value, (self._parent).rnd) sig_off() - return x,y + return x, y def arccos(self): """ @@ -5070,9 +5106,11 @@ cdef class RealNumber(sage.structure.element.RingElement): _other = self._parent(other) x = self._new() - if (self._parent)._prec > 10000: sig_on() + if (self._parent)._prec > 10000: + sig_on() mpfr_agm(x.value, self.value, _other.value, (self._parent).rnd) - if (self._parent)._prec > 10000: sig_off() + if (self._parent)._prec > 10000: + sig_off() return x def erf(self): @@ -5225,16 +5263,19 @@ cdef class RealNumber(sage.structure.element.RingElement): 0.886226925452758 """ cdef RealNumber x = self._new() - if (self._parent)._prec > SIG_PREC_THRESHOLD: sig_on() + if (self._parent)._prec > SIG_PREC_THRESHOLD: + sig_on() mpfr_gamma(x.value, self.value, (self._parent).rnd) - if (self._parent)._prec > SIG_PREC_THRESHOLD: sig_off() + if (self._parent)._prec > SIG_PREC_THRESHOLD: + sig_off() return x def log_gamma(self): """ - Return the principal branch of the log gamma of ``self``. Note that - this is not in general equal to log(gamma(``self``)) for negative - input. + Return the principal branch of the log gamma of ``self``. + + Note that this is not in general equal to log(gamma(``self``)) + for negative input. EXAMPLES:: @@ -5334,7 +5375,7 @@ cdef class RealNumber(sage.structure.element.RingElement): sage: r.algebraic_dependency(5) x^2 - 2 """ - return sage.arith.misc.algdep(self,n) + return sage.arith.misc.algdep(self, n) algdep = algebraic_dependency @@ -5827,9 +5868,9 @@ def create_RealNumber(s, int base=10, int pad=0, rnd="RNDN", int min_prec=53): if base == 10: # hard-code the common case - bits = int(LOG_TEN_TWO_PLUS_EPSILON*sigfigs)+1 + bits = int(LOG_TEN_TWO_PLUS_EPSILON * sigfigs) + 1 else: - bits = int(math.log(base,2)*1.00001*sigfigs)+1 + bits = int(math.log(base, 2) * 1.00001 * sigfigs) + 1 R = RealField(prec=max(bits + pad, min_prec), rnd=rnd) @@ -5855,6 +5896,7 @@ def is_RealNumber(x): """ return isinstance(x, RealNumber) + def __create__RealField_version0(prec, sci_not, rnd): """ Create a :class:`RealField_class` by calling :func:`RealField()`. @@ -5866,6 +5908,7 @@ def __create__RealField_version0(prec, sci_not, rnd): """ return RealField(prec, sci_not, rnd) + def __create__RealNumber_version0(parent, x, base=10): """ Create a :class:`RealNumber`. diff --git a/src/sage/schemes/curves/affine_curve.py b/src/sage/schemes/curves/affine_curve.py index 992cd528803..efbc690b543 100644 --- a/src/sage/schemes/curves/affine_curve.py +++ b/src/sage/schemes/curves/affine_curve.py @@ -174,10 +174,11 @@ class AffineCurve(Curve_generic, AlgebraicScheme_subscheme_affine): EXAMPLES:: + sage: # needs sage.rings.number_field sage: R. = QQ[] - sage: K. = NumberField(v^2 + 3) # needs sage.rings.number_field - sage: A. = AffineSpace(K, 3) # needs sage.rings.number_field - sage: C = Curve([z - u*x^2, y^2], A); C # needs sage.rings.number_field + sage: K. = NumberField(v^2 + 3) + sage: A. = AffineSpace(K, 3) + sage: C = Curve([z - u*x^2, y^2], A); C Affine Curve over Number Field in u with defining polynomial v^2 + 3 defined by (-u)*x^2 + z, y^2 @@ -194,10 +195,11 @@ def __init__(self, A, X): EXAMPLES:: + sage: # needs sage.rings.number_field sage: R. = QQ[] - sage: K. = NumberField(v^2 + 3) # needs sage.rings.number_field - sage: A. = AffineSpace(K, 3) # needs sage.rings.number_field - sage: C = Curve([z - u*x^2, y^2], A); C # needs sage.rings.number_field + sage: K. = NumberField(v^2 + 3) + sage: A. = AffineSpace(K, 3) + sage: C = Curve([z - u*x^2, y^2], A); C Affine Curve over Number Field in u with defining polynomial v^2 + 3 defined by (-u)*x^2 + z, y^2 @@ -456,23 +458,25 @@ def plot(self, *args, **kwds): sage: R. = QQ[] sage: C = Curve(x^3 - y^2) - sage: C.plot() # needs sage.plot + sage: C.plot() # needs sage.plot Graphics object consisting of 1 graphics primitive A 5-nodal curve of degree 11. This example also illustrates some of the optional arguments:: + sage: # needs sage.plot() sage: R. = ZZ[] sage: C = Curve(32*x^2 - 2097152*y^11 + 1441792*y^9 ....: - 360448*y^7 + 39424*y^5 - 1760*y^3 + 22*y - 1) - sage: C.plot((x, -1, 1), (y, -1, 1), plot_points=400) # needs sage.plot + sage: C.plot((x, -1, 1), (y, -1, 1), plot_points=400) Graphics object consisting of 1 graphics primitive A line over `\mathbf{RR}`:: + sage: # needs sage.symbolic sage.plot sage: R. = RR[] - sage: C = Curve(R(y - sqrt(2)*x)) # needs sage.symbolic - sage: C.plot() # needs sage.plot + sage: C = Curve(R(y - sqrt(2)*x)) + sage: C.plot() Graphics object consisting of 1 graphics primitive """ Id = self.defining_ideal() @@ -843,10 +847,11 @@ def __init__(self, A, X): EXAMPLES:: + sage: # needs sage.rings.number_field sage: R. = QQ[] - sage: K. = NumberField(v^2 + 3) # needs sage.rings.number_field - sage: A. = AffineSpace(K, 3) # needs sage.rings.number_field - sage: C = Curve([z - u*x^2, y^2], A); C # needs sage.rings.number_field + sage: K. = NumberField(v^2 + 3) + sage: A. = AffineSpace(K, 3) + sage: C = Curve([z - u*x^2, y^2], A); C Affine Curve over Number Field in u with defining polynomial v^2 + 3 defined by (-u)*x^2 + z, y^2 @@ -1293,9 +1298,10 @@ def blowup(self, P=None): :: - sage: A. = AffineSpace(QuadraticField(-1), 2) # needs sage.rings.number_field - sage: C = A.curve([y^2 + x^2]) # needs sage.rings.number_field - sage: C.blowup() # needs sage.rings.number_field + sage: # needs sage.rings.number_field + sage: A. = AffineSpace(QuadraticField(-1), 2) + sage: C = A.curve([y^2 + x^2]) + sage: C.blowup() Traceback (most recent call last): ... TypeError: this curve must be irreducible @@ -1725,7 +1731,6 @@ def tangent_line(self, p): defined by: -2*y + z + 1, x + y + z sage: _ == C.tangent_line(p) True - """ A = self.ambient_space() R = A.coordinate_ring() @@ -1752,8 +1757,48 @@ class AffinePlaneCurve_field(AffinePlaneCurve, AffineCurve_field): """ _point = AffinePlaneCurvePoint_field + def has_vertical_asymptote(self): + """ + Check if the curve is not a line and has vertical asymptotes. + + EXAMPLES:: + + sage: A2. = AffineSpace(2, QQ) + sage: Curve(x).has_vertical_asymptote() + False + sage: Curve(y^2 * x + x + y).has_vertical_asymptote() + True + """ + A = self.ambient_space() + R = A.coordinate_ring() + x, y = R.gens() + f = self.defining_polynomial().radical() + dy = f.degree(y) + dxy = f.coefficient({y: dy}).degree() + return dxy > 0 and f.degree() > 1 + + def is_vertical_line(self): + """ + Check if the curve is a vertical line. + + EXAMPLES:: + + sage: A2. = AffineSpace(2, QQ) + sage: Curve(x - 1).is_vertical_line() + True + sage: Curve(x - y).is_vertical_line() + False + sage: Curve(y^2 * x + x + y).is_vertical_line() + False + """ + A = self.ambient_space() + R = A.coordinate_ring() + x, y = R.gens() + f = self.defining_polynomial().radical() + return f.degree(y) == 0 and f.degree() == 1 + @cached_method - def fundamental_group(self, simplified=True, puiseux=False): + def fundamental_group(self, simplified=True, puiseux=True): r""" Return a presentation of the fundamental group of the complement of ``self``. @@ -1762,9 +1807,9 @@ def fundamental_group(self, simplified=True, puiseux=False): - ``simplified`` -- (default: ``True``) boolean to simplify the presentation. - - ``puiseux`` -- (default: ``False``) boolean to decide if the + - ``puiseux`` -- (default: ``True``) boolean to decide if the presentation is constructed in the classical way or using Puiseux - shortcut. If ``True``, ``simplified`` is set to ``False``. + shortcut. OUTPUT: @@ -1781,17 +1826,18 @@ def fundamental_group(self, simplified=True, puiseux=False): .. NOTE:: The curve must be defined over the rationals or a number field - with an embedding over `\QQbar`. + with an embedding over `\QQbar`. This functionality requires + the ``sirocco`` package to be installed. EXAMPLES:: - sage: # optional - sirocco + sage: # needs sirocco sage: A. = AffineSpace(QQ, 2) sage: C = A.curve(y^2 - x^3 - x^2) - sage: C.fundamental_group() + sage: C.fundamental_group(puiseux=False) Finitely presented group < x0 | > sage: bm = C.braid_monodromy() - sage: g = C.fundamental_group(puiseux=True) + sage: g = C.fundamental_group(simplified=False) sage: g.sorted_presentation() Finitely presented group < x0, x1 | x1^-1*x0^-1*x1*x0, x1^-1*x0 > sage: g.simplified() @@ -1808,15 +1854,28 @@ def fundamental_group(self, simplified=True, puiseux=False): Defining a sage: A. = AffineSpace(F, 2) sage: C = A.curve(y^2 - a*x^3 - x^2) - sage: C.fundamental_group() # optional - sirocco + sage: C.fundamental_group() # needs sirocco Finitely presented group < x0 | > - - .. WARNING:: - - This functionality requires the sirocco package to be installed. + sage: C = A.curve(x * (x - 1)) + sage: C.fundamental_group() # needs sirocco + Finitely presented group < x0, x1 | > """ from sage.schemes.curves.zariski_vankampen import fundamental_group_from_braid_mon - return fundamental_group_from_braid_mon(self.braid_monodromy(), simplified=simplified, puiseux=puiseux) + bm = self.braid_monodromy() + if not bm: + f = self.defining_polynomial() + x, y = f.parent().gens() + d0 = f.degree(y) + f0 = f.coefficient({y: d0}) + d = d0 + f0.degree(x) + else: + d = bm[0].parent().strands() + G = fundamental_group_from_braid_mon(bm, degree=d, + simplified=simplified, + puiseux=puiseux) + if simplified: + G = G.simplified() + return G @cached_method def braid_monodromy(self): @@ -1829,20 +1888,31 @@ def braid_monodromy(self): each of this paths is the conjugated of a loop around one of the points in the discriminant of the projection of ``self``. - NOTE: + .. NOTE:: + + The projection over the `x` axis is used if there are no vertical asymptotes. + Otherwise, a linear change of variables is done to fall into the previous case. - The projection over the `x` axis is used if there are no vertical asymptotes. - Otherwise, a linear change of variables is done to fall into the previous case. + .. NOTE:: + + This functionality requires the ``sirocco`` package to be installed. EXAMPLES:: sage: A. = AffineSpace(QQ, 2) sage: C = A.curve((x^2-y^3)*(x+3*y-5)) - sage: C.braid_monodromy() # optional - sirocco + sage: C.braid_monodromy() # needs sirocco [s1*s0*(s1*s2)^2*s0*s2^2*s0^-1*(s2^-1*s1^-1)^2*s0^-1*s1^-1, s1*s0*(s1*s2)^2*(s0*s2^-1*s1*s2*s1*s2^-1)^2*(s2^-1*s1^-1)^2*s0^-1*s1^-1, s1*s0*(s1*s2)^2*s2*s1^-1*s2^-1*s1^-1*s0^-1*s1^-1, s1*s0*s2*s0^-1*s2*s1^-1] + sage: T. = QQ[] + sage: K. = NumberField(t^3 + 2, 'a') + sage: A. = AffineSpace(K, 2) + sage: Curve(y^2 + a * x).braid_monodromy() + Traceback (most recent call last): + ... + NotImplementedError: the base field must have an embedding to the algebraic field """ from sage.schemes.curves.zariski_vankampen import braid_monodromy @@ -2047,9 +2117,10 @@ def function_field(self): :: - sage: A. = AffineSpace(GF(8), 2) # needs sage.rings.finite_rings - sage: C = Curve(x^5 + y^5 + x*y + 1) # needs sage.rings.finite_rings - sage: C.function_field() # needs sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: A. = AffineSpace(GF(8), 2) + sage: C = Curve(x^5 + y^5 + x*y + 1) + sage: C.function_field() Function field in y defined by y^5 + x*y + x^5 + 1 """ return self._function_field diff --git a/src/sage/schemes/curves/all.py b/src/sage/schemes/curves/all.py index 147c2e1e6fe..b34592457c6 100644 --- a/src/sage/schemes/curves/all.py +++ b/src/sage/schemes/curves/all.py @@ -23,3 +23,11 @@ from .constructor import Curve from .projective_curve import Hasse_bounds + +from sage.misc.lazy_import import lazy_import + +lazy_import('sage.schemes.curves.plane_curve_arrangement', 'PlaneCurveArrangements') + +lazy_import('sage.schemes.curves.plane_curve_arrangement', 'AffinePlaneCurveArrangements') + +lazy_import('sage.schemes.curves.plane_curve_arrangement', 'ProjectivePlaneCurveArrangements') diff --git a/src/sage/schemes/curves/plane_curve_arrangement.py b/src/sage/schemes/curves/plane_curve_arrangement.py new file mode 100644 index 00000000000..df4ba63f4e6 --- /dev/null +++ b/src/sage/schemes/curves/plane_curve_arrangement.py @@ -0,0 +1,1304 @@ +r""" +Affine and Projective Plane Curve Arrangements + +We create classes :class:`AffinePlaneCurveArrangements` +and :class:`ProjectivePlaneCurveArrangements` +following the properties of :class:`HyperplaneArrangements`:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: C = H(3*x + 2*y - x^2 + y^3 - 7); C + Arrangement (y^3 - x^2 + 3*x + 2*y - 7) in Affine Space of dimension 2 over Rational Field + +The individual curves will be in :class:`AffinePlaneCurve` or in :class:`ProjectivePlaneCurve`:: + + sage: C[0].parent() + + +The default base field is `\QQ`, the rational numbers. +Number fields are also possible (also with fixed embeddings in +`\QQbar`):: + + sage: # needs sage.rings.number_field + sage: x = polygen(QQ, 'x') + sage: NF. = NumberField(x^4 - 5 * x^2 + 5, embedding=1.90) + sage: H. = AffinePlaneCurveArrangements(NF) + sage: A = H(y^2 - a * z, y^2 + a * z); A + Arrangement (y^2 + (-a)*z, y^2 + a*z) in Affine Space of dimension 2 + over Number Field in a with defining polynomial + x^4 - 5*x^2 + 5 with a = 1.902113032590308? + sage: A.base_ring() + Number Field in a with defining polynomial x^4 - 5*x^2 + 5 + with a = 1.902113032590308? + + +AUTHORS: + +- Enrique Artal (2023-10): initial version +""" + +# ***************************************************************************** +# Copyright (C) 2023 Enrique Artal +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +# ***************************************************************************** + +from itertools import combinations +from sage.categories.sets_cat import Sets +from sage.groups.free_group import FreeGroup +from sage.misc.abstract_method import abstract_method +from sage.misc.cachefunc import cached_method +from sage.misc.misc_c import prod +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.qqbar import QQbar +from sage.rings.ring import _Fields +from sage.schemes.affine.affine_space import AffineSpace +from sage.schemes.curves.affine_curve import AffinePlaneCurve +from sage.schemes.curves.constructor import Curve +from sage.schemes.curves.projective_curve import ProjectiveSpace, ProjectivePlaneCurve +from sage.schemes.curves.zariski_vankampen import braid_monodromy, fundamental_group_arrangement +from sage.structure.category_object import normalize_names +from sage.structure.parent import Parent +from sage.structure.element import Element +from sage.structure.richcmp import richcmp +from sage.structure.unique_representation import UniqueRepresentation + + +class PlaneCurveArrangementElement(Element): + """ + An ordered plane curve arrangement. + """ + def __init__(self, parent, curves, check=True): + """ + Construct a plane curve arrangement. + + INPUT: + + - ``parent`` -- the parent :class:`PlaneCurveArrangements` + + - ``curves`` -- a tuple of curves + + EXAMPLES:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: elt = H(x, y); elt + Arrangement (x, y) in Affine Space of dimension 2 over Rational Field + sage: TestSuite(elt).run() + sage: H. = ProjectivePlaneCurveArrangements(QQ) + sage: elt = H(x, y); elt + Arrangement (x, y) in Projective Space of dimension 2 over Rational Field + sage: TestSuite(elt).run() + """ + super().__init__(parent) + self._curves = tuple(curves) + if check: + affine = all(isinstance(h, AffinePlaneCurve) for h in curves) + projective = all(isinstance(h, ProjectivePlaneCurve) for h in curves) + if not (affine or projective): + raise ValueError("not all elements are curves") + if not all(h.ambient_space() is parent.ambient_space() + for h in curves): + raise ValueError("not all curves are in the same ambient space") + + def __getitem__(self, i): + """ + Return the `i`-th curve. + + INPUT: + + - ``i`` -- integer + + OUTPUT: + + The `i`-th curve. + + EXAMPLES:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: H(y^2 - x, y^3 + 2 * x^2, x^4 + y^4 + 1) + Arrangement (y^2 - x, y^3 + 2*x^2, x^4 + y^4 + 1) + in Affine Space of dimension 2 over Rational Field + """ + return self._curves[i] + + def __hash__(self): + r""" + TESTS:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: H((x * y, x + y +1)).__hash__() # random + -4938643871296220686 + """ + return hash(self.curves()) + + def ncurves(self): + r""" + Return the number of curves in the arrangement. + + OUTPUT: + + An integer. + + EXAMPLES:: + + sage: H. = ProjectivePlaneCurveArrangements(QQ) + sage: h = H((x * y, x + y + z)) + sage: h.ncurves() + 2 + sage: len(h) # equivalent + 2 + """ + return len(self._curves) + + __len__ = ncurves + + def curves(self): + r""" + Return the curves in the arrangement as a tuple. + + OUTPUT: + + A tuple. + + EXAMPLES:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: h = H((x * y, x + y + 1)) + sage: h.curves() + (Affine Plane Curve over Rational Field defined by x*y, + Affine Plane Curve over Rational Field defined by x + y + 1) + + Note that the curves can be indexed as if they were a list:: + + sage: h[1] + Affine Plane Curve over Rational Field defined by x + y + 1 + """ + return self._curves + + def _repr_(self): + r""" + String representation for a curve arrangement. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: h = H([x * y, x + y + 1, x^3 - y^5, x^2 * y^2 + x^5 + y^5, (x^2 + y^2)^3 + (x^3 + y^3 - 1)^2]) + sage: h + Arrangement of 5 curves in Affine Space of dimension 2 over Rational Field + sage: H(()) + Arrangement () in Affine Space of dimension 2 over Rational Field + sage: H. = ProjectivePlaneCurveArrangements(QQ) + sage: h = H([x * y, x + y + z, x^3 * z^2 - y^5, x^2 * y^2 * z + x^5 + y^5, (x^2 + y^2)^3 + (x^3 + y^3 - z^3)^2]) + sage: h + Arrangement of 5 curves in Projective Space of dimension 2 over Rational Field + """ + if not self: + return 'Empty curve arrangement in {0}'.format(self.parent().ambient_space()) + elif len(self) < 5: + curves = ', '.join(h.defining_polynomial()._repr_() + for h in self._curves) + return 'Arrangement ({0}) in {1}'.format(curves, + self.parent().ambient_space()) + return 'Arrangement of {0} curves in {1}'.format(len(self), + self.parent().ambient_space()) + + def _richcmp_(self, other, op): + """ + Compare two curve arrangements. + + EXAMPLES:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: H(x) == H(y) + False + sage: H(x) == H(2 * x) + True + + TESTS:: + + sage: H(x) == 0 + False + """ + return richcmp(self._curves, other._curves, op) + + def union(self, other): + r""" + The union of ``self`` with ``other``. + + INPUT: + + - ``other`` -- a curve arrangement or something that can + be converted into a curve arrangement + + OUTPUT: + + A new curve arrangement. + + EXAMPLES:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: h = H([x * y, x + y + 1, x^3 - y^5, x^2 * y^2 + x^5 + y^5, (x^2 + y^2)^3 + (x^3 + y^3 - 1)^2]) + sage: C = Curve(x^8 - y^8 -x^4 * y^4) + sage: h1 = h.union(C); h1 + Arrangement of 6 curves in Affine Space of dimension 2 over Rational Field + sage: h1 == h1.union(C) + True + """ + P = self.parent() + other_h = P(other) + curves0 = self._curves + other_h._curves + curves = [] + for h in curves0: + if h not in curves: + curves.append(h) + result = P(*curves) + return result + + add_curves = union + + __or__ = union + + def deletion(self, curves): + r""" + Return the curve arrangement obtained by removing ``curves``. + + INPUT: + + - ``curves`` -- a curve or curve arrangement + + OUTPUT: + + A new curve arrangement with the given curve(s) + ``h`` removed. + + EXAMPLES:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: h = H([x * y, x + y + 1, x^3 - y^5, x^2 * y^2 + x^5 + y^5, (x^2 + y^2)^3 + (x^3 + y^3 - 1)^2]) + sage: C = h[-1] + sage: h.deletion(C) + Arrangement (x*y, x + y + 1, -y^5 + x^3, x^5 + y^5 + x^2*y^2) + in Affine Space of dimension 2 over Rational Field + sage: h.deletion(x) + Traceback (most recent call last): + ... + ValueError: curve is not in the arrangement + """ + parent = self.parent() + curves = parent(curves) + planes = list(self) + for curve in curves: + try: + planes.remove(curve) + except ValueError: + raise ValueError('curve is not in the arrangement') + return parent(planes) + + def change_ring(self, base_ring): + """ + Return curve arrangement over the new base ring. + + INPUT: + + - ``base_ring`` -- the new base ring; must be a field for + curve arrangements + + OUTPUT: + + The curve arrangement obtained by changing the base + field, as a new curve arrangement. + + EXAMPLES:: + + sage: # needs sage.rings.number_field + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: A = H(y^2 - x^3, x, y, y^2 + x * y + x^2) + sage: K. = CyclotomicField(3) + sage: A.change_ring(K) + Arrangement (-x^3 + y^2, x, y, x^2 + x*y + y^2) in Affine Space of + dimension 2 over Cyclotomic Field of order 3 and degree 2 + """ + parent = self.parent().change_ring(base_ring) + curves = tuple(c.change_ring(base_ring) for c in self) + return parent(curves) + + @cached_method + def coordinate_ring(self): + """ + Return the coordinate ring of ``self``. + + OUTPUT: + + The coordinate ring of the curve arrangement. + + EXAMPLES:: + + sage: L. = AffinePlaneCurveArrangements(QQ) + sage: C = L(x, y) + sage: C.coordinate_ring() + Multivariate Polynomial Ring in x, y over Rational Field + sage: P. = ProjectivePlaneCurveArrangements(QQ) + sage: C = P(x, y) + sage: C.coordinate_ring() + Multivariate Polynomial Ring in x, y, z over Rational Field + """ + return self._curves[0].defining_polynomial().parent() + + def defining_polynomials(self): + r""" + Return the defining polynomials of the elements of ``self``. + + EXAMPLES:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: A = H(y^2 - x^3, x, y, y^2 + x * y + x^2) + sage: A.defining_polynomials() + (-x^3 + y^2, x, y, x^2 + x*y + y^2) + """ + return tuple([h.defining_polynomial() for h in self._curves]) + + def defining_polynomial(self, simplified=True): + r""" + Return the defining polynomial of the union of the curves in ``self``. + + EXAMPLES:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: A = H(y ** 2 + x ** 2, x, y) + sage: prod(A.defining_polynomials()) == A.defining_polynomial() + True + """ + return prod(self.defining_polynomials()) + + def have_common_factors(self): + r""" + Check if the curves have common factors. + + EXAMPLES:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: A = H(x * y, x^2 + x* y^3) + sage: A.have_common_factors() + True + sage: H(x * y, x + y^3).have_common_factors() + False + """ + L = [c.defining_polynomial() for c in self._curves] + C = combinations(L, 2) + return any(f1.gcd(f2).degree() > 0 for f1, f2 in C) + + def reduce(self, clean=False, verbose=False): + r""" + Replace the curves by their reduction. + + INPUT: + + - ``clean`` -- boolean (default: ``False``); if ``False`` + and there are common factors it returns ``None`` and + a warning message. If ``True``, the common factors are kept + only in the first occurance. + + EXAMPLES:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: A = H(y^2, (x + y)^3 * (x^2 + x * y + y^2)) + sage: A.reduce() + Arrangement (y, x^3 + 2*x^2*y + 2*x*y^2 + y^3) in Affine Space + of dimension 2 over Rational Field + sage: C = H(x*y, x*(y + 1)) + sage: C.reduce(verbose=True) + Some curves have common components + sage: C.reduce(clean=True) + Arrangement (x*y, y + 1) in Affine Space of dimension 2 + over Rational Field + sage: C = H(x*y, x) + sage: C.reduce(clean=True) + Arrangement (x*y) in Affine Space of dimension 2 over Rational Field + """ + P = self.parent() + L = [self._curves[0].defining_polynomial().radical()] + for c in self._curves[1:]: + g = c.defining_polynomial().radical() + for f in L: + d = g.gcd(f) + if d.degree() > 0 and not clean: + if verbose: + print("Some curves have common components") + return None + g //= d + if g.degree() > 0: + L.append(g) + return P(*L) + + +class AffinePlaneCurveArrangementElement(PlaneCurveArrangementElement): + """ + An ordered affine plane curve arrangement. + """ + def __init__(self, parent, curves, check=True): + """ + Construct an ordered affine plane curve arrangement. + + INPUT: + + - ``parent`` -- the parent :class:`AffinePlaneCurveArrangements` + + - ``curves`` -- a tuple of curves + + EXAMPLES:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: elt = H(x, y); elt + Arrangement (x, y) in Affine Space of dimension 2 over Rational Field + sage: TestSuite(elt).run() + """ + Element.__init__(self, parent) + self._curves = tuple(curves) + if check: + if not all(isinstance(h, AffinePlaneCurve) for h in curves): + raise ValueError("not all elements are curves") + if not all(h.ambient_space() is self.parent().ambient_space() + for h in curves): + raise ValueError("not all curves are in the same ambient space") + self._braid_monodromy_non_vertical = None + self._braid_monodromy_vertical = None + self._strands_nonvertical = None + self._strands_vertical = None + self._fundamental_group_nonsimpl_nonvertical = None + self._fundamental_group_nonsimpl_vertical = None + self._fundamental_group_simpl_nonvertical = None + self._fundamental_group_simpl_vertical = None + self._meridians_nonsimpl_nonvertical = None + self._meridians_nonsimpl_vertical = None + self._meridians_simpl_nonvertical = None + self._meridians_simpl_vertical = None + self._vertical_lines_in_braid_mon = None + + def fundamental_group(self, simplified=True, vertical=True, + projective=False): + r""" + Return the fundamental group of the complement of the union + of affine plane curves in `\CC^2`. + + INPUT: + + - ``vertical`` -- boolean (default: ``True``); if ``True``, there + are no vertical asymptotes, and there are vertical lines, then a + simplified braid :func:`braid_monodromy` is used + + - ``simplified`` -- boolean (default: ``True``); if it is ``True``, the + group is simplified + + - ``projective`` -- boolean (default: ``False``); to be used in the + method for projective curves + + OUTPUT: + + A finitely presented group. + + .. NOTE:: + + This functionality requires the ``sirocco`` package to be installed. + + EXAMPLES:: + + sage: # needs sirocco + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: A = H(y^2 + x, y + x - 1, x) + sage: A.fundamental_group() + Finitely presented group + < x0, x1, x2 | x2*x0*x2^-1*x0^-1, x1*x0*x1^-1*x0^-1, (x2*x1)^2*(x2^-1*x1^-1)^2 > + sage: A.meridians() + {0: [x1, x2*x1*x2^-1], 1: [x0], 2: [x2], + 3: [x1^-1*x2^-1*x1^-1*x0^-1]} + sage: G = A.fundamental_group(simplified=False) + sage: G.sorted_presentation() + Finitely presented group + < x0, x1, x2, x3 | x3^-1*x2^-1*x3*x0*x1*x0^-1, + x3^-1*x1^-1*x3*x0*x1*x0^-1*x2^-1*x0^-1*(x2*x0)^2*x1^-1*x0^-1, + x3^-1*x0^-1*x3*x0*x1*x0^-1*x2^-1*x0*x2*x0*x1^-1*x0^-1, + x2^-1*x0^-1*x2*x0, x1^-1*x0^-1*x1*x0 > + sage: A.meridians(simplified=False) + {0: [x1, x2], 1: [x0], 2: [x3], 3: [x3^-1*x2^-1*x1^-1*x0^-1]} + sage: A.fundamental_group(vertical=False) + Finitely presented group + < x0, x1, x2 | x2^-1*x1^-1*x2*x1, x1*x0*x1^-1*x0^-1, (x0*x2)^2*(x0^-1*x2^-1)^2 > + sage: A.meridians(vertical=False) + {0: [x2, x0*x2*x0^-1], 1: [x1], 2: [x0], 3: [x0*x2^-1*x0^-1*x2^-1*x1^-1*x0^-1]} + sage: G = A.fundamental_group(simplified=False, vertical=False) + sage: G.sorted_presentation() + Finitely presented group + < x0, x1, x2, x3 | x3^-1*x2^-1*x1^-1*x2*x3*x2^-1*x1*x2, + x3^-1*x2^-1*x1^-1*x2*x3*x2^-1*x1*x2, + (x3^-1*x2^-1*x0^-1*x2)^2*(x3*x2^-1*x0*x2)^2, + x3^-1*x2^-1*x0^-1*x2*x3^-1*x2^-1*x0*x2*x3*x2, + x1^-1*x0^-1*x1*x0 > + sage: A.meridians(simplified=False, vertical=False) + {0: [x2, x3], 1: [x1], 2: [x0], 3: [x3^-1*x2^-1*x1^-1*x0^-1]} + sage: A = H(x * y^2 + x + y, y + x -1, x, y) + sage: G = A.fundamental_group() + sage: G.sorted_presentation() + Finitely presented group + < x0, x1, x2, x3 | x3^-1*x2^-1*x3*x2, x3^-1*x1^-1*x3*x1, + x3^-1*x0^-1*x3*x0, x2^-1*x1^-1*x2*x1, + x2^-1*x0^-1*x2*x0, x1^-1*x0^-1*x1*x0 > + """ + if simplified and vertical: + computed = self._fundamental_group_simpl_vertical + elif simplified and not vertical: + computed = self._fundamental_group_simpl_nonvertical + elif not simplified and vertical: + computed = self._fundamental_group_nonsimpl_vertical + else: + computed = self._fundamental_group_nonsimpl_nonvertical + if computed: + return computed + K = self.base_ring() + R = self.coordinate_ring() + if not K.is_subring(QQbar): + raise TypeError('the base field is not in QQbar') + C = self.reduce() + L = C.defining_polynomials() + if vertical: + bm = self._braid_monodromy_vertical + else: + bm = self._braid_monodromy_non_vertical + if bm is not None: # bm could be [] + if not vertical: + st = self._strands_nonvertical + d1 = prod(L).degree() + bd = (bm, st, dict(), d1) + else: + st = self._strands_vertical + d1 = prod(L).degree(R.gen(1)) + bd = (bm, st, self._vertical_lines_in_braid_mon, d1) + else: + bd = None + G, dic = fundamental_group_arrangement(L, simplified=simplified, + puiseux=True, + projective=projective, + vertical=vertical, + braid_data=bd) + if simplified and vertical: + self._fundamental_group_simpl_vertical = G + self._meridians_simpl_vertical = dic + elif simplified and not vertical: + self._fundamental_group_simpl_nonvertical = G + self._meridians_simpl_nonvertical = dic + elif not simplified and vertical: + self._fundamental_group_nonsimpl_vertical = G + self._meridians_nonsimpl_vertical = dic + else: + self._fundamental_group_nonsimpl_nonvertical = G + self._meridians_nonsimpl_nonvertical = dic + return G + + def meridians(self, simplified=True, vertical=True): + r""" + Return the meridians of each irreducible component. + + OUTPUT: + + A dictionary which associates the index of each curve with its meridians, + including the line at infinity if it can be omputed + + .. NOTE:: + + This functionality requires the ``sirocco`` package to be installed + and :meth:`AffinePlaneCurveArrangements.fundamental_group` with the same options, + where some examples are shown. + + EXAMPLES:: + + sage: # needs sirocco + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: A = H(x-1, y, x, y - 1) + sage: A.fundamental_group() + Finitely presented group + < x0, x1, x2, x3 | x2*x0*x2^-1*x0^-1, x2*x1*x2^-1*x1^-1, + x3*x0*x3^-1*x0^-1, x3*x1*x3^-1*x1^-1 > + sage: A.meridians() + {0: [x2], 1: [x0], 2: [x3], 3: [x1], 4: [x3^-1*x2^-1*x1^-1*x0^-1]} + """ + if simplified and vertical: + computed = self._meridians_simpl_vertical + elif simplified and not vertical: + computed = self._meridians_simpl_nonvertical + elif not simplified and vertical: + computed = self._meridians_nonsimpl_vertical + else: + computed = self._meridians_nonsimpl_nonvertical + if computed: + return dict(computed) + self.fundamental_group(simplified=simplified, vertical=vertical) + if simplified and vertical: + return dict(self._meridians_simpl_vertical) + elif simplified and not vertical: + return dict(self._meridians_group_simpl_nonvertical) + elif not simplified and vertical: + return dict(self._meridians_nonsimpl_vertical) + else: + return dict(self._meridians_nonsimpl_nonvertical) + + def braid_monodromy(self, vertical=True): + r""" + Return the braid monodromy of the complement of the union + of affine plane curves in `\CC^2`. If there are vertical + asymptotes a change of variable is done. + + INPUT: + + - ``vertical`` -- boolean (default: ``True``); if it is ``True``, there + are no vertical asymptotes, and there are vertical lines, then a + simplified :func:`braid_monodromy` is computed. + + OUTPUT: + + A braid monodromy with dictionnaries identifying strands with components + and braids with vertical lines. + + .. NOTE:: + + This functionality requires the ``sirocco`` package to be installed. + + EXAMPLES:: + + sage: # needs sirocco + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: A = H(y^2 + x, y + x - 1, x) + sage: A.braid_monodromy(vertical=False) + [s1*s0*(s1*s2*s1)^2*s2*(s1^-1*s2^-1)^2*s1^-1*s0^-1*s1^-1, + s1*s0*(s1*s2)^2*s2*s1^-1*s2^-1*s1^-1*s0^-1*s1^-1, + s1*s0*s1*s2*(s1*s2^-1)^2*s0*s1*s2*s1*s0*s2^-1*s1^-3*s2*s1^-1*s2^-1*s1^-1*s0^-1*s1^-1, + s1*s0*s1*s2*s1*s2^-1*s1^4*s2*s1^-1*s2^-1*s1^-1*s0^-1*s1^-1, + s1*s0*s1*s2*s1*s2^-1*s1^-1*s2*s0^-1*s1^-1] + sage: A.braid_monodromy(vertical=True) + [s1*s0*s1*s0^-1*s1^-1*s0, s0^-1*s1*s0*s1^-1*s0, s0^-1*s1^2*s0] + """ + if vertical: + computed = self._braid_monodromy_vertical + else: + computed = self._braid_monodromy_non_vertical + if computed is not None: + return computed + K = self.base_ring() + if not K.is_subring(QQbar): + raise TypeError('the base field is not in QQbar') + L = self.defining_polynomials() + bm, dic, dv, d1 = braid_monodromy(prod(L), arrangement=L, + vertical=vertical) + if vertical: + self._braid_monodromy_vertical = bm + self._strands_vertical = dic + self._vertical_lines_in_braid_mon = dv + else: + self._braid_monodromy_non_vertical = bm + self._strands_nonvertical = dic + return bm + + def strands(self): + r""" + Return the strands for each member of the arrangement. + + OUTPUT: + + A dictionary which associates to the index of each strand + its associated component if the braid monodromy has been + calculated with ``vertical=False``. + + .. NOTE:: + + This functionality requires the ``sirocco`` package to be installed. + + EXAMPLES:: + + sage: # needs sirocco + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: A = H(y^2 + x, y + x - 1, x) + sage: bm = A.braid_monodromy() + sage: A.strands() + {0: 2, 1: 1, 2: 0, 3: 0} + """ + if not self._strands_nonvertical: + self._braid_monodromy = self.braid_monodromy(vertical=False) + return self._strands_nonvertical + + def vertical_strands(self): + r""" + Return the strands if the braid monodromy has been computed with + the vertical option. + + OUTPUT: + + A dictionary which associates to the index of each strand + its associated component if the braid monodromy has been + calculated with ``vertical=True``. + + .. NOTE:: + + This functionality requires the ``sirocco`` package to be installed. + + EXAMPLES:: + + sage: # needs sirocco + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: A = H(y^2 + x, y + x - 1, x) + sage: A.vertical_strands() + {0: 1, 1: 0, 2: 0} + sage: A.braid_monodromy(vertical=True) + [s1*s0*s1*s0^-1*s1^-1*s0, s0^-1*s1*s0*s1^-1*s0, s0^-1*s1^2*s0] + """ + if not self._strands_vertical: + self.braid_monodromy(vertical=True) + return self._strands_vertical + + def vertical_lines_in_braid_monodromy(self): + r""" + Return the vertical lines in the arrangement. + + OUTPUT: + + A dictionary which associates the index of a braid + to the index of the vertical line associated to the braid. + + .. NOTE:: + + This functionality requires the ``sirocco`` package to be installed. + + EXAMPLES:: + + sage: # needs sirocco + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: A = H(y^2 + x, y + x - 1, x) + sage: A.vertical_lines_in_braid_monodromy() + {1: 2} + sage: A.braid_monodromy(vertical=True) + [s1*s0*s1*s0^-1*s1^-1*s0, s0^-1*s1*s0*s1^-1*s0, s0^-1*s1^2*s0] + """ + if not self._vertical_lines_in_braid_mon: + self.braid_monodromy(vertical=True) + return self._vertical_lines_in_braid_mon + + +class ProjectivePlaneCurveArrangementElement(PlaneCurveArrangementElement): + """ + An ordered projective plane curve arrangement. + """ + def __init__(self, parent, curves, check=True): + """ + Construct an ordered projective plane curve arrangement. + + INPUT: + + - ``parent`` -- the parent :class:`ProjectivePlaneCurveArrangements` + + - ``curves`` -- a tuple of curves + + EXAMPLES:: + + sage: H. = ProjectivePlaneCurveArrangements(QQ) + sage: elt = H(x, y, z); elt + Arrangement (x, y, z) in Projective Space of dimension 2 over Rational Field + sage: TestSuite(elt).run() + """ + Element.__init__(self, parent) + self._curves = tuple(curves) + if check: + if not all(isinstance(h, ProjectivePlaneCurve) for h in curves): + raise ValueError("not all elements are curves") + if not all(h.ambient_space() is self.parent().ambient_space() + for h in curves): + raise ValueError("not all curves are in the same ambient space") + self._fundamental_group_nonsimpl = None + self._fundamental_group_simpl = None + self._meridians_nonsimpl = None + self._meridians_simpl = None + + def fundamental_group(self, simplified=True): + r""" + Return the fundamental group of the complement of the union + of an arragnement of projective plane curves + in the projective plane. + + INPUT: + + - ``simplified`` -- boolean (default: True); set if the group + is simplified + + OUTPUT: + + A finitely presented group. + + .. NOTE:: + + This functionality requires the ``sirocco`` package to be installed. + + EXAMPLES:: + + sage: # needs sirocco + sage: H. = ProjectivePlaneCurveArrangements(QQ) + sage: H(z).fundamental_group() + Finitely presented group < | > + sage: H(x*y).fundamental_group() + Finitely presented group < x | > + sage: A = H(y^2 + x*z, y + x - z, x) + sage: A.fundamental_group().sorted_presentation() + Finitely presented group < x0, x1 | x1^-1*x0^-1*x1*x0 > + sage: A.meridians() + {0: [x1], 1: [x0], 2: [x1^-1*x0^-1*x1^-1]} + sage: G = A.fundamental_group(simplified=False) + sage: G.sorted_presentation() + Finitely presented group + < x0, x1, x2, x3 | x3^-1*x2^-1*x1^-1*x0^-1, x3^-1*x2^-1*x3*x0*x1*x0^-1, + x3^-1*x1^-1*x3*x0*x1*x0^-1*x2^-1*x0^-1*(x2*x0)^2*x1^-1*x0^-1, + x3^-1*x0^-1*x3*x0*x1*x0^-1*x2^-1*x0*x2*x0*x1^-1*x0^-1, + x2^-1*x0^-1*x2*x0, x1^-1*x0^-1*x1*x0 > + sage: A.meridians(simplified=False) + {0: [x1, x2], 1: [x0], 2: [x3]} + sage: A = H(y^2 + x*z, z, x) + sage: A.fundamental_group() + Finitely presented group < x0, x1 | (x1*x0)^2*(x1^-1*x0^-1)^2 > + sage: A = H(y^2 + x*z, z*x, y) + sage: A.fundamental_group() + Finitely presented group + < x0, x1, x2 | x2*x0*x1*x0^-1*x2^-1*x1^-1, + x1*(x2*x0)^2*x2^-1*x1^-1*x0^-1*x2^-1*x0^-1 > + """ + if simplified: + computed = self._fundamental_group_simpl + else: + computed = self._fundamental_group_nonsimpl + if computed: + return computed + H = self.parent() + K = self.base_ring() + R = self.coordinate_ring() + x, y, z = R.gens() + if not K.is_subring(QQbar): + raise TypeError('the base field is not in QQbar') + C = self.reduce() + n = len(C) + infinity = Curve(z) + infinity_in_C = infinity in C + if infinity_in_C and n == 1: + G = FreeGroup(0) / [] + if simplified: + self._fundamental_group_simpl = G + self._meridians_simpl = {0: [G.one()]} + else: + self._fundamental_group_nonsimpl = G + self._meridians_nonsimpl = {0: [G.one()]} + return G + if infinity_in_C: + j = C.curves().index(infinity) + C = H(C.curves()[:j] + C.curves()[j + 1:]) + infinity_divides = False + for j, c in enumerate(C): + g = c.defining_polynomial() + infinity_divides = z.divides(g) + if infinity_divides: + h = R(g / z) + C = H(C.curves()[:j] + (h, ) + C.curves()[j + 1:]) + break + affine = AffinePlaneCurveArrangements(K, names=('u', 'v')) + u, v = affine.gens() + affines = [f.defining_polynomial().subs({x: u, y: v, z: 1}) for f in C] + changes = any(g.degree(v) < g.degree() > 1 for g in affines) + while changes: + affines = [f.subs({u: u + v}) for f in affines] + changes = any(g.degree(v) < g.degree() > 1 for g in affines) + C_affine = affine(affines) + proj = not (infinity_divides or infinity_in_C) + G = C_affine.fundamental_group(simplified=simplified, vertical=True, + projective=proj) + dic = C_affine.meridians(simplified=simplified, vertical=True) + if infinity_in_C: + dic1 = dict() + for k in range(j): + dic1[k] = dic[k] + dic1[j] = dic[n - 1] + for k in range(j + 1, n): + dic1[k] = dic[k - 1] + elif infinity_divides: + dic1 = {k: dic[k] for k in range(n)} + dic1[j] += dic[n] + else: + dic1 = dic + if simplified: + self._fundamental_group_simpl = G + self._meridians_simpl = dic1 + else: + self._fundamental_group_nonsimpl = G + self._meridians_nonsimpl = dic1 + return G + + def meridians(self, simplified=True): + r""" + Return the meridians of each irreducible component. + + OUTPUT: + + A dictionary which associates the index of each curve with + its meridians, including the line at infinity if it can be + computed + + .. NOTE:: + + This function requires the ``sirocco`` package to be installed and + :func:`ProjectivePlaneCurveArrangements.fundamental_group` + with the same options, where some examples are shown. + + EXAMPLES:: + + sage: # needs sirocco + sage: H. = ProjectivePlaneCurveArrangements(QQ) + sage: A = H(y^2 + x*z, y + x - z, x) + sage: A.fundamental_group().sorted_presentation() + Finitely presented group < x0, x1 | x1^-1*x0^-1*x1*x0 > + sage: A.meridians() + {0: [x1], 1: [x0], 2: [x1^-1*x0^-1*x1^-1]} + sage: A = H(y^2 + x*z, z, x) + sage: A.fundamental_group() + Finitely presented group < x0, x1 | (x1*x0)^2*(x1^-1*x0^-1)^2 > + sage: A.meridians() + {0: [x0, x1*x0*x1^-1], 1: [x0^-1*x1^-1*x0^-1], 2: [x1]} + sage: A = H(y^2 + x*z, z*x, y) + sage: A.fundamental_group() + Finitely presented group < x0, x1, x2 | x2*x0*x1*x0^-1*x2^-1*x1^-1, + x1*(x2*x0)^2*x2^-1*x1^-1*x0^-1*x2^-1*x0^-1 > + sage: A.meridians() + {0: [x0, x2*x0*x2^-1], 1: [x2, x0^-1*x2^-1*x1^-1*x0^-1], 2: [x1]} + """ + if simplified: + computed = self._meridians_simpl + else: + computed = self._meridians_nonsimpl + if computed: + return dict(computed) + self._fundamental_group(simplified=simplified) + if simplified: + return dict(self._meridians_simpl) + else: + return dict(self._meridians_nonsimpl) + + +class PlaneCurveArrangements(UniqueRepresentation, Parent): + """ + Plane curve arrangements. + + INPUT: + + - ``base_ring`` -- ring; the base ring + + - ``names`` -- tuple of strings; the variable names + + EXAMPLES:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: H(x, y^2, x-1, y-1) + Arrangement (x, y^2, x - 1, y - 1) in Affine Space + of dimension 2 over Rational Field + """ + Element = PlaneCurveArrangementElement + + @staticmethod + def __classcall__(cls, base, names=()): + """ + Normalize the inputs to ensure a unique representation. + + TESTS:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: K = AffinePlaneCurveArrangements(QQ, names=('x', 'y')) + sage: H is K + True + """ + names = normalize_names(len(names), names) + return super().__classcall__(cls, base, names) + + def __init__(self, base_ring, names=tuple()): + """ + Initialize ``self``. + + TESTS:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: TestSuite(H).run() + """ + if base_ring not in _Fields: + raise ValueError('base ring must be a field') + super().__init__(base_ring, names=names, category=Sets()) + self._embedded = None + if len(names) == 2: + self._embedded = 'affine' + elif len(names) == 3: + self._embedded = 'projective' + + def coordinate_ring(self): + """ + Return the coordinate ring. + + OUTPUT: + + The coordinate ring of the curve arrangement. + + EXAMPLES:: + + sage: L. = AffinePlaneCurveArrangements(QQ) + sage: L.coordinate_ring() + Multivariate Polynomial Ring in x, y over Rational Field + """ + return PolynomialRing(self.base_ring(), self.variable_names()) + + def change_ring(self, base_ring): + """ + Return curve arrangements over a different base ring. + + INPUT: + + - ``base_ring`` -- a ring; the new base ring. + + OUTPUT: + + A new :class:`PlaneCurveArrangements` instance over the new + base ring + + EXAMPLES:: + + sage: L. = AffinePlaneCurveArrangements(QQ) + sage: L.change_ring(RR).base_ring() + Real Field with 53 bits of precision + + TESTS:: + + sage: L.change_ring(QQ) is L + True + """ + return self.__reduce__()[1][0](base_ring, names=self.variable_names()) + + @abstract_method + def ambient_space(self): + """ + Return the ambient space. + + EXAMPLES:: + + sage: L. = PlaneCurveArrangements(QQ) + Traceback (most recent call last): + ... + NotImplementedError: + sage: L. = AffinePlaneCurveArrangements(QQ) + sage: L.ambient_space() + Affine Space of dimension 2 over Rational Field + sage: L. = ProjectivePlaneCurveArrangements(QQ) + sage: L.ambient_space() + Projective Space of dimension 2 over Rational Field + """ + + def _repr_(self): + """ + Return a string representation. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: L. = AffinePlaneCurveArrangements(QQ); L + Curve arrangements in Affine Space of dimension 2 over Rational Field + """ + return 'Curve arrangements in {0}'.format(self.ambient_space()) + + def _element_constructor_(self, *args, **kwds): + """ + Construct an element of ``self``. + + INPUT: + + - ``*args`` -- positional arguments, each defining a curve + + EXAMPLES:: + + sage: L. = AffinePlaneCurveArrangements(QQ) + sage: A = L(x, y); A + Arrangement (x, y) in Affine Space of dimension 2 over Rational Field + sage: L([x, y]) == A + True + sage: L(Curve(x), Curve(y)) == A + True + sage: L(y, x) == A + False + """ + if len(args) == 1: + if not isinstance(args[0], (tuple, list)): + arg = (args[0], ) + else: + arg = tuple(args[0]) + else: + arg = tuple(args) + ambient_space = self.ambient_space() + R = ambient_space.coordinate_ring() + curves = () + for h in arg: + try: + ambient = h.ambient_space() + if ambient == ambient_space: + curves += (h, ) + else: + raise TypeError('the curves do not have the same ambient space') + except AttributeError: + try: + h = R(h) + curves += (Curve(h), ) + except TypeError: + raise TypeError('elements are not curves') + return self.element_class(self, curves) + + def _an_element_(self): + """ + Return an element of ``self``. + + TESTS:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: H._an_element_() + Arrangement (t) in Affine Space of dimension 2 over Rational Field + sage: H. = ProjectivePlaneCurveArrangements(QQ) + sage: H._an_element_() + Arrangement (t) in Projective Space of dimension 2 over Rational Field + """ + return self(self.gen(0)) + + @cached_method + def ngens(self): + """ + Return the number of variables, i.e. 2 or 3, kept for completness. + + OUTPUT: + + An integer, 2 or 3, depending if the arrangement is projective or affine. + + EXAMPLES:: + + sage: L. = AffinePlaneCurveArrangements(QQ) + sage: L.ngens() + 2 + sage: L. = ProjectivePlaneCurveArrangements(QQ) + sage: L.ngens() + 3 + """ + return len(self.variable_names()) + + def gens(self): + """ + Return the coordinates. + + OUTPUT: + + A tuple of linear expressions, one for each linear variable. + + EXAMPLES:: + + sage: L = AffinePlaneCurveArrangements(QQ, ('x', 'y')) + sage: L.gens() + (x, y) + sage: L = ProjectivePlaneCurveArrangements(QQ, ('x', 'y', 'z')) + sage: L.gens() + (x, y, z) + """ + return self.ambient_space().gens() + + def gen(self, i): + """ + Return the `i`-th coordinate. + + INPUT: + + - ``i`` -- integer + + OUTPUT: + + A variable. + + EXAMPLES:: + + sage: L. = AffinePlaneCurveArrangements(QQ) + sage: L.gen(1) + y + sage: L. = ProjectivePlaneCurveArrangements(QQ) + sage: L.gen(2) + z + """ + return self.gens()[i] + + +class AffinePlaneCurveArrangements(PlaneCurveArrangements): + """ + Affine curve arrangements. + + INPUT: + + - ``base_ring`` -- ring; the base ring + + - ``names`` -- tuple of strings; the variable names + + EXAMPLES:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: H(x, y^2, x-1, y-1) + Arrangement (x, y^2, x - 1, y - 1) in Affine Space + of dimension 2 over Rational Field + """ + Element = AffinePlaneCurveArrangementElement + + def ambient_space(self): + """ + Return the ambient space. + + EXAMPLES:: + + sage: L. = AffinePlaneCurveArrangements(QQ) + sage: L.ambient_space() + Affine Space of dimension 2 over Rational Field + """ + return AffineSpace(self.base_ring(), 2, self._names) + + +class ProjectivePlaneCurveArrangements(PlaneCurveArrangements): + """ + Projective curve arrangements. + + INPUT: + + - ``base_ring`` -- ring; the base ring + + - ``names`` -- tuple of strings; the variable names + + EXAMPLES:: + + sage: H. = ProjectivePlaneCurveArrangements(QQ) + sage: H(x, y^2, x-z, y-z) + Arrangement (x, y^2, x - z, y - z) in Projective Space + of dimension 2 over Rational Field + """ + Element = ProjectivePlaneCurveArrangementElement + + def ambient_space(self): + """ + Return the ambient space. + + EXAMPLES:: + + sage: L. = ProjectivePlaneCurveArrangements(QQ) + sage: L.ambient_space() + Projective Space of dimension 2 over Rational Field + """ + return ProjectiveSpace(self.base_ring(), 2, self._names) diff --git a/src/sage/schemes/curves/projective_curve.py b/src/sage/schemes/curves/projective_curve.py index bb19979b3c4..1cc7fce0906 100644 --- a/src/sage/schemes/curves/projective_curve.py +++ b/src/sage/schemes/curves/projective_curve.py @@ -487,12 +487,12 @@ def projection(self, P=None, PS=None): # defining polynomials of this curve with the polynomials defining the inverse of the change of coordinates invcoords = [Q[i]*PP.gens()[j] + PP.gens()[i] for i in range(n + 1)] invcoords[j] = Q[j]*PP.gens()[j] - I = PP.coordinate_ring().ideal([f(invcoords) for f in self.defining_polynomials()]) - J = I.elimination_ideal(PP.gens()[j]) + id = PP.coordinate_ring().ideal([f(invcoords) for f in self.defining_polynomials()]) + J = id.elimination_ideal(PP.gens()[j]) K = Hom(PP.coordinate_ring(), PP2.coordinate_ring()) - l = list(PP2.gens()) - l.insert(j, 0) - phi = K(l) + ll = list(PP2.gens()) + ll.insert(j, 0) + phi = K(ll) G = [phi(f) for f in J.gens()] C = PP2.curve(G) return (psi, C) @@ -781,32 +781,35 @@ def plot(self, *args, **kwds): The other affine patches of the same curve:: - sage: C.plot(patch=0) # needs sage.plot + sage: # needs sage.plot + sage: C.plot(patch=0) Graphics object consisting of 1 graphics primitive - sage: C.plot(patch=1) # needs sage.plot + sage: C.plot(patch=1) Graphics object consisting of 1 graphics primitive An elliptic curve:: + sage: # needs sage.plot sage: E = EllipticCurve('101a') sage: C = Curve(E) - sage: C.plot() # needs sage.plot + sage: C.plot() Graphics object consisting of 1 graphics primitive - sage: C.plot(patch=0) # needs sage.plot + sage: C.plot(patch=0) Graphics object consisting of 1 graphics primitive - sage: C.plot(patch=1) # needs sage.plot + sage: C.plot(patch=1) Graphics object consisting of 1 graphics primitive A hyperelliptic curve:: + sage: # needs sage.plot sage: P. = QQ[] sage: f = 4*x^5 - 30*x^3 + 45*x - 22 sage: C = HyperellipticCurve(f) - sage: C.plot() # needs sage.plot + sage: C.plot() Graphics object consisting of 1 graphics primitive - sage: C.plot(patch=0) # needs sage.plot + sage: C.plot(patch=0) Graphics object consisting of 1 graphics primitive - sage: C.plot(patch=1) # needs sage.plot + sage: C.plot(patch=1) Graphics object consisting of 1 graphics primitive """ # if user has not specified a favorite affine patch, take the @@ -862,16 +865,17 @@ def is_singular(self, P=None): Over `\CC`:: + sage: # needs sage.rings.function_field sage: F = CC sage: P2. = ProjectiveSpace(F, 2) sage: C = Curve(X) - sage: C.is_singular() # needs sage.rings.function_field + sage: C.is_singular() False sage: D = Curve(Y^2*Z - X^3) - sage: D.is_singular() # needs sage.rings.function_field + sage: D.is_singular() True sage: E = Curve(Y^2*Z - X^3 + Z^3) - sage: E.is_singular() # needs sage.rings.function_field + sage: E.is_singular() False Showing that :issue:`12187` is fixed:: @@ -883,10 +887,11 @@ def is_singular(self, P=None): :: + sage: # needs sage.fings.function_field sage: P. = ProjectiveSpace(CC, 2) sage: C = Curve([y^4 - x^3*z], P) sage: Q = P([0,0,1]) - sage: C.is_singular() # needs sage.rings.function_field + sage: C.is_singular() True """ if P is None: @@ -1277,11 +1282,11 @@ def excellent_position(self, Q): if j == 0: div_pow = min(e[1] for e in npoly.exponents()) npoly = PP.coordinate_ring()({(v0, v1 - div_pow, v2): g - for (v0, v1, v2), g in npoly.dict().items()}) + for (v0, v1, v2), g in npoly.dict().items()}) else: div_pow = min(e[0] for e in npoly.exponents()) npoly = PP.coordinate_ring()({(v0 - div_pow, v1, v2): g - for (v0, v1, v2), g in npoly.dict().items()}) + for (v0, v1, v2), g in npoly.dict().items()}) # check the degree again if npoly.degree() != d - r: need_continue = True @@ -1660,9 +1665,9 @@ def is_complete_intersection(self): False """ singular.lib("sing.lib") - I = singular.simplify(self.defining_ideal(), 10) - L = singular.is_ci(I).sage() - return len(self.ambient_space().gens()) - len(I.sage().gens()) == L[-1] + id = singular.simplify(self.defining_ideal(), 10) + L = singular.is_ci(id).sage() + return len(self.ambient_space().gens()) - len(id.sage().gens()) == L[-1] def tangent_line(self, p): """ @@ -1742,12 +1747,18 @@ def fundamental_group(self): The curve must be defined over the rationals or a number field with an embedding over `\QQbar`. + .. NOTE:: + + This functionality requires the ``sirocco`` package to be installed. + EXAMPLES:: sage: P. = ProjectiveSpace(QQ, 2) sage: C = P.curve(x^2*z - y^3) - sage: C.fundamental_group() # optional - sirocco + sage: C.fundamental_group() # needs sirocco Finitely presented group < x0 | x0^3 > + sage: P.curve(z*(x^2*z - y^3)).fundamental_group() # needs sirocco + Finitely presented group < x0, x1 | x1*x0*x1*x0^-1*x1^-1*x0^-1 > In the case of number fields, they need to have an embedding into the algebraic field:: @@ -1761,13 +1772,9 @@ def fundamental_group(self): sage: F.inject_variables() Defining a sage: C = P.curve(x^2 + a * y^2) - sage: C.fundamental_group() # optional - sirocco + sage: C.fundamental_group() # needs sirocco Finitely presented group < x0 | > - .. WARNING:: - - This functionality requires the ``sirocco`` package to be installed. - TESTS:: sage: F. = FreeGroup() @@ -1778,11 +1785,13 @@ def fundamental_group(self): sage: C = P.curve(z^2*y^3 - z*(33*x*z+2*x^2+8*z^2)*y^2 ....: + (21*z^2+21*x*z-x^2)*(z^2+11*x*z-x^2)*y ....: + (x-18*z)*(z^2+11*x*z-x^2)^2) - sage: G0 = C.fundamental_group() # optional - sirocco - sage: G.is_isomorphic(G0) # optional - sirocco + sage: G0 = C.fundamental_group() # needs sirocco + sage: G.is_isomorphic(G0) # needs sirocco #I Forcing finiteness test True - + sage: C = P.curve(z) + sage: C.fundamental_group() # needs sirocco + Finitely presented group < | > """ from sage.schemes.curves.zariski_vankampen import fundamental_group F = self.base_ring() @@ -1790,7 +1799,11 @@ def fundamental_group(self): if QQbar.coerce_map_from(F) is None: raise NotImplementedError("the base field must have an embedding" " to the algebraic field") - f = self.affine_patch(2).defining_polynomial() + g = self.defining_polynomial() + ring = self.ambient_space().affine_patch(2).coordinate_ring() + if g.degree() == 1: + return fundamental_group(ring.one()) + f = ring(self.affine_patch(2).defining_polynomial()) if f.degree() == self.degree(): return fundamental_group(f, projective=True) else: # in this case, the line at infinity is part of the curve, so the complement lies in the affine patch @@ -2724,6 +2737,46 @@ def places_on(self, point): places.append(p) return places + def jacobian(self, model, base_div=None): + """ + Return the Jacobian of this curve. + + INPUT: + + - ``model`` -- model to use for arithmetic + + - ``base_div`` -- an effective divisor for the model + + The degree of the base divisor should satisfy certain degree condition + corresponding to the model used. The following table lists these + conditions. Let `g` be the geometric genus of the curve. + + - ``hess``: ideal-based arithmetic; requires base divisor of degree `g` + + - ``km_large``: Khuri-Makdisi's large model; requires base divisor of + degree at least `2g + 1` + + - ``km_medium``: Khuri-Makdisi's medium model; requires base divisor of + degree at least `2g + 1` + + - ``km_small``: Khuri-Makdisi's small model requires base divisor of + degree at least `g + 1` + + We assume the curve (or its function field) has a rational place. If a + base divisor is not given, one is chosen using a rational place. + + EXAMPLES:: + + sage: A. = AffineSpace(GF(5), 2) + sage: C = Curve(y^2*(x^3 - 1) - (x^3 - 2)).projective_closure() + sage: J = C.jacobian(model='hess'); J + Jacobian of Projective Plane Curve over Finite Field of size 5 + defined by 2*x0^5 - x0^2*x1^3 - x0^3*x2^2 + x1^3*x2^2 (Hess model) + sage: J.base_divisor().degree() == C.genus() + True + """ + return self.function_field().jacobian(model, base_div, curve=self) + class IntegralProjectiveCurve_finite_field(IntegralProjectiveCurve): """ diff --git a/src/sage/schemes/curves/zariski_vankampen.py b/src/sage/schemes/curves/zariski_vankampen.py index 0fc01b81347..c6b5ca66017 100644 --- a/src/sage/schemes/curves/zariski_vankampen.py +++ b/src/sage/schemes/curves/zariski_vankampen.py @@ -23,12 +23,12 @@ EXAMPLES:: - sage: # optional - sirocco + sage: # needs sirocco sage: from sage.schemes.curves.zariski_vankampen import fundamental_group, braid_monodromy sage: R. = QQ[] sage: f = y^3 + x^3 - 1 sage: braid_monodromy(f) - ([s1*s0, s1*s0, s1*s0], {0: 0, 1: 0, 2: 0}) + ([s1*s0, s1*s0, s1*s0], {0: 0, 1: 0, 2: 0}, {}, 3) sage: fundamental_group(f) Finitely presented group < x0 | > """ @@ -44,7 +44,7 @@ import itertools from copy import copy -from sage.combinat.combination import Combinations +from itertools import combinations from sage.combinat.permutation import Permutation from sage.functions.generalized import sign from sage.geometry.voronoi_diagram import VoronoiDiagram @@ -67,7 +67,7 @@ from sage.rings.qqbar import QQbar from sage.rings.rational_field import QQ from sage.rings.real_mpfr import RealField -# from sage.sets.set import Set +from .constructor import Curve roots_interval_cache = {} @@ -88,7 +88,7 @@ def braid_from_piecewise(strands): EXAMPLES:: - sage: # optional - sirocco + sage: # needs sirocco sage: from sage.schemes.curves.zariski_vankampen import braid_from_piecewise sage: paths = [[(0, 0, 1), (0.2, -1, -0.5), (0.8, -1, 0), (1, 0, -1)], ....: [(0, -1, 0), (0.5, 0, -1), (1, 1, 0)], @@ -109,8 +109,8 @@ def braid_from_piecewise(strands): yauxi = val[indices[j]][2] aaux = val[indices[j] - 1][0] baux = val[indices[j]][0] - interpolar = xauxr + (yauxr - xauxr) * (i - aaux) / (baux - aaux) - interpolai = xauxi + (yauxi - xauxi) * (i - aaux) / (baux - aaux) + interpolar = xauxr + (yauxr - xauxr)*(i - aaux) / (baux - aaux) + interpolai = xauxi + (yauxi - xauxi)*(i - aaux) / (baux - aaux) totalpoints[j].append([interpolar, interpolai]) else: totalpoints[j].append([val[indices[j]][1], @@ -129,6 +129,7 @@ def sgn(x, y): if x > y: return -1 return 0 + for i in range(len(totalpoints[0]) - 1): l1 = [totalpoints[j][i] for j in range(len(L))] l2 = [totalpoints[j][i + 1] for j in range(len(L))] @@ -177,7 +178,7 @@ def discrim(pols) -> tuple: INPUT: - ``pols`` -- a list or tuple of polynomials in two variables with - coefficients in a number field with a fixed embedding in `\QQbar` + coefficients in a number field with a fixed embedding in `\QQbar`. OUTPUT: @@ -188,13 +189,13 @@ def discrim(pols) -> tuple: sage: from sage.schemes.curves.zariski_vankampen import discrim sage: R. = QQ[] sage: flist = (y^3 + x^3 - 1, 2 * x + y) - sage: discrim(flist) - (1, - -0.500000000000000? - 0.866025403784439?*I, - -0.500000000000000? + 0.866025403784439?*I, - -0.522757958574711?, - 0.2613789792873551? - 0.4527216721561923?*I, - 0.2613789792873551? + 0.4527216721561923?*I) + sage: sorted((discrim(flist))) + [-0.522757958574711?, + -0.500000000000000? - 0.866025403784439?*I, + -0.500000000000000? + 0.866025403784439?*I, + 0.2613789792873551? - 0.4527216721561923?*I, + 0.2613789792873551? + 0.4527216721561923?*I, + 1] """ x, y = pols[0].parent().gens() field = pols[0].base_ring() @@ -206,7 +207,8 @@ def discrim_pairs(f, g): return pol_ring(f.discriminant(y)) return pol_ring(f.resultant(g, y)) - pairs = [(f, None) for f in pols] + [tuple(t) for t in Combinations(pols, 2)] + pairs = [(f, None) for f in pols] + [tuple(t) for t + in combinations(pols, 2)] fdiscrim = discrim_pairs(pairs) rts = () poly = 1 @@ -226,7 +228,7 @@ def corrected_voronoi_diagram(points): INPUT: - - ``points`` -- a list of complex numbers + - ``points`` -- a tuple of complex numbers OUTPUT: @@ -266,7 +268,8 @@ def corrected_voronoi_diagram(points): V = VoronoiDiagram(configuration) valid = True for r in V.regions().items(): - if not r[1].rays() and not r[1].interior_contains(apprpoints[r[0].affine()]): + if (not r[1].rays() and + not r[1].interior_contains(apprpoints[r[0].affine()])): prec += 53 valid = False break @@ -284,12 +287,12 @@ def orient_circuit(circuit, convex=False, precision=53, verbose=False): - ``circuit`` -- a circuit in the graph of a Voronoi Diagram, given by a list of edges - - ``convex`` -- boolean (default: `False`), if set to ``True`` a simpler + - ``convex`` -- boolean (default: ``False``); if set to ``True`` a simpler computation is made - ``precision`` -- bits of precision (default: 53) - - ``verbose`` -- boolean (default: ``False``) for testing purposes + - ``verbose`` -- boolean (default: ``False``); for testing purposes OUTPUT: @@ -353,7 +356,6 @@ def orient_circuit(circuit, convex=False, precision=53, verbose=False): # return circuit return circuit_vertex elif pr < 0: - # return list(reversed([(c[1], c[0]) + c[2:] for c in circuit])) return tuple(reversed(circuit_vertex)) prec = precision while True: @@ -361,17 +363,15 @@ def orient_circuit(circuit, convex=False, precision=53, verbose=False): totalangle = sum((CIF(*vectors[i]) / CIF(*vectors[i - 1])).argument() for i in range(len(vectors))) if totalangle < 0: - # return list(reversed([(c[1], c[0]) + c[2:] for c in circuit])) return tuple(reversed(circuit_vertex)) if totalangle > 0: - # return circuit return circuit_vertex prec *= 2 if verbose: print(prec) -def voronoi_cells(V): +def voronoi_cells(V, vertical_lines=frozenset()): r""" Compute the graph, the boundary graph, a base point, a positive orientation of the boundary graph, and the dual graph of a corrected Voronoi diagram. @@ -380,6 +380,9 @@ def voronoi_cells(V): - ``V`` -- a corrected Voronoi diagram + - ``vertical_lines`` -- frozenset (default: ``frozenset()``); indices of the + vertical lines + OUTPUT: - ``G`` -- the graph of the 1-skeleton of ``V`` @@ -389,13 +392,15 @@ def voronoi_cells(V): of ``E``) with identical first and last elements) - ``DG`` -- the dual graph of ``V``, where the vertices are labelled by the compact regions of ``V`` and the edges by their dual edges. + - ``vertical_regions`` -- dictionary for the regions associated + with vertical lines EXAMPLES:: sage: from sage.schemes.curves.zariski_vankampen import corrected_voronoi_diagram, voronoi_cells sage: points = (2, I, 0.000001, 0, 0.000001*I) sage: V = corrected_voronoi_diagram(points) - sage: G, E, p, EC, DG = voronoi_cells(V) + sage: G, E, p, EC, DG, VR = voronoi_cells(V, vertical_lines=frozenset((1,))) sage: Gv = G.vertices(sort=True) sage: Ge = G.edges(sort=True) sage: len(Gv), len(Ge) @@ -443,25 +448,38 @@ def voronoi_cells(V): (A vertex at (2000001/2000000, 500001/1000000), A vertex at (11/4, 4), None)) sage: edg[-1] in Ge True + sage: VR + {1: (A vertex at (-49000001/14000000, 1000001/2000000), + A vertex at (1000001/2000000, 1000001/2000000), + A vertex at (2000001/2000000, 500001/1000000), + A vertex at (11/4, 4), + A vertex at (-4, 4), + A vertex at (-49000001/14000000, 1000001/2000000))} """ - compact_regions = [_ for _ in V.regions().values() if _.is_compact()] - non_compact_regions = [_ for _ in V.regions().values() if not _.is_compact()] - G = Graph([u.vertices() for v in compact_regions for u in v.faces(1)], format='list_of_edges') - E = Graph([u.vertices() for v in non_compact_regions for u in v.faces(1) if u.is_compact()], format='list_of_edges') + regions = V.regions() + points = [p for p in V.regions().keys() if V.regions()[p].is_compact()] + compact_regions = [regions[p] for p in points] + vertical_regions = {} + non_compact_regions = [reg for reg in V.regions().values() + if not reg.is_compact()] + G = Graph([u.vertices() for v in compact_regions for u in v.faces(1)], + format='list_of_edges') + E = Graph([u.vertices() for v in non_compact_regions for u in v.faces(1) + if u.is_compact()], format='list_of_edges') p = next(E.vertex_iterator()) EC = orient_circuit(E.eulerian_circuit()) - # EC = [EC0[0][0]] + [e[1] for e in EC0] DG = Graph() for i, reg in enumerate(compact_regions): Greg0 = orient_circuit(reg.graph().eulerian_circuit(), convex=True) - # Greg = (Greg0[0][0],) + tuple(e[1] for e in Greg0) + if i in vertical_lines: + vertical_regions[i] = Greg0 DG.add_vertex((i, Greg0)) for e in G.edges(sort=True): a, b = e[:2] regs = [v for v in DG.vertices(sort=True) if a in v[1] and b in v[1]] if len(regs) == 2: DG.add_edge(regs[0], regs[1], e) - return (G, E, p, EC, DG) + return (G, E, p, EC, DG, vertical_regions) def followstrand(f, factors, x0, x1, y0a, prec=53) -> list: @@ -492,7 +510,7 @@ def followstrand(f, factors, x0, x1, y0a, prec=53) -> list: EXAMPLES:: - sage: # optional - sirocco + sage: # needs sirocco sage: from sage.schemes.curves.zariski_vankampen import followstrand sage: R. = QQ[] sage: f = x^2 + y^3 @@ -548,7 +566,7 @@ def followstrand(f, factors, x0, x1, y0a, prec=53) -> list: ci = c.imag() coefsfactors += list(cr.endpoints()) coefsfactors += list(ci.endpoints()) - from sage.libs.sirocco import contpath, contpath_mp, contpath_comps, contpath_mp_comps + from sage.libs.sirocco import (contpath, contpath_mp, contpath_comps, contpath_mp_comps) try: if prec == 53: if factors: @@ -603,11 +621,11 @@ def fieldI(field): INPUT: - - ``field`` -- a number field with an embedding in ``QQbar``. + - ``field`` -- a number field with an embedding in `\QQbar`. OUTPUT: - The extension ``F`` of ``field`` containing ``I`` with an embedding in ``QQbar``. + The extension ``F`` of ``field`` containing ``I`` with an embedding in `\QQbar`. EXAMPLES:: @@ -616,9 +634,20 @@ def fieldI(field): sage: a0 = p.roots(QQbar, multiplicities=False)[0] sage: F0. = NumberField(p, embedding=a0) sage: fieldI(F0) - Number Field in b with defining polynomial + Number Field in prim with defining polynomial x^10 + 5*x^8 + 14*x^6 - 2*x^5 - 10*x^4 + 20*x^3 - 11*x^2 - 14*x + 10 - with b = 0.4863890359345430? + 1.000000000000000?*I + with prim = 0.4863890359345430? + 1.000000000000000?*I + sage: F0 = CyclotomicField(5) + sage: fieldI(F0) + Number Field in prim with defining polynomial + x^8 - 2*x^7 + 7*x^6 - 10*x^5 + 16*x^4 - 10*x^3 - 2*x^2 + 4*x + 1 + with prim = -0.3090169943749474? + 0.04894348370484643?*I + sage: fieldI(QuadraticField(3)) + Number Field in prim with defining polynomial x^4 - 4*x^2 + 16 + with prim = -1.732050807568878? + 1.000000000000000?*I + sage: fieldI(QuadraticField(-3)) + Number Field in prim with defining polynomial x^4 + 8*x^2 + 4 + with prim = 0.?e-18 - 0.732050807568878?*I If ``I`` is already in the field, the result is the field itself:: @@ -629,6 +658,8 @@ def fieldI(field): sage: F1 = fieldI(F0) sage: F0 == F1 True + sage: QuadraticField(-1) == fieldI(QuadraticField(-1)) + True """ I0 = QQbar.gen() if I0 in field: @@ -641,8 +672,8 @@ def fieldI(field): for h1 in qembd: b1 = h1(b0) b2 = h1(field_b(field_a.gen(0))) - b3 = field.gen(0) - F1 = NumberField(q, 'b', embedding=b1) + b3 = QQbar(field.gen(0)) + F1 = NumberField(q, 'prim', embedding=b1) if b3 in F1 and b2.imag() > 0: return F1 @@ -803,7 +834,7 @@ def braid_in_segment(glist, x0, x1, precision={}): - ``glist`` -- a tuple of polynomials in two variables - ``x0`` -- a Gauss rational - ``x1`` -- a Gauss rational - - ``precision`` -- a dictionary (default: `dict()`) which assigns a number + - ``precision`` -- a dictionary (default: `{}`) which assigns a number precision bits to each element of ``glist`` OUTPUT: @@ -812,7 +843,6 @@ def braid_in_segment(glist, x0, x1, precision={}): EXAMPLES:: - sage: # optional - sirocco sage: from sage.schemes.curves.zariski_vankampen import braid_in_segment, fieldI sage: R. = QQ[] sage: K = fieldI(QQ) @@ -820,7 +850,7 @@ def braid_in_segment(glist, x0, x1, precision={}): sage: f = f.change_ring(K) sage: x0 = 1 sage: x1 = 1 + I / 2 - sage: braid_in_segment(tuple(_[0] for _ in f.factor()), x0, x1) + sage: braid_in_segment(tuple(_[0] for _ in f.factor()), x0, x1) # needs sirocco s1 TESTS: @@ -844,43 +874,44 @@ def braid_in_segment(glist, x0, x1, precision={}): sage: p2a = CC(p2) sage: p2b = QQ(p2a.real()) + I*QQ(p2a.imag()) sage: glist = tuple([_[0] for _ in g.factor()]) - sage: B = braid_in_segment(glist, p1b, p2b); B # optional - sirocco + sage: B = braid_in_segment(glist, p1b, p2b); B # needs sirocco s5*s3^-1 """ precision1 = precision.copy() g = prod(glist) F1 = g.base_ring() x, y = g.parent().gens() - X0 = F1(x0) - X1 = F1(x1) intervals = {} - if not precision1: # new - precision1 = {f: 53 for f in glist} # new + if not precision1: + precision1 = {f: 53 for f in glist} y0s = [] for f in glist: if f.variables() == (y,): f0 = F1[y](f) else: - f0 = F1[y](f.subs({x: X0})) + f0 = F1[y](f.subs({x: F1(x0)})) y0sf = f0.roots(QQbar, multiplicities=False) y0s += list(y0sf) while True: CIFp = ComplexIntervalField(precision1[f]) intervals[f] = [r.interval(CIFp) for r in y0sf] - if not any(a.overlaps(b) for a, b in itertools.combinations(intervals[f], 2)): + if not any(a.overlaps(b) for a, b in + itertools.combinations(intervals[f], 2)): break precision1[f] *= 2 strands = [] for f in glist: for i in intervals[f]: - aux = followstrand(f, [p for p in glist if p != f], x0, x1, i.center(), precision1[f]) + aux = followstrand(f, [p for p in glist if p != f], + x0, x1, i.center(), precision1[f]) strands.append(aux) - complexstrands = [[(QQ(a[0]), QQ(a[1]), QQ(a[2])) for a in b] for b in strands] + complexstrands = [[(QQ(a[0]), QQ(a[1]), QQ(a[2])) for a in b] + for b in strands] centralbraid = braid_from_piecewise(complexstrands) initialstrands = [] finalstrands = [] - initialintervals = roots_interval_cached(g, X0) - finalintervals = roots_interval_cached(g, X1) + initialintervals = roots_interval_cached(g, x0) + finalintervals = roots_interval_cached(g, x1) I1 = QQbar.gen() for cs in complexstrands: ip = cs[0][1] + I1 * cs[0][2] @@ -888,27 +919,29 @@ def braid_in_segment(glist, x0, x1, precision={}): matched = 0 for center, interval in initialintervals.items(): if ip in interval: - initialstrands.append([(0, center.real(), center.imag()), (1, cs[0][1], cs[0][2])]) + initialstrands.append([(0, center.real(), center.imag()), + (1, cs[0][1], cs[0][2])]) matched += 1 if matched != 1: - precision1 = {f: precision1[f] * 2 for f in glist} # new - return braid_in_segment(glist, x0, x1, precision=precision1) # new + precision1 = {f: precision1[f] * 2 for f in glist} + return braid_in_segment(glist, x0, x1, precision=precision1) matched = 0 for center, interval in finalintervals.items(): if fp in interval: - finalstrands.append([(0, cs[-1][1], cs[-1][2]), (1, center.real(), center.imag())]) + finalstrands.append([(0, cs[-1][1], cs[-1][2]), + (1, center.real(), center.imag())]) matched += 1 if matched != 1: - precision1 = {f: precision1[f] * 2 for f in glist} # new - return braid_in_segment(glist, x0, x1, precision=precision1) # new + precision1 = {f: precision1[f] * 2 for f in glist} + return braid_in_segment(glist, x0, x1, precision=precision1) initialbraid = braid_from_piecewise(initialstrands) finalbraid = braid_from_piecewise(finalstrands) return initialbraid * centralbraid * finalbraid -def geometric_basis(G, E, EC0, p, dual_graph) -> list: +def geometric_basis(G, E, EC0, p, dual_graph, vertical_regions={}) -> list: r""" Return a geometric basis, based on a vertex. @@ -927,74 +960,76 @@ def geometric_basis(G, E, EC0, p, dual_graph) -> list: ``E`` is the boundary of the non-bounded component of the complement. The edges are labelled as the dual edges and the vertices are labelled by a tuple whose first element is the an integer for the position and the - second one is the cyclic ordered list of vertices in the region. + second one is the cyclic ordered list of vertices in the region + + - ``vertical_regions`` -- dictionary (default: `{}`); its keys are + the vertices of ``dual_graph`` to fix regions associated with + vertical lines - OUTPUT: A geometric basis. It is formed by a list of sequences of paths. - Each path is a list of vertices, that form a closed path in ``G``, based at - ``p``, that goes to a region, surrounds it, and comes back by the same - path it came. The concatenation of all these paths is equivalent to ``E``. + OUTPUT: A geometric basis and a dictionary. + + The geometric basis is formed by a list of sequences of paths. Each path is a + ist of vertices, that form a closed path in ``G``, based at ``p``, that goes + to a region, surrounds it, and comes back by the same path it came. The + concatenation of all these paths is equivalent to ``E``. + + The dictionary associates to each vertical line the index of the generator + of the geometric basis associated to it. EXAMPLES:: - sage: from sage.schemes.curves.zariski_vankampen import geometric_basis, voronoi_cells - sage: points = [(-3,0),(3,0),(0,3),(0,-3)]+ [(0,0),(0,-1),(0,1),(1,0),(-1,0)] - sage: V = VoronoiDiagram(points) - sage: G, E, p, EC, DG = voronoi_cells(V) - sage: geometric_basis(G, E, EC, p, DG) - [[A vertex at (-2, -2), - A vertex at (2, -2), - A vertex at (2, 2), - A vertex at (1/2, 1/2), - A vertex at (1/2, -1/2), - A vertex at (2, -2), - A vertex at (-2, -2)], - [A vertex at (-2, -2), - A vertex at (2, -2), - A vertex at (1/2, -1/2), - A vertex at (1/2, 1/2), - A vertex at (-1/2, 1/2), - A vertex at (-1/2, -1/2), - A vertex at (1/2, -1/2), - A vertex at (2, -2), - A vertex at (-2, -2)], - [A vertex at (-2, -2), - A vertex at (2, -2), - A vertex at (1/2, -1/2), - A vertex at (-1/2, -1/2), - A vertex at (-2, -2)], - [A vertex at (-2, -2), - A vertex at (-1/2, -1/2), - A vertex at (-1/2, 1/2), - A vertex at (1/2, 1/2), - A vertex at (2, 2), - A vertex at (-2, 2), - A vertex at (-1/2, 1/2), - A vertex at (-1/2, -1/2), - A vertex at (-2, -2)], - [A vertex at (-2, -2), - A vertex at (-1/2, -1/2), - A vertex at (-1/2, 1/2), - A vertex at (-2, 2), - A vertex at (-2, -2)]] + sage: from sage.schemes.curves.zariski_vankampen import geometric_basis, corrected_voronoi_diagram, voronoi_cells + sage: points = (0, -1, I, 1, -I) + sage: V = corrected_voronoi_diagram(points) + sage: G, E, p, EC, DG, VR = voronoi_cells(V, vertical_lines=frozenset((0 .. 4))) + sage: gb, vd = geometric_basis(G, E, EC, p, DG, vertical_regions=VR) + sage: gb + [[A vertex at (5/2, -5/2), A vertex at (5/2, 5/2), A vertex at (-5/2, 5/2), + A vertex at (-1/2, 1/2), A vertex at (-1/2, -1/2), A vertex at (1/2, -1/2), + A vertex at (1/2, 1/2), A vertex at (-1/2, 1/2), A vertex at (-5/2, 5/2), + A vertex at (5/2, 5/2), A vertex at (5/2, -5/2)], + [A vertex at (5/2, -5/2), A vertex at (5/2, 5/2), A vertex at (-5/2, 5/2), + A vertex at (-1/2, 1/2), A vertex at (1/2, 1/2), A vertex at (5/2, 5/2), + A vertex at (5/2, -5/2)], + [A vertex at (5/2, -5/2), A vertex at (5/2, 5/2), A vertex at (1/2, 1/2), + A vertex at (1/2, -1/2), A vertex at (5/2, -5/2)], [A vertex at (5/2, -5/2), + A vertex at (1/2, -1/2), A vertex at (-1/2, -1/2), A vertex at (-1/2, 1/2), + A vertex at (-5/2, 5/2), A vertex at (-5/2, -5/2), A vertex at (-1/2, -1/2), + A vertex at (1/2, -1/2), A vertex at (5/2, -5/2)], + [A vertex at (5/2, -5/2), A vertex at (1/2, -1/2), A vertex at (-1/2, -1/2), + A vertex at (-5/2, -5/2), A vertex at (5/2, -5/2)]] + sage: vd + {0: 0, 1: 3, 2: 1, 3: 2, 4: 4} """ i = EC0.index(p) - EC = EC0[i:-1] + EC0[:i + 1] # A counterclockwise eulerian circuit on the boundary, starting and ending at p + EC = EC0[i:-1] + EC0[:i + 1] + # A counterclockwise eulerian circuit on the boundary, + # starting and ending at p if G.size() == E.size(): if E.is_cycle(): - return [EC] + j = next(dual_graph.vertex_iterator())[0] + if j in vertical_regions: + vd = {j: 0} + else: + vd = {} + return [EC], vd edges_E = E.edges(sort=True) InternalEdges = [e for e in G.edges(sort=True) if e not in edges_E] InternalVertices = [v for e in InternalEdges for v in e[:2]] Internal = G.subgraph(vertices=InternalVertices, edges=InternalEdges) for i, ECi in enumerate(EC): # q and r are the points we will cut through if ECi in Internal: - EI = [v for v in E if v in Internal.connected_component_containing_vertex(ECi, sort=True) and v != ECi] + EI = [v for v in E if v in + Internal.connected_component_containing_vertex(ECi, sort=True) + and v != ECi] if EI: q = ECi connecting_path = list(EC[:i]) break if EC[-i] in Internal: - EI = [v for v in E if v in Internal.connected_component_containing_vertex(EC[-i], sort=True) and v != EC[-i]] + EI = [v for v in E if v in + Internal.connected_component_containing_vertex(EC[-i], sort=True) + and v != EC[-i]] if EI: q = EC[-i] connecting_path = list(reversed(EC[-i:])) @@ -1003,7 +1038,6 @@ def geometric_basis(G, E, EC0, p, dual_graph) -> list: E_dist_q = E.shortest_path_lengths(q) I_dist_q = Internal.shortest_path_lengths(q) distancequotients = [(E_dist_q[v]**2 / I_dist_q[v], v) for v in EI] - # distancequotients = [(E.distance(q, v)**2 / Internal.distance(q, v), v) for v in EI] r = max(distancequotients)[1] cutpath = Internal.shortest_path(q, r) for i, v in enumerate(cutpath): @@ -1037,7 +1071,8 @@ def geometric_basis(G, E, EC0, p, dual_graph) -> list: E1.add_edge(cutpath[i], cutpath[i + 1], None) E2.add_edge(cutpath[i], cutpath[i + 1], None) Gd = copy(dual_graph) - to_delete = [e for e in Gd.edges(sort=True) if e[2][0] in cutpath and e[2][1] in cutpath] + to_delete = [e for e in Gd.edges(sort=True) if e[2][0] in cutpath and + e[2][1] in cutpath] Gd.delete_edges(to_delete) Gd1, Gd2 = Gd.connected_components_subgraphs() edges_2 = [] @@ -1045,16 +1080,20 @@ def geometric_basis(G, E, EC0, p, dual_graph) -> list: for reg in Gd2.vertices(sort=True): vertices_2 += reg[1][:-1] reg_circuit = reg[1] - edges_2 += [(v1, reg_circuit[i + 1]) for i, v1 in enumerate(reg_circuit[:-1])] - edges_2 += [(v1, reg_circuit[i - 1]) for i, v1 in enumerate(reg_circuit[1:])] + edges_2 += [(v1, reg_circuit[i + 1]) + for i, v1 in enumerate(reg_circuit[:-1])] + edges_2 += [(v1, reg_circuit[i - 1]) + for i, v1 in enumerate(reg_circuit[1:])] G2 = G.subgraph(vertices=vertices_2, edges=edges_2) edges_1 = [] vertices_1 = [] for reg in Gd1.vertices(sort=True): vertices_1 += reg[1] reg_circuit = reg[1] + (reg[1][0],) - edges_1 += [(v1, reg_circuit[i + 1]) for i, v1 in enumerate(reg_circuit[:-1])] - edges_1 += [(v1, reg_circuit[i - 1]) for i, v1 in enumerate(reg_circuit[1:])] + edges_1 += [(v1, reg_circuit[i + 1]) + for i, v1 in enumerate(reg_circuit[:-1])] + edges_1 += [(v1, reg_circuit[i - 1]) + for i, v1 in enumerate(reg_circuit[1:])] G1 = G.subgraph(vertices=vertices_1, edges=edges_1) if EC[qi + 1] in G2: G1, G2 = G2, G1 @@ -1067,9 +1106,13 @@ def geometric_basis(G, E, EC0, p, dual_graph) -> list: EC1 = list(EC[qi:] + EC[1:ri]) + list(reversed(cutpath)) EC2 = cutpath + list(EC[ri + 1:qi + 1]) - gb1 = geometric_basis(G1, E1, EC1, q, Gd1) - gb2 = geometric_basis(G2, E2, EC2, q, Gd2) + gb1, vd1 = geometric_basis(G1, E1, EC1, q, Gd1, vertical_regions=vertical_regions) + gb2, vd2 = geometric_basis(G2, E2, EC2, q, Gd2, vertical_regions=vertical_regions) + vd = {j: vd1[j] for j in vd1} + m = len(gb1) + for j in vd2.keys(): + vd[j] = vd2[j] + m reverse_connecting = list(reversed(connecting_path)) resul = [connecting_path + path + reverse_connecting for path in gb1 + gb2] @@ -1083,10 +1126,52 @@ def geometric_basis(G, E, EC0, p, dual_graph) -> list: i -= 1 else: i += 1 - return resul + return (resul, vd) -def strand_components(f, flist, p1): +def vertical_lines_in_braidmon(pols) -> list: + r""" + Return the vertical lines in ``pols``, unless + one of the other components has a vertical asymptote. + + INPUT: + + - ``pols`` -- a list of polynomials with two variables whose + product equals ``f`` + + OUTPUT: + + A list with the indices of the vertical lines in ``flist`` if there is + no other componnet with vertical asymptote; otherwise it returns an empty + list. + + EXAMPLES:: + + sage: from sage.schemes.curves.zariski_vankampen import vertical_lines_in_braidmon + sage: R. = QQ[] + sage: flist = [x^2 - y^3, x, x + 3 * y - 5, 1 - x] + sage: vertical_lines_in_braidmon(flist) + [1, 3] + sage: flist += [x * y - 1] + sage: vertical_lines_in_braidmon(flist) + [] + sage: vertical_lines_in_braidmon([]) + [] + """ + if not pols: + return [] + res = [] + for j, f in enumerate(pols): + C = Curve(f) + vertical_asymptote = C.has_vertical_asymptote() + if vertical_asymptote: + return [] + if C.is_vertical_line(): + res.append(j) + return res + + +def strand_components(f, pols, p1): r""" Compute only the assignment from strands to elements of ``flist``. @@ -1095,7 +1180,7 @@ def strand_components(f, flist, p1): - ``f`` -- a reduced polynomial with two variables, over a number field with an embedding in the complex numbers - - ``flist`` -- a list of polynomials with two variables whose + - ``pols`` -- a list of polynomials with two variables whose product equals ``f`` - ``p1`` -- a Gauss rational @@ -1103,13 +1188,13 @@ def strand_components(f, flist, p1): OUTPUT: - A list and a dictionary. The first one is an ordered list of pairs - consisting of ``(z,i)`` where ``z`` is a root of ``f(p_1,y)`` and `i` is the position - of the polynomial in the list whose root is ``z``. The second one attaches - a number `i` (strand) to a number `j` (a polynomial in the list). + consisting of ``(z,i)`` where ``z`` is a root of ``f(p_1,y)`` + and `i` is the position of the polynomial in the list whose root + is ``z``. The second one attaches a number `i` (strand) to a + number `j` (a polynomial in the list). EXAMPLES:: - sage: # optional - sirocco sage: from sage.schemes.curves.zariski_vankampen import strand_components sage: R. = QQ[] sage: flist = [x^2 - y^3, x + 3 * y - 5] @@ -1119,20 +1204,20 @@ def strand_components(f, flist, p1): (1, 0), (1.333333333333334?, 1)], {0: 0, 1: 0, 2: 0, 3: 1}) """ x, y = f.parent().gens() - F = flist[0].base_ring() + F = pols[0].base_ring() strands = {} roots_base = [] - for i, h in enumerate(flist): + for i, h in enumerate(pols): h0 = h.subs({x: p1}) h1 = F[y](h0) rt = h1.roots(QQbar, multiplicities=False) roots_base += [(r, i) for r in rt] roots_base.sort() - strands = {i: par[1] for i, par in enumerate(roots_base)} # quitar +1 despues de revision + strands = {i: par[1] for i, par in enumerate(roots_base)} return (roots_base, strands) -def braid_monodromy(f, arrangement=()): +def braid_monodromy(f, arrangement=(), vertical=False): r""" Compute the braid monodromy of a projection of the curve defined by a polynomial. @@ -1142,31 +1227,45 @@ def braid_monodromy(f, arrangement=()): - ``f`` -- a polynomial with two variables, over a number field with an embedding in the complex numbers - - ``arrangement`` -- an optional tuple of polynomials whose product - equals ``f``. + - ``arrangement`` -- tuple (default: ``()``); an optional tuple + of polynomials whose product equals ``f`` + + - ``vertical`` -- boolean (default: ``False`); if set to ``True``, + ``arrangements`` contains more than one polynomial, some of them + are of degree `1` in `x` and degree `0` in `y`, and none of + the other components have vertical asymptotes, then these + components are marked as *vertical* and not used for the computation + of the braid monodromy. The other ones are marked as *horizontal*. If + a vertical component does not pass through a singular points of the + projection of the horizontal components a trivial braid is added + to the list. OUTPUT: - A list of braids and a dictionary. - The braids correspond to paths based in the same point; - each of these paths is the conjugated of a loop around one of the points - in the discriminant of the projection of ``f``. The dictionary assigns each - strand to the index of the corresponding factor in ``arrangement``. + - A list of braids, images by the braid monodromy of a geometric + basis of the complement of the discriminant of `f` in `\CC`. + + - A dictionary: ``i``, index of a strand is sent to the index of + the corresponding factor in ``arrangement``. + + - Another dictionary ``dv``, only relevant if ``vertical`` is ``True``. + If ``j`` is the index + of a braid corresponding to a vertical line with index ``i`` + in ``arrangement``, then ``dv[j] = i``. + + - A non-negative integer: the number of strands of the braids, + only necessary if the list of braids is empty. .. NOTE:: The projection over the `x` axis is used if there are no vertical asymptotes. Otherwise, a linear change of variables is done to fall - into the previous case. - - .. TODO:: - - Create a class ``arrangements_of_curves`` with a ``braid_monodromy`` - method; it can be also a method for affine line arrangements. + into the previous case except if the only vertical asymptotes are lines + and ``vertical=True``. EXAMPLES:: - sage: # optional - sirocco + sage: # needs sirocco sage: from sage.schemes.curves.zariski_vankampen import braid_monodromy sage: R. = QQ[] sage: f = (x^2 - y^3) * (x + 3*y - 5) @@ -1174,7 +1273,7 @@ def braid_monodromy(f, arrangement=()): ([s1*s0*(s1*s2)^2*s0*s2^2*s0^-1*(s2^-1*s1^-1)^2*s0^-1*s1^-1, s1*s0*(s1*s2)^2*(s0*s2^-1*s1*s2*s1*s2^-1)^2*(s2^-1*s1^-1)^2*s0^-1*s1^-1, s1*s0*(s1*s2)^2*s2*s1^-1*s2^-1*s1^-1*s0^-1*s1^-1, - s1*s0*s2*s0^-1*s2*s1^-1], {0: 0, 1: 0, 2: 0, 3: 0}) + s1*s0*s2*s0^-1*s2*s1^-1], {0: 0, 1: 0, 2: 0, 3: 0}, {}, 4) sage: flist = (x^2 - y^3, x + 3*y - 5) sage: bm1 = braid_monodromy(f, arrangement=flist) sage: bm1[0] == bm[0] @@ -1182,42 +1281,86 @@ def braid_monodromy(f, arrangement=()): sage: bm1[1] {0: 0, 1: 1, 2: 0, 3: 0} sage: braid_monodromy(R(1)) - ([], {}) + ([], {}, {}, 0) sage: braid_monodromy(x*y^2 - 1) - ([s0*s1*s0^-1*s1*s0*s1^-1*s0^-1, s0*s1*s0^-1, s0], {0: 0, 1: 0, 2: 0}) + ([s0*s1*s0^-1*s1*s0*s1^-1*s0^-1, s0*s1*s0^-1, s0], {0: 0, 1: 0, 2: 0}, {}, 3) + sage: L = [x, y, x - 1, x -y] + sage: braid_monodromy(prod(L), arrangement=L, vertical=True) + ([s^2, 1], {0: 1, 1: 3}, {0: 0, 1: 2}, 2) """ global roots_interval_cache F = fieldI(f.base_ring()) I1 = F(QQbar.gen()) f = f.change_ring(F) - if arrangement == (): + if not arrangement: arrangement1 = (f,) else: - arrangement1 = tuple(_.change_ring(F) for _ in arrangement) + arrangement1 = tuple(g.change_ring(F) for g in arrangement) x, y = f.parent().gens() - glist = tuple(_[0] for f0 in arrangement1 for _ in f0.factor()) + if vertical: + indices_v = vertical_lines_in_braidmon(arrangement1) + else: + indices_v = [] + arrangement_h = tuple(f0 for j, f0 in enumerate(arrangement1) + if j not in indices_v) + arrangement_v = tuple(f0 for j, f0 in enumerate(arrangement1) + if j in indices_v) + glist = tuple(fc[0] for f0 in arrangement_h for fc in f0.factor()) g = f.parent()(prod(glist)) d = g.degree(y) - while not g.coefficient(y**d) in F: - g = g.subs({x: x + y}) - d = g.degree(y) - arrangement1 = tuple(f1.subs({x: x + y}) for f1 in arrangement1) - glist = tuple(f1.subs({x: x + y}) for f1 in glist) + if not arrangement_v: # change of coordinates only if indices_v is empty + while not g.coefficient(y**d) in F: + g = g.subs({x: x + y}) + d = g.degree(y) + arrangement_h = tuple(f1.subs({x: x + y}) for f1 in arrangement_h) + arrangement1 = arrangement_h + glist = tuple(f1.subs({x: x + y}) for f1 in glist) if d > 0: disc = discrim(glist) else: disc = [] + vertical_braid = {} + transversal = {} + vl = [] + for f0 in arrangement_v: + pt = [j for j, t in enumerate(disc) if f0.subs({x: t}) == 0] + if pt: + vertical_braid[f0] = (pt[0], arrangement1.index(f0)) + vl.append(pt[0]) + else: + transversal[f0] = arrangement1.index(f0) + vl.sort() + vl = frozenset(vl) if not disc: - result = [] + vertical_braids = {i: transversal[f0] + for i, f0 in enumerate(transversal)} + if d > 1: + result = [BraidGroup(d).one() for p in transversal] + else: + G = FreeGroup(0) / [] + result = [G.one() for p in transversal] p1 = F(0) - roots_base, strands = strand_components(g, arrangement1, p1) - return ([], strands) + if d > 0: + roots_base, strands = strand_components(g, arrangement_h, p1) + strands1 = {} + for j in range(d): + i = strands[j] + k = arrangement1.index(arrangement_h[i]) + strands1[j] = k + else: + strands1 = {} + return (result, strands1, vertical_braids, d) V = corrected_voronoi_diagram(tuple(disc)) - G, E, p, EC, DG = voronoi_cells(V) + G, E, p, EC, DG, VR = voronoi_cells(V, vertical_lines=vl) p0 = (p[0], p[1]) p1 = p0[0] + I1 * p0[1] - roots_base, strands = strand_components(g, arrangement1, p1) - geombasis = geometric_basis(G, E, EC, p, DG) + roots_base, strands = strand_components(g, arrangement_h, p1) + strands1 = {} + for j in range(d): + i = strands[j] + k = arrangement1.index(arrangement_h[i]) + strands1[j] = k + geombasis, vd = geometric_basis(G, E, EC, p, DG, vertical_regions=VR) segs = set() for p in geombasis: for s in zip(p[:-1], p[1:]): @@ -1253,7 +1396,18 @@ def braid_monodromy(f, arrangement=()): x1 = tuple(path[i + 1].vector()) braidpath = braidpath * segsbraids[(x0, x1)] result.append(braidpath) - return (result, strands) + vertical_braids = {} + r = len(result) + t = 0 + for f0 in arrangement_v: + if f0 in vertical_braid.keys(): + k, j = vertical_braid[f0] + vertical_braids[vd[k]] = j + else: + vertical_braids[r + t] = transversal[f0] + t += 1 + result.append(B.one()) + return (result, strands1, vertical_braids, d) def conjugate_positive_form(braid): @@ -1263,7 +1417,7 @@ def conjugate_positive_form(braid): INPUT: - - ``braid`` -- a braid ``\sigma``. + - ``braid`` -- a braid `\sigma` OUTPUT: @@ -1316,10 +1470,7 @@ def conjugate_positive_form(braid): A1 = rightnormalform(sg) par = A1[-1][0] % 2 A1 = [B(a) for a in A1[:-1]] - if not A1: - b = B.one() - else: - b = prod(A1) + b = prod(A1, B.one()) b1 = len(b.Tietze()) / (len(A1) + 1) if res is None or b1 < res[3]: res = [tau, A1, par, b1] @@ -1373,13 +1524,13 @@ def braid2rels(L): k = min(T1) - 1 B0 = BraidGroup(m) F0 = FreeGroup(m) - br0 = B0([_-k for _ in T]) + br0 = B0([j-k for j in T]) br0_left = leftnormalform(br0) q, r = ZZ(br0_left[0][0]).quo_rem(2) - br1 = B0.delta()**r * B0(prod(B0(_) for _ in br0_left[1:])) + br1 = B0.delta()**r * prod(map(B0, br0_left[1:]), B0.one()) cox = prod(F0.gens()) U0 = [cox**q * (f0 * br1) / cox**q / f0 for f0 in F0.gens()[:-1]] - U = [tuple(sign(k1) * (abs(k1) + k) for k1 in _.Tietze()) for _ in U0] + U = [tuple(sign(k1) * (abs(k1) + k) for k1 in br.Tietze()) for br in U0] pasos = [B.one()] + list(reversed(L1)) for C in pasos: U = [(F(a) * C.inverse()).Tietze() for a in U] @@ -1392,7 +1543,7 @@ def braid2rels(L): P.TzGoGo() P.TzGoGo() gb = wrap_FpGroup(P.FpGroupPresentation()) - U = [_.Tietze() for _ in gb.relations()] + U = [rel.Tietze() for rel in gb.relations()] return U @@ -1406,7 +1557,9 @@ def relation(x, b): return x * b / x -def fundamental_group_from_braid_mon(bm, degree=None, simplified=True, projective=False, puiseux=False, vertical=[]): +def fundamental_group_from_braid_mon(bm, degree=None, + simplified=True, projective=False, + puiseux=True, vertical=[]): r""" Return a presentation of the fundamental group computed from a braid monodromy. @@ -1426,16 +1579,15 @@ def fundamental_group_from_braid_mon(bm, degree=None, simplified=True, projectiv of the curve will be computed, otherwise, the fundamental group of the complement in the affine plane will be computed - - ``puiseux`` -- boolean (default: ``False``); if set to ``True``, - ``simplified`` is set to ``False``, and + - ``puiseux`` -- boolean (default: ``True``); if set to ``True`` a presentation of the fundamental group with the homotopy type of the complement of the affine curve will be computed, adding one relation if ``projective`` is set to ``True``. - ``vertical`` -- list of integers (default: ``[]``); the indices in - ``[1..r]`` of the braids that surround a vertical line + ``[0 .. r - 1]`` of the braids that surround a vertical line - If ``simplified` and ``projective``` are ``False`` and ``puiseux`` is + If ``projective``` is ``False`` and ``puiseux`` is ``True``, a Zariski-VanKampen presentation is returned. OUTPUT: @@ -1450,37 +1602,45 @@ def fundamental_group_from_braid_mon(bm, degree=None, simplified=True, projectiv sage: bm = [s1*s2*s0*s1*s0^-1*s1^-1*s0^-1, ....: s0*s1^2*s0*s2*s1*(s0^-1*s1^-1)^2*s0^-1, ....: (s0*s1)^2] - sage: g = fundamental_group_from_braid_mon(bm, projective=True); g - Finitely presented group < x0, x1 | x1*x0^2*x1, x0^-1*x1^-1*x0^-1*x1*x0^-1*x1^-1 > - sage: print (g.order(), g.abelian_invariants()) + sage: g = fundamental_group_from_braid_mon(bm, projective=True); g # needs sirocco + Finitely presented group + < x1, x3 | x3^2*x1^2, x1^-1*x3^-1*x1*x3^-1*x1^-1*x3^-1 > + sage: print(g.order(), g.abelian_invariants()) # needs sirocco 12 (4,) sage: B2 = BraidGroup(2) sage: bm = [B2(3 * [1])] - sage: g = fundamental_group_from_braid_mon(bm, vertical=[1]); g - Finitely presented group < x0, x1, x2 | x2*x0*x1*x2^-1*x1^-1*x0^-1, - x2*x0*x1*x0*x1^-1*x0^-1*x2^-1*x1^-1 > - sage: fundamental_group_from_braid_mon([]) is None # optional - sirocco + sage: g = fundamental_group_from_braid_mon(bm, vertical=[0]); g # needs sirocco + Finitely presented group + < x0, x1, x2 | x2*x0*x1*x2^-1*x1^-1*x0^-1, + x2*x0*x1*x0*x1^-1*x0^-1*x2^-1*x1^-1 > + sage: fundamental_group_from_braid_mon([]) is None # needs sirocco True - sage: fundamental_group_from_braid_mon([], degree=2) # optional - sirocco + sage: fundamental_group_from_braid_mon([], degree=2) # needs sirocco Finitely presented group < x0, x1 | > + sage: fundamental_group_from_braid_mon([SymmetricGroup(1).one()]) # needs sirocco + Finitely presented group < x | > """ vertical0 = sorted(vertical) v = len(vertical0) if not bm: d = degree + elif bm[0].parent().order() == 1: + d = 1 else: d = bm[0].parent().strands() if d is None: return None F = FreeGroup(d) Fv = FreeGroup(d + v) - bmh = [br for j, br in enumerate(bm) if j + 1 not in vertical0] + if d == 0: + return Fv / [] + if d == 1: + return Fv / [(1, j, -1, -j) for j in range(2, d + v + 1)] + bmh = [br for j, br in enumerate(bm) if j not in vertical0] if not puiseux: relations_h = (relation([(x, b) for x in F.gens() for b in bmh])) rel_h = [r[1] for r in relations_h] - simplified0 = simplified else: - simplified0 = False conjugate_desc = conjugate_positive_form_p(bmh) trenzas_desc = [b1[-1] for b1 in conjugate_desc] trenzas_desc_1 = flatten(trenzas_desc, max_level=1) @@ -1490,7 +1650,7 @@ def fundamental_group_from_braid_mon(bm, degree=None, simplified=True, projectiv rel_v = [] for j, k in enumerate(vertical0): l1 = d + j + 1 - br = bm[k - 1] + br = bm[k] for gen in F.gens(): j0 = gen.Tietze()[0] rl = (l1,) + (gen * br).Tietze() + (-l1, -j0) @@ -1499,12 +1659,12 @@ def fundamental_group_from_braid_mon(bm, degree=None, simplified=True, projectiv if projective: rel.append(prod(Fv.gens()).Tietze()) G = Fv / rel - if simplified0: + if simplified: return G.simplified() return G -def fundamental_group(f, simplified=True, projective=False, puiseux=False): +def fundamental_group(f, simplified=True, projective=False, puiseux=True): r""" Return a presentation of the fundamental group of the complement of the algebraic set defined by the polynomial ``f``. @@ -1522,12 +1682,14 @@ def fundamental_group(f, simplified=True, projective=False, puiseux=False): of the curve will be computed, otherwise, the fundamental group of the complement in the affine plane will be computed - - ``puiseux`` -- boolean (default: ``False``); if set to ``True``, + - ``puiseux`` -- boolean (default: ``True``); if set to ``True``, a presentation of the fundamental group with the homotopy type - of the complement of the affine curve is computed, ``simplified`` is - ignored. One relation is added if ``projective`` is set to ``True``. + of the complement of the affine curve is computed. If the Euler + characteristic does not match, the homotopy type is obtained + with a wedge of 2-spheres. One relation is added if ``projective`` + is set to ``True``. - If ``simplified` and ``projective``` are ``False`` and ``puiseux`` is + If ``projective``` is ``False`` and ``puiseux`` is ``True``, a Zariski-VanKampen presentation is returned. OUTPUT: @@ -1537,13 +1699,13 @@ def fundamental_group(f, simplified=True, projective=False, puiseux=False): EXAMPLES:: - sage: # optional - sirocco + sage: # needs sirocco sage: from sage.schemes.curves.zariski_vankampen import fundamental_group, braid_monodromy sage: R. = QQ[] sage: f = x^2 + y^3 sage: fundamental_group(f) - Finitely presented group < x1, x2 | x1*x2*x1^-1*x2^-1*x1^-1*x2 > - sage: fundamental_group(f, simplified=False).sorted_presentation() + Finitely presented group < x0, x1 | x0*x1^-1*x0^-1*x1^-1*x0*x1 > + sage: fundamental_group(f, simplified=False, puiseux=False).sorted_presentation() Finitely presented group < x0, x1, x2 | x2^-1*x1^-1*x0*x1, x2^-1*x0*x1*x0^-1, x1^-1*x0^-1*x1^-1*x0*x1*x0 > @@ -1554,17 +1716,17 @@ def fundamental_group(f, simplified=True, projective=False, puiseux=False): :: - sage: # optional - sirocco + sage: # needs sirocco sage: from sage.schemes.curves.zariski_vankampen import fundamental_group sage: R. = QQ[] sage: f = y^3 + x^3 - sage: fundamental_group(f) - Finitely presented group < x0, x1, x2 | x0*x1*x2*x0^-1*x2^-1*x1^-1, x2*x0*x1*x2^-1*x1^-1*x0^-1 > + sage: fundamental_group(f).sorted_presentation() + Finitely presented group < x0, x1, x2 | x2^-1*x1^-1*x0^-1*x2*x0*x1, + x2^-1*x1^-1*x2*x0*x1*x0^-1 > It is also possible to have coefficients in a number field with a fixed embedding in `\QQbar`:: - sage: # optional - sirocco sage: from sage.schemes.curves.zariski_vankampen import fundamental_group sage: zeta = QQbar['x']('x^2 + x+ 1').roots(multiplicities=False)[0] sage: zeta @@ -1574,22 +1736,19 @@ def fundamental_group(f, simplified=True, projective=False, puiseux=False): Defining zeta sage: R. = F[] sage: f = y^3 + x^3 + zeta * x + 1 - sage: fundamental_group(f) + sage: fundamental_group(f) # needs sirocco Finitely presented group < x0 | > - We compute the fundamental group of the complement of a quartic using the ``puiseux`` option:: + We compute the fundamental group of the complement of a + quartic using the ``puiseux`` option:: sage: # optional - sirocco sage: from sage.schemes.curves.zariski_vankampen import fundamental_group sage: R. = QQ[] sage: f = x^2 * y^2 + x^2 + y^2 - 2 * x * y * (x + y + 1) - sage: g = fundamental_group(f, puiseux=True); g.sorted_presentation() - Finitely presented group - < x0, x1, x2, x3 | x3^-1*x2^-1*x1^-1*x0^-1*x1*x2*x1^-1*x0*x1*x2, - x3^-1*x2^-1*x1*x2, x2^-1*x1^-1*x0^-1*x1*x2*x1, x2^-1*x0 > - sage: g.simplified().sorted_presentation() + sage: g = fundamental_group(f); g.sorted_presentation() Finitely presented group < x0, x1 | x1^-2*x0^2, (x1^-1*x0)^3 > - sage: g = fundamental_group(f, puiseux=True, projective=True) + sage: g = fundamental_group(f, projective=True) sage: g.order(), g.abelian_invariants() (12, (4,)) sage: fundamental_group(y * (y - 1)) @@ -1610,10 +1769,15 @@ def fundamental_group(f, simplified=True, projective=False, puiseux=False): d = g.degree(y) else: d = bm[0].parent().strands() - return fundamental_group_from_braid_mon(bm, degree=d, simplified=simplified, projective=projective, puiseux=puiseux) + return fundamental_group_from_braid_mon(bm, degree=d, + simplified=simplified, + projective=projective, + puiseux=puiseux) -def fundamental_group_arrangement(flist, simplified=True, projective=False, puiseux=False): +def fundamental_group_arrangement(flist, simplified=True, projective=False, + puiseux=True, vertical=False, + braid_data=None): r""" Compute the fundamental group of the complement of a curve defined by a list of polynomials with the extra information @@ -1633,27 +1797,34 @@ def fundamental_group_arrangement(flist, simplified=True, projective=False, puis of the curve will be computed, otherwise, the fundamental group of the complement in the affine plane will be computed - - ``puiseux`` -- boolean (default: ``False``); if set to ``True``, - ``simplified`` is set to ``False``, and + - ``puiseux`` -- boolean (default: ``True``); if set to ``True`` a presentation of the fundamental group with the homotopy type of the complement of the affine curve will be computed, adding one relation if ``projective`` is set to ``True``. + - ``vertical`` -- boolean (default: ``False``); if set to ``True``, + whenever no curve has vertical asymptotes the computation of braid + monodromy is simpler if some lines are vertical + + - ``braid_data`` -- tuple (default: ``None``); if it is not the default + it is the output of ``fundamental_group_from_braid_mon`` previously + computed + OUTPUT: - A list of braids. The braids correspond to paths based in the same point; each of this paths is the conjugated of a loop around one of the points in the discriminant of the projection of ``f``. - - A dictionary attaching a tuple ``(i,)`` (generator) to a number ``j`` - (a polynomial in the list). If ``simplified`` is set to ``True``, - a longer key may appear for either the meridian of the line at infinity, - if ``projective`` is ``True``, or a simplified generator, - if ``projective`` is ``False`` + - A dictionary attaching to ``j`` a tuple a list of elements + of the group which are meridians of the curve in position ``j``. + If ``projective`` is ``False`` and the `y`-degree of the horizontal + components coincide with the total degree, another key is added + to give a meridian of the line at infinity. EXAMPLES:: - sage: # optional - sirocco + sage: # needs sirocco sage: from sage.schemes.curves.zariski_vankampen import braid_monodromy sage: from sage.schemes.curves.zariski_vankampen import fundamental_group_arrangement sage: R. = QQ[] @@ -1661,36 +1832,48 @@ def fundamental_group_arrangement(flist, simplified=True, projective=False, puis sage: g, dic = fundamental_group_arrangement(flist) sage: g.sorted_presentation() Finitely presented group - < x0, x1, x2 | x2^-1*x1^-1*x2*x1, x2^-1*x0^-1*x2^-1*x0*x2*x0, x1^-1*x0^-1*x1*x0 > + < x0, x1, x2 | x2^-1*x1^-1*x2*x1, x2^-1*x0^-1*x2^-1*x0*x2*x0, + x1^-1*x0^-1*x1*x0 > sage: dic - {0: [x0, x2, x0], 1: [x1], 2: [x0^-1*x2^-1*x1^-1*x0^-1]} - sage: g, dic = fundamental_group_arrangement(flist, simplified=False) + {0: [x0, x2], 1: [x1], 2: [x0^-1*x2^-1*x1^-1*x0^-1]} + sage: g, dic = fundamental_group_arrangement(flist, simplified=False, puiseux=False) sage: g.sorted_presentation(), dic (Finitely presented group - < x0, x1, x2, x3 | 1, 1, 1, 1, 1, 1, 1, x3^-1*x2^-1*x1^-1*x2*x3*x2^-1*x1*x2, + < x0, x1, x2, x3 | 1, 1, 1, 1, 1, 1, 1, + x3^-1*x2^-1*x1^-1*x2*x3*x2^-1*x1*x2, x3^-1*x2^-1*x1^-1*x0^-1*x1*x2*x3*x2, x3^-1*x2^-1*x1^-1*x0^-1*x1*x2*x1^-1*x0*x1*x2, - x3^-1*x2^-1*x1^-1*x2*x3*x2^-1*x1*x2, x3^-1*x1^-1*x0*x1, - x1^-1*x0^-1*x1*x0, x1^-1*x0^-1*x1*x0, x1^-1*x0^-1*x1*x0, - x1^-1*x0^-1*x1*x0 >, + x3^-1*x2^-1*x1^-1*x2*x3*x2^-1*x1*x2, + x3^-1*x1^-1*x0*x1, + x1^-1*x0^-1*x1*x0, x1^-1*x0^-1*x1*x0, + x1^-1*x0^-1*x1*x0, x1^-1*x0^-1*x1*x0 >, {0: [x0, x2, x3], 1: [x1], 2: [x3^-1*x2^-1*x1^-1*x0^-1]}) sage: fundamental_group_arrangement(flist, projective=True) - (Finitely presented group < x | >, {0: [x0, x0, x0], 1: [x0^-3]}) + (Finitely presented group < x | >, {0: [x], 1: [x^-3]}) sage: fundamental_group_arrangement([]) (Finitely presented group < | >, {}) sage: g, dic = fundamental_group_arrangement([x * y]) sage: g.sorted_presentation(), dic (Finitely presented group < x0, x1 | x1^-1*x0^-1*x1*x0 >, - {0: [x0, x1], 1: [x1^-1*x0^-1]}) - sage: fundamental_group_arrangement([y + x^2], projective=True) - (Finitely presented group < x | x^2 >, {0: [x0, x0]}) - - .. TODO:: - - Create a class ``arrangements_of_curves`` with a ``fundamental_group`` - method it can be also a method for affine or projective line - arrangements, even for hyperplane arrangements defined over a number - subfield of ``QQbar`` after applying a generic line section. + {0: [x0, x1], 1: [x1^-1*x0^-1]}) + sage: fundamental_group_arrangement([y + x^2]) + (Finitely presented group < x | >, {0: [x]}) + sage: fundamental_group_arrangement([y^2 + x], projective=True) + (Finitely presented group < x | x^2 >, {0: [x]}) + sage: L = [x, y, x - 1, x -y] + sage: G, dic =fundamental_group_arrangement(L) + sage: G.sorted_presentation() + Finitely presented group + < x0, x1, x2, x3 | x3^-1*x2^-1*x3*x2, x3^-1*x1^-1*x0^-1*x1*x3*x0, + x3^-1*x1^-1*x3*x0*x1*x0^-1, x2^-1*x0^-1*x2*x0 > + sage: dic + {0: [x1], 1: [x3], 2: [x2], 3: [x0], 4: [x3^-1*x2^-1*x1^-1*x0^-1]} + sage: fundamental_group_arrangement(L, vertical=True) + (Finitely presented group + < x0, x1, x2, x3 | x3*x0*x3^-1*x0^-1, x3*x1*x3^-1*x1^-1, + x1*x2*x0*x2^-1*x1^-1*x0^-1, + x1*x2*x0*x1^-1*x0^-1*x2^-1 >, + {0: [x2], 1: [x0], 2: [x3], 3: [x1], 4: [x3^-1*x2^-1*x1^-1*x0^-1]}) """ if flist: f = prod(flist) @@ -1699,23 +1882,32 @@ def fundamental_group_arrangement(flist, simplified=True, projective=False, puis R = PolynomialRing(QQ, ('x', 'y')) f = R(1) x, y = R.gens() - F = R.base_ring() - flist1 = list(flist) - d = f.degree(y) - while not f.coefficient(y**d) in F: - flist1 = [g.subs({x: x + y}) for g in flist1] - f = prod(flist1) - d = f.degree(y) - if projective: - while f.degree(y) < f.degree(): - flist1 = [g.subs({x: x + y}) for g in flist] - f = prod(flist1) - if not flist1: + flist1 = tuple(flist) + if vertical and vertical_lines_in_braidmon(flist1): + infinity = all([Curve(g).is_vertical_line() or + g.degree(y) == g.degree() for g in flist1]) + else: + infinity = any([Curve(g).has_vertical_asymptote() or + Curve(g).is_vertical_line() for g in flist1]) + if not infinity: + infinity = all([g.degree(y) == g.degree() for g in flist1]) + if braid_data: + bm, dic, dv, d1 = braid_data + elif not flist: bm = [] dic = {} + dv = {j: j for j, f in flist1} + d1 = 0 else: - bm, dic = braid_monodromy(f, flist1) - g = fundamental_group_from_braid_mon(bm, degree=d, simplified=False, projective=projective, puiseux=puiseux) + bm, dic, dv, d1 = braid_monodromy(f, flist1, vertical=vertical) + vert_lines = list(dv) + vert_lines.sort() + for i, j in enumerate(vert_lines): + dic[d1 + i] = dv[j] + g = fundamental_group_from_braid_mon(bm, degree=d1, simplified=False, + projective=projective, + puiseux=puiseux, + vertical=vert_lines) if simplified: hom = g.simplification_isomorphism() else: @@ -1725,12 +1917,13 @@ def fundamental_group_arrangement(flist, simplified=True, projective=False, puis return (g1, {}) dic1 = {} for i in range(len(flist1)): - L = [j1 for j1 in dic.keys() if dic[j1] == i] + L = [j1 for j1 in dic if dic[j1] == i] dic1[i] = [hom(g.gen(j)) for j in L] - if not projective and f.degree(y) == f.degree(): - t = prod(hom(x) for x in g.gens()).inverse() + if not projective and infinity: + t = prod(hom(a) for a in g.gens()).inverse() dic1[len(flist1)] = [t] n = g1.ngens() - rels = [_.Tietze() for _ in g1.relations()] + rels = [rel.Tietze() for rel in g1.relations()] g1 = FreeGroup(n) / rels + dic1 = {i: list(set([g1(el.Tietze()) for el in dic1[i]])) for i in dic1} return (g1, dic1) diff --git a/src/sage/schemes/elliptic_curves/ell_finite_field.py b/src/sage/schemes/elliptic_curves/ell_finite_field.py index 6d97f3094b4..f67e15b5627 100644 --- a/src/sage/schemes/elliptic_curves/ell_finite_field.py +++ b/src/sage/schemes/elliptic_curves/ell_finite_field.py @@ -1369,22 +1369,18 @@ def set_order(self, value, *, check=True, num_checks=8): EXAMPLES: - This example illustrates basic usage. + This example illustrates basic usage:: - :: - - sage: E = EllipticCurve(GF(7), [0, 1]) # This curve has order 6 - sage: E.set_order(6) + sage: E = EllipticCurve(GF(7), [0, 1]) # This curve has order 12 + sage: E.set_order(12) sage: E.order() - 6 + 12 sage: E.order() * E.random_point() (0 : 1 : 0) We now give a more interesting case, the NIST-P521 curve. Its order is too big to calculate with Sage, and takes a long time - using other packages, so it is very useful here. - - :: + using other packages, so it is very useful here:: sage: p = 2^521 - 1 sage: prev_proof_state = proof.arithmetic() @@ -1403,7 +1399,7 @@ def set_order(self, value, *, check=True, num_checks=8): It is an error to pass a value which is not an integer in the Hasse-Weil range:: - sage: E = EllipticCurve(GF(7), [0, 1]) # This curve has order 6 + sage: E = EllipticCurve(GF(7), [0, 1]) # This curve has order 12 sage: E.set_order("hi") Traceback (most recent call last): ... @@ -1422,40 +1418,50 @@ def set_order(self, value, *, check=True, num_checks=8): ``num_checks``, the factorization of the actual order, and the actual group structure:: - sage: E = EllipticCurve(GF(7), [0, 1]) # This curve has order 6 + sage: E = EllipticCurve(GF(1009), [0, 1]) # This curve has order 948 + sage: E.set_order(947) + Traceback (most recent call last): + ... + ValueError: Value 947 illegal (multiple of random point not the identity) + + For curves over small finite fields, the order is cheap to compute, so it is computed + directly and compared:: + + sage: E = EllipticCurve(GF(7), [0, 1]) # This curve has order 12 sage: E.set_order(11) Traceback (most recent call last): ... - ValueError: Value 11 illegal (multiple of random point not the identity) + ValueError: Value 11 illegal (correct order is 12) - However, set_order can be fooled, though it's not likely in - "real cases of interest". For instance, the order can be set - to a multiple of the actual order:: + TESTS: - sage: E = EllipticCurve(GF(7), [0, 1]) # This curve has order 6 - sage: E.set_order(12) # 12 just fits in the Hasse range - sage: E.order() - 12 + The previous version's random tests are not strong enough. In particular, the following used + to work:: + + sage: E = EllipticCurve(GF(2), [0, 0, 1, 1, 1]) # This curve has order 1 + sage: E.set_order(3) + Traceback (most recent call last): + ... + ValueError: Value 3 illegal (correct order is 1) - Or, the order can be set incorrectly along with ``num_checks`` set - too small:: + :: - sage: E = EllipticCurve(GF(7), [0, 1]) # This curve has order 6 + sage: E = EllipticCurve(GF(7), [0, 1]) # This curve has order 12 sage: E.set_order(4, num_checks=0) + Traceback (most recent call last): + ... + ValueError: Value 4 illegal (correct order is 12) sage: E.order() - 4 - - The value of ``num_checks`` must be an integer. Negative values - are interpreted as zero, which means don't do any checking:: + 12 - sage: E = EllipticCurve(GF(7), [0, 1]) # This curve has order 6 - sage: E.set_order(4, num_checks=-12) - sage: E.order() - 4 + .. TODO:: Add provable correctness check by computing the abelian group structure and + comparing. AUTHORS: - - Mariah Lenox (2011-02-16) + - Mariah Lenox (2011-02-16): Initial implementation + + - Gareth Ma (2024-01-21): Fix bug for small curves """ value = Integer(value) @@ -1464,12 +1470,19 @@ def set_order(self, value, *, check=True, num_checks=8): q = self.base_field().order() a,b = Hasse_bounds(q,1) if not a <= value <= b: - raise ValueError('Value %s illegal (not an integer in the Hasse range)' % value) + raise ValueError(f"Value {value} illegal (not an integer in the Hasse range)") + + # For really small values, the random tests are too weak to detect wrong orders + # So we go with computing directly instead. + if q <= 100: + if self.order() != value: + raise ValueError(f"Value {value} illegal (correct order is {self.order()})") + # Is value*random == identity? - for i in range(num_checks): + for _ in range(num_checks): G = self.random_point() if value * G != self(0): - raise ValueError('Value %s illegal (multiple of random point not the identity)' % value) + raise ValueError(f"Value {value} illegal (multiple of random point not the identity)") # TODO: It might help some of PARI's algorithms if we # could copy this over to the .pari_curve() as well. @@ -1564,10 +1577,9 @@ def height_above_floor(self, ell, e): F = j.parent() x = polygen(F) from sage.rings.polynomial.polynomial_ring import polygens - from sage.libs.pari.convert_sage import gen_to_sage - from sage.libs.pari.all import pari - X,Y = polygens(F,['X', 'Y'],2) - phi = gen_to_sage(pari.polmodular(ell),{'x':X, 'y':Y}) + from sage.schemes.elliptic_curves.mod_poly import classical_modular_polynomial + X, Y = polygens(F, "X, Y", 2) + phi = classical_modular_polynomial(ell)(X, Y) j1 = phi([x,j]).roots(multiplicities=False) nj1 = len(j1) on_floor = self.two_torsion_rank() < 2 if ell == 2 else nj1 <= ell @@ -1836,9 +1848,10 @@ def twists(self): twists = curves_with_j_0(K) elif j == 1728: twists = curves_with_j_1728(K) - if twists: # i.e. if j=0 or 1728 + + if twists: # i.e. if j=0 or 1728 # replace the one isomorphic to self with self and move to front - for i,t in enumerate(twists): + for i, t in enumerate(twists): if self.is_isomorphic(t): twists[i] = twists[0] twists[0] = self @@ -1847,15 +1860,20 @@ def twists(self): # Now j is not 0 or 1728, and we only have a quadratic twist - if K.characteristic() == 2: # find D with trace 1 for the additive twist - D = K.one() # will work if degree is odd - while D.trace() == 0: + if K.characteristic() == 2: + # find D with trace 1 for the additive twist + D = K.one() + while not D or D.trace() == 0: D = K.random_element() - else: # find a nonsquare D + else: + # find a nonsquare D. D = K.gen() - q2 = (K.cardinality()-1)//2 + q2 = (K.cardinality() - 1) // 2 while not D or D**q2 == 1: D = K.random_element() + # assert D and D**q2 != 1 + # assert not D.is_square() + return [self, self.quadratic_twist(D)] def curves_with_j_0(K): @@ -1872,6 +1890,7 @@ def curves_with_j_0(K): For `K=\GF{q}` where `q\equiv1\mod{6}` there are six curves, the sextic twists of `y^2=x^3+1`:: + sage: # needs sage.rings.finite_rings sage: from sage.schemes.elliptic_curves.ell_finite_field import curves_with_j_0 sage: sorted(curves_with_j_0(GF(7)), key = lambda E: E.a_invariants()) [Elliptic Curve defined by y^2 = x^3 + 1 over Finite Field of size 7, @@ -1880,13 +1899,12 @@ def curves_with_j_0(K): Elliptic Curve defined by y^2 = x^3 + 4 over Finite Field of size 7, Elliptic Curve defined by y^2 = x^3 + 5 over Finite Field of size 7, Elliptic Curve defined by y^2 = x^3 + 6 over Finite Field of size 7] - sage: curves_with_j_0(GF(25)) # needs sage.rings.finite_rings - [Elliptic Curve defined by y^2 = x^3 + 1 over Finite Field in z2 of size 5^2, - Elliptic Curve defined by y^2 = x^3 + z2 over Finite Field in z2 of size 5^2, - Elliptic Curve defined by y^2 = x^3 + (z2+3) over Finite Field in z2 of size 5^2, - Elliptic Curve defined by y^2 = x^3 + (4*z2+3) over Finite Field in z2 of size 5^2, - Elliptic Curve defined by y^2 = x^3 + (2*z2+2) over Finite Field in z2 of size 5^2, - Elliptic Curve defined by y^2 = x^3 + (4*z2+1) over Finite Field in z2 of size 5^2] + sage: curves = curves_with_j_0(GF(25)); len(curves) + 6 + sage: all(not curves[i].is_isomorphic(curves[j]) for i in range(6) for j in range(i + 1, 6)) + True + sage: set(E.j_invariant() for E in curves) + {0} For `K=\GF{q}` where `q\equiv5\mod{6}` there are two curves, quadratic twists of each other by `-3`: `y^2=x^3+1` and @@ -1910,14 +1928,22 @@ def curves_with_j_0(K): q = K.cardinality() if q % 3 == 2: # Then we only have two quadratic twists (and -3 is non-square) - return [EllipticCurve(K, [0,a]) for a in [1,-27]] + return [EllipticCurve(K, [0, a]) for a in [1, -27]] # Now we have genuine sextic twists, find D generating K* mod 6th powers - q2 = (q-1)//2 - q3 = (q-1)//3 + q2 = (q - 1) // 2 + q3 = (q - 1) // 3 D = K.gen() while not D or D**q2 == 1 or D**q3 == 1: D = K.random_element() - return [EllipticCurve(K, [0,D**i]) for i in range(6)] + + curves = [EllipticCurve(K, [0, D**i]) for i in range(6)] + # TODO: issue 37110, Precompute orders of sextic twists + docs + # The idea should be to evaluate the character (D / q) or something + # Probably reference [RS2010]_ and [Connell1999]_ + # Also a necessary change is `curves_with_j_0` should take in an optional "starting curve" + # (passed from the original .twists call), because if you start twisting from that curve, + # then you can also compute the orders! + return curves def curves_with_j_1728(K): r""" @@ -1939,7 +1965,7 @@ def curves_with_j_1728(K): Elliptic Curve defined by y^2 = x^3 + 2*x over Finite Field of size 5, Elliptic Curve defined by y^2 = x^3 + 3*x over Finite Field of size 5, Elliptic Curve defined by y^2 = x^3 + 4*x over Finite Field of size 5] - sage: curves_with_j_1728(GF(49)) # needs sage.rings.finite_rings + sage: curves_with_j_1728(GF(49)) # random # needs sage.rings.finite_rings [Elliptic Curve defined by y^2 = x^3 + x over Finite Field in z2 of size 7^2, Elliptic Curve defined by y^2 = x^3 + z2*x over Finite Field in z2 of size 7^2, Elliptic Curve defined by y^2 = x^3 + (z2+4)*x over Finite Field in z2 of size 7^2, @@ -1968,11 +1994,12 @@ def curves_with_j_1728(K): if q % 4 == 3: return [EllipticCurve(K, [a,0]) for a in [1,-1]] # Now we have genuine quartic twists, find D generating K* mod 4th powers - q2 = (q-1)//2 + q2 = (q - 1) // 2 D = K.gen() while not D or D**q2 == 1: D = K.random_element() - return [EllipticCurve(K, [D**i,0]) for i in range(4)] + curves = [EllipticCurve(K, [D**i, 0]) for i in range(4)] + return curves def curves_with_j_0_char2(K): r""" @@ -2644,3 +2671,119 @@ def special_supersingular_curve(F, *, endomorphism=False): endo._degree = ZZ(q) endo.trace.set_cache(ZZ.zero()) return E, endo + +def EllipticCurve_with_order(m, *, D=None): + r""" + Return an iterator for elliptic curves over finite fields with the given order. The curves are + computed using the Complex Multiplication (CM) method. + + A `:sage:`~sage.structure.factorization.Factorization` can be passed for ``m``, in which case + the algorithm is more efficient. + + If ``D`` is specified, it is used as the discriminant. + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.ell_finite_field import EllipticCurve_with_order + sage: E = next(EllipticCurve_with_order(1234)); E # random + Elliptic Curve defined by y^2 = x^3 + 1142*x + 1209 over Finite Field of size 1237 + sage: E.order() == 1234 + True + + When ``iter`` is set, the function returns an iterator of all elliptic curves with the given + order:: + + sage: from sage.schemes.elliptic_curves.ell_finite_field import EllipticCurve_with_order + sage: it = EllipticCurve_with_order(21); it + + sage: E = next(it); E # random + Elliptic Curve defined by y^2 = x^3 + 6*x + 14 over Finite Field of size 23 + sage: E.order() == 21 + True + sage: Es = [E] + list(it); Es # random + [Elliptic Curve defined by y^2 = x^3 + 6*x + 14 over Finite Field of size 23, + Elliptic Curve defined by y^2 = x^3 + 12*x + 4 over Finite Field of size 23, + Elliptic Curve defined by y^2 = x^3 + 5*x + 2 over Finite Field of size 23, + Elliptic Curve defined by y^2 = x^3 + (z2+3) over Finite Field in z2 of size 5^2, + Elliptic Curve defined by y^2 = x^3 + (2*z2+2) over Finite Field in z2 of size 5^2, + Elliptic Curve defined by y^2 = x^3 + 7*x + 1 over Finite Field of size 19, + Elliptic Curve defined by y^2 = x^3 + 17*x + 10 over Finite Field of size 19, + Elliptic Curve defined by y^2 = x^3 + 5*x + 12 over Finite Field of size 17, + Elliptic Curve defined by y^2 = x^3 + 9*x + 1 over Finite Field of size 17, + Elliptic Curve defined by y^2 = x^3 + 7*x + 6 over Finite Field of size 17, + Elliptic Curve defined by y^2 = x^3 + z3^2*x^2 + (2*z3^2+z3) over Finite Field in z3 of size 3^3, + Elliptic Curve defined by y^2 = x^3 + (z3^2+2*z3+1)*x^2 + (2*z3^2+2*z3) over Finite Field in z3 of size 3^3, + Elliptic Curve defined by y^2 = x^3 + (z3^2+z3+1)*x^2 + (2*z3^2+1) over Finite Field in z3 of size 3^3, + Elliptic Curve defined by y^2 + (z4^2+z4+1)*y = x^3 over Finite Field in z4 of size 2^4, + Elliptic Curve defined by y^2 + (z4^2+z4)*y = x^3 over Finite Field in z4 of size 2^4, + Elliptic Curve defined by y^2 = x^3 + 18*x + 26 over Finite Field of size 29, + Elliptic Curve defined by y^2 = x^3 + 11*x + 19 over Finite Field of size 29, + Elliptic Curve defined by y^2 = x^3 + 4 over Finite Field of size 19, + Elliptic Curve defined by y^2 = x^3 + 19 over Finite Field of size 31, + Elliptic Curve defined by y^2 = x^3 + 4 over Finite Field of size 13] + sage: all(E.order() == 21 for E in Es) + True + + Indeed, we can verify that this is correct. Hasse's bounds tell us that $p \leq 50$ + (approximately), and the rest can be checked via bruteforce:: + + sage: for p in prime_range(50): + ....: for j in range(p): + ....: E0 = EllipticCurve(GF(p), j=j) + ....: for Et in E0.twists(): + ....: if Et.order() == 21: + ....: assert any(Et.is_isomorphic(E) for E in Es) + + .. NOTE:: + + The output curves are not deterministic, as :func:`EllipticCurve_finite_field.twists` is not + deterministic. However, the order of the j-invariants and base fields is fixed. + + AUTHORS: + + - Gareth Ma and Giacomo Pope (Sage Days 123): initial version + """ + from sage.arith.misc import is_prime_power, factor + from sage.quadratic_forms.binary_qf import BinaryQF + from sage.structure.factorization import Factorization + from sage.schemes.elliptic_curves.cm import hilbert_class_polynomial + + def find_q(m, m4_fac, D): + for t, _ in BinaryQF(1, 0, -D).solve_integer(m4_fac, _flag=3): + yield m + 1 - t + yield m + 1 + t + + if isinstance(m, Factorization): + m4_fac = m * factor(4) + m_val = m.value() + else: + m4_fac = factor(m * 4) + m_val = m + + if D is None: + Ds = (D for D in range(-4 * m_val, 0) if D % 4 in [0, 1]) + else: + assert D < 0 and D % 4 in [0, 1] + Ds = [D] + + seen = set() + for D in Ds: + for q in find_q(m_val, m4_fac, D): + if not is_prime_power(q): + continue + + H = hilbert_class_polynomial(D) + K = GF(q) + roots = H.roots(ring=K) + for j0, _ in roots: + E = EllipticCurve(j=j0) + for Et in E.twists(): + if any(Et.is_isomorphic(E) for E in seen): + continue + try: + # This tests whether the curve has given order + Et.set_order(m_val) + seen.add(Et) + yield Et + except ValueError: + pass diff --git a/src/sage/schemes/elliptic_curves/ell_point.py b/src/sage/schemes/elliptic_curves/ell_point.py index a1c1ff796ed..9889808b35d 100644 --- a/src/sage/schemes/elliptic_curves/ell_point.py +++ b/src/sage/schemes/elliptic_curves/ell_point.py @@ -2337,6 +2337,62 @@ def ate_pairing(self, Q, n, k, t, q=None): ret = ret**e return ret + def point_of_jacobian_of_curve(self): + r""" + Return the point in the Jacobian of the curve. + + The Jacobian is the one attached to the projective curve associated + with this elliptic curve. + + EXAMPLES:: + + sage: # needs sage.rings.finite_rings + sage: k. = GF((5,2)) + sage: E = EllipticCurve(k,[1,0]); E + Elliptic Curve defined by y^2 = x^3 + x over Finite Field in a of size 5^2 + sage: E.order() + 32 + sage: P = E([a, 2*a + 4]) + sage: P + (a : 2*a + 4 : 1) + sage: P.order() + 8 + sage: p = P.point_of_jacobian_of_curve() + sage: p + [Place (x + 4*a, y + 3*a + 1)] + sage: p.order() + 8 + sage: Q = 3*P + sage: q = Q.point_of_jacobian_of_curve() + sage: q == 3*p + True + sage: G = p.parent() + sage: G.order() + 32 + sage: G + Group of rational points of Jacobian over Finite Field in a of size 5^2 (Hess model) + sage: J = G.parent(); J + Jacobian of Projective Plane Curve over Finite Field in a of size 5^2 + defined by x^2*y + y^3 - x*z^2 (Hess model) + sage: J.curve() == E.affine_patch(2).projective_closure() + True + """ + from sage.schemes.curves.constructor import Curve + C = self.curve() + A = C.ambient_space() # projective plane + x, y, z = self + + X = Curve(C.defining_ideal().gens(), A) + X = X.affine_patch(2).projective_closure() + F = X.function_field() + P = X(z,x,y).place() + + Pinf = F.places_infinite()[0] + assert Pinf.degree() == 1, "no rational point at infinity" + + J = X.jacobian(model='hess', base_div=F.genus()*Pinf) + G = J.group(self.base_ring()) + return G(P - P.degree()*Pinf) class EllipticCurvePoint_number_field(EllipticCurvePoint_field): """ diff --git a/src/sage/schemes/elliptic_curves/jacobian.py b/src/sage/schemes/elliptic_curves/jacobian.py index 23151109453..fcd941b4276 100644 --- a/src/sage/schemes/elliptic_curves/jacobian.py +++ b/src/sage/schemes/elliptic_curves/jacobian.py @@ -99,7 +99,7 @@ def Jacobian(X, **kwds): """ try: return X.jacobian(**kwds) - except AttributeError: + except (AttributeError, TypeError): pass morphism = kwds.pop('morphism', False) diff --git a/src/sage/schemes/hyperelliptic_curves/hyperelliptic_rational_field.py b/src/sage/schemes/hyperelliptic_curves/hyperelliptic_rational_field.py index f9337b0fbc0..1e33f9b67fd 100644 --- a/src/sage/schemes/hyperelliptic_curves/hyperelliptic_rational_field.py +++ b/src/sage/schemes/hyperelliptic_curves/hyperelliptic_rational_field.py @@ -20,21 +20,48 @@ class HyperellipticCurve_rational_field(hyperelliptic_generic.HyperellipticCurve ProjectivePlaneCurve_field): def matrix_of_frobenius(self, p, prec=20): + """ + Compute the matrix of Frobenius on Monsky-Washnitzer cohomology using + the `p`-adic field with precision ``prec``. + + This function is essentially a wrapper function of + :meth:`sage.schemes.hyperelliptic_curves.monsky_washnitzer.matrix_of_frobenius_hyperelliptic`. + + INPUT: - # BUG: should get this method from HyperellipticCurve_generic - def my_chage_ring(self, R): - from .constructor import HyperellipticCurve - f, h = self._hyperelliptic_polynomials - y = self._printing_ring.gen() - x = self._printing_ring.base_ring().gen() - return HyperellipticCurve(f.change_ring(R), h, "%s,%s" % (x,y)) + - ``p`` (prime integer or pAdic ring / field) -- if ``p`` is an integer, + constructs a ``pAdicField`` with ``p`` to compute the matrix of + Frobenius, otherwise uses the supplied pAdic ring or field. + - ``prec`` (optional) -- if ``p`` is an prime integer, the `p`-adic + precision of the coefficient ring constructed. + + EXAMPLES:: + + sage: K = pAdicField(5, prec=3) + sage: R. = QQ['x'] + sage: H = HyperellipticCurve(x^5 - 2*x + 3) + sage: H.matrix_of_frobenius(K) + [ 4*5 + O(5^3) 5 + 2*5^2 + O(5^3) 2 + 3*5 + 2*5^2 + O(5^3) 2 + 5 + 5^2 + O(5^3)] + [ 3*5 + 5^2 + O(5^3) 3*5 + O(5^3) 4*5 + O(5^3) 2 + 5^2 + O(5^3)] + [ 4*5 + 4*5^2 + O(5^3) 3*5 + 2*5^2 + O(5^3) 5 + 3*5^2 + O(5^3) 2*5 + 2*5^2 + O(5^3)] + [ 5^2 + O(5^3) 5 + 4*5^2 + O(5^3) 4*5 + 3*5^2 + O(5^3) 2*5 + O(5^3)] + + You can also pass directly a prime `p` with to construct a pAdic field with precision + ``prec``:: + + sage: H.matrix_of_frobenius(3, prec=2) + [ O(3^2) 3 + O(3^2) O(3^2) O(3^2)] + [ 3 + O(3^2) O(3^2) O(3^2) 2 + 3 + O(3^2)] + [ 2*3 + O(3^2) O(3^2) O(3^2) 3^-1 + O(3)] + [ O(3^2) O(3^2) 3 + O(3^2) O(3^2)] + """ import sage.schemes.hyperelliptic_curves.monsky_washnitzer as monsky_washnitzer if isinstance(p, (sage.rings.abc.pAdicField, sage.rings.abc.pAdicRing)): K = p else: K = pAdicField(p, prec) - frob_p, forms = monsky_washnitzer.matrix_of_frobenius_hyperelliptic(my_chage_ring(self, K)) + frob_p, _ = monsky_washnitzer.matrix_of_frobenius_hyperelliptic(self.change_ring(K)) return frob_p def lseries(self, prec=53): diff --git a/src/sage/schemes/hyperelliptic_curves/jacobian_morphism.py b/src/sage/schemes/hyperelliptic_curves/jacobian_morphism.py index 72024866cb6..0a820f5bda6 100644 --- a/src/sage/schemes/hyperelliptic_curves/jacobian_morphism.py +++ b/src/sage/schemes/hyperelliptic_curves/jacobian_morphism.py @@ -489,33 +489,103 @@ def scheme(self): r""" Return the scheme this morphism maps to; or, where this divisor lives. - .. warning:: + .. WARNING:: - Although a pointset is defined over a specific field, the - scheme returned may be over a different (usually smaller) - field. The example below demonstrates this: the pointset - is determined over a number field of absolute degree 2 but - the scheme returned is defined over the rationals. + Although a pointset is defined over a specific field, the + scheme returned may be over a different (usually smaller) + field. The example below demonstrates this: the pointset + is determined over a number field of absolute degree 2 but + the scheme returned is defined over the rationals. EXAMPLES:: + sage: # needs sage.rings.number_field sage: x = QQ['x'].gen() sage: f = x^5 + x sage: H = HyperellipticCurve(f) - sage: F. = NumberField(x^2 - 2, 'a') # needs sage.rings.number_field - sage: J = H.jacobian()(F); J # needs sage.rings.number_field + sage: F. = NumberField(x^2 - 2, 'a') + sage: J = H.jacobian()(F); J Set of rational points of Jacobian of Hyperelliptic Curve over Number Field in a with defining polynomial x^2 - 2 defined by y^2 = x^5 + x - - :: - - sage: P = J(H.lift_x(F(1))) # needs sage.rings.number_field - sage: P.scheme() # needs sage.rings.number_field + sage: P = J(H.lift_x(F(1))) + sage: P.scheme() Jacobian of Hyperelliptic Curve over Rational Field defined by y^2 = x^5 + x """ return self.codomain() + def point_of_jacobian_of_curve(self): + r""" + Return the point in the Jacobian of the curve. + + The Jacobian is the one attached to the projective curve + corresponding to this hyperelliptic curve. + + EXAMPLES:: + + sage: R. = PolynomialRing(GF(11)) + sage: f = x^6 + x + 1 + sage: H = HyperellipticCurve(f) + sage: J = H.jacobian() + sage: D = J(H.lift_x(1)) + sage: D # divisor in Mumford representation + (x + 10, y + 6) + sage: jacobian_order = sum(H.frobenius_polynomial()) + sage: jacobian_order + 234 + sage: p = D.point_of_jacobian_of_curve(); p + [Place (1/x0, 1/x0^3*x1 + 1) + + Place (x0 + 10, x1 + 6)] + sage: p # Jacobian point represented by an effective divisor + [Place (1/x0, 1/x0^3*x1 + 1) + + Place (x0 + 10, x1 + 6)] + sage: p.order() + 39 + sage: 234*p == 0 + True + sage: G = p.parent() + sage: G + Group of rational points of Jacobian over Finite Field of size 11 (Hess model) + sage: J = G.parent() + sage: J + Jacobian of Projective Plane Curve over Finite Field of size 11 + defined by x0^6 + x0^5*x1 + x1^6 - x0^4*x2^2 (Hess model) + sage: C = J.curve() + sage: C + Projective Plane Curve over Finite Field of size 11 + defined by x0^6 + x0^5*x1 + x1^6 - x0^4*x2^2 + sage: C.affine_patch(0) == H.affine_patch(2) + True + """ + from sage.schemes.curves.constructor import Curve + C = self.parent().curve() + P = C.ambient_space() # projective plane + x0, x1, x2 = P.gens() + + # X is the curve positioned in the ambient space + # such that x1 = x and x2 = y + X = Curve(C.defining_ideal().gens(), P) + X = X.affine_patch(2).projective_closure() + + u0, v0 = list(self) + u1 = u0.subs(x1).homogenize(x0) + v1 = (x2 - v0.subs(x1)).homogenize(x0) + u2 = u1/x0**u1.degree() + v2 = v1/x0**v1.degree() + u = X.function(u2) + v = X.function(v2) + + F = X.function_field() + O = F.maximal_order() + D = O.ideal([u,v]).divisor() + + Pinf = F.places_infinite()[0] + assert Pinf.degree() == 1, "no rational point at infinity" + + J = X.jacobian(model='hess', base_div=F.genus()*Pinf) + G = J.group(self.base_ring()) + return G(D - D.degree()*Pinf) + def __list__(self): r""" Return a list `(a(x), b(x))` of the polynomials giving the diff --git a/src/sage/schemes/jacobians/abstract_jacobian.py b/src/sage/schemes/jacobians/abstract_jacobian.py index 9ab2ef283ae..570c5efaa76 100644 --- a/src/sage/schemes/jacobians/abstract_jacobian.py +++ b/src/sage/schemes/jacobians/abstract_jacobian.py @@ -8,19 +8,24 @@ - William Stein (2005) """ -#***************************************************************************** + +# **************************************************************************** # Copyright (C) 2005 William Stein # -# 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/ -#***************************************************************************** +# 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.categories.schemes import Jacobians from sage.categories.fields import Fields -_Fields = Fields() from sage.schemes.generic.scheme import Scheme, is_Scheme from sage.structure.richcmp import richcmp_method, richcmp +_Fields = Fields() + def is_Jacobian(J): """ @@ -33,6 +38,9 @@ def is_Jacobian(J): sage: C = Curve(x^3 + y^3 + z^3) sage: J = Jacobian(C) sage: is_Jacobian(J) + ... + DeprecationWarning: Use Jacobian_generic directly + See https://github.com/sagemath/sage/issues/35467 for details. True :: @@ -41,6 +49,8 @@ def is_Jacobian(J): sage: is_Jacobian(E) False """ + from sage.misc.superseded import deprecation + deprecation(35467, "Use Jacobian_generic directly") return isinstance(J, Jacobian_generic) @@ -56,7 +66,7 @@ def Jacobian(C): """ try: return C.jacobian() - except AttributeError: + except (AttributeError, TypeError): return Jacobian_generic(C) @@ -75,7 +85,7 @@ class Jacobian_generic(Scheme): sage: J = Jacobian(C); J Jacobian of Projective Plane Curve over Rational Field defined by x^3 + y^3 + z^3 """ - def __init__(self, C): + def __init__(self, C, category=None): """ Initialize. @@ -91,12 +101,14 @@ def __init__(self, C): Note: this is an abstract parent, so we skip element tests:: - sage: TestSuite(J).run(skip =["_test_an_element",\ - "_test_elements",\ - "_test_elements_eq_reflexive",\ - "_test_elements_eq_symmetric",\ - "_test_elements_eq_transitive",\ - "_test_elements_neq",\ + sage: TestSuite(J).run(skip =["_test_an_element", \ + "_test_zero", \ + "_test_elements", \ + "_test_elements_eq_reflexive", \ + "_test_elements_eq_symmetric", \ + "_test_elements_eq_transitive", \ + "_test_additive_associativity", \ + "_test_elements_neq", \ "_test_some_elements"]) :: @@ -128,7 +140,7 @@ def __init__(self, C): if C.dimension() != 1: raise ValueError("C (=%s) must have dimension 1." % C) self.__curve = C - Scheme.__init__(self, C.base_scheme()) + Scheme.__init__(self, C.base_scheme(), category=Jacobians(C.base_ring()).or_subcategory(category)) def __richcmp__(self, J, op): """ @@ -152,7 +164,7 @@ def __richcmp__(self, J, op): sage: J1 != J2 True """ - if not is_Jacobian(J): + if not isinstance(J, Jacobian_generic): return NotImplemented return richcmp(self.curve(), J.curve(), op) @@ -206,6 +218,8 @@ def curve(self): """ return self.__curve + base_curve = curve + def change_ring(self, R): r""" Return the Jacobian over the ring `R`. @@ -246,8 +260,10 @@ def base_extend(self, R): sage: Jac = H.jacobian(); Jac Jacobian of Hyperelliptic Curve over Rational Field defined by y^2 = x^3 - 10*x + 9 - sage: F. = QQ.extension(x^2 + 1) # needs sage.rings.number_field - sage: Jac.base_extend(F) # needs sage.rings.number_field + + sage: # needs sage.rings.number_field + sage: F. = QQ.extension(x^2 + 1) + sage: Jac.base_extend(F) Jacobian of Hyperelliptic Curve over Number Field in a with defining polynomial x^2 + 1 defined by y^2 = x^3 - 10*x + 9 """ diff --git a/src/sage/schemes/toric/chow_group.py b/src/sage/schemes/toric/chow_group.py index 9b612ece0eb..482bd5ce81b 100644 --- a/src/sage/schemes/toric/chow_group.py +++ b/src/sage/schemes/toric/chow_group.py @@ -230,12 +230,14 @@ def _repr_(self) -> str: def degree(self) -> int: r""" - The degree of the Chow cycle. + Return the degree of the Chow cycle. OUTPUT: Integer. The complex dimension of the subvariety representing - the Chow cycle. Raises a ``ValueError`` if the Chow cycle is a + the Chow cycle. + + This raises a :class:`ValueError` if the Chow cycle is a sum of mixed degree cycles. EXAMPLES:: @@ -354,7 +356,7 @@ def intersection_with_divisor(self, divisor): OUTPUT: A new :class:`ChowCycle`. If the divisor is not Cartier then - this method potentially raises a ``ValueError``, indicating + this method potentially raises a :class:`ValueError`, indicating that the divisor cannot be made transversal to the Chow cycle. EXAMPLES:: @@ -471,7 +473,7 @@ def cohomology_class(self): If the toric variety is not simplicial, that is, has worse than orbifold singularities, there is no way to associate a cohomology class of the correct degree. In this case, - :meth:`cohomology_class` raises a ``ValueError``. + :meth:`cohomology_class` raises a :class:`ValueError`. EXAMPLES:: diff --git a/src/sage/schemes/toric/divisor.py b/src/sage/schemes/toric/divisor.py index a67312516fa..3a72638ac2d 100644 --- a/src/sage/schemes/toric/divisor.py +++ b/src/sage/schemes/toric/divisor.py @@ -567,7 +567,7 @@ def m(self, cone): returned. - If there is no such vector (i.e. ``self`` is not even a - `\QQ`-Cartier divisor), a ``ValueError`` is raised. + `\QQ`-Cartier divisor), a :class:`ValueError` is raised. EXAMPLES:: @@ -773,7 +773,7 @@ def move_away_from(self, cone): .. NOTE:: A divisor that is Weil but not Cartier might be impossible - to move away. In this case, a ``ValueError`` is raised. + to move away. In this case, a :class:`ValueError` is raised. EXAMPLES:: diff --git a/src/sage/schemes/toric/divisor_class.pyx b/src/sage/schemes/toric/divisor_class.pyx index 5a92fe5b38d..a0334faee88 100644 --- a/src/sage/schemes/toric/divisor_class.pyx +++ b/src/sage/schemes/toric/divisor_class.pyx @@ -204,7 +204,7 @@ cdef class ToricRationalDivisorClass(Vector_rational_dense): cpdef _dot_product_(self, Vector right): r""" - Raise a ``TypeError`` exception. + Raise a :class:`TypeError` exception. Dot product is not defined on toric rational divisor classes. @@ -214,7 +214,7 @@ cdef class ToricRationalDivisorClass(Vector_rational_dense): OUTPUT: - - ``TypeError`` exception is raised. + A :class:`TypeError` exception is raised. TESTS:: diff --git a/src/sage/schemes/toric/ideal.py b/src/sage/schemes/toric/ideal.py index 6d611719c14..8feca9114ad 100644 --- a/src/sage/schemes/toric/ideal.py +++ b/src/sage/schemes/toric/ideal.py @@ -161,7 +161,7 @@ class ToricIdeal(MPolynomialIdeal): You may specify the ambient polynomial ring via the ``polynomial_ring`` parameter or via the ``names`` and - ``base_ring`` parameter. A ``ValueError`` is raised if you + ``base_ring`` parameter. A :class:`ValueError` is raised if you specify both. - ``algorithm`` -- string (optional). The algorithm to use. For diff --git a/src/sage/schemes/toric/morphism.py b/src/sage/schemes/toric/morphism.py index b5d11c0d808..d5b61a651f1 100644 --- a/src/sage/schemes/toric/morphism.py +++ b/src/sage/schemes/toric/morphism.py @@ -521,7 +521,8 @@ def as_fan_morphism(self): OUTPUT: A :class:`SchemeMorphism_polynomial_toric_variety`. - Raises a ``TypeError`` if the morphism cannot be written in such a way. + This raises a :class:`TypeError` if the morphism cannot be written + in such a way. EXAMPLES:: @@ -680,9 +681,10 @@ def as_polynomial_map(self): OUTPUT: - A :class:`SchemeMorphism_polynomial_toric_variety`. Raises a - ``TypeError`` if the morphism cannot be written in terms of - homogeneous polynomials. + A :class:`SchemeMorphism_polynomial_toric_variety`. + + This raises a :class:`TypeError` if the morphism cannot be + written in terms of homogeneous polynomials. The defining polynomials are not necessarily unique. There are choices if multiple ambient space ray generators project to @@ -716,7 +718,7 @@ def as_polynomial_map(self): orbit = self.domain() codomain_fan = self.codomain().fan() R = orbit.coordinate_ring() - polys = [ R.one() ] * codomain_fan.nrays() + polys = [R.one()] * codomain_fan.nrays() for i in self._defining_cone.ambient_ray_indices(): polys[i] = R.zero() ray_index_map = self._reverse_ray_map() @@ -1070,8 +1072,8 @@ def as_polynomial_map(self): OUTPUT: A :class:`SchemeMorphism_polynomial_toric_variety`. - Raises a ``TypeError`` if the morphism cannot be written in terms of - homogeneous polynomials. + This raises a :class:`TypeError` if the morphism cannot be written + in terms of homogeneous polynomials. EXAMPLES:: @@ -1104,8 +1106,8 @@ def as_polynomial_map(self): 'homogeneous polynomials') polys[i] *= x**d if phi.domain_fan().virtual_rays(): - raise NotImplementedError("polynomial representations for fans " - "with virtual rays are not implemented yet") + raise NotImplementedError("polynomial representations for fans with" + " virtual rays are not implemented yet") return SchemeMorphism_polynomial_toric_variety(self.parent(), polys) def is_bundle(self): @@ -1477,7 +1479,7 @@ def fiber_component(self, domain_cone, multiplicity=False): embedding = SchemeMorphism_fan_fiber_component_toric_variety(self, domain_cone) if multiplicity: return embedding.domain(), \ - self.fan_morphism().index(embedding.base_cone()) + self.fan_morphism().index(embedding.base_cone()) else: return embedding.domain() @@ -1609,7 +1611,7 @@ def is_union_in_fan(self, c0, c1): except ValueError: return False - m = matrix(ZZ, n, n, lambda i,j:is_union_in_fan(self,prim[i], prim[j])) + m = matrix(ZZ, n, n, lambda i, j: is_union_in_fan(self, prim[i], prim[j])) for i in range(n): m[i, i] = 0 @@ -1727,9 +1729,10 @@ def as_polynomial_map(self): OUTPUT: - A :class:`SchemeMorphism_polynomial_toric_variety`. Raises a - ``ValueError`` if the morphism cannot be written in terms of - homogeneous polynomials. + A :class:`SchemeMorphism_polynomial_toric_variety`. + + This raises a :class:`ValueError` if the morphism cannot be + written in terms of homogeneous polynomials. EXAMPLES:: @@ -1830,7 +1833,7 @@ def projection(ray): star_rays = set() for cone in fm.relative_star_generators(defining_cone): star_rays.update(cone.rays()) - projected_rays = [ projection(r) for r in cone.rays() ] + projected_rays = [projection(r) for r in cone.rays()] cones.append(Cone(projected_rays)) fiber_fan = Fan(cones) diff --git a/src/sage/schemes/toric/variety.py b/src/sage/schemes/toric/variety.py index ac2adcfdb30..6a6a8d3252a 100644 --- a/src/sage/schemes/toric/variety.py +++ b/src/sage/schemes/toric/variety.py @@ -666,7 +666,8 @@ def _check_satisfies_equations(self, coordinates): OUTPUT: - ``True`` if ``coordinates`` do define a valid point of ``self``, - otherwise a ``TypeError`` or ``ValueError`` exception is raised. + otherwise a :class:`TypeError` or :class:`ValueError` exception + is raised. TESTS:: @@ -927,7 +928,7 @@ def _validate(self, polynomials): - ``polynomials`` (the input parameter without any modifications) if ``polynomials`` do define valid polynomial functions on ``self``, - otherwise a ``ValueError`` exception is raised. + otherwise a :class:`ValueError` exception is raised. TESTS: @@ -1102,7 +1103,7 @@ def embedding_morphism(self): - :class:`scheme morphism ` if the default embedding morphism was defined for ``self``, - otherwise a ``ValueError`` exception is raised. + otherwise a :class:`ValueError` exception is raised. EXAMPLES:: @@ -1196,7 +1197,7 @@ def inject_coefficients(self, scope=None, verbose=True): while True: scope = sys._getframe(depth).f_globals if (scope["__name__"] == "__main__" - and scope.get("__package__", None) is None): + and scope.get("__package__", None) is None): break depth += 1 try: @@ -1216,7 +1217,7 @@ def dimension_singularities(self): this method will return the dimension of the largest-dimensional component. - Returns -1 if the toric variety is smooth. + This returns `-1` if the toric variety is smooth. EXAMPLES:: @@ -1227,7 +1228,7 @@ def dimension_singularities(self): sage: toric_varieties.P2().dimension_singularities() -1 """ - for codim in range(self.dimension()+1): + for codim in range(self.dimension() + 1): if any(not cone.is_smooth() for cone in self.fan(codim)): return self.dimension() - codim return -1 @@ -1291,7 +1292,7 @@ def is_homogeneous(self, polynomial): from sage.modules.free_module import FreeModule rays = fan.rays() + fan.virtual_rays() degrees_group = FreeModule(ZZ, len(rays)).quotient( - rays.matrix().columns()) + rays.matrix().columns()) self._homogeneous_degrees_group = degrees_group degrees_group = self._homogeneous_degrees_group S = self.coordinate_ring() @@ -1303,9 +1304,9 @@ def is_homogeneous(self, polynomial): monomials = polynomial.monomials() if not monomials: return True - degree = degrees_group(vector(ZZ,monomials[0].degrees())) + degree = degrees_group(vector(ZZ, monomials[0].degrees())) for monomial in monomials: - if degrees_group(vector(ZZ,monomial.degrees())) != degree: + if degrees_group(vector(ZZ, monomial.degrees())) != degree: return False return True @@ -1464,12 +1465,12 @@ def Kaehler_cone(self): from sage.schemes.toric.divisor import \ ToricRationalDivisorClassGroup_basis_lattice L = ToricRationalDivisorClassGroup_basis_lattice( - self.rational_class_group()) + self.rational_class_group()) n = fan.nrays() K = None for cone in fan: sigma = Cone([GT[i] for i in range(n) - if i not in cone.ambient_ray_indices()], + if i not in cone.ambient_ray_indices()], lattice=L) K = K.intersection(sigma) if K is not None else sigma return K @@ -1477,7 +1478,7 @@ def Kaehler_cone(self): @cached_method def Mori_cone(self): r""" - Returns the Mori cone of ``self``. + Return the Mori cone of ``self``. OUTPUT: :class:`cone `. @@ -1515,7 +1516,7 @@ def Mori_cone(self): # so far this is not the case. rays = (ray * self._fan.Gale_transform() for ray in self.Kaehler_cone().dual().rays()) - return Cone(rays, lattice=ZZ**(self._fan.nrays()+1)) + return Cone(rays, lattice=ZZ**(self._fan.nrays() + 1)) def plot(self, **options): r""" @@ -1604,7 +1605,7 @@ def Chow_group(self, base_ring=ZZ): (( 0 | 0 | 1 ), ( 0 | 1 | 0 ), ( 1 | 0 | 0 )) """ from sage.schemes.toric.chow_group import ChowGroup - return ChowGroup(self,base_ring) + return ChowGroup(self, base_ring) def cartesian_product(self, other, coordinate_names=None, coordinate_indices=None): @@ -2001,7 +2002,7 @@ def volume_class(self): A :class:`CohomologyClass`. If it exists, it is the class of the (properly normalized) volume form, that is, it is the Poincaré dual of a single point. If it does not exist, a - ``ValueError`` is raised. + :class:`ValueError` is raised. EXAMPLES:: @@ -2177,11 +2178,9 @@ def Chern_class(self, deg=None): True """ assert self.is_orbifold(), "Requires the toric variety to be an orbifold." - c = prod([ 1+self.cohomology_ring().gen(i) for i in range(self._fan.nrays()) ]) - if deg is None: - return c - else: - return c.part_of_degree(deg) + c = prod([1 + self.cohomology_ring().gen(i) + for i in range(self._fan.nrays())]) + return c if deg is None else c.part_of_degree(deg) @cached_method def Chern_character(self, deg=None): @@ -2218,12 +2217,9 @@ def Chern_character(self, deg=None): """ assert self.is_orbifold(), "Requires the toric variety to be an orbifold." n_rels = self._fan.nrays() - self.dimension() - ch = sum([ self.cohomology_ring().gen(i).exp() - for i in range(self._fan.nrays()) ]) - n_rels - if deg is None: - return ch - else: - return ch.part_of_degree(deg) + ch = sum([self.cohomology_ring().gen(i).exp() + for i in range(self._fan.nrays())]) - n_rels + return ch if deg is None else ch.part_of_degree(deg) @cached_method def Todd_class(self, deg=None): @@ -2265,17 +2261,14 @@ def Todd_class(self, deg=None): c2 = self.Chern_class(2) Td += QQ.one() / 12 * (c1**2 + c2) if dim >= 3: - Td += QQ.one() / 24 * c1*c2 + Td += QQ.one() / 24 * c1 * c2 if dim >= 4: c3 = self.Chern_class(3) c4 = self.Chern_class(4) Td += -QQ.one() / 720 * (c1**4 - 4*c1**2*c2 - 3*c2**2 - c1*c3 + c4) if dim >= 5: raise NotImplementedError('Todd class is currently only implemented up to degree 4') - if deg is None: - return Td - else: - return Td.part_of_degree(deg) + return Td if deg is None else Td.part_of_degree(deg) c = Chern_class ch = Chern_character @@ -2308,7 +2301,7 @@ def Euler_number(self): else: chi = 0 H = self.cohomology_basis() - for d in range(self.dimension()+1): + for d in range(self.dimension() + 1): chi += (-1)**d * len(H[d]) self._chi = chi return self._chi @@ -2317,7 +2310,7 @@ def Euler_number(self): def K(self): r""" - Returns the canonical divisor of the toric variety. + Return the canonical divisor of the toric variety. EXAMPLES: @@ -2331,7 +2324,7 @@ def K(self): 6 """ from sage.schemes.toric.divisor import ToricDivisor - return ToricDivisor(self, [-1]*self._fan.nrays()) + return ToricDivisor(self, [-1] * self._fan.nrays()) def divisor(self, arg, base_ring=None, check=True, reduce=True): r""" @@ -2632,8 +2625,8 @@ def _orbit_closure_projection(self, cone, x): This quotient lattice is the ambient lattice for the fan of the orbit closure corresponding to ``cone``. - If ``x`` is a cone not in the star of ``cone``, an ``IndexError`` is - raised. + If ``x`` is a cone not in the star of ``cone``, an :class:`IndexError` + is raised. See :meth:`orbit_closure` for more details. @@ -2667,7 +2660,7 @@ def _orbit_closure_projection(self, cone, x): # TODO: make the following work nicely. # if x in cone.lattice(): # return quot(x) - # assert is_Cone(x) + # assert x is ConvexRationalPolyhedralCone object # return Cone(x.rays(), lattice=quot) def orbit_closure(self, cone): @@ -2719,11 +2712,10 @@ def orbit_closure(self, cone): sage: A2.orbit_closure(A2.fan(2)[0]) 0-d affine toric variety """ - cone = self.fan().embed(cone) - cones = [] - for star_cone in cone.star_generators(): - cones.append( self._orbit_closure_projection(cone, star_cone) ) from sage.geometry.fan import discard_faces + cone = self.fan().embed(cone) + cones = [self._orbit_closure_projection(cone, star_cone) + for star_cone in cone.star_generators()] fan = Fan(discard_faces(cones), check=False) orbit_closure = ToricVariety(fan) @@ -2809,7 +2801,7 @@ def Demazure_roots(self): antiK = -self.K() fan_rays = self.fan().rays() roots = [m for m in antiK.sections() - if [ray*m for ray in fan_rays].count(-1) == 1] + if [ray * m for ray in fan_rays].count(-1) == 1] return tuple(roots) def Aut_dimension(self): @@ -2984,7 +2976,7 @@ def normalize_names(names=None, ngens=None, prefix=None, indices=None, names = list(names) except TypeError: raise TypeError( - "names must be a string or a list or tuple of them") + "names must be a string or a list or tuple of them") for name in names: if not isinstance(name, str): raise TypeError( @@ -3137,7 +3129,7 @@ def _latex_(self): """ return fr'H^\ast\left({self._variety._latex_()},{latex(QQ)}\right)' - def _element_constructor_(self,x): + def _element_constructor_(self, x): r""" Construct a :class:`CohomologyClass`. @@ -3260,7 +3252,8 @@ def gens(self): ([z], [z], [z]) """ if "_gens" not in self.__dict__: - self._gens = tuple( self.gen(i) for i in range(self._variety.fan().nrays()) ) + self._gens = tuple(self.gen(i) + for i in range(self._variety.fan().nrays())) return self._gens def gen(self, i): @@ -3469,7 +3462,7 @@ def exp(self): OUTPUT: The cohomology class `\exp(` ``self`` `)` if the constant part - vanishes, otherwise a ``ValueError`` is raised. + vanishes, otherwise a :class:`ValueError` is raised. EXAMPLES:: @@ -3484,6 +3477,6 @@ def exp(self): if not self.part_of_degree(0).is_zero(): raise ValueError('must not have a constant part') exp_x = self.parent().one() - for d in range(1, self.parent()._variety.dimension()+1): + for d in range(1, self.parent()._variety.dimension() + 1): exp_x += self**d / factorial(d) return exp_x diff --git a/src/sage/schemes/toric/weierstrass.py b/src/sage/schemes/toric/weierstrass.py index b727e586141..492f6b41126 100644 --- a/src/sage/schemes/toric/weierstrass.py +++ b/src/sage/schemes/toric/weierstrass.py @@ -176,8 +176,8 @@ def Discriminant(polynomial, variables=None): sage: Discriminant([quadratic1, quadratic2]) -1/16 """ - (f, g) = WeierstrassForm(polynomial, variables) - return 4*f**3+27*g**2 + f, g = WeierstrassForm(polynomial, variables) + return 4*f**3 + 27*g**2 ###################################################################### @@ -199,7 +199,7 @@ def j_invariant(polynomial, variables=None): * A nodal cubic: `j(-y^2 + x^2 + x^3) = \infty` * A cuspidal cubic `y^2=x^3` has undefined `j`-invariant. In this - case, a ``ValueError`` is raised. + case, a :class:`ValueError` is raised. EXAMPLES:: @@ -220,10 +220,10 @@ def j_invariant(polynomial, variables=None): ... ValueError: curve is singular and has no well-defined j-invariant """ - (f, g) = WeierstrassForm(polynomial, variables) - disc = 4*f**3+27*g**2 + f, g = WeierstrassForm(polynomial, variables) + disc = 4*f**3 + 27*g**2 if disc != 0: - return 1728 * 4*f**3/disc + return 1728 * 4 * f**3 / disc if f != 0: return Infinity raise ValueError('curve is singular and has no well-defined j-invariant') @@ -505,7 +505,7 @@ def WeierstrassForm(polynomial, variables=None, transformation=False): ###################################################################### def _check_homogeneity(polynomial, variables, weights, total_weight=None): """ - Raise ``ValueError`` if the polynomial is not weighted + Raise :class:`ValueError` if the polynomial is not weighted homogeneous. INPUT: @@ -526,7 +526,7 @@ def _check_homogeneity(polynomial, variables, weights, total_weight=None): OUTPUT: This function returns nothing. If the polynomial is not weighted - homogeneous, a ``ValueError`` is raised. + homogeneous, a :class:`ValueError` is raised. EXAMPLES:: @@ -552,8 +552,8 @@ def _check_homogeneity(polynomial, variables, weights, total_weight=None): total_weight = weight_e else: if weight_e != total_weight: - raise ValueError('the polynomial is not homogeneous with ' - 'weights '+str(weights)) + msg = f'the polynomial is not homogeneous with weights {weights}' + raise ValueError(msg) ###################################################################### @@ -602,8 +602,8 @@ def index(monomial): coeffs[i] = c*m + coeffs.pop(i, R.zero()) result = tuple(coeffs.pop(index(m), R.zero()) // m for m in monomials) if coeffs: - raise ValueError('the polynomial contains more monomials than ' - 'given: ' + str(coeffs)) + msg = f'the polynomial contains more monomials than given: {coeffs}' + raise ValueError(msg) return result @@ -623,7 +623,7 @@ def _check_polynomial_P2(cubic, variables): OUTPUT: This functions returns ``variables``, potentially guessed from the - polynomial ring. A ``ValueError`` is raised if the polynomial is + polynomial ring. A :class:`ValueError` is raised if the polynomial is not homogeneous. EXAMPLES:: @@ -757,7 +757,7 @@ def WeierstrassForm_P2(polynomial, variables=None): F = polynomial.base_ring() S = cubic.S_invariant() T = cubic.T_invariant() - return (27*S, -27/F(4)*T) + return (27 * S, -27 / F(4) * T) ###################################################################### @@ -780,7 +780,7 @@ def _check_polynomial_P1xP1(biquadric, variables): OUTPUT: This functions returns ``variables``, potentially guessed from the - polynomial ring. A ``ValueError`` is raised if the polynomial is + polynomial ring. A :class:`ValueError` is raised if the polynomial is not homogeneous. EXAMPLES:: @@ -853,7 +853,7 @@ def _partial_discriminant(quadric, y0, y1=None): monomials = (quadric.parent().one(), y0, y0**2) variables = [y0] else: - monomials = (y1**2, y0*y1, y0**2) + monomials = (y1**2, y0 * y1, y0**2) variables = [y0, y1] c = _extract_coefficients(quadric, monomials, variables) return c[1]**2 - 4*c[0]*c[2] @@ -941,7 +941,7 @@ def WeierstrassForm_P1xP1(biquadric, variables=None): Q = invariant_theory.binary_quartic(delta, x, y) g2 = Q.EisensteinD() g3 = -Q.EisensteinE() - return (-g2/4, -g3/4) + return (-g2 / 4, -g3 / 4) ###################################################################### @@ -964,7 +964,7 @@ def _check_polynomial_P2_112(polynomial, variables): OUTPUT: This functions returns ``variables``, potentially guessed from the - polynomial ring. A ``ValueError`` is raised if the polynomial is + polynomial ring. A :class:`ValueError` is raised if the polynomial is not homogeneous. EXAMPLES:: @@ -1076,4 +1076,4 @@ def WeierstrassForm_P2_112(polynomial, variables=None): Q = invariant_theory.binary_quartic(delta, x, z) g2 = Q.EisensteinD() g3 = -Q.EisensteinE() - return (-g2/4, -g3/4) + return (-g2 / 4, -g3 / 4) diff --git a/src/sage/schemes/toric/weierstrass_higher.py b/src/sage/schemes/toric/weierstrass_higher.py index 6e2cc57e59d..48d683d53a4 100644 --- a/src/sage/schemes/toric/weierstrass_higher.py +++ b/src/sage/schemes/toric/weierstrass_higher.py @@ -81,7 +81,7 @@ def _check_polynomials_P3(quadratic1, quadratic2, variables): OUTPUT: This function returns ``variables``, potentially guessed from the - polynomial ring. A ``ValueError`` is raised if the polynomial is + polynomial ring. A :class:`ValueError` is raised if the polynomial is not homogeneous. EXAMPLES:: diff --git a/src/sage/sets/disjoint_set.pxd b/src/sage/sets/disjoint_set.pxd index 1ec38f5966b..3c8351983e7 100644 --- a/src/sage/sets/disjoint_set.pxd +++ b/src/sage/sets/disjoint_set.pxd @@ -11,13 +11,25 @@ from sage.groups.perm_gps.partn_ref.data_structures cimport OrbitPartition from sage.structure.sage_object cimport SageObject +cpdef DisjointSet(arg) + cdef class DisjointSet_class(SageObject): cdef OrbitPartition *_nodes + cpdef cardinality(self) + cpdef number_of_subsets(self) cdef class DisjointSet_of_integers(DisjointSet_class): - pass + cpdef int find(self, int i) + cpdef void union(self, int i, int j) + cpdef root_to_elements_dict(self) + cpdef element_to_root_dict(self) + cpdef to_digraph(self) cdef class DisjointSet_of_hashables(DisjointSet_class): cdef list _int_to_el cdef dict _el_to_int - cdef DisjointSet_of_integers _d + cpdef find(self, e) + cpdef void union(self, e, f) + cpdef root_to_elements_dict(self) + cpdef element_to_root_dict(self) + cpdef to_digraph(self) diff --git a/src/sage/sets/disjoint_set.pyx b/src/sage/sets/disjoint_set.pyx index e8ae11fc3a1..ce44a37710a 100644 --- a/src/sage/sets/disjoint_set.pyx +++ b/src/sage/sets/disjoint_set.pyx @@ -6,7 +6,7 @@ The main entry point is :func:`DisjointSet` which chooses the appropriate type to return. For more on the data structure, see :func:`DisjointSet`. This module defines a class for mutable partitioning of a set, which -cannot be used as a key of a dictionary, vertex of a graph etc. For +cannot be used as a key of a dictionary, a vertex of a graph, etc. For immutable partitioning see :class:`SetPartition`. AUTHORS: @@ -14,6 +14,7 @@ AUTHORS: - Sébastien Labbé (2008) - Initial version. - Sébastien Labbé (2009-11-24) - Pickling support - Sébastien Labbé (2010-01) - Inclusion into sage (:issue:`6775`). +- Giorgos Mousa (2024-04-22): Optimize EXAMPLES: @@ -39,9 +40,9 @@ Disjoint set of hashables objects:: sage: d = DisjointSet('abcde') sage: d {{'a'}, {'b'}, {'c'}, {'d'}, {'e'}} - sage: d.union('a','b') - sage: d.union('b','c') - sage: d.union('c','d') + sage: d.union('a', 'b') + sage: d.union('b', 'c') + sage: d.union('c', 'd') sage: d {{'a', 'b', 'c', 'd'}, {'e'}} sage: d.find('c') @@ -58,15 +59,14 @@ Disjoint set of hashables objects:: # https://www.gnu.org/licenses/ # **************************************************************************** -from sage.rings.integer import Integer +from sage.rings.integer cimport Integer from sage.structure.sage_object cimport SageObject from cpython.object cimport PyObject_RichCompare from sage.groups.perm_gps.partn_ref.data_structures cimport * - -def DisjointSet(arg): +cpdef DisjointSet(arg): r""" - Constructs a disjoint set where each element of ``arg`` is in its + Construct a disjoint set where each element of ``arg`` is in its own set. If ``arg`` is an integer, then the disjoint set returned is made of the integers from ``0`` to ``arg - 1``. @@ -86,11 +86,11 @@ def DisjointSet(arg): INPUT: - - ``arg`` -- non negative integer or an iterable of hashable objects. + - ``arg`` -- nonnegative integer or an iterable of hashable objects EXAMPLES: - From a non-negative integer:: + From a nonnegative integer:: sage: DisjointSet(5) {{0}, {1}, {2}, {3}, {4}} @@ -101,7 +101,7 @@ def DisjointSet(arg): {{'a'}, {'b'}, {'c'}, {'d'}, {'e'}} sage: DisjointSet(range(6)) {{0}, {1}, {2}, {3}, {4}, {5}} - sage: DisjointSet(['yi',45,'cheval']) + sage: DisjointSet(['yi', 45, 'cheval']) {{'cheval'}, {'yi'}, {45}} TESTS:: @@ -113,12 +113,12 @@ def DisjointSet(arg): sage: DisjointSet([]) {} - The argument must be a non negative integer:: + The argument must be a nonnegative integer:: sage: DisjointSet(-1) Traceback (most recent call last): ... - ValueError: arg (=-1) must be a non negative integer + ValueError: arg must be a nonnegative integer (-1 given) or an iterable:: @@ -136,12 +136,11 @@ def DisjointSet(arg): """ if isinstance(arg, (Integer, int)): if arg < 0: - raise ValueError('arg (=%s) must be a non negative integer' % arg) + raise ValueError('arg must be a nonnegative integer (%s given)' % arg) return DisjointSet_of_integers(arg) else: return DisjointSet_of_hashables(arg) - cdef class DisjointSet_class(SageObject): r""" Common class and methods for :class:`DisjointSet_of_integers` and @@ -149,24 +148,28 @@ cdef class DisjointSet_class(SageObject): """ def _repr_(self): r""" - Return ``self`` as a unique str. + Return ``self`` as a unique ``str``. EXAMPLES:: sage: e = DisjointSet(5) - sage: e.union(2,4); e._repr_() + sage: e.union(2, 4) + sage: e._repr_() '{{0}, {1}, {2, 4}, {3}}' sage: e = DisjointSet(5) - sage: e.union(4,2); e._repr_() + sage: e.union(4, 2) + sage: e._repr_() '{{0}, {1}, {2, 4}, {3}}' :: sage: e = DisjointSet(range(5)) - sage: e.union(2,4); e._repr_() + sage: e.union(2, 4) + sage: e._repr_() '{{0}, {1}, {2, 4}, {3}}' sage: e = DisjointSet(range(5)) - sage: e.union(4,2); e._repr_() + sage: e.union(4, 2) + sage: e._repr_() '{{0}, {1}, {2, 4}, {3}}' """ res = [] @@ -183,10 +186,9 @@ cdef class DisjointSet_class(SageObject): EXAMPLES:: sage: d = DisjointSet(4) - sage: d.union(2,0) + sage: d.union(2, 0) sage: sorted(d) [[0, 2], [1], [3]] - sage: d = DisjointSet('abc') sage: sorted(d) [['a'], ['b'], ['c']] @@ -211,10 +213,10 @@ cdef class DisjointSet_class(SageObject): :: - sage: d.union(0,3) - sage: d.union(3,4) - sage: e.union(4,0) - sage: e.union(3,0) + sage: d.union(0, 3) + sage: d.union(3, 4) + sage: e.union(4, 0) + sage: e.union(3, 0) sage: e == d True @@ -229,12 +231,12 @@ cdef class DisjointSet_class(SageObject): sage: d = DisjointSet('abcde') sage: e = DisjointSet('abcde') - sage: d.union('a','b') - sage: d.union('b','c') - sage: e.union('c','a') + sage: d.union('a', 'b') + sage: d.union('b', 'c') + sage: e.union('c', 'a') sage: e == d False - sage: e.union('a','b') + sage: e.union('a', 'b') sage: e == d True """ @@ -246,7 +248,43 @@ cdef class DisjointSet_class(SageObject): return NotImplemented return PyObject_RichCompare(s, t, op) - def cardinality(self): + def __dealloc__(self): + r""" + Deallocate ``self`` (i.e. the ``self._nodes``). + + EXAMPLES:: + + sage: d = DisjointSet(5) + sage: del d + sage: d = DisjointSet('abc') + sage: del d + """ + OP_dealloc(self._nodes) + + def __reduce__(self): + r""" + Return a tuple of three elements: + + - The function :func:`DisjointSet` + - Arguments for the function :func:`DisjointSet` + - The actual state of ``self``. + + EXAMPLES:: + + sage: d = DisjointSet(5) + sage: d.__reduce__() + (, (5,), [0, 1, 2, 3, 4]) + + :: + + sage: d.union(2, 4) + sage: d.union(1, 3) + sage: d.__reduce__() + (, (5,), [0, 1, 2, 1, 2]) + """ + return DisjointSet, (self._nodes.degree,), self.__getstate__() + + cpdef cardinality(self): r""" Return the number of elements in ``self``, *not* the number of subsets. @@ -267,7 +305,7 @@ cdef class DisjointSet_class(SageObject): """ return self._nodes.degree - def number_of_subsets(self): + cpdef number_of_subsets(self): r""" Return the number of subsets in ``self``. @@ -297,8 +335,8 @@ cdef class DisjointSet_of_integers(DisjointSet_class): sage: d = DisjointSet(5) sage: d {{0}, {1}, {2}, {3}, {4}} - sage: d.union(2,4) - sage: d.union(0,2) + sage: d.union(2, 4) + sage: d.union(0, 2) sage: d {{0, 2, 4}, {1}, {3}} sage: d.find(2) @@ -312,18 +350,18 @@ cdef class DisjointSet_of_integers(DisjointSet_class): :: - sage: a.union(3,4) + sage: a.union(3, 4) sage: a == loads(dumps(a)) True """ def __init__(self, n): r""" - Construction of the DisjointSet where each element (integers from ``0`` + Construct the ``DisjointSet`` where each element (integers from ``0`` to ``n-1``) is in its own set. INPUT: - - ``n`` -- Non negative integer + - ``n`` -- nonnegative integer EXAMPLES:: @@ -336,40 +374,6 @@ cdef class DisjointSet_of_integers(DisjointSet_class): """ self._nodes = OP_new(n) - def __dealloc__(self): - r""" - Deallocates self, i.e. the self._nodes - - EXAMPLES:: - - sage: d = DisjointSet(5) - sage: del d - """ - OP_dealloc(self._nodes) - - def __reduce__(self): - r""" - Return a tuple of three elements: - - - The function :func:`DisjointSet` - - Arguments for the function :func:`DisjointSet` - - The actual state of ``self``. - - EXAMPLES:: - - sage: d = DisjointSet(5) - sage: d.__reduce__() - (, (5,), [0, 1, 2, 3, 4]) - - :: - - sage: d.union(2,4) - sage: d.union(1,3) - sage: d.__reduce__() - (, (5,), [0, 1, 2, 1, 2]) - """ - return DisjointSet, (self._nodes.degree,), self.__getstate__() - def __getstate__(self): r""" Return a list of the parent of each node from ``0`` to ``n-1``. @@ -379,28 +383,29 @@ cdef class DisjointSet_of_integers(DisjointSet_class): sage: d = DisjointSet(5) sage: d.__getstate__() [0, 1, 2, 3, 4] - sage: d.union(2,3) + sage: d.union(2, 3) sage: d.__getstate__() [0, 1, 2, 2, 4] - sage: d.union(3,0) + sage: d.union(3, 0) sage: d.__getstate__() [2, 1, 2, 2, 4] - Other parents are obtained when the operations are done is a - distinct order:: + Other parents are obtained when the operations are done in a + different order:: sage: d = DisjointSet(5) - sage: d.union(0,3) + sage: d.union(0, 3) sage: d.__getstate__() [0, 1, 2, 0, 4] - sage: d.union(2,0) + sage: d.union(2, 0) sage: d.__getstate__() [0, 1, 0, 0, 4] """ - l = [] + cdef Py_ssize_t card = self._nodes.degree + cdef list l = [None] * card cdef int i - for i in range(self.cardinality()): - l.append(self._nodes.parent[i]) + for i in range(card): + l[i] = self._nodes.parent[i] return l def __setstate__(self, l): @@ -415,35 +420,36 @@ cdef class DisjointSet_of_integers(DisjointSet_class): EXAMPLES:: sage: d = DisjointSet(5) - sage: d.__setstate__([0,1,2,3,4]) + sage: d.__setstate__([0, 1, 2, 3, 4]) sage: d {{0}, {1}, {2}, {3}, {4}} :: sage: d = DisjointSet(5) - sage: d.__setstate__([1,2,3,4,0]) + sage: d.__setstate__([1, 2, 3, 4, 0]) sage: d {{0, 1, 2, 3, 4}} :: sage: d = DisjointSet(5) - sage: d.__setstate__([1,1,1]) + sage: d.__setstate__([1, 1, 1]) sage: d {{0, 1, 2}, {3}, {4}} :: sage: d = DisjointSet(5) - sage: d.__setstate__([3,3,3]) + sage: d.__setstate__([3, 3, 3]) sage: d {{0, 1, 2, 3}, {4}} """ + cdef int i, parent for i, parent in enumerate(l): self.union(parent, i) - def find(self, int i): + cpdef int find(self, int i): r""" Return the representative of the set that ``i`` currently belongs to. @@ -454,41 +460,46 @@ cdef class DisjointSet_of_integers(DisjointSet_class): EXAMPLES:: sage: e = DisjointSet(5) - sage: e.union(4,2) + sage: e.union(4, 2) sage: e {{0}, {1}, {2, 4}, {3}} sage: e.find(2) 4 sage: e.find(4) 4 - sage: e.union(1,3) + sage: e.union(1, 3) sage: e {{0}, {1, 3}, {2, 4}} sage: e.find(1) 1 sage: e.find(3) 1 - sage: e.union(3,2) + sage: e.union(3, 2) sage: e {{0}, {1, 2, 3, 4}} sage: [e.find(i) for i in range(5)] [0, 1, 1, 1, 1] - sage: e.find(5) - Traceback (most recent call last): + sage: e.find(2**10) + ValueError: i must be between 0 and 4 (1024 given) ... - ValueError: i(=5) must be between 0 and 4 + + .. NOTE:: + + This method performs input checks. To avoid them you may directly + use :meth:`~sage.groups.perm_gps.partn_ref.data_structures.OP_find`. """ - card = self.cardinality() - if i < 0 or i>= card: - raise ValueError('i(=%s) must be between 0 and %s' % (i, card - 1)) + card = self._nodes.degree + if i < 0 or i >= card: + raise ValueError('i must be between 0 and %s (%s given)' % (card - 1, i)) return OP_find(self._nodes, i) - def union(self, int i, int j): + cpdef void union(self, int i, int j): r""" Combine the set of ``i`` and the set of ``j`` into one. All elements in those two sets will share the same representative - that can be gotten using find. + that can be retrieved using + :meth:`~sage.sets.disjoint_set.DisjointSet_of_integers.find`. INPUT: @@ -500,28 +511,32 @@ cdef class DisjointSet_of_integers(DisjointSet_class): sage: d = DisjointSet(5) sage: d {{0}, {1}, {2}, {3}, {4}} - sage: d.union(0,1) + sage: d.union(0, 1) sage: d {{0, 1}, {2}, {3}, {4}} - sage: d.union(2,4) + sage: d.union(2, 4) sage: d {{0, 1}, {2, 4}, {3}} - sage: d.union(1,4) + sage: d.union(1, 4) sage: d {{0, 1, 2, 4}, {3}} - sage: d.union(1,5) - Traceback (most recent call last): + sage: d.union(1, 5) + ValueError: j must be between 0 and 4 (5 given) ... - ValueError: j(=5) must be between 0 and 4 + + .. NOTE:: + + This method performs input checks. To avoid them you may directly + use :meth:`~sage.groups.perm_gps.partn_ref.data_structures.OP_join`. """ cdef int card = self._nodes.degree if i < 0 or i >= card: - raise ValueError('i(=%s) must be between 0 and %s' % (i, card - 1)) + raise ValueError('i must be between 0 and %s (%s given)' % (card - 1, i)) if j < 0 or j >= card: - raise ValueError('j(=%s) must be between 0 and %s' % (j, card - 1)) + raise ValueError('j must be between 0 and %s (%s given)' % (card - 1, j)) OP_join(self._nodes, i, j) - def root_to_elements_dict(self): + cpdef root_to_elements_dict(self): r""" Return the dictionary where the keys are the roots of ``self`` and the values are the elements in the same set as the root. @@ -531,25 +546,25 @@ cdef class DisjointSet_of_integers(DisjointSet_class): sage: d = DisjointSet(5) sage: sorted(d.root_to_elements_dict().items()) [(0, [0]), (1, [1]), (2, [2]), (3, [3]), (4, [4])] - sage: d.union(2,3) + sage: d.union(2, 3) sage: sorted(d.root_to_elements_dict().items()) [(0, [0]), (1, [1]), (2, [2, 3]), (4, [4])] - sage: d.union(3,0) + sage: d.union(3, 0) sage: sorted(d.root_to_elements_dict().items()) [(1, [1]), (2, [0, 2, 3]), (4, [4])] sage: d {{0, 2, 3}, {1}, {4}} """ - s = {} - cdef int i - for i in range(self.cardinality()): - o = self.find(i) + cdef dict s = {} + cdef int i, o + for i in range(self._nodes.degree): + o = OP_find(self._nodes, i) if o not in s: s[o] = [] s[o].append(i) return s - def element_to_root_dict(self): + cpdef element_to_root_dict(self): r""" Return the dictionary where the keys are the elements of ``self`` and the values are their representative inside a list. @@ -557,30 +572,31 @@ cdef class DisjointSet_of_integers(DisjointSet_class): EXAMPLES:: sage: d = DisjointSet(5) - sage: d.union(2,3) - sage: d.union(4,1) - sage: e = d.element_to_root_dict(); e + sage: d.union(2, 3) + sage: d.union(4, 1) + sage: e = d.element_to_root_dict() + sage: e {0: 0, 1: 4, 2: 2, 3: 2, 4: 4} sage: WordMorphism(e) # needs sage.combinat WordMorphism: 0->0, 1->4, 2->2, 3->2, 4->4 """ - d = {} + cdef dict d = {} cdef int i - for i in range(self.cardinality()): - d[i] = self.find(i) + for i in range(self._nodes.degree): + d[i] = OP_find(self._nodes, i) return d - def to_digraph(self): + cpdef to_digraph(self): r""" - Return the current digraph of ``self`` where `(a,b)` is an oriented + Return the current digraph of ``self`` where `(a, b)` is an oriented edge if `b` is the parent of `a`. EXAMPLES:: sage: d = DisjointSet(5) - sage: d.union(2,3) - sage: d.union(4,1) - sage: d.union(3,4) + sage: d.union(2, 3) + sage: d.union(4, 1) + sage: d.union(3, 4) sage: d {{0}, {1, 2, 3, 4}} sage: g = d.to_digraph(); g # needs sage.graphs @@ -591,15 +607,15 @@ cdef class DisjointSet_of_integers(DisjointSet_class): The result depends on the ordering of the union:: sage: d = DisjointSet(5) - sage: d.union(1,2) - sage: d.union(1,3) - sage: d.union(1,4) + sage: d.union(1, 2) + sage: d.union(1, 3) + sage: d.union(1, 4) sage: d {{0}, {1, 2, 3, 4}} sage: d.to_digraph().edges(sort=True) # needs sage.graphs [(0, 0, None), (1, 1, None), (2, 1, None), (3, 1, None), (4, 1, None)] """ - d = {i: [self._nodes.parent[i]] for i in range(self.cardinality())} + cdef dict d = {i: [self._nodes.parent[i]] for i in range(self._nodes.degree)} from sage.graphs.digraph import DiGraph return DiGraph(d) @@ -626,18 +642,17 @@ cdef class DisjointSet_of_hashables(DisjointSet_class): :: - sage: a.union('a','c') + sage: a.union('a', 'c') sage: a == loads(dumps(a)) True """ def __init__(self, iterable): r""" - Construction of the trivial disjoint set where each element is in its - own set. + Construct the trivial disjoint set where each element is in its own set. INPUT: - - ``iterable`` -- An iterable of hashable objects. + - ``iterable`` -- iterable of hashable objects EXAMPLES:: @@ -645,18 +660,18 @@ cdef class DisjointSet_of_hashables(DisjointSet_class): {{'a'}, {'b'}, {'c'}, {'d'}, {'e'}} sage: DisjointSet(range(6)) {{0}, {1}, {2}, {3}, {4}, {5}} - sage: DisjointSet(['yi',45,'cheval']) + sage: DisjointSet(['yi', 45, 'cheval']) {{'cheval'}, {'yi'}, {45}} sage: DisjointSet(set([0, 1, 2, 3, 4])) {{0}, {1}, {2}, {3}, {4}} """ + cdef int i self._int_to_el = [] self._el_to_int = {} for i, e in enumerate(iterable): self._int_to_el.append(e) self._el_to_int[e] = i - self._d = DisjointSet_of_integers(len(self._int_to_el)) - self._nodes = self._d._nodes + self._nodes = OP_new(len(self._int_to_el)) def __reduce__(self): r""" @@ -678,8 +693,8 @@ cdef class DisjointSet_of_hashables(DisjointSet_class): :: - sage: d.union(2,4) - sage: d.union(1,3) + sage: d.union(2, 4) + sage: d.union(1, 3) sage: d.__reduce__() (, ([0, 1, 2, 3, 4],), @@ -696,32 +711,32 @@ cdef class DisjointSet_of_hashables(DisjointSet_class): sage: d = DisjointSet('abcde') sage: d.__getstate__() [('a', 'a'), ('b', 'b'), ('c', 'c'), ('d', 'd'), ('e', 'e')] - sage: d.union('c','d') + sage: d.union('c', 'd') sage: d.__getstate__() [('a', 'a'), ('b', 'b'), ('c', 'c'), ('d', 'c'), ('e', 'e')] - sage: d.union('d','a') + sage: d.union('d', 'a') sage: d.__getstate__() [('a', 'c'), ('b', 'b'), ('c', 'c'), ('d', 'c'), ('e', 'e')] - Other parents are obtained when the operations are done is a + Other parents are obtained when the operations are done in a different order:: sage: d = DisjointSet('abcde') - sage: d.union('d','c') + sage: d.union('d', 'c') sage: d.__getstate__() [('a', 'a'), ('b', 'b'), ('c', 'd'), ('d', 'd'), ('e', 'e')] """ - gs = self._d.__getstate__() - l = [] + cdef int card = self._nodes.degree + cdef list l = [None] * card cdef int i - for i in range(self.cardinality()): - l.append(self._int_to_el[gs[i]]) + for i in range(card): + l[i] = self._int_to_el[self._nodes.parent[i]] return list(zip(self._int_to_el, l)) def __setstate__(self, l): r""" Merge the nodes ``a`` and ``b`` for each pair of nodes - ``(a,b)`` in ``l``. + ``(a, b)`` in ``l``. INPUT: @@ -730,21 +745,21 @@ cdef class DisjointSet_of_hashables(DisjointSet_class): EXAMPLES:: sage: d = DisjointSet('abcde') - sage: d.__setstate__([('a','a'),('b','b'),('c','c')]) + sage: d.__setstate__([('a', 'a'), ('b', 'b'), ('c', 'c')]) sage: d {{'a'}, {'b'}, {'c'}, {'d'}, {'e'}} :: sage: d = DisjointSet('abcde') - sage: d.__setstate__([('a','b'),('b','c'),('c','d'),('d','e')]) + sage: d.__setstate__([('a', 'b'), ('b', 'c'), ('c', 'd'), ('d', 'e')]) sage: d {{'a', 'b', 'c', 'd', 'e'}} """ for a, b in l: self.union(a, b) - def find(self, e): + cpdef find(self, e): r""" Return the representative of the set that ``e`` currently belongs to. @@ -755,7 +770,7 @@ cdef class DisjointSet_of_hashables(DisjointSet_class): EXAMPLES:: sage: e = DisjointSet(range(5)) - sage: e.union(4,2) + sage: e.union(4, 2) sage: e {{0}, {1}, {2, 4}, {3}} sage: e.find(2) @@ -769,7 +784,7 @@ cdef class DisjointSet_of_hashables(DisjointSet_class): 1 sage: e.find(3) 1 - sage: e.union(3,2) + sage: e.union(3, 2) sage: e {{0}, {1, 2, 3, 4}} sage: [e.find(i) for i in range(5)] @@ -779,16 +794,17 @@ cdef class DisjointSet_of_hashables(DisjointSet_class): ... KeyError: 5 """ - i = self._el_to_int[e] - r = self._d.find(i) + cdef int i = self._el_to_int[e] + cdef int r = OP_find(self._nodes, i) return self._int_to_el[r] - def union(self, e, f): + cpdef void union(self, e, f): r""" Combine the set of ``e`` and the set of ``f`` into one. All elements in those two sets will share the same representative - that can be gotten using find. + that can be retrieved using + :meth:`~sage.sets.disjoint_set.DisjointSet_of_hashables.find`. INPUT: @@ -800,21 +816,24 @@ cdef class DisjointSet_of_hashables(DisjointSet_class): sage: e = DisjointSet('abcde') sage: e {{'a'}, {'b'}, {'c'}, {'d'}, {'e'}} - sage: e.union('a','b') + sage: e.union('a', 'b') sage: e {{'a', 'b'}, {'c'}, {'d'}, {'e'}} - sage: e.union('c','e') + sage: e.union('c', 'e') sage: e {{'a', 'b'}, {'c', 'e'}, {'d'}} - sage: e.union('b','e') + sage: e.union('b', 'e') sage: e {{'a', 'b', 'c', 'e'}, {'d'}} + sage: e.union('a', 2**10) + KeyError: 1024 + ... """ - i = self._el_to_int[e] - j = self._el_to_int[f] - self._d.union(i, j) + cdef int i = self._el_to_int[e] + cdef int j = self._el_to_int[f] + OP_join(self._nodes, i, j) - def root_to_elements_dict(self): + cpdef root_to_elements_dict(self): r""" Return the dictionary where the keys are the roots of ``self`` and the values are the elements in the same set. @@ -822,13 +841,13 @@ cdef class DisjointSet_of_hashables(DisjointSet_class): EXAMPLES:: sage: d = DisjointSet(range(5)) - sage: d.union(2,3) - sage: d.union(4,1) + sage: d.union(2, 3) + sage: d.union(4, 1) sage: e = d.root_to_elements_dict() sage: sorted(e.items()) [(0, [0]), (2, [2, 3]), (4, [1, 4])] """ - s = {} + cdef dict s = {} for e in self._int_to_el: r = self.find(e) if r not in s: @@ -836,7 +855,7 @@ cdef class DisjointSet_of_hashables(DisjointSet_class): s[r].append(e) return s - def element_to_root_dict(self): + cpdef element_to_root_dict(self): r""" Return the dictionary where the keys are the elements of ``self`` and the values are their representative inside a list. @@ -844,33 +863,34 @@ cdef class DisjointSet_of_hashables(DisjointSet_class): EXAMPLES:: sage: d = DisjointSet(range(5)) - sage: d.union(2,3) - sage: d.union(4,1) + sage: d.union(2, 3) + sage: d.union(4, 1) sage: e = d.element_to_root_dict() sage: sorted(e.items()) [(0, 0), (1, 4), (2, 2), (3, 2), (4, 4)] sage: WordMorphism(e) # needs sage.combinat WordMorphism: 0->0, 1->4, 2->2, 3->2, 4->4 """ - d = {} + cdef dict d = {} for a in self._int_to_el: d[a] = self.find(a) return d - def to_digraph(self): + cpdef to_digraph(self): r""" - Return the current digraph of ``self`` where `(a,b)` is an oriented + Return the current digraph of ``self`` where `(a, b)` is an oriented edge if `b` is the parent of `a`. EXAMPLES:: sage: d = DisjointSet(range(5)) - sage: d.union(2,3) - sage: d.union(4,1) - sage: d.union(3,4) + sage: d.union(2, 3) + sage: d.union(4, 1) + sage: d.union(3, 4) sage: d {{0}, {1, 2, 3, 4}} - sage: g = d.to_digraph(); g # needs sage.graphs + sage: g = d.to_digraph() + sage: g # needs sage.graphs Looped digraph on 5 vertices sage: g.edges(sort=True) # needs sage.graphs [(0, 0, None), (1, 2, None), (2, 2, None), (3, 2, None), (4, 2, None)] @@ -878,16 +898,17 @@ cdef class DisjointSet_of_hashables(DisjointSet_class): The result depends on the ordering of the union:: sage: d = DisjointSet(range(5)) - sage: d.union(1,2) - sage: d.union(1,3) - sage: d.union(1,4) + sage: d.union(1, 2) + sage: d.union(1, 3) + sage: d.union(1, 4) sage: d {{0}, {1, 2, 3, 4}} sage: d.to_digraph().edges(sort=True) # needs sage.graphs [(0, 0, None), (1, 1, None), (2, 1, None), (3, 1, None), (4, 1, None)] """ - d = {} - for i in range(self.cardinality()): + cdef dict d = {} + cdef int i + for i in range(self._nodes.degree): e = self._int_to_el[i] p = self._int_to_el[self._nodes.parent[i]] d[e] = [p] diff --git a/src/sage/sets/family.pyx b/src/sage/sets/family.pyx index 36a6b0d6b1f..9579c7caab6 100644 --- a/src/sage/sets/family.pyx +++ b/src/sage/sets/family.pyx @@ -55,8 +55,6 @@ from sage.rings.integer import Integer from sage.sets.finite_enumerated_set import FiniteEnumeratedSet from sage.sets.non_negative_integers import NonNegativeIntegers -CombinatorialClass = LazyImport('sage.combinat.combinat', 'CombinatorialClass') - def Family(indices, function=None, hidden_keys=[], hidden_function=None, lazy=False, name=None): r""" @@ -971,7 +969,7 @@ class LazyFamily(AbstractFamily): category = FiniteEnumeratedSets() elif set in InfiniteEnumeratedSets(): category = InfiniteEnumeratedSets() - elif isinstance(set, (list, tuple, range, CombinatorialClass)): + elif isinstance(set, (list, tuple, range)): category = FiniteEnumeratedSets() else: category = EnumeratedSets() diff --git a/src/sage/sets/image_set.py b/src/sage/sets/image_set.py index 0472d793c19..c98fc8c534b 100644 --- a/src/sage/sets/image_set.py +++ b/src/sage/sets/image_set.py @@ -7,7 +7,7 @@ # 2012 Christian Stump # 2020-2021 Frédéric Chapoton # 2021 Travis Scrimshaw -# 2021 Matthias Koeppe +# 2021-2024 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 @@ -131,6 +131,64 @@ def map(arg): self._domain_subset = domain_subset self._is_injective = is_injective + def __eq__(self, other): + r""" + EXAMPLES:: + + sage: from sage.sets.image_set import ImageSubobject + sage: D = ZZ + sage: def f(x): + ....: return 2 * x + sage: I = ImageSubobject(f, ZZ) + sage: I == ImageSubobject(f, ZZ) + True + + This method does not take into account whether an inverse is provided, + injectivity is declared, or the category:: + + sage: def f_inv(y): + ....: return y // 2 + sage: I == ImageSubobject(f, ZZ, inverse=f_inv) + True + sage: I == ImageSubobject(f, ZZ, is_injective=True) + True + sage: I.category() + Category of enumerated subobjects of sets + sage: I == ImageSubobject(f, ZZ, category=EnumeratedSets().Infinite()) + True + """ + if not isinstance(other, ImageSubobject): + return False + return (self._map == other._map + and self._domain_subset == other._domain_subset) + + def __ne__(self, other): + r""" + EXAMPLES:: + + sage: from sage.sets.image_set import ImageSubobject + sage: D = ZZ + sage: def f(x): + ....: return 2 * x + sage: I = ImageSubobject(f, ZZ) + sage: I != ImageSubobject(f, QQ) + True + """ + return not (self == other) + + def __hash__(self): + r""" + TESTS:: + + sage: from sage.sets.image_set import ImageSubobject + sage: def f(x): + ....: return 2 * x + sage: I = ImageSubobject(f, ZZ) + sage: hash(I) == hash(ImageSubobject(f, ZZ)) + True + """ + return hash((self._map, self._domain_subset)) + def _element_constructor_(self, x): """ EXAMPLES:: diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index e14674678a0..02b23d16cf4 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -7497,14 +7497,14 @@ cdef class Expression(Expression_abc): INPUT: - - ``base_ring`` - (optional) the base ring for the polynomial + - ``base_ring`` -- (optional) the base ring for the polynomial - - ``ring`` - (optional) the parent for the polynomial + - ``ring`` -- (optional) the parent for the polynomial .. warning:: This is different from :meth:`poly` which is used to rewrite - self as a polynomial in terms of one of the variables. + ``self`` as a polynomial in terms of one of the variables. EXAMPLES:: @@ -9019,7 +9019,7 @@ cdef class Expression(Expression_abc): This also works using functional notation:: - sage: arccos(1,hold=True) + sage: arccos(1, hold=True) arccos(1) sage: arccos(1) 0 @@ -9070,7 +9070,7 @@ cdef class Expression(Expression_abc): This also works using functional notation:: - sage: arctan(1,hold=True) + sage: arctan(1, hold=True) arctan(1) sage: arctan(1) 1/4*pi @@ -9342,7 +9342,7 @@ cdef class Expression(Expression_abc): This also works using functional notation:: - sage: tanh(arcsinh(x),hold=True) + sage: tanh(arcsinh(x), hold=True) tanh(arcsinh(x)) sage: tanh(arcsinh(x)) x/sqrt(x^2 + 1) @@ -9623,7 +9623,7 @@ cdef class Expression(Expression_abc): The ``hold`` parameter also works in functional notation:: - sage: log(-1,hold=True) + sage: log(-1, hold=True) log(-1) sage: log(-1) I*pi @@ -9668,7 +9668,7 @@ cdef class Expression(Expression_abc): This also works using functional notation:: - sage: zeta(2,hold=True) + sage: zeta(2, hold=True) zeta(2) sage: zeta(2) 1/6*pi^2 @@ -9845,7 +9845,7 @@ cdef class Expression(Expression_abc): This also works using functional notation:: - sage: gamma(1/2,hold=True) + sage: gamma(1/2, hold=True) gamma(1/2) sage: gamma(1/2) sqrt(pi) @@ -9876,8 +9876,8 @@ cdef class Expression(Expression_abc): def log_gamma(self, hold=False): """ - Return the log gamma function evaluated at self. - This is the logarithm of gamma of self, where + Return the log gamma function evaluated at ``self``. + This is the logarithm of gamma of ``self``, where gamma is a complex function such that `gamma(n)` equals `factorial(n-1)`. @@ -12142,12 +12142,12 @@ cdef class Expression(Expression_abc): .. warning:: - This is *not* a numerical solver - use ``find_root`` to - solve for self == 0 numerically on an interval. + This is *not* a numerical solver - use :meth:`find_root` to + solve for ``self == 0`` numerically on an interval. INPUT: - - ``x`` - variable to view the function in terms of + - ``x`` -- variable to view the function in terms of (use default variable if not given) - ``explicit_solutions`` -- bool (default ``True``); require that @@ -12157,7 +12157,7 @@ cdef class Expression(Expression_abc): multiplicities - ``ring`` -- a ring (default ``None``): if not ``None``, convert - ``self`` to a polynomial over ring and find roots over ring + ``self`` to a polynomial over ``ring`` and find roots over ``ring`` OUTPUT: @@ -12210,9 +12210,9 @@ cdef class Expression(Expression_abc): .. NOTE:: It is possible to solve a greater variety of equations - using ``solve()`` and the keyword ``to_poly_solve``, + using :func:`solve` and the keyword ``to_poly_solve``, but only at the price of possibly encountering - approximate solutions. See documentation for f.solve + approximate solutions. See documentation for :meth:`solve` for more details. We derive the roots of a general quadratic polynomial:: @@ -12388,7 +12388,7 @@ cdef class Expression(Expression_abc): Solve a polynomial equation in the integers (a so called Diophantine). If the argument is just a polynomial expression, equate to zero. - If ``solution_dict=True`` return a list of dictionaries instead of + If ``solution_dict=True``, return a list of dictionaries instead of a list of tuples. EXAMPLES:: @@ -12505,26 +12505,26 @@ cdef class Expression(Expression_abc): def find_root(self, a, b, var=None, xtol=10e-13, rtol=2.0**-50, maxiter=100, full_output=False, imaginary_tolerance=1e-8): """ - Numerically find a root of self on the closed interval [a,b] (or - [b,a]) if possible, where self is a function in the one variable. + Numerically find a root of ``self`` on the closed interval [a,b] (or + [b,a]) if possible, where ``self`` is a function in one variable. Note: this function only works in fixed (machine) precision, it is not possible to get arbitrary precision approximations with it. INPUT: - - ``a, b`` - endpoints of the interval + - ``a``, ``b`` -- endpoints of the interval - - ``var`` - optional variable + - ``var`` -- optional variable - - ``xtol, rtol`` - the routine converges when a root - is known to lie within xtol of the value return. Should be >= 0. The + - ``xtol, rtol`` -- the routine converges when a root + is known to lie within ``xtol`` of the value return. Should be >= 0. The routine modifies this to take into account the relative precision of doubles. - - ``maxiter`` - integer; if convergence is not + - ``maxiter`` -- integer; if convergence is not achieved in maxiter iterations, an error is raised. Must be >= 0. - - ``full_output`` - bool (default: False), if True, + - ``full_output`` -- bool (default: ``False``), if ``True``, also return object that contains information about convergence. - ``imaginary_tolerance`` -- (default: ``1e-8``); if an imaginary @@ -12698,19 +12698,19 @@ cdef class Expression(Expression_abc): INPUT: - - ``a`` - real number; left endpoint of interval on which to + - ``a`` -- real number; left endpoint of interval on which to minimize - - ``b`` - real number; right endpoint of interval on which to + - ``b`` -- real number; right endpoint of interval on which to minimize - - ``var`` - variable (default: first variable in self); the + - ``var`` -- variable (default: first variable in self); the variable in self to maximize over - - ``tol`` - positive real (default: 1.48e-08); the convergence + - ``tol`` -- positive real (default: 1.48e-08); the convergence tolerance - - ``maxfun`` - natural number (default: 500); maximum function + - ``maxfun`` -- natural number (default: 500); maximum function evaluations - ``imaginary_tolerance`` -- (default: ``1e-8``); if an imaginary @@ -12842,7 +12842,7 @@ cdef class Expression(Expression_abc): def plot(self, *args, **kwds): """ - Plot a symbolic expression. All arguments are passed onto the standard plot command. + Plot a symbolic expression. All arguments are passed onto the standard :func:`plot` command. EXAMPLES: @@ -12890,7 +12890,8 @@ cdef class Expression(Expression_abc): sage: plot(2*sin, -4, 4) # needs sage.plot Traceback (most recent call last): ... - TypeError: unsupported operand parent(s) for *: 'Integer Ring' and '' + TypeError: unsupported operand parent(s) for *: + 'Integer Ring' and '' You should evaluate the function first:: @@ -12990,7 +12991,7 @@ cdef class Expression(Expression_abc): - ``b`` -- upper endpoint of the sum - - ``algorithm`` - (default: ``'maxima'``) one of + - ``algorithm`` -- (default: ``'maxima'``) one of - ``'maxima'`` -- use Maxima (the default) - ``'maple'`` -- (optional) use Maple diff --git a/src/sage/symbolic/expression_conversions.py b/src/sage/symbolic/expression_conversions.py index 3944390c746..486bb4324b1 100644 --- a/src/sage/symbolic/expression_conversions.py +++ b/src/sage/symbolic/expression_conversions.py @@ -695,32 +695,32 @@ def pyobject(self, ex, obj): EXAMPLES:: sage: 2._fricas_().domainOf() # optional - fricas - PositiveInteger() + PositiveInteger... sage: (-1/2)._fricas_().domainOf() # optional - fricas - Fraction(Integer()) + Fraction(Integer...) sage: SR(2)._fricas_().domainOf() # optional - fricas - Expression(Integer()) + Expression(Integer...) sage: (sqrt(2))._fricas_().domainOf() # optional - fricas - Expression(Integer()) + Expression(Integer...) sage: pi._fricas_().domainOf() # optional - fricas - Pi() + Pi... sage: asin(pi)._fricas_() # optional - fricas asin(%pi) sage: I._fricas_().domainOf() # optional - fricas - Complex(Integer()) + Complex(Integer...) sage: SR(I)._fricas_().domainOf() # optional - fricas - Expression(Complex(Integer())) + Expression(Complex(Integer...)) sage: ex = (I+sqrt(2)+2) sage: ex._fricas_().domainOf() # optional - fricas - Expression(Complex(Integer())) + Expression(Complex(Integer...)) sage: ex._fricas_()^2 # optional - fricas +-+ @@ -759,7 +759,7 @@ def symbol(self, ex): Variable(x) sage: (x^2)._fricas_().domainOf() # optional - fricas - Expression(Integer()) + Expression(Integer...) sage: (2*x)._fricas_().integrate(x) # optional - fricas 2 diff --git a/src/sage/symbolic/function.pyx b/src/sage/symbolic/function.pyx index f33b8eb47e6..574fa649597 100644 --- a/src/sage/symbolic/function.pyx +++ b/src/sage/symbolic/function.pyx @@ -290,7 +290,7 @@ cdef class Function(SageObject): sage: coth(5) # indirect doctest # needs sage.symbolic coth(5) - sage: coth(0.5) + sage: coth(0.5) # needs sage.rings.real_mpfr 2.16395341373865 sage: from sage.symbolic.function import BuiltinFunction sage: class Test(BuiltinFunction): @@ -307,13 +307,13 @@ cdef class Function(SageObject): ....: else: ....: return sage: test = Test() - sage: test(1.3, 4) + sage: test(1.3, 4) # needs sage.rings.real_mpfr 2.30000000000000 sage: test(pi, 4) # needs sage.symbolic test(pi, 4) sage: test(2, x) # needs sage.symbolic 3 - sage: test(2., 4) + sage: test(2., 4) # needs sage.rings.real_mpfr 3.00000000000000 sage: test(1 + 1.0*I, 2) # needs sage.symbolic 2.00000000000000 + 1.00000000000000*I @@ -329,7 +329,7 @@ cdef class Function(SageObject): ....: else: ....: return 3 sage: test2 = Test2() - sage: test2(1.3) + sage: test2(1.3) # needs sage.rings.real_mpfr 0.500000000000000 sage: test2(pi) # needs sage.symbolic 3 @@ -456,7 +456,7 @@ cdef class Function(SageObject): Precision of the result depends on the precision of the input:: - sage: arctan(RR(1)) + sage: arctan(RR(1)) # needs sage.rings.real_mpfr 0.785398163397448 sage: arctan(RealField(100)(1)) # needs sage.rings.real_mpfr 0.78539816339744830961566084582 @@ -646,7 +646,7 @@ cdef class Function(SageObject): sage: airy_ai(iv) # needs sage.rings.real_interval_field airy_ai(1.0001?) - sage: airy_ai(CIF(iv)) # needs sage.rings.complex_interval_field + sage: airy_ai(CIF(iv)) # needs sage.rings.complex_interval_field sage.rings.real_interval_field airy_ai(1.0001?) """ if isinstance(x, (float, complex)): diff --git a/src/sage/symbolic/integration/integral.py b/src/sage/symbolic/integration/integral.py index 61e1513d87f..554af7063ce 100644 --- a/src/sage/symbolic/integration/integral.py +++ b/src/sage/symbolic/integration/integral.py @@ -452,28 +452,28 @@ def integrate(expression, v=None, a=None, b=None, algorithm=None, hold=False): INPUT: - - ``v`` - a variable or variable name. This can also be a tuple of + - ``v`` -- a variable or variable name. This can also be a tuple of the variable (optional) and endpoints (i.e., ``(x,0,1)`` or ``(0,1)``). - - ``a`` - (optional) lower endpoint of definite integral + - ``a`` -- (optional) lower endpoint of definite integral - - ``b`` - (optional) upper endpoint of definite integral + - ``b`` -- (optional) upper endpoint of definite integral - - ``algorithm`` - (default: 'maxima', 'libgiac' and 'sympy') one of + - ``algorithm`` -- (default: ``'maxima'``, ``'libgiac'`` and ``'sympy'``) one of - - 'maxima' - use maxima + - ``'maxima'`` -- use maxima - - 'sympy' - use sympy (also in Sage) + - ``'sympy'`` -- use sympy (also in Sage) - - 'mathematica_free' - use http://integrals.wolfram.com/ + - ``'mathematica_free'`` -- use http://integrals.wolfram.com/ - - 'fricas' - use FriCAS (the optional fricas spkg has to be installed) + - ``'fricas'`` -- use FriCAS (the optional fricas spkg has to be installed) - - 'giac' - use Giac + - ``'giac'`` - use Giac - - 'libgiac' - use libgiac + - ``'libgiac'`` - use libgiac - To prevent automatic evaluation use the ``hold`` argument. + To prevent automatic evaluation, use the ``hold`` argument. .. SEEALSO:: @@ -596,7 +596,7 @@ def integrate(expression, v=None, a=None, b=None, algorithm=None, hold=False): sage: _ = var('x, y, z') sage: f = sin(x^2) + y^z - sage: g = mathematica(f) # optional - mathematica + sage: g = mathematica(f) # optional - mathematica sage: print(g) # optional - mathematica z 2 y + Sin[x ] @@ -605,7 +605,10 @@ def integrate(expression, v=None, a=None, b=None, algorithm=None, hold=False): x y + Sqrt[--] FresnelS[Sqrt[--] x] 2 Pi sage: print(f.integral(x)) - x*y^z + 1/16*sqrt(pi)*((I + 1)*sqrt(2)*erf((1/2*I + 1/2)*sqrt(2)*x) + (I - 1)*sqrt(2)*erf((1/2*I - 1/2)*sqrt(2)*x) - (I - 1)*sqrt(2)*erf(sqrt(-I)*x) + (I + 1)*sqrt(2)*erf((-1)^(1/4)*x)) + x*y^z + 1/16*sqrt(pi)*((I + 1)*sqrt(2)*erf((1/2*I + 1/2)*sqrt(2)*x) + + (I - 1)*sqrt(2)*erf((1/2*I - 1/2)*sqrt(2)*x) + - (I - 1)*sqrt(2)*erf(sqrt(-I)*x) + + (I + 1)*sqrt(2)*erf((-1)^(1/4)*x)) Alternatively, just use algorithm='mathematica_free' to integrate via Mathematica over the internet (does NOT require a Mathematica license!):: @@ -659,12 +662,15 @@ def integrate(expression, v=None, a=None, b=None, algorithm=None, hold=False): sage: f(x) = sqrt(x+sqrt(1+x^2))/x sage: integrate(f(x), x, algorithm="fricas") # optional - fricas - 2*sqrt(x + sqrt(x^2 + 1)) - 2*arctan(sqrt(x + sqrt(x^2 + 1))) - log(sqrt(x + sqrt(x^2 + 1)) + 1) + log(sqrt(x + sqrt(x^2 + 1)) - 1) + 2*sqrt(x + sqrt(x^2 + 1)) - 2*arctan(sqrt(x + sqrt(x^2 + 1))) + - log(sqrt(x + sqrt(x^2 + 1)) + 1) + log(sqrt(x + sqrt(x^2 + 1)) - 1) where the default integrator obtains another answer:: sage: integrate(f(x), x) # long time - 1/8*sqrt(x)*gamma(1/4)*gamma(-1/4)^2*hypergeometric((-1/4, -1/4, 1/4), (1/2, 3/4), -1/x^2)/(pi*gamma(3/4)) + 1/8*sqrt(x)*gamma(1/4)*gamma(-1/4)^2*hypergeometric((-1/4, -1/4, 1/4), + (1/2, 3/4), + -1/x^2)/(pi*gamma(3/4)) The following definite integral is not found by maxima:: @@ -713,7 +719,10 @@ def integrate(expression, v=None, a=None, b=None, algorithm=None, hold=False): sage: assume(a>0) sage: integrate(1/(x^3 *(a+b*x)^(1/3)), x) - 2/9*sqrt(3)*b^2*arctan(1/3*sqrt(3)*(2*(b*x + a)^(1/3) + a^(1/3))/a^(1/3))/a^(7/3) - 1/9*b^2*log((b*x + a)^(2/3) + (b*x + a)^(1/3)*a^(1/3) + a^(2/3))/a^(7/3) + 2/9*b^2*log((b*x + a)^(1/3) - a^(1/3))/a^(7/3) + 1/6*(4*(b*x + a)^(5/3)*b^2 - 7*(b*x + a)^(2/3)*a*b^2)/((b*x + a)^2*a^2 - 2*(b*x + a)*a^3 + a^4) + 2/9*sqrt(3)*b^2*arctan(1/3*sqrt(3)*(2*(b*x + a)^(1/3) + a^(1/3))/a^(1/3))/a^(7/3) + - 1/9*b^2*log((b*x + a)^(2/3) + (b*x + a)^(1/3)*a^(1/3) + a^(2/3))/a^(7/3) + + 2/9*b^2*log((b*x + a)^(1/3) - a^(1/3))/a^(7/3) + 1/6*(4*(b*x + a)^(5/3)*b^2 + - 7*(b*x + a)^(2/3)*a*b^2)/((b*x + a)^2*a^2 - 2*(b*x + a)*a^3 + a^4) TESTS: diff --git a/src/sage/tensor/modules/ext_pow_free_module.py b/src/sage/tensor/modules/ext_pow_free_module.py index 785b4ecc6bd..bdae1b58f22 100644 --- a/src/sage/tensor/modules/ext_pow_free_module.py +++ b/src/sage/tensor/modules/ext_pow_free_module.py @@ -173,7 +173,7 @@ class ExtPowerFreeModule(FiniteRankFreeModule_abstract): a = 3 e_0∧e_1 - e_0∧e_2 + 4 e_1∧e_2 An alternative is to construct the alternating contravariant tensor from an - empty list of components and to set the nonzero components afterwards:: + empty list of components and to set the nonzero components afterwards:: sage: a = A([], name='a') sage: a.set_comp(e)[0,1] = 3 diff --git a/src/sage/tensor/modules/finite_rank_free_module.py b/src/sage/tensor/modules/finite_rank_free_module.py index 0434ed84c53..cb0922eaa10 100644 --- a/src/sage/tensor/modules/finite_rank_free_module.py +++ b/src/sage/tensor/modules/finite_rank_free_module.py @@ -539,7 +539,9 @@ class :class:`~sage.modules.free_module.FreeModule_generic` from typing import Generator, Optional from sage.categories.fields import Fields +from sage.categories.homset import Hom from sage.categories.modules import Modules +from sage.categories.morphism import SetIsomorphism from sage.categories.rings import Rings from sage.misc.cachefunc import cached_method from sage.rings.integer import Integer @@ -755,15 +757,15 @@ def isomorphism_with_fixed_basis(self, basis=None, codomain=None): - ``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 + 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` + - a module isomorphism represented by + :class:`~sage.categories.morphism.SetIsomorphism` EXAMPLES:: @@ -800,6 +802,12 @@ def isomorphism_with_fixed_basis(self, basis=None, codomain=None): To: Free module generated by {'a', 'b', 'c'} over Rational Field sage: phi_eW(e[1] + 2 * e[2]) B['a'] + 2*B['b'] + sage: ~phi_eW + Generic morphism: + From: Free module generated by {'a', 'b', 'c'} over Rational Field + To: 3-dimensional vector space over the Rational Field + sage: (~phi_eW)(W.basis()['b']).display() + e_2 Providing a :class:`~sage.modules.free_module.Module_free_ambient` as the codomain:: @@ -814,7 +822,8 @@ def isomorphism_with_fixed_basis(self, basis=None, codomain=None): 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 + 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 @@ -822,7 +831,8 @@ def isomorphism_with_fixed_basis(self, basis=None, codomain=None): 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 + 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 @@ -830,20 +840,33 @@ def isomorphism_with_fixed_basis(self, basis=None, codomain=None): [1/2 0 0] [ 0 0 0] [ 0 0 0] + sage: ~phi_e_T11 + Generic morphism: + From: Full MatrixSpace of 3 by 3 dense matrices over Rational Field + To: Free module of type-(1,1) tensors on the + 3-dimensional vector space over the Rational Field + sage: (~phi_e_T11)(W([[0, 1/2, 1/3], + ....: [-1/2, 0, 0], + ....: [-1/3, 0, 0]])).display() + 1/2 e_1⊗e^2 + 1/3 e_1⊗e^3 - 1/2 e_2⊗e^1 - 1/3 e_3⊗e^1 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 + 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 + 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 + 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() @@ -851,7 +874,8 @@ def isomorphism_with_fixed_basis(self, basis=None, codomain=None): 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 + 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] @@ -860,15 +884,18 @@ def isomorphism_with_fixed_basis(self, basis=None, codomain=None): 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 + 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 + 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] @@ -877,25 +904,34 @@ def isomorphism_with_fixed_basis(self, basis=None, codomain=None): ] 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 + sage: phi_e_Sym2Vdual = Sym2Vdual.isomorphism_with_fixed_basis(e_Sym2Vdual, + ....: codomain=W) + sage: 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 + 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 + 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 + 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 + 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 + 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) @@ -940,13 +976,17 @@ def isomorphism_with_fixed_basis(self, basis=None, codomain=None): codomain_basis = Family(codomain.basis()) if isinstance(codomain_basis, TrivialFamily): - # assume that codomain basis keys are to be ignored - key_pairs = enumerate(basis.keys()) + # assume that codomain basis keys are to be ignored; + # need them several times, can't keep as generators + key_pairs = tuple(enumerate(basis.keys())) + basis_by_codomain_key = basis 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) + # need them several times, can't keep as generators + key_pairs = tuple(zip(codomain_basis.keys(), basis.keys())) + basis_by_codomain_key = {} + for codomain_key, domain_key in key_pairs: + basis_by_codomain_key[codomain_key] = basis[domain_key] def _isomorphism(x): r""" @@ -955,7 +995,21 @@ def _isomorphism(x): 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 _inverse(y): + r""" + Concrete isomorphism from ``codomain`` to ``self``. + """ + return self.linear_combination( + (basis_by_codomain_key[codomain_key], coefficient) + for codomain_key, coefficient in y.monomial_coefficients().items()) + + category = Modules(self.base_ring()) + homset = Hom(self, codomain, category) + isomorphism = SetIsomorphism(homset, _isomorphism) + inverse = SetIsomorphism(homset.reversed(), _inverse) + isomorphism._set_inverse(inverse) + inverse._set_inverse(isomorphism) + return isomorphism def _test_isomorphism_with_fixed_basis(self, **options): r""" @@ -3160,7 +3214,6 @@ def hom(self, codomain, matrix_rep, bases=None, name=None, for more documentation. """ - from sage.categories.homset import Hom homset = Hom(self, codomain) return homset(matrix_rep, bases=bases, name=name, latex_name=latex_name) diff --git a/src/sage/tensor/modules/free_module_automorphism.py b/src/sage/tensor/modules/free_module_automorphism.py index ebe30d6255b..b74a3798728 100644 --- a/src/sage/tensor/modules/free_module_automorphism.py +++ b/src/sage/tensor/modules/free_module_automorphism.py @@ -32,6 +32,7 @@ # https://www.gnu.org/licenses/ # ***************************************************************************** +from sage.misc.lazy_attribute import lazy_attribute from sage.structure.element import MultiplicativeGroupElement from sage.tensor.modules.free_module_tensor import FreeModuleTensor @@ -1054,6 +1055,55 @@ def matrix(self, basis1=None, basis2=None): raise NotImplementedError("basis1 != basis2 not implemented yet") return self._matrices[(basis1, basis2)] + def _some_matrix(self): + r""" + Return the matrix of ``self`` w.r.t. some basis. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(QQ, 2, name='M') + sage: e = M.basis('e') + sage: a = M.automorphism([[1,1],[0,2]], name='a') + sage: a._some_matrix() + [1 1] + [0 2] + """ + self.matrix() # forces the update of the matrix in the module's default + # basis, to make sure that the dictionary self._matrices + # is not empty + return next(iter(self._matrices.values())) + + @lazy_attribute + def characteristic_polynomial(self): + r""" + Return the characteristic polynomial of ``self``. + + :meth:`characteristic_polynomial` and :meth:`charpoly` are the same method. + + INPUT: + + - ``var`` -- string (default: ``'x'``); a variable name + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(QQ, 2, name='M') + sage: e = M.basis('e') + sage: a = M.automorphism([[1,1],[0,2]], name='a') + sage: a.matrix(e) + [1 1] + [0 2] + sage: a.characteristic_polynomial() + x^2 - 3*x + 2 + sage: a.charpoly() + x^2 - 3*x + 2 + sage: a.charpoly('T') + T^2 - 3*T + 2 + """ + return self._some_matrix().characteristic_polynomial + + charpoly = characteristic_polynomial + + @lazy_attribute def det(self): r""" Return the determinant of ``self``. @@ -1084,13 +1134,62 @@ def det(self): 1 """ - self.matrix() # forces the update of the matrix in the module's default - # basis, to make sure that the dictionary self._matrices - # is not empty - return next(iter(self._matrices.values())).det() # pick a random value in the - # dictionary self._matrices - # and compute the determinant + return self._some_matrix().det + + determinant = det + + @lazy_attribute + def fcp(self): + r""" + Return the factorization of the characteristic polynomial of ``self``. + + INPUT: + + - ``var`` -- string (default: ``'x'``); a variable name + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(QQ, 2, name='M') + sage: e = M.basis('e') + sage: a = M.automorphism([[1,1],[0,2]], name='a') + sage: a.matrix(e) + [1 1] + [0 2] + sage: a.fcp() # needs sage.libs.pari + (x - 2) * (x - 1) + sage: a.fcp('T') # needs sage.libs.pari + (T - 2) * (T - 1) + """ + return self._some_matrix().fcp + + @lazy_attribute + def minimal_polynomial(self): + r""" + Return the minimal polynomial of ``self``. + + :meth:`minimal_polynomial` and :meth:`minpoly` are the same method. + + INPUT: + + - ``var`` -- string (default: ``'x'``); a variable name + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(GF(7), 3, name='M') + sage: e = M.basis('e') + sage: a = M.automorphism([[0,1,2], [-1,0,3], [2,4,1]], name='a') + sage: a.minpoly() # needs sage.libs.pari + x^3 + 6*x^2 + 6*x + 1 + sage: a.minimal_polynomial() # needs sage.libs.pari + x^3 + 6*x^2 + 6*x + 1 + sage: a.minimal_polynomial('T') # needs sage.libs.pari + T^3 + 6*T^2 + 6*T + 1 + """ + return self._some_matrix().minimal_polynomial + + minpoly = minimal_polynomial + @lazy_attribute def trace(self): r""" Return the trace of ``self``. @@ -1117,9 +1216,4 @@ def trace(self): 2 """ - self.matrix() # forces the update of the matrix in the module's default - # basis, to make sure that the dictionary self._matrices - # is not empty - return next(iter(self._matrices.values())).trace() # pick a random value in the - # dictionary self._matrices - # and compute the trace + return self._some_matrix().trace diff --git a/src/sage/tensor/modules/free_module_basis.py b/src/sage/tensor/modules/free_module_basis.py index 9d112ef48d7..44447401b1f 100644 --- a/src/sage/tensor/modules/free_module_basis.py +++ b/src/sage/tensor/modules/free_module_basis.py @@ -48,7 +48,7 @@ class Basis_abstract(UniqueRepresentation, AbstractFamily): :class:`Mapping` subclasses, not the :meth:`keys` but the :meth:`values` are considered the elements. - EXAMPLES: + EXAMPLES:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: e = M.basis('e'); e @@ -111,8 +111,8 @@ def values(self): sage: e = M.basis('e') sage: list(e.values()) [Element e_0 of the Rank-3 free module M over the Integer Ring, - Element e_1 of the Rank-3 free module M over the Integer Ring, - Element e_2 of the Rank-3 free module M over the Integer Ring] + Element e_1 of the Rank-3 free module M over the Integer Ring, + Element e_2 of the Rank-3 free module M over the Integer Ring] """ return self._vec diff --git a/src/sage/tensor/modules/free_module_morphism.py b/src/sage/tensor/modules/free_module_morphism.py index 073cb91e60c..b9c53074e32 100644 --- a/src/sage/tensor/modules/free_module_morphism.py +++ b/src/sage/tensor/modules/free_module_morphism.py @@ -1021,6 +1021,58 @@ def is_identity(self): # End of Morphism methods # + def _modules_and_bases(self, basis1=None, basis2=None): + r""" + Return domain, codomain, domain basis, and codomain basis. + + This method implements default argument handling for methods + :meth:`matrix` and :meth:`display`. + + INPUT: + + - ``basis1`` -- (default: ``None``) basis of the domain of ``self``; if + none is provided, the domain's default basis is assumed + - ``basis2`` -- (default: ``None``) basis of the codomain of ``self``; + if none is provided, ``basis2`` is set to ``basis1`` if ``self`` is + an endomorphism, otherwise, ``basis2`` is set to the codomain's + default basis. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: N = FiniteRankFreeModule(ZZ, 2, name='N') + sage: e = M.basis('e'); f = N.basis('f') + sage: phi = M.hom(N, [[-1,2,0], [5,1,2]]) + sage: phi._modules_and_bases() + (Rank-3 free module M over the Integer Ring, + Rank-2 free module N over the Integer Ring, + Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring, + Basis (f_0,f_1) on the Rank-2 free module N over the Integer Ring) + sage: a = M.automorphism(matrix=[[-1,0,0],[0,1,2],[0,1,3]], basis=e) + sage: ep = e.new_basis(a, 'ep', latex_symbol="e'") + sage: phi._modules_and_bases(ep) + (Rank-3 free module M over the Integer Ring, + Rank-2 free module N over the Integer Ring, + Basis (ep_0,ep_1,ep_2) on the Rank-3 free module M over the Integer Ring, + Basis (f_0,f_1) on the Rank-2 free module N over the Integer Ring) + """ + fmodule1 = self.domain() + fmodule2 = self.codomain() + if basis1 is None: + basis1 = fmodule1.default_basis() + elif basis1 not in fmodule1.bases(): + raise TypeError(str(basis1) + " is not a basis on the " + + str(fmodule1) + ".") + if basis2 is None: + if self.is_endomorphism(): + basis2 = basis1 + else: + basis2 = fmodule2.default_basis() + elif basis2 not in fmodule2.bases(): + raise TypeError(str(basis2) + " is not a basis on the " + + str(fmodule2) + ".") + return fmodule1, fmodule2, basis1, basis2 + def matrix(self, basis1=None, basis2=None): r""" Return the matrix of ``self`` w.r.t to a pair of bases. @@ -1108,21 +1160,7 @@ def matrix(self, basis1=None, basis2=None): """ from sage.matrix.constructor import matrix - fmodule1 = self.domain() - fmodule2 = self.codomain() - if basis1 is None: - basis1 = fmodule1.default_basis() - elif basis1 not in fmodule1.bases(): - raise TypeError(str(basis1) + " is not a basis on the " + - str(fmodule1) + ".") - if basis2 is None: - if self.is_endomorphism(): - basis2 = basis1 - else: - basis2 = fmodule2.default_basis() - elif basis2 not in fmodule2.bases(): - raise TypeError(str(basis2) + " is not a basis on the " + - str(fmodule2) + ".") + fmodule1, fmodule2, basis1, basis2 = self._modules_and_bases(basis1, basis2) if (basis1, basis2) not in self._matrices: if self._is_identity: # The identity endomorphism @@ -1247,3 +1285,73 @@ def _common_bases(self, other): except ValueError: continue return resu + + def display(self, basis1=None, basis2=None): + r""" + Display ``self`` as a matrix w.r.t to a pair of bases. + + If the matrix is not known already, it is computed from the matrix in + another pair of bases by means of the change-of-basis formula. + + INPUT: + + - ``basis1`` -- (default: ``None``) basis of the domain of ``self``; if + none is provided, the domain's default basis is assumed + - ``basis2`` -- (default: ``None``) basis of the codomain of ``self``; + if none is provided, ``basis2`` is set to ``basis1`` if ``self`` is + an endomorphism, otherwise, ``basis2`` is set to the codomain's + default basis. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: N = FiniteRankFreeModule(ZZ, 2, name='N') + sage: e = M.basis('e'); f = N.basis('f') + sage: phi = M.hom(N, [[-1,2,0], [5,1,2]]) + sage: phi.display() # default bases + e_0 e_1 e_2 + f_0⎛ -1 2 0⎞ + f_1⎝ 5 1 2⎠ + sage: phi.display(e, f) # given bases + e_0 e_1 e_2 + f_0⎛ -1 2 0⎞ + f_1⎝ 5 1 2⎠ + + Matrix of an endomorphism:: + + sage: a = M.automorphism(matrix=[[-1,0,0],[0,1,2],[0,1,3]], basis=e) + sage: ep = e.new_basis(a, 'ep', latex_symbol="e'") + sage: phi = M.endomorphism([[1,2,3], [4,5,6], [7,8,9]], basis=ep) + sage: phi.display(ep) + ep_0 ep_1 ep_2 + ep_0⎛ 1 2 3⎞ + ep_1⎜ 4 5 6⎟ + ep_2⎝ 7 8 9⎠ + sage: phi.display(ep, ep) # same as above + ep_0 ep_1 ep_2 + ep_0⎛ 1 2 3⎞ + ep_1⎜ 4 5 6⎟ + ep_2⎝ 7 8 9⎠ + sage: phi.display() # matrix w.r.t to the module's default basis + e_0 e_1 e_2 + e_0⎛ 1 -3 1⎞ + e_1⎜-18 39 -18⎟ + e_2⎝-25 54 -25⎠ + """ + from sage.misc.latex import latex + from .format_utilities import is_atomic, FormattedExpansion + fmodule1, fmodule2, basis1, basis2 = self._modules_and_bases(basis1, basis2) + matrix = self.matrix(basis1, basis2) + if all(element._name for element in basis1): + basis1_names = [element._name for element in basis1] + else: + basis1_names = None + if all(element._name for element in basis2): + basis2_names = [element._name for element in basis2] + else: + basis2_names = None + resu_txt = matrix.str(unicode=True, + top_border=basis1_names, + left_border=basis2_names) + resu_latex = latex(matrix) + return FormattedExpansion(resu_txt, resu_latex) diff --git a/src/sage/tensor/modules/free_module_tensor.py b/src/sage/tensor/modules/free_module_tensor.py index 2dd54a64c1a..5cd35997b0a 100644 --- a/src/sage/tensor/modules/free_module_tensor.py +++ b/src/sage/tensor/modules/free_module_tensor.py @@ -825,11 +825,11 @@ def display_comp(self, basis=None, format_spec=None, symbol=None, The LaTeX output for the notebook:: sage: latex(t.display_comp()) - \begin{array}{lcl} T_{\phantom{\, 1}\phantom{\, 2}\,1}^{\,1\,2\phantom{\, 1}} - & = & \frac{2}{3} \\ T_{\phantom{\, 1}\phantom{\, 2}\,2}^{\,1\,2\phantom{\, 2}} - & = & -\frac{1}{4} \\ T_{\phantom{\, 2}\phantom{\, 1}\,1}^{\,2\,1\phantom{\, 1}} - & = & \frac{2}{3} \\ T_{\phantom{\, 2}\phantom{\, 1}\,2}^{\,2\,1\phantom{\, 2}} - & = & -\frac{1}{4} \\ T_{\phantom{\, 2}\phantom{\, 2}\,2}^{\,2\,2\phantom{\, 2}} + \begin{array}{lcl} {T}_{\phantom{\, 1}\phantom{\, 2}\,1}^{\,1\,2\phantom{\, 1}} + & = & \frac{2}{3} \\ {T}_{\phantom{\, 1}\phantom{\, 2}\,2}^{\,1\,2\phantom{\, 2}} + & = & -\frac{1}{4} \\ {T}_{\phantom{\, 2}\phantom{\, 1}\,1}^{\,2\,1\phantom{\, 1}} + & = & \frac{2}{3} \\ {T}_{\phantom{\, 2}\phantom{\, 1}\,2}^{\,2\,1\phantom{\, 2}} + & = & -\frac{1}{4} \\ {T}_{\phantom{\, 2}\phantom{\, 2}\,2}^{\,2\,2\phantom{\, 2}} & = & 3 \end{array} By default, only the non-vanishing components are displayed; to see @@ -887,7 +887,7 @@ def display_comp(self, basis=None, format_spec=None, symbol=None, symbol = 'X' if latex_symbol is None: if self._latex_name is not None: - latex_symbol = self._latex_name + latex_symbol = r'{' + self._latex_name + r'}' else: latex_symbol = 'X' index_positions = self._tensor_type[0]*'u' + self._tensor_type[1]*'d' 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 5ac636c433a..86ab2edd193 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 @@ -97,7 +97,7 @@ Sage example in ./polynomes.tex, line 551:: sage: A = QQ['x'] - sage: A.is_ring() and A.is_noetherian() + sage: A in Rings() and A.is_noetherian() True Sage example in ./polynomes.tex, line 559:: diff --git a/src/sage/topology/cubical_complex.py b/src/sage/topology/cubical_complex.py index 4e8e80a564b..ebabf526b15 100644 --- a/src/sage/topology/cubical_complex.py +++ b/src/sage/topology/cubical_complex.py @@ -1689,30 +1689,6 @@ def algebraic_topological_model(self, base_ring=None): base_ring = QQ return algebraic_topological_model(self, base_ring) - def _chomp_repr_(self): - r""" - String representation of self suitable for use by the CHomP - program. This lists each maximal cube on its own line. - - This function is deprecated. - - EXAMPLES:: - - sage: C = cubical_complexes.Cube(0).product(cubical_complexes.Cube(2)) - sage: C.maximal_cells() - {[0,0] x [0,1] x [0,1]} - sage: C._chomp_repr_() - doctest:...: DeprecationWarning: the CHomP interface is deprecated; hence so is this function - See https://github.com/sagemath/sage/issues/33777 for details. - '[0,0] x [0,1] x [0,1]\n' - """ - deprecation(33777, "the CHomP interface is deprecated; hence so is this function") - s = "" - for c in self.maximal_cells(): - s += str(c) - s += "\n" - return s - def _simplicial_(self): r""" Simplicial complex constructed from self. diff --git a/src/sage/topology/simplicial_complex.py b/src/sage/topology/simplicial_complex.py index aba3289607e..9c1e8e8f7f1 100644 --- a/src/sage/topology/simplicial_complex.py +++ b/src/sage/topology/simplicial_complex.py @@ -159,22 +159,24 @@ # cohomology: compute cup products (and Massey products?) from copy import copy -from sage.misc.lazy_import import lazy_import -from sage.misc.cachefunc import cached_method +from itertools import combinations, chain +from functools import total_ordering + from .cell_complex import GenericCellComplex -from sage.structure.sage_object import SageObject -from sage.structure.parent import Parent +from sage.categories.fields import Fields +from sage.misc.cachefunc import cached_method +from sage.misc.latex import latex +from sage.misc.lazy_import import lazy_import +from sage.misc.superseded import deprecation from sage.rings.integer import Integer -from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing -from sage.rings.polynomial.polynomial_ring import polygens -from sage.sets.set import Set from sage.rings.integer_ring import ZZ +from sage.rings.polynomial.polynomial_ring import polygens +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.rational_field import QQ +from sage.sets.set import Set from sage.structure.category_object import normalize_names -from sage.misc.latex import latex -from sage.misc.superseded import deprecation -from functools import total_ordering -from itertools import combinations, chain +from sage.structure.parent import Parent +from sage.structure.sage_object import SageObject lazy_import('sage.categories.simplicial_complexes', 'SimplicialComplexes') lazy_import('sage.matrix.constructor', 'matrix') @@ -321,10 +323,7 @@ def rename_vertex(n, keep, left=True): try: return lookup[n] except KeyError: - if left: - return "L" + str(n) - else: - return "R" + str(n) + return ("L" + str(n)) if left else ("R" + str(n)) @total_ordering @@ -644,7 +643,7 @@ def product(self, other, rename_vertices=True): answer = [] for x in lattice_paths(self.tuple(), other.tuple()): - new = tuple(["L" + str(v) + "R" + str(w) for (v, w) in x]) + new = tuple(["L" + str(v) + "R" + str(w) for v, w in x]) answer.append(Simplex(new)) return answer @@ -2435,10 +2434,10 @@ def _homology_(self, dim=None, base_ring=ZZ, subcomplex=None, if H_with_gens: chains = self.n_chains(i, base_ring=base_ring) new_H = [] - for (H, gen) in H_with_gens: + for H, gen in H_with_gens: v = gen.vector(i) new_gen = chains.zero() - for (coeff, chaine) in zip(v, chains.gens()): + for coeff, chaine in zip(v, chains.gens()): new_gen += coeff * chaine new_H.append((H, new_gen)) answer[i] = new_H @@ -2450,7 +2449,7 @@ def _homology_(self, dim=None, base_ring=ZZ, subcomplex=None, # Fix non-reduced answer. if subcomplex is None and not reduced and 0 in dim: try: - if base_ring.is_field(): + if base_ring in Fields(): rank = answer[0].dimension() else: rank = len(answer[0].invariants()) @@ -3076,7 +3075,7 @@ def is_cohen_macaulay(self, base_ring=QQ, ncpus=0): def all_homologies_vanish(F): S = self.link(F) H = S.homology(base_ring=base_ring) - if base_ring.is_field(): + if base_ring in Fields(): return all(H[j].dimension() == 0 for j in range(S.dimension())) else: return not any(H[j].invariants() for j in range(S.dimension())) @@ -4497,42 +4496,6 @@ def _translation_from_numeric(self): d = self._vertex_to_index return {idx: v for v, idx in d.items()} - def _chomp_repr_(self): - r""" - String representation of ``self`` suitable for use by the CHomP - program. This lists each facet on its own line, and makes - sure vertices are listed as numbers. - - This function is deprecated. - - EXAMPLES:: - - sage: S = SimplicialComplex([(0,1,2), (2,3,5)]) - sage: print(S._chomp_repr_()) - doctest:...: DeprecationWarning: the CHomP interface is deprecated; hence so is this function - See https://github.com/sagemath/sage/issues/33777 for details. - (2, 3, 5) - (0, 1, 2) - - A simplicial complex whose vertices are tuples, not integers:: - - sage: S = SimplicialComplex([[(0,1), (1,2), (3,4)]]) - sage: S._chomp_repr_() - '(0, 1, 2)\n' - """ - deprecation(33777, "the CHomP interface is deprecated; hence so is this function") - s = "" - numeric = self._is_numeric() - if not numeric: - d = self._translation_to_numeric() - for f in self.facets(): - if numeric: - s += str(f) - else: - s += '(' + ', '.join(str(d[a]) for a in f) + ')' - s += '\n' - return s - # this function overrides the standard one for GenericCellComplex, # because it lists the maximal faces, not the total number of faces. def _repr_(self): @@ -4998,7 +4961,7 @@ def bigraded_betti_number(self, a, b, base_ring=ZZ, verbose=False): """ if b % 2: return ZZ.zero() - if a == 0 and b == 0: + if a == 0 == b: return ZZ.one() if base_ring in self._bbn and not verbose: if base_ring in self._bbn_all_computed: @@ -5049,7 +5012,7 @@ def is_golod(self) -> bool: sage: Y.is_golod() True """ - H = [a+b for (a, b) in self.bigraded_betti_numbers()] + H = [a+b for a, b in self.bigraded_betti_numbers()] if 0 in H: H.remove(0) @@ -5121,6 +5084,7 @@ def moment_angle_complex(self): from .moment_angle_complex import MomentAngleComplex return MomentAngleComplex(self) + # Miscellaneous utility functions. # The following two functions can be used to generate the facets for diff --git a/src/sage/version.py b/src/sage/version.py index d7de8c78611..fd7b8305d1d 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 = '10.4.beta2' -date = '2024-04-08' -banner = 'SageMath version 10.4.beta2, Release Date: 2024-04-08' +version = '10.4.beta5' +date = '2024-05-02' +banner = 'SageMath version 10.4.beta5, Release Date: 2024-05-02' diff --git a/src/sage_setup/clean.py b/src/sage_setup/clean.py index e9c81c9ed1a..1b00f211d75 100644 --- a/src/sage_setup/clean.py +++ b/src/sage_setup/clean.py @@ -80,6 +80,7 @@ def _find_stale_files(site_packages, python_packages, python_modules, ext_module course. We check that when the doctest is being run, that is, after installation, there are no stale files:: + sage: # needs SAGE_SRC sage: from sage.env import SAGE_SRC, SAGE_LIB, SAGE_ROOT sage: from sage_setup.find import _cythonized_dir sage: cythonized_dir = _cythonized_dir(SAGE_SRC) @@ -98,6 +99,7 @@ def _find_stale_files(site_packages, python_packages, python_modules, ext_module TODO: Also check extension modules:: + sage: # needs SAGE_SRC sage: stale_iter = _find_stale_files(SAGE_LIB, python_packages, python_modules, [], extra_files) sage: from importlib.machinery import EXTENSION_SUFFIXES sage: skip_extensions = tuple(EXTENSION_SUFFIXES) diff --git a/src/sage_setup/find.py b/src/sage_setup/find.py index 61d91abc2eb..69bad9a3ebf 100644 --- a/src/sage_setup/find.py +++ b/src/sage_setup/find.py @@ -1,3 +1,4 @@ +# sage.doctest: needs SAGE_SRC """ Recursive Directory Contents """ diff --git a/src/setup.cfg.m4 b/src/setup.cfg.m4 index e2a3330518d..c06001fd105 100644 --- a/src/setup.cfg.m4 +++ b/src/setup.cfg.m4 @@ -11,7 +11,6 @@ include(`setup_cfg_metadata.m4')dnl' [options] python_requires = >=3.9, <3.13 install_requires = - SPKG_INSTALL_REQUIRES_sage_conf SPKG_INSTALL_REQUIRES_six dnl From build/pkgs/sagelib/dependencies SPKG_INSTALL_REQUIRES_conway_polynomials diff --git a/src/tox.ini b/src/tox.ini index aab671441a9..ca69ed6c8bf 100644 --- a/src/tox.ini +++ b/src/tox.ini @@ -26,8 +26,10 @@ envlist = doctest, coverage, startuptime, pycodestyle-minimal, relint, codespell skipsdist = true requires = - # For the renamed "allowlist_externals" keyword + # For the renamed "allowlist_externals" keyword, need >= 3.18 + # Because of https://github.com/tox-dev/tox/issues/3238, need <4.14.1 tox>=3.18 + tox<4.14.1 [sagedirect] # Base for tox environments that bypass the virtual environment set up by tox, @@ -86,6 +88,24 @@ commands_post = {env:SAGE} --python -m coverage report {env:SAGE} --python -m coverage html -d "{envdir}" +[testenv:coverage.py-xml] +# https://coverage.readthedocs.io/en/latest/index.html +description = + run the Sage doctester with Coverage.py, generate XML report +## This toxenv bypasses the virtual environment set up by tox. +passenv = {[sagedirect]passenv} +setenv = {[sagedirect]setenv} +envdir = {[sagedirect]envdir} +allowlist_externals = {[sagedirect]allowlist_externals} +commands_pre = + {env:SAGE} -pip install -U coverage +commands = + {env:SAGE} --python -m coverage run "{toxinidir}/../venv/bin/sage-runtests" -p 0 {posargs:--all} +commands_post = + {env:SAGE} --python -m coverage combine + {env:SAGE} --python -m coverage report + {env:SAGE} --python -m coverage xml -o "{envdir}/coverage.xml" + [testenv:coverage] description = give information about doctest coverage of files diff --git a/tox.ini b/tox.ini index df7ec10f674..b06f4529afc 100644 --- a/tox.ini +++ b/tox.ini @@ -129,8 +129,10 @@ envlist = # pycodestyle requires = - # For repaired numerical factors in tox 4: + # For repaired numerical factors in tox 4, need >= 4.2.7. + # Because of https://github.com/tox-dev/tox/issues/3238, need <4.14.1 tox>=4.2.7 + tox<4.14.1 skipsdist = true @@ -185,6 +187,7 @@ setenv = # What system packages should be installed. Default: All standard packages with spkg-configure. SAGE_PACKAGE_LIST_ARGS=--has-file=spkg-configure.m4 :standard: recommended: EXTRA_SAGE_PACKAGES_3=_recommended $(head -n 1 build/pkgs/_recommended/dependencies) + incremental: EXTRA_SAGE_PACKAGES_4=git develop: EXTRA_SAGE_PACKAGES_4=_develop $(head -n 1 build/pkgs/_develop/dependencies) minimal: SAGE_PACKAGE_LIST_ARGS=_prereq maximal: SAGE_PACKAGE_LIST_ARGS=:standard: :optional: @@ -205,8 +208,9 @@ setenv = docker: BASE_TAG=latest # # https://hub.docker.com/_/ubuntu?tab=description - # as of 2023-05, latest=jammy=22.04, rolling=lunar=23.04, devel=mantic=23.10 + # as of 2024-02, latest=jammy=22.04, rolling=mantic=23.10, devel=noble=24.04 # ubuntu-focal does not have libgap-dev + # ubuntu-noble does not have libbrial-dev # ubuntu: SYSTEM=debian ubuntu: BASE_IMAGE=ubuntu @@ -226,6 +230,8 @@ setenv = ubuntu-jammy: BASE_TAG=jammy ubuntu-lunar: BASE_TAG=lunar ubuntu-mantic: BASE_TAG=mantic + ubuntu-noble: BASE_TAG=noble + ubuntu-noble: IGNORE_MISSING_SYSTEM_PACKAGES=yes # # https://hub.docker.com/_/debian # debian-buster does not have libfreetype-dev (it only has libfreetype6-dev) @@ -264,6 +270,7 @@ setenv = linuxmint-21: BASE_IMAGE=linuxmintd/mint21 linuxmint-21.1: BASE_IMAGE=linuxmintd/mint21.1 linuxmint-21.2: BASE_IMAGE=linuxmintd/mint21.2 + linuxmint-21.3: BASE_IMAGE=linuxmintd/mint21.3 # # https://hub.docker.com/_/fedora # as of 2024-01, latest=39, rawhide=40 @@ -495,8 +502,9 @@ setenv = # 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 -E "s/(docker-|-incremental|-sitepackages)//g")-{env:FROM_DOCKER_TARGET:with-targets}:{env:FROM_DOCKER_TAG:dev} - docker-incremental: SKIP_SYSTEM_PKG_INSTALL=yes - docker-incremental-sitepackages: SKIP_SYSTEM_PKG_INSTALL=no + # Can SKIP_SYSTEM_PKG_INSTALL if the base image already has git + docker-incremental-{develop,recommended,maximal}: SKIP_SYSTEM_PKG_INSTALL=yes + docker-incremental-sitepackages: SKIP_SYSTEM_PKG_INSTALL=no # docker-nobootstrap: BOOTSTRAP=./bootstrap -D ### @@ -750,7 +758,7 @@ commands = local: bash -c 'if [ ! -d prefix -o -L prefix ]; then rm -f prefix; ln -sf {env:PREFIX:{envdir}/local} prefix; fi' ##commands = - docker: bash -c 'build/bin/write-dockerfile.sh {env:SYSTEM} "{env:SAGE_PACKAGE_LIST_ARGS:}" {env:WITH_SYSTEM_SPKG} {env:IGNORE_MISSING_SYSTEM_PACKAGES} "{env:ALL_EXTRA_SAGE_PACKAGES}" > {envdir}/Dockerfile' + docker: bash -c '.ci/write-dockerfile.sh {env:SYSTEM} "{env:SAGE_PACKAGE_LIST_ARGS:}" {env:WITH_SYSTEM_SPKG} {env:IGNORE_MISSING_SYSTEM_PACKAGES} "{env:ALL_EXTRA_SAGE_PACKAGES}" > {envdir}/Dockerfile' # From https://hub.docker.com/r/multiarch/ubuntu-core/ # configure binfmt-support on the Docker host (works locally or remotely, i.e: using boot2docker) docker-{arm64,armhf}: docker run --rm --privileged multiarch/qemu-user-static:register --reset @@ -764,10 +772,6 @@ commands = docker: docker build . -f {envdir}/Dockerfile \ docker: --target $docker_target \ docker: $TAG_ARGS \ - docker: --build-arg EXTRA_CONFIGURE_ARGS="{env:CONFIGURE_ARGS}" \ - docker: --build-arg BASE_IMAGE={env:FULL_BASE_IMAGE_AND_TAG} \ - docker-conda: --build-arg USE_CONDARC="{env:USE_CONDARC}" \ - docker: --build-arg BOOTSTRAP="{env:BOOTSTRAP}" \ docker: --build-arg TARGETS_PRE="$(if test -n "$TARGETS_PRE"; then echo $TARGETS_PRE; else echo {posargs:all-sage-local}; fi)" \ docker: --build-arg TARGETS="{posargs:build}" \ docker: --build-arg TARGETS_OPTIONAL="{env:TARGETS_OPTIONAL:ptest}" \ @@ -848,16 +852,16 @@ setenv = # Master list of platforms tested in CI Linux # DEFAULT_SYSTEM_FACTORS=\ - ubuntu-{trusty-toolchain-gcc_9,xenial-toolchain-gcc_9,bionic-gcc_8,focal,jammy,lunar,mantic} \ + ubuntu-{xenial-toolchain-gcc_9,bionic-gcc_8,focal,jammy,lunar,mantic,noble} \ debian-{buster-gcc_spkg,bullseye,bookworm,trixie,sid} \ - linuxmint-{20.1,20.2,20.3,21,21.1,21.2} \ - fedora-{30,31,32,33,34,35,36,37,38,39} \ + linuxmint-{20.1,20.2,20.3,21,21.1,21.2,21.3} \ + fedora-{30,31,32,33,34,35,36,37,38,39,40} \ centos-7-devtoolset-gcc_11 \ centos-stream-{8,9}-python3.9 \ almalinux-{8-python3.9,9-python3.11} \ - gentoo-python{3.10,3.11} \ + gentoo-python{3.10,3.11,3.12} \ archlinux-latest \ - opensuse-{15.3-gcc_11-python3.9,15.4-gcc_11-python3.10,15.5-gcc_11-python3.11} \ + opensuse-15.5-gcc_11-python3.11 \ opensuse-tumbleweed{-python3.10,} \ conda-forge-python3.11 \ ubuntu-bionic-gcc_8-i386 \ @@ -993,7 +997,22 @@ allowlist_externals = {[sage_src]allowlist_externals} [testenv:coverage.py] description = run the Sage doctester with Coverage.py - (https://coverage.readthedocs.io/en/latest/index.html) +passenv = {[sage_src]passenv} +envdir = {[sage_src]envdir} +commands = {[sage_src]commands} +allowlist_externals = {[sage_src]allowlist_externals} + +[testenv:coverage.py-html] +description = + run the Sage doctester with Coverage.py, generate HTML report +passenv = {[sage_src]passenv} +envdir = {[sage_src]envdir} +commands = {[sage_src]commands} +allowlist_externals = {[sage_src]allowlist_externals} + +[testenv:coverage.py-xml] +description = + run the Sage doctester with Coverage.py, generate XML report passenv = {[sage_src]passenv} envdir = {[sage_src]envdir} commands = {[sage_src]commands}