diff --git a/.ci/merge-fixes.sh b/.ci/merge-fixes.sh index 73b4c665caf..13350018221 100755 --- a/.ci/merge-fixes.sh +++ b/.ci/merge-fixes.sh @@ -42,6 +42,7 @@ for REPO in ${SAGE_CI_FIXES_FROM_REPOSITORIES:-sagemath/sage}; do # Considered alternative: Use https://github.com/$REPO/pull/$a.diff, # which squashes everything into one diff without commit metadata. PULL_URL="https://github.com/$REPO/pull/$a" + PULL_SHORT="$REPO#$a" PULL_FILE="$REPO_FILE-$a" PATH=build/bin:$PATH build/bin/sage-download-file --quiet "$PULL_URL.patch" $PULL_FILE.patch date -u +"%Y-%m-%dT%H:%M:%SZ" > $PULL_FILE.date # Record the date, for future reference @@ -67,7 +68,7 @@ for REPO in ${SAGE_CI_FIXES_FROM_REPOSITORIES:-sagemath/sage}; do git am --signoff --show-current-patch=diff echo "--------------------------------------------------------------------8<-----------------------------" echo "::endgroup::" - echo "Failure applying $PULL_URL as a patch, resetting" + echo "Failure applying $PULL_SHORT as a patch, resetting" git am --signoff --abort fi done diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ce3c7889f4b..5944e32a19d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,13 +35,13 @@ jobs: uses: actions/checkout@v4 - name: Merge CI fixes from sagemath/sage run: | - .ci/merge-fixes.sh + mkdir -p upstream + .ci/merge-fixes.sh 2>&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: | - mkdir -p upstream if git format-patch --stdout test_base > ci_fixes.patch; then cp ci_fixes.patch upstream/ fi diff --git a/.github/workflows/doc-build-pdf.yml b/.github/workflows/doc-build-pdf.yml index 7ae675d9e64..6e6e8776062 100644 --- a/.github/workflows/doc-build-pdf.yml +++ b/.github/workflows/doc-build-pdf.yml @@ -29,13 +29,13 @@ jobs: uses: actions/checkout@v4 - name: Merge CI fixes from sagemath/sage run: | - .ci/merge-fixes.sh + mkdir -p upstream + .ci/merge-fixes.sh 2>&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: | - mkdir -p upstream if git format-patch --stdout test_base > ci_fixes.patch; then cp ci_fixes.patch upstream/ fi diff --git a/.github/workflows/doc-build.yml b/.github/workflows/doc-build.yml index 9d82909ef5f..21e19e3859a 100644 --- a/.github/workflows/doc-build.yml +++ b/.github/workflows/doc-build.yml @@ -24,13 +24,13 @@ jobs: uses: actions/checkout@v4 - name: Merge CI fixes from sagemath/sage run: | - .ci/merge-fixes.sh + mkdir -p upstream + .ci/merge-fixes.sh 2>&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: | - mkdir -p upstream if git format-patch --stdout test_base > ci_fixes.patch; then cp ci_fixes.patch upstream/ fi diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 1ee938339b3..18910ca50a6 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -248,9 +248,8 @@ jobs: - name: Copy logs from the Docker image or build container run: | mkdir -p "artifacts/$LOGS_ARTIFACT_NAME" - cp -r .tox/$TOX_ENV/Dockerfile .tox/$TOX_ENV/log "artifacts/$LOGS_ARTIFACT_NAME" - if [ -f .tox/$TOX_ENV/Dockertags ]; then CONTAINERS=$(docker create $(tail -1 .tox/$TOX_ENV/Dockertags) /bin/bash || true); fi - if [ -n "$CONTAINERS" ]; then for CONTAINER in $CONTAINERS; do for ARTIFACT in /sage/logs; do docker cp $CONTAINER:$ARTIFACT artifacts/$LOGS_ARTIFACT_NAME && HAVE_LOG=1; done; if [ -n "$HAVE_LOG" ]; then break; fi; done; fi + cp -r .tox/$TOX_ENV/* "artifacts/$LOGS_ARTIFACT_NAME" + rm -rf "artifacts/$LOGS_ARTIFACT_NAME"/{bin,lib,pyvenv.cfg} if: always() - uses: actions/upload-artifact@v3 with: @@ -262,9 +261,46 @@ jobs: run: | .github/workflows/scan-logs.sh "artifacts/$LOGS_ARTIFACT_NAME" if: always() - - name: List docker images + - name: List Docker images run: | - if [ -f .tox/$TOX_ENV/Dockertags ]; then - cat .tox/$TOX_ENV/Dockertags + if [ -n "$DOCKER_PUSH_REPOSITORY" -a -f .tox/$TOX_ENV/Dockertags.pushed ]; then + set -- $(cat .tox/$TOX_ENV/Dockertags.pushed) + case $# in + 1) images="image"; one_image="the image";; + *) images="images"; one_image="one of the images";; + esac + echo "::notice title=Docker $images pushed::Pushed $images $*)" + echo + echo "To pull $one_image and enter the container, type:" + echo + for TAG in $*; do + echo " \$ docker run -it $TAG bash" + done + echo + echo "To use $one_image as the base for an incremental build, type:" + echo + TOX_ENV_SANS_INCREMENTAL=${TOX_ENV/-incremental/} + DOCKER_IMAGE=${TOX_ENV_SANS_INCREMENTAL#docker-} + for TAG in $*; do + echo -n " \$" + if [ "$DOCKER_PUSH_REPOSITORY" != "ghcr.io/sagemath/sage/" ]; then + echo -n " FROM_DOCKER_REPOSITORY=$DOCKER_PUSH_REPOSITORY" + fi + eval DOCKER_TARGET=\${TAG#*$DOCKER_IMAGE-} + DOCKER_TARGET=${DOCKER_TARGET%:*} + if [ "$DOCKER_TARGET" != "with-targets" ]; then + echo -n " FROM_DOCKER_TARGET=$DOCKER_TARGET" + fi + echo " FROM_DOCKER_TAG=${TAG#*:} tox -e $TOX_ENV_SANS_INCREMENTAL-incremental" + done + elif [ -n "$DOCKER_PUSH_REPOSITORY" -a -f .tox/$TOX_ENV/Dockertags ]; then + echo "Unable to push Docker images to $DOCKER_PUSH_REPOSITORY." + echo "This is normal in a pull request to sagemath/sage or to another user's repository." + echo + echo "If you need Docker images, " + echo " - either run this GitHub Actions workflow in your repository fork" + echo " - or use the method described in https://doc.sagemath.org/html/en/developer/portability_testing.html#automatic-docker-based-build-testing-using-tox" + else + echo "No Docker images created." fi - if: always() + if: always() && ${{ inputs.docker_push_repository }} diff --git a/.upstream.d/20-github.com-sagemath-sage-releases b/.upstream.d/20-github.com-sagemath-sage-releases index 1ab20a34a06..db4fdc08b38 100644 --- a/.upstream.d/20-github.com-sagemath-sage-releases +++ b/.upstream.d/20-github.com-sagemath-sage-releases @@ -1,5 +1,5 @@ # Upstream packages as uploaded as GitHub release assets. # This file is automatically updated by the sage-update-version script. +https://github.com/sagemath/sage/releases/download/10.4/ https://github.com/sagemath/sage/releases/download/10.3/ https://github.com/sagemath/sage/releases/download/10.2/ -https://github.com/sagemath/sage/releases/download/10.1/ diff --git a/CITATION.cff b/CITATION.cff index 95876d7d4e4..50004258150 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.3.rc4 +version: 10.4.beta0 doi: 10.5281/zenodo.593563 -date-released: 2024-03-17 +date-released: 2024-03-25 repository-code: "https://github.com/sagemath/sage" url: "https://www.sagemath.org/" diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 0e64fa46a1f..eca34dc7e4c 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -37,6 +37,4 @@ Sage can more effectively collaborate with others if they follow this code. If you believe someone is violating the code of conduct, we ask that you report it to https://groups.google.com/g/sage-abuse. The group administrators will -consider the issue and explore resolutions. It is also possible to move heated -discussions to https://groups.google.com/g/sage-flame. - +consider the issue and explore resolutions. diff --git a/Makefile b/Makefile index 550039a1df6..56e11c69f50 100644 --- a/Makefile +++ b/Makefile @@ -44,13 +44,25 @@ SAGE_ROOT_LOGS = logs # except for build/make/Makefile-auto, which is unused by the build system CONFIG_FILES = build/make/Makefile src/bin/sage-env-config build/bin/sage-build-env-config pkgs/sage-conf/_sage_conf/_conf.py -# SPKG_COLLECT_FILES contains all files that influence the SAGE_SPKG_COLLECT macro -SPKG_COLLECT_FILES = build/pkgs/*/type build/pkgs/*/package-version.txt build/pkgs/*/dependencies build/pkgs/*/requirements.txt build/pkgs/*/checksums.ini build/pkgs/*/spkg-install - -# If configure was run before, rerun it with the old arguments. -# Otherwise, run configure with argument $PREREQ_OPTIONS. +# SPKG_COLLECT_FILES contains the files that influence the *runtime* of the +# portions of the 'configure' script generated by the SAGE_SPKG_COLLECT macro +SPKG_COLLECT_FILES = build/pkgs/*/package-version.txt build/pkgs/*/dependencies* + +# If configure was not run before, complain. +# If configure is newer than the files it generated (we test build/make/Makefile), +# we regenerate config.status by running the "config.status --recheck". +# Either way we regenerate the generated files by calling "config.status". build/make/Makefile: configure $(SPKG_COLLECT_FILES) $(CONFIG_FILES:%=%.in) - $(MAKE) reconfigure + @if [ -x config.status ]; then \ + case '$?' in \ + *configure*|*package-version*|*dependencies*) \ + $(MAKE) reconfigure;; \ + *) \ + ./config.status;; \ + esac; \ + else \ + $(MAKE) reconfigure; \ + fi reconfigure: rm -f config.log @@ -324,7 +336,25 @@ ptestoptionallong-nodoc: ############################################################################### -configure: bootstrap src/doc/bootstrap configure.ac src/bin/sage-version.sh m4/*.m4 build/pkgs/*/spkg-configure.m4 build/pkgs/*/type build/pkgs/*/install-requires.txt build/pkgs/*/package-version.txt build/pkgs/*/distros/*.txt +# The 'configure' script is just one of the files generated by 'bootstrap'. +# CONFIGURE_DEPENDENCIES is the list of files that influence the generation of 'configure'. +CONFIGURE_DEPENDENCIES = \ + configure.ac src/bin/sage-version.sh m4/*.m4 \ + build/pkgs/*/spkg-configure.m4 \ + build/pkgs/*/type build/pkgs/*/SPKG.rst \ + build/pkgs/*/checksums.ini build/pkgs/*/requirements.txt \ + build/pkgs/*/install-requires.txt build/pkgs/*/package-version.txt \ + build/pkgs/*/spkg-install build/pkgs/*/spkg-install.in + +# SPKG_INFO_DEPENDENCIES is the list of files that influence the run of 'sage-spkg-info' and hence +# the generation of the files generated in 'src/doc' by 'src/doc/bootstrap'. +SPKG_INFO_DEPENDENCIES = \ + build/pkgs/*/type build/pkgs/*/SPKG.rst \ + build/pkgs/*/requirements.txt \ + build/pkgs/*/install-requires.txt build/pkgs/*/package-version.txt \ + build/pkgs/*/distros/*.txt + +configure: bootstrap src/doc/bootstrap $(CONFIGURE_DEPENDENCIES) $(SPKG_INFO_DEPENDENCIES) ./bootstrap -d install: all diff --git a/README.md b/README.md index 8add84575d8..d57111b5e1b 100644 --- a/README.md +++ b/README.md @@ -78,10 +78,11 @@ mailing list](https://groups.google.com/group/sage-devel). [Windows] Preparing the Platform -------------------------------- -The preferred way to run Sage on Windows is using the [Windows Subsystem for -Linux](https://docs.microsoft.com/en-us/windows/wsl/faq), a.k.a. WSL, which allows -you to install a standard Linux distribution such as Ubuntu within -your Windows. Make sure you allocate WSL sufficient RAM; 5GB is known to work, while +The preferred way to run Sage on Windows is using Windows Subsystem for +Linux (WSL). Follow the +[official WSL setup guide](https://docs.microsoft.com/en-us/windows/wsl/faq) +to install Ubuntu (or another Linux distribution). +Make sure you allocate WSL sufficient RAM; 5GB is known to work, while 2GB might be not enough for building Sage from source. Then all instructions for installation in Linux apply. @@ -131,9 +132,8 @@ in the Installation Guide. - On personal computers, any subdirectory of your :envvar:`HOME` directory should do. - - For example, you could use `SAGE_ROOT=~/sage/sage-x.y`, which we - will use as the running example below, where `x.y` is the - current Sage version. + - For example, you could use `SAGE_ROOT=~/sage/sage`, which we + will use as the running example below. - You need at least 10 GB of free disk space. @@ -151,52 +151,64 @@ in the Installation Guide. capitalization when changing into :envvar:`SAGE_ROOT` can lead to build errors for dependencies requiring exact capitalization in path names. -2. Download/unpack or clone the sources. +2. Clone the sources with `git`: - - Go to https://www.sagemath.org/download-source.html, select a mirror, - and download the file :file:`sage-x.y.tar.gz`. + - To check that `git` is available, open a terminal and enter + the following command at the shell prompt (`$`): - This compressed archive file contains the source code for Sage and - the source for all programs on which Sage depends. + $ git --version + git version 2.42.0 - - After downloading the source tarball `sage-x.y.tar.gz` into - `~/sage/`: + The exact version does not matter, but if this command gives an error, + install `git` using your package manager, using one of these commands: - $ cd ~/sage/ - $ tar xf sage-x.y.tar.gz # adapt x.y; takes a while + $ sudo pacman -S git # on Arch Linux + $ sudo apt-get update && apt-get install git # on Debian/Ubuntu + $ sudo yum install git # on Fedora/Redhat/CentOS + $ sudo zypper install git # on openSUSE + $ sudo xbps-install git # on Void Linux - This creates the subdirectory `sage-x.y`. Now change into it: + - Create the directory where `SAGE_ROOT` should be established: - $ cd sage-x.y/ # adapt x.y + $ mkdir -p ~/sage + $ cd ~/sage - - [Git] Alternatively, and required for Sage development, clone the Sage - git repository: + - Clone the Sage git repository: - $ ORIG=https://github.com/sagemath/sage.git - $ git clone -c core.symlinks=true --branch develop --tags $ORIG + $ git clone -c core.symlinks=true --filter blob:none \ + --origin upstream --branch develop --tags \ + https://github.com/sagemath/sage.git - This will create the directory `sage`. (See the section + This command obtains the most recent development release. + Replace `--branch develop` by `--branch master` to select + the most recent stable release instead. + + This will create the subdirectory `~/sage/sage`. (See the section [Setting up git](https://doc.sagemath.org/html/en/developer/git_setup.html) and the following sections in the Sage Developer's Guide for more information.) - Change into it and pick the branch you need, typically - the latest development branch: + - Change into the created subdirectory: $ cd sage - $ git checkout develop - [Windows] The Sage source tree contains symbolic links, and the build will not work if Windows line endings rather than UNIX line endings are used. - Therefore it is crucial that you unpack the source tree from the - WSL `bash` using the WSL `tar` utility and not using other - Windows tools (including mingw). Likewise, when using `git`, it - is recommended (but not necessary) to use the WSL version of - `git`. + Therefore it is recommended (but not necessary) to use the + WSL version of `git`. + +3. Install system packages. + + Either refer for this to the [section on installation from + source](https://doc.sagemath.org/html/en/installation/source.html) in the + Sage Installation Manual for compilations of system packages + that you can install. When done, skip to step 7 (bootstrapping). -3. [Linux, WSL] Install the required minimal build prerequisites. + Alternatively, follow the more fine-grained approach below. + +4. [Linux, WSL] Install the required minimal build prerequisites: - Compilers: `gcc`, `gfortran`, `g++` (GCC versions from 8.4.0 to 13.x and recent versions of Clang (LLVM) are supported). @@ -205,12 +217,12 @@ in the Installation Guide. for a discussion of suitable compilers. - Build tools: GNU `make`, GNU `m4`, `perl` (including - ``ExtUtils::MakeMaker``), `ranlib`, `git`, `tar`, `bc`. + `ExtUtils::MakeMaker`), `ranlib`, `git`, `tar`, `bc`. See [build/pkgs/_prereq/SPKG.rst](build/pkgs/_prereq/SPKG.rst) for more details. - Python 3.4 or later, or Python 2.7, a full installation including - `urllib`; but ideally version 3.9.x, 3.10.x, or 3.11.x, which + `urllib`; but ideally version 3.9.x, 3.10.x, 3.11.x, 3.12.x, which will avoid having to build Sage's own copy of Python 3. See [build/pkgs/python3/SPKG.rst](build/pkgs/python3/SPKG.rst) for more details. @@ -229,22 +241,25 @@ in the Installation Guide. [void.txt](build/pkgs/_prereq/distros/void.txt), or visit https://doc.sagemath.org/html/en/reference/spkg/_prereq.html#spkg-prereq -4. [Git] If you plan to do Sage development or otherwise work with ticket branches - and not only releases, install the bootstrapping prerequisites. See the - files in the folder +5. Optional: It is recommended that you have both LaTeX and + the ImageMagick tools (e.g. the "convert" command) installed + since some plotting functionality benefits from them. + +6. [Development] If you plan to do Sage development or otherwise work with + ticket branches and not only releases, install the bootstrapping + prerequisites. See the files in the folder [build/pkgs/_bootstrap/distros](build/pkgs/_bootstrap/distros), or visit https://doc.sagemath.org/html/en/reference/spkg/_bootstrap.html#spkg-bootstrap -5. [Git] If you cloned the Sage repository using `git`, bootstrap the - source tree using the following command: +7. Bootstrap the source tree using the following command: $ make configure - (If the bootstrapping prerequisites are not installed, this command will - download a package providing pre-built bootstrap output instead.) + (If the bootstrapping prerequisites are not installed, this command + will download a package providing pre-built bootstrap output instead.) -6. Sanitize the build environment. Use the command +8. Sanitize the build environment. Use the command $ env @@ -276,7 +291,7 @@ in the Installation Guide. can also add it to your shell profile so that it gets run automatically in all future sessions.) -7. Optionally, decide on the installation prefix (`SAGE_LOCAL`): +9. Optionally, decide on the installation prefix (`SAGE_LOCAL`): - Traditionally, and by default, Sage is installed into the subdirectory hierarchy rooted at `SAGE_ROOT/local/`. @@ -294,15 +309,11 @@ in the Installation Guide. installs (`make install` is a no-op). Therefore the installation hierarchy must be writable by the user. - - See the installation manual for options if you want to + - See the Sage Installation Manual for options if you want to install into shared locations such as `/usr/local/`. Do not attempt to build Sage as `root`. -8. Optional: It is recommended that you have both LaTeX and - the ImageMagick tools (e.g. the "convert" command) installed - since some plotting functionality benefits from them. - -9. Optionally, review the configuration options, which includes +10. Optionally, review the configuration options, which includes many optional packages: $ ./configure --help @@ -320,7 +331,7 @@ in the Installation Guide. a great speedup when switching between different branches, at the expense of disk space use. -10. Optional, but highly recommended: Set some environment variables to +11. Optional, but highly recommended: Set some environment variables to customize the build. For example, the `MAKE` environment variable controls whether to @@ -342,7 +353,7 @@ in the Installation Guide. building Sage, see [the installation guide](https://doc.sagemath.org/html/en/installation/source.html#environment-variables). -11. Type `./configure`, followed by any options that you wish to use. +12. Type `./configure`, followed by any options that you wish to use. For example, to build Sage with `gf2x` package supplied by Sage, use `./configure --with-system-gf2x=no`. @@ -362,60 +373,60 @@ in the Installation Guide. available; only the most recent releases of your distribution will have all of these recommended packages. -12. Optional: If you choose to install the additional system packages, +13. Optional: If you choose to install the additional system packages, a re-run of `./configure` will test whether the versions installed are usable for Sage; if they are, this will reduce the compilation time and disk space needed by Sage. The usage of packages may be adjusted by `./configure` parameters (check again the output of `./configure --help`). -13. Type `make`. That's it! Everything is automatic and +14. Type `make`. That's it! Everything is automatic and non-interactive. If you followed the above instructions, in particular regarding the installation of system packages recommended by the output of - `./configure` (step 10), and regarding the parallel build (step 9), + `./configure` (step 11), and regarding the parallel build (step 10), building Sage takes less than one hour on a modern computer. (Otherwise, it can take much longer.) The build should work fine on all fully supported platforms. If it does not, we want to know! -14. Type `./sage` to try it out. In Sage, try for example `2 + 2`, +15. Type `./sage` to try it out. In Sage, try for example `2 + 2`, `plot(x^2)`, `plot3d(lambda x, y: x*y, (-1, 1), (-1, 1))` to test a simple computation and plotting in 2D and 3D. Type Ctrl+D or `quit` to quit Sage. -15. Optional: Type `make ptestlong` to test all examples in the documentation +16. Optional: Type `make ptestlong` to test all examples in the documentation (over 200,000 lines of input!) -- this takes from 10 minutes to several hours. Don't get too disturbed if there are 2 to 3 failures, but always feel free to email the section of `logs/ptestlong.log` that contains errors to the [sage-support mailing list](https://groups.google.com/group/sage-support). If there are numerous failures, there was a serious problem with your build. -16. The HTML version of the [documentation](https://doc.sagemath.org/html/en/index.html) +17. The HTML version of the [documentation](https://doc.sagemath.org/html/en/index.html) is built during the compilation process of Sage and resides in the directory `local/share/doc/sage/html/`. You may want to bookmark it in your browser. -17. Optional: If you want to build the PDF version of the documentation, +18. Optional: If you want to build the PDF version of the documentation, run `make doc-pdf` (this requires LaTeX to be installed). -18. Optional: Install optional packages of interest to you: +19. Optional: Install optional packages of interest to you: get a list by typing `./sage --optional` or by visiting the [packages documentation page](https://doc.sagemath.org/html/en/reference/spkg/). -19. Optional: Create a symlink to the installed `sage` script in a - directory in your `PATH`, for example ``/usr/local``. This will +20. Optional: Create a symlink to the installed `sage` script in a + directory in your `PATH`, for example `/usr/local`. This will allow you to start Sage by typing `sage` from anywhere rather than having to either type the full path or navigate to the Sage directory and type `./sage`. This can be done by running: $ sudo ln -s $(./sage -sh -c 'ls $SAGE_ROOT/venv/bin/sage') /usr/local/bin -20. Optional: Set up SageMath as a Jupyter kernel in an existing Jupyter notebook +21. Optional: Set up SageMath as a Jupyter kernel in an existing Jupyter notebook or JupyterLab installation, as described in [section "Launching SageMath"](https://doc.sagemath.org/html/en/installation/launching.html) - in the installation manual. + in the Sage Installation Manual. Alternative Installation using PyPI --------------- @@ -504,7 +515,7 @@ Directory Layout Simplified directory layout (only essential files/directories): ``` -SAGE_ROOT Root directory (sage-x.y in Sage tarball) +SAGE_ROOT Root directory (create by git clone) ├── build │ └── pkgs Every package is a subdirectory here │ ├── 4ti2/ diff --git a/VERSION.txt b/VERSION.txt index ba55211dc5e..e17d5f059fb 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -SageMath version 10.3.rc4, Release Date: 2024-03-17 +SageMath version 10.4.beta0, Release Date: 2024-03-25 diff --git a/build/bin/write-dockerfile.sh b/build/bin/write-dockerfile.sh index 2d0de123665..3c62d6082e4 100755 --- a/build/bin/write-dockerfile.sh +++ b/build/bin/write-dockerfile.sh @@ -214,6 +214,18 @@ EOF ;; esac esac + +case ${DOCKER_BUILDKIT-0} in + 1) + # With buildkit we cannot retrieve failed builds. + # So we do not allow the main step of a build stage to fail. + # Instead we record the exit code in the file STATUS. + THEN_SAVE_STATUS='; echo $? > STATUS' + # ... and at the beginning of the next build stage, + # we check the status and exit with an error status. + CHECK_STATUS_THEN='STATUS=$(cat STATUS 2>/dev/null); case "$STATUS" in ""|0) ;; *) exit $STATUS;; esac; ' +esac + cat <=python-3.11. Here + # we test for a python minor version component greater than or equal + # to 11, and mark this package as "not required" if we succeed. + AC_MSG_CHECKING([for >=python-3.11]) + + # Keep in mind that False (~ zero) in python is success in the shell + AS_IF(["${PYTHON_FOR_VENV}" -c "import sys; sys.exit(sys.version_info.minor < 11)"],[ + AC_MSG_RESULT([yes]) + sage_require_importlib_metadata="no" + ],[ + AC_MSG_RESULT([no]) + ]) ]) + diff --git a/build/pkgs/importlib_resources/spkg-configure.m4 b/build/pkgs/importlib_resources/spkg-configure.m4 index 50df55b4643..1416ec5c200 100644 --- a/build/pkgs/importlib_resources/spkg-configure.m4 +++ b/build/pkgs/importlib_resources/spkg-configure.m4 @@ -1,3 +1,23 @@ SAGE_SPKG_CONFIGURE([importlib_resources], [ SAGE_PYTHON_PACKAGE_CHECK([importlib_resources]) +],[ + # Three of our python packages are backport packages providing + # python-3.11 features (see coding_in_python.rst): + # + # * importlib_metadata + # * importlib_resources + # * typing_extensions + # + # These packages are therefore not needed with >=python-3.11. Here + # we test for a python minor version component greater than or equal + # to 11, and mark this package as "not required" if we succeed. + AC_MSG_CHECKING([for >=python-3.11]) + + # Keep in mind that False (~ zero) in python is success in the shell + AS_IF(["${PYTHON_FOR_VENV}" -c "import sys; sys.exit(sys.version_info.minor < 11)"],[ + AC_MSG_RESULT([yes]) + sage_require_importlib_resources="no" + ],[ + AC_MSG_RESULT([no]) + ]) ]) diff --git a/build/pkgs/info/checksums.ini b/build/pkgs/info/checksums.ini index c01f8b97505..cf6afe0684c 100644 --- a/build/pkgs/info/checksums.ini +++ b/build/pkgs/info/checksums.ini @@ -1,5 +1,5 @@ tarball=texinfo-VERSION.tar.xz -sha1=ce3776715e655400485381b8ae94e34c5632e729 -md5=a91b404e30561a5df803e6eb3a53be71 -cksum=3632265516 +sha1=356a623b88401d7c993408f33450c8104aad9df8 +md5=37bf94fd255729a14d4ea3dda119f81a +cksum=1448415744 upstream_url=https://ftp.gnu.org/gnu/texinfo/texinfo-VERSION.tar.xz diff --git a/build/pkgs/info/package-version.txt b/build/pkgs/info/package-version.txt index 21afad37646..a50da181e9b 100644 --- a/build/pkgs/info/package-version.txt +++ b/build/pkgs/info/package-version.txt @@ -1 +1 @@ -6.8 +7.0.3 diff --git a/build/pkgs/ipympl/front-end b/build/pkgs/ipympl/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/ipython/front-end b/build/pkgs/ipython/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/ipywidgets/front-end b/build/pkgs/ipywidgets/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/isl/math b/build/pkgs/isl/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/isoduration/spkg-configure.m4 b/build/pkgs/isoduration/spkg-configure.m4 new file mode 100644 index 00000000000..2b42e17f0e8 --- /dev/null +++ b/build/pkgs/isoduration/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([isoduration], [SAGE_PYTHON_PACKAGE_CHECK([isoduration])]) diff --git a/build/pkgs/jmol/front-end b/build/pkgs/jmol/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/json5/SPKG.rst b/build/pkgs/json5/SPKG.rst index 5f85e534785..19bb7036bfa 100644 --- a/build/pkgs/json5/SPKG.rst +++ b/build/pkgs/json5/SPKG.rst @@ -1,10 +1,10 @@ -json5: A Python implementation of the JSON5 data format. -======================================================== +json5: Python implementation of the JSON5 data format +===================================================== Description ----------- -A Python implementation of the JSON5 data format. +Python implementation of the JSON5 data format License ------- diff --git a/build/pkgs/json5/spkg-configure.m4 b/build/pkgs/json5/spkg-configure.m4 new file mode 100644 index 00000000000..50f9ab8c882 --- /dev/null +++ b/build/pkgs/json5/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([json5], [SAGE_PYTHON_PACKAGE_CHECK([json5])]) diff --git a/build/pkgs/jsonpointer/spkg-configure.m4 b/build/pkgs/jsonpointer/spkg-configure.m4 new file mode 100644 index 00000000000..3d99f290be9 --- /dev/null +++ b/build/pkgs/jsonpointer/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([jsonpointer], [SAGE_PYTHON_PACKAGE_CHECK([jsonpointer])]) diff --git a/build/pkgs/jsonschema_specifications/spkg-configure.m4 b/build/pkgs/jsonschema_specifications/spkg-configure.m4 new file mode 100644 index 00000000000..f6557ca1bbd --- /dev/null +++ b/build/pkgs/jsonschema_specifications/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([jsonschema_specifications], [SAGE_PYTHON_PACKAGE_CHECK([jsonschema_specifications])]) diff --git a/build/pkgs/jupymake/math b/build/pkgs/jupymake/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/jupymake/spkg-configure.m4 b/build/pkgs/jupymake/spkg-configure.m4 new file mode 100644 index 00000000000..aca24c94eae --- /dev/null +++ b/build/pkgs/jupymake/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([jupymake], [SAGE_PYTHON_PACKAGE_CHECK([jupymake])]) diff --git a/build/pkgs/jupyter_events/spkg-configure.m4 b/build/pkgs/jupyter_events/spkg-configure.m4 new file mode 100644 index 00000000000..e48d110521f --- /dev/null +++ b/build/pkgs/jupyter_events/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([jupyter_events], [SAGE_PYTHON_PACKAGE_CHECK([jupyter_events])]) diff --git a/build/pkgs/jupyter_jsmol/front-end b/build/pkgs/jupyter_jsmol/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/jupyter_lsp/spkg-configure.m4 b/build/pkgs/jupyter_lsp/spkg-configure.m4 new file mode 100644 index 00000000000..ca4ba801296 --- /dev/null +++ b/build/pkgs/jupyter_lsp/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([jupyter_lsp], [SAGE_PYTHON_PACKAGE_CHECK([jupyter_lsp])]) diff --git a/build/pkgs/jupyter_server/spkg-configure.m4 b/build/pkgs/jupyter_server/spkg-configure.m4 new file mode 100644 index 00000000000..5419748c46d --- /dev/null +++ b/build/pkgs/jupyter_server/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([jupyter_server], [SAGE_PYTHON_PACKAGE_CHECK([jupyter_server])]) diff --git a/build/pkgs/jupyter_server_terminals/spkg-configure.m4 b/build/pkgs/jupyter_server_terminals/spkg-configure.m4 new file mode 100644 index 00000000000..1fe9e5ed896 --- /dev/null +++ b/build/pkgs/jupyter_server_terminals/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([jupyter_server_terminals], [SAGE_PYTHON_PACKAGE_CHECK([jupyter_server_terminals])]) diff --git a/build/pkgs/jupyterlab/front-end b/build/pkgs/jupyterlab/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/jupyterlab/spkg-configure.m4 b/build/pkgs/jupyterlab/spkg-configure.m4 new file mode 100644 index 00000000000..33865328e22 --- /dev/null +++ b/build/pkgs/jupyterlab/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([jupyterlab], [SAGE_PYTHON_PACKAGE_CHECK([jupyterlab])]) diff --git a/build/pkgs/jupyterlab_mathjax2/spkg-configure.m4 b/build/pkgs/jupyterlab_mathjax2/spkg-configure.m4 new file mode 100644 index 00000000000..5d1b366b376 --- /dev/null +++ b/build/pkgs/jupyterlab_mathjax2/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([jupyterlab_mathjax2], [SAGE_PYTHON_PACKAGE_CHECK([jupyterlab_mathjax2])]) diff --git a/build/pkgs/jupyterlab_server/SPKG.rst b/build/pkgs/jupyterlab_server/SPKG.rst index aca71f627ec..450a6f70ef2 100644 --- a/build/pkgs/jupyterlab_server/SPKG.rst +++ b/build/pkgs/jupyterlab_server/SPKG.rst @@ -1,10 +1,10 @@ -jupyterlab_server: A set of server components for JupyterLab and JupyterLab like applications. -============================================================================================== +jupyterlab_server: Set of server components for JupyterLab and JupyterLab like applications +=========================================================================================== Description ----------- -A set of server components for JupyterLab and JupyterLab like applications. +Set of server components for JupyterLab and JupyterLab like applications License ------- diff --git a/build/pkgs/jupyterlab_server/spkg-configure.m4 b/build/pkgs/jupyterlab_server/spkg-configure.m4 new file mode 100644 index 00000000000..0173ac2c800 --- /dev/null +++ b/build/pkgs/jupyterlab_server/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([jupyterlab_server], [SAGE_PYTHON_PACKAGE_CHECK([jupyterlab_server])]) diff --git a/build/pkgs/jupyterlab_widgets/front-end b/build/pkgs/jupyterlab_widgets/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/kenzo/math b/build/pkgs/kenzo/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/kissat/math b/build/pkgs/kissat/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/kiwisolver/SPKG.rst b/build/pkgs/kiwisolver/SPKG.rst index f461bf01f33..9c3ad4350f7 100644 --- a/build/pkgs/kiwisolver/SPKG.rst +++ b/build/pkgs/kiwisolver/SPKG.rst @@ -1,5 +1,5 @@ -kiwisolver: An implementation of the Cassowary constraint solving algorithm -=========================================================================== +kiwisolver: Fast implementation of the Cassowary constraint solver +================================================================== Description ----------- diff --git a/build/pkgs/latte_int/math b/build/pkgs/latte_int/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/lcalc/math b/build/pkgs/lcalc/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/libbraiding/math b/build/pkgs/libbraiding/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/libhomfly/math b/build/pkgs/libhomfly/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/libnauty/math b/build/pkgs/libnauty/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/libsemigroups/math b/build/pkgs/libsemigroups/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/lidia/math b/build/pkgs/lidia/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/lie/math b/build/pkgs/lie/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/linbox/math b/build/pkgs/linbox/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/lrcalc/math b/build/pkgs/lrcalc/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/lrcalc_python/math b/build/pkgs/lrcalc_python/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/lrslib/math b/build/pkgs/lrslib/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/m4ri/math b/build/pkgs/m4ri/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/m4rie/math b/build/pkgs/m4rie/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/mathics/SPKG.rst b/build/pkgs/mathics/SPKG.rst index 09f9e87aea0..9ec090e0446 100644 --- a/build/pkgs/mathics/SPKG.rst +++ b/build/pkgs/mathics/SPKG.rst @@ -1,10 +1,10 @@ -mathics: A general-purpose computer algebra system -================================================== +mathics: General-purpose computer algebra system +================================================ Description ----------- -A general-purpose computer algebra system. +General-purpose computer algebra system License ------- diff --git a/build/pkgs/mathics/math b/build/pkgs/mathics/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/mathics/spkg-configure.m4 b/build/pkgs/mathics/spkg-configure.m4 new file mode 100644 index 00000000000..e297793c591 --- /dev/null +++ b/build/pkgs/mathics/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([mathics], [SAGE_PYTHON_PACKAGE_CHECK([mathics])]) diff --git a/build/pkgs/mathics_scanner/SPKG.rst b/build/pkgs/mathics_scanner/SPKG.rst index 873cbc97304..f5b5fb463d8 100644 --- a/build/pkgs/mathics_scanner/SPKG.rst +++ b/build/pkgs/mathics_scanner/SPKG.rst @@ -1,10 +1,10 @@ -mathics_scanner: Character Tables and Tokenizer for Mathics and the Wolfram Language. -===================================================================================== +mathics_scanner: Character Tables and Tokenizer for Mathics and the Wolfram Language +==================================================================================== Description ----------- -Character Tables and Tokenizer for Mathics and the Wolfram Language. +Character Tables and Tokenizer for Mathics and the Wolfram Language License ------- diff --git a/build/pkgs/mathics_scanner/math b/build/pkgs/mathics_scanner/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/mathics_scanner/spkg-configure.m4 b/build/pkgs/mathics_scanner/spkg-configure.m4 new file mode 100644 index 00000000000..09917819a0f --- /dev/null +++ b/build/pkgs/mathics_scanner/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([mathics_scanner], [SAGE_PYTHON_PACKAGE_CHECK([mathics_scanner])]) diff --git a/build/pkgs/matplotlib/front-end b/build/pkgs/matplotlib/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/matplotlib_inline/front-end b/build/pkgs/matplotlib_inline/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/maxima/math b/build/pkgs/maxima/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/mcqd/math b/build/pkgs/mcqd/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/meataxe/math b/build/pkgs/meataxe/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/mistune/SPKG.rst b/build/pkgs/mistune/SPKG.rst index 7d08e818625..f9c18ff407b 100644 --- a/build/pkgs/mistune/SPKG.rst +++ b/build/pkgs/mistune/SPKG.rst @@ -1,19 +1,18 @@ -mistune: A markdown parser in pure Python -========================================= +mistune: Sane and fast Markdown parser with useful plugins and renderers +======================================================================== Description ----------- -The fastest markdown parser in pure Python +Sane and fast Markdown parser with useful plugins and renderers License ------- -BSD License - +BSD-3-Clause Upstream Contact ---------------- -Home Page: https://github.com/lepture/mistune +https://pypi.org/project/mistune/ diff --git a/build/pkgs/modular_decomposition/math b/build/pkgs/modular_decomposition/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/modular_resolution/SPKG.rst b/build/pkgs/modular_resolution/SPKG.rst index 22c3f90f0fe..9de3398c83a 100644 --- a/build/pkgs/modular_resolution/SPKG.rst +++ b/build/pkgs/modular_resolution/SPKG.rst @@ -1,4 +1,4 @@ -p_group_cohomology: Modular cohomology rings of finite groups +modular_resolution: Modular cohomology rings of finite groups ============================================================= Description diff --git a/build/pkgs/modular_resolution/math b/build/pkgs/modular_resolution/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/mpc/math b/build/pkgs/mpc/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/mpfi/math b/build/pkgs/mpfi/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/mpfr/math b/build/pkgs/mpfr/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/mpfrcx/math b/build/pkgs/mpfrcx/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/mpmath/math b/build/pkgs/mpmath/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/msolve/math b/build/pkgs/msolve/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/nauty/math b/build/pkgs/nauty/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/nbclient/SPKG.rst b/build/pkgs/nbclient/SPKG.rst index ea6a94cc0aa..53e69bcdc05 100644 --- a/build/pkgs/nbclient/SPKG.rst +++ b/build/pkgs/nbclient/SPKG.rst @@ -1,10 +1,10 @@ -nbclient: A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor. -============================================================================================= +nbclient: Client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor +========================================================================================== Description ----------- -A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor. +Client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor License ------- diff --git a/build/pkgs/nbconvert/front-end b/build/pkgs/nbconvert/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/networkx/math b/build/pkgs/networkx/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/nibabel/spkg-configure.m4 b/build/pkgs/nibabel/spkg-configure.m4 new file mode 100644 index 00000000000..e5c298f9a5f --- /dev/null +++ b/build/pkgs/nibabel/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([nibabel], [SAGE_PYTHON_PACKAGE_CHECK([nibabel])]) diff --git a/build/pkgs/normaliz/math b/build/pkgs/normaliz/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/notebook/front-end b/build/pkgs/notebook/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/notebook_shim/spkg-configure.m4 b/build/pkgs/notebook_shim/spkg-configure.m4 new file mode 100644 index 00000000000..9131ca968f5 --- /dev/null +++ b/build/pkgs/notebook_shim/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([notebook_shim], [SAGE_PYTHON_PACKAGE_CHECK([notebook_shim])]) diff --git a/build/pkgs/notedown/spkg-configure.m4 b/build/pkgs/notedown/spkg-configure.m4 new file mode 100644 index 00000000000..a45e08710f5 --- /dev/null +++ b/build/pkgs/notedown/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([notedown], [SAGE_PYTHON_PACKAGE_CHECK([notedown])]) diff --git a/build/pkgs/ntl/checksums.ini b/build/pkgs/ntl/checksums.ini index d5ea9216c02..57769694456 100644 --- a/build/pkgs/ntl/checksums.ini +++ b/build/pkgs/ntl/checksums.ini @@ -1,4 +1,5 @@ tarball=ntl-VERSION.tar.gz -sha1=f4c7dc1fd448b499ef98549e8702b320ba6a7830 -md5=536b72b7ba5b0075fb137137c00e5773 -cksum=1481984929 +sha1=a55050ca07fb42c6f9e9a479b6f80be6f1f77886 +md5=abd887865df30c02609210a86cb953b1 +cksum=322384454 +upstream_url=https://src.fedoraproject.org/repo/pkgs/ntl/ntl-11.5.1.tar.gz/sha512/cf1f642b8a0f9cdc6dda888e07183817dc67ff494e56a852053aeb15b3d2a0e61fbc05824779c5d1f20b8115fba6f97266acf7e0b0b527c25df5989c86d5928f/ntl-11.5.1.tar.gz diff --git a/build/pkgs/ntl/math b/build/pkgs/ntl/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/ntl/package-version.txt b/build/pkgs/ntl/package-version.txt index 72f04fb9d60..326ba189b47 100644 --- a/build/pkgs/ntl/package-version.txt +++ b/build/pkgs/ntl/package-version.txt @@ -1 +1 @@ -11.4.3 +11.5.1 diff --git a/build/pkgs/numpy/math b/build/pkgs/numpy/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/openblas/math b/build/pkgs/openblas/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/ore_algebra/math b/build/pkgs/ore_algebra/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/ore_algebra/spkg-configure.m4 b/build/pkgs/ore_algebra/spkg-configure.m4 new file mode 100644 index 00000000000..4f230702942 --- /dev/null +++ b/build/pkgs/ore_algebra/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([ore_algebra], [SAGE_PYTHON_PACKAGE_CHECK([ore_algebra])]) diff --git a/build/pkgs/osqp_python/math b/build/pkgs/osqp_python/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/osqp_python/spkg-configure.m4 b/build/pkgs/osqp_python/spkg-configure.m4 new file mode 100644 index 00000000000..ce4821c39da --- /dev/null +++ b/build/pkgs/osqp_python/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([osqp_python], [SAGE_PYTHON_PACKAGE_CHECK([osqp_python])]) diff --git a/build/pkgs/overrides/SPKG.rst b/build/pkgs/overrides/SPKG.rst index bc6dcdbfdd6..61cc5173cf2 100644 --- a/build/pkgs/overrides/SPKG.rst +++ b/build/pkgs/overrides/SPKG.rst @@ -1,10 +1,10 @@ -overrides: A decorator to automatically detect mismatch when overriding a method. -================================================================================= +overrides: Decorator to automatically detect mismatch when overriding a method +============================================================================== Description ----------- -A decorator to automatically detect mismatch when overriding a method. +Decorator to automatically detect mismatch when overriding a method License ------- diff --git a/build/pkgs/overrides/spkg-configure.m4 b/build/pkgs/overrides/spkg-configure.m4 new file mode 100644 index 00000000000..52a1e2fcd3c --- /dev/null +++ b/build/pkgs/overrides/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([overrides], [SAGE_PYTHON_PACKAGE_CHECK([overrides])]) diff --git a/build/pkgs/p_group_cohomology/math b/build/pkgs/p_group_cohomology/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/p_group_cohomology/spkg-configure.m4 b/build/pkgs/p_group_cohomology/spkg-configure.m4 new file mode 100644 index 00000000000..3934f8d0751 --- /dev/null +++ b/build/pkgs/p_group_cohomology/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([p_group_cohomology], [SAGE_PYTHON_PACKAGE_CHECK([p_group_cohomology])]) diff --git a/build/pkgs/palettable/spkg-configure.m4 b/build/pkgs/palettable/spkg-configure.m4 new file mode 100644 index 00000000000..76d3715e9fd --- /dev/null +++ b/build/pkgs/palettable/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([palettable], [SAGE_PYTHON_PACKAGE_CHECK([palettable])]) diff --git a/build/pkgs/palp/math b/build/pkgs/palp/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pandoc/front-end b/build/pkgs/pandoc/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pandoc_attributes/spkg-configure.m4 b/build/pkgs/pandoc_attributes/spkg-configure.m4 new file mode 100644 index 00000000000..57aba6b68ba --- /dev/null +++ b/build/pkgs/pandoc_attributes/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pandoc_attributes], [SAGE_PYTHON_PACKAGE_CHECK([pandoc_attributes])]) diff --git a/build/pkgs/papilo/math b/build/pkgs/papilo/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pari/math b/build/pkgs/pari/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pari_elldata/math b/build/pkgs/pari_elldata/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pari_galdata/math b/build/pkgs/pari_galdata/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pari_galpol/math b/build/pkgs/pari_galpol/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pari_jupyter/SPKG.rst b/build/pkgs/pari_jupyter/SPKG.rst index e4b26710359..87a9ab9cf71 100644 --- a/build/pkgs/pari_jupyter/SPKG.rst +++ b/build/pkgs/pari_jupyter/SPKG.rst @@ -1,10 +1,10 @@ -pari_jupyter: A Jupyter kernel for PARI/GP -========================================== +pari_jupyter: Jupyter kernel for PARI/GP +======================================== Description ----------- -A Jupyter kernel for PARI/GP +Jupyter kernel for PARI/GP License ------- diff --git a/build/pkgs/pari_jupyter/math b/build/pkgs/pari_jupyter/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pari_jupyter/spkg-configure.m4 b/build/pkgs/pari_jupyter/spkg-configure.m4 new file mode 100644 index 00000000000..7e7f7ecbb0e --- /dev/null +++ b/build/pkgs/pari_jupyter/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pari_jupyter], [SAGE_PYTHON_PACKAGE_CHECK([pari_jupyter])]) diff --git a/build/pkgs/pari_nftables/math b/build/pkgs/pari_nftables/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pari_seadata/math b/build/pkgs/pari_seadata/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pari_seadata_small/math b/build/pkgs/pari_seadata_small/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pathspec/SPKG.rst b/build/pkgs/pathspec/SPKG.rst index 4d1ee72eba3..12d2b49b79a 100644 --- a/build/pkgs/pathspec/SPKG.rst +++ b/build/pkgs/pathspec/SPKG.rst @@ -1,10 +1,10 @@ -pathspec: Utility library for gitignore style pattern matching of file paths. -============================================================================= +pathspec: Utility library for gitignore style pattern matching of file paths +============================================================================ Description ----------- -Utility library for gitignore style pattern matching of file paths. +Utility library for gitignore style pattern matching of file paths License ------- diff --git a/build/pkgs/pdf2svg/SPKG.rst b/build/pkgs/pdf2svg/SPKG.rst index 2a87b276be0..a9dc895e8a6 100644 --- a/build/pkgs/pdf2svg/SPKG.rst +++ b/build/pkgs/pdf2svg/SPKG.rst @@ -1,5 +1,5 @@ -pdf2svg - PDF to SVG convertor -============================== +pdf2svg: PDF to SVG convertor +============================= Description ----------- diff --git a/build/pkgs/pdf2svg/front-end b/build/pkgs/pdf2svg/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/phitigra/SPKG.rst b/build/pkgs/phitigra/SPKG.rst index 253c9fee441..1f0d522f9c3 100644 --- a/build/pkgs/phitigra/SPKG.rst +++ b/build/pkgs/phitigra/SPKG.rst @@ -1,10 +1,10 @@ -phitigra: A graph editor for SageMath/Jupyter -============================================= +phitigra: Graph editor for SageMath/Jupyter +=========================================== Description ----------- -A graph editor for SageMath/Jupyter +Graph editor for SageMath/Jupyter License ------- diff --git a/build/pkgs/phitigra/math b/build/pkgs/phitigra/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/phitigra/spkg-configure.m4 b/build/pkgs/phitigra/spkg-configure.m4 new file mode 100644 index 00000000000..06fc3367f18 --- /dev/null +++ b/build/pkgs/phitigra/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([phitigra], [SAGE_PYTHON_PACKAGE_CHECK([phitigra])]) diff --git a/build/pkgs/pint/spkg-configure.m4 b/build/pkgs/pint/spkg-configure.m4 new file mode 100644 index 00000000000..bfa78b5f327 --- /dev/null +++ b/build/pkgs/pint/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pint], [SAGE_PYTHON_PACKAGE_CHECK([pint])]) diff --git a/build/pkgs/planarity/math b/build/pkgs/planarity/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/plantri/math b/build/pkgs/plantri/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/platformdirs/SPKG.rst b/build/pkgs/platformdirs/SPKG.rst index ef40ff09e0c..80f9641d5f6 100644 --- a/build/pkgs/platformdirs/SPKG.rst +++ b/build/pkgs/platformdirs/SPKG.rst @@ -1,10 +1,10 @@ -platformdirs: A small Python module for determining appropriate platform-specific dirs, e.g. a "user data dir". -=============================================================================================================== +platformdirs: Small Python package for determining appropriate platform-specific dirs, e.g. a "user data dir" +============================================================================================================= Description ----------- -A small Python module for determining appropriate platform-specific dirs, e.g. a "user data dir". +Small Python package for determining appropriate platform-specific dirs, e.g. a "user data dir" License ------- diff --git a/build/pkgs/pluggy/SPKG.rst b/build/pkgs/pluggy/SPKG.rst index b15967007ed..13f4f3ba3c4 100644 --- a/build/pkgs/pluggy/SPKG.rst +++ b/build/pkgs/pluggy/SPKG.rst @@ -1,10 +1,10 @@ -pluggy: plugin and hook calling mechanisms for python +pluggy: Plugin and hook calling mechanisms for python ===================================================== Description ----------- -plugin and hook calling mechanisms for python +Plugin and hook calling mechanisms for python License ------- diff --git a/build/pkgs/polylib/math b/build/pkgs/polylib/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/polymake/math b/build/pkgs/polymake/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/polytopes_db/math b/build/pkgs/polytopes_db/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/polytopes_db_4d/math b/build/pkgs/polytopes_db_4d/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/ppl/math b/build/pkgs/ppl/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pplpy/math b/build/pkgs/pplpy/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/primecount/math b/build/pkgs/primecount/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/primecountpy/math b/build/pkgs/primecountpy/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/primesieve/math b/build/pkgs/primesieve/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/psutil/spkg-configure.m4 b/build/pkgs/psutil/spkg-configure.m4 new file mode 100644 index 00000000000..ce714751c5f --- /dev/null +++ b/build/pkgs/psutil/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([psutil], [SAGE_PYTHON_PACKAGE_CHECK([psutil])]) diff --git a/build/pkgs/py/SPKG.rst b/build/pkgs/py/SPKG.rst index bf0409acda7..4e8657e33f2 100644 --- a/build/pkgs/py/SPKG.rst +++ b/build/pkgs/py/SPKG.rst @@ -1,10 +1,10 @@ -py: library with cross-python path, ini-parsing, io, code, log facilities +py: Library with cross-python path, ini-parsing, io, code, log facilities ========================================================================= Description ----------- -library with cross-python path, ini-parsing, io, code, log facilities +Library with cross-python path, ini-parsing, io, code, log facilities License ------- diff --git a/build/pkgs/pybtex/spkg-configure.m4 b/build/pkgs/pybtex/spkg-configure.m4 new file mode 100644 index 00000000000..77e95f59f47 --- /dev/null +++ b/build/pkgs/pybtex/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pybtex], [SAGE_PYTHON_PACKAGE_CHECK([pybtex])]) diff --git a/build/pkgs/pycosat/math b/build/pkgs/pycosat/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pycosat/spkg-configure.m4 b/build/pkgs/pycosat/spkg-configure.m4 new file mode 100644 index 00000000000..1d4969fa697 --- /dev/null +++ b/build/pkgs/pycosat/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pycosat], [SAGE_PYTHON_PACKAGE_CHECK([pycosat])]) diff --git a/build/pkgs/pycryptosat/math b/build/pkgs/pycryptosat/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pycryptosat/spkg-configure.m4 b/build/pkgs/pycryptosat/spkg-configure.m4 new file mode 100644 index 00000000000..542bb5676ef --- /dev/null +++ b/build/pkgs/pycryptosat/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pycryptosat], [SAGE_PYTHON_PACKAGE_CHECK([pycryptosat])]) diff --git a/build/pkgs/pycygwin/spkg-configure.m4 b/build/pkgs/pycygwin/spkg-configure.m4 new file mode 100644 index 00000000000..7876a693d7b --- /dev/null +++ b/build/pkgs/pycygwin/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pycygwin], [SAGE_PYTHON_PACKAGE_CHECK([pycygwin])]) diff --git a/build/pkgs/pygraphviz/front-end b/build/pkgs/pygraphviz/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pygraphviz/spkg-configure.m4 b/build/pkgs/pygraphviz/spkg-configure.m4 new file mode 100644 index 00000000000..7ae8d558c95 --- /dev/null +++ b/build/pkgs/pygraphviz/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pygraphviz], [SAGE_PYTHON_PACKAGE_CHECK([pygraphviz])]) diff --git a/build/pkgs/pynormaliz/math b/build/pkgs/pynormaliz/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pynormaliz/spkg-configure.m4 b/build/pkgs/pynormaliz/spkg-configure.m4 new file mode 100644 index 00000000000..7bfc840f137 --- /dev/null +++ b/build/pkgs/pynormaliz/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pynormaliz], [SAGE_PYTHON_PACKAGE_CHECK([pynormaliz])]) diff --git a/build/pkgs/pyppeteer/spkg-configure.m4 b/build/pkgs/pyppeteer/spkg-configure.m4 new file mode 100644 index 00000000000..371c5ba346a --- /dev/null +++ b/build/pkgs/pyppeteer/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pyppeteer], [SAGE_PYTHON_PACKAGE_CHECK([pyppeteer])]) diff --git a/build/pkgs/pyproject_api/spkg-configure.m4 b/build/pkgs/pyproject_api/spkg-configure.m4 new file mode 100644 index 00000000000..19951851d6b --- /dev/null +++ b/build/pkgs/pyproject_api/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pyproject_api], [SAGE_PYTHON_PACKAGE_CHECK([pyproject_api])]) diff --git a/build/pkgs/pyscipopt/math b/build/pkgs/pyscipopt/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pyscipopt/spkg-configure.m4 b/build/pkgs/pyscipopt/spkg-configure.m4 new file mode 100644 index 00000000000..4e1a264fe3e --- /dev/null +++ b/build/pkgs/pyscipopt/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pyscipopt], [SAGE_PYTHON_PACKAGE_CHECK([pyscipopt])]) diff --git a/build/pkgs/pysingular/math b/build/pkgs/pysingular/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/pysingular/spkg-configure.m4 b/build/pkgs/pysingular/spkg-configure.m4 new file mode 100644 index 00000000000..30ef02f02f7 --- /dev/null +++ b/build/pkgs/pysingular/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pysingular], [SAGE_PYTHON_PACKAGE_CHECK([pysingular])]) diff --git a/build/pkgs/pytest/spkg-configure.m4 b/build/pkgs/pytest/spkg-configure.m4 new file mode 100644 index 00000000000..4d91d44fc27 --- /dev/null +++ b/build/pkgs/pytest/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pytest], [SAGE_PYTHON_PACKAGE_CHECK([pytest])]) diff --git a/build/pkgs/pytest_mock/spkg-configure.m4 b/build/pkgs/pytest_mock/spkg-configure.m4 new file mode 100644 index 00000000000..da82e85e76a --- /dev/null +++ b/build/pkgs/pytest_mock/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pytest_mock], [SAGE_PYTHON_PACKAGE_CHECK([pytest_mock])]) diff --git a/build/pkgs/pytest_xdist/spkg-configure.m4 b/build/pkgs/pytest_xdist/spkg-configure.m4 new file mode 100644 index 00000000000..62150d6edff --- /dev/null +++ b/build/pkgs/pytest_xdist/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pytest_xdist], [SAGE_PYTHON_PACKAGE_CHECK([pytest_xdist])]) diff --git a/build/pkgs/python_build/spkg-configure.m4 b/build/pkgs/python_build/spkg-configure.m4 new file mode 100644 index 00000000000..6d4543116f1 --- /dev/null +++ b/build/pkgs/python_build/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([python_build], [SAGE_PYTHON_PACKAGE_CHECK([python_build])]) diff --git a/build/pkgs/python_igraph/math b/build/pkgs/python_igraph/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/python_igraph/spkg-configure.m4 b/build/pkgs/python_igraph/spkg-configure.m4 new file mode 100644 index 00000000000..392ceb0a8f1 --- /dev/null +++ b/build/pkgs/python_igraph/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([python_igraph], [SAGE_PYTHON_PACKAGE_CHECK([python_igraph])]) diff --git a/build/pkgs/python_json_logger/SPKG.rst b/build/pkgs/python_json_logger/SPKG.rst index 8e25e0f8387..9cd588b3023 100644 --- a/build/pkgs/python_json_logger/SPKG.rst +++ b/build/pkgs/python_json_logger/SPKG.rst @@ -1,10 +1,10 @@ -python_json_logger: A python library adding a json log formatter -================================================================ +python_json_logger: Python library adding a json log formatter +============================================================== Description ----------- -A python library adding a json log formatter +Python library adding a json log formatter License ------- diff --git a/build/pkgs/python_json_logger/spkg-configure.m4 b/build/pkgs/python_json_logger/spkg-configure.m4 new file mode 100644 index 00000000000..23245405de8 --- /dev/null +++ b/build/pkgs/python_json_logger/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([python_json_logger], [SAGE_PYTHON_PACKAGE_CHECK([python_json_logger])]) diff --git a/build/pkgs/pytz_deprecation_shim/spkg-configure.m4 b/build/pkgs/pytz_deprecation_shim/spkg-configure.m4 new file mode 100644 index 00000000000..89e68bb45c4 --- /dev/null +++ b/build/pkgs/pytz_deprecation_shim/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pytz_deprecation_shim], [SAGE_PYTHON_PACKAGE_CHECK([pytz_deprecation_shim])]) diff --git a/build/pkgs/pyx/spkg-configure.m4 b/build/pkgs/pyx/spkg-configure.m4 new file mode 100644 index 00000000000..91199e8574b --- /dev/null +++ b/build/pkgs/pyx/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pyx], [SAGE_PYTHON_PACKAGE_CHECK([pyx])]) diff --git a/build/pkgs/pyyaml/spkg-configure.m4 b/build/pkgs/pyyaml/spkg-configure.m4 new file mode 100644 index 00000000000..05d245261c8 --- /dev/null +++ b/build/pkgs/pyyaml/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pyyaml], [SAGE_PYTHON_PACKAGE_CHECK([pyyaml])]) diff --git a/build/pkgs/qdldl_python/math b/build/pkgs/qdldl_python/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/qdldl_python/spkg-configure.m4 b/build/pkgs/qdldl_python/spkg-configure.m4 new file mode 100644 index 00000000000..a0408be6143 --- /dev/null +++ b/build/pkgs/qdldl_python/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([qdldl_python], [SAGE_PYTHON_PACKAGE_CHECK([qdldl_python])]) diff --git a/build/pkgs/qepcad/math b/build/pkgs/qepcad/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/qhull/math b/build/pkgs/qhull/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/r/math b/build/pkgs/r/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/r_jupyter/math b/build/pkgs/r_jupyter/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/referencing/spkg-configure.m4 b/build/pkgs/referencing/spkg-configure.m4 new file mode 100644 index 00000000000..6bf88a31d8f --- /dev/null +++ b/build/pkgs/referencing/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([referencing], [SAGE_PYTHON_PACKAGE_CHECK([referencing])]) diff --git a/build/pkgs/rfc3339_validator/spkg-configure.m4 b/build/pkgs/rfc3339_validator/spkg-configure.m4 new file mode 100644 index 00000000000..0746c1bb785 --- /dev/null +++ b/build/pkgs/rfc3339_validator/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([rfc3339_validator], [SAGE_PYTHON_PACKAGE_CHECK([rfc3339_validator])]) diff --git a/build/pkgs/rfc3986_validator/spkg-configure.m4 b/build/pkgs/rfc3986_validator/spkg-configure.m4 new file mode 100644 index 00000000000..e6c5e896a46 --- /dev/null +++ b/build/pkgs/rfc3986_validator/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([rfc3986_validator], [SAGE_PYTHON_PACKAGE_CHECK([rfc3986_validator])]) diff --git a/build/pkgs/rpy2/math b/build/pkgs/rpy2/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/rst2ipynb/front-end b/build/pkgs/rst2ipynb/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/rst2ipynb/spkg-configure.m4 b/build/pkgs/rst2ipynb/spkg-configure.m4 new file mode 100644 index 00000000000..48b6c7a04a7 --- /dev/null +++ b/build/pkgs/rst2ipynb/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([rst2ipynb], [SAGE_PYTHON_PACKAGE_CHECK([rst2ipynb])]) diff --git a/build/pkgs/rubiks/math b/build/pkgs/rubiks/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/rw/math b/build/pkgs/rw/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/saclib/math b/build/pkgs/saclib/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/sage_conf/install-requires.txt b/build/pkgs/sage_conf/install-requires.txt index b73225585e4..f85b4cdfcb8 100644 --- a/build/pkgs/sage_conf/install-requires.txt +++ b/build/pkgs/sage_conf/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-conf ~= 10.3rc4 +sage-conf ~= 10.4b0 diff --git a/build/pkgs/sage_docbuild/install-requires.txt b/build/pkgs/sage_docbuild/install-requires.txt index 7d8263cbc62..224b629eea2 100644 --- a/build/pkgs/sage_docbuild/install-requires.txt +++ b/build/pkgs/sage_docbuild/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-docbuild ~= 10.3rc4 +sage-docbuild ~= 10.4b0 diff --git a/build/pkgs/sage_flatsurf/SPKG.rst b/build/pkgs/sage_flatsurf/SPKG.rst index 51e815d8a90..82c4d1c0562 100644 --- a/build/pkgs/sage_flatsurf/SPKG.rst +++ b/build/pkgs/sage_flatsurf/SPKG.rst @@ -1,5 +1,5 @@ -sage_flatsurf: computation with flat surfaces -============================================= +sage_flatsurf: Flat surfaces in SageMath +======================================== Description ----------- diff --git a/build/pkgs/sage_flatsurf/math b/build/pkgs/sage_flatsurf/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/sage_numerical_backends_coin/math b/build/pkgs/sage_numerical_backends_coin/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/sage_numerical_backends_cplex/math b/build/pkgs/sage_numerical_backends_cplex/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/sage_numerical_backends_gurobi/math b/build/pkgs/sage_numerical_backends_gurobi/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/sage_setup/install-requires.txt b/build/pkgs/sage_setup/install-requires.txt index 89568499d00..a37d8cf5709 100644 --- a/build/pkgs/sage_setup/install-requires.txt +++ b/build/pkgs/sage_setup/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-setup ~= 10.3rc4 +sage-setup ~= 10.4b0 diff --git a/build/pkgs/sage_sws2rst/front-end b/build/pkgs/sage_sws2rst/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/sage_sws2rst/install-requires.txt b/build/pkgs/sage_sws2rst/install-requires.txt index 3cc53bf8f37..c23f8d227fb 100644 --- a/build/pkgs/sage_sws2rst/install-requires.txt +++ b/build/pkgs/sage_sws2rst/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-sws2rst ~= 10.3rc4 +sage-sws2rst ~= 10.4b0 diff --git a/build/pkgs/sagelib/install-requires.txt b/build/pkgs/sagelib/install-requires.txt index 417573d6f4d..60adfbe95bf 100644 --- a/build/pkgs/sagelib/install-requires.txt +++ b/build/pkgs/sagelib/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-standard ~= 10.3rc4 +sagemath-standard ~= 10.4b0 diff --git a/build/pkgs/sagelib/spkg-configure.m4 b/build/pkgs/sagelib/spkg-configure.m4 new file mode 100644 index 00000000000..7c833a729dc --- /dev/null +++ b/build/pkgs/sagelib/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([sagelib], [SAGE_PYTHON_PACKAGE_CHECK([sagelib])]) diff --git a/build/pkgs/sagemath_bliss/install-requires.txt b/build/pkgs/sagemath_bliss/install-requires.txt index 699d6d369b9..e6f0797c09a 100644 --- a/build/pkgs/sagemath_bliss/install-requires.txt +++ b/build/pkgs/sagemath_bliss/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-bliss ~= 10.3rc4 +sagemath-bliss ~= 10.4b0 diff --git a/build/pkgs/sagemath_categories/install-requires.txt b/build/pkgs/sagemath_categories/install-requires.txt index e78428baa84..bf45cbd1fa8 100644 --- a/build/pkgs/sagemath_categories/install-requires.txt +++ b/build/pkgs/sagemath_categories/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-categories ~= 10.3rc4 +sagemath-categories ~= 10.4b0 diff --git a/build/pkgs/sagemath_coxeter3/install-requires.txt b/build/pkgs/sagemath_coxeter3/install-requires.txt index 1e3289d9826..ade373da6e1 100644 --- a/build/pkgs/sagemath_coxeter3/install-requires.txt +++ b/build/pkgs/sagemath_coxeter3/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-coxeter3 ~= 10.3rc4 +sagemath-coxeter3 ~= 10.4b0 diff --git a/build/pkgs/sagemath_environment/install-requires.txt b/build/pkgs/sagemath_environment/install-requires.txt index 0ad75840b0b..33f664a19ad 100644 --- a/build/pkgs/sagemath_environment/install-requires.txt +++ b/build/pkgs/sagemath_environment/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-environment ~= 10.3rc4 +sagemath-environment ~= 10.4b0 diff --git a/build/pkgs/sagemath_mcqd/install-requires.txt b/build/pkgs/sagemath_mcqd/install-requires.txt index 28daaa77a8b..64980c5b61c 100644 --- a/build/pkgs/sagemath_mcqd/install-requires.txt +++ b/build/pkgs/sagemath_mcqd/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-mcqd ~= 10.3rc4 +sagemath-mcqd ~= 10.4b0 diff --git a/build/pkgs/sagemath_meataxe/install-requires.txt b/build/pkgs/sagemath_meataxe/install-requires.txt index 43ff0fd958e..c58a8112bb7 100644 --- a/build/pkgs/sagemath_meataxe/install-requires.txt +++ b/build/pkgs/sagemath_meataxe/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-meataxe ~= 10.3rc4 +sagemath-meataxe ~= 10.4b0 diff --git a/build/pkgs/sagemath_objects/install-requires.txt b/build/pkgs/sagemath_objects/install-requires.txt index 385101bcc9a..00fb377cc1b 100644 --- a/build/pkgs/sagemath_objects/install-requires.txt +++ b/build/pkgs/sagemath_objects/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-objects ~= 10.3rc4 +sagemath-objects ~= 10.4b0 diff --git a/build/pkgs/sagemath_repl/install-requires.txt b/build/pkgs/sagemath_repl/install-requires.txt index ef39766e021..bd684d1f75b 100644 --- a/build/pkgs/sagemath_repl/install-requires.txt +++ b/build/pkgs/sagemath_repl/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-repl ~= 10.3rc4 +sagemath-repl ~= 10.4b0 diff --git a/build/pkgs/sagemath_sirocco/install-requires.txt b/build/pkgs/sagemath_sirocco/install-requires.txt index 5b072cd2588..050fb066235 100644 --- a/build/pkgs/sagemath_sirocco/install-requires.txt +++ b/build/pkgs/sagemath_sirocco/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-sirocco ~= 10.3rc4 +sagemath-sirocco ~= 10.4b0 diff --git a/build/pkgs/sagemath_tdlib/install-requires.txt b/build/pkgs/sagemath_tdlib/install-requires.txt index 9ed823b39ee..3e1142acd56 100644 --- a/build/pkgs/sagemath_tdlib/install-requires.txt +++ b/build/pkgs/sagemath_tdlib/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-tdlib ~= 10.3rc4 +sagemath-tdlib ~= 10.4b0 diff --git a/build/pkgs/sagenb_export/front-end b/build/pkgs/sagenb_export/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/sagenb_export/spkg-configure.m4 b/build/pkgs/sagenb_export/spkg-configure.m4 new file mode 100644 index 00000000000..b09db05b663 --- /dev/null +++ b/build/pkgs/sagenb_export/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([sagenb_export], [SAGE_PYTHON_PACKAGE_CHECK([sagenb_export])]) diff --git a/build/pkgs/sagetex/front-end b/build/pkgs/sagetex/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/sagetex/spkg-configure.m4 b/build/pkgs/sagetex/spkg-configure.m4 new file mode 100644 index 00000000000..13d44a7ef7e --- /dev/null +++ b/build/pkgs/sagetex/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([sagetex], [SAGE_PYTHON_PACKAGE_CHECK([sagetex])]) diff --git a/build/pkgs/scip/math b/build/pkgs/scip/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/scip_sdp/math b/build/pkgs/scip_sdp/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/scipy/math b/build/pkgs/scipy/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/scs/math b/build/pkgs/scs/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/scs/spkg-configure.m4 b/build/pkgs/scs/spkg-configure.m4 new file mode 100644 index 00000000000..21cf4812045 --- /dev/null +++ b/build/pkgs/scs/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([scs], [SAGE_PYTHON_PACKAGE_CHECK([scs])]) diff --git a/build/pkgs/singular/math b/build/pkgs/singular/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/singular_jupyter/math b/build/pkgs/singular_jupyter/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/singular_jupyter/spkg-configure.m4 b/build/pkgs/singular_jupyter/spkg-configure.m4 new file mode 100644 index 00000000000..4b0124dad10 --- /dev/null +++ b/build/pkgs/singular_jupyter/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([singular_jupyter], [SAGE_PYTHON_PACKAGE_CHECK([singular_jupyter])]) diff --git a/build/pkgs/sirocco/math b/build/pkgs/sirocco/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/slabbe/math b/build/pkgs/slabbe/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/slabbe/spkg-configure.m4 b/build/pkgs/slabbe/spkg-configure.m4 new file mode 100644 index 00000000000..3d5663df620 --- /dev/null +++ b/build/pkgs/slabbe/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([slabbe], [SAGE_PYTHON_PACKAGE_CHECK([slabbe])]) diff --git a/build/pkgs/snappy/math b/build/pkgs/snappy/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/snappy/spkg-configure.m4 b/build/pkgs/snappy/spkg-configure.m4 new file mode 100644 index 00000000000..4ffb99a0045 --- /dev/null +++ b/build/pkgs/snappy/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([snappy], [SAGE_PYTHON_PACKAGE_CHECK([snappy])]) diff --git a/build/pkgs/sniffio/spkg-configure.m4 b/build/pkgs/sniffio/spkg-configure.m4 new file mode 100644 index 00000000000..c24914e3780 --- /dev/null +++ b/build/pkgs/sniffio/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([sniffio], [SAGE_PYTHON_PACKAGE_CHECK([sniffio])]) diff --git a/build/pkgs/soplex/math b/build/pkgs/soplex/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/soupsieve/SPKG.rst b/build/pkgs/soupsieve/SPKG.rst index 401519ceec9..09be828d5b5 100644 --- a/build/pkgs/soupsieve/SPKG.rst +++ b/build/pkgs/soupsieve/SPKG.rst @@ -1,10 +1,10 @@ -soupsieve: A modern CSS selector implementation for Beautiful Soup. -=================================================================== +soupsieve: Modern CSS selector implementation for Beautiful Soup +================================================================ Description ----------- -A modern CSS selector implementation for Beautiful Soup. +Modern CSS selector implementation for Beautiful Soup License ------- diff --git a/build/pkgs/soupsieve/spkg-configure.m4 b/build/pkgs/soupsieve/spkg-configure.m4 new file mode 100644 index 00000000000..5d7588f7974 --- /dev/null +++ b/build/pkgs/soupsieve/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([soupsieve], [SAGE_PYTHON_PACKAGE_CHECK([soupsieve])]) diff --git a/build/pkgs/sphinx_basic_ng/SPKG.rst b/build/pkgs/sphinx_basic_ng/SPKG.rst index c137da5c63b..a09b406026f 100644 --- a/build/pkgs/sphinx_basic_ng/SPKG.rst +++ b/build/pkgs/sphinx_basic_ng/SPKG.rst @@ -1,10 +1,10 @@ -sphinx_basic_ng: A modern skeleton for Sphinx themes. -===================================================== +sphinx_basic_ng: Modern skeleton for Sphinx themes +================================================== Description ----------- -A modern skeleton for Sphinx themes. +Modern skeleton for Sphinx themes License ------- diff --git a/build/pkgs/sphinx_copybutton/SPKG.rst b/build/pkgs/sphinx_copybutton/SPKG.rst index 2a4d04e7bbf..eefeb83b8b6 100644 --- a/build/pkgs/sphinx_copybutton/SPKG.rst +++ b/build/pkgs/sphinx_copybutton/SPKG.rst @@ -1,10 +1,10 @@ -sphinx_copybutton: Add a copy button to each of your code cells. -================================================================ +sphinx_copybutton: Add a copy button to each of your code cells +=============================================================== Description ----------- -Add a copy button to each of your code cells. +Add a copy button to each of your code cells License ------- diff --git a/build/pkgs/sphinx_inline_tabs/SPKG.rst b/build/pkgs/sphinx_inline_tabs/SPKG.rst index 1360f35d925..1d7164453e6 100644 --- a/build/pkgs/sphinx_inline_tabs/SPKG.rst +++ b/build/pkgs/sphinx_inline_tabs/SPKG.rst @@ -1,10 +1,10 @@ -sphinx_inline_tabs: Add inline tabbed content to your Sphinx documentation. -=========================================================================== +sphinx_inline_tabs: Add inline tabbed content to your Sphinx documentation +========================================================================== Description ----------- -Add inline tabbed content to your Sphinx documentation. +Add inline tabbed content to your Sphinx documentation License ------- diff --git a/build/pkgs/sqlalchemy/spkg-configure.m4 b/build/pkgs/sqlalchemy/spkg-configure.m4 new file mode 100644 index 00000000000..dba8bc35444 --- /dev/null +++ b/build/pkgs/sqlalchemy/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([sqlalchemy], [SAGE_PYTHON_PACKAGE_CHECK([sqlalchemy])]) diff --git a/build/pkgs/suitesparse/math b/build/pkgs/suitesparse/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/surf/math b/build/pkgs/surf/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/surface_dynamics/math b/build/pkgs/surface_dynamics/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/surface_dynamics/spkg-configure.m4 b/build/pkgs/surface_dynamics/spkg-configure.m4 new file mode 100644 index 00000000000..0426cbfe45d --- /dev/null +++ b/build/pkgs/surface_dynamics/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([surface_dynamics], [SAGE_PYTHON_PACKAGE_CHECK([surface_dynamics])]) diff --git a/build/pkgs/symengine/math b/build/pkgs/symengine/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/symengine_py/math b/build/pkgs/symengine_py/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/symmetrica/math b/build/pkgs/symmetrica/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/sympow/math b/build/pkgs/sympow/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/sympy/math b/build/pkgs/sympy/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/tachyon/front-end b/build/pkgs/tachyon/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/tdlib/math b/build/pkgs/tdlib/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/texlive/front-end b/build/pkgs/texlive/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/threejs/front-end b/build/pkgs/threejs/front-end new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/tides/math b/build/pkgs/tides/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/topcom/math b/build/pkgs/topcom/math new file mode 100644 index 00000000000..e69de29bb2d diff --git a/build/pkgs/trove_classifiers/SPKG.rst b/build/pkgs/trove_classifiers/SPKG.rst index 4e1e7d08746..c0f7ef6ae05 100644 --- a/build/pkgs/trove_classifiers/SPKG.rst +++ b/build/pkgs/trove_classifiers/SPKG.rst @@ -1,10 +1,10 @@ -trove_classifiers: Canonical source for classifiers on PyPI (pypi.org). -======================================================================= +trove_classifiers: Canonical source for classifiers on PyPI (pypi.org) +====================================================================== Description ----------- -Canonical source for classifiers on PyPI (pypi.org). +Canonical source for classifiers on PyPI (pypi.org) License ------- diff --git a/build/pkgs/types_python_dateutil/spkg-configure.m4 b/build/pkgs/types_python_dateutil/spkg-configure.m4 new file mode 100644 index 00000000000..d4a0940919d --- /dev/null +++ b/build/pkgs/types_python_dateutil/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([types_python_dateutil], [SAGE_PYTHON_PACKAGE_CHECK([types_python_dateutil])]) diff --git a/build/pkgs/typing_extensions/install-requires.txt b/build/pkgs/typing_extensions/install-requires.txt index d10f4dffbd3..e1a912109ed 100644 --- a/build/pkgs/typing_extensions/install-requires.txt +++ b/build/pkgs/typing_extensions/install-requires.txt @@ -1,3 +1,3 @@ # According to https://github.com/python/typing_extensions/blob/main/CHANGELOG.md, # version 4.4.0 adds another Python 3.11 typing backport -typing_extensions >= 4.4.0; python_version<"3.11" +typing_extensions >= 4.4.0; python_version<'3.11' diff --git a/build/pkgs/typing_extensions/spkg-configure.m4 b/build/pkgs/typing_extensions/spkg-configure.m4 index cbb75fec955..fec3271a44b 100644 --- a/build/pkgs/typing_extensions/spkg-configure.m4 +++ b/build/pkgs/typing_extensions/spkg-configure.m4 @@ -1,3 +1,24 @@ SAGE_SPKG_CONFIGURE([typing_extensions],[ SAGE_PYTHON_PACKAGE_CHECK([typing_extensions]) +],[ + # Three of our python packages are backport packages providing + # python-3.11 features (see coding_in_python.rst): + # + # * importlib_metadata + # * importlib_resources + # * typing_extensions + # + # These packages are therefore not needed with >=python-3.11. Here + # we test for a python minor version component greater than or equal + # to 11, and mark this package as "not required" if we succeed. + AC_MSG_CHECKING([for >=python-3.11]) + + # Keep in mind that False (~ zero) in python is success in the shell + AS_IF(["${PYTHON_FOR_VENV}" -c "import sys; sys.exit(sys.version_info.minor < 11)"],[ + AC_MSG_RESULT([yes]) + sage_require_typing_extensions="no" + ],[ + AC_MSG_RESULT([no]) + ]) ]) + diff --git a/build/pkgs/uri_template/spkg-configure.m4 b/build/pkgs/uri_template/spkg-configure.m4 new file mode 100644 index 00000000000..35335475c2f --- /dev/null +++ b/build/pkgs/uri_template/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([uri_template], [SAGE_PYTHON_PACKAGE_CHECK([uri_template])]) diff --git a/build/pkgs/urllib3/SPKG.rst b/build/pkgs/urllib3/SPKG.rst index 966295796c4..f40dc17c993 100644 --- a/build/pkgs/urllib3/SPKG.rst +++ b/build/pkgs/urllib3/SPKG.rst @@ -1,10 +1,10 @@ -urllib3: HTTP library with thread-safe connection pooling, file post, and more. -=============================================================================== +urllib3: HTTP library with thread-safe connection pooling, file post, and more +============================================================================== Description ----------- -HTTP library with thread-safe connection pooling, file post, and more. +HTTP library with thread-safe connection pooling, file post, and more License ------- diff --git a/build/pkgs/webcolors/SPKG.rst b/build/pkgs/webcolors/SPKG.rst index 760b53943d2..043b971415e 100644 --- a/build/pkgs/webcolors/SPKG.rst +++ b/build/pkgs/webcolors/SPKG.rst @@ -1,10 +1,10 @@ -webcolors: A library for working with the color formats defined by HTML and CSS. -================================================================================ +webcolors: Library for working with the color formats defined by HTML and CSS +============================================================================= Description ----------- -A library for working with the color formats defined by HTML and CSS. +Library for working with the color formats defined by HTML and CSS License ------- diff --git a/build/pkgs/webcolors/spkg-configure.m4 b/build/pkgs/webcolors/spkg-configure.m4 new file mode 100644 index 00000000000..6bfc58db199 --- /dev/null +++ b/build/pkgs/webcolors/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([webcolors], [SAGE_PYTHON_PACKAGE_CHECK([webcolors])]) diff --git a/build/pkgs/websocket_client/spkg-configure.m4 b/build/pkgs/websocket_client/spkg-configure.m4 new file mode 100644 index 00000000000..67712d904b6 --- /dev/null +++ b/build/pkgs/websocket_client/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([websocket_client], [SAGE_PYTHON_PACKAGE_CHECK([websocket_client])]) diff --git a/build/sage_bootstrap/creator.py b/build/sage_bootstrap/creator.py index a9a0384e129..a7455b2ba26 100644 --- a/build/sage_bootstrap/creator.py +++ b/build/sage_bootstrap/creator.py @@ -63,6 +63,21 @@ def set_description(self, description, license, upstream_contact): Write the ``SPKG.rst`` file """ with open(os.path.join(self.path, 'SPKG.rst'), 'w+') as f: + # Attempt to bring title to a common style + if description.startswith(self.package_name + ':'): + description = description[len(self.package_name + ':'):] + if description.startswith(self.package_name + ' is'): + description = description[len(self.package_name + ' is'):] + description = description.strip() + if not description.endswith('etc.'): + description = description.rstrip('.') + if description.startswith('A ') or description.startswith('a '): + description = description[2:].strip() + if description.startswith('An ') or description.startswith('an '): + description = description[3:].strip() + if description: + description = description[0].upper() + description[1:] + def heading(title, char='-'): return '{0}\n{1}\n\n'.format(title, char * len(title)) if description: diff --git a/pkgs/sage-conf/VERSION.txt b/pkgs/sage-conf/VERSION.txt index 8c5dddbbe19..1d2c036dee5 100644 --- a/pkgs/sage-conf/VERSION.txt +++ b/pkgs/sage-conf/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.4.beta0 diff --git a/pkgs/sage-conf/_sage_conf/_conf.py.in b/pkgs/sage-conf/_sage_conf/_conf.py.in index ccc1c9695fb..54a59fdbacf 100644 --- a/pkgs/sage-conf/_sage_conf/_conf.py.in +++ b/pkgs/sage-conf/_sage_conf/_conf.py.in @@ -32,6 +32,8 @@ ECL_CONFIG = "@SAGE_ECL_CONFIG@".replace('${prefix}', SAGE_LOCAL) SAGE_NAUTY_BINS_PREFIX = "@SAGE_NAUTY_BINS_PREFIX@" +SAGE_ECMBIN = "@SAGE_ECMBIN@" + # Names or paths of the 4ti2 executables FOURTITWO_HILBERT = "@FOURTITWO_HILBERT@" FOURTITWO_MARKOV = "@FOURTITWO_MARKOV@" diff --git a/pkgs/sage-conf_conda/VERSION.txt b/pkgs/sage-conf_conda/VERSION.txt index 8c5dddbbe19..1d2c036dee5 100644 --- a/pkgs/sage-conf_conda/VERSION.txt +++ b/pkgs/sage-conf_conda/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.4.beta0 diff --git a/pkgs/sage-conf_pypi/VERSION.txt b/pkgs/sage-conf_pypi/VERSION.txt index 8c5dddbbe19..1d2c036dee5 100644 --- a/pkgs/sage-conf_pypi/VERSION.txt +++ b/pkgs/sage-conf_pypi/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.4.beta0 diff --git a/pkgs/sage-docbuild/VERSION.txt b/pkgs/sage-docbuild/VERSION.txt index 8c5dddbbe19..1d2c036dee5 100644 --- a/pkgs/sage-docbuild/VERSION.txt +++ b/pkgs/sage-docbuild/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.4.beta0 diff --git a/pkgs/sage-setup/VERSION.txt b/pkgs/sage-setup/VERSION.txt index 8c5dddbbe19..1d2c036dee5 100644 --- a/pkgs/sage-setup/VERSION.txt +++ b/pkgs/sage-setup/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.4.beta0 diff --git a/pkgs/sage-sws2rst/VERSION.txt b/pkgs/sage-sws2rst/VERSION.txt index 8c5dddbbe19..1d2c036dee5 100644 --- a/pkgs/sage-sws2rst/VERSION.txt +++ b/pkgs/sage-sws2rst/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.4.beta0 diff --git a/pkgs/sagemath-bliss/VERSION.txt b/pkgs/sagemath-bliss/VERSION.txt index 8c5dddbbe19..1d2c036dee5 100644 --- a/pkgs/sagemath-bliss/VERSION.txt +++ b/pkgs/sagemath-bliss/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.4.beta0 diff --git a/pkgs/sagemath-categories/VERSION.txt b/pkgs/sagemath-categories/VERSION.txt index 8c5dddbbe19..1d2c036dee5 100644 --- a/pkgs/sagemath-categories/VERSION.txt +++ b/pkgs/sagemath-categories/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.4.beta0 diff --git a/pkgs/sagemath-coxeter3/VERSION.txt b/pkgs/sagemath-coxeter3/VERSION.txt index 8c5dddbbe19..1d2c036dee5 100644 --- a/pkgs/sagemath-coxeter3/VERSION.txt +++ b/pkgs/sagemath-coxeter3/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.4.beta0 diff --git a/pkgs/sagemath-environment/VERSION.txt b/pkgs/sagemath-environment/VERSION.txt index 8c5dddbbe19..1d2c036dee5 100644 --- a/pkgs/sagemath-environment/VERSION.txt +++ b/pkgs/sagemath-environment/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.4.beta0 diff --git a/pkgs/sagemath-mcqd/VERSION.txt b/pkgs/sagemath-mcqd/VERSION.txt index 8c5dddbbe19..1d2c036dee5 100644 --- a/pkgs/sagemath-mcqd/VERSION.txt +++ b/pkgs/sagemath-mcqd/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.4.beta0 diff --git a/pkgs/sagemath-meataxe/VERSION.txt b/pkgs/sagemath-meataxe/VERSION.txt index 8c5dddbbe19..1d2c036dee5 100644 --- a/pkgs/sagemath-meataxe/VERSION.txt +++ b/pkgs/sagemath-meataxe/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.4.beta0 diff --git a/pkgs/sagemath-objects/VERSION.txt b/pkgs/sagemath-objects/VERSION.txt index 8c5dddbbe19..1d2c036dee5 100644 --- a/pkgs/sagemath-objects/VERSION.txt +++ b/pkgs/sagemath-objects/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.4.beta0 diff --git a/pkgs/sagemath-repl/VERSION.txt b/pkgs/sagemath-repl/VERSION.txt index 8c5dddbbe19..1d2c036dee5 100644 --- a/pkgs/sagemath-repl/VERSION.txt +++ b/pkgs/sagemath-repl/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.4.beta0 diff --git a/pkgs/sagemath-sirocco/VERSION.txt b/pkgs/sagemath-sirocco/VERSION.txt index 8c5dddbbe19..1d2c036dee5 100644 --- a/pkgs/sagemath-sirocco/VERSION.txt +++ b/pkgs/sagemath-sirocco/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.4.beta0 diff --git a/pkgs/sagemath-tdlib/VERSION.txt b/pkgs/sagemath-tdlib/VERSION.txt index 8c5dddbbe19..1d2c036dee5 100644 --- a/pkgs/sagemath-tdlib/VERSION.txt +++ b/pkgs/sagemath-tdlib/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.4.beta0 diff --git a/src/VERSION.txt b/src/VERSION.txt index 8c5dddbbe19..1d2c036dee5 100644 --- a/src/VERSION.txt +++ b/src/VERSION.txt @@ -1 +1 @@ -10.3.rc4 +10.4.beta0 diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index 0f483e4713f..d5b691b3ef1 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.3.rc4' -SAGE_RELEASE_DATE='2024-03-17' -SAGE_VERSION_BANNER='SageMath version 10.3.rc4, Release Date: 2024-03-17' +SAGE_VERSION='10.4.beta0' +SAGE_RELEASE_DATE='2024-03-25' +SAGE_VERSION_BANNER='SageMath version 10.4.beta0, Release Date: 2024-03-25' diff --git a/src/doc/Makefile b/src/doc/Makefile index 912eb5cb0e9..2d433b3727c 100644 --- a/src/doc/Makefile +++ b/src/doc/Makefile @@ -69,7 +69,7 @@ doc-html: doc-html-reference doc-html-other # Matches doc-pdf--developer, doc-pdf--reference-manifolds etc. doc-pdf--%: - sage --docbuild $(subst -,/,$(subst doc-pdf--,,$@)) pdf $(SAGE_DOCBUILD_OPTS) + LATEXOPTS="--file-line-error --interaction=batchmode" sage --docbuild $(subst -,/,$(subst doc-pdf--,,$@)) pdf $(SAGE_DOCBUILD_OPTS) # reference manual, pdf doc-pdf-reference: doc-inventory-reference diff --git a/src/doc/bootstrap b/src/doc/bootstrap index 35ad8b20879..32a31430554 100755 --- a/src/doc/bootstrap +++ b/src/doc/bootstrap @@ -86,6 +86,7 @@ if [ "${BOOTSTRAP_QUIET}" = "no" ]; then fi OUTPUT_INDEX="$OUTPUT_DIR"/index.rst cat > "$OUTPUT_INDEX" <> "$OUTPUT_INDEX" +cat >> "$OUTPUT_INDEX" <> "$OUTPUT_INDEX" cat >> "$OUTPUT_INDEX" <> "$OUTPUT_INDEX" +cat >> "$OUTPUT_INDEX" <> "$OUTPUT_INDEX" cat >> "$OUTPUT_INDEX" <> "$OUTPUT_INDEX" +cat >> "$OUTPUT_INDEX" <> "$OUTPUT_INDEX" +cat >> "$OUTPUT_INDEX" <> "$OUTPUT_INDEX" cat >> "$OUTPUT_INDEX" <> "$OUTPUT_INDEX" + +cat >> "$OUTPUT_INDEX" < { + elem.addEventListener("click", function(event) { + if (elem.nextElementSibling) { + if (elem.nextElementSibling.nextElementSibling) { + if (elem.nextElementSibling.nextElementSibling.querySelector('div[class="thebelab-code"]')) { + initThebelab(); + } + } + } + }); +}); diff --git a/src/doc/el/a_tour_of_sage/conf.py b/src/doc/el/a_tour_of_sage/conf.py new file mode 100644 index 00000000000..f451fdf5ab8 --- /dev/null +++ b/src/doc/el/a_tour_of_sage/conf.py @@ -0,0 +1,40 @@ +# nodoctest +# Numerical Sage documentation build configuration file, created by +# sphinx-quickstart on Sat Dec 6 11:08:04 2008. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# The contents of this file are pickled, so don't put values in the namespace +# that aren't pickleable (module imports are okay, they're removed automatically). +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +from sage_docbuild.conf import release +from sage_docbuild.conf import * # NOQA + +# Add any paths that contain custom static files (such as style sheets), +# relative to this directory to html_static_path. They are copied after the +# builtin static files, so a file named "default.css" will overwrite the +# builtin "default.css". html_common_static_path imported from sage_docbuild.conf +# contains common paths. +html_static_path = [] + html_common_static_path + +# General information about the project. +project = 'Περιήγηση στο Sage' +name = 'a_tour_of_sage' +language = 'el' + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +html_title = project + ' v' + release + +# Output file base name for HTML help builder. +htmlhelp_basename = name + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, document class [howto/manual]). +latex_documents = [ + ('index', name + '.tex', project, + 'The Sage Development Team', 'manual'), +] diff --git a/src/doc/el/a_tour_of_sage/eigen_plot.png b/src/doc/el/a_tour_of_sage/eigen_plot.png new file mode 100644 index 00000000000..925264764f1 Binary files /dev/null and b/src/doc/el/a_tour_of_sage/eigen_plot.png differ diff --git a/src/doc/el/a_tour_of_sage/index.rst b/src/doc/el/a_tour_of_sage/index.rst new file mode 100644 index 00000000000..8ac2414fcf8 --- /dev/null +++ b/src/doc/el/a_tour_of_sage/index.rst @@ -0,0 +1,143 @@ +===================== +Καλώς ήρθατε στο Sage +===================== + +Αυτή είναι μία σύντομη περιήγηση στο Sage και στην χρήση του ως αριθμομηχανή. + +Η γραμμή εντολών στο Sage εκκινεί με το μήνυμα προτροπής "``sage:``". Για +πειραματισμό με τα ακόλουθα παραδείγματα, αρκεί να εισαγάγετε το μέρος μετά το +μήνυμα προτροπής. + +:: + + sage: 3 + 5 + 8 + +Εάν χρησιμοποιείτε το Sage σε σημειωματάριο Jupyter, τότε -- παρομοίως -- +τοποθετείστε τα πάντα έπειτα του μηνύματος προτροπής εντός ενός κελιού +εισαγωγής, και πατήστε :kbd:`Shift-Enter` για να λάβετε την αντίστοιχη έξοδο. + +Το σύμβολο εκθέτη σημαίνει «ύψωση σε δύναμη». + +:: + + sage: 57.1^100 + 4.60904368661396e175 + +Υπολογίζουμε τον αντίστροφο ενός :math:`2 \times 2` πίνακα στο Sage. + +:: + + sage: matrix([[1, 2], [3, 4]])^(-1) + [ -2 1] + [ 3/2 -1/2] + +Εδώ υπολογίζουμε το ολοκλήρωμα μίας απλής συνάρτησης. + +:: + + sage: x = var('x') # δημιουργίας συμβολικής μεταβλητής + sage: integrate(sqrt(x) * sqrt(1 + x), x) + 1/4*((x + 1)^(3/2)/x^(3/2) + sqrt(x + 1)/sqrt(x))/((x + 1)^2/x^2 - 2*(x + 1)/x + 1) + - 1/8*log(sqrt(x + 1)/sqrt(x) + 1) + 1/8*log(sqrt(x + 1)/sqrt(x) - 1) + +Εδώ το Sage καλείται να λύσει μία δευτεροβάθμια εξίσωση. Το σύμβολο ``==`` +αντιπροσωπεύει την ισότητα στο Sage. + +:: + + sage: a = var('a') + sage: S = solve(x^2 + x == a, x); S + [x == -1/2*sqrt(4*a + 1) - 1/2, x == 1/2*sqrt(4*a + 1) - 1/2] + +Το αποτέλεσμα είναι μία λίστα από ισότητες. + +.. link + +:: + + sage: S[0].rhs() # δεξί μέρος της εξίσωσης (rhs = right hand side) + -1/2*sqrt(4*a + 1) - 1/2 + +Το Sage μπορεί να παραγάγει γραφήματα για διάφορες συναρτήσεις. + +:: + + sage: show(plot(sin(x) + sin(1.6*x), 0, 40)) + +.. image:: sin_plot.* + + +Το Sage είναι μία πολύ ισχυρή αριθμομηχανή. Για να το δείτε αυτό, δημιουργείστε +έναν :math:`500 \times 500` πίνακα με τυχαίους αριθμούς. + +:: + + sage: m = random_matrix(RDF, 500) + +Το Sage χρειάζεται ένα δευτερόλεπτο για τον υπολογισμό και την γραφική +παρουσίαση των ιδιοτιμών του πίνακα. + +.. link + +:: + + sage: e = m.eigenvalues() # περίπου 1 δευτερόλεπτο + sage: w = [(i, abs(e[i])) for i in range(len(e))] + sage: show(points(w)) + +.. image:: eigen_plot.* + + +Το Sage μπορεί να διαχειριστεί τεράστιους αριθμούς, ακόμα και με εκατομμύρια ή +δισεκατομμύρια ψηφία. + +:: + + sage: factorial(100) + 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000 + +:: + + sage: n = factorial(1000000) # περίπου 1 δευτερόλεπτο + sage: len(n.digits()) + 5565709 + +Εδώ υπολογίζουμε τουλάχιστον 100 ψηφία του αριθμού :math:`\pi`. + +:: + + sage: N(pi, digits=100) + 3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068 + +Εδώ το Sage παραγοντοποιεί ένα πολυώνυμο δύο μεταβλητών. + +:: + + sage: R. = QQ[] + sage: F = factor(x^99 + y^99) + sage: F + (x + y) * (x^2 - x*y + y^2) * (x^6 - x^3*y^3 + y^6) * + (x^10 - x^9*y + x^8*y^2 - x^7*y^3 + x^6*y^4 - x^5*y^5 + + x^4*y^6 - x^3*y^7 + x^2*y^8 - x*y^9 + y^10) * + (x^20 + x^19*y - x^17*y^3 - x^16*y^4 + x^14*y^6 + x^13*y^7 - + x^11*y^9 - x^10*y^10 - x^9*y^11 + x^7*y^13 + x^6*y^14 - + x^4*y^16 - x^3*y^17 + x*y^19 + y^20) * (x^60 + x^57*y^3 - + x^51*y^9 - x^48*y^12 + x^42*y^18 + x^39*y^21 - x^33*y^27 - + x^30*y^30 - x^27*y^33 + x^21*y^39 + x^18*y^42 - x^12*y^48 - + x^9*y^51 + x^3*y^57 + y^60) + sage: F.expand() + x^99 + y^99 + +Το Sage χρειάζεται λιγότερο από 1 δευτερόλεπτο για να υπολογίσει τους τρόπους +με τους οποίους ο αριθμός 100 εκατομμύρια μπορεί να γραφεί ως άθροισμα θετικών +ακεραίων. + +:: + + sage: z = Partitions(10^8).cardinality() # περίπου .1 δευτερόλεπτα + sage: z + 1760517045946249141360373894679135204009... + +Το Sage είναι το πιο προηγμένο λογισμικό ανοιχτού κώδικα για μαθηματικά στον +κόσμο. diff --git a/src/doc/el/a_tour_of_sage/sin_plot.png b/src/doc/el/a_tour_of_sage/sin_plot.png new file mode 100644 index 00000000000..ef4e87c69c1 Binary files /dev/null and b/src/doc/el/a_tour_of_sage/sin_plot.png differ diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index 764d3781d33..7471a9d4022 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -7,10 +7,10 @@ General Conventions =================== -There are many ways to contribute to Sage including sharing scripts -and Sage worksheets that implement new functionality using Sage, +There are many ways to contribute to Sage, including sharing scripts +and Jupyter notebooks that implement new functionality using Sage, improving to the Sage library, or to working on the many underlying -libraries distributed with Sage [1]_. +libraries distributed with Sage, see :ref:`spkg`. This guide focuses on editing the Sage library itself. Sage is not just about gathering together functionality. It is about @@ -20,10 +20,6 @@ mathematically. In the design of Sage, the semantics of objects, the definitions, etc., are informed by how the corresponding objects are used in everyday mathematics. -.. [1] - See https://www.sagemath.org/links-components.html for a full list - of packages shipped with every copy of Sage - To meet the goal of making Sage easy to read, maintain, and improve, all Python/Cython code that is included with Sage should adhere to the style conventions discussed in this chapter. diff --git a/src/doc/en/developer/coding_in_other.rst b/src/doc/en/developer/coding_in_other.rst index 6c378097796..54ff5d3b772 100644 --- a/src/doc/en/developer/coding_in_other.rst +++ b/src/doc/en/developer/coding_in_other.rst @@ -8,9 +8,9 @@ When writing code for Sage, use Python for the basic structure and interface. For speed, efficiency, or convenience, you can implement parts of the code using any of the following languages: :ref:`Cython `, C/C++, Fortran 95, GAP, Common Lisp, Singular, and -PARI/GP. You can also use all C/C++ libraries included with Sage -[SageComponents]_. And if you are okay with your code depending on -optional Sage packages, you can use Octave, or even Magma, +PARI/GP. You can also use all C/C++ libraries included with Sage, +see :ref:`spkg`. And if you are okay with your code depending on +external programs, you can use Octave, or even Magma, Mathematica, or Maple. In this chapter, we discuss interfaces between Sage and :ref:`PARI @@ -753,7 +753,3 @@ These are only excerpts from ``octave.py``; check that file for more definitions and examples. Look at other files in the directory ``SAGE_ROOT/src/sage/interfaces/`` for examples of interfaces to other software packages. - - -.. [SageComponents] See http://www.sagemath.org/links-components.html - for a list diff --git a/src/doc/en/developer/doctesting.rst b/src/doc/en/developer/doctesting.rst index f7c54bbbeec..e6324640ca1 100644 --- a/src/doc/en/developer/doctesting.rst +++ b/src/doc/en/developer/doctesting.rst @@ -26,7 +26,7 @@ this syntax: .. CODE-BLOCK:: text - /path/to/sage-x.y.z/sage -t [--long] /path/to/sage-x.y.z/path/to/module.py[x] + /path/to/sage_root/sage -t [--long] /path/to/sage_root/path/to/module.py[x] where ``--long`` is an optional argument (see :ref:`section-options` for more options). The version of ``sage`` used must match the version diff --git a/src/doc/en/developer/git_setup.rst b/src/doc/en/developer/git_setup.rst index f95b0d653da..bcd19a916a6 100644 --- a/src/doc/en/developer/git_setup.rst +++ b/src/doc/en/developer/git_setup.rst @@ -12,11 +12,9 @@ Installing Git Depending on your platform, use the following to install Git: -Debian / Ubuntu - Run ``sudo apt-get install git-core`` - -Fedora - Run ``sudo yum install git-core`` +Linux + See :ref:`spkg_git` for the installation command on your + Linux distribution. Windows (WSL) We strongly recommend to install the package using the Linux diff --git a/src/doc/en/developer/packaging.rst b/src/doc/en/developer/packaging.rst index ea391941c9c..3e014169a7c 100644 --- a/src/doc/en/developer/packaging.rst +++ b/src/doc/en/developer/packaging.rst @@ -776,20 +776,33 @@ optional packages. Package tags ------------ -You can mark a package as "huge" by placing an empty file named -``huge`` in the package directory. For example, the package -``polytopes_db_4d`` is a large database whose compressed tarball has a -size of 9 GB. - -For some other packages, we have placed an empty file named -``has_nonfree_dependencies`` in the package directory. This is to -indicate that Sage with this package installed cannot be -redistributed, and also that the package can only be installed after -installing some other, non-free package. - -We use these tags in our continuous integration scripts to filter +We use the following "tags" to organize our :ref:`index of packages in the +Reference Manual `. + +- Place an empty file named ``math`` in the package directory to make the + package appear in the "Mathematics" subsections of the index of standard, + optional, and experimental packages. + +- Place an empty file name ``front-end`` in the package directory to make + the package appear in the "Front-end, graphics, document preparation" + subsections. + +- Packages without these tags appear in the "Other dependencies" subsections. + +We use the following tags in our continuous integration scripts to filter out packages that we cannot or should not test automatically. +- You can mark a package as "huge" by placing an empty file named + ``huge`` in the package directory. For example, the package + ``polytopes_db_4d`` is a large database whose compressed tarball has a + size of 9 GB. + +- For some other packages, we have placed an empty file named + ``has_nonfree_dependencies`` in the package directory. This is to + indicate that Sage with this package installed cannot be + redistributed, and also that the package can only be installed after + installing some other, non-free package. + .. _section-trees: diff --git a/src/doc/en/developer/walkthrough.rst b/src/doc/en/developer/walkthrough.rst index 6e1b686989a..1faf7c271f2 100644 --- a/src/doc/en/developer/walkthrough.rst +++ b/src/doc/en/developer/walkthrough.rst @@ -61,8 +61,8 @@ Obtaining the Sage source code ============================== Obviously one needs the Sage source code to develop. You can use your -local installation of Sage, or (to start from scratch) download it -from our Sage repository on GitHub:: +local installation of Sage (if you installed Sage from source), or +(to start from scratch) download it from our Sage repository on GitHub:: [alice@localhost ~]$ git clone --origin upstream https://github.com/sagemath/sage.git Cloning into 'sage'... diff --git a/src/doc/en/installation/index.rst b/src/doc/en/installation/index.rst index 21d5649f2e0..f88a92dc5c7 100644 --- a/src/doc/en/installation/index.rst +++ b/src/doc/en/installation/index.rst @@ -16,6 +16,8 @@ SageMath release. **Where would you like to run SageMath?** Pick one of the following sections. +.. _installation-guide-macos: + macOS ===== @@ -51,6 +53,8 @@ macOS - Alternatively, build SageMath from source as described in section :ref:`sec-installation-from-sources`. +.. _installation-guide-windows: + Windows ======= @@ -58,31 +62,96 @@ Windows - **Yes, development:** - Enable Windows Subsystem for Linux (WSL) by following the - `official WSL setup guide - `_. Be - sure to do the steps to install WSL2 and set it as default. - Make sure to allocate enough RAM to WSL: 5GB is known to be enough, - 2GB might not allow you to build some packages. - Then go to the Microsoft Store and install Ubuntu (or another - Linux distribution). Start Ubuntu from the start menu. + Enable `Windows Subsystem for Linux (WSL) + `_ and install + Ubuntu as follows. + + - Make sure that hardware-assisted virtualization is enabled in + the EFI or BIOS of your system. If in doubt, refer to your + system's documentation for instructions on how to do this. + + - `Run the WSL install command as administrator. + `_ + This will install Ubuntu Linux. + + Note that the basic instructions in the linked article apply to + up-to-date installations of Windows 10 and 11, but there are + also links to the procedures for older builds of Windows 10. + + - If you had installed WSL previously or installed it using + different instructions, `verify that you are running WSL 2 + `_. - Then follow the instructions for development on Linux below. + - `Set up your Linux username and password. + `_ + Do not include any spaces in your username. + + - If your computer has less than 10GB of RAM, `change the WSL settings + `_ + to make at least 5GB of RAM available to WSL. + + Start Ubuntu from the Start menu. Then follow the instructions for + development on Linux below. - **No development:** - Enable Windows Subsystem for Linux (WSL) by following the - `official WSL setup guide - `_. Be - sure to do the steps to install WSL2 and set it as default. - Make sure to allocate enough RAM to WSL: 5GB is known to be enough, - 2GB might not allow you to build some packages. - Then go to the Microsoft Store and install Ubuntu (or another - Linux distribution). Start Ubuntu from the start menu. - - On the Linux running on WSL, you always have root access, so you - can use any of the installation methods described below for - Linux. + Enable `Windows Subsystem for Linux (WSL) + `_ and install + Ubuntu as follows. + + - Make sure that hardware-assisted virtualization is enabled in + the EFI or BIOS of your system. If in doubt, refer to your + system's documentation for instructions on how to do this. + + - `Run the WSL install command as administrator. + `_ + This will install Ubuntu Linux. + + Note that the basic instructions in the linked article apply to + up-to-date installations of Windows 10 and 11, but there are + also links to the procedures for older builds of Windows 10. + + - If you had installed WSL previously or installed it using + different instructions, `verify that you are running WSL 2 + `_. + + - `Set up your Linux username and password. + `_ + Do not include any spaces in your username. + + - If your computer has less than 8GB of RAM, `change the WSL settings + `_ + to make at least 4GB of RAM available to WSL. + + Start Ubuntu from the Start menu, and type the following commands + to install Sage from conda-forge. (The ``$`` represents the command + line prompt, don't type it!) The second step will ask a few questions, + and you may need to hit :kbd:`Enter` to confirm or type ``yes`` + and then hit :kbd:`Enter`. + + .. code-block:: shell + + $ curl -L -O "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh" + $ bash Miniforge3-$(uname)-$(uname -m).sh + $ conda create -n sage sage python=3.11 + + (If there are any installation failures, please report them to + the conda-forge maintainers by opening a `GitHub Issue for + conda-forge/sage-feedstock `_.) + + You can now start SageMath as follows: + + .. code-block:: shell + + $ conda activate sage + $ sage + + This way of starting Sage gives you the most basic way of using + Sage in the terminal. See :ref:`sec-launching` for recommended next steps, + in particular for setting up the Jupyter notebook, which is required if + you want to use graphics. + +.. _installation-guide-linux: Linux ===== diff --git a/src/doc/en/installation/source.rst b/src/doc/en/installation/source.rst index 26454e4d6d3..093d0ed9f26 100644 --- a/src/doc/en/installation/source.rst +++ b/src/doc/en/installation/source.rst @@ -264,24 +264,10 @@ WSL prerequisites Ubuntu on Windows Subsystem for Linux (WSL) prerequisite installation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Sage can be installed onto Linux running on Windows Subsystem for Linux (WSL). These instructions describe a fresh install of Ubuntu 20.10, but other distributions or installation methods should work too, though have not been tested. - -- Enable hardware-assisted virtualization in the EFI or BIOS of your system. Refer to your system (or motherboard) maker's documentation for instructions on how to do this. - -- Set up WSL by following the `official WSL setup guide `_. Be sure to do the steps to install WSL2 and set it as default. - -- Go to the Microsoft Store and install Ubuntu. - -- Start Ubuntu from the start menu. Update all packages to the latest version. - -- Reboot the all running WSL instances one of the following ways: - - - Open Windows Services and restart the LxssManager service. - - Open the Command Prompt or Powershell and enter this command:: - - wsl --shutdown - -- `Upgrade to the Ubuntu 20.10 `_. This step will not be necessary once Ubuntu 20.10 is available in the Microsoft Store. +Refer to :ref:`installation-guide-windows` for installing Ubuntu on +Windows Subsystem for Linux (WSL). These instructions describe a fresh +install of Ubuntu, the default distribution in WSL, but other +distributions or installation methods should work too. From this point on, follow the instructions in the :ref:`sec-installation-from-sources-linux-recommended-installation` section. It is strongly recommended to put the Sage source files in the Linux file system, for example, in the ``/home/username/sage`` directory, and not in the Windows file system (e.g. ``/mnt/c/...``). @@ -387,9 +373,49 @@ Installation steps #. Follow the procedure in the file `README.md `_ in ``SAGE_ROOT``. +#. If you wish to prepare for having to build Sage in an environment + without sufficient Internet connectivity: + + - After running ``configure``, you can use ``make download`` to force + downloading packages before building. After this, the packages + are in the subdirectory ``upstream``. + + - Alternatively, instead of cloning the git repository, you + can download a self-contained release tarball for any + stable release from the Sage project's + `GitHub Releases `_. + Use the file named ``sage-x.y.tar.gz`` (1.25 GB as of Sage 10.2) + in the Release Assets, which contains a prepopulated subdirectory + ``upstream``. + + After downloading the source tarball ``sage-x.y.tar.gz`` into + a directory ``~/sage/``:: + + $ cd ~/sage/ + $ tar xf sage-x.y.tar.gz # adapt x.y; takes a while + + This creates the subdirectory ``sage-x.y``. Now change into it:: + + $ cd sage-x.y/ # adapt x.y + + .. note:: + + On Windows, it is crucial that you unpack the source tree from the + WSL `bash` using the WSL `tar` utility and not using other + Windows tools (including mingw). + + This is because the Sage source tree contains symbolic links, and the + build will not work if Windows line endings rather than UNIX + line endings are used. + + - The Sage mirrors also provide such self-contained tarballs + for all `stable releases `_ + and additionally for all `development releases + `_. + #. Additional remarks: You do not need to be logged in as root, since no files are - changed outside of the :file:`sage-x.y` directory. + changed outside of the :file:`SAGE_ROOT` directory. In fact, **it is inadvisable to build Sage as root**, as the root account should only be used when absolutely necessary and mistyped commands can have serious consequences if you are logged in as root. @@ -504,7 +530,7 @@ Installation steps - Make a symbolic link from :file:`/usr/local/bin/sage` (or another directory in your :envvar:`PATH`) to :file:`$SAGE_ROOT/sage`:: - $ ln -s /path/to/sage-x.y/sage /usr/local/bin/sage + $ ln -s /path/to/sage_root/sage /usr/local/bin/sage Now simply typing ``sage`` from any directory should be sufficient to run Sage. @@ -940,11 +966,19 @@ Environment variables controlling the documentation build The value of this variable is passed as an argument to ``sage --docbuild all html`` or ``sage --docbuild all pdf`` when - you run ``make``, ``make doc``, or ``make doc-pdf``. For example, you can - add ``--no-plot`` to this variable to avoid building the graphics coming from - the ``.. PLOT`` directive within the documentation, or you can add - ``--include-tests-blocks`` to include all "TESTS" blocks in the reference - manual. Run ``sage --docbuild help`` to see the full list of options. + you run ``make``, ``make doc``, or ``make doc-pdf``. For example: + + - add ``--no-plot`` to this variable to avoid building the graphics coming from + the ``.. PLOT`` directive within the documentation, + + - add ``--no-preparsed-examples`` to only show the original Sage code of + "EXAMPLES" blocks, suppressing the tab with the preparsed, plain Python + version, or + + - add ``--include-tests-blocks`` to include all "TESTS" blocks in the reference + manual. + + Run ``sage --docbuild help`` to see the full list of options. .. envvar:: SAGE_SPKG_INSTALL_DOCS diff --git a/src/doc/en/reference/algebras/index.rst b/src/doc/en/reference/algebras/index.rst index 90c9a66ab87..6f50276a5ed 100644 --- a/src/doc/en/reference/algebras/index.rst +++ b/src/doc/en/reference/algebras/index.rst @@ -61,6 +61,7 @@ Named associative algebras sage/algebras/quantum_clifford sage/algebras/quantum_groups/quantum_group_gap sage/algebras/quantum_matrix_coordinate_algebra + sage/algebras/quantum_oscillator sage/algebras/quatalg/quaternion_algebra sage/algebras/rational_cherednik_algebra sage/algebras/schur_algebra diff --git a/src/doc/en/reference/algebras/lie_algebras.rst b/src/doc/en/reference/algebras/lie_algebras.rst index 457eb90f14e..03f66f4da88 100644 --- a/src/doc/en/reference/algebras/lie_algebras.rst +++ b/src/doc/en/reference/algebras/lie_algebras.rst @@ -7,6 +7,7 @@ Lie Algebras sage/algebras/lie_algebras/abelian sage/algebras/lie_algebras/affine_lie_algebra sage/algebras/lie_algebras/bch + sage/algebras/lie_algebras/center_uea sage/algebras/lie_algebras/classical_lie_algebra sage/algebras/lie_algebras/examples sage/algebras/lie_algebras/free_lie_algebra diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 5f854c44c58..8040ad64f9b 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -4060,6 +4060,11 @@ REFERENCES: study of the subgroups of the modular group", American Journal of Mathematics 113 (1991), no 6, 1053-1133 +.. [Kuniba2022] Atsuo Kuniba, + *Quantum Groups in Three-Dimensional Integrability*. + Theoretical and Mathematical Physics, Springer. 2022. + :doi:`10.1007/978-981-19-3262-5`. + .. [Kur2008] Chris Kurth, "K Farey package for Sage", http://wayback.archive-it.org/855/20100510123900/http://www.public.iastate.edu/~kurthc/research/index.html @@ -4778,6 +4783,11 @@ REFERENCES: .. [MoPa1994] \P. Morton and P. Patel. The Galois theory of periodic points of polynomial maps. Proc. London Math. Soc., 68 (1994), 225-263. +.. [Motsak2006] Olekasandr Motsak. *Computation of the central elements and + centralizers of sets of elements in non-commutative polynomial + algebras*. PhD Thesis, 2006. + https://kluedo.ub.rptu.de/frontdoor/deliver/index/docId/2308/file/Thesis.pdf + .. [MP2019] \M. Montes, D. Penazzi "Yarara and Coral v1" https://csrc.nist.gov/CSRC/media/Projects/Lightweight-Cryptography/documents/round-1/spec-doc/yarara_and_coral-spec.pdf @@ -5141,6 +5151,11 @@ REFERENCES: small Schroeder paths*, JCTA 125 (2014), 357-378, :doi:`10.1016/j.jcta.2014.04.002` +.. [Pei2010] Peikert, C. (2010). An Efficient and Parallel Gaussian Sampler for + Lattices. In: Rabin, T. (eds) Advances in Cryptology – CRYPTO 2010. + CRYPTO 2010. Lecture Notes in Computer Science, vol 6223. Springer, + Berlin, Heidelberg. :doi:`10.1007/978-3-642-14623-7_5` + .. [Pen2012] \R. Pendavingh, On the evaluation at `(-i, i)` of the Tutte polynomial of a binary matroid. Preprint: :arxiv:`1203.0910` @@ -5819,6 +5834,11 @@ REFERENCES: Proceedings of the American Mathematical Society, Volume 90. Number 2, February 1984, pp. 199-202. +.. [Sq1998] Matthew B. Squire. + *Generating the acyclic orientations of a graph*. + Journal of Algorithms, Volume 26, Issue 2, Pages 275 - 290, 1998. + (https://doi.org/10.1006/jagm.1997.0891) + .. [SS1983] Shorey and Stewart. "On the Diophantine equation a x^{2t} + b x^t y + c y^2 = d and pure powers in recurrence sequences." Mathematica Scandinavica, 1983. diff --git a/src/doc/en/website/root_index.html b/src/doc/en/website/root_index.html index c3d25ff495b..0daea8b0cfa 100644 --- a/src/doc/en/website/root_index.html +++ b/src/doc/en/website/root_index.html @@ -62,6 +62,11 @@

Sage Documentation

Tutorial
Thematic Tutorials
+
+
Greek
+
Hungarian
@@ -135,6 +140,11 @@

Sage Documentation

Tutorial
Thematic Tutorials
+
+
Greek
+
Hungarian
diff --git a/src/sage/algebras/catalog.py b/src/sage/algebras/catalog.py index ec48a6debed..afa5db12e8e 100644 --- a/src/sage/algebras/catalog.py +++ b/src/sage/algebras/catalog.py @@ -59,6 +59,8 @@ ` - :class:`algebras.QuantumMatrixCoordinate ` +- :class:`algebras.QuantumOscillator + ` - :class:`algebras.QSym ` - :class:`algebras.Partition ` - :class:`algebras.PlanarPartition ` @@ -128,6 +130,7 @@ lazy_import('sage.combinat.ncsf_qsym.qsym', 'QuasiSymmetricFunctions', 'QSym') lazy_import('sage.combinat.grossman_larson_algebras', 'GrossmanLarsonAlgebra', 'GrossmanLarson') lazy_import('sage.algebras.quantum_clifford', 'QuantumCliffordAlgebra', 'QuantumClifford') +lazy_import('sage.algebras.quantum_oscillator', 'QuantumOscillatorAlgebra', 'QuantumOscillator') lazy_import('sage.algebras.quantum_matrix_coordinate_algebra', 'QuantumMatrixCoordinateAlgebra', 'QuantumMatrixCoordinate') lazy_import('sage.algebras.quantum_matrix_coordinate_algebra', 'QuantumGL') diff --git a/src/sage/algebras/commutative_dga.py b/src/sage/algebras/commutative_dga.py index c537c3e64a8..13565314140 100644 --- a/src/sage/algebras/commutative_dga.py +++ b/src/sage/algebras/commutative_dga.py @@ -1581,6 +1581,83 @@ def dict(self): """ return self.lift().dict() + def __call__(self, *values, **kwargs): + r""" + Evaluate the reduced expression of this element at ``x``, where ``x`` + is either the tuple of values to evaluate in, a dictionary indicating + to which value is each generator evaluated, or keywords giving + the value to which generators should be evaluated. + + INPUT: + + - ``values`` -- (optional) either the values in which the variables + will be evaluated or a dictionary + + OUTPUT: + + this element evaluated at the given values + + EXAMPLES:: + + sage: A. = GradedCommutativeAlgebra(QQ, degrees=(1, 2, 2, 3)) + sage: f = x*y - 5*y*z + 7*x*y^2*z^3*t + sage: f(3, y, x^2, x*z) + 3*y + sage: f(x=3) + 21*y^2*z^3*t - 5*y*z + 3*y + sage: f({x:3, z:x^2}) + 3*y + + If the wrong number of values is provided, it results in an error:: + + sage: f(3, 5, y) + Traceback (most recent call last): + ... + ValueError: number of arguments does not match number of variables in parent + + It is also possible to use keywords like this:: + + sage: A. = GradedCommutativeAlgebra(QQ, degrees=(1, 2, 2, 3)) + sage: f = x*y - 5*y*z + 7*x*y^2*z^3*t + sage: f(x=3) + 21*y^2*z^3*t - 5*y*z + 3*y + sage: f(t=x,y=z) + -5*z^2 + x*z + + If both a dictionary and keywords are used, only the dictionary is + considered:: + + sage: A. = GradedCommutativeAlgebra(QQ, degrees=(1, 2, 2, 3)) + sage: f = x*y - 5*y*z + 7*x*y^2*z^3*t + sage: f({x:1}, t=x,y=z) + 7*y^2*z^3*t - 5*y*z + y + """ + gens = self.parent().gens() + images = list(gens) + if values and not isinstance(values[0], dict): + for (i, p) in enumerate(values): + images[i] = p + if len(values) == 1 and isinstance(values[0], dict): + images = list(gens) + for (i, g) in enumerate(gens): + if g in values[0]: + images[i] = values[0][g] + elif len(values) == len(gens): + images = list(values) + elif values: + raise ValueError("number of arguments does not match number of variables in parent") + else: + images = list(gens) + for (i, g) in enumerate(gens): + gstr = str(g) + if gstr in kwargs: + images[i] = kwargs[gstr] + res = 0 + for (m, c) in self.dict().items(): + term = prod((gen**y for (y, gen) in zip(m, images)), c) + res += term + return res + def basis_coefficients(self, total=False): """ Return the coefficients of this homogeneous element with diff --git a/src/sage/algebras/lie_algebras/center_uea.py b/src/sage/algebras/lie_algebras/center_uea.py new file mode 100644 index 00000000000..61f5c6e05d7 --- /dev/null +++ b/src/sage/algebras/lie_algebras/center_uea.py @@ -0,0 +1,765 @@ +""" +Center of a Universal Enveloping Algebra + +AUTHORS: + +- Travis Scrimshaw (2024-01-02): Initial version +""" + +#***************************************************************************** +# Copyright (C) 2024 Travis Scrimshaw +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +#from sage.structure.unique_representation import UniqueRepresentation +#from sage.structure.parent import Parent +from sage.combinat.free_module import CombinatorialFreeModule +from sage.combinat.integer_lists.invlex import IntegerListsLex +from sage.matrix.constructor import matrix +from sage.monoids.indexed_free_monoid import IndexedFreeAbelianMonoid #, IndexedFreeAbelianMonoidElement +from sage.monoids.indexed_free_monoid import IndexedMonoid +from sage.combinat.root_system.coxeter_group import CoxeterGroup +from sage.combinat.integer_vector_weighted import iterator_fast as intvecwt_iterator +from sage.sets.non_negative_integers import NonNegativeIntegers +from sage.sets.finite_enumerated_set import FiniteEnumeratedSet +from sage.sets.family import Family +from sage.rings.integer_ring import ZZ +from sage.categories.kac_moody_algebras import KacMoodyAlgebras +from sage.categories.finite_dimensional_lie_algebras_with_basis import FiniteDimensionalLieAlgebrasWithBasis +from sage.categories.graded_algebras_with_basis import GradedAlgebrasWithBasis +from sage.categories.fields import Fields +from sage.categories.monoids import Monoids +from sage.categories.enumerated_sets import EnumeratedSets +from sage.misc.cachefunc import cached_method +from sage.misc.lazy_attribute import lazy_attribute +from sage.data_structures.blas_dict import iaxpy +from collections import deque + + +class CenterIndices(IndexedFreeAbelianMonoid): + r""" + Set of basis indices for the center of a universal enveloping algebra. + + This also constructs the lift from the center to the universal enveloping + algebra as part of computing the generators and basis elements. The + basic algorithm is to construct the centralizer of each filtered + component in increasing order (as each is a finite dimensional vector + space). For more precise details, see [Motsak2006]_. + """ + @staticmethod + def __classcall__(cls, center): + r""" + Normalize input to ensure a unique representation. + + EXAMPLES:: + + sage: from sage.algebras.lie_algebras.center_uea import CenterIndices + sage: g = lie_algebras.pwitt(GF(3), 3) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: CenterIndices(Z) is CenterIndices(Z) + True + """ + return super(IndexedMonoid, cls).__classcall__(cls, center) + + def __init__(self, center, indices=None): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: g = lie_algebras.pwitt(GF(5), 5) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: I = Z.indices() + sage: TestSuite(I).run(max_runs=7) + """ + if indices is None: + indices = NonNegativeIntegers() + category = Monoids() & EnumeratedSets().Infinite() + IndexedFreeAbelianMonoid.__init__(self, indices, prefix='Z', category=category) + + self._center = center + self._envelop_alg = self._center._envelop_alg + self._g = self._envelop_alg._g + # The _lift_map will be a dict with the keys being the degree and the values + # given as dicts with the keys being the leading support. This will be + # used to do the corresponding reductions. + self._lift_map = {0: {self._envelop_alg.one_basis(): self._envelop_alg.one()}} + self._cur_deg = 0 + self._cur_vecs = deque() # we do a lot of deletions in the middle + # The _cur_basis is a mapping from the leading supports to a monoid element of self + self._cur_basis = {self._envelop_alg.one_basis(): self.one()} + self._cur_basis_inv = {self.one(): self._envelop_alg.one_basis()} + self._gen_degrees = {} + self._cur_num_gens = 0 + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: g = lie_algebras.pwitt(GF(5), 5) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: Z.indices() + Basis indices of Center of Universal enveloping algebra of + The 5-Witt Lie algebra over Finite Field of size 5 in the Poincare-Birkhoff-Witt basis + """ + return "Basis indices of {}".format(self._center) + + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: g = lie_algebras.pwitt(GF(5), 5) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: I = Z.indices() + sage: latex(I) + B\left( Z\left( PBW\left( \mathcal{W}(5)_{\Bold{F}_{5}} \right) \right) \right) + """ + from sage.misc.latex import latex + return r"B\left( {} \right)".format(latex(self._center)) + + def lift_on_basis(self, m): + r""" + Return the image of the basis element indexed by ``m`` in the + universal enveloping algebra. + + EXAMPLES:: + + sage: g = lie_algebras.Heisenberg(QQ, 3) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: I = Z.indices() + sage: z0 = I.monoid_generators()[0] + sage: I._lift_map + {0: {1: 1}} + sage: I.lift_on_basis(z0) + PBW['z'] + sage: I._lift_map + {0: {1: 1}, 1: {PBW['z']: PBW['z']}} + sage: I.lift_on_basis(z0^3) + PBW['z']^3 + sage: I._lift_map + {0: {1: 1}, 1: {PBW['z']: PBW['z']}} + sage: I._construct_next_degree() + sage: I._construct_next_degree() + sage: I._lift_map + {0: {1: 1}, + 1: {PBW['z']: PBW['z']}, + 2: {PBW['z']^2: PBW['z']^2}, + 3: {PBW['z']^3: PBW['z']^3}} + sage: I.lift_on_basis(z0^3) + PBW['z']^3 + """ + while m not in self._cur_basis_inv: + supp = m.support() + # We might have not computed the correct degree, but we can lift the + # element if we have computed all of the corresponding generators. + if all(i in self._gen_degrees and self._gen_degrees[i] in self._lift_map + for i in supp): + ret = self._envelop_alg.one() + divisors = [mp for mp in self._cur_basis_inv if mp.divides(m) and not mp.is_one()] + while not m.is_one(): + div = max(divisors, key=lambda elt: len(elt)) + ls = self._cur_basis_inv[div] + deg = ls.length() + ret *= self._lift_map[deg][ls] + m = m // div + divisors = [mp for mp in divisors if mp.divides(m)] + return ret + self._construct_next_degree() + ls = self._cur_basis_inv[m] + deg = ls.length() + return self._lift_map[deg][ls] + + def __iter__(self): + r""" + Iterate over ``self`` in degree increasing order. + + EXAMPLES:: + + sage: g = lie_algebras.pwitt(GF(3), 6) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: I = Z.indices() + sage: it = iter(I) + sage: [next(it) for _ in range(10)] + [1, Z[0], Z[1], Z[2], Z[3], Z[4], Z[5], Z[6], Z[7], Z[0]^2] + """ + yield self.one() # start with the identity + deg = 1 + while True: + while deg not in self._lift_map: + self._construct_next_degree() + for ls in self._lift_map[deg]: + yield self._cur_basis[ls] + deg += 1 + + def some_elements(self): + r""" + Return some elements of ``self``. + + EXAMPLES:: + + sage: g = lie_algebras.pwitt(GF(3), 3) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: I = Z.indices() + sage: I.some_elements() + [1, Z[0], Z[1], Z[2], Z[0]*Z[1]*Z[2], Z[0]*Z[2]^4, Z[0]^4*Z[1]^3] + """ + it = iter(self) + gens = [next(it) for _ in range(4)] + # We construct it as a set in case we introduce duplicates. + ret = set(gens) + ret.update([self.prod(gens), gens[1] * gens[3]**4, gens[1]**4 * gens[2]**3]) + # Sort the output for uniqueness + ret = sorted(ret, key=lambda m: (self.degree(m), m.to_word_list())) + return ret + + def degree(self, m): + r""" + Return the degre of ``m`` in ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['E', 6]) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: I = Z.indices() + sage: [I.degree(g) for g in I.monoid_generators()] + [2, 5, 6, 8, 9, 12] + sage: [(elt, I.degree(elt)) for elt in I.some_elements()] + [(1, 0), (Z[0], 2), (Z[0]^2, 4), (Z[1], 5), (Z[0]^3*Z[1], 11), + (Z[0]^10, 20), (Z[0]*Z[1]^4, 22)] + """ + return ZZ.sum(e * self._gen_degrees[i] for i, e in m._monomial.items()) + + def _construct_next_degree(self): + r""" + Construct the next elements of ``self`` for the next (uncomputed) degree. + + EXAMPLES:: + + sage: g = lie_algebras.three_dimensional_by_rank(QQ, 2, 1) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: I = Z.indices() + sage: I._lift_map + {0: {1: 1}} + sage: I._construct_next_degree() + sage: I._construct_next_degree() + sage: I._construct_next_degree() + sage: I._construct_next_degree() + sage: I._lift_map + {0: {1: 1}, 1: {}, 2: {}, 3: {}, 4: {}} + """ + UEA = self._envelop_alg + gens = UEA.algebra_generators() + monoid = UEA.basis().keys() + self._cur_deg += 1 + + # We first update the lift map with all possible products + # Note that we are using the fact that the elements are central + # so the product order doesn't matter. + # Since we always update this, it is sufficient to compute it + new_red = {} + for i in range(1, self._cur_deg//2+1): + for ls, lelt in self._lift_map[self._cur_deg-i].items(): + for rs, relt in self._lift_map[i].items(): + supp = ls * rs + new_red[supp] = lelt * relt + mon = self._cur_basis[ls] * self._cur_basis[rs] + self._cur_basis[supp] = mon + self._cur_basis_inv[mon] = supp + # TODO: Determine if we need to or benefit from another reduction of the new elements + self._lift_map[self._cur_deg] = new_red + + # Determine the PBW elements of the current degree that are not reduced + # modulo the currently computed center. + for exps in IntegerListsLex(n=self._cur_deg, length=len(gens)): + elt = monoid.element_class(monoid, {k: p for k, p in zip(monoid._indices, exps) if p}) + if elt in new_red: # already has a central element with this leading term + continue + # A new basis element to consider + self._cur_vecs.append(UEA.monomial(elt)) + + # Perform the centralization + R = UEA.base_ring() + vecs = list(self._cur_vecs) + for g in gens: + # TODO: We should hold onto previously computed values under the adjoint action + ad = [g * v - v * g for v in vecs] + # Compute the kernel + supp = set() + for v in ad: + supp.update(v._monomial_coefficients) + supp = sorted(supp, key=UEA._monomial_key, reverse=True) + if not supp: # no support for the image, so everything is in the kernel + continue + M = matrix(R, [[v[s] for v in ad] for s in supp]) + ker = M.right_kernel_matrix() + vecs = [self._reduce(UEA.linear_combination((vecs[i], c) for i, c in kv.iteritems())) + for kv in ker.rows()] + + # Lastly, update the appropriate data + if not vecs: # No new central elements, so nothing to do + return + new_gens = {} + for v in vecs: + v = self._reduce(v) # possibly not needed to check this + if not v: + continue + ls = v.trailing_support(key=UEA._monomial_key) + self._cur_vecs.remove(UEA.monomial(ls)) + new_gens[ls] = self._reduce(v) + assert (self._cur_num_gens not in self._gen_degrees + or self._gen_degrees[self._cur_num_gens] == self._cur_deg) + self._gen_degrees[self._cur_num_gens] = self._cur_deg + mon = self.gen(self._cur_num_gens) + self._cur_basis[ls] = mon + self._cur_basis_inv[mon] = ls + self._cur_num_gens += 1 + self._lift_map[self._cur_deg].update(new_gens) + + def _reduce(self, vec): + r""" + Return the UEA vector ``vec`` by the currently computed center. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['A', 1]) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: I = Z.indices() + sage: z0 = I.gen(0) + sage: I._reduce(I.lift_on_basis(z0)) + 0 + sage: max(I._lift_map) + 2 + sage: I._reduce(I.lift_on_basis(z0^2)) + 4*PBW[alpha[1]]^2*PBW[-alpha[1]]^2 + + 2*PBW[alpha[1]]*PBW[alphacheck[1]]^2*PBW[-alpha[1]] + + 1/4*PBW[alphacheck[1]]^4 - PBW[alphacheck[1]]^3 + + PBW[alphacheck[1]]^2 + sage: I._reduce(I.lift_on_basis(z0^2) - I.lift_on_basis(z0)) + 4*PBW[alpha[1]]^2*PBW[-alpha[1]]^2 + + 2*PBW[alpha[1]]*PBW[alphacheck[1]]^2*PBW[-alpha[1]] + + 1/4*PBW[alphacheck[1]]^4 - PBW[alphacheck[1]]^3 + + PBW[alphacheck[1]]^2 + sage: I._construct_next_degree() + sage: I._construct_next_degree() + sage: max(I._lift_map) + 4 + sage: I._reduce(I.lift_on_basis(z0^2) - I.lift_on_basis(z0)) + 0 + """ + # This is replicating what SubmoduleWithBasis does + ret = dict(vec._monomial_coefficients) + for data in self._lift_map.values(): + for m, qv in data.items(): + if m not in ret: + continue + iaxpy(-ret[m] / qv[m], qv._monomial_coefficients, ret) + return self._envelop_alg._from_dict(ret, remove_zeros=False) + + +class SimpleLieCenterIndices(CenterIndices): + r""" + Set of basis indices for the center of a universal enveloping algebra of + a simple Lie algebra. + + For more information, see + :class:`~sage.algebras.lie_algebras.center_uea.CenterIndices`. + """ + def __init__(self, center): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['E', 6]) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: I = Z.indices() + sage: TestSuite(I).run() + """ + self._cartan_type = center._envelop_alg._g.cartan_type() + r = self._cartan_type.rank() + super().__init__(center, indices=FiniteEnumeratedSet(range(r))) + W = CoxeterGroup(self._cartan_type) + self._gen_degrees = dict(enumerate(W.degrees())) + + def __iter__(self): + r""" + Iterate over ``self`` in degree increasing order. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['E', 6]) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: I = Z.indices() + sage: it = iter(I) + sage: [next(it) for _ in range(10)] + [1, Z[0], Z[0]^2, Z[1], Z[2], Z[0]^3, Z[0]*Z[1], Z[3], Z[0]*Z[2], Z[0]^4] + """ + deg = 0 + n = len(self._gen_degrees) + wts = sorted(self._gen_degrees.values(), reverse=True) + while True: + total = 0 + for exps in intvecwt_iterator(deg, wts): + yield self.element_class(self, {n-1-i: e for i, e in enumerate(exps) if e}) + deg += 1 + + +class CenterUEA(CombinatorialFreeModule): + r""" + The center of a universal enveloping algebra. + + .. TODO:: + + Generalize this to be the centralizer of any set of the UEA. + + .. TODO:: + + For characteristic `p > 0`, implement the `p`-center of a simple + Lie algebra. See, e.g., + + - Theorem 5.12 of [Motsak2006]_ + - http://www.math.kobe-u.ac.jp/icms2006/icms2006-video/slides/059.pdf + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['A', 2]) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: B = Z.basis() + sage: it = iter(B) + sage: center_elts = [next(it) for _ in range(6)]; center_elts + [1, Z[0], Z[1], Z[0]^2, Z[0]*Z[1], Z[1]^2] + sage: elts = [U(v) for v in center_elts] # long time + sage: all(v * g == g * v for g in U.algebra_generators() for v in elts) # long time + True + + The Heisenberg Lie algebra `H_4` over a finite field; note the basis + elements `b^p \in Z(U(H_4))` for the basis elements `b \in H_4`:: + + sage: g = lie_algebras.Heisenberg(GF(3), 4) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: B = Z.basis() + sage: it = iter(B) + sage: center_elts = [next(it) for _ in range(12)]; center_elts + [1, Z[0], Z[0]^2, Z[0]^3, Z[1], Z[2], Z[3], Z[4], Z[5], Z[6], Z[7], Z[8]] + sage: elts = [U(v) for v in center_elts]; elts + [1, PBW['z'], PBW['z']^2, PBW['z']^3, PBW['p1']^3, PBW['p2']^3, PBW['p3']^3, + PBW['p4']^3, PBW['q1']^3, PBW['q2']^3, PBW['q3']^3, PBW['q4']^3] + sage: all(v * g == g * v for g in U.algebra_generators() for v in elts) + True + + An example with a free 4-step nilpotent Lie algebras on 2 generators:: + + sage: L = LieAlgebra(QQ, 2, step=4); L + Free Nilpotent Lie algebra on 8 generators + (X_1, X_2, X_12, X_112, X_122, X_1112, X_1122, X_1222) over Rational Field + sage: U = L.pbw_basis() + sage: Z = U.center() + sage: it = iter(Z.basis()) + sage: center_elts = [next(it) for _ in range(10)]; center_elts + [1, Z[0], Z[1], Z[2], Z[0]^2, Z[0]*Z[1], Z[0]*Z[2], Z[1]^2, Z[1]*Z[2], Z[2]^2] + sage: elts = [U(v) for v in center_elts]; elts + [1, PBW[(1, 1, 1, 2)], PBW[(1, 1, 2, 2)], PBW[(1, 2, 2, 2)], PBW[(1, 1, 1, 2)]^2, + PBW[(1, 1, 1, 2)]*PBW[(1, 1, 2, 2)], PBW[(1, 1, 1, 2)]*PBW[(1, 2, 2, 2)], + PBW[(1, 1, 2, 2)]^2, PBW[(1, 1, 2, 2)]*PBW[(1, 2, 2, 2)], PBW[(1, 2, 2, 2)]^2] + sage: all(v * g == g * v for g in U.algebra_generators() for v in elts) + True + + Using the Engel Lie algebra:: + + sage: L. = LieAlgebra(QQ, {('X','Y'): {'Z': 1}}, nilpotent=True) + sage: U = L.pbw_basis() + sage: Z = U.center() + sage: it = iter(Z.basis()) + sage: center_elts = [next(it) for _ in range(6)]; center_elts + [1, Z[0], Z[0]^2, Z[0]^3, Z[0]^4, Z[0]^5] + sage: elts = [U(v) for v in center_elts]; elts + [1, PBW['Z'], PBW['Z']^2, PBW['Z']^3, PBW['Z']^4, PBW['Z']^5] + sage: all(v * g == g * v for g in U.algebra_generators() for v in elts) + True + """ + def __init__(self, g, UEA): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(ZZ['t'].fraction_field(), cartan_type=['D', 4]) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: TestSuite(Z).run() + + sage: g = lie_algebras.Heisenberg(GF(3), 4) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: TestSuite(Z).run() + """ + if g not in FiniteDimensionalLieAlgebrasWithBasis: + raise NotImplementedError("only implemented for finite dimensional Lie algebras with a distinguished basis") + + R = UEA.base_ring() + if R not in Fields(): + raise NotImplementedError("only implemented for the base ring a field") + + self._g = g + self._envelop_alg = UEA + if (self._g in KacMoodyAlgebras + and self._g.cartan_type().is_finite() + and R.characteristic() == 0): + indices = SimpleLieCenterIndices(self) + else: + indices = CenterIndices(self) + category = UEA.category() + base = category.base() + category = GradedAlgebrasWithBasis(base).Commutative() | category.Subobjects() + CombinatorialFreeModule.__init__(self, R, indices, category=category, + prefix='', bracket=False, latex_bracket=False, + sorting_key=self._sorting_key) + self.lift.register_as_coercion() + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['A',2]) + sage: U = g.pbw_basis() + sage: U.center() + Center of Universal enveloping algebra of Lie algebra of ['A', 2] + in the Chevalley basis in the Poincare-Birkhoff-Witt basis + """ + return "Center of " + repr(self._envelop_alg) + + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: g = lie_algebras.pwitt(GF(5), 5) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: latex(Z) + Z\left( PBW\left( \mathcal{W}(5)_{\Bold{F}_{5}} \right) \right) + """ + from sage.misc.latex import latex + return r"Z\left( {} \right)".format(latex(self._envelop_alg)) + + def _sorting_key(self, m): + r""" + Return a key for ``m`` used in sorting elements of ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['A', 2]) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: z0, z1 = Z.algebra_generators() + sage: z0 * z1 # indirect doctest + Z[0]*Z[1] + sage: z1^2 + z0*z1 + z0^2 # indirect doctest + Z[0]^2 + Z[0]*Z[1] + Z[1]^2 + sage: z1^3 + z0*z1^2 + z0^2*z1 + z0^3 # indirect doctest + Z[0]^3 + Z[0]^2*Z[1] + Z[0]*Z[1]^2 + Z[1]^3 + """ + return (-m.length(), m.to_word_list()) + + @cached_method + def algebra_generators(self): + r""" + Return the algebra generators of ``self``. + + .. WARNING:: + + When the universal enveloping algebra is not known to have + a finite generating set, the generating set will be the basis + of ``self`` in a degree (weakly) increasing order indexed by + `\ZZ_{\geq 0}`. In particular, the `0`-th generator will be + the multiplicative identity `1`. + + EXAMPLES:: + + sage: g = lie_algebras.Heisenberg(QQ, 3) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: Z.algebra_generators()[0] + 1 + sage: Z.algebra_generators()[1] + Z[0] + + sage: g = LieAlgebra(QQ, cartan_type=['G', 2]) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: Z.algebra_generators() + Finite family {0: Z[0], 1: Z[1]} + """ + mon_gens = self._indices.monoid_generators() + if mon_gens.cardinality() == float("inf"): + return Family(NonNegativeIntegers(), lambda m: self.monomial(self._indices.unrank(m))) + return Family({i: self.monomial(mon_gens[i]) for i in mon_gens.keys()}) + + @cached_method + def one_basis(self): + r""" + Return the basis index of `1` in ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ['t'].fraction_field(), cartan_type=['B', 5]) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: ob = Z.one_basis(); ob + 1 + sage: ob.parent() is Z.indices() + True + """ + return self._indices.one() + + def ambient(self): + r""" + Return the ambient algebra of ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(GF(5), cartan_type=['A', 2]) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: Z.ambient() is U + True + """ + return self._envelop_alg + + def product_on_basis(self, left, right): + r""" + Return the product of basis elements indexed by ``left`` and ``right``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['E', 6]) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: mg = Z.indices().monoid_generators() + sage: Z.product_on_basis(mg[1]*mg[2], mg[0]*mg[1]^3*mg[2]*mg[3]^3) + Z[0]*Z[1]^4*Z[2]^2*Z[3]^3 + """ + return self.monomial(left * right) + + def degree_on_basis(self, m): + r""" + Return the degree of the basis element indexed by ``m`` in ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['E', 6]) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: I = Z.indices() + sage: it = iter(I) + sage: supports = [next(it) for _ in range(10)]; supports + [1, Z[0], Z[0]^2, Z[1], Z[2], Z[0]^3, Z[0]*Z[1], Z[3], Z[0]*Z[2], Z[0]^4] + sage: [Z.degree_on_basis(m) for m in supports] + [0, 2, 4, 5, 6, 6, 7, 8, 8, 8] + """ + return self._indices.degree(m) + + @lazy_attribute + def lift(self): + r""" + The lift map from ``self`` to the universal enveloping algebra. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['A', 1]) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: gens = Z.algebra_generators() + sage: U(gens[0]^2 + gens[0]) + 4*PBW[alpha[1]]^2*PBW[-alpha[1]]^2 + + 2*PBW[alpha[1]]*PBW[alphacheck[1]]^2*PBW[-alpha[1]] + + 1/4*PBW[alphacheck[1]]^4 - PBW[alphacheck[1]]^3 + - 2*PBW[alpha[1]]*PBW[-alpha[1]] + 1/2*PBW[alphacheck[1]]^2 + + PBW[alphacheck[1]] + sage: U(-1/4*gens[0]) == U.casimir_element() + True + """ + # This is correct if we are using key=self._envelop_alg._monomial_key, + # but we are currently unable to pass such an option. + return self.module_morphism(self._indices.lift_on_basis, codomain=self._envelop_alg, unitriangular='upper') + + def retract(self, elt): + r""" + The retraction map to ``self`` from the universal enveloping algebra. + + .. TODO:: + + Implement a version of this that checks if the leading term of + ``elt`` is divisible by a product of all of the currently known + generators in order to avoid constructing the full centralizer + of larger degrees than needed. + + EXAMPLES:: + + sage: g = lie_algebras.Heisenberg(QQ, 3) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: z0 = Z.algebra_generators()[1]; z0 + Z[0] + sage: Z.retract(U(z0^2) - U(3*z0)) + Z[0]^2 - 3*Z[0] + + sage: g = LieAlgebra(QQ, cartan_type=['A', 2]) + sage: U = g.pbw_basis() + sage: Z = U.center() + sage: z0, z1 = Z.algebra_generators() + sage: Z.retract(U(z0*z0) - U(z1)) # long time + Z[0]^2 - Z[1] + sage: zc = Z.retract(U.casimir_element()); zc + -1/3*Z[0] + sage: U(zc) == U.casimir_element() + True + """ + # This should work except it needs the monomials of the PBW basis to be + # compariable. However, this does not work for, e.g., Lie algebras + # in the Chevalley basis as ee are unable to pass a key for the + # module morphism. Additionally, the implementation below does more + # operations in-place than the module morphism. + #return self.lift.section() + UEA = self._envelop_alg + elt = UEA(elt) + # We manipulate the dictionary (in place) to avoid creating elements + data = elt.monomial_coefficients(copy=True) + indices = self._indices + ret = {} + while data: + lm = min(data, key=UEA._monomial_key) + while indices._cur_deg < UEA.degree_on_basis(lm): + indices._construct_next_degree() + ind = indices._cur_basis[lm] + other = indices.lift_on_basis(ind).monomial_coefficients(copy=False) + coeff = data[lm] / other[lm] + ret[ind] = coeff + iaxpy(-coeff, other, data) + return self.element_class(self, ret) diff --git a/src/sage/algebras/lie_algebras/classical_lie_algebra.py b/src/sage/algebras/lie_algebras/classical_lie_algebra.py index 4466cf6e46e..e7a1b9a8b33 100644 --- a/src/sage/algebras/lie_algebras/classical_lie_algebra.py +++ b/src/sage/algebras/lie_algebras/classical_lie_algebra.py @@ -1796,6 +1796,19 @@ def _repr_(self): """ return "Lie algebra of {} in the Chevalley basis".format(self._cartan_type) + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['A', 2]) + sage: latex(g) + \mathfrak{g}(A_{2})_{\Bold{Q}} + """ + from sage.misc.latex import latex + return r"\mathfrak{{g}}({})_{{{}}}".format(latex(self._cartan_type), latex(self.base_ring())) + def _test_structure_coeffs(self, **options): """ Check the structure coefficients against the GAP implementation. diff --git a/src/sage/algebras/lie_algebras/lie_algebra_element.pyx b/src/sage/algebras/lie_algebras/lie_algebra_element.pyx index ca53753153b..9e46da295c4 100644 --- a/src/sage/algebras/lie_algebras/lie_algebra_element.pyx +++ b/src/sage/algebras/lie_algebras/lie_algebra_element.pyx @@ -156,10 +156,6 @@ cdef class LieAlgebraElement(IndexedFreeModuleElement): PBW[-1] + PBW[0] - 3*PBW[1] """ UEA = self._parent.universal_enveloping_algebra() - try: - gen_dict = UEA.algebra_generators() - except (TypeError, AttributeError): - gen_dict = UEA.gens_dict() s = UEA.zero() if not self: return s @@ -167,9 +163,14 @@ cdef class LieAlgebraElement(IndexedFreeModuleElement): # does not match the generators index set of the UEA. if hasattr(self._parent, '_UEA_names_map'): names_map = self._parent._UEA_names_map + gen_dict = UEA.gens_dict() for t, c in self._monomial_coefficients.items(): s += c * gen_dict[names_map[t]] else: + try: + gen_dict = UEA.algebra_generators() + except (TypeError, AttributeError): + gen_dict = UEA.gens_dict() for t, c in self._monomial_coefficients.items(): s += c * gen_dict[t] return s diff --git a/src/sage/algebras/lie_algebras/poincare_birkhoff_witt.py b/src/sage/algebras/lie_algebras/poincare_birkhoff_witt.py index 723a4e04179..3b59303d0dc 100644 --- a/src/sage/algebras/lie_algebras/poincare_birkhoff_witt.py +++ b/src/sage/algebras/lie_algebras/poincare_birkhoff_witt.py @@ -4,10 +4,11 @@ AUTHORS: - Travis Scrimshaw (2013-11-03): Initial version +- Travis Scrimshaw (2024-01-02): Adding the center """ #***************************************************************************** -# Copyright (C) 2013-2017 Travis Scrimshaw +# Copyright (C) 2013-2024 Travis Scrimshaw # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -96,6 +97,14 @@ class PoincareBirkhoffWittBasis(CombinatorialFreeModule): PBW[2]*PBW[3] + PBW[5] sage: G[-2] * G[3] * G[2] PBW[-2]*PBW[2]*PBW[3] + PBW[-2]*PBW[5] + + .. TODO:: + + When the Lie algebra is finite dimensional, set the ordering of the + basis elements, translate the structure coefficients, and work with + fixed-length lists as the exponent vectors. This way we only will + run any nontrivial sorting only once and avoid other potentially + expensive comparisons between keys. """ @staticmethod def __classcall_private__(cls, g, basis_key=None, prefix='PBW', **kwds): @@ -247,6 +256,20 @@ def _repr_(self): """ return "Universal enveloping algebra of {} in the Poincare-Birkhoff-Witt basis".format(self._g) + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: g = lie_algebras.pwitt(GF(3), 6) + sage: U = g.pbw_basis() + sage: latex(U) + PBW\left( \mathcal{W}(6)_{\Bold{F}_{3}} \right) + """ + from sage.misc.latex import latex + return r"PBW\left( {} \right)".format(latex(self._g)) + def _coerce_map_from_(self, R): """ Return ``True`` if there is a coercion map from ``R`` to ``self``. @@ -496,7 +519,7 @@ def degree_on_basis(self, m): """ return m.length() - def casimir_element(self, order=2): + def casimir_element(self, order=2, *args, **kwds): r""" Return the Casimir element of ``self``. @@ -534,7 +557,32 @@ def casimir_element(self, order=2): from sage.rings.infinity import Infinity if self._g.dimension() == Infinity: raise ValueError("the Lie algebra must be finite dimensional") - return self._g.casimir_element(order=order, UEA=self) + return self._g.casimir_element(order=order, UEA=self, *args, **kwds) + + def center(self): + r""" + Return the center of ``self``. + + .. SEEALSO:: + + :class:`~sage.algebras.lie_algebras.center_uea.CenterUEA` + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['A', 2]) + sage: U = g.pbw_basis() + sage: U.center() + Center of Universal enveloping algebra of Lie algebra of ['A', 2] + in the Chevalley basis in the Poincare-Birkhoff-Witt basis + + sage: g = lie_algebras.Heisenberg(GF(3), 4) + sage: U = g.pbw_basis() + sage: U.center() + Center of Universal enveloping algebra of Heisenberg algebra of rank 4 + over Finite Field of size 3 in the Poincare-Birkhoff-Witt basis + """ + from sage.algebras.lie_algebras.center_uea import CenterUEA + return CenterUEA(self._g, self) class Element(CombinatorialFreeModule.Element): def _act_on_(self, x, self_on_left): diff --git a/src/sage/algebras/lie_algebras/virasoro.py b/src/sage/algebras/lie_algebras/virasoro.py index 297ccf184a8..aa11a9ed089 100644 --- a/src/sage/algebras/lie_algebras/virasoro.py +++ b/src/sage/algebras/lie_algebras/virasoro.py @@ -80,6 +80,19 @@ def _repr_(self): """ return "The Lie algebra of regular vector fields over {}".format(self.base_ring()) + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: g = lie_algebras.regular_vector_fields(QQ) + sage: latex(g) + \mathcal{W}_{\Bold{Q}} + """ + from sage.misc.latex import latex + return r"\mathcal{{W}}_{{{}}}".format(latex(self.base_ring())) + # For compatibility with CombinatorialFreeModuleElement _repr_term = IndexedGenerators._repr_generator _latex_term = IndexedGenerators._latex_generator @@ -217,6 +230,19 @@ def _repr_(self): """ return "The {}-Witt Lie algebra over {}".format(self._p, self.base_ring()) + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: g = lie_algebras.pwitt(GF(3), 15) + sage: latex(g) + \mathcal{W}(15)_{\Bold{F}_{3}} + """ + from sage.misc.latex import latex + return r"\mathcal{{W}}({})_{{{}}}".format(latex(self._p), latex(self.base_ring())) + # For compatibility with CombinatorialFreeModuleElement _repr_term = IndexedGenerators._repr_generator _latex_term = IndexedGenerators._latex_generator @@ -444,6 +470,19 @@ def _repr_(self): """ return "The Virasoro algebra over {}".format(self.base_ring()) + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: g = lie_algebras.VirasoroAlgebra(QQ) + sage: latex(g) + \mathcal{V}_{\Bold{Q}} + """ + from sage.misc.latex import latex + return r"\mathcal{{V}}_{{{}}}".format(latex(self.base_ring())) + @cached_method def lie_algebra_generators(self): """ diff --git a/src/sage/algebras/quantum_oscillator.py b/src/sage/algebras/quantum_oscillator.py new file mode 100644 index 00000000000..a688283705f --- /dev/null +++ b/src/sage/algebras/quantum_oscillator.py @@ -0,0 +1,621 @@ +r""" +Quantum Oscillator Algebras + +AUTHORS: + +- Travis Scrimshaw (2023-12): initial version +""" + +#***************************************************************************** +# Copyright (C) 2023 Travis Scrimshaw +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.misc.cachefunc import cached_method +from sage.misc.misc_c import prod +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.integer_ring import ZZ +from sage.categories.algebras import Algebras +from sage.combinat.free_module import CombinatorialFreeModule +from sage.categories.cartesian_product import cartesian_product +from sage.sets.family import Family +from sage.sets.non_negative_integers import NonNegativeIntegers + + +class QuantumOscillatorAlgebra(CombinatorialFreeModule): + r""" + The quantum oscillator algebra. + + Let `R` be a commutative algebra and `q \in R` be a unit. + The *quantum oscillator algebra*, or `q`-oscillator algebra, + is the unital associative `R`-algebra with generators `a^+`, + `a^-` and `k^{\pm 1}` satisfying the following relations: + + .. MATH:: + + k a^{\pm} = q^{\pm 1} a^{\pm} k, \qquad + a^- a^+ = 1 - q^2 k^2, \qquad + a^+ a^- = 1 - k^2. + + INPUT: + + - ``q`` -- (optional) the parameter `q` + - ``R`` -- (default: `\QQ(q)`) the base ring that contains ``q`` + + EXAMPLES: + + We construct the algebra and perform some basic computations:: + + sage: O = algebras.QuantumOscillator() + sage: ap, am, k, ki = O.algebra_generators() + sage: q = O.q() + sage: k^-3 * ap * ki * am^2 * k - q^3 * ap * k^3 + q^5*a-*k^-3 - q^3*a-*k^-1 - q^3*a+*k^3 + + We construct representations of the type `A_1` quantum coordinate ring + using the quantum oscillator algebra and verify the quantum determinant:: + + sage: pi = matrix([[am, k], [-q*k, ap]]); pi + [ a- k] + [-q*k a+] + sage: pi[0,0] * pi[1,1] - q * pi[0,1] * pi[1,0] + 1 + + Next, we use this to build representations for type `A_2`:: + + sage: def quantum_det(M): + ....: n = M.nrows() + ....: return sum((-q)**sigma.length() + ....: * prod(M[i,sigma[i]-1] for i in range(n)) + ....: for sigma in Permutations(n)) + sage: def build_repr(wd, gens): + ....: n = gens[0].nrows() + ....: ret = gens[wd[0]-1] + ....: for ind in wd[1:]: + ....: g = gens[ind-1] + ....: temp = [[None]*n for _ in range(n)] + ....: for i in range(n): + ....: for j in range(n): + ....: temp[i][j] = sum(tensor([ret[i,k], g[k,j]]) for k in range(n)) + ....: ret = matrix(temp) + ....: return ret + sage: pi1 = matrix.block_diagonal(pi, matrix.identity(1)); pi1 + [ a- k| 0] + [-q*k a+| 0] + [---------+----] + [ 0 0| 1] + sage: pi2 = matrix.block_diagonal(matrix.identity(1), pi); pi2 + [ 1| 0 0] + [----+---------] + [ 0| a- k] + [ 0|-q*k a+] + sage: quantum_det(pi1) == 1 + True + sage: quantum_det(pi2) == 1 + True + sage: pi12 = build_repr([1,2], [pi1, pi2]); pi12 + [ a- # 1 k # a- k # k] + [-q*k # 1 a+ # a- a+ # k] + [ 0 -q*1 # k 1 # a+] + sage: quantum_det(pi12) + 1 # 1 + sage: pi121 = build_repr([1,2,1], [pi1, pi2]); pi121 + [ a- # 1 # a- - q*k # a- # k a- # 1 # k + k # a- # a+ k # k # 1] + [-q*k # 1 # a- - q*a+ # a- # k -q*k # 1 # k + a+ # a- # a+ a+ # k # 1] + [ q^2*1 # k # k -q*1 # k # a+ 1 # a+ # 1] + sage: quantum_det(pi121) + 1 # 1 # 1 + sage: pi212 = build_repr([2,1,2], [pi1, pi2]); pi212 + [ 1 # a- # 1 1 # k # a- 1 # k # k] + [ -q*a- # k # 1 a- # a+ # a- - q*k # 1 # k a- # a+ # k + k # 1 # a+] + [ q^2*k # k # 1 -q*k # a+ # a- - q*a+ # 1 # k -q*k # a+ # k + a+ # 1 # a+] + sage: quantum_det(pi212) + 1 # 1 # 1 + + REFERENCES: + + - [Kuniba2022]_ Section 3.2 + """ + @staticmethod + def __classcall_private__(cls, q=None, R=None): + r""" + Standardize input to ensure a unique representation. + + TESTS:: + + sage: O1 = algebras.QuantumOscillator() + sage: q = PolynomialRing(ZZ, 'q').fraction_field().gen() + sage: O2 = algebras.QuantumOscillator(q=q) + sage: O3 = algebras.QuantumOscillator(q, q.parent()) + sage: O1 is O2 and O2 is O3 + True + """ + if q is None: + q = PolynomialRing(ZZ, 'q').fraction_field().gen() + if R is None: + R = q.parent() + q = R(q) + + return super().__classcall__(cls, q, R) + + def __init__(self, q, R): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: TestSuite(O).run() + """ + self._q = q + self._k_poly = PolynomialRing(R, 'k') + indices = cartesian_product([ZZ, ZZ]) + + cat = Algebras(R).WithBasis() + CombinatorialFreeModule.__init__(self, R, indices, category=cat) + self._assign_names(('ap', 'am', 'k', 'ki')) + + def _repr_(self) -> str: + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: algebras.QuantumOscillator() + Quantum oscillator algebra with q=q over + Fraction Field of Univariate Polynomial Ring in q over Integer Ring + """ + return "Quantum oscillator algebra with q={} over {}".format( + self._q, self.base_ring()) + + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: latex(O) + \operatorname{Osc}_{q} + """ + return "\\operatorname{Osc}_{%s}" % self._q + + def q(self): + r""" + Return the `q` of ``self``. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: O.q() + q + sage: O = algebras.QuantumOscillator(q=QQ(-5)) + sage: O.q() + -5 + """ + return self._q + + @cached_method + def algebra_generators(self): + r""" + Return the algebra generators of ``self``. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: O.algebra_generators() + Finite family {'am': a-, 'ap': a+, 'k': k, 'ki': k^-1} + """ + d = {'ap': self.monomial((ZZ.one(), ZZ.zero())), + 'am': self.monomial((-ZZ.one(), ZZ.zero())), + 'k': self.monomial((ZZ.zero(), ZZ.one())), + 'ki': self.monomial((ZZ.zero(), -ZZ.one()))} + return Family(d) + + @cached_method + def gens(self) -> tuple: + r""" + Return the generators of ``self``. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: O.gens() + (a+, a-, k, k^-1) + """ + return tuple(self.algebra_generators()) + + @cached_method + def one_basis(self) -> tuple: + r""" + Return the index of the basis element of `1`. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: O.one_basis() + (0, 0) + """ + return (ZZ.zero(), ZZ.zero()) + + def some_elements(self) -> tuple: + r""" + Return some elements of ``self``. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: O.some_elements() + (a+, a-, k, k^-1, 1, a+^3, a-^4, k^2, k^-5, a+*k, + a-^4*k^-3, 1 + 3*k + 2*a+ + a+*k) + """ + ap, am, k, ki = self.gens() + return (ap, am, k, ki, self.one(), + ap**3, am**4, k**2, ki**5, ap*k, am**4*ki**3, + self.an_element()) + + def fock_space_representation(self): + r""" + Return the Fock space representation of ``self``. + + .. SEEALSO:: + + :class:`~sage.algebras.quantum_oscillator.FockSpaceRepresentation` + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: O.fock_space_representation() + Fock space representation of Quantum oscillator algebra with q=q + over Fraction Field of Univariate Polynomial Ring in q over Integer Ring + """ + return FockSpaceRepresentation(self) + + def _repr_term(self, m) -> str: + r""" + Return a string representation of the basis element indexed by ``m``. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: O._repr_term((1, 3)) + 'a+*k^3' + sage: O._repr_term((-1, 1)) + 'a-*k' + sage: O._repr_term((5, 0)) + 'a+^5' + sage: O._repr_term((-4, -2)) + 'a-^4*k^-2' + sage: O._repr_term((0, -4)) + 'k^-4' + sage: O._repr_term((0, 0)) + '1' + + sage: O(5) + 5 + """ + a, k = m + + astr = '' + if a == 1: + astr = 'a+' + elif a > 1: + astr = 'a+^{}'.format(a) + elif a == -1: + astr = 'a-' + elif a < -1: + astr = 'a-^{}'.format(-a) + + kstr = '' + if k == 1: + kstr = 'k' + elif k != 0: + kstr = 'k^{}'.format(k) + + if astr: + if kstr: + return astr + '*' + kstr + return astr + if kstr: + return kstr + return '1' + + def _latex_term(self, m): + r""" + Return a latex representation for the basis element indexed by ``m``. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: O._latex_term((1, 3)) + 'a^+ k^{3}' + sage: O._latex_term((-1, 1)) + 'a^- k' + sage: O._latex_term((5, 0)) + '(a^+)^{5}' + sage: O._latex_term((-4, -2)) + '(a^-)^{4} k^{-2}' + sage: O._latex_term((0, -4)) + 'k^{-4}' + sage: O._latex_term((0, 0)) + '1' + + sage: latex(O(5)) + 5 + """ + a, k = m + + astr = '' + if a == 1: + astr = 'a^+' + elif a > 1: + astr = '(a^+)^{{{}}}'.format(a) + elif a == -1: + astr = 'a^-' + elif a < -1: + astr = '(a^-)^{{{}}}'.format(-a) + + kstr = '' + if k == 1: + kstr = 'k' + elif k != 0: + kstr = 'k^{{{}}}'.format(k) + + if astr: + if kstr: + return astr + ' ' + kstr + return astr + if kstr: + return kstr + return '1' + + @cached_method + def product_on_basis(self, ml, mr): + r""" + Return the product of the basis elements indexed by ``ml`` and ``mr``. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: ap, am, k, ki = O.algebra_generators() + sage: O.product_on_basis((-2, 3), (-4, 5)) + 1/q^12*a-^6*k^8 + sage: O.product_on_basis((2, 3), (4, -5)) + q^12*a+^6*k^-2 + sage: O.product_on_basis((2, 3), (0, -3)) + a+^2 + sage: k^5 * ki^10 + k^-5 + sage: k^10 * ki^5 + k^5 + sage: ap^3 * k^5 + a+^3*k^5 + sage: am^3 * k^5 + a-^3*k^5 + sage: k^5 * ap^3 + q^15*a+^3*k^5 + sage: k^5 * am^3 + 1/q^15*a-^3*k^5 + sage: ki^5 * ap^3 + 1/q^15*a+^3*k^-5 + sage: ki^5 * am^3 + q^15*a-^3*k^-5 + sage: ap * am + 1 - k^2 + sage: am * ap + 1 - q^2*k^2 + + sage: (ap + am + k + ki)^2 + a-^2 + (q+1)*a-*k^-1 + ((q+1)/q)*a-*k + k^-2 + 4 - q^2*k^2 + + ((q+1)/q)*a+*k^-1 + (q+1)*a+*k + a+^2 + + sage: (ap)^3 * (am)^5 + a-^2 + ((-q^4-q^2-1)/q^8)*a-^2*k^2 + ((q^4+q^2+1)/q^14)*a-^2*k^4 - 1/q^18*a-^2*k^6 + sage: (ap)^5 * (am)^3 + a+^2 + ((-q^4-q^2-1)/q^4)*a+^2*k^2 + ((q^4+q^2+1)/q^6)*a+^2*k^4 - 1/q^6*a+^2*k^6 + sage: (am)^3 * (ap)^5 + a+^2 + (-q^10-q^8-q^6)*a+^2*k^2 + (q^18+q^16+q^14)*a+^2*k^4 - q^24*a+^2*k^6 + sage: (am)^5 * (ap)^3 + a-^2 + (-q^6-q^4-q^2)*a-^2*k^2 + (q^10+q^8+q^6)*a-^2*k^4 - q^12*a-^2*k^6 + """ + q = self._q + k = self._k_poly.gen() + al, kl = ml + ar, kr = mr + coeff = q ** (kl * ar) + if (al <= 0 and ar <= 0) or (al >= 0 and ar >= 0): + return self.element_class(self, {(al + ar, kl + kr): coeff}) + # now al and ar have different signs + if al < 0: # a^- * a^+ case + kp = self._k_poly.prod(1 - q**(2*(ar-i)) * k**2 for i in range(min(-al,ar))) + else: # a^+ * a^- case + kp = self._k_poly.prod(1 - q**(2*(ar+i)) * k**2 for i in range(1,min(al,-ar)+1)) + a = al + ar + return self.element_class(self, {(a, kl+kr+i): c * coeff for i, c in enumerate(kp) if c}) + + class Element(CombinatorialFreeModule.Element): + def __invert__(self): + r""" + Return the inverse if ``self`` is a basis element. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: ap, am, k, ki = O.algebra_generators() + sage: k.inverse() + k^-1 + sage: ~k^5 + k^-5 + sage: ~ki^2 + k^2 + sage: O.zero().inverse() + Traceback (most recent call last): + ... + ZeroDivisionError + sage: ~ap + Traceback (most recent call last): + ... + NotImplementedError: only implemented for monomials in k + sage: ~(k + ki) + Traceback (most recent call last): + ... + NotImplementedError: only implemented for monomials in k + """ + if not self: + raise ZeroDivisionError + if len(self) != 1 or self.leading_support()[0] != 0: + raise NotImplementedError("only implemented for monomials in k") + + ((a, k), coeff), = list(self._monomial_coefficients.items()) + O = self.parent() + return O.element_class(O, {(a, -k): coeff.inverse_of_unit()}) + + +class FockSpaceRepresentation(CombinatorialFreeModule): + r""" + The unique Fock space representation of the + :class:`~sage.algebras.quantum_oscillator.QuantumOscillatorAlgebra`. + """ + def __init__(self, oscillator_algebra): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: F = O.fock_space_representation() + sage: TestSuite(F).run() + """ + self._O = oscillator_algebra + ind = NonNegativeIntegers() + CombinatorialFreeModule.__init__(self, oscillator_algebra.base_ring(), ind, prefix='', bracket=['|', '>'], + latex_bracket=[r'\lvert', r'\rangle']) + + def _test_representation(self, **options): + r""" + Test that ``self`` is a representation of the quantum + oscillator algebra. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator(q=GF(7)(3)) + sage: F = O.fock_space_representation() + sage: F._test_representation() + """ + tester = self._tester(**options) + S = self._O.some_elements() + num_trials = 0 + from itertools import product + for a, b in product(S, repeat=2): + for elt in tester.some_elements(): + num_trials += 1 + if num_trials > tester._max_runs: + return + tester.assertEqual((a*b)*elt, a*(b*elt)) + + def _repr_(self) -> str: + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator(q=GF(5)(2)) + sage: O.fock_space_representation() + Fock space representation of Quantum oscillator algebra + with q=2 over Finite Field of size 5 + """ + return "Fock space representation of {}".format(self._O) + + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: F = O.fock_space_representation() + sage: latex(F) + \mathfrak{F}_{q} + """ + return r"\mathfrak{{F}}_{{{}}}".format(self._O._q) + + def vacuum(self): + r""" + Return the vacuum element `|0\rangle` of ``self``. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: F = O.fock_space_representation() + sage: F.vacuum() + |0> + """ + return self.basis()[0] + + def some_elements(self): + r""" + Return some elements of ``self``. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: F = O.fock_space_representation() + sage: F.some_elements() + (|0>, |1>, |52>, |0> + 2*|1> + 3*|2> + |42>) + """ + B = self.basis() + return (B[0], B[1], B[52], self.an_element()) + + class Element(CombinatorialFreeModule.Element): + def _acted_upon_(self, scalar, self_on_left=True): + r""" + Return the action of ``scalar`` on ``self``. + + EXAMPLES:: + + sage: O = algebras.QuantumOscillator() + sage: ap, am, k, ki = O.gens() + sage: F = O.fock_space_representation() + sage: B = F.basis() + sage: [ap * B[i] for i in range(3)] + [|1>, |2>, |3>] + sage: [am * B[i] for i in range(3)] + [0, (-q^2+1)*|0>, (-q^4+1)*|1>] + sage: [k * B[i] for i in range(3)] + [|0>, q*|1>, q^2*|2>] + sage: [ki * B[i] for i in range(3)] + [|0>, 1/q*|1>, 1/q^2*|2>] + sage: (am)^3 * B[5] + (-q^24+q^18+q^16+q^14-q^10-q^8-q^6+1)*|2> + sage: (7*k^3 + am) * (B[0] + B[1] + B[2]) + (-q^2+8)*|0> + (-q^4+7*q^3+1)*|1> + 7*q^6*|2> + sage: 5 * (B[2] + B[3]) + 5*|2> + 5*|3> + """ + # Check for scalars first + ret = super()._acted_upon_(scalar, self_on_left) + if ret is not None: + return ret + P = self.parent() + if self_on_left or scalar not in P._O: # needs to be a left Osc-action + return None + scalar = P._O(scalar) + q = P._O._q + + ret = [] + for om, oc in scalar: + a, k = om + for fm, fc in self: + if fm < -a: # the result will be 0 + continue + c = q ** (fm*k) + if a < 0: + c *= prod(1 - q**(2*(fm-i)) for i in range(-a)) + if c: + ret.append((fm+a, oc * fc * c)) + return P.sum_of_terms(ret) diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 849a5bdd727..2c97fd70429 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -919,6 +919,29 @@ def invariants(self): """ return self._a, self._b + def is_definite(self): + """ + Checks whether the quaternion algebra ``self`` is definite, i.e. whether it ramifies at the + unique Archimedean place of its base field QQ. This is the case if and only if both + invariants of ``self`` are negative. + + EXAMPLES:: + + sage: QuaternionAlgebra(QQ,-5,-2).is_definite() + True + sage: QuaternionAlgebra(1).is_definite() + False + + sage: QuaternionAlgebra(RR(2.),1).is_definite() + Traceback (most recent call last): + ... + ValueError: base field must be rational numbers + """ + if not is_RationalField(self.base_ring()): + raise ValueError("base field must be rational numbers") + a, b = self.invariants() + return a < 0 and b < 0 + def __eq__(self, other): """ Compare self and other. @@ -2101,16 +2124,20 @@ def ternary_quadratic_form(self, include_basis=False): else: return Q - def isomorphism_to(self, other, *, conjugator=False): + def isomorphism_to(self, other, *, conjugator=False, B=10): r""" Compute an isomorphism from this quaternion order `O` to another order `O'` in the same quaternion algebra. - If the optional keyword argument ``conjugator`` is set - to ``True``, this method returns a single quaternion - `\gamma \in O \cap O'` of minimal norm such that - `O' = \gamma^{-1} O \gamma`, - rather than the ring isomorphism it defines. + INPUT: + + - ``conjugator`` -- bool (default: False), if True this + method returns a single quaternion `\gamma \in O \cap O'` + of minimal norm such that `O' = \gamma^{-1} O \gamma`, + rather than the ring isomorphism it defines. + + - ``B`` -- postive integer, bound on theta series + coefficients to rule out non isomorphic orders. .. NOTE:: @@ -2183,6 +2210,15 @@ def isomorphism_to(self, other, *, conjugator=False): sage: {iso(g * h) == iso(g) * iso(h) for g in els for h in els} {True} + Test edge case:: + + sage: Quat. = QuaternionAlgebra(419) + sage: O = Quat.quaternion_order([1/2 + 3/2*j + k, 1/18*i + 25/9*j + 5/6*k, 3*j + 2*k, 3*k]) + sage: Oconj = j.inverse() * O * j + sage: Oconj = Quat.quaternion_order(Oconj.basis()) + sage: O.isomorphism_to(Oconj, conjugator=True) + -j + Test error cases:: sage: Quat. = QuaternionAlgebra(-1,-11) @@ -2235,36 +2271,85 @@ def isomorphism_to(self, other, *, conjugator=False): ... ValueError: quaternion orders not isomorphic + :: + + sage: Quat. = QuaternionAlgebra(-5, -17) + sage: O1 = Quat.quaternion_order([1, i, j, 1/2 + 1/2*i + 1/2*j + 1/2*k]) + sage: O2 = Quat.quaternion_order([1/2 + 1/2*i + 1/6*j + 13/6*k, i, 1/3*j + 4/3*k, 3*k]) + sage: O1.isomorphism_to(O2) + Traceback (most recent call last): + ... + NotImplementedError: isomorphism_to was not able to recognize the given orders as isomorphic + ALGORITHM: Find a generator of the principal lattice `N\cdot O\cdot O'` where `N = [O : O cap O']` using :meth:`QuaternionFractionalIdeal_rational.minimal_element()`. An isomorphism is given by conjugation by such an element. - """ + Works providing reduced norm of conjugation element is not + a ramified prime times a square. To cover cases where it is + we repeat the check for orders conjugated by i, j, and k. + """ + + # Method to find isomorphism, which might not work when O2 is + # O1 conjugated by an alpha such that nrd(alpha) is a + # ramified prime times a square + def attempt_isomorphism(self, other): + N = self.intersection(other).free_module().index_in(self.free_module()) + I = N * self * other + gamma = I.minimal_element() + if self*gamma != I: + return False, None + if gamma*other != I: + return False, None + return True, gamma + if not isinstance(other, QuaternionOrder): raise TypeError('not a quaternion order') Q = self.quaternion_algebra() if other.quaternion_algebra() != Q: raise TypeError('not an order in the same quaternion algebra') - if not self.quadratic_form().is_positive_definite(): + if not is_RationalField(Q.base_ring()): + raise NotImplementedError('only implemented for orders in a rational quaternion algebra') + if not Q.is_definite(): raise NotImplementedError('only implemented for definite quaternion orders') if not (self.discriminant() == Q.discriminant() == other.discriminant()): raise NotImplementedError('only implemented for maximal orders') - N = self.intersection(other).free_module().index_in(self.free_module()) - I = N * self * other - gamma = I.minimal_element() - if self*gamma != I: + # First try a theta series check, up to bound B + if self.unit_ideal().theta_series_vector(B) != other.unit_ideal().theta_series_vector(B): raise ValueError('quaternion orders not isomorphic') - assert gamma*other == I - if conjugator: - return gamma + # Want to iterate over elements alpha where the square-free part of nrd(alpha) divides prod(Q.ramified_primes()), + # and each time try attempt_isomorphism with the order conjugated by alpha. + # But in general finding all such elements alpha is hard, + # so we just try 1, i, j, k first. + for alpha in [1] + list(Q.gens()): + other_conj = other + if alpha != 1: + other_conj = Q.quaternion_order((alpha * other * alpha.inverse()).basis()) + found, gamma = attempt_isomorphism(self, other_conj) + if found: + gamma = gamma * alpha + if conjugator: + return gamma + else: + ims = [~gamma * gen * gamma for gen in Q.gens()] + return self.hom(ims, other, check=False) + + # We can tell if 1, i, j, k cover all the alpha we need to test, + # by checking if we have additional ramified primes which are not the square-free parts of nrd(i), nrd(j) or nrd(k) + a, b = -Q.invariants()[0], -Q.invariants()[1] + square_free_invariants = [a.squarefree_part(), b.squarefree_part(), (a*b).squarefree_part()] + is_result_guaranteed = len([a for a in Q.ramified_primes() if a not in square_free_invariants]) == 0 + + if is_result_guaranteed: + raise ValueError('quaternion orders not isomorphic') - ims = [~gamma * gen * gamma for gen in Q.gens()] - return self.hom(ims, other, check=False) + # Otherwise, there might be other unknown alpha's giving isomorphism. If so we can't find them. + raise NotImplementedError("isomorphism_to was not able to recognize the given orders as isomorphic") class QuaternionFractionalIdeal(Ideal_fractional): @@ -2316,6 +2401,8 @@ def __init__(self, Q, basis, left_order=None, right_order=None, check=True): raise TypeError("basis must be a list or tuple") basis = tuple([Q(v) for v in (QQ**4).span([Q(v).coefficient_tuple() for v in basis], ZZ).basis()]) + if len(basis) != 4: + raise ValueError("fractional ideal must have rank 4") self.__left_order = left_order self.__right_order = right_order Ideal_fractional.__init__(self, Q, basis) @@ -2667,6 +2754,39 @@ def basis_matrix(self): C, d = B._clear_denom() return C.hermite_form() / d + def reduced_basis(self): + r""" + Let `I` = ``self`` be a fractional ideal in a (rational) definite quaternion algebra. + This function returns an LLL reduced basis of I. + + OUTPUT: + + - A tuple of four elements in I forming an LLL reduced basis of I as a lattice + + EXAMPLES:: + + sage: B = BrandtModule(2,37); I = B.right_ideals()[0] + sage: I + Fractional ideal (2 + 2*i + 2*j + 2*k, 4*i + 108*k, 4*j + 44*k, 148*k) + sage: I.reduced_basis() + (2 + 2*i + 2*j + 2*k, 4, -2 - 2*i - 14*j + 14*k, -16*i + 12*k) + sage: l = I.reduced_basis() + sage: assert all(l[i].reduced_norm() <= l[i+1].reduced_norm() for i in range(len(l) - 1)) + + sage: B = QuaternionAlgebra(next_prime(2**50)) + sage: O = B.maximal_order() + sage: i,j,k = B.gens() + sage: alpha = 1/2 - 1/2*i + 3/2*j - 7/2*k + sage: I = O*alpha + O*3089622859 + sage: I.reduced_basis()[0] + 1/2*i + j + 5/2*k + """ + if not self.quaternion_algebra().is_definite(): + raise TypeError("The quaternion algebra must be definite") + + U = self.gram_matrix().LLL_gram().transpose() + return tuple(sum(c * g for c, g in zip(row, self.basis())) for row in U) + def theta_series_vector(self, B): r""" Return theta series coefficients of ``self``, as a vector @@ -2757,10 +2877,9 @@ def minimal_element(self): sage: el.reduced_norm() 282 """ - qf = self.quadratic_form() - if not qf.is_positive_definite(): + if not self.quaternion_algebra().is_definite(): raise ValueError('quaternion algebra must be definite') - pariqf = qf.__pari__() + pariqf = self.quadratic_form().__pari__() _,v = pariqf.qfminim(None, None, 1) return sum(ZZ(c)*g for c,g in zip(v, self.basis())) @@ -3063,9 +3182,11 @@ def multiply_by_conjugate(self, J): R = self.quaternion_algebra() return R.ideal(basis, check=False) - def is_equivalent(self, J, B=10) -> bool: - """ - Return ``True`` if ``self`` and ``J`` are equivalent as right ideals. + def is_equivalent(self, J, B=10, certificate=False, side=None): + r""" + Checks whether ``self`` and ``J`` are equivalent as ideals. + Tests equivalence as right ideals by default. Requires the underlying + rational quaternion algebra to be definite. INPUT: @@ -3074,44 +3195,168 @@ def is_equivalent(self, J, B=10) -> bool: - ``B`` -- a bound to compute and compare theta series before doing the full equivalence test - OUTPUT: bool + - ``certificate`` -- if ``True`` returns an element alpha such that + alpha*J = I or J*alpha = I for right and left ideals respectively + + - ``side`` -- If ``'left'`` performs left equivalence test. If ``'right' + ``or ``None`` performs right ideal equivalence test + + OUTPUT: bool, or (bool, alpha) if ``certificate`` is ``True`` EXAMPLES:: sage: R = BrandtModule(3,5).right_ideals(); len(R) 2 - sage: R[0].is_equivalent(R[1]) + sage: OO = R[0].left_order() + sage: S = OO.right_ideal([3*a for a in R[0].basis()]) + sage: R[0].is_equivalent(S) + doctest:...: DeprecationWarning: is_equivalent is deprecated, + please use is_left_equivalent or is_right_equivalent + accordingly instead + See https://github.com/sagemath/sage/issues/37100 for details. + True + """ + from sage.misc.superseded import deprecation + deprecation(37100, 'is_equivalent is deprecated, please use is_left_equivalent' + ' or is_right_equivalent accordingly instead') + if side == 'left': + return self.is_left_equivalent(J, B, certificate) + # If None, assume right ideals, for backwards compatibility + return self.is_right_equivalent(J, B, certificate) + + def is_left_equivalent(self, J, B=10, certificate=False): + r""" + Checks whether ``self`` and ``J`` are equivalent as left ideals. + Requires the underlying rational quaternion algebra to be definite. + + INPUT: + + - ``J`` -- a fractional quaternion left ideal with same order as ``self`` + + - ``B`` -- a bound to compute and compare theta series before + doing the full equivalence test + + - ``certificate`` -- if ``True`` returns an element alpha such that J*alpha=I + + OUTPUT: bool, or (bool, alpha) if ``certificate`` is ``True`` + """ + if certificate: + is_equiv, cert = self.conjugate().is_right_equivalent(J.conjugate(), B, True) + if is_equiv: + return True, cert.conjugate() + return False, None + return self.conjugate().is_right_equivalent(J.conjugate(), B, False) + + def is_right_equivalent(self, J, B=10, certificate=False): + r""" + Checks whether ``self`` and ``J`` are equivalent as right ideals. + Requires the underlying rational quaternion algebra to be definite. + + INPUT: + + - ``J`` -- a fractional quaternion right ideal with same order as ``self`` + + - ``B`` -- a bound to compute and compare theta series before + doing the full equivalence test + + - ``certificate`` -- if ``True`` returns an element alpha such that alpha*J=I + + OUTPUT: bool, or (bool, alpha) if ``certificate`` is ``True`` + + EXAMPLES:: + + sage: R = BrandtModule(3,5).right_ideals(); len(R) + 2 + sage: R[0].is_right_equivalent(R[1]) False - sage: R[0].is_equivalent(R[0]) + + sage: R[0].is_right_equivalent(R[0]) True sage: OO = R[0].left_order() sage: S = OO.right_ideal([3*a for a in R[0].basis()]) - sage: R[0].is_equivalent(S) + sage: R[0].is_right_equivalent(S, certificate=True) + (True, -1/3) + sage: -1/3*S == R[0] + True + + sage: B = QuaternionAlgebra(101) + sage: i,j,k = B.gens() + sage: I = B.maximal_order().unit_ideal() + sage: beta = B.random_element() # random + sage: J = beta*I + sage: bool, alpha = I.is_right_equivalent(J, certificate=True) + sage: bool + True + sage: alpha*J == I True """ - # shorthand: let I be self - if not isinstance(self, QuaternionFractionalIdeal_rational): - return False + if not isinstance(J, QuaternionFractionalIdeal_rational): + raise TypeError('J must be a fractional ideal' + ' in a rational quaternion algebra') if self.right_order() != J.right_order(): - raise ValueError("self and J must be right ideals") + raise ValueError('self and J must be right ideals over the same order') - # Just test theta series first. If the theta series are - # different, the ideals are definitely not equivalent. + if not self.quaternion_algebra().is_definite(): + raise NotImplementedError('equivalence test of ideals not implemented' + ' for indefinite quaternion algebras') + + # Just test theta series first; if the theta series are + # different, the ideals are definitely not equivalent if B > 0 and self.theta_series_vector(B) != J.theta_series_vector(B): + if certificate: + return False, None return False - # The theta series are the same, so perhaps the ideals are - # equivalent. We use Prop 1.18 of [Pizer, 1980] to decide. + # The theta series are the same, so perhaps the ideals are equivalent + # We adapt Prop 1.18 of [Piz1980]_ to right ideals to decide: # 1. Compute I * Jbar - # see Prop. 1.17 in Pizer. Note that we use IJbar instead of - # JbarI since we work with right ideals IJbar = self.multiply_by_conjugate(J) - # 2. Determine if there is alpha in K such - # that N(alpha) = N(I)*N(J) as explained by Pizer. - c = IJbar.theta_series_vector(2)[1] - return c != 0 + # 2. Determine if there is alpha in I * Jbar with N(alpha) = N(I)*N(J) + # Equivalently, we can simply call the principality test on IJbar, + # but we rescale by 1/N(J) to make sure this test directly gives back + # the correct alpha if a certificate is requested + return (1/J.norm()*IJbar).is_principal(certificate) + + def is_principal(self, certificate=False): + r""" + Checks whether ``self`` is principal as a full rank quaternion ideal. + Requires the underlying quaternion algebra to be definite. + Independent of whether ``self`` is a left or a right ideal. + + INPUT: + + - ``certificate`` -- if ``True`` returns a generator alpha s.t. I = alpha*O + where O is the right order of I. + + OUTPUT: bool, or (bool, alpha) if ``certificate`` is ``True`` + + EXAMPLES:: + + sage: B. = QuaternionAlgebra(419) + sage: O = B.quaternion_order([1/2 + 3/2*j, 1/6*i + 2/3*j + 1/2*k, 3*j, k]) + sage: beta = O.random_element() # random + sage: I = O*beta + sage: bool, alpha = I.is_principal(True) + sage: bool + True + sage: I == O*alpha + True + """ + if not self.quaternion_algebra().is_definite(): + raise NotImplementedError('principality test not implemented in' + ' indefinite quaternion algebras') + + c = self.theta_series_vector(2)[1] + if not certificate: + return c != 0 + if certificate and c == 0: + return False, None + + # From this point on we know that self is principal, so it suffices to + # find an element of minimal norm in self; see [Piz1980]_, Corollary 1.20. + return True, self.minimal_element() def __contains__(self, x): """ 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 e6ed304aa7b..6cb7badd99a 100644 --- a/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py +++ b/src/sage/categories/finite_dimensional_lie_algebras_with_basis.py @@ -7,7 +7,7 @@ """ # **************************************************************************** -# Copyright (C) 2013-2017 Travis Scrimshaw +# Copyright (C) 2013-2024 Travis Scrimshaw # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -1745,10 +1745,10 @@ def universal_commutative_algebra(self): R = P[0].parent() return R.quotient(P) - def casimir_element(self, order=2, UEA=None, force_generic=False): + def casimir_element(self, order=2, UEA=None, force_generic=False, basis=False): r""" - Return the Casimir element in the universal enveloping algebra - of ``self``. + Return a Casimir element of order ``order`` in the universal + enveloping algebra of ``self``. A *Casimir element* of order `k` is a distinguished basis element for the center of `U(\mathfrak{g})` of homogeneous degree `k` @@ -1760,11 +1760,13 @@ def casimir_element(self, order=2, UEA=None, force_generic=False): INPUT: - ``order`` -- (default: ``2``) the order of the Casimir element - - ``UEA`` -- (optional) the universal enveloping algebra to - return the result in + - ``UEA`` -- (optional) the universal enveloping algebra + implementation to return the result in - ``force_generic`` -- (default: ``False``) if ``True`` for the quadratic order, then this uses the default algorithm; otherwise this is ignored + - ``basis`` -- (default: ``False``) if ``True``, this returns a + basis of all Casimir elements of order ``order`` as a list ALGORITHM: @@ -1832,6 +1834,13 @@ def casimir_element(self, order=2, UEA=None, force_generic=False): sage: L.casimir_element() 0 + sage: # needs sage.combinat sage.modules + sage: g = LieAlgebra(QQ, cartan_type=['D',2]) + sage: U = g.pbw_basis() + sage: U.casimir_element(2, basis=True) + [2*PBW[alpha[2]]*PBW[-alpha[2]] + 1/2*PBW[alphacheck[2]]^2 - PBW[alphacheck[2]], + 2*PBW[alpha[1]]*PBW[-alpha[1]] + 1/2*PBW[alphacheck[1]]^2 - PBW[alphacheck[1]]] + TESTS:: sage: # needs sage.combinat sage.modules @@ -1856,7 +1865,7 @@ def casimir_element(self, order=2, UEA=None, force_generic=False): B = self.basis() - if order == 2 and not force_generic: + if order == 2 and not force_generic and not basis: # Special case for the quadratic using the Killing form try: K = self.killing_form_matrix().inverse() @@ -1896,11 +1905,10 @@ def casimir_element(self, order=2, UEA=None, force_generic=False): if ker.dimension() == 0: return self.zero() - tens = ker.basis()[0] del eqns # no need to hold onto the matrix - def to_prod(index): - coeff = tens[index] + def to_prod(vec, index): + coeff = vec[index] p = [0] * order base = dim ** (order-1) for i in range(order): @@ -1910,7 +1918,14 @@ def to_prod(index): p.reverse() return coeff * UEA.prod(UEA(B[keys[i]]) for i in p) - return UEA.sum(to_prod(index) for index in tens.support()) + tens = ker.basis() + + if not basis: + vec = tens[0] + return UEA.sum(to_prod(vec, index) for index in vec.support()) + + return [UEA.sum(to_prod(vec, index) for index in vec.support()) + for vec in tens] class ElementMethods: def adjoint_matrix(self, sparse=False): # In #11111 (more or less) by using matrix of a morphism diff --git a/src/sage/categories/lie_algebras.py b/src/sage/categories/lie_algebras.py index 81596a7786c..d0958f12d89 100644 --- a/src/sage/categories/lie_algebras.py +++ b/src/sage/categories/lie_algebras.py @@ -346,6 +346,25 @@ def _construct_UEA(self): Multivariate Polynomial Ring in x0, x1, x2 over Rational Field """ + def center_universal_enveloping_algebra(self, UEA=None): + """ + Return the center of the universal enveloping algebra of ``self``. + + EXAMPLES:: + + sage: L = LieAlgebra(QQ, 3, 'x', abelian=True) + sage: L.center_universal_enveloping_algebra() + Center of Universal enveloping algebra of Abelian Lie algebra on 3 generators (x0, x1, x2) + over Rational Field in the Poincare-Birkhoff-Witt basis + sage: PBW = L.pbw_basis() + sage: L.center_universal_enveloping_algebra(PBW) + Center of Universal enveloping algebra of Abelian Lie algebra on 3 generators (x0, x1, x2) + over Rational Field in the Poincare-Birkhoff-Witt basis + """ + if UEA is not None: + return UEA.center() + return self.pbw_basis().center() + @abstract_method(optional=True) def module(self): r""" diff --git a/src/sage/categories/lie_algebras_with_basis.py b/src/sage/categories/lie_algebras_with_basis.py index 6eaebfde844..67570b611b3 100644 --- a/src/sage/categories/lie_algebras_with_basis.py +++ b/src/sage/categories/lie_algebras_with_basis.py @@ -7,13 +7,13 @@ """ #***************************************************************************** -# Copyright (C) 2013-2017 Travis Scrimshaw +# Copyright (C) 2013-2024 Travis Scrimshaw # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. -# http://www.gnu.org/licenses/ +# https://www.gnu.org/licenses/ #***************************************************************************** from sage.misc.abstract_method import abstract_method @@ -239,10 +239,6 @@ def lift(self): """ P = self.parent() UEA = P.universal_enveloping_algebra() - try: - gen_dict = UEA.algebra_generators() - except (TypeError, AttributeError): - gen_dict = UEA.gens_dict() s = UEA.zero() if not self: return s @@ -250,9 +246,14 @@ def lift(self): # does not match the generators index set of the UEA. if hasattr(P, '_UEA_names_map'): names_map = P._UEA_names_map + gen_dict = UEA.gens_dict() for t, c in self.monomial_coefficients(copy=False).items(): s += c * gen_dict[names_map[t]] else: + try: + gen_dict = UEA.algebra_generators() + except (TypeError, AttributeError): + gen_dict = UEA.gens_dict() for t, c in self.monomial_coefficients(copy=False).items(): s += c * gen_dict[t] return s diff --git a/src/sage/categories/magmatic_algebras.py b/src/sage/categories/magmatic_algebras.py index f0537c104d6..763f5aab764 100644 --- a/src/sage/categories/magmatic_algebras.py +++ b/src/sage/categories/magmatic_algebras.py @@ -221,8 +221,8 @@ def _product_from_product_on_basis_multiply( self, left, right ): """ return self.linear_combination((self.product_on_basis(mon_left, mon_right), coeff_left * coeff_right ) - for (mon_left, coeff_left) in left.monomial_coefficients().items() - for (mon_right, coeff_right) in right.monomial_coefficients().items() ) + for (mon_left, coeff_left) in left.monomial_coefficients(copy=False).items() + for (mon_right, coeff_right) in right.monomial_coefficients(copy=False).items() ) class FiniteDimensional(CategoryWithAxiom_over_base_ring): class ParentMethods: diff --git a/src/sage/combinat/algebraic_combinatorics.py b/src/sage/combinat/algebraic_combinatorics.py index a024d2fa67d..3dd16bf7799 100644 --- a/src/sage/combinat/algebraic_combinatorics.py +++ b/src/sage/combinat/algebraic_combinatorics.py @@ -12,7 +12,8 @@ ---------------------------------------- - :ref:`sage.combinat.catalog_partitions` -- :class:`~sage.combinat.gelfand_tsetlin_patterns.GelfandTsetlinPattern`, :class:`~sage.combinat.gelfand_tsetlin_patterns.GelfandTsetlinPatterns` +- :class:`~sage.combinat.gelfand_tsetlin_patterns.GelfandTsetlinPattern`, + :class:`~sage.combinat.gelfand_tsetlin_patterns.GelfandTsetlinPatterns` - :class:`~sage.combinat.knutson_tao_puzzles.KnutsonTaoPuzzleSolver` Groups and Algebras @@ -39,7 +40,7 @@ - :ref:`sage.combinat.cluster_algebra_quiver.all` - :class:`~sage.combinat.kazhdan_lusztig.KazhdanLusztigPolynomial` - :class:`~sage.combinat.symmetric_group_representations.SymmetricGroupRepresentation` -- :class:`~sage.combinat.specht_module.SpechtModule` +- :ref:`sage.combinat.specht_module` - :ref:`sage.combinat.yang_baxter_graph` - :ref:`sage.combinat.hall_polynomial` - :ref:`sage.combinat.key_polynomial` diff --git a/src/sage/combinat/catalog_partitions.py b/src/sage/combinat/catalog_partitions.py index ca143fa540c..302e8f112d6 100644 --- a/src/sage/combinat/catalog_partitions.py +++ b/src/sage/combinat/catalog_partitions.py @@ -8,6 +8,7 @@ - :ref:`sage.combinat.skew_partition` - :ref:`sage.combinat.partition_tuple` - :ref:`sage.combinat.superpartition` +- :ref:`sage.combinat.tableau` - :ref:`sage.combinat.tableau_tuple` - :ref:`sage.combinat.skew_tableau` - :ref:`sage.combinat.ribbon` diff --git a/src/sage/combinat/composition.py b/src/sage/combinat/composition.py index 8d01e9ac081..0be0ba09074 100644 --- a/src/sage/combinat/composition.py +++ b/src/sage/combinat/composition.py @@ -45,6 +45,9 @@ from sage.combinat.combinatorial_map import combinatorial_map from sage.misc.persist import register_unpickle_override +from sage.misc.lazy_import import lazy_import +lazy_import("sage.combinat.partition", "Partition") + class Composition(CombinatorialElement): r""" @@ -1186,7 +1189,6 @@ def to_partition(self): sage: Composition([]).to_partition() # needs sage.combinat [] """ - from sage.combinat.partition import Partition return Partition(sorted(self, reverse=True)) def to_skew_partition(self, overlap=1): @@ -1753,8 +1755,10 @@ def _element_constructor_(self, lst) -> Composition: sage: P = Compositions() sage: P([3,3,1]) # indirect doctest [3, 3, 1] + sage: P(Partition([5,2,1])) + [5, 2, 1] """ - if isinstance(lst, Composition): + if isinstance(lst, (Composition, Partition)): lst = list(lst) elt = self.element_class(self, lst) if elt not in self: @@ -1774,7 +1778,7 @@ def __contains__(self, x) -> bool: sage: [0,0] in Compositions() True """ - if isinstance(x, Composition): + if isinstance(x, (Composition, Partition)): return True elif isinstance(x, list): for i in x: diff --git a/src/sage/combinat/partition.py b/src/sage/combinat/partition.py index 3ff9bb2f1d6..196cc0b401a 100644 --- a/src/sage/combinat/partition.py +++ b/src/sage/combinat/partition.py @@ -5495,9 +5495,8 @@ def specht_module(self, base_ring=None): EXAMPLES:: sage: SM = Partition([2,2,1]).specht_module(QQ); SM - Specht module of [(0, 0), (0, 1), (1, 0), (1, 1), (2, 0)] over Rational Field - sage: s = SymmetricFunctions(QQ).s() - sage: s(SM.frobenius_image()) # needs sage.modules + Specht module of [2, 2, 1] over Rational Field + sage: SM.frobenius_image() # needs sage.modules s[2, 2, 1] """ from sage.combinat.specht_module import SpechtModule @@ -5570,6 +5569,24 @@ def simple_module_dimension(self, base_ring=None): from sage.combinat.specht_module import simple_module_rank return simple_module_rank(self, base_ring) + def tabloid_module(self, base_ring=None): + r""" + Return the tabloid module corresponding to ``self``. + + EXAMPLES:: + + sage: TM = Partition([2,2,1]).tabloid_module(QQ); TM + Tabloid module of [2, 2, 1] over Rational Field + sage: TM.frobenius_image() + s[2, 2, 1] + s[3, 1, 1] + 2*s[3, 2] + 2*s[4, 1] + s[5] + """ + from sage.combinat.specht_module import TabloidModule + from sage.combinat.symmetric_group_algebra import SymmetricGroupAlgebra + if base_ring is None: + from sage.rings.rational_field import QQ + base_ring = QQ + R = SymmetricGroupAlgebra(base_ring, sum(self)) + return TabloidModule(R, self) ############## # Partitions # diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index 8152ed81a05..4769442d3dd 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -1263,7 +1263,7 @@ def __mul__(self, rp): sage: p213 * SGA.an_element() 3*[1, 2, 3] + [1, 3, 2] + [2, 1, 3] + 2*[3, 1, 2] sage: p213 * SM.an_element() - 2*B[0] - 4*B[1] + 2*S[[1, 2], [3]] - 4*S[[1, 3], [2]] """ if not isinstance(rp, Permutation) and isinstance(rp, Element): return get_coercion_model().bin_op(self, rp, operator.mul) @@ -7092,7 +7092,19 @@ def _element_constructor_(self, x, check=True): [1, 4, 5, 2, 3, 6] sage: Permutations(6)(x) # known bug [1, 4, 5, 2, 3, 6] + + Ensure that :issue:`37284` is fixed:: + + sage: PG = PermutationGroup([[(1,2,3),(5,6)],[(7,8)]]) + sage: P8 = Permutations(8) + sage: p = PG.an_element() + sage: q = P8(p); q + [2, 3, 1, 4, 6, 5, 8, 7] + sage: q.parent() + Standard permutations of 8 """ + if isinstance(x, PermutationGroupElement): + return self. _from_permutation_group_element(x) if len(x) < self.n: x = list(x) + list(range(len(x) + 1, self.n + 1)) return self.element_class(self, x, check=check) @@ -7342,6 +7354,19 @@ def cardinality(self): """ return factorial(self.n) + @cached_method + def gens(self) -> tuple: + r""" + Return a set of generators for ``self`` as a group. + + EXAMPLES:: + + sage: P4 = Permutations(4) + sage: P4.gens() + ([2, 1, 3, 4], [1, 3, 2, 4], [1, 2, 4, 3]) + """ + return tuple(self.group_generators()) + def degree(self): """ Return the degree of ``self``. diff --git a/src/sage/combinat/skew_tableau.py b/src/sage/combinat/skew_tableau.py index 44dece78ab0..a342e7005e8 100644 --- a/src/sage/combinat/skew_tableau.py +++ b/src/sage/combinat/skew_tableau.py @@ -6,11 +6,11 @@ - Mike Hansen: Initial version - Travis Scrimshaw, Arthur Lubovsky (2013-02-11): Factored out ``CombinatorialClass`` -- Trevor K. Karn (2022-08-03): added `backward_slide` +- Trevor K. Karn (2022-08-03): added ``backward_slide`` """ # **************************************************************************** # Copyright (C) 2007 Mike Hansen , -# Copyright (C) 2013 Travis Scrimshaw +# Copyright (C) 2013 Travis Scrimshaw # Copyright (C) 2013 Arthur Lubovsky # # Distributed under the terms of the GNU General Public License (GPL) diff --git a/src/sage/combinat/specht_module.py b/src/sage/combinat/specht_module.py index 62842b11a30..127903829f7 100644 --- a/src/sage/combinat/specht_module.py +++ b/src/sage/combinat/specht_module.py @@ -5,6 +5,13 @@ AUTHORS: - Travis Scrimshaw (2023-1-22): initial version +- Travis Scrimshaw (2023-11-23): added simple modules based on code + from Sacha Goldman + +.. TODO:: + + Integrate this with the implementations in + :mod:`sage.modules.with_basis.representation`. """ # **************************************************************************** @@ -18,15 +25,243 @@ # **************************************************************************** from sage.misc.cachefunc import cached_method +from sage.misc.lazy_attribute import lazy_attribute from sage.combinat.diagram import Diagram +from sage.combinat.partition import _Partitions +from sage.combinat.free_module import CombinatorialFreeModule +from sage.modules.with_basis.representation import Representation_abstract from sage.sets.family import Family from sage.matrix.constructor import matrix from sage.rings.rational_field import QQ -from sage.modules.with_basis.subquotient import SubmoduleWithBasis +from sage.modules.with_basis.subquotient import SubmoduleWithBasis, QuotientModuleWithBasis +from sage.modules.free_module_element import vector from sage.categories.modules_with_basis import ModulesWithBasis +class SymmetricGroupRepresentation: + """ + Mixin class for symmetric group (algebra) representations. + """ + def __init__(self, SGA): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: SM = Partition([3,1,1]).specht_module(GF(3)) + sage: TestSuite(SM).run() + """ + self._semigroup = SGA.group() + self._semigroup_algebra = SGA + + def side(self): + r""" + Return the side of the action defining ``self``. + + EXAMPLES:: + + sage: SM = Partition([3,1,1]).specht_module(GF(3)) + sage: SM.side() + 'left' + """ + return "left" + + @cached_method + def frobenius_image(self): + r""" + Return the Frobenius image of ``self``. + + The Frobenius map is defined as the map to symmetric functions + + .. MATH:: + + F(\chi) = \frac{1}{n!} \sum_{w \in S_n} \chi(w) p_{\rho(w)}, + + where `\chi` is the character of the `S_n`-module ``self``, + `p_{\lambda}` is the powersum symmetric function basis element + indexed by `\lambda`, and `\rho(w)` is the cycle type of `w` as a + partition. Specifically, this map takes irreducible representations + indexed by `\lambda` to the Schur function `s_{\lambda}`. + + EXAMPLES:: + + sage: SM = Partition([2,2,1]).specht_module(QQ) + sage: SM.frobenius_image() + s[2, 2, 1] + sage: SM = Partition([4,1]).specht_module(CyclotomicField(5)) + sage: SM.frobenius_image() + s[4, 1] + + We verify the regular representation:: + + sage: from sage.combinat.diagram import Diagram + sage: D = Diagram([(0,0), (1,1), (2,2), (3,3), (4,4)]) + sage: F = D.specht_module(QQ).frobenius_image(); F + s[1, 1, 1, 1, 1] + 4*s[2, 1, 1, 1] + 5*s[2, 2, 1] + + 6*s[3, 1, 1] + 5*s[3, 2] + 4*s[4, 1] + s[5] + sage: s = SymmetricFunctions(QQ).s() + sage: F == sum(StandardTableaux(la).cardinality() * s[la] + ....: for la in Partitions(5)) + True + sage: all(s[la] == la.specht_module(QQ).frobenius_image() + ....: for n in range(1, 5) for la in Partitions(n)) + True + + sage: D = Diagram([(0,0), (1,1), (1,2), (2,3), (2,4)]) + sage: SM = D.specht_module(QQ) + sage: SM.frobenius_image() + s[2, 2, 1] + s[3, 1, 1] + 2*s[3, 2] + 2*s[4, 1] + s[5] + + An example using the tabloid module:: + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: TM = SGA.tabloid_module([2, 2, 1]) + sage: TM.frobenius_image() + s[2, 2, 1] + s[3, 1, 1] + 2*s[3, 2] + 2*s[4, 1] + s[5] + """ + from sage.combinat.sf.sf import SymmetricFunctions + p = SymmetricFunctions(QQ).p() + s = SymmetricFunctions(QQ).s() + G = self._semigroup + CCR = [(elt, elt.cycle_type()) for elt in G.conjugacy_classes_representatives()] + return s(p.sum(QQ(self.representation_matrix(elt).trace()) / la.centralizer_size() * p[la] + for elt, la in CCR)) + + # TODO: Move these methods up to methods of general representations + + def representation_matrix(self, elt): + r""" + Return the matrix corresponding to the left action of the symmetric + group (algebra) element ``elt`` on ``self``. + + EXAMPLES:: + + sage: SM = Partition([3,1,1]).specht_module(QQ) + sage: SM.representation_matrix(Permutation([2,1,3,5,4])) + [-1 0 0 0 0 0] + [ 0 0 0 -1 0 0] + [ 1 0 0 -1 1 0] + [ 0 -1 0 0 0 0] + [ 1 -1 1 0 0 0] + [ 0 -1 0 1 0 -1] + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: SM = SGA.specht_module([(0,0), (0,1), (0,2), (1,0), (2,0)]) + sage: SM.representation_matrix(Permutation([2,1,3,5,4])) + [-1 0 0 1 -1 0] + [ 0 0 1 0 -1 1] + [ 0 1 0 -1 0 1] + [ 0 0 0 0 -1 0] + [ 0 0 0 -1 0 0] + [ 0 0 0 0 0 -1] + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: SM.representation_matrix(SGA([3,1,5,2,4])) + [ 0 -1 0 1 0 -1] + [ 0 0 0 0 0 -1] + [ 0 0 0 -1 0 0] + [ 0 0 -1 0 1 -1] + [ 1 0 0 -1 1 0] + [ 0 0 0 0 1 0] + """ + return matrix(self.base_ring(), [(elt * b).to_vector() for b in self.basis()]) + + @cached_method + def character(self): + r""" + Return the character of ``self``. + + EXAMPLES:: -class SpechtModule(SubmoduleWithBasis): + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: SM = SGA.specht_module([3,2]) + sage: SM.character() + (5, 1, 1, -1, 1, -1, 0) + sage: matrix(SGA.specht_module(la).character() for la in Partitions(5)) + [ 1 1 1 1 1 1 1] + [ 4 2 0 1 -1 0 -1] + [ 5 1 1 -1 1 -1 0] + [ 6 0 -2 0 0 0 1] + [ 5 -1 1 -1 -1 1 0] + [ 4 -2 0 1 1 0 -1] + [ 1 -1 1 1 -1 -1 1] + + sage: SGA = SymmetricGroupAlgebra(QQ, SymmetricGroup(5)) + sage: SM = SGA.specht_module([3,2]) + sage: SM.character() + Character of Symmetric group of order 5! as a permutation group + sage: SM.character().values() + [5, 1, 1, -1, 1, -1, 0] + sage: matrix(SGA.specht_module(la).character().values() for la in reversed(Partitions(5))) + [ 1 -1 1 1 -1 -1 1] + [ 4 -2 0 1 1 0 -1] + [ 5 -1 1 -1 -1 1 0] + [ 6 0 -2 0 0 0 1] + [ 5 1 1 -1 1 -1 0] + [ 4 2 0 1 -1 0 -1] + [ 1 1 1 1 1 1 1] + sage: SGA.group().character_table() + [ 1 -1 1 1 -1 -1 1] + [ 4 -2 0 1 1 0 -1] + [ 5 -1 1 -1 -1 1 0] + [ 6 0 -2 0 0 0 1] + [ 5 1 1 -1 1 -1 0] + [ 4 2 0 1 -1 0 -1] + [ 1 1 1 1 1 1 1] + """ + G = self._semigroup + chi = [self.representation_matrix(g).trace() + for g in G.conjugacy_classes_representatives()] + try: + return G.character(chi) + except AttributeError: + return vector(chi, immutable=True) + + @cached_method + def brauer_character(self): + r""" + Return the Brauer character of ``self``. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(GF(2), 5) + sage: SM = SGA.specht_module([3,2]) + sage: SM.brauer_character() + (5, -1, 0) + sage: SM.simple_module().brauer_character() + (4, -2, -1) + """ + from sage.rings.number_field.number_field import CyclotomicField + from sage.arith.functions import lcm + G = self._semigroup + p = self.base_ring().characteristic() + # We manually compute the order since a Permutation does not implement order() + chi = [] + for g in G.conjugacy_classes_representatives(): + if p.divides(lcm(g.cycle_type())): + # ignore the non-p-regular elements + continue + evals = self.representation_matrix(g).eigenvalues() + K = evals[0].parent() + val = 0 + orders = {la: la.multiplicative_order() for la in evals if la != K.one()} + zetas = {o: CyclotomicField(o).gen() for o in orders.values()} + prims = {o: K.zeta(o) for o in orders.values()} + for la in evals: + if la == K.one(): + val += 1 + continue + o = la.multiplicative_order() + zeta = zetas[o] + prim = prims[o] + for deg in range(o): + if prim ** deg == la: + val += zeta ** deg + break + chi.append(val) + + return vector(chi, immutable=True) + + +class SpechtModule(SubmoduleWithBasis, SymmetricGroupRepresentation, Representation_abstract): r""" A Specht module. @@ -84,13 +319,13 @@ class SpechtModule(SubmoduleWithBasis): sage: S5 = SGA.group() sage: v = SM.an_element(); v - 2*B[0] + 2*B[1] + 3*B[2] + 2*S[0] + 2*S[1] + 3*S[2] sage: S5([2,1,5,3,4]) * v - 3*B[0] + 2*B[1] + 2*B[2] + 3*S[0] + 2*S[1] + 2*S[2] sage: x = SGA.an_element(); x [1, 2, 3, 4, 5] + 2*[1, 2, 3, 5, 4] + 3*[1, 2, 4, 3, 5] + [5, 1, 2, 3, 4] sage: x * v - 15*B[0] + 14*B[1] + 16*B[2] - 7*B[5] + 2*B[6] + 2*B[7] + 15*S[0] + 14*S[1] + 16*S[2] - 7*S[5] + 2*S[6] + 2*S[7] .. SEEALSO:: @@ -120,6 +355,9 @@ def __classcall_private__(cls, SGA, D): ... ValueError: the domain size (=3) does not match the number of boxes (=2) of the diagram """ + if D in _Partitions: + D = _Partitions(D) + return TabloidModule(SGA, D).specht_module() D = _to_diagram(D) D = Diagram(D) n = len(D) @@ -138,6 +376,7 @@ def __init__(self, SGA, D): sage: SM = SGA.specht_module([(0,0), (1,1), (1,2), (2,1)]) sage: TestSuite(SM).run() """ + SymmetricGroupRepresentation.__init__(self, SGA) self._diagram = D Mod = ModulesWithBasis(SGA.category().base_ring()) span_set = specht_module_spanning_set(D, SGA) @@ -145,7 +384,8 @@ def __init__(self, SGA, D): basis = SGA.echelon_form(span_set, False, order=support_order) basis = Family(basis) SubmoduleWithBasis.__init__(self, basis, support_order, ambient=SGA, - unitriangular=False, category=Mod.Subobjects()) + unitriangular=False, category=Mod.Subobjects(), + prefix='S') def _repr_(self): r""" @@ -156,6 +396,9 @@ def _repr_(self): sage: SGA = SymmetricGroupAlgebra(QQ, 4) sage: SGA.specht_module([(0,0), (1,1), (1,2), (2,1)]) Specht module of [(0, 0), (1, 1), (1, 2), (2, 1)] over Rational Field + + sage: SGA.specht_module([3, 1]) + Specht module of [3, 1] over Rational Field """ return f"Specht module of {self._diagram} over {self.base_ring()}" @@ -168,13 +411,22 @@ def _latex_(self): sage: SGA = SymmetricGroupAlgebra(QQ, 4) sage: SM = SGA.specht_module([(0,0), (1,1), (1,2), (2,1)]) sage: latex(SM) - S^{{\def\lr#1{\multicolumn{1}{|@{\hspace{.6ex}}c@{\hspace{.6ex}}|}{\raisebox{-.3ex}{$#1$}}} - \raisebox{-.6ex}{$\begin{array}[b]{*{3}{p{0.6ex}}}\cline{1-1} - \lr{\phantom{x}}&&\\\cline{1-1}\cline{2-2}\cline{3-3} - &\lr{\phantom{x}}&\lr{\phantom{x}}\\\cline{2-2}\cline{3-3}\cline{2-2} - &\lr{\phantom{x}}&\\\cline{2-2} - \end{array}$} - }} + S^{{\def\lr#1{\multicolumn{1}{|@{\hspace{.6ex}}c@{\hspace{.6ex}}|}{\raisebox{-.3ex}{$#1$}}} + \raisebox{-.6ex}{$\begin{array}[b]{*{3}{p{0.6ex}}}\cline{1-1} + \lr{\phantom{x}}&&\\\cline{1-1}\cline{2-2}\cline{3-3} + &\lr{\phantom{x}}&\lr{\phantom{x}}\\\cline{2-2}\cline{3-3}\cline{2-2} + &\lr{\phantom{x}}&\\\cline{2-2} + \end{array}$} + }} + + sage: SM = SGA.specht_module([3,1]) + sage: latex(SM) + S^{{\def\lr#1{\multicolumn{1}{|@{\hspace{.6ex}}c@{\hspace{.6ex}}|}{\raisebox{-.3ex}{$#1$}}} + \raisebox{-.6ex}{$\begin{array}[b]{*{3}c}\cline{1-3} + \lr{\phantom{x}}&\lr{\phantom{x}}&\lr{\phantom{x}}\\\cline{1-3} + \lr{\phantom{x}}\\\cline{1-1} + \end{array}$} + }} """ from sage.misc.latex import latex return f"S^{{{latex(self._diagram)}}}" @@ -192,6 +444,12 @@ def _ascii_art_(self): . O O . O . S + + sage: SM = SGA.specht_module([3,1]) + sage: ascii_art(SM) + *** + * + S """ from sage.typeset.ascii_art import ascii_art return ascii_art("S", baseline=0) + ascii_art(self._diagram, baseline=-1) @@ -213,99 +471,299 @@ def _unicode_art_(self): │ │X│ │ └─┴─┴─┘ S + + sage: SM = SGA.specht_module([3,1]) + sage: unicode_art(SM) + ┌┬┬┐ + ├┼┴┘ + └┘ + S """ from sage.typeset.unicode_art import unicode_art return unicode_art("S", baseline=0) + unicode_art(self._diagram, baseline=-1) - def representation_matrix(self, elt): + class Element(SubmoduleWithBasis.Element): + def _acted_upon_(self, x, self_on_left=False): + """ + Return the action of ``x`` on ``self``. + + INPUT: + + - ``x`` -- an element of the base ring or can be converted into + the defining symmetric group algebra + - ``self_on_left`` -- boolean (default: ``False``); which side + ``self`` is on for the action + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: from sage.combinat.diagram import Diagram + sage: D = Diagram([(0,0), (1,1), (2,2), (3,3), (4,4)]) + sage: SM = SGA.specht_module(D) + sage: SGA.an_element() * SM.an_element() + 15*S[0] + 6*S[1] + 9*S[2] + 6*S[3] + 6*S[4] + 2*S[72] + 2*S[96] + 3*S[97] + + sage: SGA = SymmetricGroupAlgebra(QQ, 4) + sage: SM = SGA.specht_module([3,1]) + sage: SGA.an_element() * SM.an_element() + 9*S[[1, 2, 3], [4]] + 17*S[[1, 2, 4], [3]] + 14*S[[1, 3, 4], [2]] + sage: 4 * SM.an_element() + 12*S[[1, 2, 3], [4]] + 8*S[[1, 2, 4], [3]] + 8*S[[1, 3, 4], [2]] + + TESTS:: + + sage: SGA = SymmetricGroupAlgebra(QQ, 4) + sage: SM = SGA.specht_module([3,1]) + sage: SM.an_element() * SGA.an_element() + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for *: + 'Specht module of [3, 1] over Rational Field' + and 'Symmetric group algebra of order 4 over Rational Field' + sage: groups.permutation.Dihedral(3).an_element() * SM.an_element() + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for *: + 'Dihedral group of order 6 as a permutation group' + and 'Specht module of [3, 1] over Rational Field' + """ + # Check for a scalar first + ret = super()._acted_upon_(x, self_on_left) + if ret is not None: + return ret + # Check if it is in the symmetric group algebra + P = self.parent() + if x in P._semigroup_algebra or x in P._semigroup_algebra.group(): + if self_on_left: # it is only a left module + return None + else: + return P.retract(P._semigroup_algebra(x) * self.lift()) + return None + + +class TabloidModule(SymmetricGroupRepresentation, Representation_abstract): + r""" + The vector space of all tabloids of a fixed shape with the natural + symmetric group action. + + A *tabloid* is an :class:`OrderedSetPartition` whose underlying set + is `\{1, \ldots, n\}`. The symmetric group acts by permuting the + entries of the set. Hence, this is a representation of the symmetric + group defined over any field. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(GF(3), 5) + sage: TM = SGA.tabloid_module([2, 2, 1]) + sage: TM.dimension() + 30 + sage: TM.brauer_character() + (30, 6, 2, 0, 0) + sage: IM = TM.invariant_module() + sage: IM.dimension() + 1 + sage: IM.basis()[0].lift() == sum(TM.basis()) + True + """ + @staticmethod + def __classcall_private__(cls, SGA, shape): r""" - Return the matrix corresponding to the left action of the symmetric - group (algebra) element ``elt`` on ``self``. + Normalize input to ensure a unique representation. - .. SEEALSO:: + EXAMPLES:: + + sage: from sage.combinat.specht_module import TabloidModule + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: TM1 = TabloidModule(SGA, [2, 2, 1]) + sage: TM2 = TabloidModule(SGA, Partition([2, 2, 1])) + sage: TM1 is TM2 + True - :class:`~sage.combinat.symmetric_group_representations.SpechtRepresentation` + sage: TabloidModule(SGA, [3, 2, 1]) + Traceback (most recent call last): + ... + ValueError: the domain size (=5) does not match the number of boxes (=6) of the diagram + """ + shape = _Partitions(shape) + if SGA.group().rank() != sum(shape) - 1: + rk = SGA.group().rank() + 1 + n = sum(shape) + raise ValueError(f"the domain size (={rk}) does not match the number of boxes (={n}) of the diagram") + return super().__classcall__(cls, SGA, shape) + + def __init__(self, SGA, shape): + r""" + Initialize ``self``. EXAMPLES:: - sage: SM = Partition([3,1,1]).specht_module(QQ) - sage: SM.representation_matrix(Permutation([2,1,3,5,4])) - [-1 0 0 1 -1 0] - [ 0 0 1 0 -1 1] - [ 0 1 0 -1 0 1] - [ 0 0 0 0 -1 0] - [ 0 0 0 -1 0 0] - [ 0 0 0 0 0 -1] sage: SGA = SymmetricGroupAlgebra(QQ, 5) - sage: SM.representation_matrix(SGA([3,1,5,2,4])) - [ 0 -1 0 1 0 -1] - [ 0 0 0 0 0 -1] - [ 0 0 0 -1 0 0] - [ 0 0 -1 0 1 -1] - [ 1 0 0 -1 1 0] - [ 0 0 0 0 1 0] + sage: TM = SGA.tabloid_module([2,2,1]) + sage: TestSuite(TM).run() """ - SGA = self._ambient - return matrix(self.base_ring(), [self.retract(SGA(elt) * b.lift()).to_vector() - for b in self.basis()]) + from sage.combinat.set_partition_ordered import OrderedSetPartitions + from sage.groups.perm_gps.permgroup_named import SymmetricGroup + self._shape = shape + n = sum(shape) + self._symgp = SymmetricGroup(n) + cat = ModulesWithBasis(SGA.base_ring()).FiniteDimensional() + tabloids = OrderedSetPartitions(n, shape) + CombinatorialFreeModule.__init__(self, SGA.base_ring(), tabloids, + category=cat, prefix='T', bracket='') + SymmetricGroupRepresentation.__init__(self, SGA) - @cached_method - def frobenius_image(self): + def _repr_(self): r""" - Return the Frobenius image of ``self``. + Return a string representation of ``self``. - The Frobenius map is defined as the map to symmetric functions + EXAMPLES:: - .. MATH:: + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: SGA.tabloid_module([2,2,1]) + Tabloid module of [2, 2, 1] over Rational Field + """ + return f"Tabloid module of {self._shape} over {self.base_ring()}" - F(\chi) = \frac{1}{n!} \sum_{w \in S_n} \chi(w) p_{\rho(w)}, + def _ascii_art_term(self, T): + r""" + Return an ascii art representation of the term indexed by ``T``. - where `\chi` is the character of the `S_n`-module ``self``, - `p_{\lambda}` is the powersum symmetric function basis element - indexed by `\lambda`, and `\rho(w)` is partition of the cycle type - of `w`. Specifically, this map takes irreducible representations - indexed by `\lambda` to the Schur function `s_{\lambda}`. + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: TM = SGA.tabloid_module([2,2,1]) + sage: ascii_art(TM.an_element()) # indirect doctest + 2*T + 2*T + 3*T + {1, 2} {1, 2} {1, 2} + {3, 4} {3, 5} {4, 5} + {5} {4} {3} + """ + # This is basically copied from CombinatorialFreeModule._ascii_art_term + from sage.typeset.ascii_art import AsciiArt, ascii_art + pref = AsciiArt([self.prefix()]) + tab = "\n".join("{" + ", ".join(str(val) for val in sorted(row)) + "}" for row in T) + if not tab: + tab = '-' + r = pref * (AsciiArt([" " * len(pref)]) + ascii_art(tab)) + r._baseline = r._h - 1 + return r + + def _unicode_art_term(self, T): + r""" + Return a unicode art representation of the term indexed by ``T``. EXAMPLES:: - sage: s = SymmetricFunctions(QQ).s() - sage: SM = Partition([2,2,1]).specht_module(QQ) - sage: s(SM.frobenius_image()) - s[2, 2, 1] - sage: SM = Partition([4,1]).specht_module(CyclotomicField(5)) - sage: s(SM.frobenius_image()) - s[4, 1] + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: TM = SGA.tabloid_module([2,2,1]) + sage: unicode_art(TM.an_element()) # indirect doctest + 2*T + 2*T + 3*T + {1, 2} {1, 2} {1, 2} + {3, 4} {3, 5} {4, 5} + {5} {4} {3} + """ + from sage.typeset.unicode_art import unicode_art + r = unicode_art(repr(self._ascii_art_term(T))) + r._baseline = r._h - 1 + return r - We verify the regular representation:: + def _latex_term(self, T): + r""" + Return a latex representation of the term indexed by ``T``. - sage: from sage.combinat.diagram import Diagram - sage: D = Diagram([(0,0), (1,1), (2,2), (3,3), (4,4)]) - sage: F = s(D.specht_module(QQ).frobenius_image()); F - s[1, 1, 1, 1, 1] + 4*s[2, 1, 1, 1] + 5*s[2, 2, 1] - + 6*s[3, 1, 1] + 5*s[3, 2] + 4*s[4, 1] + s[5] - sage: F == sum(StandardTableaux(la).cardinality() * s[la] - ....: for la in Partitions(5)) - True - sage: all(s[la] == s(la.specht_module(QQ).frobenius_image()) - ....: for n in range(1, 5) for la in Partitions(n)) - True + EXAMPLES:: - sage: D = Diagram([(0,0), (1,1), (1,2), (2,3), (2,4)]) - sage: SM = D.specht_module(QQ) - sage: s(SM.frobenius_image()) - s[2, 2, 1] + s[3, 1, 1] + 2*s[3, 2] + 2*s[4, 1] + s[5] + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: TM = SGA.tabloid_module([2,2,1]) + sage: latex(TM.an_element()) # indirect doctest + 2 T_{{\def\lr#1{\multicolumn{1}{@{\hspace{.6ex}}c@{\hspace{.6ex}}}{\raisebox{-.3ex}{$#1$}}} + \raisebox{-.6ex}{$\begin{array}[b]{*{2}c}\cline{1-2} + \lr{1}&\lr{2}\\\cline{1-2} + \lr{3}&\lr{4}\\\cline{1-2} + \lr{5}\\\cline{1-1} + \end{array}$} + }} + 2 T_{{\def\lr#1{\multicolumn{1}{@{\hspace{.6ex}}c@{\hspace{.6ex}}}{\raisebox{-.3ex}{$#1$}}} + \raisebox{-.6ex}{$\begin{array}[b]{*{2}c}\cline{1-2} + \lr{1}&\lr{2}\\\cline{1-2} + \lr{3}&\lr{5}\\\cline{1-2} + \lr{4}\\\cline{1-1} + \end{array}$} + }} + 3 T_{{\def\lr#1{\multicolumn{1}{@{\hspace{.6ex}}c@{\hspace{.6ex}}}{\raisebox{-.3ex}{$#1$}}} + \raisebox{-.6ex}{$\begin{array}[b]{*{2}c}\cline{1-2} + \lr{1}&\lr{2}\\\cline{1-2} + \lr{4}&\lr{5}\\\cline{1-2} + \lr{3}\\\cline{1-1} + \end{array}$} + }} """ - from sage.combinat.sf.sf import SymmetricFunctions - BR = self._ambient.base_ring() - p = SymmetricFunctions(BR).p() - G = self._ambient.group() - CCR = [(elt, elt.cycle_type()) for elt in G.conjugacy_classes_representatives()] - return p.sum(self.representation_matrix(elt).trace() / la.centralizer_size() * p[la] - for elt, la in CCR) + if not T: + tab = "\\emptyset" + else: + from sage.combinat.output import tex_from_array + A = list(map(sorted, T)) + tab = str(tex_from_array(A)) + tab = tab.replace("|", "") + return f"{self.prefix()}_{{{tab}}}" - class Element(SubmoduleWithBasis.Element): - def _acted_upon_(self, x, self_on_left=False): - """ + def _symmetric_group_action(self, osp, g): + r""" + Return the action of the symmetric group element ``g`` on the + ordered set partition ``osp``. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: TM = SGA.tabloid_module([2,2,1]) + sage: osp = TM._indices([[1,4],[3,5],[2]]) + sage: g = SGA.group().an_element(); g + [5, 1, 2, 3, 4] + sage: TM._symmetric_group_action(osp, g) + [{3, 5}, {2, 4}, {1}] + """ + P = self._indices + g = self._symgp(g) + return P.element_class(P, [[g(val) for val in row] for row in osp], check=False) + + def specht_module(self): + r""" + Return the Specht submodule of ``self``. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: TM = SGA.tabloid_module([2,2,1]) + sage: TM.specht_module() is SGA.specht_module([2,2,1]) + True + """ + return SpechtModuleTableauxBasis(self) + + def bilinear_form(self, u, v): + r""" + Return the natural bilinear form of ``self`` applied to ``u`` and ``v``. + + The natural bilinear form is given by defining the tabloid basis + to be orthonormal. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: TM = SGA.tabloid_module([2,2,1]) + sage: u = TM.an_element(); u + 2*T[{1, 2}, {3, 4}, {5}] + 2*T[{1, 2}, {3, 5}, {4}] + 3*T[{1, 2}, {4, 5}, {3}] + sage: v = sum(TM.basis()) + sage: TM.bilinear_form(u, v) + 7 + sage: TM.bilinear_form(u, TM.zero()) + 0 + """ + if len(v) < len(u): + u, v = v, u + R = self.base_ring() + return R.sum(c * v[T] for T, c in u) + + class Element(CombinatorialFreeModule.Element): + def _acted_upon_(self, x, self_on_left): + r""" Return the action of ``x`` on ``self``. INPUT: @@ -317,25 +775,399 @@ def _acted_upon_(self, x, self_on_left=False): EXAMPLES:: - sage: SGA = SymmetricGroupAlgebra(QQ, 4) - sage: SM = SGA.specht_module([3,1]) + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: SM = SGA.tabloid_module([2,2,1]) sage: SGA.an_element() * SM.an_element() - 14*B[0] + 18*B[1] + 8*B[2] + 2*T[{1, 5}, {2, 3}, {4}] + 2*T[{1, 5}, {2, 4}, {3}] + 3*T[{1, 5}, {3, 4}, {2}] + + 12*T[{1, 2}, {3, 4}, {5}] + 15*T[{1, 2}, {3, 5}, {4}] + 15*T[{1, 2}, {4, 5}, {3}] sage: 4 * SM.an_element() - 8*B[0] + 8*B[1] + 12*B[2] + 8*T[{1, 2}, {3, 4}, {5}] + 8*T[{1, 2}, {3, 5}, {4}] + 12*T[{1, 2}, {4, 5}, {3}] + sage: SM.an_element() * SGA.an_element() + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for *: + 'Tabloid module of [2, 2, 1] over Rational Field' + and 'Symmetric group algebra of order 5 over Rational Field' """ - # Check for a scalar first + # first check for the base action ret = super()._acted_upon_(x, self_on_left) if ret is not None: return ret - # Check if it is in the symmetric group algebra + + if self_on_left: + return None P = self.parent() - if x in P._ambient or x in P._ambient.group(): - if self_on_left: # it is only a left module - return None - else: - return P.retract(P._ambient(x) * self.lift()) - return None + if x in P._semigroup_algebra: + return P.sum(c * (perm * self) for perm, c in x.monomial_coefficients().items()) + if x in P._semigroup_algebra.indices(): + return P.element_class(P, {P._symmetric_group_action(T, x): c + for T, c in self._monomial_coefficients.items()}) + + +class SpechtModuleTableauxBasis(SpechtModule): + r""" + A Specht module of a partition in the classical standard + tableau basis. + + This is constructed as a `S_n`-submodule of the :class:`TabloidModule` + (also referred to as the standard module). + + .. SEEALSO:: + + - :class:`SpechtModule` for the generic diagram implementation + constructed as a left ideal of the group algebra + - :class:`~sage.combinat.symmetric_group_representations.SpechtRepresentation` + for an implementation of the representation by matrices. + """ + def __init__(self, ambient): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: SM = SGA.specht_module([2,2,1]) + sage: TestSuite(SM).run() + """ + self._diagram = ambient._shape + SymmetricGroupRepresentation.__init__(self, ambient._semigroup_algebra) + + ambient_basis = ambient.basis() + tabloids = ambient_basis.keys() + support_order = list(tabloids) + + def elt(T): + tab = tabloids.element_class(tabloids, list(T), check=False) + return ambient.sum_of_terms((ambient._symmetric_group_action(tab, sigma), sigma.sign()) + for sigma in T.column_stabilizer()) + + basis = Family({T: elt(T) + for T in self._diagram.standard_tableaux()}) + cat = ambient.category().Subobjects() + SubmoduleWithBasis.__init__(self, basis, support_order, ambient=ambient, + unitriangular=False, category=cat, + prefix='S', bracket='') + + @lazy_attribute + def lift(self): + r""" + The lift (embedding) map from ``self`` to the ambient space. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: SM = SGA.specht_module([3, 1, 1]) + sage: SM.lift + Generic morphism: + From: Specht module of [3, 1, 1] over Rational Field + To: Tabloid module of [3, 1, 1] over Rational Field + """ + return self.module_morphism(self.lift_on_basis, codomain=self.ambient()) + + @lazy_attribute + def retract(self): + r""" + The retract map from the ambient space. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: X = SGA.tabloid_module([2,2,1]) + sage: Y = X.specht_module() + sage: Y.retract + Generic morphism: + From: Tabloid module of [2, 2, 1] over Rational Field + To: Specht module of [2, 2, 1] over Rational Field + sage: all(Y.retract(u.lift()) == u for u in Y.basis()) + True + + sage: Y.retract(X.zero()) + 0 + sage: Y.retract(sum(X.basis())) + Traceback (most recent call last): + ... + ValueError: ... is not in the image + """ + B = self.basis() + COB = matrix([b.lift().to_vector() for b in B]).T + P, L, U = COB.LU() + # Since U is upper triangular, the nonzero entriesm must be in the + # upper square portiion of the matrix + n = len(B) + + Uinv = U.matrix_from_rows(range(n)).inverse() + # This is a slight abuse as the codomain should be a module with a different + # S_n action, but we only use it internally, so there isn't any problems + PLinv = (P*L).inverse() + + def retraction(elt): + vec = PLinv * elt.to_vector(order=self._support_order) + if not vec: + return self.zero() + # vec is now in the image of self under U, which is + if max(vec.support()) >= n: + raise ValueError(f"{elt} is not in the image") + return self._from_dict(dict(zip(B.keys(), Uinv * vec[:n]))) + + return self._ambient.module_morphism(function=retraction, codomain=self) + + def bilinear_form(self, u, v): + r""" + Return the natural bilinear form of ``self`` applied to ``u`` and ``v``. + + The natural bilinear form is given by the pullback of the natural + bilinear form on the tabloid module (where the tabloid basis is an + orthonormal basis). + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: SM = SGA.specht_module([2,2,1]) + sage: u = SM.an_element(); u + 3*S[[1, 2], [3, 5], [4]] + 2*S[[1, 3], [2, 5], [4]] + 2*S[[1, 4], [2, 5], [3]] + sage: v = sum(SM.basis()) + sage: SM.bilinear_form(u, v) + 140 + """ + TM = self._ambient + return TM.bilinear_form(u.lift(), v.lift()) + + @cached_method + def gram_matrix(self): + r""" + Return the Gram matrix of the natural bilinear form of ``self``. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: SM = SGA.specht_module([2,2,1]) + sage: M = SM.gram_matrix(); M + [12 4 -4 -4 4] + [ 4 12 4 4 4] + [-4 4 12 4 4] + [-4 4 4 12 4] + [ 4 4 4 4 12] + sage: M.det() != 0 + True + """ + B = self.basis() + M = matrix([[self.bilinear_form(b, bp) for bp in B] for b in B]) + M.set_immutable() + return M + + def maximal_submodule(self): + """ + Return the maximal submodule of ``self``. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(GF(3), 5) + sage: SM = SGA.specht_module([3,2]) + sage: U = SM.maximal_submodule() + sage: U.dimension() + 4 + """ + return MaximalSpechtSubmodule(self) + + def simple_module(self): + r""" + Return the simple (or irreducible) `S_n`-submodule of ``self``. + + .. SEEALSO:: + + :class:`~sage.combinat.specht_module.SimpleModule` + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(GF(3), 5) + sage: SM = SGA.specht_module([3,2]) + sage: L = SM.simple_module() + sage: L.dimension() + 1 + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: SM = SGA.specht_module([3,2]) + sage: SM.simple_module() is SM + True + """ + if self.base_ring().characteristic() == 0: + return self + return SimpleModule(self) + + +class MaximalSpechtSubmodule(SubmoduleWithBasis, SymmetricGroupRepresentation): + r""" + The maximal submodule `U^{\lambda}` of the Specht module `S^{\lambda}`. + + ALGORITHM: + + We construct `U^{\lambda}` as the intersection `S \cap S^{\perp}`, + where `S^{\perp}` is the orthogonal complement of the Specht module `S` + inside of the tabloid module `T` (with respect to the natural + bilinear form on `T`). + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(GF(3), 5) + sage: SM = SGA.specht_module([3,2]) + sage: U = SM.maximal_submodule() + sage: u = U.an_element(); u + 2*U[0] + 2*U[1] + sage: [p * u for p in list(SGA.basis())[:4]] + [2*U[0] + 2*U[1], 2*U[2] + 2*U[3], 2*U[0] + 2*U[1], U[0] + 2*U[2]] + sage: sum(SGA.basis()) * u + 0 + """ + def __init__(self, specht_module): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(GF(3), 5) + sage: SM = SGA.specht_module([3,2]) + sage: U = SM.maximal_submodule() + sage: TestSuite(U).run() + + sage: SM = SGA.specht_module([2,1,1,1]) + sage: SM.maximal_submodule() + Traceback (most recent call last): + ... + NotImplementedError: only implemented for 3-regular partitions + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: SM = SGA.specht_module([3,2]) + sage: U = SM.maximal_submodule() + sage: TestSuite(U).run() + sage: U.dimension() + 0 + """ + SymmetricGroupRepresentation.__init__(self, specht_module._semigroup_algebra) + + p = specht_module.base_ring().characteristic() + if p == 0: + basis = Family([]) + else: + TM = specht_module._ambient + if not TM._shape.is_regular(p): + raise NotImplementedError(f"only implemented for {p}-regular partitions") + TV = TM._dense_free_module() + SV = TV.submodule(specht_module.lift.matrix().columns()) + basis = (SV & SV.complement()).basis() + basis = [specht_module.retract(TM.from_vector(b)) for b in basis] + basis = Family(specht_module.echelon_form(basis)) + + unitriangular = all(b.leading_support() == 1 for b in basis) + support_order = list(specht_module.basis().keys()) + cat = specht_module.category().Subobjects() + SubmoduleWithBasis.__init__(self, basis, support_order, ambient=specht_module, + unitriangular=unitriangular, category=cat, + prefix='U') + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(GF(3), 5) + sage: SM = SGA.specht_module([3,2]) + sage: SM.maximal_submodule() + Maximal submodule of Specht module of [3, 2] over Finite Field of size 3 + """ + return f"Maximal submodule of {self._ambient}" + + Element = SpechtModule.Element + + +class SimpleModule(QuotientModuleWithBasis, SymmetricGroupRepresentation): + r""" + The simgle `S_n`-module associated with a partition `\lambda`. + + The simple module `D^{\lambda}` is the quotient of the Specht module + `S^{\lambda}` by its :class:`maximal submodule ` + `U^{\lambda}`. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(GF(3), 5) + sage: SM = SGA.specht_module([3,1,1]) + sage: D = SM.simple_module() + sage: v = D.an_element(); v + 2*D[[[1, 3, 5], [2], [4]]] + 2*D[[[1, 4, 5], [2], [3]]] + sage: SGA.an_element() * v + 2*D[[[1, 2, 4], [3], [5]]] + 2*D[[[1, 3, 5], [2], [4]]] + + We give an example on how to construct the decomposition matrix + (the Specht modules are a complete set of irreducible projective + modules) and the Cartan matrix of a symmetric group algebra:: + + sage: SGA = SymmetricGroupAlgebra(GF(3), 4) + sage: BM = matrix(SGA.simple_module(la).brauer_character() + ....: for la in Partitions(4, regular=3)) + sage: SBT = matrix(SGA.specht_module(la).brauer_character() + ....: for la in Partitions(4)) + sage: D = SBT * ~BM; D + [1 0 0 0] + [0 1 0 0] + [1 0 1 0] + [0 0 0 1] + [0 0 1 0] + sage: D.transpose() * D + [2 0 1 0] + [0 1 0 0] + [1 0 2 0] + [0 0 0 1] + + We verify this against the direct computation (up to reindexing the + rows and columns):: + + sage: SGA.cartan_invariants_matrix() # long time + [1 0 0 0] + [0 1 0 0] + [0 0 2 1] + [0 0 1 2] + """ + def __init__(self, specht_module): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(GF(3), 5) + sage: SM = SGA.specht_module([3,1,1]) + sage: D = SM.simple_module() + sage: TestSuite(D).run() + + sage: SGA = SymmetricGroupAlgebra(GF(3), 5) + sage: SM = SGA.specht_module([2,1,1,1]) + sage: SM.simple_module() + Traceback (most recent call last): + ... + ValueError: the partition must be 3-regular + """ + self._diagram = specht_module._diagram + p = specht_module.base_ring().characteristic() + if not self._diagram.is_regular(p): + raise ValueError(f"the partition must be {p}-regular") + SymmetricGroupRepresentation.__init__(self, specht_module._semigroup_algebra) + cat = specht_module.category() + QuotientModuleWithBasis.__init__(self, specht_module.maximal_submodule(), cat, prefix='D') + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(GF(3), 5) + sage: SM = SGA.specht_module([3,1,1]) + sage: SM.simple_module() + Simple module of [3, 1, 1] over Finite Field of size 3 + """ + return f"Simple module of {self._diagram} over {self.base_ring()}" + + Element = SpechtModule.Element def _to_diagram(D): @@ -358,7 +1190,6 @@ def _to_diagram(D): """ from sage.combinat.integer_vector import IntegerVectors from sage.combinat.skew_partition import SkewPartitions - from sage.combinat.partition import _Partitions if isinstance(D, Diagram): return D if D in _Partitions: @@ -527,7 +1358,8 @@ def bilinear_form(p1, p2): p1, p2 = p2, p1 return sum(c1 * p2.get(T1, 0) for T1, c1 in p1.items() if c1) - gram_matrix = [[bilinear_form(polytabloid(T1), polytabloid(T2)) for T1 in ST] for T2 in ST] + PT = {T: polytabloid(T) for T in ST} + gram_matrix = [[bilinear_form(PT[T1], PT[T2]) for T1 in ST] for T2 in ST] return matrix(base_ring, gram_matrix) diff --git a/src/sage/combinat/symmetric_group_algebra.py b/src/sage/combinat/symmetric_group_algebra.py index 4672a53570d..b0214063e3b 100644 --- a/src/sage/combinat/symmetric_group_algebra.py +++ b/src/sage/combinat/symmetric_group_algebra.py @@ -1572,20 +1572,40 @@ def specht_module(self, D): sage: SGA = SymmetricGroupAlgebra(QQ, 5) sage: SM = SGA.specht_module(Partition([3,1,1])) sage: SM - Specht module of [(0, 0), (0, 1), (0, 2), (1, 0), (2, 0)] over Rational Field - sage: s = SymmetricFunctions(QQ).s() - sage: s(SM.frobenius_image()) + Specht module of [3, 1, 1] over Rational Field + sage: SM.frobenius_image() s[3, 1, 1] sage: SM = SGA.specht_module([(1,1),(1,3),(2,2),(3,1),(3,2)]) sage: SM Specht module of [(1, 1), (1, 3), (2, 2), (3, 1), (3, 2)] over Rational Field - sage: s(SM.frobenius_image()) + sage: SM.frobenius_image() s[2, 2, 1] + s[3, 1, 1] + s[3, 2] """ from sage.combinat.specht_module import SpechtModule return SpechtModule(self, D) + def tabloid_module(self, D): + r""" + Return the module of tabloids with the natural action of ``self``. + + .. SEEALSO:: + + :class:`~sage.combinat.specht_module.TabloidModule` + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(QQ, 5) + sage: TM = SGA.tabloid_module(Partition([3,1,1])) + sage: TM + Tabloid module of [3, 1, 1] over Rational Field + sage: s = SymmetricFunctions(QQ).s() + sage: s(TM.frobenius_image()) + s[3, 1, 1] + s[3, 2] + 2*s[4, 1] + s[5] + """ + from sage.combinat.specht_module import TabloidModule + return TabloidModule(self, D) + def specht_module_dimension(self, D): r""" Return the dimension of the Specht module of ``self`` indexed by ``D``. @@ -1603,6 +1623,29 @@ def specht_module_dimension(self, D): span_set = specht_module_spanning_set(D, self) return matrix(self.base_ring(), [v.to_vector() for v in span_set]).rank() + def simple_module(self, la): + r""" + Return the simple module of ``self`` indexed by the partition ``la``. + + Over a field of characteristic `0`, this simply returns the Specht + module. + + .. SEEALSO:: + + :class:`sage.combinat.specht_module.SimpleModule` + + EXAMPLES:: + + sage: SGA = SymmetricGroupAlgebra(GF(3), 5) + sage: D = SGA.simple_module(Partition([3,1,1])) + sage: D + Simple module of [3, 1, 1] over Finite Field of size 3 + sage: D.brauer_character() + (6, 0, -2, 0, 1) + """ + from sage.combinat.specht_module import SpechtModule + return SpechtModule(self, la).simple_module() + def simple_module_dimension(self, la): r""" Return the dimension of the simple module of ``self`` indexed by the @@ -2591,10 +2634,10 @@ def e(tableau, star=0): one = QQ.one() P = Permutation - rd = dict((P(h), one) for h in rs) + rd = {P(h): one for h in rs} sym = QSn._from_dict(rd) - cd = dict((P(v), v.sign() * one) for v in cs) + cd = {P(v): QQ(v.sign()) for v in cs} antisym = QSn._from_dict(cd) res = QSn.right_action_product(antisym, sym) @@ -2604,7 +2647,7 @@ def e(tableau, star=0): # being [1] rather than [] (which seems to have its origins in # permutation group code). # TODO: Fix this. - if len(tableau) == 0: + if not tableau: res = QSn.one() e_cache[t] = res diff --git a/src/sage/crypto/lattice.py b/src/sage/crypto/lattice.py index ce6c63f66f3..513730ff89f 100644 --- a/src/sage/crypto/lattice.py +++ b/src/sage/crypto/lattice.py @@ -112,10 +112,10 @@ def gen_lattice(type='modular', n=4, m=8, q=11, seed=None, [ 0 11 0 0 0 0 0 0] [ 0 0 11 0 0 0 0 0] [ 0 0 0 11 0 0 0 0] - [-2 -3 -3 4 1 0 0 0] - [ 4 -2 -3 -3 0 1 0 0] - [-3 4 -2 -3 0 0 1 0] - [-3 -3 4 -2 0 0 0 1] + [-3 -3 -2 4 1 0 0 0] + [ 4 -3 -3 -2 0 1 0 0] + [-2 4 -3 -3 0 0 1 0] + [-3 -2 4 -3 0 0 0 1] Ideal bases also work with polynomials:: @@ -125,10 +125,10 @@ def gen_lattice(type='modular', n=4, m=8, q=11, seed=None, [ 0 11 0 0 0 0 0 0] [ 0 0 11 0 0 0 0 0] [ 0 0 0 11 0 0 0 0] - [ 1 4 -3 3 1 0 0 0] - [ 3 1 4 -3 0 1 0 0] - [-3 3 1 4 0 0 1 0] - [ 4 -3 3 1 0 0 0 1] + [-3 4 1 4 1 0 0 0] + [ 4 -3 4 1 0 1 0 0] + [ 1 4 -3 4 0 0 1 0] + [ 4 1 4 -3 0 0 0 1] Cyclotomic bases with n=2^k are SWIFFT bases:: @@ -137,10 +137,10 @@ def gen_lattice(type='modular', n=4, m=8, q=11, seed=None, [ 0 11 0 0 0 0 0 0] [ 0 0 11 0 0 0 0 0] [ 0 0 0 11 0 0 0 0] - [-2 -3 -3 4 1 0 0 0] - [-4 -2 -3 -3 0 1 0 0] - [ 3 -4 -2 -3 0 0 1 0] - [ 3 3 -4 -2 0 0 0 1] + [-3 -3 -2 4 1 0 0 0] + [-4 -3 -3 -2 0 1 0 0] + [ 2 -4 -3 -3 0 0 1 0] + [ 3 2 -4 -3 0 0 0 1] Dual modular bases are related to Regev's famous public-key encryption [Reg2005]_:: diff --git a/src/sage/crypto/lwe.py b/src/sage/crypto/lwe.py index 25bb2a3fb47..40281916b19 100644 --- a/src/sage/crypto/lwe.py +++ b/src/sage/crypto/lwe.py @@ -670,7 +670,7 @@ def __init__(self, ringlwe): sage: lwe = RingLWEConverter(RingLWE(16, 257, D, secret_dist='uniform')) sage: set_random_seed(1337) sage: lwe() - ((32, 216, 3, 125, 58, 197, 171, 43), ...) + ((171, 197, 58, 125, 3, 216, 32, 130), ...) """ self.ringlwe = ringlwe self._i = 0 @@ -686,7 +686,7 @@ def __call__(self): sage: lwe = RingLWEConverter(RingLWE(16, 257, D, secret_dist='uniform')) sage: set_random_seed(1337) sage: lwe() - ((32, 216, 3, 125, 58, 197, 171, 43), ...) + ((171, 197, 58, 125, 3, 216, 32, 130), ...) """ R_q = self.ringlwe.R_q diff --git a/src/sage/doctest/control.py b/src/sage/doctest/control.py index 79b2fbc90ca..138e5241024 100644 --- a/src/sage/doctest/control.py +++ b/src/sage/doctest/control.py @@ -416,7 +416,7 @@ def __init__(self, options, args): if options.gc: options.timeout *= 2 if options.nthreads == 0: - options.nthreads = int(os.getenv('SAGE_NUM_THREADS_PARALLEL',1)) + options.nthreads = int(os.getenv('SAGE_NUM_THREADS_PARALLEL', 1)) if options.failed and not (args or options.new): # If the user doesn't specify any files then we rerun all failed files. options.all = True @@ -450,15 +450,11 @@ def __init__(self, options, args): options.hide.discard('all') from sage.features.all import all_features feature_names = {f.name for f in all_features() if not f.is_standard()} - from sage.doctest.external import external_software - feature_names.difference_update(external_software) options.hide = options.hide.union(feature_names) if 'optional' in options.hide: options.hide.discard('optional') from sage.features.all import all_features feature_names = {f.name for f in all_features() if f.is_optional()} - from sage.doctest.external import external_software - feature_names.difference_update(external_software) options.hide = options.hide.union(feature_names) options.disabled_optional = set() @@ -1008,7 +1004,7 @@ def expand(): if os.path.isdir(path): for root, dirs, files in os.walk(path): for dir in list(dirs): - if dir[0] == "." or skipdir(os.path.join(root,dir)): + if dir[0] == "." or skipdir(os.path.join(root, dir)): dirs.remove(dir) for file in files: if not skipfile(os.path.join(root, file), @@ -1345,9 +1341,9 @@ def run_val_gdb(self, testing=False): flags = os.getenv("SAGE_MEMCHECK_FLAGS") if flags is None: flags = "--leak-resolution=high --leak-check=full --num-callers=25 " - flags += '''--suppressions="%s" ''' % (os.path.join(SAGE_EXTCODE,"valgrind", "pyalloc.supp")) - flags += '''--suppressions="%s" ''' % (os.path.join(SAGE_EXTCODE,"valgrind", "sage.supp")) - flags += '''--suppressions="%s" ''' % (os.path.join(SAGE_EXTCODE,"valgrind", "sage-additional.supp")) + flags += '''--suppressions="%s" ''' % (os.path.join(SAGE_EXTCODE, "valgrind", "pyalloc.supp")) + flags += '''--suppressions="%s" ''' % (os.path.join(SAGE_EXTCODE, "valgrind", "sage.supp")) + flags += '''--suppressions="%s" ''' % (os.path.join(SAGE_EXTCODE, "valgrind", "sage-additional.supp")) elif opt.massif: toolname = "massif" flags = os.getenv("SAGE_MASSIF_FLAGS", "--depth=6 ") @@ -1362,7 +1358,7 @@ def run_val_gdb(self, testing=False): if opt.omega: toolname = "omega" if "%s" in flags: - flags %= toolname + ".%p" # replace %s with toolname + flags %= toolname + ".%p" # replace %s with toolname cmd += flags + sage_cmd sys.stdout.flush() @@ -1489,6 +1485,25 @@ def run(self): cumulative wall time: ... seconds Features detected... 0 + + Test *Features that have been hidden* message:: + + sage: DC.run() # optional - meataxe + Running doctests with ID ... + Using --optional=sage + Features to be detected: ... + Doctesting 1 file. + sage -t ....py + [4 tests, ... s] + ---------------------------------------------------------------------- + All tests passed! + ---------------------------------------------------------------------- + Total time for all tests: ... seconds + cpu time: ... seconds + cumulative wall time: ... seconds + Features detected... + Features that have been hidden: ...meataxe... + 0 """ opt = self.options L = (opt.gdb, opt.lldb, opt.valgrind, opt.massif, opt.cachegrind, opt.omega) @@ -1516,10 +1531,10 @@ def run(self): pass try: ref = subprocess.check_output(["git", - "--git-dir=" + SAGE_ROOT_GIT, - "describe", - "--always", - "--dirty"]) + "--git-dir=" + SAGE_ROOT_GIT, + "describe", + "--always", + "--dirty"]) ref = ref.decode('utf-8') self.log("Git ref: " + ref, end="") except subprocess.CalledProcessError: @@ -1537,12 +1552,11 @@ def run(self): pass else: f = available_software._features[i] - if f.is_present(): - f.hide() - self.options.hidden_features.add(f) - for g in f.joined_features(): - if g.name in self.options.optional: - self.options.optional.discard(g.name) + f.hide() + self.options.hidden_features.add(f) + for g in f.joined_features(): + if g.name in self.options.optional: + self.options.optional.discard(g.name) for o in self.options.disabled_optional: try: @@ -1553,8 +1567,6 @@ def run(self): available_software._seen[i] = -1 self.log("Features to be detected: " + ','.join(available_software.detectable())) - if self.options.hidden_features: - self.log("Hidden features: " + ','.join([f.name for f in self.options.hidden_features])) if self.options.probe: self.log("Features to be probed: " + ('all' if self.options.probe is True else ','.join(self.options.probe))) @@ -1564,11 +1576,11 @@ def run(self): self.sort_sources() self.run_doctests() - for f in self.options.hidden_features: - f.unhide() - self.log("Features detected for doctesting: " + ','.join(available_software.seen())) + if self.options.hidden_features: + features_hidden = [f.name for f in self.options.hidden_features if f.unhide()] + self.log("Features that have been hidden: " + ','.join(features_hidden)) self.cleanup() return self.reporter.error_status diff --git a/src/sage/env.py b/src/sage/env.py index da9bad48da9..a6a99ca9303 100644 --- a/src/sage/env.py +++ b/src/sage/env.py @@ -220,6 +220,7 @@ def var(key: str, *fallbacks: Optional[str], force: bool = False) -> Optional[st MAXIMA_FAS = var("MAXIMA_FAS") KENZO_FAS = var("KENZO_FAS") SAGE_NAUTY_BINS_PREFIX = var("SAGE_NAUTY_BINS_PREFIX", "") +SAGE_ECMBIN = var("SAGE_ECMBIN") RUBIKS_BINS_PREFIX = var("RUBIKS_BINS_PREFIX", "") FOURTITWO_HILBERT = var("FOURTITWO_HILBERT") FOURTITWO_MARKOV = var("FOURTITWO_MARKOV") diff --git a/src/sage/features/__init__.py b/src/sage/features/__init__.py index ea8fd6bdb05..6af9413e55a 100644 --- a/src/sage/features/__init__.py +++ b/src/sage/features/__init__.py @@ -28,7 +28,7 @@ Here we test whether the grape GAP package is available:: sage: from sage.features.gap import GapPackage - sage: GapPackage("grape", spkg="gap_packages").is_present() # optional - gap_packages + sage: GapPackage("grape", spkg="gap_packages").is_present() # optional - gap_package_grape FeatureTestResult('gap_package_grape', True) Note that a :class:`FeatureTestResult` acts like a bool in most contexts:: @@ -158,6 +158,12 @@ def __init__(self, name, spkg=None, url=None, description=None, type='optional') self._hidden = False self._type = type + # For multiprocessing of doctests, the data self._num_hidings should be + # shared among subprocesses. Thus we use the Value class from the + # multiprocessing module (cf. self._seen of class AvailableSoftware) + from multiprocessing import Value + self._num_hidings = Value('i', 0) + try: from sage.misc.package import spkg_type except ImportError: # may have been surgically removed in a downstream distribution @@ -182,7 +188,7 @@ def is_present(self): EXAMPLES:: sage: from sage.features.gap import GapPackage - sage: GapPackage("grape", spkg="gap_packages").is_present() # optional - gap_packages + sage: GapPackage("grape", spkg="gap_packages").is_present() # optional - gap_package_grape FeatureTestResult('gap_package_grape', True) sage: GapPackage("NOT_A_PACKAGE", spkg="gap_packages").is_present() FeatureTestResult('gap_package_NOT_A_PACKAGE', False) @@ -205,8 +211,6 @@ def is_present(self): sage: TestFeature("other").is_present() FeatureTestResult('other', True) """ - if self._hidden: - return FeatureTestResult(self, False, reason="Feature `{name}` is hidden.".format(name=self.name)) # We do not use @cached_method here because we wish to use # Feature early in the build system of sagelib. if self._cache_is_present is None: @@ -214,6 +218,14 @@ def is_present(self): if not isinstance(res, FeatureTestResult): res = FeatureTestResult(self, res) self._cache_is_present = res + + if self._hidden: + if self._num_hidings.value > 0: + self._num_hidings.value += 1 + elif self._cache_is_present: + self._num_hidings.value = 1 + return FeatureTestResult(self, False, reason="Feature `{name}` is hidden.".format(name=self.name)) + return self._cache_is_present def _is_present(self): @@ -381,7 +393,8 @@ def hide(self): Feature `benzene` is hidden. Use method `unhide` to make it available again. - sage: Benzene().unhide() + sage: Benzene().unhide() # optional - benzene, needs sage.graphs + 1 sage: len(list(graphs.fusenes(2))) # optional - benzene, needs sage.graphs 1 """ @@ -389,32 +402,25 @@ def hide(self): def unhide(self): r""" - Revert what :meth:`hide` does. - - EXAMPLES: + Revert what :meth:`hide` did. - PolyCyclic is an optional GAP package. The following test - fails if it is hidden, regardless of whether it is installed - or not:: + OUTPUT: The number of events a present feature has been hidden. - sage: from sage.features.gap import GapPackage - sage: Polycyclic = GapPackage("polycyclic", spkg="gap_packages") - sage: Polycyclic.hide() - sage: libgap(AbelianGroup(3, [0,3,4], names="abc")) # needs sage.libs.gap # optional - gap_packages_polycyclic - Traceback (most recent call last): - ... - FeatureNotPresentError: gap_package_polycyclic is not available. - Feature `gap_package_polycyclic` is hidden. - Use method `unhide` to make it available again. - - After unhiding the feature, the test should pass again if PolyCyclic - is installed and loaded:: + EXAMPLES: - sage: Polycyclic.unhide() - sage: libgap(AbelianGroup(3, [0,3,4], names="abc")) # needs sage.libs.gap # optional - gap_packages_polycyclic - Pcp-group with orders [ 0, 3, 4 ] + sage: from sage.features.sagemath import sage__plot + sage: sage__plot().hide() + sage: sage__plot().is_present() + FeatureTestResult('sage.plot', False) + sage: sage__plot().unhide() # needs sage.plot + 1 + sage: sage__plot().is_present() # needs sage.plot + FeatureTestResult('sage.plot', True) """ + num_hidings = self._num_hidings.value + self._num_hidings.value = 0 self._hidden = False + return int(num_hidings) class FeatureNotPresentError(RuntimeError): @@ -802,7 +808,7 @@ class StaticFile(FileFeature): To install no_such_file...you can try to run...sage -i some_spkg... Further installation instructions might be available at http://rand.om. """ - def __init__(self, name, filename, *, search_path=None, **kwds): + def __init__(self, name, filename, *, search_path=None, type='optional', **kwds): r""" TESTS:: @@ -817,7 +823,7 @@ def __init__(self, name, filename, *, search_path=None, **kwds): '/bin/sh' """ - Feature.__init__(self, name, **kwds) + Feature.__init__(self, name, type=type, **kwds) self.filename = filename if search_path is None: self.search_path = [SAGE_SHARE] diff --git a/src/sage/features/databases.py b/src/sage/features/databases.py index cbb5de36ca0..844ed54de17 100644 --- a/src/sage/features/databases.py +++ b/src/sage/features/databases.py @@ -52,12 +52,12 @@ class DatabaseCremona(StaticFile): EXAMPLES:: sage: from sage.features.databases import DatabaseCremona - sage: DatabaseCremona('cremona_mini').is_present() + sage: DatabaseCremona('cremona_mini', type='standard').is_present() FeatureTestResult('database_cremona_mini_ellcurve', True) sage: DatabaseCremona().is_present() # optional - database_cremona_ellcurve FeatureTestResult('database_cremona_ellcurve', True) """ - def __init__(self, name="cremona"): + def __init__(self, name="cremona", spkg="database_cremona_ellcurve", type='optional'): r""" TESTS:: @@ -290,7 +290,7 @@ def __init__(self, name='polytopes_db'): def all_features(): return [PythonModule('conway_polynomials', spkg='conway_polynomials', type='standard'), DatabaseCremona(), - DatabaseCremona('cremona_mini'), + DatabaseCremona('cremona_mini', type='standard'), DatabaseEllcurves(), DatabaseGraphs(), DatabaseJones(), diff --git a/src/sage/features/ecm.py b/src/sage/features/ecm.py new file mode 100644 index 00000000000..79a1e77918f --- /dev/null +++ b/src/sage/features/ecm.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +r""" +Feature for testing the presence of ``ecm`` or ``gmp-ecm`` +""" +# **************************************************************************** +# Copyright (C) 2032 Dima Pasechnik +# +# 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 . import Executable +from sage.env import SAGE_ECMBIN + + +class Ecm(Executable): + r""" + A :class:`~sage.features.Feature` describing the presence of :ref:`GMP-ECM `. + + EXAMPLES:: + + sage: from sage.features.ecm import Ecm + sage: Ecm().is_present() + FeatureTestResult('ecm', True) + """ + def __init__(self): + r""" + TESTS:: + + sage: from sage.features.ecm import Ecm + sage: isinstance(Ecm(), Ecm) + True + """ + Executable.__init__(self, name="ecm", executable=SAGE_ECMBIN, + spkg="ecm", type="standard") + + +def all_features(): + return [Ecm()] diff --git a/src/sage/features/gap.py b/src/sage/features/gap.py index df5545e9c07..314ba1cc514 100644 --- a/src/sage/features/gap.py +++ b/src/sage/features/gap.py @@ -53,7 +53,7 @@ def _is_present(self): EXAMPLES:: sage: from sage.features.gap import GapPackage - sage: GapPackage("grape", spkg="gap_packages")._is_present() # optional - gap_packages + sage: GapPackage("grape", spkg="gap_packages")._is_present() # optional - gap_package_grape FeatureTestResult('gap_package_grape', True) """ try: diff --git a/src/sage/features/join_feature.py b/src/sage/features/join_feature.py index 6360eec1576..24c6583c123 100644 --- a/src/sage/features/join_feature.py +++ b/src/sage/features/join_feature.py @@ -152,7 +152,9 @@ def hide(self): def unhide(self): r""" - Revert what :meth:`hide` does. + Revert what :meth:`hide` did. + + OUTPUT: The number of events a present feature has been hidden. EXAMPLES:: @@ -165,11 +167,14 @@ def unhide(self): FeatureTestResult('sage.groups.perm_gps.permgroup', False) sage: f.unhide() + 4 sage: f.is_present() # optional sage.groups FeatureTestResult('sage.groups', True) sage: f._features[0].is_present() # optional sage.groups FeatureTestResult('sage.groups.perm_gps.permgroup', True) """ + num_hidings = 0 for f in self._features: - f.unhide() - super().unhide() + num_hidings += f.unhide() + num_hidings += super().unhide() + return num_hidings diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index f7eac6cd39c..729b001a202 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -10134,7 +10134,7 @@ def bipartite_double(self, extended=False): from sage.graphs.tutte_polynomial import tutte_polynomial from sage.graphs.lovasz_theta import lovasz_theta from sage.graphs.partial_cube import is_partial_cube - from sage.graphs.orientations import strong_orientations_iterator, random_orientation + from sage.graphs.orientations import strong_orientations_iterator, random_orientation, acyclic_orientations from sage.graphs.connectivity import bridges, cleave, spqr_tree from sage.graphs.connectivity import is_triconnected from sage.graphs.comparability import is_comparability @@ -10180,6 +10180,7 @@ def bipartite_double(self, extended=False): "lovasz_theta" : "Leftovers", "strong_orientations_iterator" : "Connectivity, orientations, trees", "random_orientation" : "Connectivity, orientations, trees", + "acyclic_orientations" : "Connectivity, orientations, trees", "bridges" : "Connectivity, orientations, trees", "cleave" : "Connectivity, orientations, trees", "spqr_tree" : "Connectivity, orientations, trees", diff --git a/src/sage/graphs/orientations.py b/src/sage/graphs/orientations.py index 7b7fa8681fc..1ecb283ba2a 100644 --- a/src/sage/graphs/orientations.py +++ b/src/sage/graphs/orientations.py @@ -41,6 +41,263 @@ from sage.graphs.digraph import DiGraph +def acyclic_orientations(G): + r""" + Return an iterator over all acyclic orientations of an undirected graph `G`. + + ALGORITHM: + + The algorithm is based on [Sq1998]_. + It presents an efficient algorithm for listing the acyclic orientations of a + graph. The algorithm is shown to require O(n) time per acyclic orientation + generated, making it the most efficient known algorithm for generating acyclic + orientations. + + The function uses a recursive approach to generate acyclic orientations of the + graph. It reorders the vertices and edges of the graph, creating a new graph + with updated labels. Then, it iteratively generates acyclic orientations by + considering subsets of edges and checking whether they form upsets in a + corresponding poset. + + INPUT: + + - ``G`` -- an undirected graph. + + OUTPUT: + + - An iterator over all acyclic orientations of the input graph. + + .. NOTE:: + + The function assumes that the input graph is undirected and the edges are unlabelled. + + EXAMPLES: + + To count number acyclic orientations for a graph:: + + sage: g = Graph([(0, 3), (0, 4), (3, 4), (1, 3), (1, 2), (2, 3), (2, 4)]) + sage: it = g.acyclic_orientations() + sage: len(list(it)) + 54 + + Test for arbitary vertex labels:: + + sage: g_str = Graph([('abc', 'def'), ('ghi', 'def'), ('xyz', 'abc'), ('xyz', 'uvw'), ('uvw', 'abc'), ('uvw', 'ghi')]) + sage: it = g_str.acyclic_orientations() + sage: len(list(it)) + 42 + + TESTS: + + To count the number of acyclic orientations for a graph with 0 vertices:: + + sage: list(Graph().acyclic_orientations()) + [] + + To count the number of acyclic orientations for a graph with 1 vertex:: + + sage: list(Graph(1).acyclic_orientations()) + [] + + To count the number of acyclic orientations for a graph with 2 vertices:: + + sage: list(Graph(2).acyclic_orientations()) + [] + + Acyclic orientations of a complete graph:: + + sage: g = graphs.CompleteGraph(5) + sage: it = g.acyclic_orientations() + sage: len(list(it)) + 120 + + Graph with one edge:: + + sage: list(Graph([(0, 1)]).acyclic_orientations()) + [Digraph on 2 vertices, Digraph on 2 vertices] + + Graph with two edges:: + + sage: len(list(Graph([(0, 1), (1, 2)]).acyclic_orientations())) + 4 + + Cycle graph:: + + sage: len(list(Graph([(0, 1), (1, 2), (2, 0)]).acyclic_orientations())) + 6 + + """ + if not G.size(): + # A graph without edge cannot be oriented + return + + from sage.rings.infinity import Infinity + from sage.combinat.subset import Subsets + + def reorder_vertices(G): + n = G.order() + ko = n + k = n + G_copy = G.copy() + vertex_labels = {v: None for v in G_copy.vertices()} + + while G_copy.size() > 0: + min_val = float('inf') + uv = None + for u, v, _ in G_copy.edges(): + du = G_copy.degree(u) + dv = G_copy.degree(v) + val = (du + dv) / (du * dv) + if val < min_val: + min_val = val + uv = (u, v) + + if uv: + u, v = uv + vertex_labels[u] = ko + vertex_labels[v] = ko - 1 + G_copy.delete_vertex(u) + G_copy.delete_vertex(v) + ko -= 2 + + if G_copy.size() == 0: + break + + for vertex, label in vertex_labels.items(): + if label is None: + vertex_labels[vertex] = ko + ko -= 1 + + return vertex_labels + + def order_edges(G, vertex_labels): + n = len(vertex_labels) + m = 1 + edge_labels = {} + + for j in range(2, n + 1): + for i in range(1, j): + if G.has_edge(i, j): + edge_labels[(i, j)] = m + m += 1 + + return edge_labels + + def is_upset_of_poset(Poset, subset, keys): + for (u, v) in subset: + for (w, x) in keys: + if (Poset[(u, v), (w, x)] == 1 and (w, x) not in subset): + return False + return True + + def generate_orientations(globO, starting_of_Ek, m, k, keys): + # Creating a poset + Poset = {} + for i in range(starting_of_Ek, m - 1): + for j in range(starting_of_Ek, m - 1): + u, v = keys[i] + w, x = keys[j] + Poset[(u, v), (w, x)] = 0 + + # Create a new graph to determine reachable vertices + new_G = DiGraph() + + # Process vertices up to starting_of_Ek + new_G.add_edges([(v, u) if globO[(u, v)] == 1 else (u, v) for u, v in keys[:starting_of_Ek]]) + + # Process vertices starting from starting_of_Ek + new_G.add_vertices([u for u, _ in keys[starting_of_Ek:]] + [v for _, v in keys[starting_of_Ek:]]) + + if (globO[(k-1, k)] == 1): + new_G.add_edge(k, k - 1) + else: + new_G.add_edge(k-1, k) + + for i in range(starting_of_Ek, m - 1): + for j in range(starting_of_Ek, m - 1): + u, v = keys[i] + w, x = keys[j] + # w should be reachable from u and v should be reachable from x + if w in new_G.depth_first_search(u) and v in new_G.depth_first_search(x): + Poset[(u, v), (w, x)] = 1 + + # For each subset of the base set of E_k, check if it is an upset or not + upsets = [] + for subset in Subsets(keys[starting_of_Ek:m-1]): + if (is_upset_of_poset(Poset, subset, keys[starting_of_Ek:m-1])): + upsets.append(list(subset)) + + for upset in upsets: + for i in range(starting_of_Ek, m - 1): + u, v = keys[i] + if (u, v) in upset: + globO[(u, v)] = 1 + else: + globO[(u, v)] = 0 + + yield globO.copy() + + def helper(G, globO, m, k): + keys = list(globO.keys()) + keys = keys[0:m] + + if m <= 0: + yield {} + return + + starting_of_Ek = 0 + for (u, v) in keys: + if u >= k - 1 or v >= k - 1: + break + else: + starting_of_Ek += 1 + + # s is the size of E_k + s = m - 1 - starting_of_Ek + + # Recursively generate acyclic orientations + orientations_G_small = helper(G, globO, starting_of_Ek, k - 2) + + # For each orientation of G_k-2, yield acyclic orientations + for alpha in orientations_G_small: + for (u, v) in alpha: + globO[(u, v)] = alpha[(u, v)] + + # Orienting H_k as 1 + globO[(k-1, k)] = 1 + yield from generate_orientations(globO, starting_of_Ek, m, k, keys) + + # Orienting H_k as 0 + globO[(k-1, k)] = 0 + yield from generate_orientations(globO, starting_of_Ek, m, k, keys) + + # Reorder vertices based on the logic in reorder_vertices function + vertex_labels = reorder_vertices(G) + + # Create a new graph with updated vertex labels using SageMath, Assuming the graph edges are unlabelled + new_G = G.relabel(perm=vertex_labels, inplace=False) + + G = new_G + + # Order the edges based on the logic in order_edges function + edge_labels = order_edges(G, vertex_labels) + + # Create globO array + globO = {uv: 0 for uv in edge_labels} + + m = len(edge_labels) + k = len(vertex_labels) + orientations = helper(G, globO, m, k) + + # Create a mapping between original and new vertex labels + reverse_vertex_labels = {label: vertex for vertex, label in vertex_labels.items()} + + # Iterate over acyclic orientations and create relabeled graphs + for orientation in orientations: + relabeled_graph = DiGraph([(reverse_vertex_labels[u], reverse_vertex_labels[v], label) for (u, v), label in orientation.items()]) + yield relabeled_graph + + def strong_orientations_iterator(G): r""" Return an iterator over all strong orientations of a graph `G`. diff --git a/src/sage/groups/generic.py b/src/sage/groups/generic.py index 206e8561e5a..84d3cd89436 100644 --- a/src/sage/groups/generic.py +++ b/src/sage/groups/generic.py @@ -119,7 +119,6 @@ from sage.arith.misc import integer_ceil, integer_floor, xlcm from sage.arith.srange import xsrange -from sage.misc.functional import log from sage.misc.misc_c import prod import sage.rings.integer_ring as integer_ring import sage.rings.integer @@ -171,9 +170,11 @@ def multiple(a, n, operation='*', identity=None, inverse=None, op=None): sage: E = EllipticCurve('389a1') sage: P = E(-1,1) sage: multiple(P, 10, '+') - (645656132358737542773209599489/22817025904944891235367494656 : 525532176124281192881231818644174845702936831/3446581505217248068297884384990762467229696 : 1) + (645656132358737542773209599489/22817025904944891235367494656 : + 525532176124281192881231818644174845702936831/3446581505217248068297884384990762467229696 : 1) sage: multiple(P, -10, '+') - (645656132358737542773209599489/22817025904944891235367494656 : -528978757629498440949529703029165608170166527/3446581505217248068297884384990762467229696 : 1) + (645656132358737542773209599489/22817025904944891235367494656 : + -528978757629498440949529703029165608170166527/3446581505217248068297884384990762467229696 : 1) """ from operator import inv, mul, neg, add @@ -182,7 +183,7 @@ def multiple(a, n, operation='*', identity=None, inverse=None, op=None): inverse = inv op = mul elif operation in addition_names: - identity = a.parent()(0) + identity = a.parent().zero() inverse = neg op = add else: @@ -329,18 +330,19 @@ def __init__(self, P, n, P0=None, indexed=False, operation='+', op=None): self.op = mul elif operation in addition_names: if P0 is None: - P0 = P.parent()(0) + P0 = P.parent().zero() self.op = add else: - self.op = op if P0 is None: raise ValueError("P0 must be supplied when operation is neither addition nor multiplication") if op is None: raise ValueError("op() must both be supplied when operation is neither addition nor multiplication") + self.op = op self.P = copy(P) self.Q = copy(P0) - assert self.P is not None and self.Q is not None + if self.P is None or self.Q is None: + raise ValueError("P and Q must not be None") self.i = 0 self.bound = n self.indexed = indexed @@ -442,7 +444,7 @@ def bsgs(a, b, bounds, operation='*', identity=None, inverse=None, op=None): This will return a multiple of the order of P:: - sage: bsgs(P, P.parent()(0), Hasse_bounds(F.order()), operation='+') # needs sage.rings.finite_rings sage.schemes + sage: bsgs(P, P.parent().zero(), Hasse_bounds(F.order()), operation='+') # needs sage.rings.finite_rings sage.schemes 69327408 AUTHOR: @@ -458,7 +460,8 @@ def bsgs(a, b, bounds, operation='*', identity=None, inverse=None, op=None): inverse = inv op = mul elif operation in addition_names: - identity = a.parent()(0) + # Should this be replaced with .zero()? With an extra AttributeError handler? + identity = a.parent().zero() inverse = neg op = add else: @@ -469,7 +472,7 @@ def bsgs(a, b, bounds, operation='*', identity=None, inverse=None, op=None): if lb < 0 or ub < lb: raise ValueError("bsgs() requires 0<=lb<=ub") - if a.is_zero() and not b.is_zero(): + if a == identity and b != identity: raise ValueError("no solution in bsgs()") ran = 1 + ub - lb # the length of the interval @@ -479,7 +482,7 @@ def bsgs(a, b, bounds, operation='*', identity=None, inverse=None, op=None): if ran < 30: # use simple search for small ranges d = c -# for i,d in multiples(a,ran,c,indexed=True,operation=operation): + # for i,d in multiples(a,ran,c,indexed=True,operation=operation): for i0 in range(ran): i = lb + i0 if identity == d: # identity == b^(-1)*a^i, so return i @@ -1008,7 +1011,7 @@ def discrete_log_lambda(a, base, bounds, operation='*', identity=None, inverse=N This will return a multiple of the order of P:: - sage: discrete_log_lambda(P.parent()(0), P, Hasse_bounds(F.order()), # needs sage.rings.finite_rings sage.schemes + sage: discrete_log_lambda(P.parent().zero(), P, Hasse_bounds(F.order()), # needs sage.rings.finite_rings sage.schemes ....: operation='+') 69327408 @@ -1187,11 +1190,13 @@ def linear_relation(P, Q, operation='+', identity=None, inverse=None, op=None): def order_from_multiple(P, m, plist=None, factorization=None, check=True, - operation='+'): + operation='+', identity=None, inverse=None, op=None): r""" Generic function to find order of a group element given a multiple of its order. + See :meth:`bsgs` for full explanation of the inputs. + INPUT: - ``P`` -- a Sage object which is a group element; @@ -1201,9 +1206,12 @@ def order_from_multiple(P, m, plist=None, factorization=None, check=True, really is a multiple of the order; - ``factorization`` -- the factorization of ``m``, or ``None`` in which case this function will need to factor ``m``; - - ``plist`` -- a list of the prime factors of ``m``, or ``None`` - kept for compatibility only, + - ``plist`` -- a list of the prime factors of ``m``, or ``None``. Kept for compatibility only, prefer the use of ``factorization``; - - ``operation`` -- string: ``'+'`` (default) or ``'*'``. + - ``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:: @@ -1255,16 +1263,25 @@ def order_from_multiple(P, m, plist=None, factorization=None, check=True, if operation in multiplication_names: identity = P.parent().one() elif operation in addition_names: - identity = P.parent()(0) + identity = P.parent().zero() else: - raise ValueError("unknown group operation") + if identity is None or inverse is None or op is None: + raise ValueError("identity, inverse and operation must all be specified") + + def _multiple(A, B): + return multiple(A, + B, + operation=operation, + identity=identity, + inverse=inverse, + op=op) if P == identity: return Z.one() M = Z(m) - if check: - assert multiple(P, M, operation=operation) == identity + if check and _multiple(P, M) != identity: + raise ValueError(f"The order of P(={P}) does not divide {M}") if factorization: F = factorization @@ -1293,7 +1310,7 @@ def _order_from_multiple_helper(Q, L, S): p, e = L[0] e0 = 0 while (Q != identity) and (e0 < e - 1): - Q = multiple(Q, p, operation=operation) + Q = _multiple(Q, p) e0 += 1 if Q != identity: e0 += 1 @@ -1312,12 +1329,8 @@ def _order_from_multiple_helper(Q, L, S): L2 = L[k:] # recursive calls o1 = _order_from_multiple_helper( - multiple(Q, prod([p**e for p, e in L2]), operation), - L1, - sum_left) - o2 = _order_from_multiple_helper(multiple(Q, o1, operation), - L2, - S - sum_left) + _multiple(Q, prod([p**e for p, e in L2])), L1, sum_left) + o2 = _order_from_multiple_helper(_multiple(Q, o1), L2, S - sum_left) return o1 * o2 return _order_from_multiple_helper(P, F, sage.functions.log.log(float(M))) @@ -1389,7 +1402,7 @@ def order_from_bounds(P, bounds, d=None, operation='+', identity = P.parent().one() elif operation in addition_names: op = add - identity = P.parent()(0) + identity = P.parent().zero() else: if op is None: raise ValueError("operation and identity must be specified") @@ -1410,6 +1423,7 @@ def order_from_bounds(P, bounds, d=None, operation='+', return order_from_multiple(P, m, operation=operation, check=False) + def has_order(P, n, operation='+'): r""" Generic function to test if a group element `P` has order @@ -1510,8 +1524,8 @@ def _rec(Q, fn): fl = fn[::2] fr = fn[1::2] - l = prod(p**k for p,k in fl) - r = prod(p**k for p,k in fr) + l = prod(p**k for p, k in fl) + r = prod(p**k for p, k in fr) L, R = mult(Q, r), mult(Q, l) return _rec(L, fl) and _rec(R, fr) @@ -1581,14 +1595,14 @@ def merge_points(P1, P2, operation='+', identity = g1.parent().one() elif operation in addition_names: op = add - identity = g1.parent()(0) + identity = g1.parent().zero() else: if op is None: raise ValueError("operation and identity must be specified") if check: - assert multiple(g1, n1, operation=operation) == identity - assert multiple(g2, n2, operation=operation) == identity + if multiple(g1, n1, operation=operation) != identity or multiple(g2, n2, operation=operation) != identity: + raise ValueError("the orders provided do not divide the orders of the points provided") # trivial cases if n1.divides(n2): diff --git a/src/sage/interfaces/ecm.py b/src/sage/interfaces/ecm.py index a2c882643c4..ae1379861f2 100644 --- a/src/sage/interfaces/ecm.py +++ b/src/sage/interfaces/ecm.py @@ -55,6 +55,7 @@ from sage.structure.sage_object import SageObject from sage.rings.integer_ring import ZZ +from sage.env import SAGE_ECMBIN class ECM(SageObject): @@ -182,7 +183,7 @@ def __init__(self, B1=10, B2=None, **kwds): self._cmd = self._make_cmd(B1, B2, kwds) def _make_cmd(self, B1, B2, kwds): - ecm = ['ecm'] + ecm = [SAGE_ECMBIN] options = [] for x, v in kwds.items(): if v is False: diff --git a/src/sage/misc/randstate.pyx b/src/sage/misc/randstate.pyx index b918b153883..756722d0b6a 100644 --- a/src/sage/misc/randstate.pyx +++ b/src/sage/misc/randstate.pyx @@ -56,22 +56,22 @@ results of these random number generators reproducible. :: sage: set_random_seed(0) sage: print(rtest()) - (303, -0.266166246380421, 1/6, (1,2), [ 0, 1, 1, 0, 0 ], 265625921, 79302, 0.2450652680687958) + (303, -0.266166246380421, 1/2*x^2 - 1/95*x - 1/2, (1,3), [ 1, 0, 0, 1, 1 ], 265625921, 5842, 0.9661911734708414) sage: set_random_seed(1) sage: print(rtest()) - (978, 0.0557699430711638, -1/8*x^2 - 1/2*x + 1/2, (1,2,3), [ 1, 0, 0, 0, 1 ], 807447831, 23865, 0.6170498912488264) + (978, 0.0557699430711638, -3*x^2 - 1/12, (1,2), [ 0, 0, 1, 1, 0 ], 807447831, 29982, 0.8335077654199736) sage: set_random_seed(2) sage: print(rtest()) - (207, -0.0141049486533456, 0, (1,3)(4,5), [ 1, 0, 1, 1, 1 ], 1642898426, 16190, 0.9343331114872127) + (207, -0.0141049486533456, 4*x^2 + 1/2, (1,2)(4,5), [ 1, 0, 0, 1, 1 ], 1642898426, 41662, 0.19982565117278328) sage: set_random_seed(0) sage: print(rtest()) - (303, -0.266166246380421, 1/6, (1,2), [ 0, 1, 1, 0, 0 ], 265625921, 79302, 0.2450652680687958) + (303, -0.266166246380421, 1/2*x^2 - 1/95*x - 1/2, (1,3), [ 1, 0, 0, 1, 1 ], 265625921, 5842, 0.9661911734708414) sage: set_random_seed(1) sage: print(rtest()) - (978, 0.0557699430711638, -1/8*x^2 - 1/2*x + 1/2, (1,2,3), [ 1, 0, 0, 0, 1 ], 807447831, 23865, 0.6170498912488264) + (978, 0.0557699430711638, -3*x^2 - 1/12, (1,2), [ 0, 0, 1, 1, 0 ], 807447831, 29982, 0.8335077654199736) sage: set_random_seed(2) sage: print(rtest()) - (207, -0.0141049486533456, 0, (1,3)(4,5), [ 1, 0, 1, 1, 1 ], 1642898426, 16190, 0.9343331114872127) + (207, -0.0141049486533456, 4*x^2 + 1/2, (1,2)(4,5), [ 1, 0, 0, 1, 1 ], 1642898426, 41662, 0.19982565117278328) Once we've set the random number seed, we can check what seed was used. (This is not the current random number state; it does not change when @@ -81,7 +81,7 @@ random numbers are generated.) :: sage: initial_seed() 12345 sage: print(rtest()) - (720, -0.612180244315804, 0, (1,3), [ 1, 0, 1, 1, 0 ], 1911581957, 65175, 0.8043027951758298) + (720, -0.612180244315804, x^2 - x, (1,2,3), [ 1, 0, 0, 0, 1 ], 1911581957, 27093, 0.9205331599518184) sage: initial_seed() 12345 @@ -216,9 +216,9 @@ that you get without intervening ``with seed``. :: sage: set_random_seed(0) sage: r1 = rtest(); print(r1) - (303, -0.266166246380421, 1/6, (1,2), [ 0, 1, 1, 0, 0 ], 265625921, 79302, 0.2450652680687958) + (303, -0.266166246380421, 1/2*x^2 - 1/95*x - 1/2, (1,3), [ 1, 0, 0, 1, 1 ], 265625921, 5842, 0.9661911734708414) sage: r2 = rtest(); print(r2) - (443, 0.185001351421963, -2, (1,3), [ 0, 0, 1, 1, 0 ], 53231108, 8171, 0.28363811590618193) + (105, 0.642309615982449, -x^2 - x - 6, (1,3)(4,5), [ 1, 0, 0, 0, 1 ], 53231108, 77132, 0.001767155077382232) We get slightly different results with an intervening ``with seed``. :: @@ -226,9 +226,9 @@ We get slightly different results with an intervening ``with seed``. :: sage: r1 == rtest() True sage: with seed(1): rtest() - (978, 0.0557699430711638, -1/8*x^2 - 1/2*x + 1/2, (1,2,3), [ 1, 0, 0, 0, 1 ], 807447831, 23865, 0.6170498912488264) + (978, 0.0557699430711638, -3*x^2 - 1/12, (1,2), [ 0, 0, 1, 1, 0 ], 807447831, 29982, 0.8335077654199736) sage: r2m = rtest(); r2m - (443, 0.185001351421963, -2, (1,3), [ 0, 0, 1, 1, 0 ], 53231108, 51295, 0.28363811590618193) + (105, 0.642309615982449, -x^2 - x - 6, (1,3)(4,5), [ 1, 0, 0, 0, 1 ], 53231108, 40267, 0.001767155077382232) sage: r2m == r2 False @@ -245,8 +245,8 @@ case, as we see in this example:: sage: with seed(1): ....: print(rtest()) ....: print(rtest()) - (978, 0.0557699430711638, -1/8*x^2 - 1/2*x + 1/2, (1,2,3), [ 1, 0, 0, 0, 1 ], 807447831, 23865, 0.6170498912488264) - (181, 0.607995392046754, -x + 1/2, (2,3)(4,5), [ 1, 0, 0, 1, 1 ], 1010791326, 9693, 0.5691716786307407) + (978, 0.0557699430711638, -3*x^2 - 1/12, (1,2), [ 0, 0, 1, 1, 0 ], 807447831, 29982, 0.8335077654199736) + (138, -0.0404945051288503, 2*x - 24, (1,2,3), [ 1, 1, 0, 1, 1 ], 1010791326, 91360, 0.0033332230808060803) sage: r2m == rtest() True @@ -258,7 +258,7 @@ NTL random numbers were generated inside the ``with seed``. True sage: with seed(1): ....: rtest() - (978, 0.0557699430711638, -1/8*x^2 - 1/2*x + 1/2, (1,2,3), [ 1, 0, 0, 0, 1 ], 807447831, 23865, 0.6170498912488264) + (978, 0.0557699430711638, -3*x^2 - 1/12, (1,2), [ 0, 0, 1, 1, 0 ], 807447831, 29982, 0.8335077654199736) sage: r2m == rtest() True diff --git a/src/sage/modular/quasimodform/element.py b/src/sage/modular/quasimodform/element.py index 622152a9711..dd45fca7713 100644 --- a/src/sage/modular/quasimodform/element.py +++ b/src/sage/modular/quasimodform/element.py @@ -31,16 +31,20 @@ class QuasiModularFormsElement(ModuleElement): r""" - A quasimodular forms ring element. Such an element is describbed by SageMath - as a polynomial + A quasimodular forms ring element. Such an element is described by + SageMath as a polynomial .. MATH:: - f_0 + f_1 E_2 + f_2 E_2^2 + \cdots + f_m E_2^m + F = f_0 + f_1 E_2 + f_2 E_2^2 + \cdots + f_m E_2^m where each `f_i` a graded modular form element (see :class:`~sage.modular.modform.element.GradedModularFormElement`) + For an integer `k`, we say that `F` is homogeneous of weight `k` if + it lies in an homogeneous component of degree `k` of the graded ring + of quasimodular forms. + EXAMPLES:: sage: QM = QuasiModularForms(1) @@ -295,6 +299,45 @@ def __bool__(self): """ return bool(self._polynomial) + def depth(self): + r""" + Return the depth of this quasimodular form. + + Note that the quasimodular form must be homogeneous of weight + `k`. Recall that the *depth* is the integer `p` such that + + .. MATH:: + + f = f_0 + f_1 E_2 + \cdots + f_p E_2^p, + + where `f_i` is a modular form of weight `k - 2i` and `f_p` is + nonzero. + + EXAMPLES:: + + sage: QM = QuasiModularForms(1) + sage: E2, E4, E6 = QM.gens() + sage: E2.depth() + 1 + sage: F = E4^2 + E6*E2 + E4*E2^2 + E2^4 + sage: F.depth() + 4 + sage: QM(7/11).depth() + 0 + + TESTS:: + + sage: QM = QuasiModularForms(1) + sage: (QM.0 + QM.1).depth() + Traceback (most recent call last): + ... + ValueError: the given graded quasiform is not an homogeneous element + """ + if not self.is_homogeneous(): + raise ValueError("the given graded quasiform is not an " + "homogeneous element") + return self._polynomial.degree() + def is_zero(self): r""" Return whether the given quasimodular form is zero. diff --git a/src/sage/modular/quasimodform/ring.py b/src/sage/modular/quasimodform/ring.py index ee939a182df..53fcea616bd 100644 --- a/src/sage/modular/quasimodform/ring.py +++ b/src/sage/modular/quasimodform/ring.py @@ -779,3 +779,49 @@ def from_polynomial(self, polynomial): 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)} return self(polynomial.subs(gens_dict)) + + def basis_of_weight(self, weight): + r""" + Return a basis of elements generating the subspace of the given + weight. + + INPUT: + + - ``weight`` (integer) -- the weight of the subspace + + OUTPUT: + + A list of quasimodular forms of the given weight. + + EXAMPLES:: + + sage: QM = QuasiModularForms(1) + sage: QM.basis_of_weight(12) + [q - 24*q^2 + 252*q^3 - 1472*q^4 + 4830*q^5 + O(q^6), + 1 + 65520/691*q + 134250480/691*q^2 + 11606736960/691*q^3 + 274945048560/691*q^4 + 3199218815520/691*q^5 + O(q^6), + 1 - 288*q - 129168*q^2 - 1927296*q^3 + 65152656*q^4 + 1535768640*q^5 + O(q^6), + 1 + 432*q + 39312*q^2 - 1711296*q^3 - 14159664*q^4 + 317412000*q^5 + O(q^6), + 1 - 576*q + 21168*q^2 + 308736*q^3 - 15034608*q^4 - 39208320*q^5 + O(q^6), + 1 + 144*q - 17712*q^2 + 524736*q^3 - 2279088*q^4 - 79760160*q^5 + O(q^6), + 1 - 144*q + 8208*q^2 - 225216*q^3 + 2634192*q^4 + 1488672*q^5 + O(q^6)] + sage: QM = QuasiModularForms(Gamma1(3)) + sage: QM.basis_of_weight(3) + [1 + 54*q^2 + 72*q^3 + 432*q^5 + O(q^6), + q + 3*q^2 + 9*q^3 + 13*q^4 + 24*q^5 + O(q^6)] + sage: QM.basis_of_weight(5) + [1 - 90*q^2 - 240*q^3 - 3744*q^5 + O(q^6), + q + 15*q^2 + 81*q^3 + 241*q^4 + 624*q^5 + O(q^6), + 1 - 24*q - 18*q^2 - 1320*q^3 - 5784*q^4 - 10080*q^5 + O(q^6), + q - 21*q^2 - 135*q^3 - 515*q^4 - 1392*q^5 + O(q^6)] + """ + basis = [] + 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()] + E2_pow *= E2 + if not weight%2: + basis.append(E2_pow) + return basis diff --git a/src/sage/modular/quatalg/brandt.py b/src/sage/modular/quatalg/brandt.py index 985cfe72b5f..4c46a481095 100644 --- a/src/sage/modular/quatalg/brandt.py +++ b/src/sage/modular/quatalg/brandt.py @@ -93,7 +93,7 @@ if there exists an element `\alpha \in I \overline{J}` such `N(\alpha)=N(I)N(J)`. -``is_equivalent(I,J)`` returns true if `I` and `J` are equivalent. This +``is_right_equivalent(I,J)`` returns true if `I` and `J` are equivalent. This method first compares the theta series of `I` and `J`. If they are the same, it computes the theta series of the lattice `I\overline(J)`. It returns true if the `n^{th}` coefficient of this series is nonzero @@ -1193,7 +1193,7 @@ def _compute_hecke_matrix_directly(self, n, B=None, sparse=False): T[r, v[0]] += 1 else: for i in v: - if C[i].is_equivalent(J, 0): + if C[i].is_right_equivalent(J, 0): T[r, i] += 1 break return T @@ -1325,7 +1325,7 @@ def right_ideals(self, B=None): sage: B = BrandtModule(1009) sage: Is = B.right_ideals() sage: n = len(Is) - sage: prod(not Is[i].is_equivalent(Is[j]) for i in range(n) for j in range(i)) + sage: prod(not Is[i].is_right_equivalent(Is[j]) for i in range(n) for j in range(i)) 1 """ p = self._smallest_good_prime() @@ -1353,7 +1353,7 @@ def right_ideals(self, B=None): J_theta = tuple(J.theta_series_vector(B)) if J_theta in ideals_theta: for K in ideals_theta[J_theta]: - if J.is_equivalent(K, 0): + if J.is_right_equivalent(K, 0): is_new = False break if is_new: diff --git a/src/sage/modules/filtered_vector_space.py b/src/sage/modules/filtered_vector_space.py index d6a1d6237a6..68bbff12c8d 100644 --- a/src/sage/modules/filtered_vector_space.py +++ b/src/sage/modules/filtered_vector_space.py @@ -806,7 +806,7 @@ def _repr_field_name(self): return 'RR' from sage.categories.finite_fields import FiniteFields if self.base_ring() in FiniteFields(): - return 'GF({0})'.format(len(self.base_ring())) + return 'GF({})'.format(len(self.base_ring())) else: raise NotImplementedError() diff --git a/src/sage/modules/fp_graded/free_element.py b/src/sage/modules/fp_graded/free_element.py index 232a7956d0e..8dc11e7764a 100755 --- a/src/sage/modules/fp_graded/free_element.py +++ b/src/sage/modules/fp_graded/free_element.py @@ -193,7 +193,7 @@ def _lmul_(self, a): Sq(1,1,1)*x0 + Sq(1,1,1)*y0 + Sq(5,1)*z3, Sq(3,2)*z3] """ - return self.parent()((a * c for c in self.dense_coefficient_list())) + return self.parent()(a * c for c in self.dense_coefficient_list()) @cached_method def vector_presentation(self): diff --git a/src/sage/modules/fp_graded/morphism.py b/src/sage/modules/fp_graded/morphism.py index 47f74fd49e5..92c2753bddf 100755 --- a/src/sage/modules/fp_graded/morphism.py +++ b/src/sage/modules/fp_graded/morphism.py @@ -1747,7 +1747,7 @@ def _resolve_kernel(self, top_dim=None, verbose=False): if not kernel_n: continue - generator_degrees = tuple((x.degree() for x in F_.generators())) + generator_degrees = tuple(x.degree() for x in F_.generators()) if j.is_zero(): # The map j is not onto in degree `n` of the kernel. @@ -1875,7 +1875,7 @@ def _resolve_image(self, top_dim=None, verbose=False): if image_n.dimension() == 0: continue - generator_degrees = tuple((x.degree() for x in F_.generators())) + generator_degrees = tuple(x.degree() for x in F_.generators()) if j.is_zero(): # The map j is not onto in degree `n` of the image. new_generator_degrees = image_n.rank() * (n,) diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index fd3c2735838..867f42bf93b 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -276,7 +276,7 @@ def create_object(self, version, key): "of left/right and both sided modules, so be careful.\n" "It's also not guaranteed that all multiplications are\n" "done from the right side.") - # raise TypeError("The base_ring must be a commutative ring.") + # raise TypeError("the base_ring must be a commutative ring") if not sparse and isinstance(base_ring, sage.rings.abc.RealDoubleField): return RealDoubleVectorSpace_class(rank) @@ -294,7 +294,7 @@ def create_object(self, version, key): return FreeModule_ambient_pid(base_ring, rank, sparse=sparse) if (isinstance(base_ring, sage.rings.abc.Order) - and base_ring.is_maximal() and base_ring.class_number() == 1): + and base_ring.is_maximal() and base_ring.class_number() == 1): return FreeModule_ambient_pid(base_ring, rank, sparse=sparse) if isinstance(base_ring, IntegralDomain) or base_ring in IntegralDomains(): @@ -305,6 +305,7 @@ def create_object(self, version, key): FreeModuleFactory_with_standard_basis = FreeModuleFactory("FreeModule") + def FreeModule(base_ring, rank_or_basis_keys=None, sparse=False, inner_product_matrix=None, *, with_basis='standard', rank=None, basis_keys=None, **args): r""" @@ -538,7 +539,7 @@ def FreeModule(base_ring, rank_or_basis_keys=None, sparse=False, inner_product_m elif with_basis == 'standard': if rank is not None: return FreeModuleFactory_with_standard_basis(base_ring, rank, sparse, - inner_product_matrix, **args) + inner_product_matrix, **args) else: if inner_product_matrix is not None: raise NotImplementedError @@ -547,6 +548,7 @@ def FreeModule(base_ring, rank_or_basis_keys=None, sparse=False, inner_product_m else: raise NotImplementedError + def VectorSpace(K, dimension_or_basis_keys=None, sparse=False, inner_product_matrix=None, *, with_basis='standard', dimension=None, basis_keys=None, **args): """ @@ -583,6 +585,7 @@ def VectorSpace(K, dimension_or_basis_keys=None, sparse=False, inner_product_mat with_basis=with_basis, rank=dimension, basis_keys=basis_keys, **args) + def span(gens, base_ring=None, check=True, already_echelonized=False): r""" Return the span of the vectors in ``gens`` using scalars from ``base_ring``. @@ -773,6 +776,7 @@ def span(gens, base_ring=None, check=True, already_echelonized=False): return M.span(gens=gens, base_ring=base_ring, check=check, already_echelonized=already_echelonized) + def basis_seq(V, vecs): """ This converts a list vecs of vectors in V to a Sequence of @@ -2002,8 +2006,9 @@ def construction(self): (VectorFunctor, Multivariate Polynomial Ring in x0, x1, x2 over Rational Field) """ from sage.categories.pushout import VectorFunctor - if hasattr(self,'_inner_product_matrix'): - return VectorFunctor(self.rank(), self.is_sparse(),self.inner_product_matrix()), self.base_ring() + if hasattr(self, '_inner_product_matrix'): + return VectorFunctor(self.rank(), self.is_sparse(), + self.inner_product_matrix()), self.base_ring() return VectorFunctor(self.rank(), self.is_sparse()), self.base_ring() # FIXME: what's the level of generality of FreeModuleHomspace? @@ -2169,10 +2174,9 @@ def _element_constructor_(self, x, coerce=True, copy=True, check=True): sage: N((0,0,0,1), check=False) in N True """ - if (isinstance(x, (int, sage.rings.integer.Integer)) and - x == 0): + if (isinstance(x, (int, sage.rings.integer.Integer)) and x == 0): return self.zero_vector() - elif isinstance(x, free_module_element.FreeModuleElement): + if isinstance(x, free_module_element.FreeModuleElement): if x.parent() is self: if copy: return x.__copy__() @@ -2326,7 +2330,7 @@ def is_submodule(self, other): pass from sage.modules.quotient_module import FreeModule_ambient_field_quotient if isinstance(other, FreeModule_ambient_field_quotient): - #if the relations agree we continue with the covers. + # if the relations agree we continue with the covers. if isinstance(self, FreeModule_ambient_field_quotient): if other.relations() != self.relations(): return False @@ -2438,23 +2442,24 @@ def __iter__(self): # Aleksei Udovenko, adapted by Lorenz Panny to order by 1-norm # primarily and by max-norm secondarily. def aux(length, norm, max_): - if not 0 <= norm <= length*max_: + if not 0 <= norm <= length * max_: return # there are no such vectors if norm == max_ == 0: - yield (0,)*length + yield (0,) * length return for pos in range(length): - for lnorm in range(norm-max_+1): + for lnorm in range(norm - max_ + 1): for lmax in range(max_): for left in aux(pos, lnorm, lmax): - for rmax in range(max_+1): - for right in aux(length-1-pos, norm-max_-lnorm, rmax): + for rmax in range(max_ + 1): + for right in aux(length - 1 - pos, + norm - max_ - lnorm, rmax): for mid in (+max_, -max_): yield left + (mid,) + right n = len(G) for norm in itertools.count(0): - mm = (norm + n-1) // n - for max_ in range(mm, norm+1): + mm = (norm + n - 1) // n + for max_ in range(mm, norm + 1): for vec in aux(n, norm, max_): yield self.linear_combination_of_basis(vec) assert False # should loop forever @@ -2620,8 +2625,9 @@ def basis_matrix(self, ring=None): A = self.__basis_matrix except AttributeError: MAT = sage.matrix.matrix_space.MatrixSpace(self.coordinate_ring(), - len(self.basis()), self.degree(), - sparse=self.is_sparse()) + len(self.basis()), + self.degree(), + sparse=self.is_sparse()) if self.is_ambient(): A = MAT.identity_matrix() else: @@ -3018,10 +3024,10 @@ def gram_matrix(self): else: if self._gram_matrix is None: B = self.basis_matrix() - self._gram_matrix = B*B.transpose() + self._gram_matrix = B * B.transpose() return self._gram_matrix - def has_user_basis(self): + def has_user_basis(self) -> bool: """ Return ``True`` if the basis of this free module is specified by the user, as opposed to being the default echelon @@ -3505,7 +3511,7 @@ def scale(self, other): return self.zero_submodule() if other == 1 or other == -1: return self - return self.span([v*other for v in self.basis()]) + return self.span([v * other for v in self.basis()]) def __radd__(self, other): """ @@ -4121,12 +4127,12 @@ def span_of_basis(self, basis, base_ring=None, check=True, already_echelonized=F M = self.change_ring(base_ring) except TypeError: raise ValueError("Argument base_ring (= %s) is not compatible " % base_ring + - "with the base ring (= %s)." % self.base_ring()) + "with the base ring (= %s)." % self.base_ring()) try: return M.span_of_basis(basis) except TypeError: raise ValueError("Argument gens (= %s) is not compatible " % basis + - "with base_ring (= %s)." % base_ring) + "with base_ring (= %s)." % base_ring) def submodule_with_basis(self, basis, check=True, already_echelonized=False): r""" @@ -4755,7 +4761,7 @@ def subspaces(self, dim): b = self.basis_matrix() from sage.matrix.echelon_matrix import reduced_echelon_matrix_iterator for m in reduced_echelon_matrix_iterator(self.base_ring(), dim, self.dimension(), self.is_sparse(), copy=False): - yield self.subspace((m*b).rows()) + yield self.subspace((m * b).rows()) def subspace_with_basis(self, gens, check=True, already_echelonized=False): """ @@ -5354,7 +5360,9 @@ def __init__(self, base_ring, rank, sparse=False, coordinate_ring=None, category True """ FreeModule_generic.__init__(self, base_ring, rank=rank, - degree=rank, sparse=sparse, coordinate_ring=coordinate_ring, category=category) + degree=rank, sparse=sparse, + coordinate_ring=coordinate_ring, + category=category) def __hash__(self): """ @@ -5399,14 +5407,14 @@ def _coerce_map_from_(self, M): return None if isinstance(M, FreeModule_ambient): if (self.base_ring().has_coerce_map_from(M.base_ring()) and - self.rank() == M.rank()): + self.rank() == M.rank()): # We could return M.hom(self.basis(), self), but the # complexity of this is quadratic in space and time, # since it constructs a matrix. return True elif isinstance(M, Submodule_free_ambient): if (self.base_ring().has_coerce_map_from(M.base_ring()) and - self.rank() == M.degree()): + self.rank() == M.degree()): return True return super()._coerce_map_from_(M) @@ -5544,7 +5552,7 @@ def _echelon_matrix_richcmp(self, other, op): if isinstance(other, FreeModule_ambient): if (isinstance(other, FreeModule_ambient_field_quotient) or isinstance(self, FreeModule_ambient_field_quotient)): - return richcmp(self,other,op) + return richcmp(self, other, op) lx = self.rank() rx = other.rank() @@ -5559,10 +5567,10 @@ def _echelon_matrix_richcmp(self, other, op): if self._inner_product_is_dot_product() and other._inner_product_is_dot_product(): return rich_to_bool(op, 0) else: - #this only affects free_quadratic_modules + # this only affects free_quadratic_modules lx = self.inner_product_matrix() rx = other.inner_product_matrix() - return richcmp(lx,rx,op) + return richcmp(lx, rx, op) try: if lx.is_subring(rx): @@ -6234,9 +6242,11 @@ def __init__(self, base_ring, rank, sparse=False, coordinate_ring=None, category """ FreeModule_ambient_domain.__init__(self, base_ring=base_ring, - rank=rank, sparse=sparse, coordinate_ring=coordinate_ring, category=category) + rank=rank, sparse=sparse, + coordinate_ring=coordinate_ring, + category=category) - def _repr_(self): + def _repr_(self) -> str: """ The printing representation of self. @@ -6402,18 +6412,18 @@ def _element_constructor_(self, e, *args, **kwds): class RealDoubleVectorSpace_class(FreeModule_ambient_field): - def __init__(self,n): - FreeModule_ambient_field.__init__(self,sage.rings.real_double.RDF,n) + def __init__(self, n): + FreeModule_ambient_field.__init__(self, sage.rings.real_double.RDF, n) - def coordinates(self,v): + def coordinates(self, v): return v class ComplexDoubleVectorSpace_class(FreeModule_ambient_field): - def __init__(self,n): - FreeModule_ambient_field.__init__(self,sage.rings.complex_double.CDF,n) + def __init__(self, n): + FreeModule_ambient_field.__init__(self, sage.rings.complex_double.CDF, n) - def coordinates(self,v): + def coordinates(self, v): return v @@ -6472,8 +6482,9 @@ class FreeModule_submodule_with_basis_pid(FreeModule_generic_pid): [ 4 5 6] """ def __init__(self, ambient, basis, check=True, - echelonize=False, echelonized_basis=None, already_echelonized=False, - category=None): + echelonize=False, echelonized_basis=None, + already_echelonized=False, + category=None): r""" See :class:`FreeModule_submodule_with_basis_pid` for documentation. @@ -6731,11 +6742,11 @@ def _echelonized_basis(self, ambient, basis): MAT = sage.matrix.matrix_space.MatrixSpace( ambient.base_ring(), len(basis), ambient.degree(), sparse=ambient.is_sparse()) if d != 1: - basis = [x*d for x in basis] + basis = [x * d for x in basis] A = MAT(basis) E = A.echelon_form() if d != 1: - E = E.matrix_over_field()*(~d) # divide out denominator + E = E.matrix_over_field() * (~d) # divide out denominator r = E.rank() if r < E.nrows(): E = E.matrix_from_rows(range(r)) @@ -6766,7 +6777,7 @@ def _denominator(self, B): d = B[0].denominator() from sage.arith.functions import lcm for x in B[1:]: - d = lcm(d,x.denominator()) + d = lcm(d, x.denominator()) return d def _repr_(self): @@ -7094,10 +7105,11 @@ def user_to_echelon_matrix(self): if self.base_ring().is_field(): self.__user_to_echelon_matrix = self._user_to_rref_matrix() else: - rows = sum([self.echelon_coordinates(b,check=False) for b in self.basis()], []) + rows = sum([self.echelon_coordinates(b, check=False) + for b in self.basis()], []) M = sage.matrix.matrix_space.MatrixSpace(self.base_ring().fraction_field(), - self.dimension(), - sparse=self.is_sparse()) + self.dimension(), + sparse=self.is_sparse()) self.__user_to_echelon_matrix = M(rows) return self.__user_to_echelon_matrix @@ -7603,8 +7615,9 @@ def __init__(self, ambient, gens, check=True, already_echelonized=False, [0 3 6] """ FreeModule_submodule_with_basis_pid.__init__(self, ambient, basis=gens, - echelonize=True, already_echelonized=already_echelonized, - category=category) + echelonize=True, + already_echelonized=already_echelonized, + category=category) def _repr_(self): """ @@ -7743,8 +7756,9 @@ class FreeModule_submodule_with_basis_field(FreeModule_generic_field, FreeModule sage: TestSuite(W).run() """ def __init__(self, ambient, basis, check=True, - echelonize=False, echelonized_basis=None, already_echelonized=False, - category=None): + echelonize=False, echelonized_basis=None, + already_echelonized=False, + category=None): """ Create a vector space with given basis. @@ -7831,12 +7845,12 @@ def _repr_(self): """ if self.is_sparse(): return "Sparse vector space of degree %s and dimension %s over %s\n" % ( - self.degree(), self.dimension(), self.base_field()) + \ - "User basis matrix:\n%r" % self.basis_matrix() - else: - return "Vector space of degree %s and dimension %s over %s\n" % ( - self.degree(), self.dimension(), self.base_field()) + \ - "User basis matrix:\n%r" % self.basis_matrix() + self.degree(), self.dimension(), self.base_field()) + \ + "User basis matrix:\n%r" % self.basis_matrix() + + return "Vector space of degree %s and dimension %s over %s\n" % ( + self.degree(), self.dimension(), self.base_field()) + \ + "User basis matrix:\n%r" % self.basis_matrix() def _denominator(self, B): """ @@ -7958,11 +7972,13 @@ def __init__(self, ambient, gens, check=True, already_echelonized=False, categor """ if is_FreeModule(gens): gens = gens.gens() - FreeModule_submodule_with_basis_field.__init__(self, ambient, basis=gens, check=check, - echelonize=not already_echelonized, already_echelonized=already_echelonized, - category=category) + FreeModule_submodule_with_basis_field.__init__(self, ambient, + basis=gens, check=check, + echelonize=not already_echelonized, + already_echelonized=already_echelonized, + category=category) - def _repr_(self): + def _repr_(self) -> str: """ The default printing representation of self. @@ -8256,7 +8272,7 @@ def element_class(R, is_sparse): @richcmp_method -class EchelonMatrixKey(): +class EchelonMatrixKey: r""" A total ordering on free modules for sorting. diff --git a/src/sage/modules/free_module_element.pyx b/src/sage/modules/free_module_element.pyx index ff43d239493..2f47c924d38 100644 --- a/src/sage/modules/free_module_element.pyx +++ b/src/sage/modules/free_module_element.pyx @@ -4148,6 +4148,87 @@ cdef class FreeModuleElement(Vector): # abstract base class nintegrate=nintegral + def concatenate(self, other, *, ring=None): + r""" + Return the result of concatenating this vector with a sequence + of elements given by another iterable. + + If the optional keyword argument ``ring`` is passed, this method + will return a vector over the specified ring (or fail). If no + base ring is given, the base ring is determined automatically by + the :func:`vector` constructor. + + EXAMPLES:: + + sage: v = vector([1, 2, 3]) + sage: w = vector([4, 5]) + sage: v.concatenate(w) + (1, 2, 3, 4, 5) + sage: v.parent() + Ambient free module of rank 3 over the principal ideal domain Integer Ring + sage: w.parent() + Ambient free module of rank 2 over the principal ideal domain Integer Ring + sage: v.concatenate(w).parent() + Ambient free module of rank 5 over the principal ideal domain Integer Ring + + Forcing a base ring is possible using the ``ring`` argument:: + + sage: v.concatenate(w, ring=QQ) + (1, 2, 3, 4, 5) + sage: v.concatenate(w, ring=QQ).parent() + Vector space of dimension 5 over Rational Field + + :: + + sage: v.concatenate(w, ring=Zmod(3)) + (1, 2, 0, 1, 2) + + The method accepts arbitrary iterables of elements which can + be coerced to a common base ring:: + + sage: v.concatenate(range(4,8)) + (1, 2, 3, 4, 5, 6, 7) + sage: v.concatenate(range(4,8)).parent() + Ambient free module of rank 7 over the principal ideal domain Integer Ring + + :: + + sage: w2 = [4, QQbar(-5).sqrt()] + sage: v.concatenate(w2) + (1, 2, 3, 4, 2.236...*I) + sage: v.concatenate(w2).parent() + Vector space of dimension 5 over Algebraic Field + sage: w2 = vector(w2) + sage: v.concatenate(w2) + (1, 2, 3, 4, 2.236...*I) + sage: v.concatenate(w2).parent() + Vector space of dimension 5 over Algebraic Field + + :: + + sage: w2 = polygen(QQ)^4 + 5 + sage: v.concatenate(w2) + (1, 2, 3, 5, 0, 0, 0, 1) + sage: v.concatenate(w2).parent() + Vector space of dimension 8 over Rational Field + sage: v.concatenate(w2, ring=ZZ) + (1, 2, 3, 5, 0, 0, 0, 1) + sage: v.concatenate(w2, ring=ZZ).parent() + Ambient free module of rank 8 over the principal ideal domain Integer Ring + + :: + + sage: v.concatenate(GF(9).gens()) + (1, 2, 0, z2) + sage: v.concatenate(GF(9).gens()).parent() + Vector space of dimension 4 over Finite Field in z2 of size 3^2 + """ + from itertools import chain + coeffs = chain(self, other) + if ring is not None: + return vector(ring, coeffs) + return vector(coeffs) + ############################################# # Generic dense element ############################################# diff --git a/src/sage/modules/matrix_morphism.py b/src/sage/modules/matrix_morphism.py index 4624465f8dc..9cd429ef0bc 100644 --- a/src/sage/modules/matrix_morphism.py +++ b/src/sage/modules/matrix_morphism.py @@ -71,6 +71,7 @@ def is_MatrixMorphism(x): """ return isinstance(x, MatrixMorphism_abstract) + class MatrixMorphism_abstract(sage.categories.morphism.Morphism): def __init__(self, parent, side='left'): """ @@ -188,7 +189,7 @@ def _call_(self, x): if parent(x) is not self.domain(): x = self.domain()(x) except TypeError: - raise TypeError("%s must be coercible into %s" % (x,self.domain())) + raise TypeError("%s must be coercible into %s" % (x, self.domain())) if self.domain().is_ambient(): x = x.element() else: @@ -860,12 +861,12 @@ def decomposition(self, *args, **kwds): ] """ if not self.is_endomorphism(): - raise ArithmeticError("Matrix morphism must be an endomorphism.") + raise ArithmeticError("matrix morphism must be an endomorphism") D = self.domain() if self.side() == "left": - E = self.matrix().decomposition(*args,**kwds) + E = self.matrix().decomposition(*args, **kwds) else: - E = self.matrix().transpose().decomposition(*args,**kwds) + E = self.matrix().transpose().decomposition(*args, **kwds) if D.is_ambient(): return Sequence([D.submodule(V, check=False) for V, _ in E], cr=True, check=False) @@ -899,7 +900,7 @@ def det(self): 2 """ if not self.is_endomorphism(): - raise ArithmeticError("Matrix morphism must be an endomorphism.") + raise ArithmeticError("matrix morphism must be an endomorphism") return self.matrix().determinant() def fcp(self, var='x'): @@ -1635,7 +1636,7 @@ def __init__(self, parent, A, copy_matrix=True, side='left'): if A.nrows() != parent.domain().rank(): raise ArithmeticError("number of rows of matrix (={}) must equal rank of domain (={})".format(A.nrows(), parent.domain().rank())) if A.ncols() != parent.codomain().rank(): - raise ArithmeticError("number of columns of matrix (={}) must equal rank of codomain (={})".format(A.ncols(), parent.codomain().rank())) + raise ArithmeticError("number of columns of matrix (={}) must equal rank of codomain (={})".format(A.ncols(), parent.codomain().rank())) if side == "right": if A.nrows() != parent.codomain().rank(): raise ArithmeticError("number of rows of matrix (={}) must equal rank of codomain (={})".format(A.nrows(), parent.domain().rank())) @@ -1697,7 +1698,7 @@ def matrix(self, side=None): ValueError: side must be 'left' or 'right', not junk """ if side not in ['left', 'right', None]: - raise ValueError("side must be 'left' or 'right', not {0}".format(side)) + raise ValueError("side must be 'left' or 'right', not {}".format(side)) if side == self.side() or side is None: return self._matrix return self._matrix.transpose() @@ -1794,7 +1795,7 @@ def _repr_(self): sage: phi._repr_() 'Free module morphism defined by the matrix\n[3 0]\n[0 2]\nDomain: Ambient free module of rank 2 over the principal ideal domain Integer Ring\nCodomain: Ambient free module of rank 2 over the principal ideal domain Integer Ring' """ - rep = "Morphism defined by the matrix\n{0}".format(self.matrix()) + rep = "Morphism defined by the matrix\n{}".format(self.matrix()) if self._side == 'right': rep += " acting by multiplication on the left" return rep diff --git a/src/sage/modules/vector_space_morphism.py b/src/sage/modules/vector_space_morphism.py index 1102d15968e..6585da0b047 100644 --- a/src/sage/modules/vector_space_morphism.py +++ b/src/sage/modules/vector_space_morphism.py @@ -705,9 +705,9 @@ def linear_transformation(arg0, arg1=None, arg2=None, side='left'): Vector_callable_symbolic_dense = () if side not in ['left', 'right']: - raise ValueError("side must be 'left' or 'right', not {0}".format(side)) + raise ValueError("side must be 'left' or 'right', not {}".format(side)) if not (is_Matrix(arg0) or is_VectorSpace(arg0)): - raise TypeError('first argument must be a matrix or a vector space, not {0}'.format(arg0)) + raise TypeError('first argument must be a matrix or a vector space, not {}'.format(arg0)) if is_Matrix(arg0): R = arg0.base_ring() if not R.is_field(): @@ -763,7 +763,7 @@ def linear_transformation(arg0, arg1=None, arg2=None, side='left'): msg = 'symbolic function must be linear in all the inputs:\n' + e.args[0] raise ValueError(msg) # have matrix with respect to standard bases, now consider user bases - images = [v*arg2 for v in D.basis()] + images = [v * arg2 for v in D.basis()] try: arg2 = matrix([C.coordinates(C(a)) for a in images]) except (ArithmeticError, TypeError) as e: @@ -779,7 +779,8 @@ def linear_transformation(arg0, arg1=None, arg2=None, side='left'): # __init__ will check matrix sizes versus domain/codomain dimensions return H(arg2) -def is_VectorSpaceMorphism(x): + +def is_VectorSpaceMorphism(x) -> bool: r""" Returns ``True`` if ``x`` is a vector space morphism (a linear transformation). @@ -859,7 +860,7 @@ def __init__(self, homspace, A, side="left"): """ if not vector_space_homspace.is_VectorSpaceHomspace(homspace): - raise TypeError('homspace must be a vector space hom space, not {0}'.format(homspace)) + raise TypeError('homspace must be a vector space hom space, not {}'.format(homspace)) if isinstance(A, matrix_morphism.MatrixMorphism): A = A.matrix() if not is_Matrix(A): diff --git a/src/sage/modules/with_basis/subquotient.py b/src/sage/modules/with_basis/subquotient.py index f36317a769d..d331e6243ad 100644 --- a/src/sage/modules/with_basis/subquotient.py +++ b/src/sage/modules/with_basis/subquotient.py @@ -82,7 +82,7 @@ def __classcall_private__(cls, submodule, category=None): category = default_category.or_subcategory(category, join=True) return super().__classcall__(cls, submodule, category) - def __init__(self, submodule, category): + def __init__(self, submodule, category, *args, **opts): r""" Initialize this quotient of a module with basis by a submodule. @@ -107,7 +107,7 @@ def __init__(self, submodule, category): indices = embedding.cokernel_basis_indices() CombinatorialFreeModule.__init__(self, submodule.base_ring(), indices, - category=category) + category=category, *args, **opts) def ambient(self): r""" @@ -394,21 +394,19 @@ def is_submodule(self, other): sage: H.is_submodule(G) False - TESTS:: + Different ambient spaces:: sage: X = CombinatorialFreeModule(QQ, range(4)); x = X.basis() sage: F = X.submodule([x[0]-x[1], x[1]-x[2], x[2]-x[3]]) sage: Y = CombinatorialFreeModule(QQ, range(6)); y = Y.basis() sage: G = Y.submodule([y[0]-y[1], y[1]-y[2], y[2]-y[3]]) sage: F.is_submodule(G) - Traceback (most recent call last): - ... - ValueError: other (=...) should be a submodule of the same ambient space + False """ if other is self._ambient: return True if not (isinstance(self, SubmoduleWithBasis) and self.ambient() is other.ambient()): - raise ValueError("other (=%s) should be a submodule of the same ambient space" % other) + return False # different ambient spaces if self not in ModulesWithBasis.FiniteDimensional: raise NotImplementedError("only implemented for finite dimensional submodules") if self.dimension() > other.dimension(): # quick dimension check diff --git a/src/sage/monoids/indexed_free_monoid.py b/src/sage/monoids/indexed_free_monoid.py index 58910533a9a..09b35530098 100644 --- a/src/sage/monoids/indexed_free_monoid.py +++ b/src/sage/monoids/indexed_free_monoid.py @@ -355,6 +355,31 @@ def to_word_list(self): """ return [k for k,e in self._sorted_items() for dummy in range(e)] + def is_one(self) -> bool: + """ + Return if ``self`` is the identity element. + + EXAMPLES:: + + sage: F = FreeMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: (b*a*c^3*a).is_one() + False + sage: F.one().is_one() + True + + :: + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: (b*c^3*a).is_one() + False + sage: F.one().is_one() + True + """ + return not self._monomial + + class IndexedFreeMonoidElement(IndexedMonoidElement): """ An element of an indexed free abelian monoid. @@ -591,6 +616,29 @@ def __floordiv__(self, elt): d[k] = diff return self.__class__(self.parent(), d) + def divides(self, m) -> bool: + r""" + Return whether ``self`` divides ``m``. + + EXAMPLES:: + + sage: F = FreeAbelianMonoid(index_set=ZZ) + sage: a,b,c,d,e = [F.gen(i) for i in range(5)] + sage: elt = a*b*c^3*d^2 + sage: a.divides(elt) + True + sage: c.divides(elt) + True + sage: (a*b*d^2).divides(elt) + True + sage: (a^4).divides(elt) + False + sage: e.divides(elt) + False + """ + other = m._monomial + return all(k in other and v <= other[k] for k, v in self._monomial.items()) + def __len__(self): """ Return the length of ``self``. @@ -605,8 +653,7 @@ def __len__(self): sage: len(elt) 7 """ - m = self._monomial - return sum(m[gen] for gen in m) + return sum(self._monomial.values()) length = __len__ diff --git a/src/sage/rings/finite_rings/element_base.pyx b/src/sage/rings/finite_rings/element_base.pyx index 1d0dd2b563a..42ca25b930e 100755 --- a/src/sage/rings/finite_rings/element_base.pyx +++ b/src/sage/rings/finite_rings/element_base.pyx @@ -143,7 +143,7 @@ cdef class FiniteRingElement(CommutativeRingElement): raise ValueError("unknown algorithm") def to_bytes(self, byteorder="big"): - """ + r""" Return an array of bytes representing an integer. Internally relies on the python ``int.to_bytes()`` method. diff --git a/src/sage/rings/finite_rings/finite_field_base.pyx b/src/sage/rings/finite_rings/finite_field_base.pyx index 00b30bb5a44..7419c158d8c 100644 --- a/src/sage/rings/finite_rings/finite_field_base.pyx +++ b/src/sage/rings/finite_rings/finite_field_base.pyx @@ -2118,7 +2118,7 @@ cdef class FiniteField(Field): for col in B.columns()] def from_bytes(self, input_bytes, byteorder="big"): - """ + r""" Return the integer represented by the given array of bytes. Internally relies on the python ``int.from_bytes()`` method. diff --git a/src/sage/rings/integer_ring.pyx b/src/sage/rings/integer_ring.pyx index bbbd989a65d..9199debbfb6 100644 --- a/src/sage/rings/integer_ring.pyx +++ b/src/sage/rings/integer_ring.pyx @@ -1575,7 +1575,7 @@ cdef class IntegerRing_class(PrincipalIdealDomain): return pAdicValuation(self, p) def from_bytes(self, input_bytes, byteorder="big", is_signed=False): - """ + r""" Return the integer represented by the given array of bytes. Internally relies on the python ``int.from_bytes()`` method. diff --git a/src/sage/rings/polynomial/plural.pxd b/src/sage/rings/polynomial/plural.pxd index 06b48c737f3..5e3618bd90e 100644 --- a/src/sage/rings/polynomial/plural.pxd +++ b/src/sage/rings/polynomial/plural.pxd @@ -37,6 +37,8 @@ cdef class NCPolynomial_plural(RingElement): cpdef _repr_short_(self) noexcept cdef long _hash_c(self) noexcept cpdef is_constant(self) noexcept + cpdef dict dict(self) noexcept + cpdef dict monomial_coefficients(self, bint copy=*) noexcept # cpdef _homogenize(self, int var) cdef NCPolynomial_plural new_NCP(NCPolynomialRing_plural parent, poly *juice) noexcept diff --git a/src/sage/rings/polynomial/plural.pyx b/src/sage/rings/polynomial/plural.pyx index 4fb7104cce6..30e6aa89dc3 100644 --- a/src/sage/rings/polynomial/plural.pyx +++ b/src/sage/rings/polynomial/plural.pyx @@ -856,6 +856,20 @@ cdef class NCPolynomialRing_plural(Ring): return new_NCP(self,_p) + def algebra_generators(self): + r""" + Return the algebra generators of ``self``. + + EXAMPLES:: + + sage: A. = FreeAlgebra(QQ, 3) + sage: P = A.g_algebra(relations={y*x:-x*y}, order='lex') + sage: P.algebra_generators() + Finite family {'x': x, 'y': y, 'z': z} + """ + from sage.sets.family import Family + return Family(self.gens_dict()) + def ideal(self, *gens, **kwds): """ Create an ideal in this polynomial ring. @@ -2178,7 +2192,7 @@ cdef class NCPolynomial_plural(RingElement): return (self._parent)._base._zero_element - def dict(self): + cpdef dict dict(self) noexcept: """ Return a dictionary representing ``self``. This dictionary is in the same format as the generic MPolynomial: The dictionary @@ -2204,7 +2218,8 @@ cdef class NCPolynomial_plural(RingElement): rChangeCurrRing(r) base = (self._parent)._base p = self._poly - pd = dict() + cdef dict d + cdef dict pd = dict() while p: d = dict() for v from 1 <= v <= r.N: @@ -2217,6 +2232,30 @@ cdef class NCPolynomial_plural(RingElement): p = pNext(p) return pd + cpdef dict monomial_coefficients(self, bint copy=True) noexcept: + """ + Return a dictionary representation of ``self`` with the keys + the exponent vectors and the values the corresponding coefficients. + + INPUT: + + * "copy" -- ignored + + EXAMPLES:: + + sage: A. = FreeAlgebra(GF(389), 3) + sage: R = A.g_algebra(relations={y*x:-x*y + z}, order='lex') + sage: R.inject_variables() + Defining x, z, y + sage: f = (2*x*y^3*z^2 + (7)*x^2 + (3)) + sage: d = f.monomial_coefficients(False); d + {(0, 0, 0): 3, (1, 2, 3): 2, (2, 0, 0): 7} + sage: d.clear() + sage: f.monomial_coefficients() + {(0, 0, 0): 3, (1, 2, 3): 2, (2, 0, 0): 7} + """ + return self.dict() + def _im_gens_(self, codomain, im_gens, base_map=None): """ Return the image of ``self`` in codomain under the map that sends diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index b9c64884a57..83251eda18e 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -281,7 +281,6 @@ def __init__(self, base_ring, name=None, sparse=False, implementation=None, sage: GF(7)['x']['y'].is_finite() False - """ # We trust that, if category is given, it is useful and does not need to be joined # with the default category @@ -1320,7 +1319,7 @@ def monomial(self, exponent): sage: R.monomial(m.degree()) == m True """ - return self({exponent:self.base_ring().one()}) + return self({exponent: self.base_ring().one()}) def krull_dimension(self): """ @@ -1362,23 +1361,25 @@ def ngens(self): """ return 1 - def random_element(self, degree=(-1,2), *args, **kwds): + def random_element(self, degree=(-1, 2), monic=False, *args, **kwds): r""" - Return a random polynomial of given degree or with given degree bounds. + Return a random polynomial of given degree (bounds). INPUT: - - ``degree`` - optional integer for fixing the degree - or a tuple of minimum and maximum degrees. By default set to - ``(-1,2)``. + - ``degree`` -- (default: ``(-1, 2)``) integer for fixing the degree or + a tuple of minimum and maximum degrees + + - ``monic`` -- boolean (optional); indicate whether the sampled + polynomial should be monic - - ``*args, **kwds`` - Passed on to the ``random_element`` method for - the base ring + - ``*args, **kwds`` -- additional keyword parameters passed on to the + ``random_element`` method for the base ring EXAMPLES:: sage: R. = ZZ[] - sage: f = R.random_element(10, 5, 10) + sage: f = R.random_element(10, x=5, y=10) sage: f.degree() 10 sage: f.parent() is R @@ -1388,14 +1389,16 @@ def random_element(self, degree=(-1,2), *args, **kwds): sage: R.random_element(6).degree() 6 - If a tuple of two integers is given for the ``degree`` argument, a degree - is first uniformly chosen, then a polynomial of that degree is given:: + If a tuple of two integers is given for the ``degree`` argument, a + polynomial is chosen among all polynomials with degree between them. If + the base ring can be sampled uniformly, then this method also samples + uniformly:: - sage: R.random_element(degree=(0, 8)).degree() in range(0, 9) + sage: R.random_element(degree=(0, 4)).degree() in range(0, 5) True - sage: found = [False]*9 + sage: found = [False]*5 sage: while not all(found): - ....: found[R.random_element(degree=(0, 8)).degree()] = True + ....: found[R.random_element(degree=(0, 4)).degree()] = True Note that the zero polynomial has degree `-1`, so if you want to consider it set the minimum degree to `-1`:: @@ -1403,6 +1406,14 @@ def random_element(self, degree=(-1,2), *args, **kwds): sage: while R.random_element(degree=(-1,2), x=-1, y=1) != R.zero(): ....: pass + Monic polynomials are chosen among all monic polynomials with degree + between the given ``degree`` argument:: + + sage: all(R.random_element(degree=(-1, 1), monic=True).is_monic() for _ in range(10^3)) + True + sage: all(R.random_element(degree=(0, 1), monic=True).is_monic() for _ in range(10^3)) + True + TESTS:: sage: R.random_element(degree=[5]) @@ -1419,14 +1430,42 @@ def random_element(self, degree=(-1,2), *args, **kwds): sage: R = PolynomialRing(GF(2), 'z') sage: for _ in range(100): - ....: d = randint(-1,20) + ....: d = randint(-1, 20) ....: P = R.random_element(degree=d) - ....: assert P.degree() == d, "problem with {} which has not degree {}".format(P,d) + ....: assert P.degree() == d + + In :issue:`37118`, ranges including integers below `-1` no longer raise + an error:: + + sage: R.random_element(degree=(-2, 3)) # random + z^3 + z^2 + 1 + + :: + + sage: 0 in [R.random_element(degree=(-1, 2), monic=True) for _ in range(500)] + False + + Testing error handling:: + + sage: R.random_element(degree=-5) + Traceback (most recent call last): + ... + ValueError: degree (=-5) must be at least -1 - sage: R.random_element(degree=-2) + sage: R.random_element(degree=(-3, -2)) Traceback (most recent call last): ... - ValueError: degree should be an integer greater or equal than -1 + ValueError: maximum degree (=-2) must be at least -1 + + Testing uniformity:: + + sage: from collections import Counter + sage: R = GF(3)["x"] + sage: samples = [R.random_element(degree=(-1, 2)) for _ in range(27000)] # long time + sage: assert all(750 <= f <= 1250 for f in Counter(samples).values()) # long time + + sage: samples = [R.random_element(degree=(-1, 2), monic=True) for _ in range(13000)] # long time + sage: assert all(750 <= f <= 1250 for f in Counter(samples).values()) # long time """ R = self.base_ring() @@ -1435,11 +1474,15 @@ def random_element(self, degree=(-1,2), *args, **kwds): raise ValueError("degree argument must be an integer or a tuple of 2 integers (min_degree, max_degree)") if degree[0] > degree[1]: raise ValueError("minimum degree must be less or equal than maximum degree") + if degree[1] < -1: + raise ValueError(f"maximum degree (={degree[1]}) must be at least -1") else: - degree = (degree,degree) + if degree < -1: + raise ValueError(f"degree (={degree}) must be at least -1") + degree = (degree, degree) if degree[0] <= -2: - raise ValueError("degree should be an integer greater or equal than -1") + degree = (-1, degree[1]) # If the coefficient range only contains 0, then # * if the degree range includes -1, return the zero polynomial, @@ -1450,24 +1493,44 @@ def random_element(self, degree=(-1,2), *args, **kwds): else: raise ValueError("No polynomial of degree >= 0 has all coefficients zero") - # Pick a random degree - d = randint(degree[0], degree[1]) - - # If degree is -1, return the 0 polynomial - if d == -1: + if degree == (-1, -1): return self.zero() - # If degree is 0, return a random constant term - if d == 0: - return self(R._random_nonzero_element(*args, **kwds)) + # If `monic` is set, zero should be ignored + if degree[0] == -1 and monic: + if degree[1] == -1: + raise ValueError("the maximum degree of monic polynomials needs to be at least 0") + if degree[1] == 0: + return self.one() + degree = (0, degree[1]) # Pick random coefficients - p = self([R.random_element(*args, **kwds) for _ in range(d)]) + end = degree[1] + if degree[0] == -1: + return self([R.random_element(*args, **kwds) for _ in range(end + 1)]) + + nonzero = False + coefs = [None] * (end + 1) + + while not nonzero: + # Pick leading coefficients, if `monic` is set it's handle here. + if monic: + for i in range(degree[1] - degree[0] + 1): + coefs[end - i] = R.random_element(*args, **kwds) + if not nonzero and not coefs[end - i].is_zero(): + coefs[end - i] = R.one() + nonzero = True + else: + # Fast path + for i in range(degree[1] - degree[0] + 1): + coefs[end - i] = R.random_element(*args, **kwds) + nonzero |= not coefs[end - i].is_zero() - # Add non-zero leading coefficient - p += R._random_nonzero_element(*args, **kwds) * self.gen() ** d + # Now we pick the remaining coefficients. + for i in range(degree[1] - degree[0] + 1, degree[1] + 1): + coefs[end - i] = R.random_element(*args, **kwds) - return p + return self(coefs) def _monics_degree(self, of_degree): """ @@ -1562,8 +1625,8 @@ def karatsuba_threshold(self): def set_karatsuba_threshold(self, Karatsuba_threshold): """ - Changes the default threshold for this ring in the method :meth:`_mul_karatsuba` - to fall back to the schoolbook algorithm. + Changes the default threshold for this ring in the method + :meth:`_mul_karatsuba` to fall back to the schoolbook algorithm. .. warning:: @@ -1587,11 +1650,11 @@ def polynomials( self, of_degree=None, max_degree=None ): INPUT: Pass exactly one of: - - ``max_degree`` - an int; the iterator will generate + - ``max_degree`` -- an int; the iterator will generate all polynomials which have degree less than or equal to ``max_degree`` - - ``of_degree`` - an int; the iterator will generate + - ``of_degree`` -- an int; the iterator will generate all polynomials which have degree ``of_degree`` OUTPUT: an iterator @@ -1653,11 +1716,11 @@ def monics( self, of_degree=None, max_degree=None ): INPUT: Pass exactly one of: - - ``max_degree`` - an int; the iterator will generate + - ``max_degree`` -- an int; the iterator will generate all monic polynomials which have degree less than or equal to ``max_degree`` - - ``of_degree`` - an int; the iterator will generate + - ``of_degree`` -- an int; the iterator will generate all monic polynomials which have degree ``of_degree`` @@ -1734,7 +1797,7 @@ def quotient_by_principal_ideal(self, f, names=None, **kwds): INPUT: - - ``f`` - either a polynomial in ``self``, or a principal + - ``f`` -- either a polynomial in ``self``, or a principal ideal of ``self``. - further named arguments that are passed to the quotient constructor. @@ -1884,7 +1947,8 @@ def __init__(self, base_ring, name="x", sparse=False, implementation=None, @cached_method(key=lambda self, d, q, sign, lead: (d, q, sign, tuple([x if isinstance(x, (tuple, list)) else (x, 0) for x in lead]) if isinstance(lead, (tuple, list)) else ((lead, 0)))) def weil_polynomials(self, d, q, sign=1, lead=1): r""" - Return all integer polynomials whose complex roots all have a specified absolute value. + Return all integer polynomials whose complex roots all have a specified + absolute value. Such polynomials `f` satisfy a functional equation @@ -1892,29 +1956,34 @@ def weil_polynomials(self, d, q, sign=1, lead=1): T^d f(q/T) = s q^{d/2} f(T) - where `d` is the degree of `f`, `s` is a sign and `q^{1/2}` is the absolute value - of the roots of `f`. + where `d` is the degree of `f`, `s` is a sign and `q^{1/2}` is the + absolute value of the roots of `f`. INPUT: - ``d`` -- integer, the degree of the polynomials - - ``q`` -- integer, the square of the complex absolute value of the roots + - ``q`` -- integer, the square of the complex absolute value of the + roots - - ``sign`` -- integer (default `1`), the sign `s` of the functional equation + - ``sign`` -- integer (default `1`), the sign `s` of the functional + equation - - ``lead`` -- integer, list of integers or list of pairs of integers (default `1`), - constraints on the leading few coefficients of the generated polynomials. - If pairs `(a, b)` of integers are given, they are treated as a constraint - of the form `\equiv a \pmod{b}`; the moduli must be in decreasing order by - divisibility, and the modulus of the leading coefficient must be 0. + - ``lead`` -- integer, list of integers or list of pairs of integers + (default `1`), constraints on the leading few coefficients of the + generated polynomials. If pairs `(a, b)` of integers are given, they + are treated as a constraint of the form `\equiv a \pmod{b}`; the + moduli must be in decreasing order by divisibility, and the modulus + of the leading coefficient must be 0. .. SEEALSO:: - More documentation and additional options are available using the iterator + More documentation and additional options are available using the + iterator :class:`sage.rings.polynomial.weil.weil_polynomials.WeilPolynomials` - directly. In addition, polynomials have a method :meth:`is_weil_polynomial` to - test whether or not the given polynomial is a Weil polynomial. + directly. In addition, polynomials have a method + :meth:`is_weil_polynomial` to test whether or not the given + polynomial is a Weil polynomial. EXAMPLES:: @@ -1949,7 +2018,8 @@ def weil_polynomials(self, d, q, sign=1, lead=1): TESTS: - We check that products of Weil polynomials are also listed as Weil polynomials:: + We check that products of Weil polynomials are also listed as Weil + polynomials:: sage: all((f * g) in R.weil_polynomials(6, q) for q in [3, 4] # needs sage.libs.flint ....: for f in R.weil_polynomials(2, q) for g in R.weil_polynomials(4, q)) @@ -1964,13 +2034,15 @@ def weil_polynomials(self, d, q, sign=1, lead=1): ....: for j in range(1, (3+i)//2 + 1)) ....: for i in range(4)]) for f in simples] - Check that every polynomial in this list has 3 real roots between `-2 \sqrt{3}` and `2 \sqrt{3}`:: + Check that every polynomial in this list has 3 real roots between `-2 + \sqrt{3}` and `2 \sqrt{3}`:: sage: roots = [f.roots(RR, multiplicities=False) for f in reals] # needs sage.libs.flint sage: all(len(L) == 3 and all(x^2 <= 12 for x in L) for L in roots) # needs sage.libs.flint True - Finally, check that the original polynomials are reconstructed as CM polynomials:: + Finally, check that the original polynomials are reconstructed as CM + polynomials:: sage: all(f == T^3*r(T + 3/T) for (f, r) in zip(simples, reals)) # needs sage.libs.flint True @@ -2126,7 +2198,8 @@ def _element_class(): def _ideal_class_(self, n=0): """ - Returns the class representing ideals in univariate polynomial rings over fields. + Returns the class representing ideals in univariate polynomial rings + over fields. EXAMPLES:: diff --git a/src/sage/rings/tests.py b/src/sage/rings/tests.py index 011cfc99070..2e20a08be10 100644 --- a/src/sage/rings/tests.py +++ b/src/sage/rings/tests.py @@ -428,16 +428,16 @@ def test_karatsuba_multiplication(base_ring, maxdeg1, maxdeg2, sage: from sage.rings.tests import test_karatsuba_multiplication sage: test_karatsuba_multiplication(ZZ, 6, 5, verbose=True, seed=42) test_karatsuba_multiplication: ring=Univariate Polynomial Ring in x over Integer Ring, threshold=2 - (2*x^6 - x^5 - x^4 - 3*x^3 + 4*x^2 + 4*x + 1)*(4*x^4 + x^3 - 2*x^2 - 20*x + 3) - (16*x^2)*(-41*x + 1) - (x^6 + 2*x^5 + 8*x^4 - x^3 + x^2 + x)*(-x^2 - 4*x + 3) - (-x^3 - x - 8)*(-1) - (x - 1)*(-x^5 + 3*x^4 - x^3 + 2*x + 1) - (x^3 + x^2 + x + 1)*(4*x^3 + 76*x^2 - x - 1) - (x^6 - 5*x^4 - x^3 + 6*x^2 + 1)*(5*x^2 - x + 4) - (3*x - 2)*(x - 1) - (21)*(14*x^5 - x^2 + 4*x + 1) - (12*x^5 - 12*x^2 + 2*x + 1)*(26*x^4 + x^3 + 1) + (x^6 + 4*x^5 + 4*x^4 - 3*x^3 - x^2 - x)*(2*x^4 + 3*x^3 - 20*x^2 - 2*x + 1) + (4*x^5 + 16*x^2 + x - 41)*(x^2 + x - 1) + (8*x^2 + 2*x + 1)*(3) + (-4*x - 1)*(-8*x^2 - x) + (-x^6 - x^3 - x^2 + x + 1)*(2*x^3 - x + 3) + (-x^2 + x + 1)*(x^4 + x^3 - x^2 - x + 76) + (4*x^3 + x^2 + 6)*(-x^2 - 5*x) + (x + 4)*(-x + 5) + (-2*x)*(3*x^2 - x) + (x^6 + 21*x^5 + x^4 + 4*x^3 - x^2)*(14*x^4 + x^3 + 2*x^2 - 12*x) Test Karatsuba multiplication of polynomials of small degree over some common rings:: @@ -474,9 +474,9 @@ def test_karatsuba_multiplication(base_ring, maxdeg1, maxdeg2, R = PolynomialRing(base_ring, 'x') if verbose: print("test_karatsuba_multiplication: ring={}, threshold={}".format(R, threshold)) - for i in range(numtests): - f = R.random_element(randint(0, maxdeg1), *base_ring_random_elt_args) - g = R.random_element(randint(0, maxdeg2), *base_ring_random_elt_args) + for _ in range(numtests): + f = R.random_element(randint(0, maxdeg1), False, *base_ring_random_elt_args) + g = R.random_element(randint(0, maxdeg2), False, *base_ring_random_elt_args) if verbose: print(" ({})*({})".format(f, g)) if ref_mul(f, g) - f._mul_karatsuba(g, threshold) != 0: diff --git a/src/sage/schemes/curves/point.py b/src/sage/schemes/curves/point.py index dcc78724497..373dc6ce19f 100644 --- a/src/sage/schemes/curves/point.py +++ b/src/sage/schemes/curves/point.py @@ -451,7 +451,7 @@ class IntegralAffineCurvePoint_finite_field(IntegralAffineCurvePoint): class IntegralAffinePlaneCurvePoint(IntegralAffineCurvePoint, AffinePlaneCurvePoint_field): """ - Point of an integral affine plane curve over a finite field. + Point of an integral affine plane curve. """ pass diff --git a/src/sage/schemes/generic/glue.py b/src/sage/schemes/generic/glue.py index 76bd9a1ab9e..e70aafa0507 100644 --- a/src/sage/schemes/generic/glue.py +++ b/src/sage/schemes/generic/glue.py @@ -16,19 +16,35 @@ class GluedScheme(scheme.Scheme): INPUT: - - ``f`` - open immersion from a scheme U to a scheme - X + - ``f`` -- open immersion from a scheme `U` to a scheme + `X` - - ``g`` - open immersion from U to a scheme Y + - ``g`` -- open immersion from `U` to a scheme `Y` - OUTPUT: The scheme obtained by gluing X and Y along the open set - U. + OUTPUT: The scheme obtained by gluing `X` and `Y` along the open set + `U`. - .. note:: + .. NOTE:: Checking that `f` and `g` are open immersions is not implemented. + + EXAMPLES:: + + sage: R. = QQ[] + sage: S. = R.quotient(x * y - 1) + sage: Rx = QQ["x"] + sage: Ry = QQ["y"] + sage: phi_x = Rx.hom([xbar]) + sage: phi_y = Ry.hom([ybar]) + sage: Sx = Schemes()(phi_x) + sage: Sy = Schemes()(phi_y) + sage: Sx.glue_along_domains(Sy) + Scheme obtained by gluing X and Y along U, where + X: Spectrum of Univariate Polynomial Ring in x over Rational Field + Y: Spectrum of Univariate Polynomial Ring in y over Rational Field + U: Spectrum of Quotient of Multivariate Polynomial Ring in x, y over Rational Field by the ideal (x*y - 1) """ def __init__(self, f, g, check=True): if check: @@ -42,6 +58,23 @@ def __init__(self, f, g, check=True): self.__g = g def gluing_maps(self): + r""" + Return the gluing maps of this glued scheme, i.e. the maps `f` and `g`. + + EXAMPLES:: + + sage: R. = QQ[] + sage: S. = R.quotient(x * y - 1) + sage: Rx = QQ["x"] + sage: Ry = QQ["y"] + sage: phi_x = Rx.hom([xbar]) + sage: phi_y = Ry.hom([ybar]) + sage: Sx = Schemes()(phi_x) + sage: Sy = Schemes()(phi_y) + sage: Sxy = Sx.glue_along_domains(Sy) + sage: Sxy.gluing_maps() == (Sx, Sy) + True + """ return self.__f, self.__g def _repr_(self): diff --git a/src/sage/schemes/generic/homset.py b/src/sage/schemes/generic/homset.py index a9a0f0735df..6fca9c1e257 100644 --- a/src/sage/schemes/generic/homset.py +++ b/src/sage/schemes/generic/homset.py @@ -2,13 +2,13 @@ Set of homomorphisms between two schemes For schemes `X` and `Y`, this module implements the set of morphisms -`Hom(X,Y)`. This is done by :class:`SchemeHomset_generic`. +`\mathrm{Hom}(X,Y)`. This is done by :class:`SchemeHomset_generic`. -As a special case, the Hom-sets can also represent the points of a -scheme. Recall that the `K`-rational points of a scheme `X` over `k` -can be identified with the set of morphisms `Spec(K) \to X`. In Sage -the rational points are implemented by such scheme morphisms. This is -done by :class:`SchemeHomset_points` and its subclasses. +As a special case, the Hom-sets can also represent the points of a scheme. +Recall that the `K`-rational points of a scheme `X` over `k` can be identified +with the set of morphisms `\mathrm{Spec}(K) \to X`. In Sage the rational points +are implemented by such scheme morphisms. This is done by +:class:`SchemeHomset_points` and its subclasses. .. note:: @@ -407,12 +407,12 @@ def _element_constructor_(self, x, check=True): # ******************************************************************* class SchemeHomset_points(SchemeHomset_generic): - """ + r""" Set of rational points of the scheme. - Recall that the `K`-rational points of a scheme `X` over `k` can - be identified with the set of morphisms `Spec(K) \to X`. In Sage, - the rational points are implemented by such scheme morphisms. + Recall that the `K`-rational points of a scheme `X` over `k` can be + identified with the set of morphisms `\mathrm{Spec}(K) \to X`. In Sage, the + rational points are implemented by such scheme morphisms. If a scheme has a finite number of points, then the homset is supposed to implement the Python iterator interface. See @@ -656,16 +656,16 @@ def _element_constructor_(self, *v, **kwds): """ if len(v) == 1: v = v[0] - return self.codomain()._point(self, v, **kwds) + return self.extended_codomain()._point(self, v, **kwds) def extended_codomain(self): - """ + r""" Return the codomain with extended base, if necessary. OUTPUT: The codomain scheme, with its base ring extended to the - codomain. That is, the codomain is of the form `Spec(R)` and + codomain. That is, the codomain is of the form `\mathrm{Spec}(R)` and the base ring of the domain is extended to `R`. EXAMPLES:: @@ -693,6 +693,12 @@ def extended_codomain(self): self._extended_codomain = X return X + def zero(self): + """ + Return the identity of the codomain with extended base, if necessary. + """ + return self.extended_codomain().zero() + def _repr_(self): """ Return a string representation of ``self``. @@ -710,8 +716,8 @@ def _repr_(self): return 'Set of rational points of '+str(self.extended_codomain()) def value_ring(self): - """ - Return `R` for a point Hom-set `X(Spec(R))`. + r""" + Return `R` for a point Hom-set `X(\mathrm{Spec}(R))`. OUTPUT: diff --git a/src/sage/schemes/generic/morphism.py b/src/sage/schemes/generic/morphism.py index 31dc4010f5b..7d91214e32a 100644 --- a/src/sage/schemes/generic/morphism.py +++ b/src/sage/schemes/generic/morphism.py @@ -28,12 +28,12 @@ new Hom-set class does not use ``MyScheme._morphism`` then you do not have to provide it. -Note that points on schemes are morphisms `Spec(K)\to X`, too. But we -typically use a different notation, so they are implemented in a -different derived class. For this, you should implement a method +Note that points on schemes are morphisms `\mathrm{Spec}(K)\to X`, too. But we +typically use a different notation, so they are implemented in a different +derived class. For this, you should implement a method -* ``MyScheme._point(*args, **kwds)`` returning a point, that is, - a morphism `Spec(K)\to X`. Your point class should derive from +* ``MyScheme._point(*args, **kwds)`` returning a point, that is, a morphism + `\mathrm{Spec}(K)\to X`. Your point class should derive from :class:`SchemeMorphism_point`. Optionally, you can also provide a special Hom-set for the points, for @@ -1790,11 +1790,11 @@ def __init__(self, X): ############################################################################ class SchemeMorphism_point(SchemeMorphism): - """ + r""" Base class for rational points on schemes. Recall that the `K`-rational points of a scheme `X` over `k` can - be identified with the set of morphisms `Spec(K) \to X`. In Sage, + be identified with the set of morphisms `\mathrm{Spec}(K) \to X`. In Sage, the rational points are implemented by such scheme morphisms. EXAMPLES:: diff --git a/src/sage/schemes/generic/point.py b/src/sage/schemes/generic/point.py index 5f8f3d024a6..7ef85645f56 100644 --- a/src/sage/schemes/generic/point.py +++ b/src/sage/schemes/generic/point.py @@ -227,28 +227,3 @@ def _richcmp_(self, other, op): False """ return richcmp(self.__P, other.__P, op) - -######################################################## -# Points on a scheme defined by a morphism -######################################################## - -def is_SchemeRationalPoint(x): - return isinstance(x, SchemeRationalPoint) - -class SchemeRationalPoint(SchemePoint): - def __init__(self, f): - """ - INPUT: - - - - ``f`` - a morphism of schemes - """ - SchemePoint.__init__(self, f.codomain(), parent=f.parent()) - self.__f = f - - def _repr_(self): - return "Point on %s defined by the morphism %s" % (self.scheme(), - self.morphism()) - - def morphism(self): - return self.__f diff --git a/src/sage/schemes/generic/scheme.py b/src/sage/schemes/generic/scheme.py index 1c66ce9bb33..2c0e3100c86 100644 --- a/src/sage/schemes/generic/scheme.py +++ b/src/sage/schemes/generic/scheme.py @@ -77,7 +77,7 @@ class Scheme(Parent): sage: ProjectiveSpace(4, QQ).category() Category of schemes over Rational Field - There is a special and unique `Spec(\ZZ)` that is the default base + There is a special and unique `\mathrm{Spec}(\ZZ)` that is the default base scheme:: sage: Spec(ZZ).base_scheme() is Spec(QQ).base_scheme() @@ -267,7 +267,7 @@ def __call__(self, *args): @cached_method def point_homset(self, S=None): - """ + r""" Return the set of S-valued points of this scheme. INPUT: @@ -276,7 +276,7 @@ def point_homset(self, S=None): OUTPUT: - The set of morphisms `Spec(S)\to X`. + The set of morphisms `\mathrm{Spec}(S) \to X`. EXAMPLES:: diff --git a/src/sage/schemes/projective/projective_morphism.py b/src/sage/schemes/projective/projective_morphism.py index cefebbe5ff4..d741c936439 100644 --- a/src/sage/schemes/projective/projective_morphism.py +++ b/src/sage/schemes/projective/projective_morphism.py @@ -372,7 +372,7 @@ def __call__(self, x, check=True): sage: F. = GF(4) sage: P = T(F)(1, a) sage: h(P) # needs sage.libs.singular - (a : a) + (1 : 1) sage: h(P).domain() Spectrum of Finite Field in a of size 2^2 sage: h.change_ring(F)(P) diff --git a/src/sage/sets/recursively_enumerated_set.pyx b/src/sage/sets/recursively_enumerated_set.pyx index bf2a9e9c363..e25dde0485c 100644 --- a/src/sage/sets/recursively_enumerated_set.pyx +++ b/src/sage/sets/recursively_enumerated_set.pyx @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- r""" -Recursively enumerated set +Recursively Enumerated Sets A set `S` is called recursively enumerable if there is an algorithm that enumerates the members of `S`. We consider here the recursively enumerated @@ -9,11 +9,11 @@ sets that are described by some ``seeds`` and a successor function graded, forest) or not. The elements of a set having a symmetric, graded or forest structure can be enumerated uniquely without keeping all of them in memory. Many kinds of iterators are provided in this module: depth first -search, breadth first search or elements of given depth. +search, breadth first search and elements of given depth. See :wikipedia:`Recursively_enumerable_set`. -See documentation of :func:`RecursivelyEnumeratedSet` below for the +See the documentation of :func:`RecursivelyEnumeratedSet` below for the description of the inputs. AUTHORS: @@ -22,12 +22,11 @@ AUTHORS: EXAMPLES: -No hypothesis on the structure ------------------------------- +.. RUBRIC:: No hypothesis on the structure What we mean by "no hypothesis" is that the set is not known to be a forest, symmetric, or graded. However, it may have other -structure, like not containing an oriented cycle, that does not +structure, such as not containing an oriented cycle, that does not help with the enumeration. In this example, the seed is 0 and the successor function is either ``+2`` @@ -50,8 +49,7 @@ Depth first search:: sage: [next(it) for _ in range(10)] [0, 3, 6, 9, 12, 15, 18, 21, 24, 27] -Symmetric structure -------------------- +.. RUBRIC:: Symmetric structure The origin ``(0, 0)`` as seed and the upper, lower, left and right lattice point as successor function. This function is symmetric since `p` is a @@ -86,8 +84,7 @@ Levels (elements of given depth):: sage: sorted(C.graded_component(2)) [(-2, 0), (-1, -1), (-1, 1), (0, -2), (0, 2), (1, -1), (1, 1), (2, 0)] -Graded structure ----------------- +.. RUBRIC:: Graded structure Identity permutation as seed and ``permutohedron_succ`` as successor function:: @@ -137,11 +134,10 @@ Graded components (set of elements of the same depth):: sage: sorted(R.graded_component(10)) [[5, 4, 3, 2, 1]] -Forest structure ----------------- +.. RUBRIC:: Forest structure (Example 1) The set of words over the alphabet `\{a,b\}` can be generated from the -empty word by appending letter `a` or `b` as a successor function. This set +empty word by appending the letter `a` or `b` as a successor function. This set has a forest structure:: sage: seeds = [''] @@ -162,9 +158,6 @@ Breadth first search iterator:: sage: [next(it) for _ in range(6)] ['', 'a', 'b', 'aa', 'ab', 'ba'] -Example: Forest structure -------------------------- - This example was provided by Florent Hivert. How to define a set using those classes? @@ -175,41 +168,23 @@ classes being very similar): .. MATH:: - \begin{picture}(-300,0)(600,0) - % Root - \put(0,0){\circle*{7}} - \put(0,10){\makebox(0,10){``\ ''}} - % First Children - \put(-150,-60){\makebox(0,10){``a''}} - \put(0,-60){\makebox(0,10){``b''}} - \put(150,-60){\makebox(0,10){``c''}} - \multiput(-150,-70)(150,0){3}{\circle*{7}} - % Second children - \put(-200,-130){\makebox(0,10){``aa''}} - \put(-150,-130){\makebox(0,10){``ab''}} - \put(-100,-130){\makebox(0,10){``ac''}} - \put(-50,-130){\makebox(0,10){``ba''}} - \put(0,-130){\makebox(0,10){``bb''}} - \put(50,-130){\makebox(0,10){``bc''}} - \put(100,-130){\makebox(0,10){``ca''}} - \put(150,-130){\makebox(0,10){``cb''}} - \put(200,-130){\makebox(0,10){``cc''}} - \multiput(-200,-140)(50,0){9}{\circle*{7}} - % Legend - \put(100,-5){\makebox(0,10)[l]{1) An initial element}} - \put(-250,-5){\makebox(0,10)[l]{2) A function of an element enumerating}} - \put(-235,-20){\makebox(0,10)[l]{its children (if any)}} - % Arrows - \thicklines - \put(0,-10){\vector(0,-1){30}} - \put(-15,-5){\vector(-2,-1){110}} - \put(15,-5){\vector(2,-1){110}} - \multiput(-150,-80)(150,0){3}{\vector(0,-1){30}} - \multiput(-160,-80)(150,0){3}{\vector(-1,-1){30}} - \multiput(-140,-80)(150,0){3}{\vector(1,-1){30}} - \put(90,0){\vector(-1,0){70}} - \put(-215,-30){\vector(1,-1){40}} - \end{picture} + \begin{array}{ccc} + & \emptyset \\ + \hfil\swarrow & \downarrow & \searrow\hfil\\ + a & b & c \\ + \begin{array}{ccc} + \swarrow & \downarrow & \searrow \\ + aa & ab & ac \\ + \end{array} & + \begin{array}{ccc} + \swarrow & \downarrow & \searrow \\ + ba & bb & bc \\ + \end{array} & + \begin{array}{ccc} + \swarrow & \downarrow & \searrow \\ + ca & cb & cc \\ + \end{array} + \end{array} For the previous example, the two necessary pieces of information are: @@ -219,7 +194,7 @@ For the previous example, the two necessary pieces of information are: lambda x: [x + letter for letter in ['a', 'b', 'c'] -This would actually describe an **infinite** set, as such rules describes +This would actually describe an **infinite** set, as such rules describe "all words" on 3 letters. Hence, it is a good idea to replace the function by:: lambda x: [x + letter for letter in ['a', 'b', 'c']] if len(x) < 2 else [] @@ -249,14 +224,13 @@ or:: sage: S.list() ['', 'a', 'aa', 'ab', 'ac', 'b', 'ba', 'bb', 'bc', 'c', 'ca', 'cb', 'cc'] -Example: Forest structure 2 ---------------------------- +.. RUBRIC:: Forest structure (Example 2) This example was provided by Florent Hivert. Here is a little more involved example. We want to iterate through all permutations of a given set `S`. One solution is to take elements of `S` one -by one an insert them at every positions. So a node of the generating tree +by one and insert them at every position. So a node of the generating tree contains two pieces of information: - the list ``lst`` of already inserted element; @@ -320,7 +294,7 @@ def RecursivelyEnumeratedSet(seeds, successors, structure=None, A set `S` is called recursively enumerable if there is an algorithm that enumerates the members of `S`. We consider here the recursively - enumerated set that are described by some ``seeds`` and a successor + enumerated sets that are described by some ``seeds`` and a successor function ``successors``. Let `U` be a set and ``successors`` `:U \to 2^U` be a successor function @@ -406,7 +380,7 @@ def RecursivelyEnumeratedSet(seeds, successors, structure=None, .. WARNING:: - If you do not set the good structure, you might obtain bad results, + If you do not set a good structure, you might obtain bad results, like elements generated twice:: sage: f = lambda a: [a-1,a+1] @@ -781,8 +755,8 @@ cdef class RecursivelyEnumeratedSet_generic(Parent): r""" Iterate over the elements of ``self`` of given depth. - An element of depth `n` can be obtained applying `n` times the - successor function to a seed. + An element of depth `n` can be obtained by applying the + successor function `n` times to a seed. INPUT: @@ -847,7 +821,7 @@ cdef class RecursivelyEnumeratedSet_generic(Parent): r""" Iterate on the elements of ``self`` (breadth first). - This code remembers every elements generated and uses python + This code remembers every element generated and uses python queues. It is 3 times slower than the other one. See :wikipedia:`Breadth-first_search`. @@ -876,7 +850,7 @@ cdef class RecursivelyEnumeratedSet_generic(Parent): r""" Iterate on the elements of ``self`` (in no particular order). - This code remembers every elements generated. + This code remembers every element generated. TESTS: @@ -905,7 +879,7 @@ cdef class RecursivelyEnumeratedSet_generic(Parent): r""" Iterate on the elements of ``self`` (depth first). - This code remembers every elements generated. + This code remembers every element generated. The elements are traversed right-to-left, so the last element returned by the successor function is visited first. @@ -1552,7 +1526,7 @@ def search_forest_iterator(roots, children, algorithm='depth'): [0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1]] - This allows for iterating trough trees of infinite depth:: + This allows for iterating through trees of infinite depth:: sage: it = search_forest_iterator([[]], lambda l: [l+[0], l+[1]], algorithm='breadth') sage: [ next(it) for i in range(16) ] @@ -1574,9 +1548,9 @@ def search_forest_iterator(roots, children, algorithm='depth'): [0, 1, 2], [0, 2, 1], [1, 0, 2], [1, 2, 0], [2, 0, 1], [2, 1, 0]] """ # Little trick: the same implementation handles both depth and - # breadth first search. Setting position to -1 makes a depth search + # breadth first search. Setting position to -1 results in a depth search # (you ask the children for the last node you met). Setting - # position on 0 makes a breadth search (enumerate all the + # position on 0 results in a breadth search (enumerate all the # descendants of a node before going on to the next father) if algorithm == 'depth': position = -1 @@ -1920,8 +1894,8 @@ class RecursivelyEnumeratedSet_forest(Parent): def _elements_of_depth_iterator_rec(self, depth=0): r""" Return an iterator over the elements of ``self`` of given depth. - An element of depth `n` can be obtained applying `n` times the - children function from a root. This function is not affected + An element of depth `n` can be obtained by applying the + children function `n` times from a root. This function is not affected by post processing. EXAMPLES:: @@ -1951,8 +1925,8 @@ class RecursivelyEnumeratedSet_forest(Parent): def elements_of_depth_iterator(self, depth=0): r""" Return an iterator over the elements of ``self`` of given depth. - An element of depth `n` can be obtained applying `n` times the - children function from a root. + An element of depth `n` can be obtained by applying the + children function `n` times from a root. EXAMPLES:: @@ -2010,7 +1984,7 @@ class RecursivelyEnumeratedSet_forest(Parent): depth first search and breadth first search failed. The following example enumerates all ordered pairs of nonnegative integers, starting from an infinite set of roots, where each - roots has an infinite number of children:: + root has an infinite number of children:: sage: from sage.sets.recursively_enumerated_set import RecursivelyEnumeratedSet_forest sage: S = RecursivelyEnumeratedSet_forest(Family(NN, lambda x : (x, 0)), diff --git a/src/sage/stats/all.py b/src/sage/stats/all.py index 69fb8f01abd..3b4c8c4ff52 100644 --- a/src/sage/stats/all.py +++ b/src/sage/stats/all.py @@ -1,3 +1,4 @@ +import sage.stats.distributions.catalog as distributions from .r import ttest from .basic_stats import (mean, mode, std, variance, median, moving_average) diff --git a/src/sage/stats/distributions/all.py b/src/sage/stats/distributions/all.py index e69de29bb2d..d37a8563ec6 100644 --- a/src/sage/stats/distributions/all.py +++ b/src/sage/stats/distributions/all.py @@ -0,0 +1,6 @@ +# We lazy_import the following modules since they import numpy which +# slows down sage startup +from sage.misc.lazy_import import lazy_import +lazy_import("sage.stats.distributions.discrete_gaussian_integer", ["DiscreteGaussianDistributionIntegerSampler"]) +lazy_import("sage.stats.distributions.discrete_gaussian_lattice", ["DiscreteGaussianDistributionLatticeSampler"]) +lazy_import("sage.stats.distributions.discrete_gaussian_polynomial", ["DiscreteGaussianDistributionPolynomialSampler"]) diff --git a/src/sage/stats/distributions/catalog.py b/src/sage/stats/distributions/catalog.py new file mode 100644 index 00000000000..5f252494e13 --- /dev/null +++ b/src/sage/stats/distributions/catalog.py @@ -0,0 +1,34 @@ +r""" +Index of distributions + +This catalogue includes the samplers for statistical distributions listed below. + +Let ```` indicate pressing the :kbd:`Tab` key. So begin by typing +``algebras.`` to the see the currently implemented named algebras. + +- :class:`distributions.discrete_gaussian_integer.DiscreteGaussianDistributionIntegerSampler + ` +- :class:`distributions.discrete_gaussian_lattice.DiscreteGaussianDistributionLatticeSampler + ` +- :class:`distributions.discrete_gaussian_polynomial.DiscreteGaussianDistributionPolynomialSampler + ` + +To import these names into the global namespace, use:: + + sage: from sage.stats.distributions.catalog import * + +""" +#***************************************************************************** +# Copyright (C) 2024 Gareth Ma +# +# Distributed under the terms of the GNU General Public License (GPL), +# version 2 or later (at your preference). +# +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.misc.lazy_import import lazy_import +lazy_import("sage.stats.distributions.discrete_gaussian_integer", ["DiscreteGaussianDistributionIntegerSampler"]) +lazy_import("sage.stats.distributions.discrete_gaussian_lattice", ["DiscreteGaussianDistributionLatticeSampler"]) +lazy_import("sage.stats.distributions.discrete_gaussian_polynomial", ["DiscreteGaussianDistributionPolynomialSampler"]) +del lazy_import diff --git a/src/sage/stats/distributions/discrete_gaussian_lattice.py b/src/sage/stats/distributions/discrete_gaussian_lattice.py index 08790bf92be..6667b2fb5cf 100644 --- a/src/sage/stats/distributions/discrete_gaussian_lattice.py +++ b/src/sage/stats/distributions/discrete_gaussian_lattice.py @@ -3,28 +3,32 @@ Discrete Gaussian Samplers over Lattices This file implements oracles which return samples from a lattice following a -discrete Gaussian distribution. That is, if `σ` is big enough relative to the -provided basis, then vectors are returned with a probability proportional to -`\exp(-|x-c|_2^2/(2σ^2))`. More precisely lattice vectors in `x ∈ Λ` are -returned with probability: +discrete Gaussian distribution. That is, if `\sigma` is big enough relative to +the provided basis, then vectors are returned with a probability proportional +to `\exp(-|x - c|_2^2 / (2\sigma^2))`. More precisely lattice vectors in `x \in +\Lambda` are returned with probability: - `\exp(-|x-c|_2^2/(2σ²))/(∑_{x ∈ Λ} \exp(-|x|_2^2/(2σ²)))` +.. MATH:: + + \frac{\exp(-|x - c|_2^2 / (2\sigma^2))}{\sum_{v \in \Lambda} \exp(-|v|_2^2 / + (2\sigma^2))}. AUTHORS: - Martin Albrecht (2014-06-28): initial version +- Gareth Ma (2023-09-22): implement non-spherical sampling + EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler - sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^10, 3.0) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^10, 3.0) sage: D(), D(), D() # random ((3, 0, -5, 0, -1, -3, 3, 3, -7, 2), (4, 0, 1, -2, -4, -4, 4, 0, 1, -4), (-3, 0, 4, 5, 0, 1, 3, 2, 0, -1)) sage: a = D() sage: a.parent() Ambient free module of rank 10 over the principal ideal domain Integer Ring """ -#****************************************************************************** +# ****************************************************************************** # # DGS - Discrete Gaussian Samplers # @@ -54,17 +58,20 @@ # The views and conclusions contained in the software and documentation are # those of the authors and should not be interpreted as representing official # policies, either expressed or implied, of the FreeBSD Project. -#*****************************************************************************/ +# *****************************************************************************/ from sage.functions.log import exp -from sage.functions.other import ceil from sage.rings.real_mpfr import RealField -from sage.rings.real_mpfr import RR from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ -from .discrete_gaussian_integer import DiscreteGaussianDistributionIntegerSampler +from sage.stats.distributions.discrete_gaussian_integer import DiscreteGaussianDistributionIntegerSampler from sage.structure.sage_object import SageObject -from sage.matrix.constructor import matrix, identity_matrix +from sage.misc.cachefunc import cached_method +from sage.misc.functional import sqrt +from sage.misc.prandom import normalvariate +from sage.misc.verbose import verbose +from sage.symbolic.constants import pi +from sage.matrix.constructor import matrix from sage.modules.free_module import FreeModule from sage.modules.free_module_element import vector @@ -90,9 +97,9 @@ def _iter_vectors(n, lower, upper, step=None): """ if step is None: if ZZ(lower) >= ZZ(upper): - raise ValueError("Expected lower < upper, but got %d >= %d" % (lower, upper)) + raise ValueError("expected lower < upper, but got %d >= %d" % (lower, upper)) if ZZ(n) <= 0: - raise ValueError("Expected n>0 but got %d <= 0" % n) + raise ValueError("expected n>0 but got %d <= 0" % n) step = n assert step > 0 @@ -115,9 +122,8 @@ class DiscreteGaussianDistributionLatticeSampler(SageObject): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler - sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^10, 3.0); D - Discrete Gaussian sampler with σ = 3.000000, c=(0, 0, 0, 0, 0, 0, 0, 0, 0, 0) over lattice with basis + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^10, 3.0); D + Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(0, 0, 0, 0, 0, 0, 0, 0, 0, 0) over lattice with basis [1 0 0 0 0 0 0 0 0 0] [0 1 0 0 0 0 0 0 0 0] @@ -133,10 +139,9 @@ class DiscreteGaussianDistributionLatticeSampler(SageObject): We plot a histogram:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler sage: import warnings sage: warnings.simplefilter('ignore', UserWarning) - sage: D = DiscreteGaussianDistributionLatticeSampler(identity_matrix(2), 3.0) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(identity_matrix(2), 3.0) sage: S = [D() for _ in range(2^12)] sage: l = [vector(v.list() + [S.count(v)]) for v in set(S)] sage: list_plot3d(l, point_list=True, interpolation='nn') # needs sage.plot @@ -156,24 +161,24 @@ def compute_precision(precision, sigma): INPUT: - - ``precision`` - an integer `> 53` nor ``None``. + - ``precision`` - an integer `>= 53` nor ``None``. - ``sigma`` - if ``precision`` is ``None`` then the precision of ``sigma`` is used. EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler - sage: DiscreteGaussianDistributionLatticeSampler.compute_precision(100, RR(3)) + sage: DGL = distributions.DiscreteGaussianDistributionLatticeSampler + sage: DGL.compute_precision(100, RR(3)) 100 - sage: DiscreteGaussianDistributionLatticeSampler.compute_precision(100, RealField(200)(3)) + sage: DGL.compute_precision(100, RealField(200)(3)) 100 - sage: DiscreteGaussianDistributionLatticeSampler.compute_precision(100, 3) + sage: DGL.compute_precision(100, 3) 100 - sage: DiscreteGaussianDistributionLatticeSampler.compute_precision(None, RR(3)) + sage: DGL.compute_precision(None, RR(3)) 53 - sage: DiscreteGaussianDistributionLatticeSampler.compute_precision(None, RealField(200)(3)) + sage: DGL.compute_precision(None, RealField(200)(3)) 200 - sage: DiscreteGaussianDistributionLatticeSampler.compute_precision(None, 3) + sage: DGL.compute_precision(None, 3) 53 """ @@ -185,28 +190,29 @@ def compute_precision(precision, sigma): precision = max(53, precision) return precision - def _normalisation_factor_zz(self, tau=3): + def _normalisation_factor_zz(self, tau=None, prec=None): r""" - This function returns an approximation of `∑_{x ∈ \ZZ^n} - \exp(-|x|_2^2/(2σ²))`, i.e. the normalisation factor such that the sum - over all probabilities is 1 for `\ZZⁿ`. + This function returns an approximation of `\sum_{x \in B} + \exp(-|x|_2^2 / (2\sigma^2))`, i.e. the normalization factor such that the sum + over all probabilities is 1 for `B`, via Poisson summation. - If this ``self.B`` is not an identity matrix over `\ZZ` a - :class:`NotImplementedError` is raised. INPUT: - - ``tau`` -- all vectors `v` with `|v|_∞ ≤ τ·σ` are enumerated - (default: ``3``). + - ``tau`` -- (default: ``None``) all vectors `v` with `|v|_2^2 \leq + \tau \sigma` are enumerated; if none is provided, enumerate vectors + with increasing norm until the sum converges to given precision. For + high dimension lattice, this is recommended. + + - ``prec`` -- (default: ``None``) passed to :meth:`compute_precision` EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler sage: n = 3; sigma = 1.0 - sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^n, sigma) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^n, sigma) sage: f = D.f - sage: c = D._normalisation_factor_zz(); c # needs sage.symbolic - 15.528... + sage: nf = D._normalisation_factor_zz(); nf + 15.7496... sage: from collections import defaultdict sage: counter = defaultdict(Integer) @@ -222,7 +228,7 @@ def _normalisation_factor_zz(self, tau=3): sage: while v not in counter: ....: add_samples(1000) - sage: while abs(m*f(v)*1.0/c/counter[v] - 1.0) >= 0.1: # needs sage.symbolic + sage: while abs(m*f(v)*1.0/nf/counter[v] - 1.0) >= 0.1: ....: add_samples(1000) sage: v = vector(ZZ, n, (-1, 2, 3)) @@ -230,43 +236,209 @@ def _normalisation_factor_zz(self, tau=3): sage: while v not in counter: ....: add_samples(1000) - sage: while abs(m*f(v)*1.0/c/counter[v] - 1.0) >= 0.2: # long time, needs sage.symbolic + sage: while abs(m*f(v)*1.0/nf/counter[v] - 1.0) >= 0.2: # long time ....: add_samples(1000) + + sage: DGL = distributions.DiscreteGaussianDistributionLatticeSampler + sage: D = DGL(ZZ^8, 0.5) + sage: D._normalisation_factor_zz(tau=3) + 3.1653... + sage: D._normalisation_factor_zz() + 6.8249... + sage: D = DGL(ZZ^8, 1000) + sage: round(D._normalisation_factor_zz(prec=100)) + 1558545456544038969634991553 + + sage: M = Matrix(ZZ, [[1, 3, 0], [-2, 5, 1], [3, -4, 2]]) + sage: D = DGL(M, 1.7) + sage: D._normalisation_factor_zz() # long time + 7247.1975... + + sage: M = Matrix(ZZ, [[1, 3, 0], [-2, 5, 1]]) + sage: D = DGL(M, 3) + sage: D._normalisation_factor_zz() + Traceback (most recent call last): + ... + NotImplementedError: basis must be a square matrix for now + + sage: D = DGL(ZZ^3, c=(1/2, 0, 0)) + sage: D._normalisation_factor_zz() + Traceback (most recent call last): + ... + NotImplementedError: lattice must contain 0 for now + + sage: D = DGL(Matrix(3, 3, 1/2)) + sage: D._normalisation_factor_zz() + Traceback (most recent call last): + ... + NotImplementedError: lattice must be integral for now """ - if self.B != identity_matrix(ZZ, self.B.nrows()): - raise NotImplementedError("This function is only implemented when B is an identity matrix.") + # If σ > 1: + # We use the Fourier transform g(t) of f(x) = exp(-k^2 / 2σ^2), but + # taking the norm of vector t^2 as input, and with norm_factor factored. + # If σ ≤ 1: + # The formula in docstring converges quickly since it has -1 / σ^2 in + # the exponent + def f_or_hat(x): + # Fun fact: If you remove this R() and delay the call to return, + # It might give an error due to precision error. For example, + # RR(1 + 100 * exp(-5.0 * pi^2)) == 0 + + if sigma > 1: + return R(exp(-pi**2 * (2 * sigma**2) * x)) + + return R(exp(-x / (2 * sigma**2))) + + if not self.is_spherical: + # TODO: This is only a poor approximation placeholder. + # It should be easy to implement, since the Fourier transform + # is essentially the same, but I can't figure out how to + # tweak the `.qfrep` call below correctly. + from warnings import warn + warn("Note: `_normalisation_factor_zz` has not been properly " + "implemented for non-spherical distributions.") + import itertools + from sage.functions.log import log + basis = self.B.LLL() + base = vector(ZZ, [v.round() for v in basis.solve_left(self._c)]) + BOUND = max(1, (self._RR(log(10**4, self.n)).ceil() - 1) // 2) + if BOUND > 10: + BOUND = 10 + coords = itertools.product(range(-BOUND, BOUND + 1), repeat=self.n) + return sum(self.f((vector(u) + base) * self.B) for u in coords) + + if self.B.nrows() != self.B.ncols(): + raise NotImplementedError("basis must be a square matrix for now") + + if self.is_spherical and not self._c_in_lattice: + raise NotImplementedError("lattice must contain 0 for now") + + if self.B.base_ring() != ZZ: + raise NotImplementedError("lattice must be integral for now") - f = self.f - n = self.B.ncols() sigma = self._sigma - return sum(f(x) for x in _iter_vectors(n, -ceil(tau * sigma), - ceil(tau * sigma))) + prec = DiscreteGaussianDistributionLatticeSampler.compute_precision( + prec, sigma + ) + R = RealField(prec=prec) + if sigma > 1: + det = self.B.det() + norm_factor = (sigma * sqrt(2 * pi))**self.n / det + else: + det = 1 + norm_factor = 1 + + # qfrep computes theta series of a quadratic form, which is *half* the + # generating function of number of vectors with given norm (and no 0) + Q = self.Q + if tau is not None: + freq = Q.__pari__().qfrep(tau * sigma, 0) + res = R(1) + for x, fq in enumerate(freq): + res += 2 * ZZ(fq) * f_or_hat((x + 1) / det**self.n) + return R(norm_factor * res) + + res = R(1) + bound = 0 + # There might still be precision issue but whatever + while True: + bound += 1 + cnt = ZZ(Q.__pari__().qfrep(bound, 0)[bound - 1]) + inc = 2 * cnt * f_or_hat(bound / det**self.n) + if cnt > 0 and res == res + inc: + return R(norm_factor * res) + res += inc + + @cached_method + def _maximal_r(self): + r""" + This function computes the largest value `r > 0` such that `\Sigma - r^2BB^T` + is positive definite. + + This is equivalent to finding `\lambda_1(\Sigma / Q) = 1 / \lambda_n(Q + / \Sigma)`, which is done via the Power iteration method. - def __init__(self, B, sigma=1, c=None, precision=None): + EXAMPLES:: + + sage: n = 3 + sage: Sigma = Matrix(ZZ, [[5, -2, 4], [-2, 10, -5], [4, -5, 5]]) + sage: c = vector(ZZ, [7, 2, 5]) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^n, Sigma, c) + sage: r = D._maximal_r(); r + 0.58402... + sage: e_vals = (D.sigma() - r^2 * D.Q).eigenvalues() + sage: assert all(e_val >= -1e-12 for e_val in e_vals) + """ + assert not self.is_spherical + + Q = self.Q.change_ring(self._RR) / self._sigma.change_ring(self._RR) + v = Q[0].change_ring(self._RR) + cnt = 0 + while cnt < 10000: + nv = (Q * v).normalized() + if (nv - v).norm() < 1e-12: + break + v = nv + cnt += 1 + res = (v[0] / (Q * v)[0]).sqrt() + return res + + def _randomise(self, v): r""" - Construct a discrete Gaussian sampler over the lattice `Λ(B)` + Randomly round to the latice coset `\ZZ + v` with Gaussian parameter + `r`. Used at :meth:`_call_non_spherical`. + + REFERENCES: + + - [Pei2010]_, Section 4.1 + + EXAMPLES:: + + sage: Sigma = Matrix(ZZ, [[5, -2, 4], [-2, 10, -5], [4, -5, 5]]) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^3, Sigma) + sage: all(D._randomise([0, 0, 0]).norm() <= 16 for _ in range(100)) + True + """ + return vector(ZZ, [DiscreteGaussianDistributionIntegerSampler(self.r, c=vi)() for vi in v]) + + def __init__(self, B, sigma=1, c=0, r=None, precision=None, sigma_basis=False): + r""" + Construct a discrete Gaussian sampler over the lattice `\Lambda(B)` with parameter ``sigma`` and center `c`. INPUT: - - ``B`` -- a basis for the lattice, one of the following: + - ``B`` -- a (row) basis for the lattice, one of the following: - an integer matrix, - - an object with a ``matrix()`` method, e.g. ``ZZ^n``, or - - an object where ``matrix(B)`` succeeds, e.g. a list of vectors. + - an object with a ``.matrix()`` method, e.g. ``ZZ^n``, or + - an object where ``matrix(B)`` succeeds, e.g. a list of vectors - - ``sigma`` -- Gaussian parameter `σ>0`. - - ``c`` -- center `c`, any vector in `\ZZ^n` is supported, but `c ∈ Λ(B)` is faster. - - ``precision`` -- bit precision `≥ 53`. + - ``sigma`` -- Gaussian parameter, one of the following: + + - a real number `\sigma > 0` (spherical), + - a positive definite matrix `\Sigma` (non-spherical), or + - any matrix-like ``S``, equivalent to `\Sigma = SS^T`, when + ``sigma_basis`` is set + + - ``c`` -- (default: 0) center `c`, any vector in `\ZZ^n` is + supported, but `c \in \Lambda(B)` is faster + + - ``r`` -- (default: ``None``) rounding parameter `r` as defined in + [Pei2010]_; ignored for spherical Gaussian parameter; if not provided, + set to be the maximal possible such that `\Sigma - rBB^T` is positive + definite + - ``precision`` -- bit precision `\geq 53`. + - ``sigma_basis`` -- (default: ``False``) When set, ``sigma`` is treated as + a (row) basis, i.e. the covariance matrix is computed by `\Sigma = SS^T` EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler sage: n = 2; sigma = 3.0 - sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^n, sigma) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^n, sigma) sage: f = D.f - sage: c = D._normalisation_factor_zz(); c # needs sage.symbolic - 56.2162803067524 + sage: nf = D._normalisation_factor_zz(); nf # needs sage.symbolic + 56.5486677646... sage: from collections import defaultdict sage: counter = defaultdict(Integer) @@ -279,34 +451,102 @@ def __init__(self, B, sigma=1, c=None, precision=None): sage: v = vector(ZZ, n, (-3, -3)) sage: v.set_immutable() - sage: while v not in counter: - ....: add_samples(1000) - sage: while abs(m*f(v)*1.0/c/counter[v] - 1.0) >= 0.1: # needs sage.symbolic + sage: while v not in counter: add_samples(1000) + sage: while abs(m*f(v)*1.0/nf/counter[v] - 1.0) >= 0.1: # needs sage.symbolic ....: add_samples(1000) + sage: counter = defaultdict(Integer) sage: v = vector(ZZ, n, (0, 0)) sage: v.set_immutable() sage: while v not in counter: ....: add_samples(1000) - sage: while abs(m*f(v)*1.0/c/counter[v] - 1.0) >= 0.1: # needs sage.symbolic + sage: while abs(m*f(v)*1.0/nf/counter[v] - 1.0) >= 0.1: # needs sage.symbolic ....: add_samples(1000) - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler - sage: qf = QuadraticForm(matrix(3, [2, 1, 1, 1, 2, 1, 1, 1, 2])) - sage: D = DiscreteGaussianDistributionLatticeSampler(qf, 3.0); D # needs sage.symbolic - Discrete Gaussian sampler with σ = 3.000000, c=(0, 0, 0) over lattice with basis + Spherical covariance are automatically handled:: + + sage: distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^3, sigma=Matrix(3, 3, 2)) + Discrete Gaussian sampler with Gaussian parameter σ = 2.00000000000000, c=(0, 0, 0) over lattice with basis + + [1 0 0] + [0 1 0] + [0 0 1] + + The sampler supports non-spherical covariance in the form of a Gram + matrix:: + + sage: n = 3 + sage: Sigma = Matrix(ZZ, [[5, -2, 4], [-2, 10, -5], [4, -5, 5]]) + sage: c = vector(ZZ, [7, 2, 5]) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^n, Sigma, c) + sage: nf = D._normalisation_factor_zz(); nf # This has not been properly implemented + 63.76927... + sage: while v not in counter: add_samples(1000) + sage: while abs(m*f(v)*1.0/nf/counter[v] - 1.0) >= 0.1: add_samples(1000) + + If the covariance provided is not positive definite, an error is thrown:: + + sage: Sigma = Matrix(ZZ, [[0, 1], [1, 0]]) + sage: distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^2, Sigma) + Traceback (most recent call last): + ... + RuntimeError: Sigma(=[0.000000000000000 1.00000000000000] + [ 1.00000000000000 0.000000000000000]) is not positive definite + + The sampler supports passing a basis for the covariance:: + + sage: n = 3 + sage: S = Matrix(ZZ, [[2, 0, 0], [-1, 3, 0], [2, -1, 1]]) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^n, S, sigma_basis=True) + sage: D.sigma() + [ 4.00000000000000 -2.00000000000000 4.00000000000000] + [-2.00000000000000 10.0000000000000 -5.00000000000000] + [ 4.00000000000000 -5.00000000000000 6.00000000000000] + + The non-spherical sampler supports offline computation to speed up + sampling. This will be useful when changing the center `c` is supported. + The difference is more significant for larger matrices. For 128x128 we + observe a 4x speedup (86s -> 20s):: + + sage: D.offline_samples = [] + sage: T = 2**12 + sage: L = [D() for _ in range(T)] # 560ms + sage: D.add_offline_samples(T) # 150ms + sage: L = [D() for _ in range(T)] # 370ms + + We can also initialise with matrix-like objects:: + + sage: qf = matrix(3, [2, 1, 1, 1, 2, 1, 1, 1, 2]) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(qf, 3.0); D + Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(0, 0, 0) over lattice with basis [2 1 1] [1 2 1] [1 1 2] - sage: D().parent() is D.c.parent() # needs sage.symbolic + sage: D().parent() is D.c().parent() True """ precision = DiscreteGaussianDistributionLatticeSampler.compute_precision(precision, sigma) self._RR = RealField(precision) - self._sigma = self._RR(sigma) - + # Check if sigma is a (real) number or a scaled identity matrix + self.is_spherical = True + try: + self._sigma = self._RR(sigma) + except TypeError: + self._sigma = matrix(self._RR, sigma) + # Will it be "annoying" if a matrix Sigma has different behaviour + # sometimes? There should be a parameter in the consrtuctor + if self._sigma == self._sigma[0, 0]: + self._sigma = self._RR(self._sigma[0, 0]) + else: + if sigma_basis: + self._sigma = self._sigma * self._sigma.T + if not self._sigma.is_positive_definite(): + raise RuntimeError(f"Sigma(={self._sigma}) is not positive definite") + self.is_spherical = False + + # TODO: Support taking a basis for the covariance try: B = matrix(B) except (TypeError, ValueError): @@ -317,40 +557,82 @@ def __init__(self, B, sigma=1, c=None, precision=None): except AttributeError: pass + self.n = B.ncols() self.B = B + self.Q = B * B.T self._G = B.gram_schmidt()[0] + self._c_in_lattice = False - try: - c = vector(ZZ, B.ncols(), c) - except TypeError: - try: - c = vector(QQ, B.ncols(), c) - except TypeError: - c = vector(RR, B.ncols(), c) + self.D = None + self.VS = None + self._c_mul_B_inv = None + self.r = r - self._c = c + self.set_c(c) - self.f = lambda x: exp(-(vector(ZZ, B.ncols(), x) - c).norm() ** 2 / (2 * self._sigma ** 2)) + def _precompute_data(self): + r""" + Precompute basis data. Do not call this method directly. - # deal with trivial case first, it is common - if self._G == 1 and self._c == 0: - self._c_in_lattice = True - D = DiscreteGaussianDistributionIntegerSampler(sigma=sigma) - self.D = tuple([D for _ in range(self.B.nrows())]) - self.VS = FreeModule(ZZ, B.nrows()) - return + EXAMPLES:: + + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1,0,0)) + sage: D.set_c((2, 0, 0)) + sage: D + Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(2, 0, 0) over lattice with basis + + [1 0 0] + [0 1 0] + [0 0 1] - w = B.solve_left(c) - if w in ZZ ** B.nrows(): - self._c_in_lattice = True - D = [] - for i in range(self.B.nrows()): - sigma_ = self._sigma / self._G[i].norm() - D.append(DiscreteGaussianDistributionIntegerSampler(sigma=sigma_)) - self.D = tuple(D) - self.VS = FreeModule(ZZ, B.nrows()) + .. NOTE:: + + Do not call this method directly, it is called automatically from + :func:`DiscreteGaussianDistributionLatticeSampler.__init__`. + """ + if self.is_spherical: + # deal with trivial case first, it is common + if self._G == 1 and self._c == 0: + self._c_in_lattice = True + D = DiscreteGaussianDistributionIntegerSampler(sigma=self._sigma) + self.D = tuple([D for _ in range(self.B.nrows())]) + self.VS = FreeModule(ZZ, self.B.nrows()) + + else: + w = self.B.solve_left(self._c) + if w in ZZ ** self.B.nrows(): + self._c_in_lattice = True + D = [] + for i in range(self.B.nrows()): + sigma_ = self._sigma / self._G[i].norm() + D.append(DiscreteGaussianDistributionIntegerSampler(sigma=sigma_)) + self.D = tuple(D) + self.VS = FreeModule(ZZ, self.B.nrows()) else: - self._c_in_lattice = False + # Variables Sigma2 and r are from [Pei2010]_ + # TODO: B is implicitly assumed to be full-rank for the + # non-spherical case. Remove this assumption :) + + # Offline samples of B⁻¹D₁ + self.offline_samples = [] + self.B_inv = self.B.inverse() + self.sigma_inv = self._sigma.inverse() + self._c_mul_B_inv = self._c * self.B_inv + + if self.r is None: + # Compute the maximal r such that (Sigma - r^2 * Q) > 0 + self.r = self._maximal_r() * 0.9999 + self.r = self._RR(self.r) + + Sigma2 = self._sigma - self.r**2 * self.Q + try: + verbose(f"Computing Cholesky decomposition of a {Sigma2.dimensions()} matrix") + self.B2 = Sigma2.cholesky().T + self.B2_B_inv = self.B2 * self.B_inv + except ValueError: + raise ValueError("Σ₂ is not positive definite. Is your " + f"r(={self.r}) too large? It should be at most " + f"{self._maximal_r()}") def __call__(self): r""" @@ -358,96 +640,167 @@ def __call__(self): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler - sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1,0,0)) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1,0,0)) sage: L = [D() for _ in range(2^12)] sage: mean_L = sum(L) / len(L) - sage: norm(mean_L.n() - D.c) < 0.25 + sage: norm(mean_L.n() - D.c()) < 0.25 True - sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1/2,0,0)) - sage: L = [D() for _ in range(2^12)] # long time - sage: mean_L = sum(L) / len(L) # long time - sage: norm(mean_L.n() - D.c) < 0.25 # long time + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1/2,0,0)) + sage: L = [D() for _ in range(2^12)] # long time + sage: mean_L = sum(L) / len(L) # long time + sage: norm(mean_L.n() - D.c()) < 0.25 # long time True """ - if self._c_in_lattice: + if not self.is_spherical: + v = self._call_non_spherical() + elif self._c_in_lattice: v = self._call_in_lattice() else: v = self._call() v.set_immutable() return v - @property + def f(self, x): + r""" + Compute the Gaussian `\rho_{\Lambda, c, \Sigma}`. + + EXAMPLES:: + + sage: Sigma = Matrix(ZZ, [[5, -2, 4], [-2, 10, -5], [4, -5, 5]]) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^3, Sigma) + sage: D.f([1, 0, 1]) + 0.802518797962478 + sage: D.f([1, 0, 3]) + 0.00562800641440405 + """ + try: + x = vector(ZZ, self.n, x) + except TypeError: + try: + x = vector(QQ, self.n, x) + except TypeError: + x = vector(self._RR, self.n, x) + x -= self._c + if self.is_spherical: + return exp(-x.norm() ** 2 / (2 * self._sigma**2)) + return exp(-x * self.sigma_inv * x / 2) + def sigma(self): r""" - Gaussian parameter `σ`. + Gaussian parameter `\sigma`. - Samples from this sampler will have expected norm `\sqrt{n}σ` where `n` - is the dimension of the lattice. + If `\sigma` is a real number, samples from this sampler will have expected norm + `\sqrt{n}\sigma` where `n` is the dimension of the lattice. EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler - sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1,0,0)) - sage: D.sigma + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1,0,0)) + sage: D.sigma() 3.00000000000000 """ return self._sigma - @property def c(self): - r"""Center `c`. + r""" + Center `c`. Samples from this sampler will be centered at `c`. EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler - sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1,0,0)); D - Discrete Gaussian sampler with σ = 3.000000, c=(1, 0, 0) over lattice with basis + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1,0,0)); D + Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(1, 0, 0) over lattice with basis [1 0 0] [0 1 0] [0 0 1] - sage: D.c + sage: D.c() (1, 0, 0) """ return self._c + def set_c(self, c): + r""" + Modifies center `c`. + + EXAMPLES:: + + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1,0,0)) + sage: D.set_c((2, 0, 0)) + sage: D + Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(2, 0, 0) over lattice with basis + + [1 0 0] + [0 1 0] + [0 0 1] + """ + if c is None: + self._c = None + return + + if c == 0: + c = vector(ZZ, self.n) + else: + try: + c = vector(ZZ, self.n, c) + except TypeError: + try: + c = vector(QQ, self.n, c) + except TypeError: + try: + c = vector(self._RR, self.n, c) + except TypeError: + c = vector(self._RR, self.n) + + self._c = c + self._precompute_data() + def __repr__(self): r""" EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler - sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1,0,0)); D - Discrete Gaussian sampler with σ = 3.000000, c=(1, 0, 0) over lattice with basis + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1,0,0)); D + Discrete Gaussian sampler with Gaussian parameter σ = 3.00000000000000, c=(1, 0, 0) over lattice with basis [1 0 0] [0 1 0] [0 0 1] + sage: Sigma = Matrix(ZZ, [[10, -6, 1], [-6, 5, -1], [1, -1, 2]]) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^3, Sigma); D + Discrete Gaussian sampler with Gaussian parameter Σ = + [ 10.0000000000000 -6.00000000000000 1.00000000000000] + [-6.00000000000000 5.00000000000000 -1.00000000000000] + [ 1.00000000000000 -1.00000000000000 2.00000000000000], c=(0, 0, 0) over lattice with basis + + [1 0 0] + [0 1 0] + [0 0 1] """ - # beware of unicode character in ascii string ! - return "Discrete Gaussian sampler with σ = %f, c=%s over lattice with basis\n\n%s" % (self._sigma, self._c, self.B) + if self.is_spherical: + sigma_str = f"σ = {self._sigma}" + else: + sigma_str = f"Σ =\n{self._sigma}" + return f"Discrete Gaussian sampler with Gaussian parameter {sigma_str}, c={self._c} over lattice with basis\n\n{self.B}" def _call_in_lattice(self): r""" - Return a new sample assuming `c ∈ Λ(B)`. + Return a new sample assuming `c \in \Lambda(B)`. EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler - sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1,0,0)) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1,0,0)) sage: L = [D._call_in_lattice() for _ in range(2^12)] sage: mean_L = sum(L) / len(L) - sage: norm(mean_L.n() - D.c) < 0.25 + sage: norm(mean_L.n() - D.c()) < 0.25 True - .. note:: + .. NOTE:: - Do not call this method directly, call :func:`DiscreteGaussianDistributionLatticeSampler.__call__` instead. + Do not call this method directly, call + :func:`DiscreteGaussianDistributionLatticeSampler.__call__` instead. """ w = self.VS([d() for d in self.D], check=False) return w * self.B + self._c @@ -458,16 +811,16 @@ def _call(self): EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler - sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1/2,0,0)) - sage: L = [D._call() for _ in range(2^12)] # long time - sage: mean_L = sum(L) / len(L) # long time - sage: norm(mean_L.n() - D.c) < 0.25 # long time + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1/2,0,0)) + sage: L = [D._call() for _ in range(2^12)] + sage: mean_L = sum(L) / len(L) + sage: norm(mean_L.n() - D.c()) < 0.25 True - .. note:: + .. NOTE:: - Do not call this method directly, call :func:`DiscreteGaussianDistributionLatticeSampler.__call__` instead. + Do not call this method directly, call + :func:`DiscreteGaussianDistributionLatticeSampler.__call__` instead. """ v = 0 c, sigma, B = self._c, self._sigma, self.B @@ -483,3 +836,46 @@ def _call(self): c = c - z * B[i] v = v + z * B[i] return v + + def add_offline_samples(self, cnt=1): + """ + Precompute samples from `B^{-1}D_1` to be used in :meth:`_call_non_spherical`. + + EXAMPLES:: + + sage: Sigma = Matrix([[5, -2, 4], [-2, 10, -5], [4, -5, 5]]) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^3, Sigma) + sage: assert not D.is_spherical + sage: D.add_offline_samples(2^12) + sage: L = [D() for _ in range(2^12)] # Takes less time + """ + # Just to document the difference with [Pei2010]_, in the paper (Algo 1) + # he samples from Λ + c, but we instead sample from Λ with distribution + # sampled at c (D_{Λ, c}), but that's the same as c + D_{Λ - c} + # Also, we use row notation instead of column notation. Sorry. + for _ in range(cnt): + coord = [normalvariate(mu=0, sigma=1) for _ in range(self.n)] + self.offline_samples.append(vector(self._RR, coord) * self.B2_B_inv) + + def _call_non_spherical(self): + """ + Return a new sample. + + EXAMPLES:: + + sage: Sigma = Matrix([[5, -2, 4], [-2, 10, -5], [4, -5, 5]]) + sage: D = distributions.DiscreteGaussianDistributionLatticeSampler(ZZ^3, Sigma, c=(1/2,0,0)) + sage: L = [D._call_non_spherical() for _ in range(2^12)] + sage: mean_L = sum(L) / len(L) + sage: norm(mean_L.n() - D.c()) < 0.25 + True + + .. NOTE:: + + Do not call this method directly, call + :func:`DiscreteGaussianDistributionLatticeSampler.__call__` instead. + """ + if len(self.offline_samples) == 0: + self.add_offline_samples() + vec = self._c_mul_B_inv - self.offline_samples.pop() + return self._randomise(vec) * self.B diff --git a/src/sage/stats/distributions/discrete_gaussian_polynomial.py b/src/sage/stats/distributions/discrete_gaussian_polynomial.py index 7d61ab7eb0c..63c8f5b800a 100644 --- a/src/sage/stats/distributions/discrete_gaussian_polynomial.py +++ b/src/sage/stats/distributions/discrete_gaussian_polynomial.py @@ -23,7 +23,7 @@ (24.0, 24.0) """ -#****************************************************************************** +# ****************************************************************************** # # DGS - Discrete Gaussian Samplers # @@ -53,7 +53,7 @@ # The views and conclusions contained in the software and documentation are # those of the authors and should not be interpreted as representing official # policies, either expressed or implied, of the FreeBSD Project. -#*****************************************************************************/ +# *****************************************************************************/ from sage.rings.real_mpfr import RR from sage.rings.integer_ring import ZZ diff --git a/src/sage/version.py b/src/sage/version.py index feec9728c2f..9c51c77cb14 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.3.rc4' -date = '2024-03-17' -banner = 'SageMath version 10.3.rc4, Release Date: 2024-03-17' +version = '10.4.beta0' +date = '2024-03-25' +banner = 'SageMath version 10.4.beta0, Release Date: 2024-03-25' diff --git a/src/sage_docbuild/__main__.py b/src/sage_docbuild/__main__.py index acfcb8392a0..80b4f9270f1 100644 --- a/src/sage_docbuild/__main__.py +++ b/src/sage_docbuild/__main__.py @@ -289,6 +289,9 @@ def setup_parser(): standard.add_argument("--no-plot", dest="no_plot", action="store_true", help="do not include graphics auto-generated using the '.. plot' markup") + standard.add_argument("--no-preparsed-examples", dest="no_preparsed_examples", + action="store_true", + help="do not show preparsed versions of EXAMPLES blocks") standard.add_argument("--include-tests-blocks", dest="skip_tests", default=True, action="store_false", help="include TESTS blocks in the reference manual") @@ -478,6 +481,8 @@ def excepthook(*exc_info): build_options.ALLSPHINXOPTS += "-n " if args.no_plot: os.environ['SAGE_SKIP_PLOT_DIRECTIVE'] = 'yes' + if args.no_preparsed_examples: + os.environ['SAGE_PREPARSED_DOC'] = 'no' if args.live_doc: os.environ['SAGE_LIVE_DOC'] = 'yes' if args.skip_tests: diff --git a/src/sage_docbuild/conf.py b/src/sage_docbuild/conf.py index c5329e79cfd..1c705c68d1f 100644 --- a/src/sage_docbuild/conf.py +++ b/src/sage_docbuild/conf.py @@ -39,6 +39,9 @@ # General configuration # --------------------- +SAGE_LIVE_DOC = os.environ.get('SAGE_LIVE_DOC', 'no') +SAGE_PREPARSED_DOC = os.environ.get('SAGE_PREPARSED_DOC', 'yes') + # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ @@ -57,7 +60,7 @@ jupyter_execute_default_kernel = 'sagemath' -if os.environ.get('SAGE_LIVE_DOC', 'no') == 'yes': +if SAGE_LIVE_DOC == 'yes': SAGE_JUPYTER_SERVER = os.environ.get('SAGE_JUPYTER_SERVER', 'binder') if SAGE_JUPYTER_SERVER.startswith('binder'): # format: "binder" or @@ -230,7 +233,7 @@ def sphinx_plot(graphics, **kwds): # console lexers. 'ipycon' is the IPython console, which is what we want # for most code blocks: anything with "sage:" prompts. For other IPython, # like blocks which might appear in a notebook cell, use 'ipython'. -highlighting.lexers['ipycon'] = IPythonConsoleLexer(in1_regex=r'sage: ', in2_regex=r'[.][.][.][.]: ') +highlighting.lexers['ipycon'] = IPythonConsoleLexer(in1_regex=r'(sage:|>>>)', in2_regex=r'([.][.][.][.]:|[.][.][.])') highlighting.lexers['ipython'] = IPyLexer() highlight_language = 'ipycon' @@ -305,7 +308,7 @@ def set_intersphinx_mappings(app, config): multidocs_is_master = True # https://sphinx-copybutton.readthedocs.io/en/latest/use.html -copybutton_prompt_text = r"sage: |[.][.][.][.]: |\$ " +copybutton_prompt_text = r"sage: |[.][.][.][.]: |>>> |[.][.][.] |\$ " copybutton_prompt_is_regexp = True copybutton_exclude = '.linenos, .c1' # exclude single comments (in particular, # optional!) copybutton_only_copy_prompt_lines = True @@ -789,8 +792,6 @@ class will be properly documented inside its surrounding class. return skip -from jupyter_sphinx.ast import JupyterCellNode, CellInputNode - class SagecodeTransform(SphinxTransform): """ Transform a code block to a live code block enabled by jupyter-sphinx. @@ -828,29 +829,87 @@ def apply(self): if self.app.builder.tags.has('html') or self.app.builder.tags.has('inventory'): for node in self.document.traverse(nodes.literal_block): if node.get('language') is None and node.astext().startswith('sage:'): - source = node.rawsource - lines = [] - for line in source.splitlines(): - newline = line.lstrip() - if newline.startswith('sage: ') or newline.startswith('....: '): - lines.append(newline[6:]) - cell_node = JupyterCellNode( - execute=False, - hide_code=True, - hide_output=True, - emphasize_lines=[], - raises=False, - stderr=True, - code_below=False, - classes=["jupyter_cell"]) - cell_input = CellInputNode(classes=['cell_input','live-doc']) - cell_input += nodes.literal_block( - text='\n'.join(lines), - linenos=False, - linenostart=1) - cell_node += cell_input - - node.parent.insert(node.parent.index(node) + 1, cell_node) + from docutils.nodes import container as Container, label as Label, literal_block as LiteralBlock, Text + from sphinx_inline_tabs._impl import TabContainer + parent = node.parent + index = parent.index(node) + if isinstance(node.previous_sibling(), TabContainer): + # Make sure not to merge inline tabs for adjacent literal blocks + parent.insert(index, Text('')) + index += 1 + parent.remove(node) + # Tab for Sage code + container = TabContainer("", type="tab", new_set=False) + textnodes = [Text('Sage')] + label = Label("", "", *textnodes) + container += label + content = Container("", is_div=True, classes=["tab-content"]) + content += node + container += content + parent.insert(index, container) + if SAGE_PREPARSED_DOC == 'yes': + # Tab for preparsed version + from sage.repl.preparse import preparse + container = TabContainer("", type="tab", new_set=False) + textnodes = [Text('Python')] + label = Label("", "", *textnodes) + container += label + content = Container("", is_div=True, classes=["tab-content"]) + example_lines = [] + preparsed_lines = ['>>> from sage.all import *'] + for line in node.rawsource.splitlines() + ['']: # one extra to process last example + newline = line.lstrip() + if newline.startswith('....: '): + example_lines.append(newline[6:]) + else: + if example_lines: + preparsed_example = preparse('\n'.join(example_lines)) + prompt = '>>> ' + for preparsed_line in preparsed_example.splitlines(): + preparsed_lines.append(prompt + preparsed_line) + prompt = '... ' + example_lines = [] + if newline.startswith('sage: '): + example_lines.append(newline[6:]) + else: + preparsed_lines.append(line) + preparsed = '\n'.join(preparsed_lines) + preparsed_node = LiteralBlock(preparsed, preparsed, language='ipycon') + content += preparsed_node + container += content + parent.insert(index + 1, container) + if SAGE_LIVE_DOC == 'yes': + # Tab for Jupyter-sphinx cell + from jupyter_sphinx.ast import JupyterCellNode, CellInputNode + source = node.rawsource + lines = [] + for line in source.splitlines(): + newline = line.lstrip() + if newline.startswith('sage: ') or newline.startswith('....: '): + lines.append(newline[6:]) + cell_node = JupyterCellNode( + execute=False, + hide_code=False, + hide_output=True, + emphasize_lines=[], + raises=False, + stderr=True, + code_below=False, + classes=["jupyter_cell"]) + cell_input = CellInputNode(classes=['cell_input','live-doc']) + cell_input += nodes.literal_block( + text='\n'.join(lines), + linenos=False, + linenostart=1) + cell_node += cell_input + container = TabContainer("", type="tab", new_set=False) + textnodes = [Text('Sage Live')] + label = Label("", "", *textnodes) + container += label + content = Container("", is_div=True, classes=["tab-content"]) + content += cell_node + container += content + parent.insert(index + 1, container) # This replaces the setup() in sage.misc.sagedoc_conf @@ -864,7 +923,7 @@ def setup(app): app.connect('autodoc-process-docstring', skip_TESTS_block) app.connect('autodoc-skip-member', skip_member) app.add_transform(SagemathTransform) - if os.environ.get('SAGE_LIVE_DOC', 'no') == 'yes': + if SAGE_LIVE_DOC == 'yes' or SAGE_PREPARSED_DOC == 'yes': app.add_transform(SagecodeTransform) # When building the standard docs, app.srcdir is set to SAGE_DOC_SRC + diff --git a/tox.ini b/tox.ini index a9ff0423488..99d04aad388 100644 --- a/tox.ini +++ b/tox.ini @@ -143,7 +143,7 @@ passenv = docker: EXTRA_DOCKER_BUILD_ARGS docker: EXTRA_DOCKER_TAGS docker: DOCKER_TAG - # Use DOCKER_BUILDKIT=1 for new version - for which unfortunately we cannot save failed builds as an image + # Use DOCKER_BUILDKIT=0 for legacy builder docker: DOCKER_BUILDKIT docker: BUILDKIT_INLINE_CACHE # Set for example to "with-system-packages configured with-targets-pre with-targets" @@ -760,7 +760,7 @@ commands = docker: BUILD_IMAGE=$DOCKER_PUSH_REPOSITORY$BUILD_IMAGE_STEM-$docker_target; \ docker: BUILD_TAG={env:DOCKER_TAG:$(git describe --dirty --always)}; \ docker: TAG_ARGS=$(for tag in $BUILD_TAG {env:EXTRA_DOCKER_TAGS:}; do echo --tag $BUILD_IMAGE:$tag; done); \ - docker: DOCKER_BUILDKIT={env:DOCKER_BUILDKIT:0} \ + docker: DOCKER_BUILDKIT={env:DOCKER_BUILDKIT:1}; \ docker: docker build . -f {envdir}/Dockerfile \ docker: --target $docker_target \ docker: $TAG_ARGS \ @@ -772,16 +772,38 @@ commands = docker: --build-arg TARGETS="{posargs:build}" \ docker: --build-arg TARGETS_OPTIONAL="{env:TARGETS_OPTIONAL:ptest}" \ docker: {env:EXTRA_DOCKER_BUILD_ARGS:}; status=$?; \ + docker: unset CONTAINER; \ docker: if [ $status != 0 ]; then \ - docker: BUILD_TAG="$BUILD_TAG-failed"; docker commit $(docker ps -l -q) $BUILD_IMAGE:$BUILD_TAG; PUSH_TAGS=$BUILD_IMAGE:$BUILD_TAG; \ - docker: else \ - docker: PUSH_TAGS=$(echo $BUILD_IMAGE:$BUILD_TAG; for tag in {env:EXTRA_DOCKER_TAGS:}; do echo "$BUILD_IMAGE:$tag"; done); \ + docker: if [ $DOCKER_BUILDKIT = 0 ]; then \ + docker: BUILD_TAG="$BUILD_TAG-failed"; CONTAINER=$(docker ps -l -q); docker commit $CONTAINER $BUILD_IMAGE:$BUILD_TAG; \ + docker: else \ + docker: unset BUILD_TAG; echo "DOCKER_BUILDKIT=1, so we cannot commit and tag the failed image"; \ + docker: fi; \ docker: fi; \ - docker: echo $BUILD_IMAGE:$BUILD_TAG >> {envdir}/Dockertags; \ - docker: if [ x"{env:DOCKER_PUSH_REPOSITORY:}" != x ]; then \ + docker: if [ -n "$BUILD_TAG" ]; then \ + docker: echo "Copying logs from the container to {envdir}/sage/"; \ + docker: rm -f {envdir}/sage/STATUS; \ + docker: docker run $BUILD_IMAGE:$BUILD_TAG bash -c " \ + docker: tar -c --ignore-failed-read -f - \ + docker: /sage/STATUS /sage/logs /sage/{prefix,venv}/var/lib/sage/installed \ + docker: ~/.sage/timings2.json /sage/pkgs/*/.tox/*/.sage/timings2.json \ + docker: /sage/pkgs/*/.tox/*/logs 2> /dev/null" \ + docker: | (cd {envdir} && tar xf -); \ + docker: if [ -f {envdir}/sage/STATUS ]; then status=$(cat {envdir}/sage/STATUS); fi; \ + docker: fi; \ + docker: unset PUSH_TAGS; \ + docker: if [ -n "$BUILD_TAG" ]; then \ + docker: if [ $status != 0 ]; then \ + docker: BUILD_TAG="${BUILD_TAG%-failed}-failed"; PUSH_TAGS=$BUILD_IMAGE:$BUILD_TAG; \ + docker: else \ + docker: PUSH_TAGS=$(echo $BUILD_IMAGE:$BUILD_TAG; for tag in {env:EXTRA_DOCKER_TAGS:}; do echo "$BUILD_IMAGE:$tag"; done); \ + docker: fi; \ + docker: echo $BUILD_IMAGE:$BUILD_TAG >> {envdir}/Dockertags; \ + docker: fi; \ + docker: if [ x"{env:DOCKER_PUSH_REPOSITORY:}" != x -a x"$PUSH_TAGS" != x ]; then \ docker: echo Pushing $PUSH_TAGS; \ docker: for tag in $PUSH_TAGS; do \ - docker: docker push $tag || echo "(ignoring errors)"; \ + docker: if docker push $tag; then echo $tag >> {envdir}/Dockertags.pushed; else echo "(ignoring errors)"; fi; \ docker: done; \ docker: fi; \ docker: if [ $status != 0 ]; then exit $status; fi; \