diff --git a/.github/workflows/tox-experimental.yml b/.github/workflows/tox-experimental.yml index 4b3c6b1eb84..e8c1fe3ba9e 100644 --- a/.github/workflows/tox-experimental.yml +++ b/.github/workflows/tox-experimental.yml @@ -38,7 +38,7 @@ jobs: fail-fast: false max-parallel: 6 matrix: - tox_system_factor: [ubuntu-trusty, ubuntu-xenial, ubuntu-bionic, ubuntu-focal, ubuntu-groovy, ubuntu-hirsute, debian-jessie, debian-stretch, debian-buster, debian-bullseye, debian-sid, linuxmint-17, linuxmint-18, linuxmint-19, linuxmint-19.3, linuxmint-20.1, fedora-26, fedora-27, fedora-28, fedora-29, fedora-30, fedora-31, fedora-32, fedora-33, fedora-34, centos-7, centos-8, gentoo, archlinux-latest, slackware-14.2, conda-forge, ubuntu-bionic-i386, manylinux-2_24-i686, debian-buster-i386, centos-7-i386] + tox_system_factor: [ubuntu-trusty, ubuntu-xenial, ubuntu-bionic, ubuntu-focal, ubuntu-groovy, ubuntu-hirsute, debian-jessie, debian-stretch, debian-buster, debian-bullseye, debian-sid, linuxmint-17, linuxmint-18, linuxmint-19, linuxmint-19.3, linuxmint-20.1, fedora-26, fedora-27, fedora-28, fedora-29, fedora-30, fedora-31, fedora-32, fedora-33, fedora-34, centos-7, centos-8, gentoo, archlinux-latest, opensuse-15, opensuse-15.3, opensuse-tumbleweed, slackware-14.2, conda-forge, ubuntu-bionic-i386, manylinux-2_24-i686, debian-buster-i386, centos-7-i386] tox_packages_factor: [maximal] targets_pattern: [0-g, h-o, p, q-z] env: @@ -112,54 +112,35 @@ jobs: fail-fast: false max-parallel: 3 matrix: - tox_system_factor: [homebrew-macos, conda-forge-macos] - tox_packages_factor: [minimal, standard] + tox_system_factor: [homebrew-macos, conda-forge-macos, homebrew-macos-python3_xcode] + tox_packages_factor: [maximal] # As of 2021-03, default xcode is 12.4 # https://github.com/actions/virtual-environments/blob/main/images/macos/macos-10.15-Readme.md#xcode xcode_version_factor: [default] + targets_pattern: [0-g, h-o, p, q-z] os: [ macos-10.15, macos-11.0 ] include: # Test xcode 11.7 only on macos-10.15 - tox_system_factor: homebrew-macos - tox_packages_factor: minimal + tox_packages_factor: maximal xcode_version_factor: 11.7 + targets_pattern: 0-g os: macos-10.15 - tox_system_factor: homebrew-macos - tox_packages_factor: standard + tox_packages_factor: maximal xcode_version_factor: 11.7 + targets_pattern: h-o os: macos-10.15 - # python3_xcode is only accepted if enough packages are available from the system - # --> to test "minimal", we will need https://trac.sagemath.org/ticket/30949 - - tox_system_factor: homebrew-macos-python3_xcode - tox_packages_factor: standard + - tox_system_factor: homebrew-macos + tox_packages_factor: maximal xcode_version_factor: 11.7 + targets_pattern: p os: macos-10.15 - - tox_system_factor: homebrew-macos-python3_xcode - tox_packages_factor: standard - xcode_version_factor: default + - tox_system_factor: homebrew-macos + tox_packages_factor: maximal + xcode_version_factor: 11.7 + targets_pattern: q-z os: macos-10.15 - - tox_system_factor: homebrew-macos-python3_xcode - tox_packages_factor: standard - xcode_version_factor: default - os: macos-11.0 - - tox_system_factor: homebrew-macos-python3_xcode-nokegonly - tox_packages_factor: standard - xcode_version_factor: default - os: macos-11.0 - # likewise for python3_pythonorg - - tox_system_factor: homebrew-macos-python3_pythonorg - tox_packages_factor: standard - xcode_version_factor: default - os: macos-11.0 - # conda-forge-macos-environment - - tox_system_factor: conda-forge-macos - tox_packages_factor: environment - xcode_version_factor: default - os: macos-11.0 - - tox_system_factor: conda-forge-macos - tox_packages_factor: environment-optional - xcode_version_factor: default - os: macos-11.0 env: TOX_ENV: local-${{ matrix.tox_system_factor }}-${{ matrix.tox_packages_factor }} LOGS_ARTIFACT_NAME: logs-commit-${{ github.sha }}-tox-local-${{ matrix.tox_system_factor }}-${{ matrix.tox_packages_factor }}-${{ matrix.os }}-xcode_${{ matrix.xcode_version_factor }} diff --git a/.github/workflows/tox-optional.yml b/.github/workflows/tox-optional.yml index 92245c2a7db..b79ffff8b20 100644 --- a/.github/workflows/tox-optional.yml +++ b/.github/workflows/tox-optional.yml @@ -38,7 +38,7 @@ jobs: fail-fast: false max-parallel: 6 matrix: - tox_system_factor: [ubuntu-trusty, ubuntu-xenial, ubuntu-bionic, ubuntu-focal, ubuntu-groovy, ubuntu-hirsute, debian-jessie, debian-stretch, debian-buster, debian-bullseye, debian-sid, linuxmint-17, linuxmint-18, linuxmint-19, linuxmint-19.3, linuxmint-20.1, fedora-26, fedora-27, fedora-28, fedora-29, fedora-30, fedora-31, fedora-32, fedora-33, fedora-34, centos-7, centos-8, gentoo, archlinux-latest, slackware-14.2, conda-forge, ubuntu-bionic-i386, manylinux-2_24-i686, debian-buster-i386, centos-7-i386] + tox_system_factor: [ubuntu-trusty, ubuntu-xenial, ubuntu-bionic, ubuntu-focal, ubuntu-groovy, ubuntu-hirsute, debian-jessie, debian-stretch, debian-buster, debian-bullseye, debian-sid, linuxmint-17, linuxmint-18, linuxmint-19, linuxmint-19.3, linuxmint-20.1, fedora-26, fedora-27, fedora-28, fedora-29, fedora-30, fedora-31, fedora-32, fedora-33, fedora-34, centos-7, centos-8, gentoo, archlinux-latest, opensuse-15, opensuse-15.3, opensuse-tumbleweed, slackware-14.2, conda-forge, ubuntu-bionic-i386, manylinux-2_24-i686, debian-buster-i386, centos-7-i386] tox_packages_factor: [maximal] targets_pattern: [0-g, h-o, p, q-z] env: @@ -114,54 +114,35 @@ jobs: fail-fast: false max-parallel: 3 matrix: - tox_system_factor: [homebrew-macos, conda-forge-macos] - tox_packages_factor: [minimal, standard] + tox_system_factor: [homebrew-macos, conda-forge-macos, homebrew-macos-python3_xcode] + tox_packages_factor: [maximal] # As of 2021-03, default xcode is 12.4 # https://github.com/actions/virtual-environments/blob/main/images/macos/macos-10.15-Readme.md#xcode xcode_version_factor: [default] + targets_pattern: [0-g, h-o, p, q-z] os: [ macos-10.15, macos-11.0 ] include: # Test xcode 11.7 only on macos-10.15 - tox_system_factor: homebrew-macos - tox_packages_factor: minimal + tox_packages_factor: maximal xcode_version_factor: 11.7 + targets_pattern: 0-g os: macos-10.15 - tox_system_factor: homebrew-macos - tox_packages_factor: standard + tox_packages_factor: maximal xcode_version_factor: 11.7 + targets_pattern: h-o os: macos-10.15 - # python3_xcode is only accepted if enough packages are available from the system - # --> to test "minimal", we will need https://trac.sagemath.org/ticket/30949 - - tox_system_factor: homebrew-macos-python3_xcode - tox_packages_factor: standard + - tox_system_factor: homebrew-macos + tox_packages_factor: maximal xcode_version_factor: 11.7 + targets_pattern: p os: macos-10.15 - - tox_system_factor: homebrew-macos-python3_xcode - tox_packages_factor: standard - xcode_version_factor: default + - tox_system_factor: homebrew-macos + tox_packages_factor: maximal + xcode_version_factor: 11.7 + targets_pattern: q-z os: macos-10.15 - - tox_system_factor: homebrew-macos-python3_xcode - tox_packages_factor: standard - xcode_version_factor: default - os: macos-11.0 - - tox_system_factor: homebrew-macos-python3_xcode-nokegonly - tox_packages_factor: standard - xcode_version_factor: default - os: macos-11.0 - # likewise for python3_pythonorg - - tox_system_factor: homebrew-macos-python3_pythonorg - tox_packages_factor: standard - xcode_version_factor: default - os: macos-11.0 - # conda-forge-macos-environment - - tox_system_factor: conda-forge-macos - tox_packages_factor: environment - xcode_version_factor: default - os: macos-11.0 - - tox_system_factor: conda-forge-macos - tox_packages_factor: environment-optional - xcode_version_factor: default - os: macos-11.0 env: TOX_ENV: local-${{ matrix.tox_system_factor }}-${{ matrix.tox_packages_factor }} LOGS_ARTIFACT_NAME: logs-commit-${{ github.sha }}-tox-local-${{ matrix.tox_system_factor }}-${{ matrix.tox_packages_factor }}-${{ matrix.os }}-xcode_${{ matrix.xcode_version_factor }} diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml index 2390bf30aaf..767a12e279d 100644 --- a/.github/workflows/tox.yml +++ b/.github/workflows/tox.yml @@ -38,7 +38,7 @@ jobs: fail-fast: false max-parallel: 20 matrix: - tox_system_factor: [ubuntu-trusty, ubuntu-xenial, ubuntu-bionic, ubuntu-focal, ubuntu-groovy, ubuntu-hirsute, debian-jessie, debian-stretch, debian-buster, debian-bullseye, debian-sid, linuxmint-17, linuxmint-18, linuxmint-19, linuxmint-19.3, linuxmint-20.1, fedora-26, fedora-27, fedora-28, fedora-29, fedora-30, fedora-31, fedora-32, fedora-33, fedora-34, centos-7, centos-8, gentoo, gentoo-python3.7, archlinux-latest, slackware-14.2, conda-forge, ubuntu-bionic-i386, manylinux-2_24-i686, debian-buster-i386, centos-7-i386] + tox_system_factor: [ubuntu-trusty, ubuntu-xenial, ubuntu-bionic, ubuntu-focal, ubuntu-groovy, ubuntu-hirsute, debian-jessie, debian-stretch, debian-buster, debian-bullseye, debian-sid, linuxmint-17, linuxmint-18, linuxmint-19, linuxmint-19.3, linuxmint-20.1, fedora-26, fedora-27, fedora-28, fedora-29, fedora-30, fedora-31, fedora-32, fedora-33, fedora-34, centos-7, centos-8, gentoo, gentoo-python3.7, archlinux-latest, opensuse-15, opensuse-15.3, opensuse-tumbleweed, slackware-14.2, conda-forge, ubuntu-bionic-i386, manylinux-2_24-i686, debian-buster-i386, centos-7-i386] tox_packages_factor: [minimal, standard] env: TOX_ENV: docker-${{ matrix.tox_system_factor }}-${{ matrix.tox_packages_factor }} diff --git a/.gitignore b/.gitignore index 8d2e8d86240..70e0765fb73 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,14 @@ /src/environment.yml /src/environment-optional.yml +/src/setup.cfg +/src/requirements.txt +/src/pyproject.toml +/src/Pipfile +/src/Pipfile.lock +/Pipfile +/Pipfile.lock + # Various editors *~ @@ -93,6 +101,9 @@ __pycache__/ *.py[cod] *$py.class +# Generated by sage_setup.autogen +/src/sage/ext/interpreters + # Generated Cython files *.so src/sage/**/*.c @@ -130,6 +141,14 @@ build/bin/sage-build-env-config /build/pkgs/*/src/*.egg-info /build/pkgs/*/src/.tox +# Generated by docbuild +/src/doc/en/reference/*/sage +/src/doc/en/reference/sage +/src/doc/en/reference/spkg/*.rst +/src/doc/output +/src/doc/en/installation/*.txt +/src/doc/en/reference/repl/*.txt + # Distribution / packaging src/*.egg-info/ /src/.cython_version @@ -152,5 +171,8 @@ src/venv.bak/ # tox generated files /.tox +/build/.tox /prefix + +# git worktree worktree* diff --git a/.zenodo.json b/.zenodo.json index 0aeb3c81cf5..f57eda9b908 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -1,10 +1,10 @@ { "description": "Mirror of the Sage https://sagemath.org/ source tree", "license": "other-open", - "title": "sagemath/sage: 9.3", - "version": "9.3", + "title": "sagemath/sage: 9.4.beta0", + "version": "9.4.beta0", "upload_type": "software", - "publication_date": "2021-05-09", + "publication_date": "2021-05-25", "creators": [ { "affiliation": "SageMath.org", @@ -15,7 +15,7 @@ "related_identifiers": [ { "scheme": "url", - "identifier": "https://github.com/sagemath/sage/tree/9.3", + "identifier": "https://github.com/sagemath/sage/tree/9.4.beta0", "relation": "isSupplementTo" }, { diff --git a/Pipfile.m4 b/Pipfile.m4 new file mode 100644 index 00000000000..76df035b999 --- /dev/null +++ b/Pipfile.m4 @@ -0,0 +1,41 @@ +## Pipfile with all packages in the Sage distribution and version information locked +## FIXME: Many packages still missing. +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] +pkgconfig = "==esyscmd(`printf $(sed "s/[.]p.*//;" ../pkgconfig/package-version.txt)')" +cython = "==esyscmd(`printf $(sed "s/[.]p.*//;" ../cython/package-version.txt)')" +pycodestyle = "*" +ipykernel = "==esyscmd(`printf $(sed "s/[.]p.*//;" ../ipykernel/package-version.txt)')" +tox = "*" +jinja2 = "==esyscmd(`printf $(sed "s/[.]p.*//;" ../jinja2/package-version.txt)')" +pytest = "*" +ipywidgets = "==esyscmd(`printf $(sed "s/[.]p.*//;" ../ipywidgets/package-version.txt)')" +sphinx = "==esyscmd(`printf $(sed "s/[.]p.*//;" ../sphinx/package-version.txt)')" +rope = "*" +six = "*" +jupyter-core = "==esyscmd(`printf $(sed "s/[.]p.*//;" ../jupyter_core/package-version.txt)')" + +[packages] +numpy = "==esyscmd(`printf $(sed "s/[.]p.*//;" ../numpy/package-version.txt)')" +cysignals = "==esyscmd(`printf $(sed "s/[.]p.*//;" ../cysignals/package-version.txt)')" +cypari2 = "==esyscmd(`printf $(sed "s/[.]p.*//;" ../cypari/package-version.txt)')" +gmpy2 = "==esyscmd(`printf $(sed "s/[.]p.*//;" ../gmpy2/package-version.txt)')" +psutil = "==esyscmd(`printf $(sed "s/[.]p.*//;" ../psutil/package-version.txt)')" +pexpect = "==esyscmd(`printf $(sed "s/[.]p.*//;" ../pexpect/package-version.txt)')" +ipython = "==esyscmd(`printf $(sed "s/[.]p.*//;" ../ipython/package-version.txt)')" +sympy = "==esyscmd(`printf $(sed "s/[.]p.*//;" ../sympy/package-version.txt)')" +scipy = "==esyscmd(`printf $(sed "s/[.]p.*//;" ../scipy/package-version.txt)')" +pplpy = "==esyscmd(`printf $(sed "s/[.]p.*//;" ../pplpy/package-version.txt)')" +matplotlib = "==esyscmd(`printf $(sed "s/[.]p.*//;" ../matplotlib/package-version.txt)')" +cvxopt = "==esyscmd(`printf $(sed "s/[.]p.*//;" ../cvxopt/package-version.txt)')" +rpy2 = "==esyscmd(`printf $(sed "s/[.]p.*//;" ../rpy2/package-version.txt)')" +networkx = "==esyscmd(`printf $(sed "s/[.]p.*//;" ../networkx/package-version.txt)')" + +sagemath-standard = { path = "src" } + +[requires] +python_version = "3.9" diff --git a/VERSION.txt b/VERSION.txt index c4a47dfc279..32443a2a4c1 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -SageMath version 9.3, Release Date: 2021-05-09 +SageMath version 9.4.beta0, Release Date: 2021-05-25 diff --git a/bootstrap b/bootstrap index be0bcf62960..1ee0c6b9ab2 100755 --- a/bootstrap +++ b/bootstrap @@ -24,6 +24,7 @@ # Set SAGE_ROOT to the path to this file and then cd into it SAGE_ROOT="$(cd "$(dirname "$0")" && pwd -P)" +export SAGE_ROOT cd "$SAGE_ROOT" export PATH="$SAGE_ROOT/build/bin:$PATH" @@ -108,6 +109,10 @@ SAGE_SPKG_CONFIGURE_$(echo ${pkgname} | tr '[a-z]' '[A-Z]')" done echo "$spkg_configures" >> m4/sage_spkg_configures.m4 + for pkgname in $(./sage --package list --has-file bootstrap); do + (cd build/pkgs/$pkgname && ./bootstrap) || exit 1 + done + # Default to no filter if "-q" was not passed. QUIET_SED_FILTER="" if [ "${BOOTSTRAP_QUIET}" = "yes" ]; then @@ -119,8 +124,6 @@ SAGE_SPKG_CONFIGURE_$(echo ${pkgname} | tr '[a-z]' '[A-Z]')" # ONLY stderr, and to re-output the results back to stderr leaving # stdout alone. Basically we swap the two descriptors using a # third, filter, and then swap them back. - BOOTSTRAP_QUIET="${BOOTSTRAP_QUIET}" \ - SAGE_ROOT="$SAGE_ROOT" \ src/doc/bootstrap && \ install_config_rpath && \ aclocal -I m4 && \ @@ -262,6 +265,7 @@ do ?) usage; exit 2;; esac done +export BOOTSTRAP_QUIET CONFBALL="upstream/configure-$CONFVERSION.tar.gz" if [ $DOWNLOAD$SAVE = yesyes ]; then diff --git a/build/bin/sage-bootstrap-python b/build/bin/sage-bootstrap-python index faee444e8dc..c3afc1261af 100755 --- a/build/bin/sage-bootstrap-python +++ b/build/bin/sage-bootstrap-python @@ -30,15 +30,6 @@ fi # is accessible by this python; this is to guard on Cygwin against Pythons # installed somewhere else in Windows. -# Trac #30008: Make it work even if the environment tries to sabotage UTF-8 -# operation in Python 3.0.x-3.6.x by setting LC_ALL=C or similar. - -if [ "$LC_ALL" = "C" -o "$LANG" = "C" -o "$LC_CTYPE" = "C" ]; then - LC_ALL=$(locale -a | grep -E -i '^(c|en_us)[-.]utf-?8$' | head -n 1) - LANG=$LC_ALL - export LC_ALL - export LANG -fi PYTHONS="python python3 python3.8 python3.7 python2.7 python3.6 python2" for PY in $PYTHONS; do diff --git a/build/bin/sage-get-system-packages b/build/bin/sage-get-system-packages index 8675f3dbfa7..8883abc8ca4 100755 --- a/build/bin/sage-get-system-packages +++ b/build/bin/sage-get-system-packages @@ -11,18 +11,41 @@ if [ -z "$SAGE_ROOT" ]; then SAGE_ROOT=`pwd` fi case "$SYSTEM" in + install-requires) + # Collect install-requires.txt and output it in the format + # needed by setup.cfg [options] install_requires= + SYSTEM_PACKAGES_FILE_NAMES="install-requires.txt" + STRIP_COMMENTS="sed s/#.*//;/^[[:space:]]*$/d;" + COLLECT= + ;; + install-requires-toml) + # Collect install-requires.txt and output it in the format + # needed by pyproject.toml [build-system] requires= + SYSTEM_PACKAGES_FILE_NAMES="install-requires.txt" + STRIP_COMMENTS="sed s/#.*//;/^[[:space:]]*$/d;s/^/'/;s/$/',/;" + COLLECT= + ;; pip) - SYSTEM_PACKAGES_FILE_NAME="requirements.txt" + SYSTEM_PACKAGES_FILE_NAMES="requirements.txt install-requires.txt" STRIP_COMMENTS='sed s/#.*//;s/[[:space:]]//g;' + COLLECT=echo ;; *) - SYSTEM_PACKAGES_FILE_NAME="distros/$SYSTEM.txt" + SYSTEM_PACKAGES_FILE_NAMES="distros/$SYSTEM.txt" STRIP_COMMENTS="sed s/#.*//;" + COLLECT=echo ;; esac for PKG_BASE in $SPKGS; do - SYSTEM_PACKAGES_FILE="$SAGE_ROOT"/build/pkgs/$PKG_BASE/$SYSTEM_PACKAGES_FILE_NAME - if [ -f $SYSTEM_PACKAGES_FILE ]; then - echo $(${STRIP_COMMENTS} $SYSTEM_PACKAGES_FILE) - fi + for NAME in $SYSTEM_PACKAGES_FILE_NAMES; do + SYSTEM_PACKAGES_FILE="$SAGE_ROOT"/build/pkgs/$PKG_BASE/$NAME + if [ -f $SYSTEM_PACKAGES_FILE ]; then + if [ -z "$COLLECT" ]; then + ${STRIP_COMMENTS} $SYSTEM_PACKAGES_FILE + else + $COLLECT $(${STRIP_COMMENTS} $SYSTEM_PACKAGES_FILE) + fi + break + fi + done done diff --git a/build/bin/sage-site b/build/bin/sage-site index 400782164b3..b99923d1eff 100755 --- a/build/bin/sage-site +++ b/build/bin/sage-site @@ -149,12 +149,7 @@ if [ "$1" = "-docbuild" -o "$1" = "--docbuild" ]; then # Trac #30002: ensure an English locale so that it is possible to # scrape out warnings by pattern matching. - # Trac #30576: But we have to avoid the C locale, which disables - # proper UTF-8 operation in Python 3.6 or older. - LC_ALL=$(locale -a | grep -E -i '^(c|en_us)[-.]utf-?8$' | head -n 1) - LANG=$LC_ALL - export LC_ALL - export LANG + export LANG=C # See #30351: bugs in macOS implementations of openblas/libgopm can cause # docbuild to hang if multiple OpenMP threads are allowed. diff --git a/build/bin/sage-spkg b/build/bin/sage-spkg index 1553f28bc0f..c92f315f36f 100755 --- a/build/bin/sage-spkg +++ b/build/bin/sage-spkg @@ -533,6 +533,7 @@ for lib in "\$SAGE_ROOT/build/bin/sage-dist-helpers" "\$SAGE_SRC/bin/sage-env-co exit 1 fi done +export PATH="$SAGE_INST_LOCAL/bin:$PATH" export SAGE_INST_LOCAL="$SAGE_INST_LOCAL" @@ -701,7 +702,7 @@ rm -f "$SAGE_DESTDIR_LOCAL/lib64" # All spkgs should eventually support this, but fall back on old behavior in # case DESTDIR=$SAGE_DESTDIR installation was not used -echo "Copying package files from temporary location $SAGE_DESTDIR to $SAGE_LOCAL" +echo "Copying package files from temporary location $SAGE_DESTDIR to $SAGE_INST_LOCAL" if [ -d "$SAGE_DESTDIR" ]; then # Some `find` implementations will put superfluous slashes in the # output if we give them a directory name with a slash; so make sure @@ -717,8 +718,8 @@ if [ -d "$SAGE_DESTDIR" ]; then # Generate installed file manifest FILE_LIST="$(cd "$PREFIX" && find . -type f -o -type l | sed 's|^\./||' | sort)" - # Copy files into $SAGE_LOCAL - $SAGE_SUDO cp -Rp "$PREFIX/." "$SAGE_LOCAL" + # Copy files into $SAGE_INST_LOCAL + $SAGE_SUDO cp -Rp "$PREFIX/." "$SAGE_INST_LOCAL" if [ $? -ne 0 ]; then error_msg "Error copying files for $PKG_NAME." exit 1 @@ -784,7 +785,7 @@ fi # Note: spkg-check tests are run after the package has been copied into -# SAGE_LOCAL. It might make more sense to run the tests before, but the +# SAGE_INST_LOCAL. It might make more sense to run the tests before, but the # spkg-check scripts were written before use of DESTDIR installs, and so # fail in many cases. This might be good to change later. diff --git a/build/make/Makefile.in b/build/make/Makefile.in index 6273b1356a5..fb3a6ed5bcf 100644 --- a/build/make/Makefile.in +++ b/build/make/Makefile.in @@ -26,10 +26,8 @@ $(error This Makefile needs to be invoked by build/make/install) endif endif -# Directory to keep track of which packages are installed -SAGE_SPKG_INST = $(SAGE_LOCAL)/var/lib/sage/installed - -INST = $(SAGE_SPKG_INST) +# Directory to keep track of which packages are installed - relative to installation prefix +SPKG_INST_RELDIR = var/lib/sage/installed # Aliases for mutually exclusive standard packages selected at configure time TOOLCHAIN = @SAGE_TOOLCHAIN@ @@ -54,6 +52,10 @@ SAGE_SPKG = sage-spkg # These are added to SAGE_SPKG in the call SAGE_SPKG_OPTIONS = @SAGE_SPKG_OPTIONS@ +# Where the Sage distribution installs Python packages. +# This can be overridden by 'make SAGE_VENV=/some/venv'. +SAGE_VENV = @SAGE_VENV@ + # Generate/install sage-specific .pc files. # see build/pkgs/gsl/spkg-configure.m4 $(SAGE_PKGCONFIG)/gsl.pc: @@ -140,10 +142,9 @@ SCRIPT_PACKAGES = @SAGE_SCRIPT_PACKAGES@ # inst_git = $(INST)/.dummy $(foreach pkgname,$(BUILT_PACKAGES),\ - $(eval inst_$(pkgname) = $$(INST)/$(pkgname)-$(vers_$(pkgname)))) - + $(eval inst_$(pkgname) = $(foreach tree, $(trees_$(pkgname)), $($(tree))/$(SPKG_INST_RELDIR)/$(pkgname)-$(vers_$(pkgname))))) $(foreach pkgname,$(DUMMY_PACKAGES),\ - $(eval inst_$(pkgname) = $$(INST)/.dummy)) + $(eval inst_$(pkgname) = $(SAGE_LOCAL)/$(SPKG_INST_RELDIR)/.dummy)) # Override this for pip packages, for which we do not keep an installation record # in addition to what pip is already doing. @@ -151,7 +152,7 @@ $(foreach pkgname,$(PIP_PACKAGES),\ $(eval inst_$(pkgname) = $(pkgname))) # Dummy target for packages which are not installed -$(INST)/.dummy: +$(SAGE_LOCAL)/$(SPKG_INST_RELDIR)/.dummy: touch $@ @@ -206,10 +207,10 @@ ifneq ($(PYTHON_FOR_VENV),) ifeq ($(PYTHON),python3) PYTHON = python3_venv endif -inst_python3_venv = $(SAGE_LOCAL)/pyvenv.cfg +inst_python3_venv = $(SAGE_VENV)/pyvenv.cfg $(inst_python3_venv): - $(PYTHON_FOR_VENV) $(SAGE_ROOT)/build/bin/sage-venv "$(SAGE_LOCAL)" + $(PYTHON_FOR_VENV) $(SAGE_ROOT)/build/bin/sage-venv "$(SAGE_VENV)" endif # Build everything and start Sage. @@ -280,7 +281,7 @@ all-toolchain: base-toolchain # All packages needed as a prerequisite to install other Python packages with # pip or which are otherwise used by the Python build tools; these should be # given as a prerequisite to any pip-installed packages -PYTHON_TOOLCHAIN = setuptools pip setuptools_scm +PYTHON_TOOLCHAIN = setuptools pip setuptools_scm wheel setuptools_wheel # Everything needed to start up Sage using "./sage". Of course, not # every part of Sage will work. It does not include Maxima for example. @@ -521,37 +522,44 @@ pkg_deps = \ # $(1): package name # $(2): package version # $(3): package dependencies +# $(4): package tree variable define NORMAL_PACKAGE_templ ########################################## $(1)-build-deps: $(3) -$$(INST)/$(1)-$(2): $(3) - +$(MAKE_REC) $(1)-no-deps +$$($(4))/$(SPKG_INST_RELDIR)/$(1)-$(2): $(3) + +$(MAKE_REC) $(1)-$(4)-no-deps -$(1): $$(INST)/$(1)-$(2) +$(1): $$($(4))/$(SPKG_INST_RELDIR)/$(1)-$(2) -$(1)-no-deps: - +$(AM_V_at)sage-logger -p 'SAGE_CHECK=$$(SAGE_CHECK_$(1)) $$(SAGE_SPKG) $$(SAGE_SPKG_OPTIONS) \ +$(1)-$(4)-no-deps: + +$(AM_V_at)sage-logger -p 'SAGE_CHECK=$$(SAGE_CHECK_$(1)) PATH=$$($(4))/bin:$$$$PATH $$(SAGE_SPKG) $$(SAGE_SPKG_OPTIONS) \ $(if $(filter $(1),$(TOOLCHAIN_DEPS)),--keep-existing) \ - $(1)-$(2)' '$$(SAGE_LOGS)/$(1)-$(2).log' + $(1)-$(2) $$($(4))' '$$(SAGE_LOGS)/$(1)-$(2).log' -$(1)-clean: +$(1)-no-deps: $(1)-$(4)-no-deps + +$(1)-$(4)-clean: sage-spkg-uninstall $(if $(filter $(1),$(TOOLCHAIN_DEPS)),--keep-files) \ - $(1) '$(SAGE_LOCAL)' + $(1) '$$($(4))' + +$(1)-clean: $(1)-$(4)-clean .PHONY: $(1) $(1)-clean $(1)-build-deps $(1)-no-deps endef ################################################################# $(foreach pkgname, $(NORMAL_PACKAGES),\ + $(foreach tree, $(trees_$(pkgname)), \ $(eval $(call NORMAL_PACKAGE_templ,$(pkgname),$(vers_$(pkgname)),\ - $(call pkg_deps,$(pkgname))))) + $(call pkg_deps,$(pkgname)),$(tree))))) ifdef DEBUG_RULES $(info # Rules for standard packages) $(foreach pkgname, $(NORMAL_PACKAGES),\ + $(foreach tree, $(trees_$(pkgname)), \ $(info $(call NORMAL_PACKAGE_templ,$(pkgname),$(vers_$(pkgname)),\ - $(call pkg_deps,$(pkgname))))) + $(call pkg_deps,$(pkgname)),$(tree))))) endif # ================================ pip packages =============================== @@ -617,44 +625,54 @@ endif # $(1): package name # $(2): package version # $(3): package dependencies +# $(4): package tree variable + define SCRIPT_PACKAGE_templ $(1)-build-deps: $(3) -$$(INST)/$(1)-$(2): $(3) - +$(MAKE_REC) $(1)-no-deps +$$($(4))/$(SPKG_INST_RELDIR)/$(1)-$(2): $(3) + +$(MAKE_REC) $(1)-$(4)-no-deps -$(1): $$(INST)/$(1)-$(2) +$(1): $$($(4))/$(SPKG_INST_RELDIR)/$(1)-$(2) -$(1)-no-deps: +$(1)-$(4)-no-deps: $(AM_V_at)cd '$$(SAGE_ROOT)/build/pkgs/$(1)' && \ + . '$$(SAGE_ROOT)/src/bin/sage-src-env-config' && \ . '$$(SAGE_ROOT)/src/bin/sage-env-config' && \ . '$$(SAGE_ROOT)/src/bin/sage-env' && \ . '$$(SAGE_ROOT)/build/bin/sage-build-env-config' && \ . '$$(SAGE_ROOT)/build/bin/sage-build-env' && \ - SAGE_SPKG_WHEELS=$$(SAGE_LOCAL)/var/lib/sage/wheels \ - SAGE_INST_LOCAL=$$(SAGE_LOCAL) \ + SAGE_SPKG_WHEELS=$$($(4))/var/lib/sage/wheels \ + SAGE_INST_LOCAL=$$($(4)) \ sage-logger -p '$$(SAGE_ROOT)/build/pkgs/$(1)/spkg-install' '$$(SAGE_LOGS)/$(1)-$(2).log' - touch "$$(INST)/$(1)-$(2)" + touch "$$($(4))/$(SPKG_INST_RELDIR)/$(1)-$(2)" + +$(1)-no-deps: $(1)-$(4)-no-deps -$(1)-uninstall: +$(1)-$(4)-uninstall: -$(AM_V_at)cd '$$(SAGE_ROOT)/build/pkgs/$(1)' && \ + . '$$(SAGE_ROOT)/src/bin/sage-src-env-config' && \ . '$$(SAGE_ROOT)/src/bin/sage-env-config' && \ . '$$(SAGE_ROOT)/src/bin/sage-env' && \ . '$$(SAGE_ROOT)/build/bin/sage-build-env-config' && \ . '$$(SAGE_ROOT)/build/bin/sage-build-env' && \ '$$(SAGE_ROOT)/build/pkgs/$(1)/spkg-uninstall' - -rm -f "$$(INST)/$(1)-$(2)" + -rm -f "$$($(4))/$(SPKG_INST_RELDIR)/$(1)-$(2)" + +$(1)-uninstall: $(1)-$(4)-uninstall .PHONY: $(1) $(1)-uninstall $(1)-build-deps $(1)-no-deps $(1)-clean endef $(foreach pkgname,$(SCRIPT_PACKAGES),\ - $(eval $(call SCRIPT_PACKAGE_templ,$(pkgname),$(vers_$(pkgname)),$(call pkg_deps,$(pkgname))))) + $(foreach tree, $(trees_$(pkgname)), \ + $(eval $(call SCRIPT_PACKAGE_templ,$(pkgname),$(vers_$(pkgname)),$(call pkg_deps,$(pkgname)),$(tree))))) ifdef DEBUG_RULES $(info # Rules for script packages) $(foreach pkgname,$(SCRIPT_PACKAGES),\ - $(info $(call SCRIPT_PACKAGE_templ,$(pkgname),$(vers_$(pkgname)),$(call pkg_deps,$(pkgname))))) + $(foreach tree, $(trees_$(pkgname)), \ + $(info $(call SCRIPT_PACKAGE_templ,$(pkgname),$(vers_$(pkgname)),$(call pkg_deps,$(pkgname)),$(tree))))) endif # sagelib depends on this so that its install script is always executed diff --git a/build/pkgs/arb/checksums.ini b/build/pkgs/arb/checksums.ini index 518dc3adbc3..3352a346073 100644 --- a/build/pkgs/arb/checksums.ini +++ b/build/pkgs/arb/checksums.ini @@ -1,5 +1,5 @@ tarball=arb-VERSION.tar.gz -sha1=e81e2a1dd7f97a4e6c44882ac7814d720eb14081 -md5=10361d31f4b5b6522afb3944424e8abc -cksum=306434042 +sha1=33c069505b30d1668f5d873499e290e517d92c74 +md5=0a60ab546a8ad3d8d46ad9f040aeca98 +cksum=1430425209 upstream_url=https://github.com/fredrik-johansson/arb/archive/VERSION.tar.gz diff --git a/build/pkgs/arb/package-version.txt b/build/pkgs/arb/package-version.txt index a36e9b0906d..ef0f38abe16 100644 --- a/build/pkgs/arb/package-version.txt +++ b/build/pkgs/arb/package-version.txt @@ -1 +1 @@ -2.18.1 +2.19.0 diff --git a/build/pkgs/boost/SPKG.rst b/build/pkgs/boost/SPKG.rst deleted file mode 100644 index 29dea49c992..00000000000 --- a/build/pkgs/boost/SPKG.rst +++ /dev/null @@ -1,23 +0,0 @@ -boost: Portable C++ libraries (full set) -======================================== - -Description ------------ - -Boost provides free peer-reviewed portable C++ source libraries. - -License -------- - -Boost software license (GPL compatible) - - -Upstream Contact ----------------- - -Home page: http://boost.org - -Dependencies ------------- - -None diff --git a/build/pkgs/boost/checksums.ini b/build/pkgs/boost/checksums.ini deleted file mode 100644 index 6c4c0eda123..00000000000 --- a/build/pkgs/boost/checksums.ini +++ /dev/null @@ -1,4 +0,0 @@ -tarball=boost_VERSION.tar.bz2 -sha1=b6b284acde2ad7ed49b44e856955d7b1ea4e9459 -md5=b2dfbd6c717be4a7bb2d88018eaccf75 -cksum=2976203026 diff --git a/build/pkgs/boost/dependencies b/build/pkgs/boost/dependencies deleted file mode 100644 index 6a9b467fe73..00000000000 --- a/build/pkgs/boost/dependencies +++ /dev/null @@ -1,5 +0,0 @@ -iconv zlib - ----------- -All lines of this file are ignored except the first. -It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/boost/distros/arch.txt b/build/pkgs/boost/distros/arch.txt deleted file mode 100644 index d579dbe4edb..00000000000 --- a/build/pkgs/boost/distros/arch.txt +++ /dev/null @@ -1 +0,0 @@ -boost diff --git a/build/pkgs/boost/distros/conda.txt b/build/pkgs/boost/distros/conda.txt deleted file mode 100644 index 93de4003ce3..00000000000 --- a/build/pkgs/boost/distros/conda.txt +++ /dev/null @@ -1 +0,0 @@ -boost-cpp diff --git a/build/pkgs/boost/distros/cygwin.txt b/build/pkgs/boost/distros/cygwin.txt deleted file mode 100644 index 444ab77a410..00000000000 --- a/build/pkgs/boost/distros/cygwin.txt +++ /dev/null @@ -1 +0,0 @@ -libboost-devel diff --git a/build/pkgs/boost/distros/debian.txt b/build/pkgs/boost/distros/debian.txt deleted file mode 100644 index c65aa1f9284..00000000000 --- a/build/pkgs/boost/distros/debian.txt +++ /dev/null @@ -1 +0,0 @@ -libboost-dev diff --git a/build/pkgs/boost/distros/fedora.txt b/build/pkgs/boost/distros/fedora.txt deleted file mode 100644 index fee2552239a..00000000000 --- a/build/pkgs/boost/distros/fedora.txt +++ /dev/null @@ -1 +0,0 @@ -boost-devel diff --git a/build/pkgs/boost/distros/freebsd.txt b/build/pkgs/boost/distros/freebsd.txt deleted file mode 100644 index 1ad13b6e2f0..00000000000 --- a/build/pkgs/boost/distros/freebsd.txt +++ /dev/null @@ -1 +0,0 @@ -devel/boost-libs diff --git a/build/pkgs/boost/distros/homebrew.txt b/build/pkgs/boost/distros/homebrew.txt deleted file mode 100644 index d579dbe4edb..00000000000 --- a/build/pkgs/boost/distros/homebrew.txt +++ /dev/null @@ -1 +0,0 @@ -boost diff --git a/build/pkgs/boost/distros/macports.txt b/build/pkgs/boost/distros/macports.txt deleted file mode 100644 index d579dbe4edb..00000000000 --- a/build/pkgs/boost/distros/macports.txt +++ /dev/null @@ -1 +0,0 @@ -boost diff --git a/build/pkgs/boost/distros/opensuse.txt b/build/pkgs/boost/distros/opensuse.txt deleted file mode 100644 index fee2552239a..00000000000 --- a/build/pkgs/boost/distros/opensuse.txt +++ /dev/null @@ -1 +0,0 @@ -boost-devel diff --git a/build/pkgs/boost/distros/repology.txt b/build/pkgs/boost/distros/repology.txt deleted file mode 100644 index d579dbe4edb..00000000000 --- a/build/pkgs/boost/distros/repology.txt +++ /dev/null @@ -1 +0,0 @@ -boost diff --git a/build/pkgs/boost/distros/slackware.txt b/build/pkgs/boost/distros/slackware.txt deleted file mode 100644 index d579dbe4edb..00000000000 --- a/build/pkgs/boost/distros/slackware.txt +++ /dev/null @@ -1 +0,0 @@ -boost diff --git a/build/pkgs/boost/package-version.txt b/build/pkgs/boost/package-version.txt deleted file mode 100644 index 4340647cb21..00000000000 --- a/build/pkgs/boost/package-version.txt +++ /dev/null @@ -1 +0,0 @@ -1_66_0 diff --git a/build/pkgs/boost/spkg-configure.m4 b/build/pkgs/boost/spkg-configure.m4 deleted file mode 100644 index 73d231f15a6..00000000000 --- a/build/pkgs/boost/spkg-configure.m4 +++ /dev/null @@ -1,11 +0,0 @@ -SAGE_SPKG_CONFIGURE([boost], [ - SAGE_SPKG_DEPCHECK([boost_cropped], [ - AC_RUN_IFELSE([dnl an extra sanity check - AC_LANG_PROGRAM( - [[#include - ]], [[ - boost::program_options::error err("Error message"); - return 0; - ]])], [], [sage_spkg_install_boost=yes]) - ]) -]) diff --git a/build/pkgs/boost/spkg-install.in b/build/pkgs/boost/spkg-install.in deleted file mode 100644 index 348a434de5d..00000000000 --- a/build/pkgs/boost/spkg-install.in +++ /dev/null @@ -1,53 +0,0 @@ -cd src - -echo "Running boost bootstrap" -# We provide the toolset as the only toolset properly auto-detected are -# gcc (or icc) on linux -# clang on OS X -# but not clang on linux for example -# First sanitize toolset name from CC, it assume something of the form -# CC=/path/to/CC_name-version at worst. -# Please do not mix CC and CXX from different vendors. -C_COMPILER_NAME=${CC##*/} -C_COMPILER_NAME=${C_COMPILER_NAME%-*} -BOOST_TOOLSET="" -if [ "$UNAME" = "Darwin" ]; then - # On darwin provided the compiler is clang, default is fine. - # Remember that /usr/bin/gcc is clang. - if [ "$(which $C_COMPILER_NAME)" != "/usr/bin/gcc" -a "$C_COMPILER_NAME" != "clang" ]; then - BOOST_TOOLSET="--with-toolset=$C_COMPILER_NAME" - fi -else - BOOST_TOOLSET="--with-toolset=$C_COMPILER_NAME" -fi - -./bootstrap.sh $BOOST_TOOLSET -if [[ $? -ne 0 ]]; then - echo >&2 "Failed to bootstrap boost." - exit 1 -fi - -echo "Building boost" -# By default this is populated by a system value. -# If the boost build system (b2, bjam and associated files under /usr/share/boost-build) -# has been installed system wide it can cause interference. -# The build will fail purely and simply without much of an explanation. -# see http://trac.sagemath.org/ticket/20776 for details. -export BOOST_BUILD_PATH="${SAGE_LOCAL}"/share/boost-build -./b2 -if [[ $? -ne 0 ]]; then - echo >&2 "Failed to build boost." - exit 1 -fi - -echo "Clean out old boost headers and libraries" -rm -rf "$SAGE_LOCAL"/include/boost -rm -rf "$SAGE_LOCAL"/lib/libboost* - -echo "Installing boost" -./b2 install --prefix="$SAGE_LOCAL" -if [[ $? -ne 0 ]]; then - echo >&2 "Failed to install boost." - exit 1 -fi - diff --git a/build/pkgs/boost/distros/nix.txt b/build/pkgs/boost_cropped/distros/nix.txt similarity index 100% rename from build/pkgs/boost/distros/nix.txt rename to build/pkgs/boost_cropped/distros/nix.txt diff --git a/build/pkgs/boost/distros/void.txt b/build/pkgs/boost_cropped/distros/void.txt similarity index 100% rename from build/pkgs/boost/distros/void.txt rename to build/pkgs/boost_cropped/distros/void.txt diff --git a/build/pkgs/bzip2/distros/homebrew.txt b/build/pkgs/bzip2/distros/homebrew.txt new file mode 100644 index 00000000000..7a457127148 --- /dev/null +++ b/build/pkgs/bzip2/distros/homebrew.txt @@ -0,0 +1 @@ +bzip2 diff --git a/build/pkgs/cbc/distros/homebrew.txt b/build/pkgs/cbc/distros/homebrew.txt new file mode 100644 index 00000000000..8cc795edd99 --- /dev/null +++ b/build/pkgs/cbc/distros/homebrew.txt @@ -0,0 +1 @@ +cbc diff --git a/build/pkgs/ccache/distros/homebrew.txt b/build/pkgs/ccache/distros/homebrew.txt new file mode 100644 index 00000000000..812b9efc0c5 --- /dev/null +++ b/build/pkgs/ccache/distros/homebrew.txt @@ -0,0 +1 @@ +ccache diff --git a/build/pkgs/cddlib/distros/homebrew.txt b/build/pkgs/cddlib/distros/homebrew.txt new file mode 100644 index 00000000000..11ade1bfb9a --- /dev/null +++ b/build/pkgs/cddlib/distros/homebrew.txt @@ -0,0 +1,3 @@ +# Until https://trac.sagemath.org/ticket/29413 is done, we cannot use homebrew's cddlib, +# which already uses the new upstream include header locations. +#cddlib diff --git a/build/pkgs/configure/checksums.ini b/build/pkgs/configure/checksums.ini index a39456c6909..5b81d62ba6e 100644 --- a/build/pkgs/configure/checksums.ini +++ b/build/pkgs/configure/checksums.ini @@ -1,4 +1,4 @@ tarball=configure-VERSION.tar.gz -sha1=2d5d2d8a2a6dbc721935f6b3d3ada9a6bd977e19 -md5=d84b8fcb2ddd6949d3461f49fae01702 -cksum=2508666370 +sha1=fc049bba332acf1d0e40275f20cdbcb734ad5bb3 +md5=971a6e2f0f92060a85aefa4130ddc287 +cksum=2143691469 diff --git a/build/pkgs/configure/package-version.txt b/build/pkgs/configure/package-version.txt index b644f6ab59f..0720b669a7c 100644 --- a/build/pkgs/configure/package-version.txt +++ b/build/pkgs/configure/package-version.txt @@ -1 +1 @@ -3f4547f2cc40338ba42329d53f7fb48c31ff9fc8 +9e7b0bd3124630ebf368909dbcea7c2480ea1f27 diff --git a/build/pkgs/cryptominisat/distros/homebrew.txt b/build/pkgs/cryptominisat/distros/homebrew.txt new file mode 100644 index 00000000000..5ba98aa9061 --- /dev/null +++ b/build/pkgs/cryptominisat/distros/homebrew.txt @@ -0,0 +1 @@ +cryptominisat diff --git a/build/pkgs/curl/distros/homebrew.txt b/build/pkgs/curl/distros/homebrew.txt new file mode 100644 index 00000000000..13368f82902 --- /dev/null +++ b/build/pkgs/curl/distros/homebrew.txt @@ -0,0 +1 @@ +curl diff --git a/build/pkgs/cypari/install-requires.txt b/build/pkgs/cypari/install-requires.txt index 0e39055cdb2..a10714cc59f 100644 --- a/build/pkgs/cypari/install-requires.txt +++ b/build/pkgs/cypari/install-requires.txt @@ -1 +1 @@ -cypari >=2.1.1 +cypari2 >=2.1.1 diff --git a/build/pkgs/cython/distros/homebrew.txt b/build/pkgs/cython/distros/homebrew.txt new file mode 100644 index 00000000000..f6629e02456 --- /dev/null +++ b/build/pkgs/cython/distros/homebrew.txt @@ -0,0 +1 @@ +cython diff --git a/build/pkgs/docutils/distros/homebrew.txt b/build/pkgs/docutils/distros/homebrew.txt new file mode 100644 index 00000000000..5492d76709b --- /dev/null +++ b/build/pkgs/docutils/distros/homebrew.txt @@ -0,0 +1 @@ +docutils diff --git a/build/pkgs/ecl/distros/homebrew.txt b/build/pkgs/ecl/distros/homebrew.txt new file mode 100644 index 00000000000..100aa2efb32 --- /dev/null +++ b/build/pkgs/ecl/distros/homebrew.txt @@ -0,0 +1 @@ +ecl diff --git a/build/pkgs/flint/checksums.ini b/build/pkgs/flint/checksums.ini index 766b27eb8fc..04b3653585a 100644 --- a/build/pkgs/flint/checksums.ini +++ b/build/pkgs/flint/checksums.ini @@ -1,5 +1,5 @@ tarball=flint-VERSION.tar.gz -sha1=7d3c6046d87e0ee143d217c76fb8e2e196c36019 -md5=ed3a6cab37fe2298d9cfaead6ccd1dc7 -cksum=2609134913 +sha1=0e095e667ed4424e2280c49a9e6a513d12622823 +md5=05a5f77732a05b590972d2349e6f6bb0 +cksum=2043627147 upstream_url=http://flintlib.org/flint-VERSION.tar.gz diff --git a/build/pkgs/flint/package-version.txt b/build/pkgs/flint/package-version.txt index ec1cf33c3f6..860487ca19c 100644 --- a/build/pkgs/flint/package-version.txt +++ b/build/pkgs/flint/package-version.txt @@ -1 +1 @@ -2.6.3 +2.7.1 diff --git a/build/pkgs/freetype/spkg-install.in b/build/pkgs/freetype/spkg-install.in index 1c33c68b4ef..c1615626e72 100644 --- a/build/pkgs/freetype/spkg-install.in +++ b/build/pkgs/freetype/spkg-install.in @@ -1,7 +1,7 @@ cd src # Enable the now-deprecated `freetype-config` -GNUMAKE=${MAKE} sdh_configure --enable-freetype-config +GNUMAKE=${MAKE} sdh_configure --enable-freetype-config $FREETYPE_CONFIGURE sdh_make sdh_make_install diff --git a/build/pkgs/gambit/distros/homebrew.txt b/build/pkgs/gambit/distros/homebrew.txt new file mode 100644 index 00000000000..c08942b85ca --- /dev/null +++ b/build/pkgs/gambit/distros/homebrew.txt @@ -0,0 +1 @@ +gambit diff --git a/build/pkgs/gcc/checksums.ini b/build/pkgs/gcc/checksums.ini index fa41fb6d0c6..b7bcc1ffafe 100644 --- a/build/pkgs/gcc/checksums.ini +++ b/build/pkgs/gcc/checksums.ini @@ -1,4 +1,5 @@ tarball=gcc-VERSION.tar.xz -sha1=306d27c3465fa36862c206738d06d65fff5c3645 -md5=3818ad8600447f05349098232c2ddc78 -cksum=1570410509 +sha1=fb51ed1660c065898c75951fb38e1ebad7d49feb +md5=443c15b92614a3ce8f22e3b24ca2226a +cksum=990531701 +upstream_url=https://mirrors.kernel.org/gnu/gcc/gcc-VERSION/gcc-VERSION.tar.xz diff --git a/build/pkgs/gcc/distros/homebrew.txt b/build/pkgs/gcc/distros/homebrew.txt new file mode 100644 index 00000000000..90584dda5b9 --- /dev/null +++ b/build/pkgs/gcc/distros/homebrew.txt @@ -0,0 +1 @@ +gcc diff --git a/build/pkgs/gcc/package-version.txt b/build/pkgs/gcc/package-version.txt index deeb3d66ef0..0719d810258 100644 --- a/build/pkgs/gcc/package-version.txt +++ b/build/pkgs/gcc/package-version.txt @@ -1 +1 @@ -9.2.0 +10.3.0 diff --git a/build/pkgs/gcc/patches/35_all_glibc-2.31-libsanitizer-1.patch b/build/pkgs/gcc/patches/35_all_glibc-2.31-libsanitizer-1.patch deleted file mode 100644 index 4906ae04511..00000000000 --- a/build/pkgs/gcc/patches/35_all_glibc-2.31-libsanitizer-1.patch +++ /dev/null @@ -1,40 +0,0 @@ -https://bugs.gentoo.org/708346 - -From ce9568e9e9cf6094be30e748821421e703754ffc Mon Sep 17 00:00:00 2001 -From: Jakub Jelinek -Date: Fri, 8 Nov 2019 19:53:18 +0100 -Subject: [PATCH] backport: re PR sanitizer/92154 (new glibc breaks arm - bootstrap due to libsanitizer) - - Backported from mainline - 2019-10-22 Tamar Christina - - PR sanitizer/92154 - * sanitizer_common/sanitizer_platform_limits_posix.cc: - Cherry-pick compiler-rt revision r375220. - -From-SVN: r277981 ---- - libsanitizer/ChangeLog | 9 +++++++++ - .../sanitizer_common/sanitizer_platform_limits_posix.cc | 6 +++++- - 2 files changed, 14 insertions(+), 1 deletion(-) - ---- a/libsanitizer/sanitizer_common/sanitizer_platform_limits_posix.cc -+++ b/libsanitizer/sanitizer_common/sanitizer_platform_limits_posix.cc -@@ -1156,8 +1156,12 @@ CHECK_SIZE_AND_OFFSET(ipc_perm, uid); - CHECK_SIZE_AND_OFFSET(ipc_perm, gid); - CHECK_SIZE_AND_OFFSET(ipc_perm, cuid); - CHECK_SIZE_AND_OFFSET(ipc_perm, cgid); --#if !defined(__aarch64__) || !SANITIZER_LINUX || __GLIBC_PREREQ (2, 21) -+#if (!defined(__aarch64__) || !SANITIZER_LINUX || __GLIBC_PREREQ (2, 21)) && \ -+ !defined(__arm__) - /* On aarch64 glibc 2.20 and earlier provided incorrect mode field. */ -+/* On Arm glibc 2.31 and later provide a different mode field, this field is -+ never used by libsanitizer so we can simply ignore this assert for all glibc -+ versions. */ - CHECK_SIZE_AND_OFFSET(ipc_perm, mode); - #endif - --- -2.25.0 - diff --git a/build/pkgs/gcc/patches/36_all_glibc-2.31-libsanitizer-2.patch b/build/pkgs/gcc/patches/36_all_glibc-2.31-libsanitizer-2.patch deleted file mode 100644 index 1960a11290f..00000000000 --- a/build/pkgs/gcc/patches/36_all_glibc-2.31-libsanitizer-2.patch +++ /dev/null @@ -1,76 +0,0 @@ -https://bugs.gentoo.org/708346 - -From 75003cdd23c310ec385344e8040d490e8dd6d2be Mon Sep 17 00:00:00 2001 -From: Jakub Jelinek -Date: Fri, 20 Dec 2019 17:58:35 +0100 -Subject: [PATCH] backport: re PR sanitizer/92154 (new glibc breaks arm - bootstrap due to libsanitizer) - - Backported from mainline - 2019-11-26 Jakub Jelinek - - PR sanitizer/92154 - * sanitizer_common/sanitizer_platform_limits_posix.h: Cherry-pick - llvm-project revision 947f9692440836dcb8d88b74b69dd379d85974ce. - * sanitizer_common/sanitizer_platform_limits_posix.cc: Likewise. - -From-SVN: r279653 ---- - libsanitizer/ChangeLog | 10 ++++++++++ - .../sanitizer_platform_limits_posix.cc | 9 +++------ - .../sanitizer_platform_limits_posix.h | 15 +-------------- - 3 files changed, 14 insertions(+), 20 deletions(-) - ---- a/libsanitizer/sanitizer_common/sanitizer_platform_limits_posix.cc -+++ b/libsanitizer/sanitizer_common/sanitizer_platform_limits_posix.cc -@@ -1156,12 +1156,9 @@ CHECK_SIZE_AND_OFFSET(ipc_perm, uid); - CHECK_SIZE_AND_OFFSET(ipc_perm, gid); - CHECK_SIZE_AND_OFFSET(ipc_perm, cuid); - CHECK_SIZE_AND_OFFSET(ipc_perm, cgid); --#if (!defined(__aarch64__) || !SANITIZER_LINUX || __GLIBC_PREREQ (2, 21)) && \ -- !defined(__arm__) --/* On aarch64 glibc 2.20 and earlier provided incorrect mode field. */ --/* On Arm glibc 2.31 and later provide a different mode field, this field is -- never used by libsanitizer so we can simply ignore this assert for all glibc -- versions. */ -+#if !SANITIZER_LINUX || __GLIBC_PREREQ (2, 31) -+/* glibc 2.30 and earlier provided 16-bit mode field instead of 32-bit -+ on many architectures. */ - CHECK_SIZE_AND_OFFSET(ipc_perm, mode); - #endif - -diff --git a/libsanitizer/sanitizer_common/sanitizer_platform_limits_posix.h b/libsanitizer/sanitizer_common/sanitizer_platform_limits_posix.h -index 73af92af1e8..6a673a7c995 100644 ---- a/libsanitizer/sanitizer_common/sanitizer_platform_limits_posix.h -+++ b/libsanitizer/sanitizer_common/sanitizer_platform_limits_posix.h -@@ -211,26 +211,13 @@ namespace __sanitizer { - u64 __unused1; - u64 __unused2; - #elif defined(__sparc__) --#if defined(__arch64__) - unsigned mode; -- unsigned short __pad1; --#else -- unsigned short __pad1; -- unsigned short mode; - unsigned short __pad2; --#endif - unsigned short __seq; - unsigned long long __unused1; - unsigned long long __unused2; --#elif defined(__mips__) || defined(__aarch64__) || defined(__s390x__) -- unsigned int mode; -- unsigned short __seq; -- unsigned short __pad1; -- unsigned long __unused1; -- unsigned long __unused2; - #else -- unsigned short mode; -- unsigned short __pad1; -+ unsigned int mode; - unsigned short __seq; - unsigned short __pad2; - #if defined(__x86_64__) && !defined(_LP64) --- -2.25.0 - diff --git a/build/pkgs/gcc/patches/homebrew-9.2.0-catalina.patch b/build/pkgs/gcc/patches/homebrew-9.2.0-catalina.patch deleted file mode 100644 index 6d0201df21f..00000000000 --- a/build/pkgs/gcc/patches/homebrew-9.2.0-catalina.patch +++ /dev/null @@ -1,139 +0,0 @@ -diff -pur gcc-9.2.0/fixincludes/fixincl.x gcc-9.2.0-fixed/fixincludes/fixincl.x ---- gcc-9.2.0/fixincludes/fixincl.x 2019-08-03 21:21:08.000000000 +0200 -+++ gcc-9.2.0-fixed/fixincludes/fixincl.x 2019-10-01 11:43:32.000000000 +0200 -@@ -2,11 +2,11 @@ - * - * DO NOT EDIT THIS FILE (fixincl.x) - * -- * It has been AutoGen-ed July 7, 2019 at 11:43:37 AM by AutoGen 5.17.4 -+ * It has been AutoGen-ed October 1, 2019 at 11:43:32 AM by AutoGen 5.18.16 - * From the definitions inclhack.def - * and the template file fixincl - */ --/* DO NOT SVN-MERGE THIS FILE, EITHER Sun Jul 7 11:43:37 BST 2019 -+/* DO NOT SVN-MERGE THIS FILE, EITHER Tue Oct 1 11:43:32 CEST 2019 - * - * You must regenerate it. Use the ./genfixes script. - * -@@ -15,7 +15,7 @@ - * certain ANSI-incompatible system header files which are fixed to work - * correctly with ANSI C and placed in a directory that GNU C will search. - * -- * This file contains 255 fixup descriptions. -+ * This file contains 256 fixup descriptions. - * - * See README for more information. - * -@@ -2636,6 +2636,48 @@ static const char* apzDarwin_Availabilit - - /* * * * * * * * * * * * * * * * * * * * * * * * * * - * -+ * Description of Darwin_Availability fix -+ */ -+tSCC zDarwin_AvailabilityName[] = -+ "darwin_availability"; -+ -+/* -+ * File name selection pattern -+ */ -+tSCC zDarwin_AvailabilityList[] = -+ "Availability.h\0"; -+/* -+ * Machine/OS name selection pattern -+ */ -+tSCC* apzDarwin_AvailabilityMachs[] = { -+ "*-*-darwin*", -+ (const char*)NULL }; -+ -+/* -+ * content selection pattern - do fix if pattern found -+ */ -+tSCC zDarwin_AvailabilitySelect0[] = -+ "#endif /\\* __OSX_AVAILABLE_STARTING \\*/"; -+ -+#define DARWIN_AVAILABILITY_TEST_CT 1 -+static tTestDesc aDarwin_AvailabilityTests[] = { -+ { TT_EGREP, zDarwin_AvailabilitySelect0, (regex_t*)NULL }, }; -+ -+/* -+ * Fix Command Arguments for Darwin_Availability -+ */ -+static const char* apzDarwin_AvailabilityPatch[] = { -+ "format", -+ "#endif /* __OSX_AVAILABLE_STARTING */\n\ -+#ifndef __OSX_AVAILABLE_STARTING\n\ -+ #define __OSX_AVAILABLE_STARTING(_osx, _ios)\n\ -+ #define __OSX_AVAILABLE_BUT_DEPRECATED(_osxIntro, _osxDep, _iosIntro, _iosDep)\n\ -+ #define __OSX_AVAILABLE_BUT_DEPRECATED_MSG(_osxIntro, _osxDep, _iosIntro, _iosDep, _msg)\n\ -+#endif", -+ (char*)NULL }; -+ -+/* * * * * * * * * * * * * * * * * * * * * * * * * * -+ * - * Description of Darwin_9_Long_Double_Funcs_2 fix - */ - tSCC zDarwin_9_Long_Double_Funcs_2Name[] = -@@ -10346,9 +10388,9 @@ static const char* apzX11_SprintfPatch[] - * - * List of all fixes - */ --#define REGEX_COUNT 293 -+#define REGEX_COUNT 294 - #define MACH_LIST_SIZE_LIMIT 187 --#define FIX_COUNT 255 -+#define FIX_COUNT 256 - - /* - * Enumerate the fixes -@@ -10416,6 +10458,7 @@ typedef enum { - CTRL_QUOTES_USE_FIXIDX, - CXX_UNREADY_FIXIDX, - DARWIN_AVAILABILITYINTERNAL_FIXIDX, -+ DARWIN_AVAILABILITY_FIXIDX, - DARWIN_9_LONG_DOUBLE_FUNCS_2_FIXIDX, - DARWIN_EXTERNC_FIXIDX, - DARWIN_GCC4_BREAKAGE_FIXIDX, -@@ -10922,6 +10965,11 @@ tFixDesc fixDescList[ FIX_COUNT ] = { - DARWIN_AVAILABILITYINTERNAL_TEST_CT, FD_MACH_ONLY | FD_SUBROUTINE, - aDarwin_AvailabilityinternalTests, apzDarwin_AvailabilityinternalPatch, 0 }, - -+ { zDarwin_AvailabilityName, zDarwin_AvailabilityList, -+ apzDarwin_AvailabilityMachs, -+ DARWIN_AVAILABILITY_TEST_CT, FD_MACH_ONLY | FD_SUBROUTINE, -+ aDarwin_AvailabilityTests, apzDarwin_AvailabilityPatch, 0 }, -+ - { zDarwin_9_Long_Double_Funcs_2Name, zDarwin_9_Long_Double_Funcs_2List, - apzDarwin_9_Long_Double_Funcs_2Machs, - DARWIN_9_LONG_DOUBLE_FUNCS_2_TEST_CT, FD_MACH_ONLY | FD_SUBROUTINE, -diff -pur gcc-9.2.0/fixincludes/inclhack.def gcc-9.2.0-fixed/fixincludes/inclhack.def ---- gcc-9.2.0/fixincludes/inclhack.def 2019-08-03 21:21:08.000000000 +0200 -+++ gcc-9.2.0-fixed/fixincludes/inclhack.def 2019-10-01 11:43:30.000000000 +0200 -@@ -1298,6 +1298,28 @@ fix = { - }; - - /* -+ * macOS 10.15 does not define __OSX_AVAILABLE_STARTING on -+ * non-clang compilers. -+ */ -+fix = { -+ hackname = darwin_availability; -+ mach = "*-*-darwin*"; -+ files = Availability.h; -+ select = "#endif /\\* __OSX_AVAILABLE_STARTING \\*/"; -+ c_fix = format; -+ c_fix_arg = <<- _EOFix_ -+ #endif /* __OSX_AVAILABLE_STARTING */ -+ #ifndef __OSX_AVAILABLE_STARTING -+ #define __OSX_AVAILABLE_STARTING(_osx, _ios) -+ #define __OSX_AVAILABLE_BUT_DEPRECATED(_osxIntro, _osxDep, _iosIntro, _iosDep) -+ #define __OSX_AVAILABLE_BUT_DEPRECATED_MSG(_osxIntro, _osxDep, _iosIntro, _iosDep, _msg) -+ #endif -+ _EOFix_; -+ -+ test_text = "#endif /* __OSX_AVAILABLE_STARTING */"; -+}; -+ -+/* - * For the AAB_darwin7_9_long_double_funcs fix (and later fixes for long long) - * to be useful, the main math.h must use <> and not "" includes. - */ diff --git a/build/pkgs/gcc/spkg-configure.m4 b/build/pkgs/gcc/spkg-configure.m4 index 9a1c92e78c7..eb90e6e2c40 100644 --- a/build/pkgs/gcc/spkg-configure.m4 +++ b/build/pkgs/gcc/spkg-configure.m4 @@ -220,6 +220,20 @@ SAGE_SPKG_CONFIGURE_BASE([gcc], [ fi AC_SUBST(CFLAGS_MARCH) + # Determine wether compiler supports OpenMP. + AC_LANG_PUSH([C]) + AX_OPENMP([ + AC_SUBST(OPENMP_CFLAGS) + ]) + AC_LANG_POP() + + AC_LANG_PUSH([C++]) + AX_OPENMP([ + AC_SUBST(OPENMP_CXXFLAGS) + ]) + AC_LANG_POP() + + ], , , [ # Trac #27907: Find location of crti.o from the system CC, in case we build our own gcc AC_MSG_CHECKING([for the location of crti.o]) diff --git a/build/pkgs/gdb/distros/homebrew.txt b/build/pkgs/gdb/distros/homebrew.txt new file mode 100644 index 00000000000..59ccb367d89 --- /dev/null +++ b/build/pkgs/gdb/distros/homebrew.txt @@ -0,0 +1 @@ +gdb diff --git a/build/pkgs/gfortran/spkg-configure.m4 b/build/pkgs/gfortran/spkg-configure.m4 index e867f39ff48..d2887976f95 100644 --- a/build/pkgs/gfortran/spkg-configure.m4 +++ b/build/pkgs/gfortran/spkg-configure.m4 @@ -86,9 +86,9 @@ SAGE_SPKG_CONFIGURE([gfortran], [ # Install our own gfortran if the system-provided one is older than gcc-4.8. SAGE_SHOULD_INSTALL_GFORTRAN([$FC is version $GFORTRAN_VERSION, which is quite old]) ], - [1[[1-9]].*], [ - # Install our own gfortran if the system-provided one is newer than 10.x. - # See https://trac.sagemath.org/ticket/29456 + [1[[2-9]].*], [ + # Install our own gfortran if the system-provided one is newer than 11.x. + # See https://trac.sagemath.org/ticket/29456, https://trac.sagemath.org/ticket/31838 SAGE_MUST_INSTALL_GFORTRAN([$FC is version $GFORTRAN_VERSION, which is too recent for this version of Sage]) ]) ]) diff --git a/build/pkgs/giac/checksums.ini b/build/pkgs/giac/checksums.ini index a21700adaea..f01435d5423 100644 --- a/build/pkgs/giac/checksums.ini +++ b/build/pkgs/giac/checksums.ini @@ -1,5 +1,5 @@ tarball=giac-VERSION.tar.bz2 -sha1=bc77da05dcad6467e22ab2ab5939230013dadd80 -md5=3fd5817179e18bc74eab9a7ea0dfd939 -cksum=1776117665 -upstream_url=https://trac.sagemath.org/raw-attachment/ticket/30537/giac-1.6.0.47p2.tar.bz2 +sha1=b0e81969eb2527964efc802bf35c31d140031571 +md5=f8253082e5dcde5724b4d6f2300d8669 +cksum=3973759340 +upstream_url=https://trac.sagemath.org/raw-attachment/ticket/31562/giac-VERSION.tar.bz2 diff --git a/build/pkgs/giac/package-version.txt b/build/pkgs/giac/package-version.txt index 69c8fb9d4d2..dc9d0d659ba 100644 --- a/build/pkgs/giac/package-version.txt +++ b/build/pkgs/giac/package-version.txt @@ -1 +1 @@ -1.6.0.47p2 +1.6.0.47p3 diff --git a/build/pkgs/giac/patches/autotools/0001-configure.ac-Do-not-link-to-libintl-if-USE_NLS-is-no.patch b/build/pkgs/giac/patches/autotools/0001-configure.ac-Do-not-link-to-libintl-if-USE_NLS-is-no.patch new file mode 100644 index 00000000000..8d838f8bd32 --- /dev/null +++ b/build/pkgs/giac/patches/autotools/0001-configure.ac-Do-not-link-to-libintl-if-USE_NLS-is-no.patch @@ -0,0 +1,30 @@ +From 2285c11b3cfebc31aa2c032015c76820e3a62030 Mon Sep 17 00:00:00 2001 +From: Matthias Koeppe +Date: Thu, 25 Mar 2021 18:00:38 -0700 +Subject: [PATCH] configure.ac: Do not link to libintl if USE_NLS is no + +--- + configure.ac | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/configure.ac b/configure.ac +index bfa767d..43c6ff9 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -464,7 +464,12 @@ AC_CHECK_FUNCS(system, , AC_DEFINE(HAVE_NO_SYSTEM, 1, [Set if system() function + ALL_LINGUAS="es fr el pt it en zh de" + AM_GNU_GETTEXT + AM_GNU_GETTEXT_VERSION([0.14.5]) +-AC_CHECK_LIB(intl, main) ++AS_VAR_IF([USE_NLS], [yes], ++ dnl Whether this is needed at all, after all the AM_GNU_GETTEXT macros ++ dnl were run, is unknown. But at least we should disable it if NLS ++ dnl is disabled. ++ AC_CHECK_LIB(intl, main) ++])dnl + dnl auto-check will work if the function checked is alone in a file + dnl and independant from the whole micropython library + dnl otherwise it will fail because it depends on giac +-- +2.28.0 + diff --git a/build/pkgs/giac/spkg-src b/build/pkgs/giac/spkg-src index 3da68c43db7..064c7a092e0 100755 --- a/build/pkgs/giac/spkg-src +++ b/build/pkgs/giac/spkg-src @@ -15,7 +15,7 @@ set -e VERSION="1.6.0" VERSIONREV="47" -PATCHSUFFIX="p2" +PATCHSUFFIX="p3" # The upstream tarball name is: giac"$SOURCEORIG".tar.gz SOURCEORIG=_"$VERSION"-"$VERSIONREV" diff --git a/build/pkgs/gmpy2/install-requires.txt b/build/pkgs/gmpy2/install-requires.txt index fc36c408dd0..c91a47224c1 100644 --- a/build/pkgs/gmpy2/install-requires.txt +++ b/build/pkgs/gmpy2/install-requires.txt @@ -1 +1,3 @@ -gmpy2 >=2.1.0b5 +# We would like to write gmpy2 >=2.1.0b5, but pipenv does not accept prereleases in version ranges +# https://github.com/pypa/pipenv/issues/1760 +gmpy2 ==2.1.0b5 diff --git a/build/pkgs/iconv/distros/homebrew.txt b/build/pkgs/iconv/distros/homebrew.txt new file mode 100644 index 00000000000..788f2359abe --- /dev/null +++ b/build/pkgs/iconv/distros/homebrew.txt @@ -0,0 +1 @@ +libiconv diff --git a/build/pkgs/ipympl/SPKG.rst b/build/pkgs/ipympl/SPKG.rst new file mode 100644 index 00000000000..8f33c1bb303 --- /dev/null +++ b/build/pkgs/ipympl/SPKG.rst @@ -0,0 +1,18 @@ +ipympl: Matplotlib Jupyter Extension +==================================== + +Description +----------- + +Matplotlib Jupyter Extension + +License +------- + +BSD License + +Upstream Contact +---------------- + +https://pypi.org/project/ipympl/ + diff --git a/build/pkgs/ipympl/checksums.ini b/build/pkgs/ipympl/checksums.ini new file mode 100644 index 00000000000..90651c38610 --- /dev/null +++ b/build/pkgs/ipympl/checksums.ini @@ -0,0 +1,5 @@ +tarball=ipympl-VERSION.tar.gz +sha1=66cbf694fd65947707f40053275ac9d3883625c0 +md5=6b9eb72b9c6b30aa0860b8f353f08d03 +cksum=2800352482 +upstream_url=https://pypi.io/packages/source/i/ipympl/ipympl-VERSION.tar.gz diff --git a/build/pkgs/ipympl/dependencies b/build/pkgs/ipympl/dependencies new file mode 100644 index 00000000000..56687b4dda0 --- /dev/null +++ b/build/pkgs/ipympl/dependencies @@ -0,0 +1,6 @@ +$(PYTHON) | $(PYTHON_TOOLCHAIN) ipywidgets matplotlib ipykernel jupyter_packaging $(findstring jupyterlab,$(OPTIONAL_INSTALLED_PACKAGES)) + +---------- +All lines of this file are ignored except the first. + +jupyterlab is listed as a "build-system requires", but the package installs correctly without it. diff --git a/build/pkgs/ipympl/install-requires.txt b/build/pkgs/ipympl/install-requires.txt new file mode 100644 index 00000000000..d715db054cd --- /dev/null +++ b/build/pkgs/ipympl/install-requires.txt @@ -0,0 +1 @@ +ipympl diff --git a/build/pkgs/ipympl/package-version.txt b/build/pkgs/ipympl/package-version.txt new file mode 100644 index 00000000000..844f6a91acb --- /dev/null +++ b/build/pkgs/ipympl/package-version.txt @@ -0,0 +1 @@ +0.6.3 diff --git a/build/pkgs/ipympl/spkg-install.in b/build/pkgs/ipympl/spkg-install.in new file mode 100644 index 00000000000..37ac1a53437 --- /dev/null +++ b/build/pkgs/ipympl/spkg-install.in @@ -0,0 +1,2 @@ +cd src +sdh_pip_install . diff --git a/build/pkgs/boost/type b/build/pkgs/ipympl/type similarity index 100% rename from build/pkgs/boost/type rename to build/pkgs/ipympl/type diff --git a/build/pkgs/ipython/distros/homebrew.txt b/build/pkgs/ipython/distros/homebrew.txt new file mode 100644 index 00000000000..49a7ffe2a95 --- /dev/null +++ b/build/pkgs/ipython/distros/homebrew.txt @@ -0,0 +1 @@ +ipython diff --git a/build/pkgs/ipywidgets/package-version.txt b/build/pkgs/ipywidgets/package-version.txt index f3b8d5cfed1..8512239657d 100644 --- a/build/pkgs/ipywidgets/package-version.txt +++ b/build/pkgs/ipywidgets/package-version.txt @@ -1 +1 @@ -7.6.3 +7.6.3.p0 diff --git a/build/pkgs/ipywidgets/patches/0001-setup.py-Remove-install-requires-of-jupyterlab_widge.patch b/build/pkgs/ipywidgets/patches/0001-setup.py-Remove-install-requires-of-jupyterlab_widge.patch new file mode 100644 index 00000000000..fed30e5445e --- /dev/null +++ b/build/pkgs/ipywidgets/patches/0001-setup.py-Remove-install-requires-of-jupyterlab_widge.patch @@ -0,0 +1,35 @@ +From e64096431a7099f8db46748ef7d1021939f1a624 Mon Sep 17 00:00:00 2001 +From: Matthias Koeppe +Date: Wed, 24 Mar 2021 12:59:13 -0700 +Subject: [PATCH] setup.py: Remove install-requires of jupyterlab_widgets, + nbformat + +--- + setup.py | 4 ---- + 1 file changed, 4 deletions(-) + +diff --git a/setup.py b/setup.py +index e544267a..bff154af 100644 +--- a/setup.py ++++ b/setup.py +@@ -112,9 +112,6 @@ setuptools_args = {} + install_requires = setuptools_args['install_requires'] = [ + 'ipykernel>=4.5.1', + 'traitlets>=4.3.1', +- # Requiring nbformat to specify bugfix version which is not required by +- # notebook. +- 'nbformat>=4.2.0', + # TODO: Dynamically add this dependency + # only if notebook 4.x is installed in this + # interpreter, to allow ipywidgets to be +@@ -125,7 +122,6 @@ install_requires = setuptools_args['install_requires'] = [ + extras_require = setuptools_args['extras_require'] = { + ':python_version<"3.3"' : ['ipython>=4.0.0,<6.0.0'], + ':python_version>="3.3"': ['ipython>=4.0.0'], +- ':python_version>="3.6"': ['jupyterlab_widgets>=1.0.0'], + 'test:python_version=="2.7"': ['mock'], + 'test': ['pytest>=3.6.0', 'pytest-cov'], + } +-- +2.28.0 + diff --git a/build/pkgs/jupyter_packaging/SPKG.rst b/build/pkgs/jupyter_packaging/SPKG.rst new file mode 100644 index 00000000000..7e6a0bcfd8a --- /dev/null +++ b/build/pkgs/jupyter_packaging/SPKG.rst @@ -0,0 +1,18 @@ +jupyter_packaging: Jupyter Packaging Utilities +============================================== + +Description +----------- + +Jupyter Packaging Utilities + +License +------- + +BSD + +Upstream Contact +---------------- + +https://pypi.org/project/jupyter-packaging/ + diff --git a/build/pkgs/jupyter_packaging/checksums.ini b/build/pkgs/jupyter_packaging/checksums.ini new file mode 100644 index 00000000000..26572ef4db1 --- /dev/null +++ b/build/pkgs/jupyter_packaging/checksums.ini @@ -0,0 +1,5 @@ +tarball=jupyter-packaging-VERSION.tar.gz +sha1=4ef13ad7b39a15d7315209d3703db4409976140d +md5=b56e58fb5d9e79af0867bb450f3e9478 +cksum=762335949 +upstream_url=https://pypi.io/packages/source/j/jupyter_packaging/jupyter-packaging-VERSION.tar.gz diff --git a/build/pkgs/jupyter_packaging/dependencies b/build/pkgs/jupyter_packaging/dependencies new file mode 100644 index 00000000000..91a41b84d7d --- /dev/null +++ b/build/pkgs/jupyter_packaging/dependencies @@ -0,0 +1,4 @@ +$(PYTHON) packaging | $(PYTHON_TOOLCHAIN) + +---------- +All lines of this file are ignored except the first. diff --git a/build/pkgs/jupyter_packaging/install-requires.txt b/build/pkgs/jupyter_packaging/install-requires.txt new file mode 100644 index 00000000000..b5ec61b7d14 --- /dev/null +++ b/build/pkgs/jupyter_packaging/install-requires.txt @@ -0,0 +1 @@ +jupyter-packaging diff --git a/build/pkgs/jupyter_packaging/package-version.txt b/build/pkgs/jupyter_packaging/package-version.txt new file mode 100644 index 00000000000..88a7b228577 --- /dev/null +++ b/build/pkgs/jupyter_packaging/package-version.txt @@ -0,0 +1 @@ +0.7.12 diff --git a/build/pkgs/jupyter_packaging/spkg-install.in b/build/pkgs/jupyter_packaging/spkg-install.in new file mode 100644 index 00000000000..37ac1a53437 --- /dev/null +++ b/build/pkgs/jupyter_packaging/spkg-install.in @@ -0,0 +1,2 @@ +cd src +sdh_pip_install . diff --git a/build/pkgs/jupyter_packaging/type b/build/pkgs/jupyter_packaging/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/jupyter_packaging/type @@ -0,0 +1 @@ +optional diff --git a/build/pkgs/jupyterlab/distros/homebrew.txt b/build/pkgs/jupyterlab/distros/homebrew.txt new file mode 100644 index 00000000000..c9356a72837 --- /dev/null +++ b/build/pkgs/jupyterlab/distros/homebrew.txt @@ -0,0 +1 @@ +jupyterlab diff --git a/build/pkgs/libffi/distros/homebrew.txt b/build/pkgs/libffi/distros/homebrew.txt new file mode 100644 index 00000000000..eb88b305fdc --- /dev/null +++ b/build/pkgs/libffi/distros/homebrew.txt @@ -0,0 +1 @@ +libffi diff --git a/build/pkgs/libgd/checksums.ini b/build/pkgs/libgd/checksums.ini index 365e6372d74..0db72cded2e 100644 --- a/build/pkgs/libgd/checksums.ini +++ b/build/pkgs/libgd/checksums.ini @@ -1,4 +1,5 @@ -tarball=libgd-VERSION.tar.bz2 -sha1=a36eef615a6fde81140ef4ebaae08663dfbe2b52 -md5=8770bffe87d46ff13819b7e62e1621d3 -cksum=1115143377 +tarball=libgd-VERSION.tar.xz +sha1=dddf5e9d25cb0b20b8642d5cbcfad67f8903532f +md5=0ee844caca06bb02bf4b4dabdfab4fb1 +cksum=902217083 +upstream_url=https://github.com/libgd/libgd/releases/download/gd-VERSION/libgd-VERSION.tar.xz diff --git a/build/pkgs/libgd/dependencies b/build/pkgs/libgd/dependencies index 05041105f3e..998d7d01593 100644 --- a/build/pkgs/libgd/dependencies +++ b/build/pkgs/libgd/dependencies @@ -1,5 +1,6 @@ -libpng freetype +libpng freetype xz +# xz needed to unpack tarball when sage-bootstrap-python is Python < 3.3 ---------- All lines of this file are ignored except the first. It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/libgd/package-version.txt b/build/pkgs/libgd/package-version.txt index 5c1151e094a..f90b1afc082 100644 --- a/build/pkgs/libgd/package-version.txt +++ b/build/pkgs/libgd/package-version.txt @@ -1 +1 @@ -2.1.1.1.p1 +2.3.2 diff --git a/build/pkgs/libgd/patches/ceill.patch b/build/pkgs/libgd/patches/ceill.patch deleted file mode 100644 index e8b32b00381..00000000000 --- a/build/pkgs/libgd/patches/ceill.patch +++ /dev/null @@ -1,18 +0,0 @@ -Do not use (sometimes unavailable) ceill function. - -This makes the build fail on systems where libm does not provide -long long versions of such function. -And this precision doesn't seem needed anyway, see: -* https://bitbucket.org/libgd/gd-libgd/issue/98/gd_bmpc-use-of-both-ceil-and-ceill -diff -druN gd-2.1.0.old/src/gd_bmp.c gd-2.1.0.new/src/gd_bmp.c ---- gd-2.1.0.old/src/gd_bmp.c 2013-06-25 02:58:23.000000000 -0700 -+++ gd-2.1.0.new/src/gd_bmp.c 2014-11-24 08:00:22.913537073 -0800 -@@ -792,7 +792,7 @@ - } - - /* The line must be divisible by 4, else its padded with NULLs */ -- padding = ((int)ceill(0.1 * info->width)) % 4; -+ padding = ((int)ceil(0.1 * info->width)) % 4; - if (padding) { - padding = 4 - padding; - } diff --git a/build/pkgs/libgd/patches/gd-2.1.1-libvpx-1.4.0.patch b/build/pkgs/libgd/patches/gd-2.1.1-libvpx-1.4.0.patch deleted file mode 100644 index c698972539e..00000000000 --- a/build/pkgs/libgd/patches/gd-2.1.1-libvpx-1.4.0.patch +++ /dev/null @@ -1,37 +0,0 @@ -From d41eb72cd4545c394578332e5c102dee69e02ee8 Mon Sep 17 00:00:00 2001 -From: Remi Collet -Date: Tue, 7 Apr 2015 13:11:03 +0200 -Subject: [PATCH] Fix build with latest libvpx 1.4.0 - -These new constants exist at least since 1.0.0 -Compatibility ones have been droped in 1.4.0 ---- - src/webpimg.c | 14 +++++++------- - 1 file changed, 7 insertions(+), 7 deletions(-) - -diff --git a/src/webpimg.c b/src/webpimg.c -index cf73d64..e49fcc6 100644 ---- a/src/webpimg.c -+++ b/src/webpimg.c -@@ -711,14 +711,14 @@ static WebPResult VPXEncode(const uint8* Y, - codec_ctl(&enc, VP8E_SET_STATIC_THRESHOLD, 0); - codec_ctl(&enc, VP8E_SET_TOKEN_PARTITIONS, 2); - -- vpx_img_wrap(&img, IMG_FMT_I420, -+ vpx_img_wrap(&img, VPX_IMG_FMT_I420, - y_width, y_height, 16, (uint8*)(Y)); -- img.planes[PLANE_Y] = (uint8*)(Y); -- img.planes[PLANE_U] = (uint8*)(U); -- img.planes[PLANE_V] = (uint8*)(V); -- img.stride[PLANE_Y] = y_stride; -- img.stride[PLANE_U] = uv_stride; -- img.stride[PLANE_V] = uv_stride; -+ img.planes[VPX_PLANE_Y] = (uint8*)(Y); -+ img.planes[VPX_PLANE_U] = (uint8*)(U); -+ img.planes[VPX_PLANE_V] = (uint8*)(V); -+ img.stride[VPX_PLANE_Y] = y_stride; -+ img.stride[VPX_PLANE_U] = uv_stride; -+ img.stride[VPX_PLANE_V] = uv_stride; - - res = vpx_codec_encode(&enc, &img, 0, 1, 0, VPX_DL_BEST_QUALITY); - diff --git a/build/pkgs/libnauty/dependencies b/build/pkgs/libnauty/dependencies index 4f7f1a035ea..88254f7b556 100644 --- a/build/pkgs/libnauty/dependencies +++ b/build/pkgs/libnauty/dependencies @@ -1,4 +1,4 @@ -$(INST)/nauty-$(vers_nauty) +$(SAGE_LOCAL)/$(SPKG_INST_RELDIR)/nauty-$(vers_nauty) ---------- All lines of this file are ignored except the first. diff --git a/build/pkgs/libnauty/distros/homebrew.txt b/build/pkgs/libnauty/distros/homebrew.txt new file mode 100644 index 00000000000..21c67b1e856 --- /dev/null +++ b/build/pkgs/libnauty/distros/homebrew.txt @@ -0,0 +1 @@ +nauty diff --git a/build/pkgs/libogg/distros/homebrew.txt b/build/pkgs/libogg/distros/homebrew.txt new file mode 100644 index 00000000000..b6a6854a477 --- /dev/null +++ b/build/pkgs/libogg/distros/homebrew.txt @@ -0,0 +1 @@ +libogg diff --git a/build/pkgs/libtheora/distros/homebrew.txt b/build/pkgs/libtheora/distros/homebrew.txt new file mode 100644 index 00000000000..bddb694122d --- /dev/null +++ b/build/pkgs/libtheora/distros/homebrew.txt @@ -0,0 +1 @@ +theora diff --git a/build/pkgs/maxima/distros/homebrew.txt b/build/pkgs/maxima/distros/homebrew.txt new file mode 100644 index 00000000000..f5fe3fdc6cb --- /dev/null +++ b/build/pkgs/maxima/distros/homebrew.txt @@ -0,0 +1 @@ +maxima diff --git a/build/pkgs/mpir/distros/homebrew.txt b/build/pkgs/mpir/distros/homebrew.txt new file mode 100644 index 00000000000..bef94785573 --- /dev/null +++ b/build/pkgs/mpir/distros/homebrew.txt @@ -0,0 +1 @@ +mpir diff --git a/build/pkgs/ncurses/distros/homebrew.txt b/build/pkgs/ncurses/distros/homebrew.txt new file mode 100644 index 00000000000..6a470ffa9e3 --- /dev/null +++ b/build/pkgs/ncurses/distros/homebrew.txt @@ -0,0 +1 @@ +ncurses diff --git a/build/pkgs/nodeenv/distros/homebrew.txt b/build/pkgs/nodeenv/distros/homebrew.txt new file mode 100644 index 00000000000..f69a126dec6 --- /dev/null +++ b/build/pkgs/nodeenv/distros/homebrew.txt @@ -0,0 +1 @@ +nodeenv diff --git a/build/pkgs/nodejs/distros/homebrew.txt b/build/pkgs/nodejs/distros/homebrew.txt new file mode 100644 index 00000000000..64f5a0a6813 --- /dev/null +++ b/build/pkgs/nodejs/distros/homebrew.txt @@ -0,0 +1 @@ +node diff --git a/build/pkgs/numpy/distros/homebrew.txt b/build/pkgs/numpy/distros/homebrew.txt new file mode 100644 index 00000000000..24ce15ab7ea --- /dev/null +++ b/build/pkgs/numpy/distros/homebrew.txt @@ -0,0 +1 @@ +numpy diff --git a/build/pkgs/pari/spkg-install.in b/build/pkgs/pari/spkg-install.in index 2da3fa54e4e..1b0fec6bfd7 100644 --- a/build/pkgs/pari/spkg-install.in +++ b/build/pkgs/pari/spkg-install.in @@ -103,14 +103,6 @@ unset with_gmp_include with_gmp_lib without_gmp unset dfltbindir dfltdatadir dfltemacsdir dfltincludedir unset dfltlibdir dfltmandir dfltsysdatadir dfltobjdir -# Avoid segmentation fault on macOS Catalina with XCode 11.4 -# #29451 -if [ $MACOSX_VERSION -ge 14 ]; then - # various packages have still have issues with - # two digit OS X versions - export MACOSX_DEPLOYMENT_TARGET=10.9 -fi - export CFLAGS=$CFLAGS_O3 if [ "$SAGE_DEBUG" = yes ]; then diff --git a/build/pkgs/pillow/spkg-install.in b/build/pkgs/pillow/spkg-install.in index 86b88f1564d..57be7347493 100644 --- a/build/pkgs/pillow/spkg-install.in +++ b/build/pkgs/pillow/spkg-install.in @@ -14,7 +14,7 @@ if [ "$UNAME" = "Darwin" ] ; then fi if [ "$CONDA_PREFIX" != "" ]; then - extra_build_ext="$extra_build_ext --disable-platform-guessing -I$CONDA_PREFIX/include -L$CONDA_PREFIX/lib" + PILLOW_BUILD_EXT="$PILLOW_BUILD_EXT --disable-platform-guessing -I$CONDA_PREFIX/include -L$CONDA_PREFIX/lib" fi # Note: Avoid shared libraries inside egg files, Trac #19467 @@ -22,6 +22,6 @@ sdh_setup_bdist_wheel \ build_ext \ --debug \ --disable-jpeg \ - $extra_build_ext + $PILLOW_BUILD_EXT sdh_store_and_pip_install_wheel . diff --git a/build/pkgs/pybind11/distros/homebrew.txt b/build/pkgs/pybind11/distros/homebrew.txt new file mode 100644 index 00000000000..e47c59fd7ce --- /dev/null +++ b/build/pkgs/pybind11/distros/homebrew.txt @@ -0,0 +1 @@ +pybind11 diff --git a/build/pkgs/pygments/distros/homebrew.txt b/build/pkgs/pygments/distros/homebrew.txt new file mode 100644 index 00000000000..a9f49e01c86 --- /dev/null +++ b/build/pkgs/pygments/distros/homebrew.txt @@ -0,0 +1 @@ +pygments diff --git a/build/pkgs/python3/distros/homebrew.txt b/build/pkgs/python3/distros/homebrew.txt index b9f04a4ea63..1054bbddaff 100644 --- a/build/pkgs/python3/distros/homebrew.txt +++ b/build/pkgs/python3/distros/homebrew.txt @@ -1,2 +1,2 @@ -# This installs /usr/local/bin/python3 -> python3.7 +# This installs /usr/local/bin/python3 -> python3.9 python3 diff --git a/build/pkgs/python3/spkg-configure.m4 b/build/pkgs/python3/spkg-configure.m4 index 637d73b74f2..a76f27e3cb9 100644 --- a/build/pkgs/python3/spkg-configure.m4 +++ b/build/pkgs/python3/spkg-configure.m4 @@ -1,5 +1,5 @@ SAGE_SPKG_CONFIGURE([python3], [ - m4_pushdef([MIN_VERSION], [3.6.0]) + m4_pushdef([MIN_VERSION], [3.7.0]) m4_pushdef([MIN_NONDEPRECATED_VERSION], [3.7.0]) m4_pushdef([LT_VERSION], [3.10.0]) AC_ARG_WITH([python], @@ -97,17 +97,26 @@ SAGE_SPKG_CONFIGURE([python3], [ CFLAGS_MARCH="" ]) ]) + + AS_IF([test -n "$OPENMP_CFLAGS$OPENMP_CXXFLAGS"], [ + AC_MSG_CHECKING([whether OpenMP works with the C/C++ compilers configured for building extensions for $PYTHON_FOR_VENV]) + SAGE_PYTHON_CHECK_DISTUTILS([CC="$CC" CXX="$CXX" CFLAGS="$CFLAGS $OPENMP_CFLAGS" CXXFLAGS="$CXXFLAGS $OPENMP_CXXFLAGS" conftest_venv/bin/python3], [ + AC_MSG_RESULT([yes]) + ], [ + AC_MSG_RESULT([no, $reason; disabling use OpenMP]) + OPENMP_CFLAGS="" + OPENMP_CXXFLAGS="" + ]) + ]) + AX_COMPARE_VERSION([$python3_version], [lt], MIN_NONDEPRECATED_VERSION, [ AC_MSG_NOTICE([deprecation notice: Support for system python < MIN_NONDEPRECATED_VERSION is deprecated and will be removed in the next development cycle. Consider using a newer version of Python that may be available on your system or can be installed using the system package manager. To build Sage with a different system python, use ./configure --with-python=/path/to/python]) ]) - ], [ - SAGE_MACOSX_DEPLOYMENT_TARGET=legacy ]) AC_SUBST([PYTHON_FOR_VENV]) - AC_SUBST([SAGE_MACOSX_DEPLOYMENT_TARGET]) dnl These temporary directories are created by the check above dnl and need to be cleaned up to prevent the "rm -f conftest*" diff --git a/build/pkgs/qhull/distros/homebrew.txt b/build/pkgs/qhull/distros/homebrew.txt new file mode 100644 index 00000000000..95d316779cf --- /dev/null +++ b/build/pkgs/qhull/distros/homebrew.txt @@ -0,0 +1 @@ +qhull diff --git a/build/pkgs/sage_conf/package-version.txt b/build/pkgs/sage_conf/package-version.txt new file mode 120000 index 00000000000..cf10fe4b4e4 --- /dev/null +++ b/build/pkgs/sage_conf/package-version.txt @@ -0,0 +1 @@ +../sagelib/package-version.txt \ No newline at end of file diff --git a/build/pkgs/sage_conf/src/sage_conf.py.in b/build/pkgs/sage_conf/src/sage_conf.py.in index 6ac91e68018..e0075b1677d 100644 --- a/build/pkgs/sage_conf/src/sage_conf.py.in +++ b/build/pkgs/sage_conf/src/sage_conf.py.in @@ -24,6 +24,10 @@ CBLAS_PC_MODULES = "cblas" MATHJAX_DIR = "@prefix@/share/mathjax" THREEJS_DIR = "@prefix@/share/threejs" +# OpenMP flags, if available. +OPENMP_CFLAGS = "@OPENMP_CFLAGS@" +OPENMP_CXXFLAGS = "@OPENMP_CXXFLAGS@" + # The following must not be used during build to determine source or installation # location of sagelib. See comments in SAGE_ROOT/src/Makefile.in SAGE_LOCAL = "@prefix@" diff --git a/build/pkgs/sage_docbuild/install-requires.txt b/build/pkgs/sage_docbuild/install-requires.txt new file mode 100644 index 00000000000..b6e14e76015 --- /dev/null +++ b/build/pkgs/sage_docbuild/install-requires.txt @@ -0,0 +1 @@ +sage_docbuild diff --git a/build/pkgs/sage_sws2rst/install-requires.txt b/build/pkgs/sage_sws2rst/install-requires.txt new file mode 100644 index 00000000000..b489fda6aeb --- /dev/null +++ b/build/pkgs/sage_sws2rst/install-requires.txt @@ -0,0 +1 @@ +sage_sws2rst diff --git a/build/pkgs/sagelib/bootstrap b/build/pkgs/sagelib/bootstrap new file mode 100755 index 00000000000..db4ae487271 --- /dev/null +++ b/build/pkgs/sagelib/bootstrap @@ -0,0 +1,11 @@ +#! /bin/sh +set -e +for infile in src/*.m4; do + if [ -f "$infile" ]; then + outfile="src/$(basename $infile .m4)" + if [ "${BOOTSTRAP_QUIET}" = "no" ]; then + echo "$0: installing build/pkgs/sagelib/$outfile" + fi + m4 "$infile" > "$outfile" + fi +done diff --git a/build/pkgs/sagelib/package-version.txt b/build/pkgs/sagelib/package-version.txt index c3cae12bcc1..9f62018ee76 100644 --- a/build/pkgs/sagelib/package-version.txt +++ b/build/pkgs/sagelib/package-version.txt @@ -1 +1 @@ -9.3 +9.4.beta0 diff --git a/build/pkgs/sagelib/src/.gitignore b/build/pkgs/sagelib/src/.gitignore new file mode 100644 index 00000000000..ac3f595461d --- /dev/null +++ b/build/pkgs/sagelib/src/.gitignore @@ -0,0 +1 @@ +/setup.cfg diff --git a/build/pkgs/sagelib/src/MANIFEST.in b/build/pkgs/sagelib/src/MANIFEST.in deleted file mode 100644 index e149951fe4e..00000000000 --- a/build/pkgs/sagelib/src/MANIFEST.in +++ /dev/null @@ -1,10 +0,0 @@ -global-include *.c *.cc *.cpp *.h *.hh *.hpp *.inc *.py *.pyx *.pxd *.pxi *.rst *.txt *.tex - -include MANIFEST.in - -prune .tox - -graft sage/libs/gap/test -prune sage/ext/interpreters # In particular, __init__.py must not be present in the distribution; or sage_setup.autogen.interpreters.rebuild will not generate the code - -prune sage_docbuild # Shipped by sage_docbuild diff --git a/build/pkgs/sagelib/src/MANIFEST.in b/build/pkgs/sagelib/src/MANIFEST.in new file mode 120000 index 00000000000..44ab66c52a2 --- /dev/null +++ b/build/pkgs/sagelib/src/MANIFEST.in @@ -0,0 +1 @@ +../../../../src/MANIFEST.in \ No newline at end of file diff --git a/build/pkgs/sagelib/src/Pipfile b/build/pkgs/sagelib/src/Pipfile new file mode 120000 index 00000000000..6453b964ea3 --- /dev/null +++ b/build/pkgs/sagelib/src/Pipfile @@ -0,0 +1 @@ +../../../../src/Pipfile \ No newline at end of file diff --git a/build/pkgs/sagelib/src/Pipfile-dist b/build/pkgs/sagelib/src/Pipfile-dist new file mode 120000 index 00000000000..82f13c0fbab --- /dev/null +++ b/build/pkgs/sagelib/src/Pipfile-dist @@ -0,0 +1 @@ +../../../../Pipfile \ No newline at end of file diff --git a/build/pkgs/sagelib/src/Pipfile-dist.m4 b/build/pkgs/sagelib/src/Pipfile-dist.m4 new file mode 120000 index 00000000000..6706de2d100 --- /dev/null +++ b/build/pkgs/sagelib/src/Pipfile-dist.m4 @@ -0,0 +1 @@ +../../../../Pipfile.m4 \ No newline at end of file diff --git a/build/pkgs/sagelib/src/Pipfile.m4 b/build/pkgs/sagelib/src/Pipfile.m4 new file mode 120000 index 00000000000..061c119135f --- /dev/null +++ b/build/pkgs/sagelib/src/Pipfile.m4 @@ -0,0 +1 @@ +../../../../src/Pipfile.m4 \ No newline at end of file diff --git a/build/pkgs/sagelib/src/pyproject.toml b/build/pkgs/sagelib/src/pyproject.toml new file mode 120000 index 00000000000..a6ea5e2d213 --- /dev/null +++ b/build/pkgs/sagelib/src/pyproject.toml @@ -0,0 +1 @@ +../../../../src/pyproject.toml \ No newline at end of file diff --git a/build/pkgs/sagelib/src/pyproject.toml.m4 b/build/pkgs/sagelib/src/pyproject.toml.m4 new file mode 120000 index 00000000000..2bba827cb78 --- /dev/null +++ b/build/pkgs/sagelib/src/pyproject.toml.m4 @@ -0,0 +1 @@ +../../../../src/pyproject.toml.m4 \ No newline at end of file diff --git a/build/pkgs/sagelib/src/requirements.txt b/build/pkgs/sagelib/src/requirements.txt deleted file mode 100644 index 7d059be408a..00000000000 --- a/build/pkgs/sagelib/src/requirements.txt +++ /dev/null @@ -1,19 +0,0 @@ -#sage_conf -#sage_setup -six # use of six should be removed from sage_setup -Cython==0.29.17 -pkgconfig -cysignals -gmpy2==2.1.0b5 - -numpy # already needed by sage.env -jinja2 # sage_setup.autogen.interpreters - -cypari2 # but building bdist_wheel of cypari2 fails with recent pip... https://github.com/sagemath/cypari2/issues/93 - -########## Runtime - -psutil -pexpect -pplpy -ipython<=5.8 diff --git a/build/pkgs/sagelib/src/requirements.txt b/build/pkgs/sagelib/src/requirements.txt new file mode 120000 index 00000000000..b70a4ee3aa9 --- /dev/null +++ b/build/pkgs/sagelib/src/requirements.txt @@ -0,0 +1 @@ +../../../../src/requirements.txt \ No newline at end of file diff --git a/build/pkgs/sagelib/src/requirements.txt.m4 b/build/pkgs/sagelib/src/requirements.txt.m4 new file mode 120000 index 00000000000..8a3e2524b49 --- /dev/null +++ b/build/pkgs/sagelib/src/requirements.txt.m4 @@ -0,0 +1 @@ +../../../../src/requirements.txt.m4 \ No newline at end of file diff --git a/build/pkgs/sagelib/src/setup.cfg.m4 b/build/pkgs/sagelib/src/setup.cfg.m4 new file mode 120000 index 00000000000..daeabd06843 --- /dev/null +++ b/build/pkgs/sagelib/src/setup.cfg.m4 @@ -0,0 +1 @@ +../../../../src/setup.cfg.m4 \ No newline at end of file diff --git a/build/pkgs/sagelib/src/setup.py b/build/pkgs/sagelib/src/setup.py index d20e408a8cf..f4550f05f01 100755 --- a/build/pkgs/sagelib/src/setup.py +++ b/build/pkgs/sagelib/src/setup.py @@ -21,6 +21,9 @@ ### Set source directory ######################################################### +# PEP 517 builds do not have . in sys.path +sys.path.insert(0, os.path.dirname(__file__)) + import sage.env sage.env.SAGE_SRC = os.getcwd() from sage.env import * diff --git a/build/pkgs/sagelib/src/tox.ini b/build/pkgs/sagelib/src/tox.ini index d58eb823aa8..5f28b9a076e 100644 --- a/build/pkgs/sagelib/src/tox.ini +++ b/build/pkgs/sagelib/src/tox.ini @@ -1,35 +1,131 @@ -# First pip-install tox: +# First install tox: # -# ./sage -pip install tox +# ./sage -i tox # -# To build and test in the tox environment: +# All tests require an installation of the non-Python components of the Sage distribution +# in SAGE_LOCAL. # -# ./sage -sh -c '(cd build/pkgs/sagelib/src && tox -v -v)' +# See envlist below for different environments. # -# To test interactively: +# To build and test in the tox environment using the concrete Python dependencies specified +# by requirements.txt, using the wheels built and stored by the Sage distribution: +# (Using 'sage -sh' ensures that we use the same Python as the one that we built the wheels +# for. This can also be done ensured manually by using the tox environment py38-sagewheels etc.) # -# build/pkgs/sagelib/src/.tox/python/bin/python +# Afterwards, to test interactively: +# +# build/pkgs/sagelib/src/.tox/ENVIRONMENT/bin/python +# build/pkgs/sagelib/src/.tox/ENVIRONMENT/bin/sage # [tox] +envlist = + # + # SUPPORTED ENVIRONMENTS: + # + # Build dependencies according to requirements.txt (all versions fixed). + # Use ONLY the wheels built and stored by the Sage distribution (no PyPI): + # + # ./sage -sh -c '(cd build/pkgs/sagelib/src && tox -v -v -v -e python-sagewheels-nopypi)' + # + python-sagewheels-nopypi, + # + # Build and test without using the concrete dependencies specified by requirements.txt, + # using the dependencies declared in pyproject.toml and setup.cfg (install-requires) only: + # Still use ONLY the wheels built and stored by the Sage distribution (no PyPI). + # + # ./sage -sh -c '(cd build/pkgs/sagelib/src && tox -v -v -v -e python-sagewheels-nopypi-norequirements)' + # + python-sagewheels-nopypi-norequirements, + # + # EXPERIMENTAL ENVIRONMENTS: + # + # Build dependencies according to requirements.txt (all versions fixed). + # Use the wheels built and stored by the Sage distribution, + # and additionally allow packages from PyPI. + # Because all versions are fixed, we "should" end up using the prebuilt wheels. + # + # ./sage -sh -c '(cd build/pkgs/sagelib/src && tox -v -v -v -e python-sagewheels)' + # + python-sagewheels, + # + # Likewise, but using pipenv using Pipfile-dist (= SAGE_ROOT/Pipfile). + # This also fixes the concrete dependencies (at least for some packages). + # + python-sagewheels-pipenv-dist, + # + # Build using the dependencies declared in pyproject.toml and setup.cfg (install-requires) only. + # Use the wheels built and stored by the Sage distribution, + # and additionally allow packages from PyPI. + # + # Because the version ranges will allow for packages to come in from PyPI (in source or wheel form), + # this is likely to fail because we do not have control over the configuration of these packages. + # + python-sagewheels-norequirements, + # + # Likewise, but using pipenv + # + python-sagewheels-pipenv [testenv] -deps = -rrequirements.txt +deps = + pipenv: pipenv + !pipenv-!norequirements: -rrequirements.txt + ## Needed for fpylll + norequirements: Cython + norequirements: cysignals passenv = - SAGE_LOCAL + # Variables set by .homebrew-build-env + CPATH + LIBRARY_PATH + PKG_CONFIG_PATH + # Parallel build + SAGE_NUM_THREADS + # SAGE_LOCAL only for finding the wheels + sagewheels: SAGE_LOCAL + # Location of the wheels (needs to include a PEP 503 compliant + # Simple Repository index, i.e., a subdirectory "simple") + sagewheels: SAGE_SPKG_WHEELS setenv = # Sage scripts such as sage-runtests like to use $HOME/.sage HOME={envdir} + # We supply pip options by environment variables so that they + # apply both to the installation of the dependencies and of the package + sagewheels: PIP_FIND_LINKS=file://{env:SAGE_SPKG_WHEELS:{env:SAGE_LOCAL:{toxinidir}/../../../../local}/var/lib/sage/wheels} + nopypi: PIP_NO_INDEX=true + # No build isolation for PEP 517 packages - use what is already in the environment + # Note that this pip env "NO" variable uses inverted logic: + # PIP_NO_BUILD_ISOLATION=False means don't use build isolation. + # See https://github.com/conda/conda-build/blob/8f1b3517fd0c816ec78b6dadf4a912b849ecd58a/conda_build/build.py#L2574 + nobuildisolation: PIP_NO_BUILD_ISOLATION=false + # Do not write or use Pipfile.lock -- we cannot seem to set its location, + # so we cannot isolate it in the tox environment. + pipenv: PIPENV_SKIP_LOCK=true + +sitepackages = + sitepackages: True + !sitepackages: False whitelist_externals = bash +skip_install = + pipenv: True + !pipenv: False + +commands_pre = + # Use Pipenv to install according to Pipfile into the virtual environment created by tox. + # https://pipenv-searchable.readthedocs.io/advanced.html#tox-automation-project + pipenv-!dist: pipenv install -v -v -v + # Same, but use $SAGE_ROOT/Pipfile + pipenv-dist: bash -c '(cd ../../../.. && pipenv install -v -v -v)' + commands = - # Beware of the treacherous non-src layout. "./sage/" shadows the install sage package. + # Beware of the treacherous non-src layout. "./sage/" shadows the installed sage package. python -c 'import sys; "" in sys.path and sys.path.remove(""); import sage.all; print(sage.all.__file__)' - # FIXME: The following loads sage-env, which loads the wrong Python. + # We check that the "sage" script invokes the correct Python. sage -c 'import sys; print("sys.path =", sys.path); import sage.all; print(sage.all.__file__)' - #sage -t --all + sage -t -p --all diff --git a/build/pkgs/scipy/distros/homebrew.txt b/build/pkgs/scipy/distros/homebrew.txt new file mode 100644 index 00000000000..9a635b910d9 --- /dev/null +++ b/build/pkgs/scipy/distros/homebrew.txt @@ -0,0 +1 @@ +scipy diff --git a/build/pkgs/singular/distros/homebrew.txt b/build/pkgs/singular/distros/homebrew.txt new file mode 100644 index 00000000000..90958411e8f --- /dev/null +++ b/build/pkgs/singular/distros/homebrew.txt @@ -0,0 +1,2 @@ +# As of 2021-03-20, build of homebrew's singular from source fails +#singular diff --git a/build/pkgs/sip/distros/homebrew.txt b/build/pkgs/sip/distros/homebrew.txt new file mode 100644 index 00000000000..641cf835012 --- /dev/null +++ b/build/pkgs/sip/distros/homebrew.txt @@ -0,0 +1 @@ +sip diff --git a/build/pkgs/sphinx/distros/homebrew.txt b/build/pkgs/sphinx/distros/homebrew.txt new file mode 100644 index 00000000000..00be791fe76 --- /dev/null +++ b/build/pkgs/sphinx/distros/homebrew.txt @@ -0,0 +1 @@ +sphinx-doc diff --git a/build/pkgs/sympy/install-requires.txt b/build/pkgs/sympy/install-requires.txt index 2241273987b..9c1c610c7c0 100644 --- a/build/pkgs/sympy/install-requires.txt +++ b/build/pkgs/sympy/install-requires.txt @@ -1 +1 @@ -sympy >=1.6, <1.7 +sympy >=1.6, <1.8 diff --git a/build/pkgs/valgrind/distros/homebrew.txt b/build/pkgs/valgrind/distros/homebrew.txt new file mode 100644 index 00000000000..e7af4129194 --- /dev/null +++ b/build/pkgs/valgrind/distros/homebrew.txt @@ -0,0 +1 @@ +valgrind diff --git a/configure.ac b/configure.ac index d2b33407d96..4fe290b225d 100644 --- a/configure.ac +++ b/configure.ac @@ -74,6 +74,13 @@ fi SAGE_SRC="$SAGE_ROOT/src" SAGE_SPKG_INST="$SAGE_LOCAL/var/lib/sage/installed" +AC_ARG_WITH([sage-venv], + [AS_HELP_STRING([--with-sage-venv=SAGE_VENV], + [put Python packages into an installation hierarchy separate from prefix])], + [SAGE_VENV="$withval"], + [SAGE_VENV='${SAGE_LOCAL}'])dnl Quoted so that it is resolved at build time by shell/Makefile +AC_SUBST([SAGE_VENV]) + #--------------------------------------------------------- AC_ARG_ENABLE([build-as-root], diff --git a/m4/ax_openmp.m4 b/m4/ax_openmp.m4 new file mode 100644 index 00000000000..866e1d66451 --- /dev/null +++ b/m4/ax_openmp.m4 @@ -0,0 +1,123 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_openmp.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_OPENMP([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) +# +# DESCRIPTION +# +# This macro tries to find out how to compile programs that use OpenMP a +# standard API and set of compiler directives for parallel programming +# (see http://www-unix.mcs/) +# +# On success, it sets the OPENMP_CFLAGS/OPENMP_CXXFLAGS/OPENMP_F77FLAGS +# output variable to the flag (e.g. -omp) used both to compile *and* link +# OpenMP programs in the current language. +# +# NOTE: You are assumed to not only compile your program with these flags, +# but also link it with them as well. +# +# If you want to compile everything with OpenMP, you should set: +# +# CFLAGS="$CFLAGS $OPENMP_CFLAGS" +# #OR# CXXFLAGS="$CXXFLAGS $OPENMP_CXXFLAGS" +# #OR# FFLAGS="$FFLAGS $OPENMP_FFLAGS" +# +# (depending on the selected language). +# +# The user can override the default choice by setting the corresponding +# environment variable (e.g. OPENMP_CFLAGS). +# +# ACTION-IF-FOUND is a list of shell commands to run if an OpenMP flag is +# found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it is +# not found. If ACTION-IF-FOUND is not specified, the default action will +# define HAVE_OPENMP. +# +# LICENSE +# +# Copyright (c) 2008 Steven G. Johnson +# Copyright (c) 2015 John W. Peterson +# Copyright (c) 2016 Nick R. Papior +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 13 + +AC_DEFUN([AX_OPENMP], [ +AC_PREREQ([2.69]) dnl for _AC_LANG_PREFIX + +AC_CACHE_CHECK([for OpenMP flag of _AC_LANG compiler], ax_cv_[]_AC_LANG_ABBREV[]_openmp, [save[]_AC_LANG_PREFIX[]FLAGS=$[]_AC_LANG_PREFIX[]FLAGS +ax_cv_[]_AC_LANG_ABBREV[]_openmp=unknown +# Flags to try: -fopenmp (gcc), -mp (SGI & PGI), +# -qopenmp (icc>=15), -openmp (icc), +# -xopenmp (Sun), -omp (Tru64), +# -qsmp=omp (AIX), +# none +ax_openmp_flags="-fopenmp -openmp -qopenmp -mp -xopenmp -omp -qsmp=omp none" +if test "x$OPENMP_[]_AC_LANG_PREFIX[]FLAGS" != x; then + ax_openmp_flags="$OPENMP_[]_AC_LANG_PREFIX[]FLAGS $ax_openmp_flags" +fi +for ax_openmp_flag in $ax_openmp_flags; do + case $ax_openmp_flag in + none) []_AC_LANG_PREFIX[]FLAGS=$save[]_AC_LANG_PREFIX[] ;; + *) []_AC_LANG_PREFIX[]FLAGS="$save[]_AC_LANG_PREFIX[]FLAGS $ax_openmp_flag" ;; + esac + AC_LINK_IFELSE([AC_LANG_SOURCE([[ +@%:@include + +static void +parallel_fill(int * data, int n) +{ + int i; +@%:@pragma omp parallel for + for (i = 0; i < n; ++i) + data[i] = i; +} + +int +main() +{ + int arr[100000]; + omp_set_num_threads(2); + parallel_fill(arr, 100000); + return 0; +} +]])],[ax_cv_[]_AC_LANG_ABBREV[]_openmp=$ax_openmp_flag; break],[]) +done +[]_AC_LANG_PREFIX[]FLAGS=$save[]_AC_LANG_PREFIX[]FLAGS +]) +if test "x$ax_cv_[]_AC_LANG_ABBREV[]_openmp" = "xunknown"; then + m4_default([$2],:) +else + if test "x$ax_cv_[]_AC_LANG_ABBREV[]_openmp" != "xnone"; then + OPENMP_[]_AC_LANG_PREFIX[]FLAGS=$ax_cv_[]_AC_LANG_ABBREV[]_openmp + fi + m4_default([$1], [AC_DEFINE(HAVE_OPENMP,1,[Define if OpenMP is enabled])]) +fi +])dnl AX_OPENMP diff --git a/src/MANIFEST.in b/src/MANIFEST.in new file mode 100644 index 00000000000..de9b84be589 --- /dev/null +++ b/src/MANIFEST.in @@ -0,0 +1,9 @@ +global-include *.c *.cc *.cpp *.h *.hh *.hpp *.inc *.py *.pyx *.pxd *.pxi *.rst *.txt *.tex + +include MANIFEST.in +include pyproject.toml + +prune .tox + +graft sage/libs/gap/test +prune sage/ext/interpreters # In particular, __init__.py must not be present in the distribution; or sage_setup.autogen.interpreters.rebuild will not generate the code diff --git a/src/Pipfile.m4 b/src/Pipfile.m4 new file mode 100644 index 00000000000..feca57f7de9 --- /dev/null +++ b/src/Pipfile.m4 @@ -0,0 +1,22 @@ +## Pipfile with all dependencies of sagelib and version information as free as possible +## (for developers to generate a dev environment) +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] +## We do not list packages that are already declared as install_requires +## in setup.cfg +pycodestyle = "*" +tox = "*" +pytest = "*" +rope = "*" +six = "*" + +[packages] +## We do not list packages that are already declared as install_requires +## in setup.cfg + +[packages.e1839a8] +path = "." diff --git a/src/VERSION.txt b/src/VERSION.txt index c3cae12bcc1..9f62018ee76 100644 --- a/src/VERSION.txt +++ b/src/VERSION.txt @@ -1 +1 @@ -9.3 +9.4.beta0 diff --git a/src/bin/sage b/src/bin/sage index 48a65d2dd2f..03cde457aef 100755 --- a/src/bin/sage +++ b/src/bin/sage @@ -147,6 +147,7 @@ if [ -x "${SELF}-config" ]; then fi if [ -f "${SELF}-src-env-config" ]; then # Not installed script, present only in src/bin/ + SAGE_SRC_ENV_CONFIG=1 . "${SELF}-src-env-config" >&2 fi if [ -z "$SAGE_VENV" -a -x "${SELF}-venv-config" ]; then @@ -309,7 +310,7 @@ fi # Prepare for running Sage, either interactively or non-interactively. sage_setup() { # Check that we're not in a source tarball which hasn't been built yet (#13561). - if [ ! -z "$SAGE_LOCAL" ] && [ ! -x "$SAGE_LOCAL/bin/sage" ]; then + if [ "$SAGE_SRC_ENV_CONFIG" = 1 ] && [ ! -z "$SAGE_VENV" ] && [ ! -x "$SAGE_VENV/bin/sage" ]; then echo >&2 '************************************************************************' echo >&2 'It seems that you are attempting to run Sage from an unpacked source' echo >&2 'tarball, but you have not compiled it yet (or maybe the build has not' @@ -339,7 +340,7 @@ sage_setup() { maybe_sage_location() { if [ -n "$SAGE_LOCAL" -a -w "$SAGE_LOCAL" ]; then - if [ -x "$SAGE_LOCAL/bin/python" ] && [ -x "$SAGE_LOCAL/bin/sage-location" ]; then + if [ -x "$SAGE_VENV/bin/python" ] && [ -x "$SAGE_VENV/bin/sage-location" ]; then sage-location || exit $? fi fi @@ -954,16 +955,6 @@ fi exec-runtests() { sage_setup export PYTHONIOENCODING="utf-8" # Fix encoding for doctests - # Trac #31191: Some versions of Python 3.6 disable utf-8 even if - # the locale is just "POSIX". This leads to errors in both the - # doctest framework and in various individual doctests. - if locale | grep -q -E -i '^LC_CTYPE="(C|POSIX)"$'; then - # Get an UTF-8 locale if possible. - LC_ALL=$(locale -a | grep -E -i '^(c|en_us)[-.]utf-?8$' | head -n 1) - LANG=$LC_ALL - export LC_ALL - export LANG - fi exec sage-runtests "$@" } diff --git a/src/bin/sage-env b/src/bin/sage-env index 4c617d8cd44..e099f2c311e 100644 --- a/src/bin/sage-env +++ b/src/bin/sage-env @@ -315,30 +315,7 @@ export UNAME=`uname | sed 's/CYGWIN.*/CYGWIN/' ` # Mac OS X-specific setup if [ "$UNAME" = "Darwin" ]; then - export MACOSX_VERSION=`uname -r | awk -F. '{print $1}'` - case "$SAGE_MACOSX_DEPLOYMENT_TARGET" in - legacy) - # legacy behavior of the Sage distribution. - # set MACOSX_DEPLOYMENT_TARGET -- if set incorrectly, this can - # cause lots of random "Abort trap" issues on OSX. see trac - # #7095 for an example. - if [ $MACOSX_VERSION -ge 14 ]; then - # various packages have still have issues with - # two digit OS X versions - MACOSX_DEPLOYMENT_TARGET=10.9 - else - MACOSX_DEPLOYMENT_TARGET=10.$[$MACOSX_VERSION-4] - fi - export MACOSX_DEPLOYMENT_TARGET - ;; - "") - # Do nothing - ;; - *) - # A fixed version set at configure time. - export MACOSX_DEPLOYMENT_TARGET=$SAGE_MACOSX_DEPLOYMENT_TARGET - ;; - esac + export MACOSX_VERSION=`uname -r | awk -F. '{print $1}'` # Work around problems on recent OS X crashing with an error message # "... may have been in progress in another thread when fork() was called" # when objective-C functions are called after fork(). See Trac #25921. diff --git a/src/bin/sage-env-config.in b/src/bin/sage-env-config.in index 7ab1995e4e8..d16269a8fae 100644 --- a/src/bin/sage-env-config.in +++ b/src/bin/sage-env-config.in @@ -52,4 +52,3 @@ CONFIGURED_OBJCXX="@OBJCXX@" ####################################### SAGE_ARCHFLAGS="@SAGE_ARCHFLAGS@" SAGE_PKG_CONFIG_PATH="@SAGE_PKG_CONFIG_PATH@" -SAGE_MACOSX_DEPLOYMENT_TARGET="@SAGE_MACOSX_DEPLOYMENT_TARGET@" diff --git a/src/bin/sage-runtests b/src/bin/sage-runtests index 57ad9dce79f..16d3295b922 100755 --- a/src/bin/sage-runtests +++ b/src/bin/sage-runtests @@ -143,4 +143,19 @@ if __name__ == "__main__": DC = DocTestController(options, args) err = DC.run() - sys.exit(err) + try: + exit_code_pytest = 0 + import pytest + pytest_options = ["--import-mode", "importlib"] + if options.verbose: + pytest_options.append("-v") + exit_code_pytest = pytest.main(pytest_options + args) + except ModuleNotFoundError: + print("Pytest is not installed, skip checking tests that rely on it.") + + + + if err == 0: + sys.exit(exit_code_pytest) + else: + sys.exit(err) diff --git a/src/bin/sage-src-env-config.in b/src/bin/sage-src-env-config.in index b9e8692f68f..d8aeb8a9023 100644 --- a/src/bin/sage-src-env-config.in +++ b/src/bin/sage-src-env-config.in @@ -32,8 +32,8 @@ export SAGE_LOCAL="@prefix@" # SAGE_VENV is the root of the virtual environment of sagelib. -# Currently, it is identical to SAGE_LOCAL -export SAGE_VENV="@prefix@" +# By default, it is identical to SAGE_LOCAL +export SAGE_VENV="@SAGE_VENV@" # SAGE_ROOT is the location of the Sage source/build tree. export SAGE_ROOT="@SAGE_ROOT@" diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index 461cb470923..c1b4451e263 100644 --- a/src/bin/sage-version.sh +++ b/src/bin/sage-version.sh @@ -1,5 +1,5 @@ # Sage version information for shell scripts # This file is auto-generated by the sage-update-version script, do not edit! -SAGE_VERSION='9.3' -SAGE_RELEASE_DATE='2021-05-09' -SAGE_VERSION_BANNER='SageMath version 9.3, Release Date: 2021-05-09' +SAGE_VERSION='9.4.beta0' +SAGE_RELEASE_DATE='2021-05-25' +SAGE_VERSION_BANNER='SageMath version 9.4.beta0, Release Date: 2021-05-25' diff --git a/src/doc/.gitignore b/src/doc/.gitignore deleted file mode 100644 index dd2abbd9cf1..00000000000 --- a/src/doc/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -/en/reference/*/sage -/en/reference/sage -/en/reference/spkg/*.rst -/output -/en/installation/*.txt -/en/reference/repl/*.txt diff --git a/src/doc/de/tutorial/latex.rst b/src/doc/de/tutorial/latex.rst index 4596e125108..7cf8e650784 100644 --- a/src/doc/de/tutorial/latex.rst +++ b/src/doc/de/tutorial/latex.rst @@ -86,13 +86,13 @@ die dann MathJax verwendet. :: sage: var('z') z sage: mj(z^12) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}z^{12}\] sage: mj(QQ) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}\Bold{Q}\] sage: mj(ZZ['x']) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}\Bold{Z}[x]\] sage: mj(integrate(z^4, z)) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}\frac{1}{5} \, z^{5}\] Grundlegende Nutzung ==================== @@ -206,10 +206,10 @@ integriert ist. :: sage: from sage.misc.html import MathJax sage: mj=MathJax() sage: mj(QQ) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}\Bold{Q}\] sage: latex.blackboard_bold(True) sage: mj(QQ) - + \[\newcommand{\Bold}[1]{\mathbb{#1}}\Bold{Q}\] sage: latex.blackboard_bold(False) Dank der Erweiterbarkeit von TeX können Sie selbst Makros und Pakete @@ -228,7 +228,7 @@ MathJax als TeX-Schnipsel interpretiert werden. :: sage: from sage.misc.html import MathJax sage: mj=MathJax() sage: mj(x+y) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}\newcommand{\foo}{bar}x + y\] Zusätzliche Makros, die so hinzugefügt wurden, werden auch vom systemweiten TeX genutzt, wenn MathJax an seine Grenzen gestoßen ist. diff --git a/src/doc/en/installation/source.rst b/src/doc/en/installation/source.rst index 13074527a07..de0493b0ef2 100644 --- a/src/doc/en/installation/source.rst +++ b/src/doc/en/installation/source.rst @@ -178,8 +178,7 @@ rather than building a Python 3 installation from scratch. Use the configure option ``--without-system-python3`` in case you want Python 3 built from scratch. -Sage will accept versions 3.6.x to 3.9.x; however, support for system python 3.6.x -is deprecated and will be removed in the next development cycle. +Sage will accept versions 3.7.x to 3.9.x. You can also use ``--with-python=/path/to/python3_binary`` to tell Sage to use ``/path/to/python3_binary`` to set up the venv. Note that setting up venv requires diff --git a/src/doc/en/reference/algebras/quantum_groups.rst b/src/doc/en/reference/algebras/quantum_groups.rst index f884972b909..d89afc4b906 100644 --- a/src/doc/en/reference/algebras/quantum_groups.rst +++ b/src/doc/en/reference/algebras/quantum_groups.rst @@ -4,6 +4,7 @@ Quantum Groups .. toctree:: :maxdepth: 2 + sage/algebras/quantum_groups/ace_quantum_onsager sage/algebras/quantum_groups/fock_space sage/algebras/quantum_groups/q_numbers sage/algebras/quantum_groups/representations diff --git a/src/doc/en/reference/coding/index.rst b/src/doc/en/reference/coding/index.rst index e737ba053d1..fd9e4de9fcd 100644 --- a/src/doc/en/reference/coding/index.rst +++ b/src/doc/en/reference/coding/index.rst @@ -58,7 +58,7 @@ classes. For some of them, implementations of special decoding algorithms or computations for structural invariants are available. .. toctree:: - :maxdepth: 2 + :maxdepth: 1 sage/coding/parity_check_code sage/coding/hamming_code @@ -69,6 +69,12 @@ computations for structural invariants are available. sage/coding/grs_code sage/coding/goppa_code sage/coding/kasami_codes + sage/coding/ag_code + +.. toctree:: + :hidden: + + sage/coding/ag_code_decoders In contrast, for some code families Sage can only construct their generator matrix and has no other a priori knowledge on them: diff --git a/src/doc/en/reference/combinat/module_list.rst b/src/doc/en/reference/combinat/module_list.rst index a95069590b4..efb6e1fba22 100644 --- a/src/doc/en/reference/combinat/module_list.rst +++ b/src/doc/en/reference/combinat/module_list.rst @@ -207,6 +207,7 @@ Comprehensive Module list sage/combinat/q_bernoulli sage/combinat/quickref sage/combinat/ranker + sage/combinat/recognizable_series sage/combinat/restricted_growth sage/combinat/ribbon sage/combinat/ribbon_shaped_tableau diff --git a/src/doc/en/reference/number_fields/index.rst b/src/doc/en/reference/number_fields/index.rst index 0fecfe43518..1440398be09 100644 --- a/src/doc/en/reference/number_fields/index.rst +++ b/src/doc/en/reference/number_fields/index.rst @@ -42,6 +42,7 @@ Orders, Ideals, Ideal Classes sage/rings/number_field/unit_group sage/rings/number_field/S_unit_solver sage/rings/number_field/small_primes_of_degree_one + sage/rings/number_field/selmer_group .. SEEALSO:: diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index befb73990a5..a5b4d54eeed 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -180,7 +180,7 @@ REFERENCES: .. [AKMRVW] \A. Alvarado, A. Koutsianas, B. Malmskog, C. Rasmussen, C. Vincent, and M. West, - *A robust implementation for solving the S-unit equation and + *A robust implementation for solving the S-unit equation and several applications.* :arxiv:`1903.00977` @@ -660,7 +660,7 @@ REFERENCES: .. [Ber1987] \M. Berger, *Geometry I*, Springer (Berlin) (1987); :doi:`10.1007/978-3-540-93815-6` - + .. [Ber1987a] \M. Berger, *Geometry II*, Springer (Berlin) (1987); :doi:`10.1007/978-3-540-93816-3` @@ -834,6 +834,10 @@ REFERENCES: and root vectors for the* `q`-*Onsager algebra*. Preprint, (2017) :arxiv:`1706.08747`. +.. [BK2005] \P. Baseilhac and K. Koizumi. *A new (in)finite dimensional algebra + for quantum integrable models*. Nuclear Phys. B **720** (2005), + pp. 325-347. + .. [BKK2000] Georgia Benkart, Seok-Jin Kang, Masaki Kashiwara. *Crystal bases for the quantum superalgebra* `U_q(\mathfrak{gl}(m,n))`, J. Amer. Math. Soc. **13** (2000), no. 2, 295-331. @@ -1062,6 +1066,10 @@ REFERENCES: Berkovich projective line. Mathematical Surveys and Monographs, Volumne 159. 2010. +.. [BR2010a] Jean Berstel and Christophe Reutenauer, + *Noncommutative Rational Series With Applications*. + Cambridge, 2010. + .. [Brandes01] Ulrik Brandes, A faster algorithm for betweenness centrality, Journal of Mathematical Sociology 25.2 (2001): 163-177, @@ -1166,6 +1174,10 @@ REFERENCES: no. 1 (2003): 97-111, http://www.moi.math.bas.bg/moiuser/~iliya/pdf_site/gf5srev.pdf. +.. [BS2010] \P. Baseilhac and K. Shigechi. *A new current algebra and the + reflection equation*. Lett. Math. Phys. **92** (2010), + pp. 47-65. :arxiv:`0906.1482`. + .. [BS2011] \E. Byrne and A. Sneyd, On the Parameters of Codes with Two Homogeneous Weights. WCC 2011-Workshop on coding and cryptography, @@ -1627,6 +1639,9 @@ REFERENCES: Bull. Belg. Math. Soc. 12(2006), 707-718. http://projecteuclid.org/euclid.bbms/1136902608 +.. [Cou2014] Alain Couvreur, *Codes and the Cartier operator*, + Proceedings of the American Mathematical Society 142.6 (2014): 1983-1996. + .. [Cox] David Cox, "What is a Toric Variety", https://dacox.people.amherst.edu/lectures/tutorial.ps @@ -2024,7 +2039,7 @@ REFERENCES: *Differential Geometry of Lightlike Submanifolds*, Frontiers in Mathematics, 2010. -.. [DSK2006] \A. De Sole and V. Kac. "Finite vs Affine W-algebras". Jpn. J. Math. +.. [DSK2006] \A. De Sole and V. Kac. "Finite vs Affine W-algebras". Jpn. J. Math. (2006) vol. 1, no. 1, pp 137--261 .. [Du2001] \I. Duursma, "From weight enumerators to zeta functions", in @@ -2559,6 +2574,9 @@ REFERENCES: crosscorrelation functions*. IEEE Transactions on Information Theory, 14, pp. 154-156, 1968. +.. [Gop1981] \V. D. Goppa, “Codes on algebraic curves,” Sov. Math. Dokl., vol. + 24, no. 1, pp. 170–172, 1981. + .. [Gos1972] Bill Gosper, "Continued Fraction Arithmetic" https://perl.plover.com/classes/cftalk/INFO/gosper.txt @@ -2583,14 +2601,14 @@ REFERENCES: .. [GR2001] Chris Godsil and Gordon Royle, *Algebraic Graph Theory*. Graduate Texts in Mathematics, Springer, 2001. -.. [Gre2006] \R. M. Green, *Star reducible Coxeter groups*, Glasgow +.. [Gre2006] \R. M. Green, *Star reducible Coxeter groups*, Glasgow Mathematical Journal, Volume 48, Issue 3, pp. 583-609. .. [Gri2021] \O. Gritsenko, *On strongly regular graph with parameters (65; 32; 15; 16)*, :arxiv:`2102.05432`. .. [GX2020] \R. M. Green, Tianyuan Xu, *Classification of Coxeter groups with - finitely many elements of a-value 2*, Algebraic Combinatorics, + finitely many elements of a-value 2*, Algebraic Combinatorics, Volume 3 (2020) no. 2, pp. 331-364. .. [Gr2006] Matthew Greenberg, @@ -3218,8 +3236,8 @@ REFERENCES: .. [Ka1990] Victor G. Kac. *Infinite-dimensional Lie Algebras*. Third edition. Cambridge University Press, Cambridge, 1990. -.. [Kac1997] \V. Kac, "Vertex algebras for beginners". Second Edition. vol 10. - university lecture series. AMS, Cambridge, 1997. +.. [Kac1997] \V. Kac, "Vertex algebras for beginners". Second Edition. vol 10. + university lecture series. AMS, Cambridge, 1997. .. [Kal1992] \B. Kaliski, *The MD2 message-digest algorithm*; in @@ -3677,6 +3695,10 @@ REFERENCES: graph by a set of intervals on the real line*. Fundamenta Mathematicae, 51:45-64, 1962; :doi:`10.4064/fm-51-1-45-64`. +.. [LBO2014] Kwankyu Lee, Maria Bras-Amorós, and Michael E. O'Sullivan, + *Unique decoding of general AG codes*, IEEE Transactions on + Information Theory, vol 60, no. 4 (2014), pp. 2038--2053. + .. [LB1988] Lee, P.J., Brickell, E.F. An observation on the security of McEliece's public-key cryptosystem. EuroCrypt 1988. LNCS, vol. 330, pp. 275–280. @@ -3685,6 +3707,9 @@ REFERENCES: of three spherical harmonics', Comput. Phys. Commun., Volume 25, pp. 81-85 (1982) +.. [Lee2016] Kwankyu Lee, *Decoding of differential AG codes*, Advances in + Mathematics of Communications, vol. 10, no. 2 (2016), pp. 307-–319. + .. [Lee1996] Marc van Leeuwen. *The Robinson-Schensted and Sch\"utzenberger algorithms, an elementary approach*. Electronic Journal of Combinatorics 3, no. 2 (1996): @@ -3947,11 +3972,11 @@ REFERENCES: (2002). http://www.eg-models.de/models/Classical_Models/2001.02.069/_direct_link.html .. [Lus1985] George Lusztig, *Cells in affine Weyl groups*, Algebraic Groups - and Related Topics, Advanced Studies in Pure mathematics 6, 1985, + and Related Topics, Advanced Studies in Pure mathematics 6, 1985, pp. 255-287. .. [Lus2013] George Lusztig, *Hecke algebras with unequal parameters*, - :arxiv:`math/0208154`. + :arxiv:`math/0208154`. .. [Lut2005] Frank H. Lutz, "Triangulated Manifolds with Few Vertices: Combinatorial Manifolds", preprint (2005), @@ -5373,6 +5398,11 @@ REFERENCES: isogenies of prime degree. Journal de Théorie des Nombres de Bordeaux, 2012. +.. [Suth2008] Andrew V. Sutherland, *Structure computation and discrete + logarithms in finite abelian p-groups*. + Mathematics of Computation **80** (2011), pp. 477-500. + :arxiv:`0809.3413v3`. + .. [SV1970] \H. Schneider and M. Vidyasagar. Cross-positive matrices. SIAM Journal on Numerical Analysis, 7:508-519, 1970. @@ -5492,6 +5522,9 @@ REFERENCES: .. [Terwilliger2011] Paul Terwilliger. *The universal Askey-Wilson algebra*. SIGMA **7** (2011), 069, 24 pages. :arxiv:`1104.2813`. +.. [Ter2021] Paul Terwilliger. *The alternating central extension of the* + `q`*-Onsager algebra*. Preprint, :arxiv:`2103.03028` (2021). + .. [TP1994] \J. Thas, S. Payne, *Spreads and ovoids in finite generalized quadrangles*. Geometriae Dedicata, Vol. 52, pp. 227-253, 1994. @@ -5819,7 +5852,7 @@ REFERENCES: upper-triangular nilpotent matrices." Electronic J. Comb. 25(1) (2018) #P1.68. :arxiv:`1703.00057`. -.. [Yu2007] \K. Yu, *p-adic logarithmic forms and group varieties. III.* +.. [Yu2007] \K. Yu, *p-adic logarithmic forms and group varieties. III.* Forum Math., 19(2):187–280, 2007. .. [Yun1976] Yun, David YY. On square-free decomposition diff --git a/src/doc/en/tutorial/latex.rst b/src/doc/en/tutorial/latex.rst index eba1c6f52ad..0f2e7e47cc1 100644 --- a/src/doc/en/tutorial/latex.rst +++ b/src/doc/en/tutorial/latex.rst @@ -83,13 +83,13 @@ LaTeX representation and then wraps it in HTML that invokes the CSS sage: var('z') z sage: mj(z^12) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}z^{12}\] sage: mj(QQ) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}\Bold{Q}\] sage: mj(ZZ['x']) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}\Bold{Z}[x]\] sage: mj(integrate(z^4, z)) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}\frac{1}{5} \, z^{5}\] Basic Use ========= @@ -194,10 +194,10 @@ done in written work. This is accomplished by redefining the sage: from sage.misc.html import MathJax sage: mj=MathJax() sage: mj(QQ) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}\Bold{Q}\] sage: latex.blackboard_bold(True) sage: mj(QQ) - + \[\newcommand{\Bold}[1]{\mathbb{#1}}\Bold{Q}\] sage: latex.blackboard_bold(False) It is possible to take advantage of the extensible nature of @@ -217,7 +217,7 @@ MathJax interprets a snippet of TeX in the notebook. :: sage: from sage.misc.html import MathJax sage: mj=MathJax() sage: mj(x+y) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}\newcommand{\foo}{bar}x + y\] Additional macros added this way will also be used in the event that the system-wide version of TeX is called on diff --git a/src/doc/fr/tutorial/latex.rst b/src/doc/fr/tutorial/latex.rst index fdc8563a9f3..4ec9426700b 100644 --- a/src/doc/fr/tutorial/latex.rst +++ b/src/doc/fr/tutorial/latex.rst @@ -83,13 +83,13 @@ possède la classe CSS "math", laquelle indique de faire appel à MathJax. :: sage: var('z') z sage: mj(z^12) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}z^{12}\] sage: mj(QQ) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}\Bold{Q}\] sage: mj(ZZ['x']) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}\Bold{Z}[x]\] sage: mj(integrate(z^4, z)) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}\frac{1}{5} \, z^{5}\] Utilisation de base =================== @@ -197,10 +197,10 @@ mais la définition de la macro TeX ``\Bold{}`` fournie par Sage. :: sage: from sage.misc.html import MathJax sage: mj=MathJax() sage: mj(QQ) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}\Bold{Q}\] sage: latex.blackboard_bold(True) sage: mj(QQ) - + \[\newcommand{\Bold}[1]{\mathbb{#1}}\Bold{Q}\] sage: latex.blackboard_bold(False) On peut aussi définir de nouvelles macros TeX ou charger des packages @@ -220,7 +220,7 @@ bloc-notes. :: sage: from sage.misc.html import MathJax sage: mj=MathJax() sage: mj(x+y) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}\newcommand{\foo}{bar}x + y\] Ces macros supplémentaires sont disponibles aussi quand Sage appelle TeX pour compiler un fragment de document trop gros pour MathJax. C'est la fonction diff --git a/src/doc/ja/tutorial/latex.rst b/src/doc/ja/tutorial/latex.rst index 0d41b35e4d4..c737dbf0146 100644 --- a/src/doc/ja/tutorial/latex.rst +++ b/src/doc/ja/tutorial/latex.rst @@ -65,13 +65,13 @@ SageはLaTeXを多種多様な形で利用している. sage: var('z') z sage: mj(z^12) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}z^{12}\] sage: mj(QQ) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}\Bold{Q}\] sage: mj(ZZ[x]) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}\Bold{Z}[x]\] sage: mj(integrate(z^4, z)) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}\frac{1}{5} \, z^{5}\] @@ -169,10 +169,10 @@ LaTeXで使われるバックスラッシュには,Pythonの文字列内でエ sage: from sage.misc.html import MathJax sage: mj=MathJax() sage: mj(QQ) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}\Bold{Q}\] sage: latex.blackboard_bold(True) sage: mj(QQ) - + \[\newcommand{\Bold}[1]{\mathbb{#1}}\Bold{Q}\] sage: latex.blackboard_bold(False) 新しいマクロやパッケージなどを追加して,TeXの高い拡張性を利用することができる. @@ -193,7 +193,7 @@ LaTeXで使われるバックスラッシュには,Pythonの文字列内でエ sage: from sage.misc.html import MathJax sage: mj=MathJax() sage: mj(x+y) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}\newcommand{\foo}{bar}x + y\] 以上のようなやり方で追加したマクロは,MathJaxでは対応しきれない大規模な処理が発生してシステム上のTeXが呼ばれるような場合にも使われる. diff --git a/src/doc/pt/tutorial/latex.rst b/src/doc/pt/tutorial/latex.rst index f410d00e992..cba4fd4344e 100644 --- a/src/doc/pt/tutorial/latex.rst +++ b/src/doc/pt/tutorial/latex.rst @@ -91,13 +91,13 @@ evoca a classe "matemática" do CSS, a qual então emprega o MathJax. :: sage: var('z') z sage: js(z^12) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}z^{12}\] sage: js(QQ) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}\Bold{Q}\] sage: js(ZZ[x]) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}\Bold{Z}[x]\] sage: js(integrate(z^4, z)) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}\frac{1}{5} \, z^{5}\] Uso Básico ========== @@ -207,11 +207,11 @@ obtido redefinindo a macro ``\Bold{}`` que faz parte do Sage. :: sage: from sage.misc.html import MathJax sage: js = MathJax() sage: js(QQ) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}\Bold{Q}\] sage: latex.blackboard_bold(True) sage: js(QQ) - + \[\newcommand{\Bold}[1]{\mathbb{#1}}\Bold{Q}\] sage: latex.blackboard_bold(False) É possível aproveitar os recursos do TeX adicionando novas funções @@ -231,7 +231,7 @@ trechos de códigos TeX no Notebook. :: sage: from sage.misc.html import MathJax sage: js = MathJax() sage: js(x+y) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}\newcommand{\foo}{bar}x + y\] Macros adicionais usadas dessa forma serão também usadas eventualmente se a versão do TeX no seu sistema for usada para lidar com algo muito diff --git a/src/pyproject.toml.m4 b/src/pyproject.toml.m4 new file mode 100644 index 00000000000..e0a63945f2f --- /dev/null +++ b/src/pyproject.toml.m4 @@ -0,0 +1,21 @@ +[build-system] +# Minimum requirements for the build system to execute. +requires = [ + # Some version of sage-conf is required. + # Note that PEP517/518 have no notion of optional build dependencies: + # https://github.com/pypa/pip/issues/6144 + 'sage-conf', + esyscmd(`sage-get-system-packages install-requires-toml \ + setuptools \ + wheel \ + cypari \ + cysignals \ + cython \ + gmpy2 \ + jinja2 \ + jupyter_core \ + numpy \ + pkgconfig \ + pplpy \ + ')] +build-backend = "setuptools.build_meta" diff --git a/src/requirements.txt.m4 b/src/requirements.txt.m4 new file mode 100644 index 00000000000..b12c71acb45 --- /dev/null +++ b/src/requirements.txt.m4 @@ -0,0 +1,55 @@ +## requirements.txt for creating venvs with sagelib +## +## Usage: +## +## $ ../sage -sh +## (sage-sh) $ python3 -m venv venv1 +## (sage-sh) $ source venv1/bin/activate +## (venv1) (sage-sh) $ pip install -r requirements.txt +## (venv1) (sage-sh) $ pip install -e . + +dnl FIXME: Including the whole package-version.txt does not work for packages that have a patchlevel.... +dnl We need a better tool to format this information. + +sage-conf==esyscmd(`printf $(sed "s/[.]p.*//;" ../sage_conf/package-version.txt)') +dnl sage_setup # Will be split out later. + +dnl From build/pkgs/sagelib/dependencies +cypari2==esyscmd(`printf $(sed "s/[.]p.*//;" ../cypari/package-version.txt)') +dnl ... but building bdist_wheel of cypari2 fails with recent pip... https://github.com/sagemath/cypari2/issues/93 +cysignals==esyscmd(`printf $(sed "s/[.]p.*//;" ../cysignals/package-version.txt)') +Cython==esyscmd(`printf $(sed "s/[.]p.*//;" ../cython/package-version.txt)') +gmpy2==esyscmd(`printf $(sed "s/[.]p.*//;" ../gmpy2/package-version.txt)') +jinja2==esyscmd(`printf $(sed "s/[.]p.*//;" ../jinja2/package-version.txt)') +dnl ... for sage_setup.autogen.interpreters +jupyter_core==esyscmd(`printf $(sed "s/[.]p.*//;" ../jupyter_core/package-version.txt)') +numpy==esyscmd(`printf $(sed "s/[.]p.*//;" ../numpy/package-version.txt)') +dnl ... already needed by sage.env +pkgconfig==esyscmd(`printf $(sed "s/[.]p.*//;" ../pkgconfig/package-version.txt)') +pplpy==esyscmd(`printf $(sed "s/[.]p.*//;" ../pplpy/package-version.txt)') +pycygwin==esyscmd(`printf $(sed "s/[.]p.*//;" ../pycygwin/package-version.txt)'); sys_platform == 'cygwin' +dnl pynac # after converting to a pip-installable package + + +dnl From Makefile.in: SAGERUNTIME +ipython==esyscmd(`printf $(sed "s/[.]p.*//;" ../ipython/package-version.txt)') +pexpect==esyscmd(`printf $(sed "s/[.]p.*//;" ../pexpect/package-version.txt)') +psutil==esyscmd(`printf $(sed "s/[.]p.*//;" ../psutil/package-version.txt)') + +dnl From Makefile.in: DOC_DEPENDENCIES +sphinx==esyscmd(`printf $(sed "s/[.]p.*//;" ../sphinx/package-version.txt)') +networkx==esyscmd(`printf $(sed "s/[.]p.*//;" ../networkx/package-version.txt)') +scipy==esyscmd(`printf $(sed "s/[.]p.*//;" ../scipy/package-version.txt)') +sympy==esyscmd(`printf $(sed "s/[.]p.*//;" ../sympy/package-version.txt)') +matplotlib==esyscmd(`printf $(sed "s/[.]p.*//;" ../matplotlib/package-version.txt)') +pillow==esyscmd(`printf $(sed "s/[.]p.*//;" ../pillow/package-version.txt)') +mpmath==esyscmd(`printf $(sed "s/[.]p.*//;" ../mpmath/package-version.txt)') +ipykernel==esyscmd(`printf $(sed "s/[.]p.*//;" ../ipykernel/package-version.txt)') +jupyter_client==esyscmd(`printf $(sed "s/[.]p.*//;" ../jupyter_client/package-version.txt)') +ipywidgets==esyscmd(`printf $(sed "s/[.]p.*//;" ../ipywidgets/package-version.txt)') + +dnl Other Python packages that are standard spkg, used in doctests +cvxopt==esyscmd(`printf $(sed "s/[.]p.*//;" ../cvxopt/package-version.txt)') +rpy2==esyscmd(`printf $(sed "s/[.]p.*//;" ../rpy2/package-version.txt)') +fpylll==esyscmd(`printf $(sed "s/[.]p.*//;" ../fpylll/package-version.txt)') +dnl pycryptosat # Sage distribution installs it as part of cryptominisat. According to its README on https://pypi.org/project/pycryptosat/: "The pycryptosat python package compiles while compiling CryptoMiniSat. It cannot be compiled on its own, it must be compiled at the same time as CryptoMiniSat." diff --git a/src/sage/.gitignore b/src/sage/.gitignore deleted file mode 100644 index 78a127212f0..00000000000 --- a/src/sage/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/ext/interpreters diff --git a/src/sage/algebras/catalog.py b/src/sage/algebras/catalog.py index 76a59c355cc..6c02093c4d3 100644 --- a/src/sage/algebras/catalog.py +++ b/src/sage/algebras/catalog.py @@ -9,6 +9,8 @@ Let ```` indicate pressing the tab key. So begin by typing ``algebras.`` to the see the currently implemented named algebras. +- :class:`algebras.AlternatingCentralExtensionQuantumOnsager + ` - :class:`algebras.ArikiKoike ` - :class:`algebras.AskeyWilson ` @@ -118,5 +120,8 @@ lazy_import('sage.algebras.quantum_matrix_coordinate_algebra', 'QuantumGL') lazy_import('sage.algebras.tensor_algebra', 'TensorAlgebra', 'Tensor') lazy_import('sage.algebras.quantum_groups.quantum_group_gap', 'QuantumGroup') +lazy_import('sage.algebras.quantum_groups.ace_quantum_onsager', + 'ACEQuantumOnsagerAlgebra', 'AlternatingCentralExtensionQuantumOnsager') lazy_import('sage.algebras.yangian', 'Yangian') del lazy_import # We remove the object from here so it doesn't appear under tab completion + diff --git a/src/sage/algebras/quantum_groups/ace_quantum_onsager.py b/src/sage/algebras/quantum_groups/ace_quantum_onsager.py new file mode 100644 index 00000000000..fa9c5fc96b7 --- /dev/null +++ b/src/sage/algebras/quantum_groups/ace_quantum_onsager.py @@ -0,0 +1,690 @@ +""" +Alternating Central Extension Quantum Onsager Algebra + +AUTHORS: + +- Travis Scrimshaw (2021-03): Initial version +""" + +# **************************************************************************** +# Copyright (C) 2021 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.lazy_attribute import lazy_attribute +from sage.categories.algebras import Algebras +from sage.combinat.free_module import CombinatorialFreeModule +from sage.monoids.indexed_free_monoid import IndexedFreeAbelianMonoid +from sage.sets.positive_integers import PositiveIntegers +from sage.sets.disjoint_union_enumerated_sets import DisjointUnionEnumeratedSets +from sage.sets.family import Family +from sage.rings.integer_ring import ZZ + +class ACEQuantumOnsagerAlgebra(CombinatorialFreeModule): + r""" + The alternating central extension of the `q`-Onsager algebra. + + The *alternating central extension* `\mathcal{A}_q` of the `q`-Onsager + alegbra `O_q` is a current algebra of `O_q` introduced by Baseilhac + and Koizumi [BK2005]_. A presentation was given by Baseilhac + and Shigechi [BS2010]_, which was then reformulated in terms of currents + in [Ter2021]_ and then used to prove that the generators form a PBW basis. + + .. NOTE:: + + This is only for the `q`-Onsager algebra with parameter + `c = q^{-1} (q - q^{-1})^2`. + + EXAMPLES:: + + sage: A = algebras.AlternatingCentralExtensionQuantumOnsager(QQ) + sage: AG = A.algebra_generators() + + We construct the generators `\mathcal{G}_3`, `\mathcal{W}_{-5}`, + `\mathcal{W}_2`, and `\widetilde{\mathcal{G}}_{4}` and perform + some computations:: + + sage: G3 = AG[0,3] + sage: Wm5 = AG[1,-5] + sage: W2 = AG[1,2] + sage: Gt4 = AG[2,4] + sage: [G3, Wm5, W2, Gt4] + [G[3], W[-5], W[2], Gt[4]] + sage: Gt4 * G3 + G[3]*Gt[4] + ((-q^12+3*q^8-3*q^4+1)/q^6)*W[-6]*W[1] + + ((-q^12+3*q^8-3*q^4+1)/q^6)*W[-5]*W[2] + + ((q^12-3*q^8+3*q^4-1)/q^6)*W[-4]*W[1] + + ((-q^12+3*q^8-3*q^4+1)/q^6)*W[-4]*W[3] + + ((-q^12+3*q^8-3*q^4+1)/q^6)*W[-3]*W[-2] + + ((q^12-3*q^8+3*q^4-1)/q^6)*W[-3]*W[2] + + ((q^12-3*q^8+3*q^4-1)/q^6)*W[-2]*W[5] + + ((-q^12+3*q^8-3*q^4+1)/q^6)*W[-1]*W[4] + + ((q^12-3*q^8+3*q^4-1)/q^6)*W[-1]*W[6] + + ((-q^12+3*q^8-3*q^4+1)/q^6)*W[0]*W[5] + + ((q^12-3*q^8+3*q^4-1)/q^6)*W[0]*W[7] + + ((q^12-3*q^8+3*q^4-1)/q^6)*W[3]*W[4] + sage: Wm5 * G3 + ((q^2-1)/q^2)*G[1]*W[-7] + ((-q^2+1)/q^2)*G[1]*W[7] + + ((q^2-1)/q^2)*G[2]*W[-6] + ((-q^2+1)/q^2)*G[2]*W[6] + G[3]*W[-5] + + ((-q^2+1)/q^2)*G[6]*W[-2] + ((q^2-1)/q^2)*G[6]*W[2] + + ((-q^2+1)/q^2)*G[7]*W[-1] + ((q^2-1)/q^2)*G[7]*W[1] + + ((-q^2+1)/q^2)*G[8]*W[0] + ((-q^8+2*q^4-1)/q^5)*W[-8] + + ((q^8-2*q^4+1)/q^5)*W[8] + sage: W2 * G3 + (q^2-1)*G[1]*W[-2] + (-q^2+1)*G[1]*W[4] + (-q^2+1)*G[3]*W[0] + + q^2*G[3]*W[2] + (q^2-1)*G[4]*W[1] + ((-q^8+2*q^4-1)/q^3)*W[-3] + + ((q^8-2*q^4+1)/q^3)*W[5] + sage: W2 * Wm5 + (q^4/(q^8+2*q^6-2*q^2-1))*G[1]*Gt[6] + (-q^4/(q^8+2*q^6-2*q^2-1))*G[6]*Gt[1] + + W[-5]*W[2] + (q/(q^2+1))*G[7] + (-q/(q^2+1))*Gt[7] + sage: Gt4 * Wm5 + ((q^2-1)/q^2)*W[-8]*Gt[1] + ((q^2-1)/q^2)*W[-7]*Gt[2] + + ((q^2-1)/q^2)*W[-6]*Gt[3] + W[-5]*Gt[4] + ((-q^2+1)/q^2)*W[-3]*Gt[6] + + ((-q^2+1)/q^2)*W[-2]*Gt[7] + ((-q^2+1)/q^2)*W[-1]*Gt[8] + + ((-q^2+1)/q^2)*W[0]*Gt[9] + ((q^2-1)/q^2)*W[1]*Gt[8] + + ((q^2-1)/q^2)*W[2]*Gt[7] + ((q^2-1)/q^2)*W[3]*Gt[6] + + ((-q^2+1)/q^2)*W[6]*Gt[3] + ((-q^2+1)/q^2)*W[7]*Gt[2] + + ((-q^2+1)/q^2)*W[8]*Gt[1] + ((-q^8+2*q^4-1)/q^5)*W[-9] + + ((q^8-2*q^4+1)/q^5)*W[9] + sage: Gt4 * W2 + (q^2-1)*W[-3]*Gt[1] + (-q^2+1)*W[0]*Gt[4] + (q^2-1)*W[1]*Gt[5] + + q^2*W[2]*Gt[4] + (-q^2+1)*W[5]*Gt[1] + ((-q^8+2*q^4-1)/q^3)*W[-4] + + ((q^8-2*q^4+1)/q^3)*W[6] + + REFERENCES: + + - [BK2005]_ + - [BS2010]_ + - [Ter2021]_ + """ + @staticmethod + def __classcall_private__(cls, R=None, q=None): + """ + Normalize input to ensure a unique representation. + + TESTS:: + + sage: A1 = algebras.AlternatingCentralExtensionQuantumOnsager(QQ) + sage: R. = QQ[] + sage: q = R.gen() + sage: A2 = algebras.AlternatingCentralExtensionQuantumOnsager(R.fraction_field(), q) + sage: A1 is A2 + True + sage: A2.q().parent() is R.fraction_field() + True + sage: q = R.fraction_field().gen() + sage: A3 = algebras.AlternatingCentralExtensionQuantumOnsager(q=q) + sage: A1 is A3 + True + """ + if q is None: + if R is None: + raise ValueError("either base ring or q must be specified") + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + q = PolynomialRing(R, 'q').fraction_field().gen() + R = q.parent() + else: + if R is None: + R = q.parent() + else: + q = R(q) + return super(ACEQuantumOnsagerAlgebra, cls).__classcall__(cls, R, q) + + def __init__(self, R, q): + r""" + Initialize ``self``. + + TESTS:: + + sage: A = algebras.AlternatingCentralExtensionQuantumOnsager(QQ) + sage: TestSuite(A).run() # long time + """ + I = DisjointUnionEnumeratedSets([PositiveIntegers(), ZZ, PositiveIntegers()], + keepkey=True, facade=True) + monomials = IndexedFreeAbelianMonoid(I, prefix='A', bracket=False) + self._q = q + CombinatorialFreeModule.__init__(self, R, monomials, + prefix='', bracket=False, latex_bracket=False, + sorting_key=self._monomial_key, + category=Algebras(R).WithBasis().Filtered()) + + def _monomial_key(self, x): + r""" + Compute the key for ``x`` so that the comparison is done by + reverse degree lexicographic order. + + EXAMPLES:: + + sage: A = algebras.AlternatingCentralExtensionQuantumOnsager(QQ) + sage: AG = A.algebra_generators() + sage: AG[1,1] * AG[1,0] * AG[0,1] # indirect doctest + G[1]*W[0]*W[1] + (q/(q^2+1))*G[1]^2 + (-q/(q^2+1))*G[1]*Gt[1] + + ((-q^8+2*q^4-1)/q^5)*W[-1]*W[1] + ((-q^8+2*q^4-1)/q^5)*W[0]^2 + + ((q^8-2*q^4+1)/q^5)*W[0]*W[2] + ((q^8-2*q^4+1)/q^5)*W[1]^2 + """ + return (-len(x), x.to_word_list()) + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: A = algebras.AlternatingCentralExtensionQuantumOnsager(QQ) + sage: A + Alternating Central Extension of q-Onsager algebra over Fraction + Field of Univariate Polynomial Ring in q over Rational Field + """ + return "Alternating Central Extension of {}-Onsager algebra over {}".format( + self._q, self.base_ring()) + + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: A = algebras.AlternatingCentralExtensionQuantumOnsager(QQ) + sage: latex(A) + \mathcal{A}_{q,\mathrm{Frac}(\Bold{Q}[q])} + """ + from sage.misc.latex import latex + return "\\mathcal{{A}}_{{{},{}}}".format(latex(self._q), + latex(self.base_ring())) + + def _repr_term(self, m): + r""" + Return a string representation of the term indexed by ``m``. + + EXAMPLES:: + + sage: A = algebras.AlternatingCentralExtensionQuantumOnsager(QQ) + sage: I = A._indices.gens() + sage: A._repr_term(I[0,3]) + 'G[3]' + sage: A._repr_term(I[1,-2]) + 'W[-2]' + sage: A._repr_term(I[1,3]) + 'W[3]' + sage: A._repr_term(I[2,5]) + 'Gt[5]' + sage: A._repr_term(I[0,1]^2 * I[1,0] * I[1,3]^13 * I[2,3]) + 'G[1]^2*W[0]*W[3]^13*Gt[3]' + """ + def to_str(x): + k, e = x + if k[0] == 0: + ret = "G[{}]".format(k[1]) + elif k[0] == 1: + ret = "W[{}]".format(k[1]) + elif k[0] == 2: + ret = "Gt[{}]".format(k[1]) + if e > 1: + ret = ret + "^{}".format(e) + return ret + return '*'.join(to_str(x) for x in m._sorted_items()) + + def _latex_term(self, m): + r""" + Return a latex representation of the term indexed by ``m``. + + EXAMPLES:: + + sage: A = algebras.AlternatingCentralExtensionQuantumOnsager(QQ) + sage: I = A._indices.gens() + sage: A._latex_term(I[0,3]) + '\\mathcal{G}_{3}' + sage: A._latex_term(I[1,-2]) + '\\mathcal{W}_{-2}' + sage: A._latex_term(I[1,3]) + '\\mathcal{W}_{3}' + sage: A._latex_term(I[2,5]) + '\\widetilde{\\mathcal{G}}_{5}' + sage: A._latex_term(I[0,1]^2 * I[1,0] * I[1,3]^13 * I[2,3]) + '\\mathcal{G}_{1}^{2} \\mathcal{W}_{0} \\mathcal{W}_{3}^{13} \\widetilde{\\mathcal{G}}_{3}' + """ + def to_str(x): + k, e = x + if k[0] == 0: + ret = "\\mathcal{{G}}_{{{}}}".format(k[1]) + elif k[0] == 1: + ret = "\\mathcal{{W}}_{{{}}}".format(k[1]) + elif k[0] == 2: + ret = "\\widetilde{{\\mathcal{{G}}}}_{{{}}}".format(k[1]) + if e > 1: + ret = ret + '^{{{}}}'.format(e) + return ret + return ' '.join(to_str(x) for x in m._sorted_items()) + + @cached_method + def algebra_generators(self): + r""" + Return the algebra generators of ``self``. + + EXAMPLES:: + + sage: A = algebras.AlternatingCentralExtensionQuantumOnsager(QQ) + sage: A.algebra_generators() + Lazy family (generator map(i))_{i in Disjoint union of + Family (Positive integers, Integer Ring, Positive integers)} + """ + G = self._indices.gens() + q = self._q + def monomial_map(x): + if x[0] != 1 and x[1] == 0: + return self.term(self.one_basis(), -(q-~q)*(q+~q)**2) + return self.monomial(G[x]) + return Family(self._indices._indices, monomial_map, + name="generator map") + + gens = algebra_generators + + def q(self): + r""" + Return the parameter `q` of ``self``. + + EXAMPLES:: + + sage: A = algebras.AlternatingCentralExtensionQuantumOnsager(QQ) + sage: A.q() + q + """ + return self._q + + @cached_method + def one_basis(self): + r""" + Return the basis element indexing `1`. + + EXAMPLES:: + + sage: A = algebras.AlternatingCentralExtensionQuantumOnsager(QQ) + sage: ob = A.one_basis(); ob + 1 + sage: ob.parent() + Free abelian monoid indexed by Disjoint union of + Family (Positive integers, Integer Ring, Positive integers) + """ + return self._indices.one() + + def _an_element_(self): + r""" + Return an element of ``self``. + + EXAMPLES:: + + sage: A = algebras.AlternatingCentralExtensionQuantumOnsager(QQ) + sage: A.an_element() + q*G[2] - 2*W[-3] + W[2] - q*Gt[1] + """ + G = self.algebra_generators() + return G[1,2] - 2*G[1,-3] + self.base_ring().an_element()*(G[0,2] - G[2,1]) + + def some_elements(self): + r""" + Return some elements of ``self``. + + EXAMPLES:: + + sage: A = algebras.AlternatingCentralExtensionQuantumOnsager(QQ) + sage: A.some_elements() + [W[0], W[3], W[-1], W[1], W[-2], G[1], G[2], Gt[1], Gt[2]] + """ + G = self.algebra_generators() + return [G[1,0], G[1,3], G[1,-1], G[1,1], G[1,-2], G[0,1], G[0,2], G[2,1], G[2,2]] + + def degree_on_basis(self, m): + r""" + Return the degree of the basis element indexed by ``m``. + + EXAMPLES:: + + sage: A = algebras.AlternatingCentralExtensionQuantumOnsager(QQ) + sage: G = A.algebra_generators() + sage: A.degree_on_basis(G[0,1].leading_support()) + 2 + sage: A.degree_on_basis(G[0,2].leading_support()) + 4 + sage: A.degree_on_basis(G[1,-1].leading_support()) + 3 + sage: A.degree_on_basis(G[1,0].leading_support()) + 1 + sage: A.degree_on_basis(G[1,1].leading_support()) + 1 + sage: A.degree_on_basis(G[2,1].leading_support()) + 2 + sage: A.degree_on_basis(G[2,2].leading_support()) + 4 + sage: [x.degree() for x in A.some_elements()] + [1, 5, 3, 1, 5, 2, 4, 2, 4] + """ + def deg(k): + if k[0] != 1: + return 2*k[1] + return -2*k[1]+1 if k[1] <= 0 else 2*k[1] - 1 + return ZZ.sum(deg(k) * c for k, c in m._monomial.items()) + + @cached_method + def quantum_onsager_pbw_generator(self, i): + r""" + Return the image of the PBW generator of the `q`-Onsager algebra + in ``self``. + + INPUT: + + - ``i`` -- a pair ``(k, m)`` such that + + * ``k=0`` and ``m`` is an integer + * ``k=1`` and ``m`` is a positive integer + + EXAMPLES:: + + sage: A = algebras.AlternatingCentralExtensionQuantumOnsager(QQ) + sage: A.quantum_onsager_pbw_generator((0,0)) + W[1] + sage: A.quantum_onsager_pbw_generator((0,1)) + (q^3/(q^4-1))*W[1]*Gt[1] - q^2*W[0] + (q^2+1)*W[2] + sage: A.quantum_onsager_pbw_generator((0,2)) + (q^6/(q^8-2*q^4+1))*W[1]*Gt[1]^2 + (-q^5/(q^4-1))*W[0]*Gt[1] + + (q^3/(q^2-1))*W[1]*Gt[2] + (q^3/(q^2-1))*W[2]*Gt[1] + + (-q^4-q^2)*W[-1] - q^2*W[1] + (q^4+2*q^2+1)*W[3] + sage: A.quantum_onsager_pbw_generator((0,-1)) + W[0] + sage: A.quantum_onsager_pbw_generator((0,-2)) + (q/(q^4-1))*W[0]*Gt[1] + ((q^2+1)/q^2)*W[-1] - 1/q^2*W[1] + sage: A.quantum_onsager_pbw_generator((0,-3)) + (q^2/(q^8-2*q^4+1))*W[0]*Gt[1]^2 + (1/(q^3-q))*W[-1]*Gt[1] + + (1/(q^3-q))*W[0]*Gt[2] - (1/(q^5-q))*W[1]*Gt[1] + + ((q^4+2*q^2+1)/q^4)*W[-2] - 1/q^2*W[0] + ((-q^2-1)/q^4)*W[2] + sage: A.quantum_onsager_pbw_generator((1,1)) + ((-q^2+1)/q^2)*W[0]*W[1] + (1/(q^3+q))*G[1] - (1/(q^3+q))*Gt[1] + sage: A.quantum_onsager_pbw_generator((1,2)) + -1/q*W[0]*W[1]*Gt[1] + (1/(q^6+q^4-q^2-1))*G[1]*Gt[1] + + ((-q^4+1)/q^4)*W[-1]*W[1] + (q^2-1)*W[0]^2 + + ((-q^4+1)/q^2)*W[0]*W[2] + ((q^2-1)/q^4)*W[1]^2 + - (1/(q^6+q^4-q^2-1))*Gt[1]^2 + 1/q^3*G[2] - 1/q^3*Gt[2] + """ + W0 = self.algebra_generators()[1,0] + W1 = self.algebra_generators()[1,1] + q = self._q + if i[0] == 0: + if i[1] < 0: + if i[1] == -1: + return W0 + Bd = self.quantum_onsager_pbw_generator((1, 1)) + Bm1 = self.quantum_onsager_pbw_generator((0, i[1]+1)) + Bm2 = self.quantum_onsager_pbw_generator((0, i[1]+2)) + return Bm2 + q/(q**-3-~q-q+q**3) * (Bd * Bm1 - Bm1 * Bd) + else: + if i[1] == 0: + return W1 + Bd = self.quantum_onsager_pbw_generator((1, 1)) + Bm1 = self.quantum_onsager_pbw_generator((0, i[1]-1)) + Bm2 = self.quantum_onsager_pbw_generator((0, i[1]-2)) + return Bm2 - q/(q**-3-~q-q+q**3) * (Bd * Bm1 - Bm1 * Bd) + elif i[0] == 1: + if i[1] == 1: + return q**-2 * W1 * W0 - W0 * W1 + if i[1] <= 0: + raise ValueError("not an index of a PBW basis element") + B = self.quantum_onsager_pbw_generator + n = i[1] + Bm1 = self.quantum_onsager_pbw_generator((0, n-1)) + return (q**-2 * Bm1 * W0 - W0 * Bm1 + + (q**-2 - 1) * sum(B((0,ell)) * B((0,n-ell-2)) + for ell in range(n-1))) + raise ValueError("not an index of a PBW basis element") + + @cached_method + def product_on_basis(self, lhs, rhs): + r""" + Return the product of the two basis elements ``lhs`` and ``rhs``. + + EXAMPLES:: + + sage: A = algebras.AlternatingCentralExtensionQuantumOnsager(QQ) + sage: G = A.algebra_generators() + sage: q = A.q() + sage: rho = -(q^2 - q^-2)^2 + + We verify the PBW ordering:: + + sage: G[0,1] * G[1,1] # indirect doctest + G[1]*W[1] + sage: G[1,1] * G[0,1] + q^2*G[1]*W[1] + ((-q^8+2*q^4-1)/q^3)*W[0] + ((q^8-2*q^4+1)/q^3)*W[2] + sage: G[1,-1] * G[1,1] + W[-1]*W[1] + sage: G[1,1] * G[1,-1] + W[-1]*W[1] + (q/(q^2+1))*G[2] + (-q/(q^2+1))*Gt[2] + sage: G[1,1] * G[2,1] + W[1]*Gt[1] + sage: G[2,1] * G[1,1] + q^2*W[1]*Gt[1] + ((-q^8+2*q^4-1)/q^3)*W[0] + ((q^8-2*q^4+1)/q^3)*W[2] + sage: G[0,1] * G[2,1] + G[1]*Gt[1] + sage: G[2,1] * G[0,1] + G[1]*Gt[1] + ((-q^12+3*q^8-3*q^4+1)/q^6)*W[-1]*W[1] + + ((-q^12+3*q^8-3*q^4+1)/q^6)*W[0]^2 + + ((q^12-3*q^8+3*q^4-1)/q^6)*W[0]*W[2] + + ((q^12-3*q^8+3*q^4-1)/q^6)*W[1]^2 + + We verify some of the defining relations (see Equations (3-14) + in [Ter2021]_), which are used to construct the PBW basis:: + + sage: G[0,1] * G[0,2] == G[0,2] * G[0,1] + True + sage: G[1,-1] * G[1,-2] == G[1,-2] * G[1,-1] + True + sage: G[1,1] * G[1,2] == G[1,2] * G[1,1] + True + sage: G[2,1] * G[2,2] == G[2,2] * G[2,1] + True + sage: G[1,0] * G[1,2] - G[1,2] * G[1,0] == G[1,-1] * G[1,1] - G[1,1] * G[1,-1] + True + sage: G[1,0] * G[1,2] - G[1,2] * G[1,0] == (G[2,2] - G[0,2]) / (q + ~q) + True + sage: q * G[1,0] * G[0,2] - ~q * G[0,2] * G[1,0] == q * G[2,2] * G[1,0] - ~q * G[1,0] * G[2,2] + True + sage: q * G[1,0] * G[0,2] - ~q * G[0,2] * G[1,0] == rho * G[1,-2] - rho * G[1,2] + True + sage: q * G[0,2] * G[1,1] - ~q * G[1,1] * G[0,2] == q * G[1,1] * G[2,2] - ~q * G[2,2] * G[1,1] + True + sage: q * G[0,2] * G[1,1] - ~q * G[1,1] * G[0,2] == rho * G[1,3] - rho * G[1,-1] + True + sage: G[1,-2] * G[1,2] - G[1,2] * G[1,-2] == G[1,-1] * G[1,3] - G[1,3] * G[1,-1] + True + sage: G[1,-2] * G[0,2] - G[0,2] * G[1,-2] == G[1,-1] * G[0,3] - G[0,3] * G[1,-1] + True + sage: G[1,1] * G[0,2] - G[0,2] * G[1,1] == G[1,2] * G[0,1] - G[0,1] * G[1,2] + True + sage: G[1,-2] * G[2,2] - G[2,2] * G[1,-2] == G[1,-1] * G[2,3] - G[2,3] * G[1,-1] + True + sage: G[1,1] * G[2,2] - G[2,2] * G[1,1] == G[1,2] * G[2,1] - G[2,1] * G[1,2] + True + sage: G[0,1] * G[2,2] - G[2,2] * G[0,1] == G[0,2] * G[2,1] - G[2,1] * G[0,2] + True + """ + # Some trivial base cases + if lhs == self.one_basis(): + return self.monomial(rhs) + if rhs == self.one_basis(): + return self.monomial(lhs) + + I = self._indices + B = I.gens() + q = self._q + kl = lhs.trailing_support() + kr = rhs.leading_support() + if kl <= kr: + return self.monomial(lhs * rhs) + + A = self.algebra_generators() + + # Create the commutator + # We have xy - yx = [x, y] -> xy = yx + LOT for x > y + if kl[0] == kr[0]: + # Commuting elements + if kl[0] != 1 or (kl[1] > 0 and kr[1] > 0) or (kl[1] <= 0 and kr[1] <= 0): + return self.monomial(lhs * B[kr]) * self.monomial(rhs // B[kr]) + + # relation (ii) + i = kl[1] - 1 + j = -kr[1] + denom = (q**2 - q**-2) * (q + q**-1)**2 + terms = A[1,-j]*A[1,i+1] + self.sum(A[0,ell]*A[2,i+j+1-ell] - A[0,i+j+1-ell]*A[2,ell] for ell in range(min(i,j)+1)) / denom + elif kl[0] == 2 and kr[0] == 0: + # relation (iii) + i = kl[1] - 1 + j = kr[1] - 1 + coeff = (q**2 - q**-2)**3 + terms = (A[0,j+1]*A[2,i+1] - coeff * A[1,-i]*A[1,-j] + coeff * A[1,i+1]*A[1,j+1] + + coeff * sum(A[1,-ell]*A[1,i+j+2-ell] - A[1,ell-1-i-j]*A[1,ell+1] for ell in range(min(i,j)+1)) + - coeff * sum(A[1,1-ell]*A[1,i+j+1-ell] - A[1,ell-i-j]*A[1,ell] for ell in range(1, min(i,j)+1))) + elif kl[0] == 1 and kr[0] == 0: + if kl[1] > 0: + # relation (vi) + i = kl[1] - 1 + j = kr[1] - 1 + coeff = q * (q - ~q) + terms = (A[0,j+1]*A[1,i+1] + coeff * sum(A[0,ell]*A[1,ell-i-j] for ell in range(min(i,j)+1)) + + coeff * sum(A[0,i+j+1-ell]*A[1,ell+1] - A[0,ell]*A[1,i+j+2-ell] for ell in range(min(i,j)+1)) + - coeff * sum(A[0,i+j+1-ell]*A[1,1-ell] for ell in range(1, min(i,j)+1))) + else: + # relation (v) + i = -kl[1] + j = kr[1] - 1 + coeff = ~q * (q - ~q) + terms = (A[0,j+1]*A[1,-i] - coeff * sum(A[0,ell]*A[1,i+j+1-ell] for ell in range(min(i,j)+1)) + + coeff * sum(A[0,ell]*A[1,ell-1-i-j] - A[0,i+j+1-ell]*A[1,-ell] for ell in range(min(i,j)+1)) + + coeff * sum(A[0,i+j+1-ell]*A[1,ell] for ell in range(1, min(i,j)+1))) + elif kl[0] == 2 and kr[0] == 1: + if kr[1] > 0: + # relation (vi) + i = kl[1] - 1 + j = kr[1] - 1 + coeff = q * (q - ~q) + terms = (A[1,j+1]*A[2,i+1] + coeff * sum(A[1,ell-i-j]*A[2,ell] for ell in range(min(i,j)+1)) + + coeff * sum(A[1,ell+1]*A[2,i+j+1-ell] - A[1,i+j+2-ell]*A[2,ell] for ell in range(min(i,j)+1)) + - coeff * sum(A[1,1-ell]*A[2,i+j+1-ell] for ell in range(1, min(i,j)+1))) + else: + # relation (vii) + i = kl[1] - 1 + j = -kr[1] + coeff = ~q * (q - ~q) + terms = (A[1,-j]*A[2,i+1] - coeff * sum(A[1,i+j+1-ell]*A[2,ell] for ell in range(min(i,j)+1)) + + coeff * sum(A[1,ell-1-i-j]*A[2,ell] - A[1,-ell]*A[2,i+j+1-ell] for ell in range(min(i,j)+1)) + + coeff * sum(A[1,ell]*A[2,i+j+1-ell] for ell in range(1, min(i,j)+1))) + + return self.monomial(lhs // B[kl]) * terms * self.monomial(rhs // B[kr]) + + def _sigma_on_basis(self, x): + r""" + Return the action of the `\sigma` automorphism on the basis element + indexed by ``x``. + + EXAMPLES:: + + sage: A = algebras.AlternatingCentralExtensionQuantumOnsager(QQ) + sage: I = A._indices.monoid_generators() + sage: A._sigma_on_basis(I[1,-1] * I[1,1]) + W[0]*W[2] + (q/(q^2+1))*G[2] + (-q/(q^2+1))*Gt[2] + sage: A._sigma_on_basis(I[0,1] * I[2,1]) + G[1]*Gt[1] + ((-q^12+3*q^8-3*q^4+1)/q^6)*W[-1]*W[1] + + ((-q^12+3*q^8-3*q^4+1)/q^6)*W[0]^2 + + ((q^12-3*q^8+3*q^4-1)/q^6)*W[0]*W[2] + + ((q^12-3*q^8+3*q^4-1)/q^6)*W[1]^2 + sage: [(x, A.sigma(x)) for x in A.some_elements()] + [(W[0], W[1]), (W[3], W[-2]), (W[-1], W[2]), (W[1], W[0]), + (W[-2], W[3]), (G[1], Gt[1]), (G[2], Gt[2]), (Gt[1], G[1]), + (Gt[2], G[2])] + """ + def tw(m): + if m[0] == 0: + return (2, m[1]) + if m[0] == 1: + return (1, -m[1]+1) + if m[0] == 2: + return (0, m[1]) + A = self.algebra_generators() + return self.prod(A[tw(m)]**e for m,e in x._sorted_items()) + + def _dagger_on_basis(self, x): + r""" + Return the action of the `\dagger` antiautomorphism on the basis element + indexed by ``x``. + + EXAMPLES:: + + sage: A = algebras.AlternatingCentralExtensionQuantumOnsager(QQ) + sage: I = A._indices.monoid_generators() + sage: A._dagger_on_basis(I[0,1] * I[1,-1] * I[2,1]) + G[1]*W[-1]*Gt[1] + sage: A._dagger_on_basis(I[1,-1] * I[1,1]) + W[-1]*W[1] + (q/(q^2+1))*G[2] + (-q/(q^2+1))*Gt[2] + sage: A._dagger_on_basis(I[0,1] * I[1,-1] * I[1,2] * I[2,1]) + (q^4/(q^8+2*q^6-2*q^2-1))*G[1]^2*Gt[1]*Gt[2] + + (-q^4/(q^8+2*q^6-2*q^2-1))*G[1]*G[2]*Gt[1]^2 + + G[1]*W[-1]*W[2]*Gt[1] + (q/(q^2+1))*G[1]*G[3]*Gt[1] + + (-q/(q^2+1))*G[1]*Gt[1]*Gt[3] + sage: [(x, A.dagger(x)) for x in A.some_elements()] + [(W[0], W[0]), (W[3], W[3]), (W[-1], W[-1]), (W[1], W[1]), + (W[-2], W[-2]), (G[1], Gt[1]), (G[2], Gt[2]), (Gt[1], G[1]), + (Gt[2], G[2])] + """ + def tw(m): + if m[0] == 0: + return (2, m[1]) + if m[0] == 1: + return (1, m[1]) + if m[0] == 2: + return (0, m[1]) + A = self.algebra_generators() + return self.prod(A[tw(m)]**e for m,e in reversed(x._sorted_items())) + + @lazy_attribute + def sigma(self): + r""" + The automorphism `\sigma`. + + EXAMPLES:: + + sage: A = algebras.AlternatingCentralExtensionQuantumOnsager(QQ) + sage: G = A.algebra_generators() + sage: x = A.an_element()^2 + sage: A.sigma(A.sigma(x)) == x + True + sage: A.sigma(G[1,-1] * G[1,1]) == A.sigma(G[1,-1]) * A.sigma(G[1,1]) + True + sage: A.sigma(G[0,2] * G[1,3]) == A.sigma(G[0,2]) * A.sigma(G[1,3]) + True + """ + return self.module_morphism(self._sigma_on_basis, codomain=self, category=self.category()) + + @lazy_attribute + def dagger(self): + r""" + The antiautomorphism `\dagger`. + + EXAMPLES:: + + sage: A = algebras.AlternatingCentralExtensionQuantumOnsager(QQ) + sage: G = A.algebra_generators() + sage: x = A.an_element()^2 + sage: A.dagger(A.dagger(x)) == x + True + sage: A.dagger(G[1,-1] * G[1,1]) == A.dagger(G[1,1]) * A.dagger(G[1,-1]) + True + sage: A.dagger(G[0,2] * G[1,3]) == A.dagger(G[1,3]) * A.dagger(G[0,2]) + True + sage: A.dagger(G[2,2] * G[1,3]) == A.dagger(G[1,3]) * A.dagger(G[2,2]) + True + """ + return self.module_morphism(self._dagger_on_basis, codomain=self) + diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 324392f7165..f6b4271edfd 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -9,6 +9,8 @@ - Julian Rueth (2014-03-02): use UniqueFactory for caching +- Peter Bruin (2021): do not require the base ring to be a field + This code is partly based on Sage code by David Kohel from 2005. TESTS: @@ -24,6 +26,7 @@ # Copyright (C) 2009 William Stein # Copyright (C) 2009 Jonathan Bober # Copyright (C) 2014 Julian Rueth +# Copyright (C) 2021 Peter Bruin # # 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 @@ -53,7 +56,7 @@ from sage.structure.sequence import Sequence from sage.structure.element import is_RingElement from sage.structure.factory import UniqueFactory -from sage.modules.free_module import VectorSpace, FreeModule +from sage.modules.free_module import FreeModule from sage.modules.free_module_element import vector from operator import itemgetter @@ -65,9 +68,7 @@ from sage.misc.cachefunc import cached_method -from sage.categories.fields import Fields from sage.categories.algebras import Algebras -_Fields = Fields() ######################################################## # Constructor @@ -76,24 +77,33 @@ class QuaternionAlgebraFactory(UniqueFactory): r""" + Construct a quaternion algebra. + + INPUT: + There are three input formats: - - ``QuaternionAlgebra(a, b)``: quaternion algebra generated by ``i``, ``j`` - subject to `i^2 = a`, `j^2 = b`, `j \cdot i = -i \cdot j`. + - ``QuaternionAlgebra(a, b)``, where `a` and `b` can be coerced to + units in a common field `K` of characteristic different from 2. - - ``QuaternionAlgebra(K, a, b)``: same as above but over a field ``K``. - Here, ``a`` and ``b`` are nonzero elements of a field (``K``) of - characteristic not 2, and we set `k = i \cdot j`. + - ``QuaternionAlgebra(K, a, b)``, where `K` is a ring in which 2 + is a unit and `a` and `b` are units of `K`. + + - ``QuaternionAlgebra(D)``, where `D \ge 1` is a squarefree + integer. This constructs a quaternion algebra of discriminant + `D` over `K = \QQ`. Suitable nonzero rational numbers `a`, `b` + as above are deduced from `D`. + + OUTPUT: - - ``QuaternionAlgebra(D)``: a rational quaternion algebra with - discriminant ``D``, where `D > 1` is a squarefree integer. + The quaternion algebra `(a, b)_K` over `K` generated by `i`, `j` + subject to `i^2 = a`, `j^2 = b`, and `ji = -ij`. EXAMPLES: - ``QuaternionAlgebra(a, b)`` - return quaternion algebra over the - *smallest* field containing the nonzero elements ``a`` and ``b`` with - generators ``i``, ``j``, ``k`` with `i^2=a`, `j^2=b` and `j \cdot i = - -i \cdot j`:: + ``QuaternionAlgebra(a, b)`` -- return the quaternion algebra + `(a, b)_K`, where the base ring `K` is a suitably chosen field + containing `a` and `b`:: sage: QuaternionAlgebra(-2,-3) Quaternion Algebra (-2, -3) with base ring Rational Field @@ -107,11 +117,15 @@ class QuaternionAlgebraFactory(UniqueFactory): Quaternion Algebra (I, sqrt(-3)) with base ring Symbolic Ring sage: QuaternionAlgebra(1r,1) Quaternion Algebra (1, 1) with base ring Rational Field + sage: A. = ZZ[] + sage: QuaternionAlgebra(-1, t) + Quaternion Algebra (-1, t) with base ring Fraction Field of Univariate Polynomial Ring in t over Integer Ring - Python ints, longs and floats may be passed to the - ``QuaternionAlgebra(a, b)`` constructor, as may all pairs of nonzero - elements of a ring not of characteristic 2. The following tests address - the issues raised in :trac:`10601`:: + Python ints and floats may be passed to the + ``QuaternionAlgebra(a, b)`` constructor, as may all pairs of + nonzero elements of a domain not of characteristic 2. + + The following tests address the issues raised in :trac:`10601`:: sage: QuaternionAlgebra(1r,1) Quaternion Algebra (1, 1) with base ring Rational Field @@ -120,28 +134,27 @@ class QuaternionAlgebraFactory(UniqueFactory): sage: QuaternionAlgebra(0,0) Traceback (most recent call last): ... - ValueError: a and b must be nonzero + ValueError: defining elements of quaternion algebra (0, 0) are not invertible in Rational Field sage: QuaternionAlgebra(GF(2)(1),1) Traceback (most recent call last): ... - ValueError: a and b must be elements of a ring with characteristic not 2 + ValueError: 2 is not invertible in Finite Field of size 2 sage: a = PermutationGroupElement([1,2,3]) sage: QuaternionAlgebra(a, a) Traceback (most recent call last): ... ValueError: a and b must be elements of a ring with characteristic not 2 - ``QuaternionAlgebra(K, a, b)`` - return quaternion algebra over the - field ``K`` with generators ``i``, ``j``, ``k`` with `i^2=a`, `j^2=b` - and `i \cdot j = -j \cdot i`:: + ``QuaternionAlgebra(K, a, b)`` -- return the quaternion algebra + defined by `(a, b)` over the ring `K`:: sage: QuaternionAlgebra(QQ, -7, -21) Quaternion Algebra (-7, -21) with base ring Rational Field sage: QuaternionAlgebra(QQ[sqrt(2)], -2,-3) Quaternion Algebra (-2, -3) with base ring Number Field in sqrt2 with defining polynomial x^2 - 2 with sqrt2 = 1.414213562373095? - ``QuaternionAlgebra(D)`` - ``D`` is a squarefree integer; returns a - rational quaternion algebra of discriminant ``D``:: + ``QuaternionAlgebra(D)`` -- `D` is a squarefree integer; return a + rational quaternion algebra of discriminant `D`:: sage: QuaternionAlgebra(1) Quaternion Algebra (-1, 1) with base ring Rational Field @@ -234,16 +247,14 @@ def create_key(self, arg0, arg1=None, arg2=None, names='i,j,k'): # QuaternionAlgebra(K, a, b) else: K = arg0 - if K not in _Fields: - raise TypeError("base ring of quaternion algebra must be a field") a = K(arg1) b = K(arg2) - if K.characteristic() == 2: - # Lameness! - raise ValueError("a and b must be elements of a ring with characteristic not 2") - if a == 0 or b == 0: - raise ValueError("a and b must be nonzero") + if not K(2).is_unit(): + raise ValueError("2 is not invertible in %s" % K) + if not (a.is_unit() and b.is_unit()): + raise ValueError("defining elements of quaternion algebra (%s, %s) are not invertible in %s" + % (a, b, K)) names = normalize_names(3, names) return (K, a, b, names) @@ -310,6 +321,7 @@ def ngens(self): """ return 3 + @cached_method def basis(self): """ Return the fixed basis of ``self``, which is `1`, `i`, `j`, `k`, where @@ -330,11 +342,8 @@ def basis(self): sage: Q.basis() is Q.basis() True """ - try: - return self.__basis - except AttributeError: - self.__basis = tuple([self(1)] + list(self.gens())) - return self.__basis + i, j, k = self.gens() + return (self.one(), i, j, k) @cached_method def inner_product_matrix(self): @@ -547,10 +556,28 @@ def random_element(self, *args, **kwds): return self([K.random_element(*args, **kwds) for _ in range(4)]) @cached_method + def free_module(self): + """ + Return the free module associated to ``self`` with inner + product given by the reduced norm. + + EXAMPLES:: + + sage: A. = LaurentPolynomialRing(GF(3)) + sage: B = QuaternionAlgebra(A, -1, t) + sage: B.free_module() + Ambient free quadratic module of rank 4 over the principal ideal domain Univariate Laurent Polynomial Ring in t over Finite Field of size 3 + Inner product matrix: + [2 0 0 0] + [0 2 0 0] + [0 0 t 0] + [0 0 0 t] + """ + return FreeModule(self.base_ring(), 4, inner_product_matrix=self.inner_product_matrix()) + def vector_space(self): """ - Return the vector space associated to ``self`` with inner product given - by the reduced norm. + Alias for :meth:`free_module`. EXAMPLES:: @@ -562,23 +589,26 @@ def vector_space(self): [ 0 0 -38 0] [ 0 0 0 -114] """ - return VectorSpace(self.base_ring(), 4, inner_product_matrix=self.inner_product_matrix()) + return self.free_module() class QuaternionAlgebra_ab(QuaternionAlgebra_abstract): """ - The quaternion algebra of the form `(a, b/K)`, where `i^2=a`, `j^2 = b`, - and `j*i = -i*j`. ``K`` is a field not of characteristic 2 and ``a``, - ``b`` are nonzero elements of ``K``. + A quaternion algebra of the form `(a, b)_K`. See ``QuaternionAlgebra`` for many more examples. INPUT: - - ``base_ring`` -- commutative ring - - ``a, b`` -- elements of ``base_ring`` + - ``base_ring`` -- a commutative ring `K` in which 2 is invertible + - ``a, b`` -- units of `K` - ``names`` -- string (optional, default 'i,j,k') names of the generators + OUTPUT: + + The quaternion algebra `(a, b)` over `K` generated by `i` and `j` + subject to `i^2 = a`, `j^2 = b`, and `ji = -ij`. + EXAMPLES:: sage: QuaternionAlgebra(QQ, -7, -21) # indirect doctest @@ -587,7 +617,7 @@ class QuaternionAlgebra_ab(QuaternionAlgebra_abstract): def __init__(self, base_ring, a, b, names='i,j,k'): """ Create the quaternion algebra with `i^2 = a`, `j^2 = b`, and - `i*j = -j*i = k`. + `ij = -ji = k`. TESTS: @@ -609,12 +639,12 @@ def __init__(self, base_ring, a, b, names='i,j,k'): sage: TestSuite(Q).run() - The base ring must be a field:: + The element 2 must be a unit in the base ring:: sage: Q. = QuaternionAlgebra(ZZ,-5,-19) Traceback (most recent call last): ... - TypeError: base ring of quaternion algebra must be a field + ValueError: 2 is not invertible in Integer Ring """ ParentWithGens.__init__(self, base_ring, names=names, category=Algebras(base_ring).Division()) self._a = a @@ -631,10 +661,8 @@ def __init__(self, base_ring, a, b, names='i,j,k'): # underlying representation of quadratic fields is a bit # tricky. self.Element = quaternion_algebra_element.QuaternionAlgebraElement_number_field - elif base_ring in _Fields: - self.Element = quaternion_algebra_element.QuaternionAlgebraElement_generic else: - raise TypeError("base ring of quaternion algebra must be a field") + self.Element = quaternion_algebra_element.QuaternionAlgebraElement_generic self._populate_coercion_lists_(coerce_list=[base_ring]) self._gens = [self([0,1,0,0]), self([0,0,1,0]), self([0,0,0,1])] @@ -850,7 +878,7 @@ def invariants(self): """ Return the structural invariants `a`, `b` of this quaternion algebra: ``self`` is generated by `i`, `j` subject to - `i^2 = a`, `j^2 = b` and `j*i = -i*j`. + `i^2 = a`, `j^2 = b` and `ji = -ij`. EXAMPLES:: diff --git a/src/sage/algebras/quatalg/quaternion_algebra_element.pyx b/src/sage/algebras/quatalg/quaternion_algebra_element.pyx index 95c2732c78a..60697f596dd 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra_element.pyx +++ b/src/sage/algebras/quatalg/quaternion_algebra_element.pyx @@ -692,6 +692,26 @@ cdef class QuaternionAlgebraElement_generic(QuaternionAlgebraElement_abstract): """ TESTS: + Test operations on quaternions over a base ring that is not a field:: + + sage: A. = LaurentPolynomialRing(GF(3)) + sage: B = QuaternionAlgebra(A, -1, t) + sage: i, j, k = B.gens() + sage: i*j + k + sage: (j + k).reduced_norm() + t + + Inverting an element is currently only possible if its reduced + norm is a unit:: + + sage: ~k + (t^-1)*k + sage: ~(i + j) + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for *: 'Fraction Field of Univariate Polynomial Ring in t over Finite Field of size 3' and 'Quaternion Algebra (2, t) with base ring Univariate Laurent Polynomial Ring in t over Finite Field of size 3' + We test pickling:: sage: R. = Frac(QQ['x']); Q. = QuaternionAlgebra(R,-5*x,-2) @@ -701,7 +721,7 @@ cdef class QuaternionAlgebraElement_generic(QuaternionAlgebraElement_abstract): """ def __init__(self, parent, v, bint check=True): """ - Create a quaternion over some general base field. + Create a quaternion over a general base ring. EXAMPLES:: diff --git a/src/sage/calculus/calculus.py b/src/sage/calculus/calculus.py index aa1d6db8388..6109e18c45f 100644 --- a/src/sage/calculus/calculus.py +++ b/src/sage/calculus/calculus.py @@ -421,6 +421,7 @@ from sage.misc.lazy_import import lazy_import lazy_import('sage.interfaces.maxima_lib', 'maxima') +from types import FunctionType ######################################################## @@ -2057,6 +2058,34 @@ def _inverse_laplace_latex_(self, *args): maxima_hyper = re.compile(r"\%f\[\d+,\d+\]") # matches %f[m,n] +def _is_function(v): + r""" + Return whether a symbolic element is a function, not a variable. + + TESTS:: + + sage: from sage.calculus.calculus import _is_function + sage: _is_function(x) + False + sage: _is_function(sin) + True + + Check that :trac:`31756` is fixed:: + + sage: from sage.libs.pynac.pynac import symbol_table + sage: _is_function(symbol_table['mathematica']['Gamma']) + True + + sage: from sage.libs.pynac.pynac import register_symbol + sage: foo = lambda x: x^2 + 1 + sage: register_symbol(foo, dict(mathematica='Foo')) # optional - mathematica + sage: mathematica('Foo[x]').sage() # optional - mathematica + x^2 + 1 + """ + # note that Sage variables are callable, so we only check the type + return isinstance(v, Function) or isinstance(v, FunctionType) + + def symbolic_expression_from_maxima_string(x, equals_sub=False, maxima=maxima): r""" Given a string representation of a Maxima expression, parse it and @@ -2144,9 +2173,9 @@ def symbolic_expression_from_maxima_string(x, equals_sub=False, maxima=maxima): +Infinity """ var_syms = {k: v for k, v in symbol_table.get('maxima', {}).items() - if not isinstance(v,Function)} + if not _is_function(v)} function_syms = {k: v for k, v in symbol_table.get('maxima', {}).items() - if isinstance(v,Function)} + if _is_function(v)} if not len(x): raise RuntimeError("invalid symbolic expression -- ''") @@ -2402,9 +2431,9 @@ def symbolic_expression_from_string(s, syms={}, accept_sequence=False): """ parse_func = SR_parser.parse_sequence if accept_sequence else SR_parser.parse_expression parser_make_var.set_names({k: v for k, v in syms.items() - if not isinstance(v,Function)}) + if not _is_function(v)}) parser_make_function.set_names({k: v for k, v in syms.items() - if isinstance(v,Function)}) + if _is_function(v)}) return parse_func(s) diff --git a/src/sage/categories/morphism.pyx b/src/sage/categories/morphism.pyx index 056526d2d98..0e56a024e7c 100644 --- a/src/sage/categories/morphism.pyx +++ b/src/sage/categories/morphism.pyx @@ -1,13 +1,30 @@ """ Morphisms +This module defines the base classes of morphisms between objects of a given +category. + +EXAMPLES: + +Typically, a morphism is defined by the images of the generators of the domain. :: + + sage: X. = ZZ[] + sage: Y. = ZZ[] + sage: X.hom([c, c^2]) + Ring morphism: + From: Multivariate Polynomial Ring in a, b over Integer Ring + To: Univariate Polynomial Ring in c over Integer Ring + Defn: a |--> c + b |--> c^2 + AUTHORS: -- William Stein: initial version +- William Stein (2005): initial version -- David Joyner (12-17-2005): added examples +- David Joyner (2005-12-17): added examples + +- Robert Bradshaw (2007-06-25): Pyrexification -- Robert Bradshaw (2007-06-25) Pyrexification """ #***************************************************************************** @@ -20,12 +37,11 @@ AUTHORS: # http://www.gnu.org/licenses/ #***************************************************************************** -from cpython.object cimport * -from sage.misc.constant_function import ConstantFunction - import operator +from cpython.object cimport * +from sage.misc.constant_function import ConstantFunction from sage.structure.element cimport Element, ModuleElement from sage.structure.richcmp cimport richcmp_not_equal, rich_to_bool @@ -340,7 +356,14 @@ cdef class Morphism(Map): ... NotImplementedError: unable to compare morphisms of type <... 'sage.categories.morphism.IdentityMorphism'> and <... 'sage.categories.morphism.SetMorphism'> with domain Partitions of the integer 5 - Check that :trac:`29632` is fixed:: + We check that :trac:`28617` is fixed:: + + sage: FF = GF(2^20) + sage: f = FF.frobenius_endomorphism() + sage: f == FF.frobenius_endomorphism() + True + + and that :trac:`29632` is fixed:: sage: R. = QuadraticField(-1)[] sage: f = R.hom(R.gens(), R) @@ -368,14 +391,19 @@ cdef class Morphism(Map): # If so, we see the base as a ring of scalars and create new # gens by picking an element of the initial domain (e) and # multiplying it with the gens of the scalar ring. + # + # It is known that this way of comparing morphisms may give + # a mathematically wrong answer. See Trac #28617 and #31783. if e is not None and isinstance(e, ModuleElement): B = (e)._parent._base - gens = [(e)._lmul_(B.coerce(x)) for x in gens] - for e in gens: - x = self(e) - y = other(e) + gens = [e * B.coerce(x) for x in gens] + for g in gens: + x = self(g) + y = other(g) if x != y: return richcmp_not_equal(x, y, op) + if e is None and g: + e = g # Check base base = domain._base if base is None or base is domain: @@ -442,7 +470,7 @@ cdef class IdentityMorphism(Morphism): cpdef Element _call_(self, x): return x - cpdef Element _call_with_args(self, x, args=(), kwds={}): + cpdef Element _call_with_args(self, x, args=(), kwds={}): if not args and not kwds: return x cdef Parent C = self._codomain diff --git a/src/sage/categories/pushout.py b/src/sage/categories/pushout.py index e270892257f..7120901bfd2 100644 --- a/src/sage/categories/pushout.py +++ b/src/sage/categories/pushout.py @@ -2351,7 +2351,7 @@ class CompletionFunctor(ConstructionFunctor): """ rank = 4 _real_types = ['Interval', 'Ball', 'MPFR', 'RDF', 'RLF', 'RR'] - _dvr_types = [None, 'fixed-mod', 'floating-point', 'capped-abs', 'capped-rel', 'lattice-cap', 'lattice-float'] + _dvr_types = [None, 'fixed-mod', 'floating-point', 'capped-abs', 'capped-rel', 'lattice-cap', 'lattice-float', 'relaxed'] def __init__(self, p, prec, extras=None): """ diff --git a/src/sage/coding/ag_code.py b/src/sage/coding/ag_code.py new file mode 100644 index 00000000000..9b12c412f18 --- /dev/null +++ b/src/sage/coding/ag_code.py @@ -0,0 +1,846 @@ +""" +AG codes + +Algebraic geometry codes or shortly AG codes are linear codes defined using +functions or differentials on algebraic curves over finite fields. Sage +implements evaluation AG codes and differential AG codes as Goppa defined in +[Gop1981]_ and provides decoding algorithms for them in full generality. + +EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: pls.remove(Q) + sage: G = 5*Q + sage: codes.EvaluationAGCode(pls, G) + [8, 5] evaluation AG code over GF(4) + sage: codes.DifferentialAGCode(pls, G) + [8, 3] differential AG code over GF(4) + +As is well known, the two kinds of AG codes are dual to each other. :: + + sage: E = codes.EvaluationAGCode(pls, G) + sage: D = codes.DifferentialAGCode(pls, G) + sage: E.dual_code() == D + True + sage: D.dual_code() == E + True + +Decoders for both evaluation and differential AG codes are available. + +.. toctree:: + + ag_code_decoders + +A natural generalization of classical Goppa codes is Cartier codes [Cou2014]_. Cartier codes are +subfield subcodes of differential AG codes. + +EXAMPLES:: + + sage: F. = GF(9) + sage: P. = ProjectiveSpace(F, 2); + sage: C = Curve(x^3*y + y^3*z + x*z^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: Z, = C([0,0,1]).places() + sage: pls.remove(Z) + sage: G = 3*Z + sage: codes.CartierCode(pls, G) # long time + [9, 4] Cartier code over GF(3) + +AUTHORS: + +- Kwankyu Lee (2019-03): initial version + +""" + +# **************************************************************************** +# Copyright (C) 2019 Kwankyu Lee +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from sage.modules.free_module_element import vector +from sage.matrix.constructor import matrix +from sage.matrix.matrix_space import MatrixSpace + +from .linear_code import (AbstractLinearCode, + LinearCodeGeneratorMatrixEncoder, + LinearCodeSyndromeDecoder) + +from .ag_code_decoders import (EvaluationAGCodeUniqueDecoder, + EvaluationAGCodeEncoder, + DifferentialAGCodeUniqueDecoder, + DifferentialAGCodeEncoder) + + +class AGCode(AbstractLinearCode): + """ + Base class of algebraic geometry codes. + + A subclass of this class is required to define ``_function_field`` + attribute that refers to an abstract functiom field or the function field + of the underlying curve used to construct a code of the class. + """ + def base_function_field(self): + """ + Return the function field used to construct the code. + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: pls.remove(Q) + sage: G = 5*Q + sage: code = codes.EvaluationAGCode(pls, G) + sage: code.base_function_field() + Function field in y defined by y^2 + y + x^3 + """ + return self._function_field + + +class EvaluationAGCode(AGCode): + """ + Evaluation AG code defined by rational places ``pls`` and a divisor ``G``. + + INPUT: + + - ``pls`` -- a list of rational places of a function field + + - ``G`` -- a divisor whose support is disjoint from ``pls`` + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: Q, = C.places_at_infinity() + sage: pls.remove(Q) + sage: G = 5*Q + sage: codes.EvaluationAGCode(pls, G) + [8, 5] evaluation AG code over GF(4) + """ + _registered_encoders = {} + _registered_decoders = {} + + def __init__(self, pls, G): + """ + Initialize. + + TESTS:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: Q, = C.places_at_infinity() + sage: pls = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.EvaluationAGCode(pls, G) + sage: TestSuite(code).run() + """ + F = G.parent().function_field() + K = F.constant_base_field() + n = len(pls) + + if any(p.degree() > 1 for p in pls): + raise ValueError("there is a nonrational place among the places") + + if any(p in pls for p in G.support()): + raise ValueError("the support of the divisor is not disjoint from the places") + + self._registered_encoders['evaluation'] = EvaluationAGCodeEncoder + self._registered_decoders['K'] = EvaluationAGCodeUniqueDecoder + + super().__init__(K, n, default_encoder_name='evaluation', + default_decoder_name='K') + + # compute basis functions associated with a generator matrix + basis_functions = G.basis_function_space() + m = matrix([vector(K, [b.evaluate(p) for p in pls]) for b in basis_functions]) + I = MatrixSpace(K, m.nrows()).identity_matrix() + mI = m.augment(I) + mI.echelonize() + M = mI.submatrix(0, 0, m.nrows(), m.ncols()) + T = mI.submatrix(0, m.ncols()) + r = M.rank() + + self._generator_matrix = M.submatrix(0, 0, r) + self._basis_functions = [sum(c * b for c, b in zip(T[i], basis_functions)) + for i in range(r)] + + self._pls = tuple(pls) + self._G = G + + self._function_field = F + + def __eq__(self, other): + """ + Test equality of ``self`` with ``other``. + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: Q, = C.places_at_infinity() + sage: pls.remove(Q) + sage: codes.EvaluationAGCode(pls, 5*Q) == codes.EvaluationAGCode(pls, 6*Q) + False + """ + if self is other: + return True + + if not isinstance(other, EvaluationAGCode): + return False + + return self._pls == other._pls and self._G == other._G + + def __hash__(self): + """ + Return the hash value of ``self``. + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: Q, = C.places_at_infinity() + sage: pls.remove(Q) + sage: code = codes.EvaluationAGCode(pls, 5*Q) + sage: {code: 1} + {[8, 5] evaluation AG code over GF(4): 1} + """ + return hash((self._pls, self._G)) + + def _repr_(self): + """ + Return the string representation of ``self``. + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: Q, = C.places_at_infinity() + sage: pls.remove(Q) + sage: codes.EvaluationAGCode(pls, 7*Q) + [8, 7] evaluation AG code over GF(4) + """ + return "[{}, {}] evaluation AG code over GF({})".format( + self.length(), self.dimension(), self.base_field().cardinality()) + + def _latex_(self): + r""" + Return the latex representation of ``self``. + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: Q, = C.places_at_infinity() + sage: pls.remove(Q) + sage: code = codes.EvaluationAGCode(pls, 3*Q) + sage: latex(code) + [8, 3]\text{ evaluation AG code over }\Bold{F}_{2^{2}} + """ + return r"[{}, {}]\text{{ evaluation AG code over }}{}".format( + self.length(), self.dimension(), self.base_field()._latex_()) + + def basis_functions(self): + r""" + Return the basis functions associated with the generator matrix. + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: Q, = C.places_at_infinity() + sage: pls.remove(Q) + sage: code = codes.EvaluationAGCode(pls, 3*Q) + sage: code.basis_functions() + (y + a*x + 1, y + x, (a + 1)*x) + sage: matrix([[f.evaluate(p) for p in pls] for f in code.basis_functions()]) + [ 1 0 0 1 a a + 1 1 0] + [ 0 1 0 1 1 0 a + 1 a] + [ 0 0 1 1 a a a + 1 a + 1] + """ + return tuple(self._basis_functions) + + def generator_matrix(self): + r""" + Return a generator matrix of the code. + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: Q, = C.places_at_infinity() + sage: pls.remove(Q) + sage: code = codes.EvaluationAGCode(pls, 3*Q) + sage: code.generator_matrix() + [ 1 0 0 1 a a + 1 1 0] + [ 0 1 0 1 1 0 a + 1 a] + [ 0 0 1 1 a a a + 1 a + 1] + """ + return self._generator_matrix + + def designed_distance(self): + """ + Return the designed distance of the AG code. + + If the code is of dimension zero, then a ``ValueError`` is raised. + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: Q, = C.places_at_infinity() + sage: pls.remove(Q) + sage: code = codes.EvaluationAGCode(pls, 3*Q) + sage: code.designed_distance() + 5 + """ + if self.dimension() == 0: + raise ValueError("not defined for zero code") + + d = self.length() - self._G.degree() + return d if d > 0 else 1 + + +class DifferentialAGCode(AGCode): + """ + Differential AG code defined by rational places ``pls`` and a divisor ``G`` + + INPUT: + + - ``pls`` -- a list of rational places of a function field + + - ``G`` -- a divisor whose support is disjoint from ``pls`` + + EXAMPLES:: + + sage: F. = GF(4) + sage: A2. = AffineSpace(F, 2) + sage: C = A2.curve(y^3 + y - x^4) + sage: Q = C.places_at_infinity()[0] + sage: O = C([0,0]).place() + sage: pls = [p for p in C.places() if p not in [O, Q]] + sage: G = -O + 3*Q + sage: codes.DifferentialAGCode(pls, -O + Q) + [3, 2] differential AG code over GF(4) + """ + _registered_encoders = {} + _registered_decoders = {} + + def __init__(self, pls, G): + """ + Initialize. + + TESTS:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: Q, = C.places_at_infinity() + sage: pls.remove(Q) + sage: code = codes.DifferentialAGCode(pls, 3*Q) + sage: TestSuite(code).run() + """ + F = G.parent().function_field() + K = F.constant_base_field() + n = len(pls) + + if any(p.degree() > 1 for p in pls): + raise ValueError("there is a nonrational place among the places") + + if any(p in pls for p in G.support()): + raise ValueError("the support of the divisor is not disjoint from the places") + + self._registered_encoders['residue'] = DifferentialAGCodeEncoder + self._registered_decoders['K'] = DifferentialAGCodeUniqueDecoder + + super().__init__(K, n, default_encoder_name='residue', + default_decoder_name='K') + + # compute basis differentials associated with a generator matrix + basis_differentials = (-sum(pls) + G).basis_differential_space() + m = matrix([vector(K, [w.residue(p) for p in pls]) for w in basis_differentials]) + I = MatrixSpace(K, m.nrows()).identity_matrix() + mI = m.augment(I) + mI.echelonize() + M = mI.submatrix(0, 0, m.nrows(), m.ncols()) + T = mI.submatrix(0, m.ncols()) + r = M.rank() + + self._generator_matrix = M.submatrix(0, 0, r) + self._basis_differentials = [sum(c * w for c, w in zip(T[i], basis_differentials)) + for i in range(r)] + + self._pls = tuple(pls) + self._G = G + self._function_field = F + + def __eq__(self, other): + """ + Test equality of ``self`` with ``other``. + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: Q, = C.places_at_infinity() + sage: pls.remove(Q) + sage: c1 = codes.DifferentialAGCode(pls, 3*Q) + sage: c2 = codes.DifferentialAGCode(pls, 3*Q) + sage: c1 is c2 + False + sage: c1 == c2 + True + """ + if self is other: + return True + + if not isinstance(other, DifferentialAGCode): + return False + + return self._pls == other._pls and self._G == other._G + + def __hash__(self): + """ + Return the hash of ``self``. + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: Q, = C.places_at_infinity() + sage: pls.remove(Q) + sage: code = codes.DifferentialAGCode(pls, 3*Q) + sage: {code: 1} + {[8, 5] differential AG code over GF(4): 1} + """ + return hash((self._pls, self._G)) + + def _repr_(self): + """ + Return the string representation of ``self``. + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: Q, = C.places_at_infinity() + sage: pls.remove(Q) + sage: codes.DifferentialAGCode(pls, 3*Q) + [8, 5] differential AG code over GF(4) + """ + return "[{}, {}] differential AG code over GF({})".format( + self.length(), self.dimension(), self.base_field().cardinality()) + + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: Q, = C.places_at_infinity() + sage: pls.remove(Q) + sage: code = codes.DifferentialAGCode(pls, 3*Q) + sage: latex(code) + [8, 5]\text{ differential AG code over }\Bold{F}_{2^{2}} + """ + return r"[{}, {}]\text{{ differential AG code over }}{}".format( + self.length(), self.dimension(), self.base_field()._latex_()) + + def basis_differentials(self): + r""" + Return the basis differentials associated with the generator matrix. + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: Q, = C.places_at_infinity() + sage: pls.remove(Q) + sage: code = codes.DifferentialAGCode(pls, 3*Q) + sage: matrix([[w.residue(p) for p in pls] for w in code.basis_differentials()]) + [ 1 0 0 0 0 a + 1 a + 1 1] + [ 0 1 0 0 0 a + 1 a 0] + [ 0 0 1 0 0 a 1 a] + [ 0 0 0 1 0 a 0 a + 1] + [ 0 0 0 0 1 1 1 1] + """ + return tuple(self._basis_differentials) + + def generator_matrix(self): + """ + Return a generator matrix of the code. + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: Q, = C.places_at_infinity() + sage: pls.remove(Q) + sage: code = codes.DifferentialAGCode(pls, 3*Q) + sage: code.generator_matrix() + [ 1 0 0 0 0 a + 1 a + 1 1] + [ 0 1 0 0 0 a + 1 a 0] + [ 0 0 1 0 0 a 1 a] + [ 0 0 0 1 0 a 0 a + 1] + [ 0 0 0 0 1 1 1 1] + """ + return self._generator_matrix + + def designed_distance(self): + """ + Return the designed distance of the differential AG code. + + If the code is of dimension zero, then a ``ValueError`` is raised. + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: Q, = C.places_at_infinity() + sage: pls.remove(Q) + sage: code = codes.DifferentialAGCode(pls, 3*Q) + sage: code.designed_distance() + 3 + """ + if self.dimension() == 0: + raise ValueError("not defined for zero code") + + d = self._G.degree() - 2 * self._function_field.genus() + 2 + return d if d > 0 else 1 + + +class CartierCode(AGCode): + r""" + Cartier code defined by rational places ``pls`` and a divisor ``G`` of a function field. + + INPUT: + + - ``pls`` -- a list of rational places + + - ``G`` -- a divisor whose support is disjoint from ``pls`` + + - ``r`` -- integer (default: 1) + + - ``name`` -- string; name of the generator of the subfield `\GF{p^r}` + + OUTPUT: Cartier code over `\GF{p^r}` where `p` is the characteristic of the + base constant field of the function field + + Note that if ``r`` is 1 the default, then ``name`` can be omitted. + + EXAMPLES:: + + sage: F. = GF(9) + sage: P. = ProjectiveSpace(F, 2); + sage: C = Curve(x^3*y + y^3*z + x*z^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: Z, = C(0,0,1).places() + sage: pls.remove(Z) + sage: G = 3*Z + sage: code = codes.CartierCode(pls, G) # long time + sage: code.minimum_distance() # long time + 2 + """ + def __init__(self, pls, G, r=1, name=None): + """ + Initialize. + + TESTS:: + + sage: F. = GF(9) + sage: P. = ProjectiveSpace(F, 2); + sage: C = Curve(x^3*y + y^3*z + x*z^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: Z, = C(0,0,1).places() + sage: pls.remove(Z) + sage: G = 3*Z + sage: code = codes.CartierCode(pls, G) # long time + sage: TestSuite(code).run() # long time + """ + F = G.parent().function_field() + K = F.constant_base_field() + + if any(p.degree() > 1 for p in pls): + raise ValueError("there is a nonrational place among the places") + + if any(p in pls for p in G.support()): + raise ValueError("the support of the divisor is not disjoint from the places") + + if K.degree() % r != 0: + raise ValueError("{} does not divide the degree of the constant base field".format(r)) + + n = len(pls) + D = sum(pls) + p = K.characteristic() + + subfield = K.subfield(r, name=name) + + # compute a basis R of the space of differentials in Omega(G - D) + # fixed by the Cartier operator + E = G - D + + Grp = E.parent() # group of divisors + V, fr_V, to_V = E.differential_space() + + EE = Grp(0) + dic = E.dict() + for place in dic: + mul = dic[place] + if mul > 0: + mul = mul // p**r + EE += mul * place + + W, fr_W, to_W = EE.differential_space() + + a = K.gen() + field_basis = [a**i for i in range(K.degree())] # over prime subfield + basis = E.basis_differential_space() + + m = [] + for w in basis: + for c in field_basis: + cw = F(c) * w # c does not coerce... + carcw = cw + for i in range(r): # apply cartier r times + carcw = carcw.cartier() + m.append([f for e in to_W(carcw - cw) for f in vector(e)]) + + ker = matrix(m).kernel() + + R = [] + s = len(field_basis) + ncols = s * len(basis) + for row in ker.basis(): + v = vector([K(row[d:d+s]) for d in range(0,ncols,s)]) + R.append(fr_V(v)) + + # construct a generator matrix + m = [] + col_index = D.support() + for w in R: + row = [] + for p in col_index: + res = w.residue(p).trace() # lies in constant base field + c = subfield(res) # as w is Cartier fixed + row.append(c) + m.append(row) + + self._generator_matrix = matrix(m).row_space().basis_matrix() + + self._pls = tuple(pls) + self._G = G + self._r = r + self._function_field = F + + self._registered_encoders['GeneratorMatrix'] = LinearCodeGeneratorMatrixEncoder + self._registered_decoders['Syndrome'] = LinearCodeSyndromeDecoder + + super().__init__(subfield, n, + default_encoder_name='GeneratorMatrix', + default_decoder_name='Syndrome') + + def __eq__(self, other): + """ + Test equality of ``self`` with ``other``. + + EXAMPLES:: + + sage: F. = GF(9) + sage: P. = ProjectiveSpace(F, 2); + sage: C = Curve(x^3*y + y^3*z + x*z^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: Z, = C(0,0,1).places() + sage: pls.remove(Z) + sage: c1 = codes.CartierCode(pls, 3*Z) # long time + sage: c2 = codes.CartierCode(pls, 1*Z) # long time + sage: c1 == c2 # long time + False + """ + if self is other: + return True + + if not isinstance(other, CartierCode): + return False + + return self._pls == other._pls and self._G == other._G and self._r == other._r + + def __hash__(self): + """ + Return the hash of ``self``. + + EXAMPLES:: + + sage: F. = GF(9) + sage: P. = ProjectiveSpace(F, 2); + sage: C = Curve(x^3*y + y^3*z + x*z^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: Z, = C(0,0,1).places() + sage: pls.remove(Z) + sage: G = 3*Z + sage: code = codes.CartierCode(pls, G) # long time + sage: {code: 1} # long time + {[9, 4] Cartier code over GF(3): 1} + """ + return hash((self._pls, self._G, self._r)) + + def _repr_(self): + """ + Return the string representation of ``self``. + + EXAMPLES:: + + sage: F. = GF(9) + sage: P. = ProjectiveSpace(F, 2); + sage: C = Curve(x^3*y + y^3*z + x*z^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: Z, = C(0,0,1).places() + sage: pls.remove(Z) + sage: G = 3*Z + sage: codes.CartierCode(pls, G) # long time + [9, 4] Cartier code over GF(3) + """ + return "[{}, {}] Cartier code over GF({})".format( + self.length(), self.dimension(), self.base_field().cardinality()) + + def _latex_(self): + r""" + Return the latex representation of ``self``. + + EXAMPLES:: + + sage: F. = GF(9) + sage: P. = ProjectiveSpace(F, 2); + sage: C = Curve(x^3*y + y^3*z + x*z^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: Z, = C(0,0,1).places() + sage: pls.remove(Z) + sage: G = 3*Z + sage: code = codes.CartierCode(pls, G) # long time + sage: latex(code) # long time + [9, 4]\text{ Cartier code over }\Bold{F}_{3} + """ + return r"[{}, {}]\text{{ Cartier code over }}{}".format( + self.length(), self.dimension(), self.base_field()._latex_()) + + def generator_matrix(self): + r""" + Return a generator matrix of the Cartier code. + + EXAMPLES:: + + sage: F. = GF(9) + sage: P. = ProjectiveSpace(F, 2); + sage: C = Curve(x^3*y + y^3*z + x*z^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: Z, = C(0,0,1).places() + sage: pls.remove(Z) + sage: G = 3*Z + sage: code = codes.CartierCode(pls, G) # long time + sage: code.generator_matrix() # long time + [1 0 0 2 2 0 2 2 0] + [0 1 0 2 2 0 2 2 0] + [0 0 1 0 0 0 0 0 2] + [0 0 0 0 0 1 0 0 2] + """ + return self._generator_matrix + + def designed_distance(self): + """ + Return the designed distance of the Cartier code. + + The designed distance is that of the differential code of which the + Cartier code is a subcode. + + EXAMPLES:: + + sage: F. = GF(9) + sage: P. = ProjectiveSpace(F, 2); + sage: C = Curve(x^3*y + y^3*z + x*z^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: Z, = C(0,0,1).places() + sage: pls.remove(Z) + sage: G = 3*Z + sage: code = codes.CartierCode(pls, G) # long time + sage: code.designed_distance() # long time + 1 + """ + if self.dimension() == 0: + raise ValueError("not defined for zero code") + + d = self._G.degree() - 2 * self._function_field.genus() + 2 + return d if d > 0 else 1 diff --git a/src/sage/coding/ag_code_decoders.pyx b/src/sage/coding/ag_code_decoders.pyx new file mode 100644 index 00000000000..4a9771f221b --- /dev/null +++ b/src/sage/coding/ag_code_decoders.pyx @@ -0,0 +1,2638 @@ +# -*- coding: utf-8 -*- +r""" +Decoders for AG codes + +This module implements decoders for evaluation and differential AG codes. + +The implemented algorithm for unique decoding of AG codes, named K, is from +[LBO2014]_ and [Lee2016]_. + +EXAMPLES:: + + sage: F. = GF(9) + sage: A2. = AffineSpace(F, 2) + sage: C = Curve(y^3 + y - x^4) + sage: Q, = C.places_at_infinity() + sage: O = C(0,0).place() + sage: pls = C.places() + sage: pls.remove(Q) + sage: pls.remove(O) + sage: G = -O + 18*Q + sage: code = codes.EvaluationAGCode(pls, G) # long time + sage: code # long time + [26, 15] evaluation AG code over GF(9) + sage: decoder = code.decoder('K') # long time + sage: tau = decoder.decoding_radius() # long time + sage: tau # long time + 4 + +The ``decoder`` is now ready for correcting vectors received from a noisy +channel:: + + sage: channel = channels.StaticErrorRateChannel(code.ambient_space(), tau) # long time + sage: message_space = decoder.message_space() # long time + sage: message = message_space.random_element() # long time + sage: encoder = decoder.connected_encoder() # long time + sage: sent_codeword = encoder.encode(message) # long time + sage: received_vector = channel(sent_codeword) # long time + sage: (received_vector - sent_codeword).hamming_weight() # long time + 4 + sage: decoder.decode_to_code(received_vector) == sent_codeword # long time + True + sage: decoder.decode_to_message(received_vector) == message # long time + True + +AUTHORS: + +- Kwankyu Lee (2019-03): initial version + +""" + +# **************************************************************************** +# Copyright (C) 2019 Kwankyu Lee +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + +cimport cython + +from sage.rings.all import PolynomialRing +from sage.rings.function_field.all import FunctionField + +from sage.modules.free_module_element import vector +from sage.matrix.constructor import matrix + +from .encoder import Encoder +from .decoder import Decoder, DecodingError + +from sage.modules.free_module_element cimport FreeModuleElement +from sage.matrix.matrix cimport Matrix +from sage.rings.polynomial.polynomial_element cimport Polynomial + + +class EvaluationAGCodeEncoder(Encoder): + """ + Encoder of an evaluation AG code + + INPUT: + + - ``code`` -- an evaluation AG code + + - ``decoder`` -- a decoder of the code + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.EvaluationAGCode(D, G) + sage: dec = code.decoder('K', Q) + sage: enc = dec.connected_encoder() + sage: enc + Encoder for [8, 5] evaluation AG code over GF(4) + """ + def __init__(self, code, decoder=None): + """ + Initialize. + + TESTS:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.EvaluationAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: enc = dec.connected_encoder() # long time + sage: TestSuite(enc).run(skip='_test_pickling') # long time + """ + super().__init__(code) + + if decoder is None: + decoder = code.decoder('K') + + self._decoder = decoder + self._encode = decoder._encode + self._unencode = decoder._decode + + def __hash__(self): + """ + Return the hash of ``self``. + + TESTS:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.EvaluationAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: enc = dec.connected_encoder() # long time + sage: {enc: 1} # long time + {Encoder for [8, 5] evaluation AG code over GF(4): 1} + """ + return hash((self.code(), self._encode)) + + def __eq__(self, other): + """ + Test equality. + + TESTS:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.EvaluationAGCode(D, G) # long time + sage: dec1 = code.decoder('K', Q) # long time + sage: enc1 = dec1.connected_encoder() # long time + sage: dec2 = code.decoder('K', Q) # long time + sage: enc2 = dec2.connected_encoder() # long time + sage: enc1 == enc2 # long time + True + """ + if self is other: + return True + + if not isinstance(other, EvaluationAGCodeEncoder): + return False + + return self.code() == other.code() and self._decoder == other._decoder + + def _repr_(self): + r""" + Return the string representation of ``self``. + + TESTS:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.EvaluationAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: enc = dec.connected_encoder() # long time + sage: enc # long time + Encoder for [8, 5] evaluation AG code over GF(4) + """ + return "Encoder for {}".format(self.code()) + + def _latex_(self): + r""" + Return the latex representation of ``self``. + + TESTS:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.EvaluationAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: enc = dec.connected_encoder() # long time + sage: latex(enc) # long time + \text{Encoder for }[8, 5]\text{ evaluation AG code over }\Bold{F}_{2^{2}} + """ + return r"\text{{Encoder for }}{}".format(self.code()._latex_()) + + def encode(self, message): + """ + Return the codeword encoded from the message. + + INPUT: + + - ``message`` -- a vector in the message space + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.EvaluationAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: enc = dec.connected_encoder() # long time + sage: msg = enc.message_space().random_element() # long time + sage: codeword = enc.encode(msg) # long time + sage: enc.unencode(codeword) == msg # long time + True + """ + return self._encode(message) + + def unencode_nocheck(self, codeword): + """ + Return the message unencoded from ``codeword``. + + INPUT: + + - ``codeword`` -- a vector in the code + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.EvaluationAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: enc = dec.connected_encoder() # long time + sage: msg = enc.message_space().random_element() # long time + sage: codeword = enc.encode(msg) # long time + sage: enc.unencode(codeword) in enc.message_space() # long time, indirect doctest + True + """ + return self._unencode(codeword) + + +class DifferentialAGCodeEncoder(Encoder): + """ + Encoder of a differential AG code. + + INPUT: + + - ``code`` -- a differential AG code + + - ``decoder`` -- a decoder of the code + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.DifferentialAGCode(D, G) + sage: dec = code.decoder('K', Q) # long time + sage: enc = dec.connected_encoder(); enc # long time + Encoder for [8, 3] differential AG code over GF(4) + """ + def __init__(self, code, decoder=None): + """ + Initialize. + + TESTS:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.DifferentialAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: enc = dec.connected_encoder() # long time + sage: TestSuite(enc).run(skip='_test_pickling') # long time + """ + super().__init__(code) + + if decoder is None: + decoder = code.decoder('K') + + self._decoder = decoder + self._encode = decoder._encode + self._unencode = decoder._decode + + def __hash__(self): + """ + Return the hash of ``self``. + + TESTS:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.DifferentialAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: enc = dec.connected_encoder() # long time + sage: {enc: 1} # long time + {Encoder for [8, 3] differential AG code over GF(4): 1} + """ + return hash((self.code(), self._encode)) + + def __eq__(self, other): + """ + Test equality. + + TESTS:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.DifferentialAGCode(D, G) # long time + sage: dec1 = code.decoder('K', Q) # long time + sage: enc1 = dec1.connected_encoder() # long time + sage: dec2 = code.decoder('K', Q) # long time + sage: enc2 = dec2.connected_encoder() # long time + sage: enc1 == enc2 # long time + True + """ + if self is other: + return True + + if not isinstance(other, DifferentialAGCodeEncoder): + return False + + return self.code() == other.code() and self._decoder == other._decoder + + def _repr_(self): + r""" + Return the string representation of ``self``. + + TESTS:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.DifferentialAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: enc = dec.connected_encoder() # long time + sage: enc # long time + Encoder for [8, 3] differential AG code over GF(4) + """ + return "Encoder for {}".format(self.code()) + + def _latex_(self): + r""" + Return the latex representation of ``self``. + + TESTS:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.DifferentialAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: enc = dec.connected_encoder() # long time + sage: latex(enc) # long time + \text{Encoder for }[8, 3]\text{ differential AG code over }\Bold{F}_{2^{2}} + """ + return r"\text{{Encoder for }}{}".format(self.code()._latex_()) + + def encode(self, message): + """ + Return the codeword encoded from the message. + + INPUT: + + - ``message`` -- a vector in the message space + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.DifferentialAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: enc = dec.connected_encoder() # long time + sage: msg = enc.message_space().random_element() # long time + sage: codeword = enc.encode(msg) # long time + sage: enc.unencode(codeword) == msg # long time + True + """ + return self._encode(message) + + def unencode_nocheck(self, codeword): + """ + Return the message unencoded from ``codeword``. + + INPUT: + + - ``codeword`` -- a vector in the code + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.DifferentialAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: enc = dec.connected_encoder() # long time + sage: msg = enc.message_space().random_element() # long time + sage: codeword = enc.encode(msg) # long time + sage: enc.unencode(codeword) in enc.message_space() # indirect doctest, long time + True + """ + return self._unencode(codeword) + + +class EvaluationAGCodeUniqueDecoder(Decoder): + """ + Unique decoder for evaluation AG codes. + + INPUT: + + - ``code`` -- an evaluation AG code + + - ``Q`` -- (optional) a place, not one of the places supporting the code + + - ``basis`` -- (optional) a basis of the space of functions to evaluate + + - ``verbose`` -- if ``True``, verbose information is printed + + EXAMPLES:: + + sage: k. = GF(4) + sage: P. = AffineSpace(k, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C(0,0) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.EvaluationAGCode(D, G) + sage: dec = code.decoder('K', Q) + sage: enc = dec.connected_encoder() + sage: chan = channels.StaticErrorRateChannel(code.ambient_space(), 1) + sage: rv = chan.transmit(code.random_element()) + sage: enc.encode(dec.decode_to_message(rv)) in code + True + + If ``basis`` is given, that defines the associated evaluation encoding map:: + + sage: basis = tuple(G.basis_function_space()) + sage: dec2 = code.decoder('K', Q, basis) + sage: enc2 = dec2.connected_encoder() + sage: f = basis[0] + sage: cw = vector(f.evaluate(p) for p in D) + sage: enc2.unencode(cw) + (1, 0, 0, 0, 0) + sage: enc2.encode(_) == cw + True + sage: f = basis[1] + sage: cw = vector(f.evaluate(p) for p in D) + sage: enc2.unencode(cw) + (0, 1, 0, 0, 0) + sage: enc2.encode(_) == cw + True + + The default ``basis`` is given by ``code.basis_functions()``. + """ + _decoder_type = {'always-succeed'} + + def __init__(self, code, Q=None, basis=None, verbose=False): + """ + Initialize. + + TESTS:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.EvaluationAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: TestSuite(dec).run() # long time + """ + if not code.dimension() > 0: + raise ValueError("no decoder for degenerate codes") + + F = code.base_function_field() + K = F.constant_base_field() + + if Q is None: + # try to get a rational place not in the support of the AG code + deg = 1 + for p in F.places(deg): + if p not in code._pls: + Q = p + break + if Q is None: # if none, then take a nonrational place + while Q is None: + deg += 1 + Q = F.get_place(deg) + elif Q in code._pls: + raise ValueError("Q is one of the places defining the code") + + if verbose: + print('auxiliary place: {} of degree {}'.format(Q, Q.degree())) + + super().__init__(code, code.ambient_space(), connected_encoder_name='evaluation') + + if Q.degree() > 1: + circuit = EvaluationAGCodeDecoder_K_extension(code._pls, code._G, Q, + verbose=verbose) + else: + circuit = EvaluationAGCodeDecoder_K(code._pls, code._G, Q, + verbose=verbose) + + if basis is None: + basis = code._basis_functions + + C = matrix([circuit.decode(vector(K, [f.evaluate(p) for p in code._pls])) + for f in basis]) + + self._extension = Q.degree() > 1 + self._K = K + self._basis = tuple(basis) + self._C = C + self._Cinv = C.inverse() + + self._circuit = circuit + self._info = circuit.info + self._Q = Q + + def __hash__(self): + """ + Return the hash of ``self``. + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.EvaluationAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: {dec: 1} # long time + {Unique decoder for [8, 5] evaluation AG code over GF(4): 1} + """ + return hash((self.code(), self._Q)) + + def __eq__(self, other): + """ + Check whether ``other`` equals ``self``. + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.EvaluationAGCode(D, G) # long time + sage: dec1 = code.decoder('K', Q) # long time + sage: dec2 = code.decoder('K', Q) # long time + sage: dec1 == dec2 # long time + True + """ + if self is other: + return True + + if not isinstance(other, type(self)): + return False + + return (self.code() == other.code() and self._Q == other._Q + and self._basis == other._basis) + + def _repr_(self): + r""" + Return the string representation of ``self``. + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.EvaluationAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: dec # long time + Unique decoder for [8, 5] evaluation AG code over GF(4) + """ + return "Unique decoder for {}".format(self.code()) + + def _latex_(self): + r""" + Return the latex representation of ``self``. + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.EvaluationAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: latex(dec) # long time + \text{Unique decoder for }[8, 5]\text{ evaluation AG code over }\Bold{F}_{2^{2}} + """ + return r"\text{{Unique decoder for }}{}".format(self.code()._latex_()) + + def _encode(self, message): + r""" + Return the codeword encoded from ``message``. + + INPUT: + + - ``message`` -- a vector to be encoded to a codeword + + TESTS:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.EvaluationAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: enc = dec.connected_encoder() # long time + sage: msg = enc.message_space().random_element() # long time + sage: dec._decode(dec._encode(msg)) == msg # long time + True + """ + K = self._K + C = self._C + circuit = self._circuit + + if self._extension: + internal_message = circuit._lift(vector(K, message)) * C + return circuit._pull_back(circuit.encode(internal_message)) + else: + return circuit.encode(vector(K, message) * C) + + def _decode(self, vector, **kwargs): + r""" + Return the message decoded from ``vector``. + + INPUT: + + - ``vector`` -- a vector to be decoded to a message + + TESTS:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.EvaluationAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: code = dec.code() # long time + sage: cw = code.random_element() # long time + sage: dec._encode(dec._decode(cw)) == cw # long time + True + """ + Cinv = self._Cinv + circuit = self._circuit + + if self._extension: + internal_message = circuit.decode(circuit._lift(vector), **kwargs) * Cinv + return circuit._pull_back(internal_message) + else: + return circuit.decode(vector, **kwargs) * Cinv + + def connected_encoder(self, *args, **kwargs): + r""" + Return the connected encoder for this decoder. + + INPUT: + + - ``args``, ``kwargs`` -- all additional arguments are forwarded to the + constructor of the connected encoder + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.EvaluationAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: dec.connected_encoder() # long time + Encoder for [8, 5] evaluation AG code over GF(4) + """ + return self.code().encoder(self._connected_encoder_name, self, *args, **kwargs) + + def decoding_radius(self): + r""" + Return the decoding radius of the decoder. + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.EvaluationAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: dec.decoding_radius() # long time + 1 + """ + return self._info['decoding_radius'] + + def decode_to_message(self, received_vector, **kwargs): + r""" + Return the message decoded from ``received_vector``. + + INPUT: + + - ``received_vector`` -- a vector in the ambient space of the code + + - ``verbose`` -- boolean; if ``True``, verbose information on the decoding process + is printed + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.EvaluationAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: enc = dec.connected_encoder() # long time + sage: code = dec.code() # long time + sage: chan = channels.StaticErrorRateChannel(code.ambient_space(), 1) # long time + sage: rv = chan.transmit(code.random_element()) # long time + sage: msg = dec.decode_to_message(rv) # long time + sage: cw = enc.encode(msg) # long time + sage: (cw - rv).hamming_weight() == 1 # long time + True + """ + return self._decode(received_vector, **kwargs) + + def decode_to_code(self, received_vector, **kwargs): + r""" + Return the codeword decoded from ``received_vector``. + + INPUT: + + - ``received_vector`` -- a vector in the ambient space of the code + + - ``verbose`` -- boolean; if ``True``, verbose information on the decoding process + is printed + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.EvaluationAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: code = dec.code() # long time + sage: chan = channels.StaticErrorRateChannel(code.ambient_space(), 1) # long time + sage: rv = chan.transmit(code.random_element()) # long time + sage: cw = dec.decode_to_code(rv) # long time + sage: (cw - rv).hamming_weight() == 1 # long time + True + """ + return self._encode(self._decode(received_vector, **kwargs)) + + +class DifferentialAGCodeUniqueDecoder(Decoder): + """ + Unique decoder for a differential AG codes. + + INPUT: + + - ``code`` -- an evaluation AG code + + - ``Q`` -- (optional) a place, not one of the places supporting the code + + - ``basis`` -- (optional) a basis of the space of differentials to take residues + + - ``verbose`` -- if ``True``, verbose information is printed + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C(0,0) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.DifferentialAGCode(D, G) + sage: chan = channels.StaticErrorRateChannel(code.ambient_space(), 2) + sage: rv = chan.transmit(code.random_element()) # long time + sage: dec = code.decoder('K', Q) # long time + sage: enc = dec.connected_encoder() # long time + sage: enc.encode(dec.decode_to_message(rv)) in code # long time + True + + If ``basis`` is given, that defines the associated residue encoding map:: + + sage: basis = tuple((G - sum(D)).basis_differential_space()) + sage: w = basis[0] + sage: cw = vector(w.residue(p) for p in D) + sage: dec2 = code.decoder('K', Q, basis) # long time + sage: enc2 = dec2.connected_encoder() # long time + sage: temp = enc2.unencode(cw); temp # long time + (1, 0, 0) + sage: enc2.encode(temp) == cw # long time + True + sage: w = basis[1] + sage: cw = vector(w.residue(p) for p in D) + sage: temp = enc2.unencode(cw); temp # long time + (0, 1, 0) + sage: enc2.encode(temp) == cw # long time + True + + The default ``basis`` is given by ``code.basis_differentials()``. + """ + _decoder_type = {'always-succeed'} + + def __init__(self, code, Q=None, basis=None, verbose=False): + """ + Initialize. + + TESTS:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.DifferentialAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: TestSuite(dec).run() # long time + """ + if not code.dimension() > 0: + raise ValueError("no decoder for degenerate codes") + + F = code.base_function_field() + K = F.constant_base_field() + + if Q is None: + # try to get a rational place not in the support of the AG code + deg = 1 + for p in F.places(deg): + if p not in code._pls: + Q = p + break + if Q is None: # then take a nonrational place + while Q is None: + deg += 1 + Q = F.get_place(deg) + elif Q in code._pls: + raise ValueError("Q is one of the places defining the code") + + if verbose: + print('auxiliary place: {} of degree {}'.format(Q, Q.degree())) + + super().__init__(code, code.ambient_space(), connected_encoder_name='residue') + + if Q.degree() > 1: + circuit = DifferentialAGCodeDecoder_K_extension(code._pls, code._G, Q, + verbose=verbose) + else: + circuit = DifferentialAGCodeDecoder_K(code._pls, code._G, Q, + verbose=verbose) + + if basis is None: + basis = code._basis_differentials + + C = matrix([circuit.decode(vector(K, [b.residue(p) for p in code._pls])) + for b in basis]) + + self._extension = Q.degree() > 1 + self._K = K + self._basis = tuple(basis) + self._C = C + self._Cinv = C.inverse() + + self._circuit = circuit + self._info = circuit.info + self._Q = Q + + def __hash__(self): + """ + Return the hash of ``self``. + + TESTS:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.DifferentialAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: {dec: 1} # long time + {Unique decoder for [8, 3] differential AG code over GF(4): 1} + """ + return hash((self.code(), self._Q)) + + def __eq__(self, other): + """ + Check whether ``other`` equals ``self``. + + TESTS:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.DifferentialAGCode(D, G) # long time + sage: dec1 = code.decoder('K', Q) # long time + sage: dec2 = code.decoder('K', Q) # long time + sage: dec1 == dec2 # long time + True + """ + if self is other: + return True + + if not isinstance(other, type(self)): + return False + + return (self.code() == other.code() and self._Q == other._Q + and self._basis == other._basis) + + def _repr_(self): + r""" + Return the string representation of ``self``. + + TESTS:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.DifferentialAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: dec # long time + Unique decoder for [8, 3] differential AG code over GF(4) + """ + return "Unique decoder for {}".format(self.code()) + + def _latex_(self): + r""" + Return the latex representation of ``self``. + + TESTS:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.DifferentialAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: latex(dec) # long time + \text{Unique decoder for }[8, 3]\text{ differential AG code over }\Bold{F}_{2^{2}} + """ + return r"\text{{Unique decoder for }}{}".format(self.code()._latex_()) + + def _encode(self, message): + r""" + Return the codeword encoded from ``message``. + + INPUT: + + - ``message`` -- a vector to be encoded to a codeword + + TESTS:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.DifferentialAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: enc = dec.connected_encoder() # long time + sage: msg = enc.message_space().random_element() # long time + sage: dec._decode(dec._encode(msg)) == msg # long time + True + """ + K = self._K + C = self._C + circuit = self._circuit + + if self._extension: + internal_message = circuit._lift(vector(K, message)) * C + return circuit._pull_back(circuit.encode(internal_message)) + else: + return circuit.encode(vector(K, message) * C) + + def _decode(self, vector, **kwargs): + r""" + Return the message decoded from ``vector``. + + INPUT: + + - ``vector`` -- a vector to be decoded to a message + + TESTS:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.DifferentialAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: code = dec.code() # long time + sage: cw = code.random_element() # long time + sage: dec._encode(dec._decode(cw)) == cw # long time + True + """ + Cinv = self._Cinv + circuit = self._circuit + + if self._extension: + internal_message = circuit.decode(circuit._lift(vector), **kwargs) * Cinv + return circuit._pull_back(internal_message) + else: + return circuit.decode(vector, **kwargs) * Cinv + + def connected_encoder(self, *args, **kwargs): + r""" + Return the connected encoder for this decoder. + + INPUT: + + - ``args``, ``kwargs`` -- all additional arguments are forwarded to the + constructor of the connected encoder + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.DifferentialAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: dec.connected_encoder() # long time + Encoder for [8, 3] differential AG code over GF(4) + """ + return self.code().encoder(self._connected_encoder_name, self, *args, **kwargs) + + def decoding_radius(self): + r""" + Return the decoding radius of the decoder. + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.DifferentialAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: dec.decoding_radius() # long time + 2 + """ + return self._info['decoding_radius'] + + def decode_to_message(self, received_vector, **kwargs): + r""" + Return the message decoded from ``received_vector``. + + INPUT: + + - ``received_vector`` -- a vector in the ambient space of the code + + - ``verbose`` -- boolean; if ``True``, verbose information on + the decoding process is printed + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.DifferentialAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: enc = dec.connected_encoder() # long time + sage: code = dec.code() # long time + sage: chan = channels.StaticErrorRateChannel(code.ambient_space(), 2) # long time + sage: rv = chan.transmit(code.random_element()) # long time + sage: msg = dec.decode_to_message(rv) # long time + sage: cw = enc.encode(msg) # long time + sage: (cw - rv).hamming_weight() == 2 # long time + True + """ + return self._decode(received_vector, **kwargs) + + def decode_to_code(self, received_vector, **kwargs): + r""" + Return the codeword decoded from ``received_vector``. + + INPUT: + + - ``received_vector`` -- a vector in the ambient space of the code + + - ``verbose`` -- boolean; if ``True``, verbose information on + the decoding process is printed + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: F = C.function_field() + sage: pls = F.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: code = codes.DifferentialAGCode(D, G) # long time + sage: dec = code.decoder('K', Q) # long time + sage: enc = dec.connected_encoder() # long time + sage: code = dec.code() # long time + sage: chan = channels.StaticErrorRateChannel(code.ambient_space(), 2) # long time + sage: rv = chan.transmit(code.random_element()) # long time + sage: cw = dec.decode_to_code(rv) # long time + sage: (cw - rv).hamming_weight() == 2 # long time + True + """ + return self._encode(self._decode(received_vector, **kwargs)) + + +cdef inline int pos_mod(int a, int b): + """ + Return ``a % b`` such that the result is positive. + + C modulus can be negative as ``a == (a / b) * b + (a % b)``. + """ + cdef int m = a % b + if m < 0: + m += b + return m + + +cdef class Decoder_K(object): + """ + Common base class for the implementation of decoding algorithm K + for AG codes. + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: pls = C.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: from sage.coding.ag_code_decoders import EvaluationAGCodeDecoder_K + sage: circuit = EvaluationAGCodeDecoder_K(D, G, Q) + """ + cdef bint is_differential + cdef int code_length, designed_distance, gamma, s0, tau + cdef list code_basis, message_index, hvecs, eta_vecs + cdef list dR, dRbar + cdef list mul_mat + cdef Matrix coeff_mat + cdef object W, x + + cdef readonly dict info + + def encode(self, message): + """ + Encode ``message`` to a codeword. + + TESTS:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: pls = C.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: from sage.coding.ag_code_decoders import EvaluationAGCodeDecoder_K + sage: circuit = EvaluationAGCodeDecoder_K(D, G, Q) # long time + sage: F. = GF(4) # long time + sage: rv = vector([0, 0, 0, a, 0, a, a + 1, 0]) # long time + sage: msg = circuit.decode(rv) # long time + sage: circuit.decode(circuit.encode(msg)) == msg # long time + True + """ + code_basis = self.code_basis + message_index = self.message_index + return vector(sum([message[i]*code_basis[i] for i in range(len(message_index))])) + + cdef inline int _degree(self, Polynomial f): + """ + Return the degree of polynomial ``f`` + + For zero polynomial, return a negative integer to effect as -infinity. + """ + if f.is_zero(): + return -0b1000000000000000000000000 # -16777216 + else: + return f.degree() + + cdef void _exponents(self, int s, int *sk, int *si): + """ + Compute the exponents of the monomial with weighted degree ``s``. + + This sets the result in ``sk`` and ``si``. + """ + cdef int i, d, gamma + cdef list dRbar + + gamma = self.gamma + dRbar = self.dRbar # dWbar for differential AG code + + i = pos_mod(s, gamma) + d = dRbar[i] + sk[0] = (s - d) // gamma + si[0] = i + + @cython.wraparound(False) + @cython.boundscheck(False) + cdef void _substitution(self, FreeModuleElement vec, w, int k, Py_ssize_t i): + r""" + Substitute ``z`` with ``(z + w*phi_s)``. + + .. WARNING:: + + This modified the ``vec`` input. + """ + cdef Py_ssize_t j, m + cdef list a, d, s + cdef FreeModuleElement temp + cdef Polynomial c + + cdef int gamma = self.gamma + cdef list mul_mat = self.mul_mat + + W = self.W + x = self.x + + # optimizing this part is crucial for the speed of the decoder + a = [vec.get_unsafe(j) for j in range(gamma, 2*gamma)] + c = w * x**k + s = [W.zero()] * gamma + for j in range(gamma): + temp = ( mul_mat[j])[i] + for m in range(gamma): + s[m] += a[j] * temp.get_unsafe(m) + for j in range(gamma): + vec.set_unsafe(j, c * s[j] + vec.get_unsafe(j)) + + def decode(self, received_vector, bint verbose=False, + bint detect_decoding_failure=True, + bint detect_Q_polynomial=True): + """ + Return the message vector that corresponds to the corrected codeword + from the received vector. + + INPUT: + + - ``received_vector`` -- a received vector in the ambient space of the + code + + - ``verbose`` -- boolean; if ``True``, verbose information is printed + + - ``detect_decoding_failure`` -- boolean; if ``True``, early failure + detection is activated + + - ``detect_Q_polynomial`` -- boolean; if ``True``, a Q-polynomial is + detected for fast decoding + + If decoding fails for some reason, ``DecodingError`` is raised. The + message contained in the exception indicates the type of the decoding + failure. + + TESTS:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: pls = C.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: from sage.coding.ag_code_decoders import EvaluationAGCodeDecoder_K # long time + sage: circuit = EvaluationAGCodeDecoder_K(D, G, Q) # long time + sage: rv = vector(F, [1, a, 1, a + 1, a + 1, a + 1, 1, a + 1]) # long time + sage: circuit.decode(rv) # long time + (1, 0, a + 1, a + 1, a) + """ + cdef int s, sk, si, i, j, c, cbar, posQs, posQi + cdef int k, ip, count, delta, dlt, wlt, pos, wd_hvec + cdef list mat, nu, mu, message + cdef list i_k, i_prime, i_value, i_count, voting_value, voting_count + cdef list std + cdef FreeModuleElement row, hvec, row_i, row_ip, nrow_i, nrow_ip + cdef Matrix coeff_mat + cdef Polynomial t + cdef bint found_Q + + cdef int code_length = self.code_length + cdef int designed_distance = self.designed_distance + + cdef int gamma = self.gamma + cdef list dR = self.dR + cdef list dRbar = self.dRbar # dWbar for differential AG code + + cdef list hvecs = self.hvecs + cdef list eta_vecs = self.eta_vecs + cdef list mul_mat = self.mul_mat + coeff_mat = self.coeff_mat + + cdef list message_index = self.message_index + cdef list code_basis = self.code_basis + + cdef int s0 = self.s0 + cdef int tau = self.tau + + W = self.W + x = self.x + + K = W.base_ring() + + if verbose: + width = 7 * (K.degree() + 2) + # auxiliary function for verbose printing + def vprint_g(g, s): + if verbose > 1: + print(g) + else: + print('[', end='') + for i in reversed(range(gamma)): + t = g[gamma + i] + wd = gamma * self._degree(t) + dR[i] + s + s1 = '{} y{}z'.format(0 if t == 0 else t.lt(), i) + if t != 0: + s2 = '{:<4}'.format('({})'.format(wd)) + else: + s2 = '(-) ' + print(('{:>' + str(width) + '} ').format(s1 + s2), end='') + for i in reversed(range(gamma)): + t = g[i] + wd = gamma * self._degree(t) + dRbar[i] + s1 = '{} w{}' if self.is_differential else '{} Y{}' + s1 = s1.format(0 if t == 0 else t.lt(), i) + if t != 0: + s2 = '{:<4}'.format('({})'.format(wd)) + else: + s2 = '(-) ' + print(('{:>' + str(width) + '} ').format(s1 + s2), end='') + print(']') + + message = [] + + # construct the initial generators of the interpolation module + hvec = sum(received_vector[i] * hvecs[i] for i in range(code_length)) + + # weighted degree of hvec + wd_hvec = max(gamma * self._degree(hvec[i]) + dRbar[i] for i in range(gamma)) + + if wd_hvec <= 0: + if verbose: + print("no error") + + for s in message_index: + self._exponents(s, &sk, &si) + message.append(hvec[si][sk]) + else: + mat = [] + for i in range(gamma): + row = vector(eta_vecs[i].list(copy=False) + [W.zero() for j in range(gamma)]) + mat.append(row) + for i in range(gamma): + std = [W.zero() for j in range(gamma)] + std[i] = W.one() + row = vector(sum(-hvec[j] * mul_mat[i][j] for j in range(gamma)).list(copy=False) + std) + mat.append(row) + + nu = [] + for i in range(gamma): + nu.append(( mat[i]).get_unsafe(i).lc()) + + found_Q = False + s = wd_hvec + + while s >= s0: + if verbose: + print("# s = {}".format(s)) + print("generators (leading terms):") + for i in reversed(range(gamma)): + g = mat[gamma + i] + print("F{} ".format(i), end='') + vprint_g(g, s) + for i in reversed(range(gamma)): + g = mat[i] + print("G{} ".format(i), end='') + vprint_g(g, s) + + self._exponents(s, &sk, &si) + delta = 0 + mu = [] + i_k = [] + i_prime = [] + i_value = [] + i_count = [] + voting_value = [] + voting_count = [] + for i in range(gamma): + # detect decoding failure + dlt = self._degree(( mat[gamma + i]).get_unsafe(gamma + i)) + delta += dlt + if detect_decoding_failure and delta > tau: + # more errors than tau; declare failure + if verbose: + print("detected decoding failure") + raise DecodingError("more errors than decoding radius") + + # detect Q-polynomial + wlt = gamma * dlt + dR[i] + if detect_Q_polynomial and wlt + s + tau < designed_distance: + found_Q = True + posQs = s + posQi = i + break + + self._exponents(wlt + s, &k, &ip) + count = self._degree(( mat[ip]).get_unsafe(ip)) - k + i_k.append(k) + i_prime.append(ip) + i_count.append(count) + + if found_Q: + break + + if s > 0 or sk < 0: # not s in message_index + for i in range(gamma): + k = i_k[i] + ip = i_prime[i] + + if k < 0: + value = K.zero() + else: + value = -( mat[gamma + i]).get_unsafe(ip)[k] + + mu.append(1) + i_value.append(value) + winner = 0 + else: + for i in range(gamma): + k = i_k[i] + ip = i_prime[i] + + mui = ( mat[gamma + i]).get_unsafe(gamma + i).lc() * coeff_mat[i, si] + value = -( mat[gamma + i]).get_unsafe(ip)[k] / mui + + mu.append(mui) + i_value.append(value) + + cbar = max(i_count[i], 0) + try: + pos = voting_value.index(value) + voting_count[pos] += cbar + except ValueError: + voting_value.append(value) + voting_count.append(cbar) + + # voting + c = -1 + for i in range(len(voting_value)): + if c < voting_count[i]: + c = voting_count[i] + winner = voting_value[i] + + if verbose: + print("i_prime:", i_prime) + print("i_count:", i_count) + print("i_value:", i_value) + + if s <= 0 and sk >= 0: # s in message_index + print("voting:", list(zip(voting_value, voting_count))) + + for i in range(gamma): + row_i = mat[gamma + i] + row_ip = mat[i_prime[i]] + if winner != 0: + self._substitution(row_i, winner, sk, si) + self._substitution(row_ip, winner, sk, si) + if i_value[i] == winner: + nrow_ip = row_ip + nrow_i = row_i + else: + nnu = mu[i] * (winner - i_value[i]) + if i_count[i] > 0: + nrow_ip = row_i + nrow_i = x**i_count[i] * row_i - nnu / nu[i_prime[i]] * row_ip + nu[i_prime[i]] = nnu + else: + nrow_ip = row_ip + nrow_i = row_i - nnu / nu[i_prime[i]] * x**(-i_count[i]) * row_ip + mat[i_prime[i]] = nrow_ip + mat[gamma + i] = nrow_i + + if s <= 0 and sk >= 0: # s in message_index + if verbose: + print("message symbol:", winner) + message.append(winner) + + s -= 1 + + if found_Q: + s = posQs + i = posQi + if verbose: + print("found a Q-polynomial at s = {}, F{}".format(s, i)) + dlt = gamma * self._degree(( mat[gamma + i]).get_unsafe(gamma + i)) + dR[i] + + while s >= s0: + if verbose: + print("# s = {}".format(s)) + print("F{} ".format(i), end='') + vprint_g(mat[gamma + i], s) + self._exponents(s, &sk, &si) + if s <= 0 and sk >= 0: # s in message_index + self._exponents(dlt + s, &k, &ip) + mui = ( mat[gamma + i]).get_unsafe(gamma + i).lc() * coeff_mat[i, si] + value = -( mat[gamma + i]).get_unsafe(ip)[k] / mui + if not value.is_zero(): + self._substitution( mat[gamma+i], value, sk, si) + if verbose: + print("message symbol:", value) + message.append(value) + s -= 1 + + for j in range(gamma): + if not ( mat[gamma + i]).get_unsafe(j).is_zero(): + if verbose: + print("detected decoding failure at division") + raise DecodingError("decoding failed") + + message.reverse() + + return vector(K, message) + + @cython.wraparound(False) + @cython.boundscheck(False) + cdef inline int _next(self, int s): + """ + Return the next value after ``s`` in dRbar(dWbar). + """ + cdef int i, d, gamma + cdef list dRbar = self.dRbar + gamma = self.gamma + i = pos_mod(s, gamma) + while True: + s += 1 + i = (i + 1) % gamma # equals s % gamma + d = dRbar[i] + if s >= d: + return s + + @cython.wraparound(False) + @cython.boundscheck(False) + cdef inline void _get_eta_basis(self, list basis, list vecs, int s0, mon_func): + """ + Compute a basis of J and h-functions via FGLM algorithm. + + This sets ``basis`` with the basis of J and ``vecs`` with the h-functions. + """ + cdef int s, sk, si, i, j, num + cdef Matrix mat, matinv + cdef list gen, delta, h + cdef tuple t + + cdef int gamma = self.gamma + cdef int code_length = self.code_length + x = self.x + W = self.W + s = s0 + self._exponents(s, &sk, &si) + delta = [(sk, si)] + mat = matrix(mon_func(sk, si)) + num = 0 + while num < gamma: + s = self._next(s) + self._exponents(s, &sk, &si) + if basis[si] is None: + v = mon_func(sk, si) + try: + sol = mat.solve_left(v) + gen = [W.zero() for i in range(gamma)] + for i in range(len(delta)): + t = delta[i] + gen[ t[1]] += -sol[i] * x**( t[0]) + gen[si] += x**sk + basis[si] = vector(gen) + num += 1 + except ValueError: + mat = mat.stack(matrix(v)) + delta.append((sk, si)) + + matinv = mat.inverse() + for i in range(code_length): + h = [W.zero() for k in range(gamma)] + for j in range(code_length): + t = delta[j] + h[ t[1]] += matinv[i,j] * x**( t[0]) + vecs[i] = vector(h) + + +@cython.auto_pickle(True) +cdef class EvaluationAGCodeDecoder_K(Decoder_K): + """ + This class implements the decoding algorithm K for evaluation AG codes. + + INPUT: + + - ``pls`` -- a list of places of a function field + + - ``G`` -- a divisor of the function field + + - ``Q`` -- a rational place not in ``pls`` + + - ``verbose`` -- if ``True``, verbose information is printed. + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: pls = C.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: from sage.coding.ag_code_decoders import EvaluationAGCodeDecoder_K + sage: circuit = EvaluationAGCodeDecoder_K(D, G, Q) + sage: rv = vector([a, 0, 0, a, 1, 1, a + 1, 0]) + sage: cw = circuit.encode(circuit.decode(rv)) + sage: rv - cw + (a + 1, 0, 0, 0, 0, 0, 0, 0) + sage: circuit.info['designed_distance'] + 3 + sage: circuit.info['decoding_radius'] + 1 + """ + def __init__(self, pls, G, Q, verbose=False): + """ + Initialize. + + TESTS:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: pls = C.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: from sage.coding.ag_code_decoders import EvaluationAGCodeDecoder_K + sage: circuit = EvaluationAGCodeDecoder_K(D, G, Q) # long time + sage: TestSuite(circuit).run(skip='_test_pickling') # long time + """ + cdef int i, j, s, s0, sk, si, n, r, d, num + cdef int code_length, genus, gamma, dLO, tau + cdef list gaps, dR, yR, dRbar, yRbar, evyRbar, nus, mul_mat + cdef list message_index, code_basis + cdef FreeModuleElement evxR + cdef set temp + + D = sum(pls) + F = D.parent().function_field() + K = F.constant_base_field() + W = PolynomialRing(K, name='x') # working polynomial ring + x = W.gen() + + # length of the code + code_length = len(pls) + + # compute gamma + gamma = 1 + while True: + if Q.divisor(gamma).dimension() > 1: + break + gamma += 1 + + # compute xR + for f in Q.divisor(gamma).basis_function_space(): + if f.valuation(Q) == -gamma: + xR = f + break + + # Apéry R + dR = [0 for i in range(gamma)] + yR = [None for i in range(gamma)] + s = 0 + n = 0 + while n < gamma: + g = 0 + for b in Q.divisor(s).basis_function_space(): + if b.valuation(Q) == -s: + g = b + break + r = pos_mod(s, gamma) + if g != 0 and not yR[r]: + dR[r] = s + yR[r] = g + n += 1 + s += 1 + + # gaps of L + temp = set() + for d in dR: + temp.update([d - gamma*(i+1) for i in range(d // gamma)]) + gaps = list(temp) + del temp + + # genus of L + genus = len(gaps) + + # Apéry Rbar + dRbar = [0 for i in range(gamma)] + yRbar = [None for i in range(gamma)] + s = -G.degree() + n = 0 + while n < gamma: + B = (Q.divisor(s) + G).basis_function_space() + g = 0 + for b in B: + if b.valuation(Q) + G.multiplicity(Q) == -s: + g = b + break + r = pos_mod(s, gamma) + if g != 0 and not yRbar[r]: + dRbar[r] = s + yRbar[r] = g + n += 1 + s += 1 + + if verbose: + print("gamma:", gamma) + print("x = {}".format(xR)) + print("Apéry system of R") + for i in range(gamma): + print(" {}: {}, y{} = {}".format(i, dR[i], i, yR[i])) + print("Apéry system of Rbar") + for i in range(gamma): + print(" {}: {}, Y{} = {}".format(i, dRbar[i], i, yRbar[i])) + + # ev map for the monomial whose weighted degree is s + evxR = vector(K, [xR.evaluate(p) for p in pls]) + evyRbar = [vector(K, [yRbar[i].evaluate(p) for p in pls]) for i in range(gamma)] + + self.is_differential = False + self.code_length = code_length + self.designed_distance = code_length - G.degree() + self.gamma = gamma + self.dR = dR + self.dRbar = dRbar + self.W = W + self.x = x + + def ev_mon(int sk, int si): + cdef int i + return vector([evxR.get_unsafe(i)**sk * evyRbar[si][i] for i in range(code_length)]) + + # minimum of nongaps of Rbar + s0 = self._next(-G.degree() - 1) + + # basis of the code ev(L(G)) + message_index = [] + code_basis = [] + s = s0 + self._exponents(s, &sk, &si) + v = ev_mon(sk, si) + V = v.parent() + while s <= 0: + if not V.are_linearly_dependent(code_basis + [v]): + message_index.append(s) + code_basis.append(v) + s = self._next(s) + self._exponents(s, &sk, &si) + v = ev_mon(sk, si) + + # compute a basis of J and h-functions via FGLM algorithm + eta_vecs = [None for i in range(gamma)] + hvecs = [None for i in range(code_length)] + + self._get_eta_basis(eta_vecs, hvecs, s0, ev_mon) + + if verbose: + print("message indices:", message_index) + print("eta basis:", eta_vecs) + print("Lagrange polynomials") + for i in range(code_length): + print("h{} = {}".format(i, hvecs[i])) + + # Lee-O'Sullivan bound + def nu(int s): + cdef int i, sk, si, m = 0 + for i in range(gamma): + self._exponents(s + dR[i], &sk, &si) + m += max(0, self._degree(eta_vecs[si][si]) - sk) + return m + + nus = [nu(s) for s in message_index] + dLO = min(nus) + tau = (dLO - 1) // 2 + + if verbose: + print("gaps:", gaps) + print("genus:", genus) + print("nus:", nus) + print("dLO:", dLO) + print("tau:", tau) + + # the vector form corresponding to f in Rbar + def vec_form(f): + r = f + cdef list l = [W.zero() for i in range(gamma)] + while r != 0: + s = -r.valuation(Q) - G.multiplicity(Q) + self._exponents(s, &sk, &si) + mon = xR**sk * yRbar[si] + c = (r / mon).evaluate(Q) + l[si] += c * x**sk + r -= c*mon + return vector(l) + + # the matrix of the leading coefficient of y_i*ybar_j and the product + coeff_mat = matrix.zero(K, gamma, gamma) + mul_mat = [[None for j in range(gamma)] for i in range(gamma)] + for i in range(gamma): + for j in range(gamma): + f = yR[i] * yRbar[j] + v = vec_form(f) + self._exponents(( dR[i]) + ( dRbar[j]), &sk, &si) + coeff_mat[i,j] = v[si][sk] + ( mul_mat[i])[j] = v + + if verbose: + print("multiplication table") + for i in range(gamma): + for j in range(gamma): + print("y{} * Y{}:".format(i, j), mul_mat[i][j]) + print("coefficient array") + print(coeff_mat) + + self.code_basis = code_basis + self.message_index = message_index + self.hvecs = hvecs + self.eta_vecs = eta_vecs + self.mul_mat = mul_mat + self.coeff_mat = coeff_mat + self.s0 = s0 + self.tau = tau + + cdef dict info = {} + info['designed_distance'] = dLO + info['decoding_radius'] = tau + + self.info = info + + +@cython.auto_pickle(True) +cdef class DifferentialAGCodeDecoder_K(Decoder_K): + """ + This class implements the decoding algorithm K for differential AG codes. + + INPUT: + + - ``pls`` -- a list of places of a function field + + - ``G`` -- a divisor of the function field + + - ``Q`` -- a rational place not in ``pls`` + + - ``verbose`` -- if ``True``, verbose information is printed + + EXAMPLES:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2); + sage: C = Curve(y^2 + y - x^3) + sage: pls = C.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: from sage.coding.ag_code_decoders import DifferentialAGCodeDecoder_K + sage: circuit = DifferentialAGCodeDecoder_K(D, G, Q) # long time + sage: rv = vector([1, a, 1, a, 1, a, a, a + 1]) + sage: cw = circuit.encode(circuit.decode(rv)) # long time + sage: rv - cw # long time + (0, 0, 0, a + 1, 1, 0, 0, 0) + sage: circuit.info['designed_distance'] # long time + 5 + sage: circuit.info['decoding_radius'] # long time + 2 + """ + def __init__(self, pls, G, Q, verbose=False): + """ + Initialize. + + TESTS:: + + sage: F. = GF(4) + sage: P. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: pls = C.places() + sage: p = C([0,0]) + sage: Q, = p.places() + sage: D = [pl for pl in pls if pl != Q] + sage: G = 5*Q + sage: from sage.coding.ag_code_decoders import DifferentialAGCodeDecoder_K + sage: circuit = DifferentialAGCodeDecoder_K(D, G, Q) # long time + sage: TestSuite(circuit).run(skip='_test_pickling') # long time + """ + cdef int i, j, s, s0, sk, si, n, r, d, num + cdef int code_length, genus, gamma, dLO, tau + cdef list gaps, dR, yR, dWbar, wWbar, reswWbar, nus, mul_mat + cdef list message_index, code_basis + cdef FreeModuleElement evxR + cdef set temp + + D = sum(pls) + F = D.parent().function_field() + K = F.constant_base_field() + W = PolynomialRing(K, name='x') # working polynomial ring + x = W.gen() + + # length of the code + code_length = len(pls) + + # compute gamma + gamma = 1 + while True: + if Q.divisor(gamma).dimension() > 1: + break + gamma += 1 + + # compute xR + for xR in Q.divisor(gamma).basis_function_space(): + if xR.valuation(Q) == -gamma: + break + + # Apéry R + dR = [0 for i in range(gamma)] + yR = [None for i in range(gamma)] + s = 0 + n = 0 + while n < gamma: + g = 0 + for b in Q.divisor(s).basis_function_space(): + if b.valuation(Q) == -s: + g = b + break + r = pos_mod(s, gamma) + if g != 0 and not yR[r]: + dR[r] = s + yR[r] = g + n += 1 + s += 1 + + # gaps of L + temp = set() + for d in dR: + temp.update([d - gamma*(i + 1) for i in range(d // gamma)]) + gaps = list(temp) + del temp + + # genus of L + genus = len(gaps) + + # Apéry Wbar + dWbar = [0 for i in range(gamma)] + wWbar = [None for i in range(gamma)] + s = -code_length + G.degree() - 2 * genus + 2 + n = 0 + while n < gamma: + B = (-D + G - Q.divisor(s)).basis_differential_space() + g = 0 + for b in B: + if b.valuation(Q) == G.multiplicity(Q) - s: + g = b + break + r = pos_mod(s, gamma) + if g != 0 and not wWbar[r]: + dWbar[r] = s + wWbar[r] = g + n += 1 + s += 1 + + if verbose: + print("gamma:", gamma) + print("x = {}".format(xR)) + print("Apéry system of R") + for i in range(gamma): + print(" {}: {}, y{} = {}".format(i, dR[i], i, yR[i])) + print("Apéry system of Wbar") + for i in range(gamma): + print(" {}: {}, w{} = {}".format(i, dWbar[i], i, wWbar[i])) + + # res map for the monomial whose weighted degree is s + evxR = vector(K, [xR.evaluate(p) for p in pls]) + reswWbar = [vector(K, [wWbar[i].residue(p) for p in pls]) for i in range(gamma)] + + self.is_differential = True + self.code_length = code_length + self.designed_distance = G.degree() - 2 * genus + 2 + self.gamma = gamma + self.dR = dR + self.dRbar = dWbar + self.W = W + self.x = x + + def res_mon(int sk, int si): + cdef int i + return vector([evxR.get_unsafe(i)**sk * reswWbar[si][i] for i in range(code_length)]) + + # minimum of nongaps of Wbar + s0 = self._next(-code_length + G.degree() - 2*genus + 1) + + # basis of the code res(Omega(G)) + message_index = [] + code_basis = [] + s = s0 + self._exponents(s, &sk, &si) + v = res_mon(sk, si) + V = v.parent() + while s <= 0: + if not V.are_linearly_dependent(code_basis + [v]): + message_index.append(s) + code_basis.append(v) + s = self._next(s) + self._exponents(s, &sk, &si) + v = res_mon(sk, si) + + # compute a basis of J and h-functions via FGLM algorithm + eta_vecs = [None for i in range(gamma)] + hvecs = [None for i in range(code_length)] + + self._get_eta_basis(eta_vecs, hvecs, s0, res_mon) + + if verbose: + print("message indices:", message_index) + print("eta basis:", eta_vecs) + print("Lagrange polynomials") + for i in range(code_length): + print("h{} = {}".format(i, hvecs[i])) + + # Lee-O'Sullivan bound + def nu(int s): + cdef int i, sk, si, m + m = 0 + for i in range(gamma): + self._exponents(s + dR[i], &sk, &si) + m += max(0, self._degree(eta_vecs[si][si]) - sk) + return m + + nus = [nu(s) for s in message_index] + dLO = min(nus) + tau = (dLO - 1) // 2 + + if verbose: + print("gaps:", gaps) + print("genus:", genus) + print("nus:", nus) + print("dLO:", dLO) + print("tau:", tau) + + # the vector form corresponding to f in Wbar + def vec_form(f): + r = f + cdef list l = [W.zero() for i in range(gamma)] + while r != 0: + s = -r.valuation(Q) + G.valuation(Q) + self._exponents(s, &sk, &si) + mon = xR**sk * wWbar[si] + c = (r / mon).evaluate(Q) + l[si] += c * x**sk + r -= c*mon + return vector(l) + + # the matrix of the leading coefficient of y_i*w_j and the product + coeff_mat = matrix.zero(K, gamma, gamma) + mul_mat = [[None for j in range(gamma)] for i in range(gamma)] + for i in range(gamma): + for j in range(gamma): + f = yR[i] * wWbar[j] + v = vec_form(f) + self._exponents(( dR[i]) + ( dWbar[j]), &sk, &si) + coeff_mat[i,j] = v[si][sk] + ( mul_mat[i])[j] = v + + if verbose: + print("multiplication table") + for i in range(gamma): + for j in range(gamma): + print("y{} * w{}:".format(i, j), mul_mat[i][j]) + print("coefficient array") + print(coeff_mat) + + self.code_basis = code_basis + self.message_index = message_index + self.hvecs = hvecs + self.eta_vecs = eta_vecs + self.mul_mat = mul_mat + self.coeff_mat = coeff_mat + self.s0 = s0 + self.tau = tau + + cdef dict info = {} + info['designed_distance'] = dLO + info['decoding_radius'] = tau + + self.info = info + + +cdef class Decoder_K_extension(object): + """ + Common base class for decoding algorithm K for AG codes via constant field extension. + + INPUT: + + - ``pls`` -- a list of places of a function field + + - ``G`` -- a divisor of the function field + + - ``Q`` -- a non-rational place + + - ``verbose`` -- if ``True``, verbose information is printed + + EXAMPLES:: + + sage: A. = AffineSpace(GF(4), 2) + sage: C = Curve(y^2 + y - x^3) + sage: pls = C.places() + sage: F = C.function_field() + sage: G = 1*F.get_place(4) + sage: code = codes.EvaluationAGCode(pls, G) + sage: dec = code.decoder('K'); dec # long time + Unique decoder for [9, 4] evaluation AG code over GF(4) + + :: + + sage: P. = ProjectiveSpace(GF(4), 1) + sage: C = Curve(P) + sage: pls = C.places() + sage: len(pls) + 5 + sage: F = C.function_field() + sage: G = F.get_place(2).divisor() + sage: code = codes.EvaluationAGCode(pls, G) + sage: code.decoder('K') + Unique decoder for [5, 3] evaluation AG code over GF(4) + """ + cdef object _embedK, _K + cdef Decoder_K decoder_ext + + cdef readonly dict info + + def __init__(self, pls, G, Q, decoder_cls, verbose=False): + """ + Initialize. + + TESTS:: + + sage: A. = AffineSpace(GF(4), 2) + sage: C = Curve(y^2 + y - x^3) + sage: pls = C.places() + sage: F = C.function_field() + sage: G = 1*F.get_place(4) + sage: code = codes.EvaluationAGCode(pls, G) # long time + sage: dec = code.decoder('K') # long time + sage: TestSuite(dec).run(skip='_test_pickling') # long time + """ + F = G.parent().function_field() + K = F.constant_base_field() + F_base = F.base_field() + + K_ext = K.extension(Q.degree()) + + if verbose: + print('extended constant field:', K_ext) + + F_ext_base = FunctionField(K_ext, F_base.variable_name()) + + if F.degree() > 1: + # construct constant field extension F_ext of F + def_poly = F.polynomial().base_extend(F_ext_base) + F_ext = F_ext_base.extension(def_poly, names=def_poly.variable_name()) + else: # rational function field + F_ext = F_ext_base + + O_ext = F_ext.maximal_order() + Oinf_ext = F_ext.maximal_order_infinite() + + # embedding of F into F_ext + embedK = K_ext.coerce_map_from(K) + embedF_base = F_base.hom(F_ext_base.gen(), embedK) + + if F.degree() > 1: + embedF = F.hom(F_ext.gen(), embedF_base) + else: + embedF = embedF_base + + self._embedK = embedK + self._K = K + + Div_ext = F_ext.divisor_group() + + def conorm_prime_divisor(pl): + """ + Conorm map for prime divisors. + """ + if pl.is_infinite_place(): + ideal = Oinf_ext.ideal([embedF(g) for g in pl.prime_ideal().gens()]) + else: + ideal = O_ext.ideal([embedF(g) for g in pl.prime_ideal().gens()]) + return ideal.divisor() + + def conorm(d): + """ + Conorm map from F to F_ext. + """ + c = Div_ext.zero() + for pl, mul in d.list(): + c += mul * conorm_prime_divisor(pl) + return c + + def lift_place(pl): + """ + Get a place of F_ext lying above pl. + """ + return conorm_prime_divisor(pl).support()[0] + + pls_ext = [lift_place(pl) for pl in pls] + G_ext = conorm(G) + Q_ext = lift_place(Q) + + self.decoder_ext = decoder_cls(pls_ext, G_ext, Q_ext, verbose=verbose) + self.info = self.decoder_ext.info + + def _lift(self, v): + """ + Lift a vector over the base field to a vector over the extension field. + + TESTS:: + + sage: A. = AffineSpace(GF(4), 2) + sage: C = Curve(y^2 + y - x^3) + sage: pls = C.places() + sage: F = C.function_field() + sage: G = 1*F.get_place(4) + sage: code = codes.EvaluationAGCode(pls, G) # long time + sage: decoder = code.decoder('K') # long time + sage: lift = decoder._circuit._lift # long time + sage: pull_back = decoder._circuit._pull_back # long time + sage: v = code.random_element() # long time + sage: pull_back(lift(v)) == v # long time + True + """ + embedK = self._embedK + return vector(embedK(e) for e in v) + + def _pull_back(self, v): + """ + Pull back a vector over the extension field to a vector over the base + field. + + TESTS:: + + sage: A. = AffineSpace(GF(4), 2) + sage: C = Curve(y^2 + y - x^3) + sage: pls = C.places() + sage: F = C.function_field() + sage: G = 1*F.get_place(4) + sage: code = codes.EvaluationAGCode(pls, G) # long time + sage: decoder = code.decoder('K') # long time + sage: code = decoder.code() # long time + sage: lift = decoder._circuit._lift # long time + sage: pull_back = decoder._circuit._pull_back # long time + sage: v = code.random_element() # long time + sage: pull_back(lift(v)) == v # long time + True + """ + K = self._K + return vector(K(e) for e in v) + + def encode(self, message, **kwargs): + """ + Encode ``message`` to a codeword. + + TESTS:: + + sage: A. = AffineSpace(GF(4), 2) + sage: C = Curve(y^2 + y - x^3) + sage: pls = C.places() + sage: F = C.function_field() + sage: G = 1*F.get_place(4) + sage: code = codes.EvaluationAGCode(pls, G) # long time + sage: decoder = code.decoder('K') # long time + sage: cw = code.random_element() # long time + sage: circuit = decoder._circuit # long time + sage: circuit.encode(circuit.decode(cw)) == cw # long time + True + """ + return self.decoder_ext.encode(message, **kwargs) + + def decode(self, received_vector, **kwargs): + """ + Decode the received vector to a message. + + INPUT: + + - ``received_vector`` -- a vector in the ambient space of the code + + TESTS:: + + sage: A. = AffineSpace(GF(4), 2) + sage: C = Curve(y^2 + y - x^3) + sage: pls = C.places() + sage: F = C.function_field() + sage: G = 1*F.get_place(4) + sage: code = codes.EvaluationAGCode(pls, G) # long time + sage: decoder = code.decoder('K') # long time + sage: cw = code.random_element() # long time + sage: circuit = decoder._circuit # long time + sage: circuit.encode(circuit.decode(cw)) == cw # long time + True + """ + return self.decoder_ext.decode(received_vector, **kwargs) + + +@cython.auto_pickle(True) +cdef class EvaluationAGCodeDecoder_K_extension(Decoder_K_extension): + """ + This class implements the decoding algorithm K for evaluation AG codes via + constant field extension. + + INPUT: + + - ``pls`` -- a list of places of a function field + + - ``G`` -- a divisor of the function field + + - ``Q`` -- a non-rational place + + - ``verbose`` -- if ``True``, verbose information is printed + + EXAMPLES:: + + sage: F. = GF(4) + sage: A. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: pls = C.places() + sage: F = C.function_field() + sage: G = 1*F.get_place(4) + sage: code = codes.EvaluationAGCode(pls, G) + sage: Q = F.get_place(3) + sage: from sage.coding.ag_code_decoders import EvaluationAGCodeDecoder_K_extension + sage: circuit = EvaluationAGCodeDecoder_K_extension(pls, G, Q) + sage: cw = code.random_element() + sage: rv = cw + vector([0,1,1,0,0,0,0,0,0]) + sage: circuit.encode(circuit.decode(circuit._lift(rv))) == circuit._lift(cw) + True + """ + def __init__(self, pls, G, Q, verbose=False): + """ + Initialize. + + TESTS:: + + sage: F. = GF(4) + sage: A. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: pls = C.places() + sage: F = C.function_field() + sage: G = 1*F.get_place(4) + sage: code = codes.EvaluationAGCode(pls, G) # long time + sage: Q = F.get_place(3) # long time + sage: from sage.coding.ag_code_decoders import EvaluationAGCodeDecoder_K_extension # long time + sage: circuit = EvaluationAGCodeDecoder_K_extension(pls, G, Q) # long time + sage: TestSuite(circuit).run(skip='_test_pickling') # long time + """ + super().__init__(pls, G, Q, EvaluationAGCodeDecoder_K, verbose=verbose) + + +@cython.auto_pickle(True) +cdef class DifferentialAGCodeDecoder_K_extension(Decoder_K_extension): + """ + This class implements the decoding algorithm K for differential AG codes via + constant field extension. + + INPUT: + + - ``pls`` -- a list of places of a function field + + - ``G`` -- a divisor of the function field + + - ``Q`` -- a non-rational place + + - ``verbose`` -- if ``True``, verbose information is printed + + EXAMPLES:: + + sage: F. = GF(4) + sage: A. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: pls = C.places() + sage: F = C.function_field() + sage: G = 1*F.get_place(4) + sage: code = codes.DifferentialAGCode(pls, G) + sage: Q = F.get_place(3) + sage: from sage.coding.ag_code_decoders import DifferentialAGCodeDecoder_K_extension + sage: circuit = DifferentialAGCodeDecoder_K_extension(pls, G, Q) # long time + sage: cw = code.random_element() + sage: rv = cw + vector([0,0,a,0,0,0,0,0,0]) + sage: circuit.encode(circuit.decode(circuit._lift(rv))) == circuit._lift(cw) # long time + True + """ + def __init__(self, pls, G, Q, verbose=False): + """ + Initialize. + + TESTS:: + + sage: F. = GF(4) + sage: A. = AffineSpace(F, 2) + sage: C = Curve(y^2 + y - x^3) + sage: pls = C.places() + sage: F = C.function_field() + sage: G = 1*F.get_place(4) + sage: code = codes.DifferentialAGCode(pls, G) # long time + sage: Q = F.get_place(3) # long time + sage: from sage.coding.ag_code_decoders import DifferentialAGCodeDecoder_K_extension # long time + sage: circuit = DifferentialAGCodeDecoder_K_extension(pls, G, Q) # long time + sage: TestSuite(circuit).run(skip='_test_pickling') # long time + """ + super().__init__(pls, G, Q, DifferentialAGCodeDecoder_K, verbose=verbose) + diff --git a/src/sage/coding/codes_catalog.py b/src/sage/coding/codes_catalog.py index c894d3a0693..9535d364ce7 100644 --- a/src/sage/coding/codes_catalog.py +++ b/src/sage/coding/codes_catalog.py @@ -102,6 +102,7 @@ _lazy_import('sage.coding.kasami_codes', 'KasamiCode') _lazy_import('sage.coding.linear_rank_metric', 'LinearRankMetricCode') _lazy_import('sage.coding.gabidulin_code', 'GabidulinCode') +_lazy_import('sage.coding.ag_code', ['EvaluationAGCode', 'DifferentialAGCode', 'CartierCode']) _lazy_import('sage.coding.guava', ['QuasiQuadraticResidueCode', 'RandomLinearCodeGuava']) diff --git a/src/sage/coding/decoders_catalog.py b/src/sage/coding/decoders_catalog.py index bc8e5b5a2cd..a95912e51b6 100644 --- a/src/sage/coding/decoders_catalog.py +++ b/src/sage/coding/decoders_catalog.py @@ -7,7 +7,7 @@ method directly on a code allows you to construct all compatible decoders for that code (:meth:`sage.coding.linear_code.AbstractLinearCode.decoder`). -**Extended code decoders** +**Extended code decoder** - :class:`extended_code.ExtendedCodeOriginalCodeDecoder ` @@ -36,10 +36,15 @@ - :class:`bch_code.BCHUnderlyingGRSDecoder ` -**Punctured codes decoders** +**Punctured code decoder** - :class:`punctured_code.PuncturedCodeOriginalCodeDecoder ` +**Evaluation and differential AG code decoders** + +- :class:`ag_code_decoders.EvaluationAGCodeUniqueDecoder ` +- :class:`ag_code_decoders.DifferentialAGCodeUniqueDecoder ` + .. NOTE:: To import these names into the global namespace, use: @@ -62,14 +67,14 @@ lazy_import('sage.coding.cyclic_code', 'CyclicCodeSurroundingBCHDecoder') lazy_import('sage.coding.extended_code', 'ExtendedCodeOriginalCodeDecoder') lazy_import('sage.coding.grs_code', ['GRSBerlekampWelchDecoder', - 'GRSErrorErasureDecoder', - 'GRSGaoDecoder', - 'GRSKeyEquationSyndromeDecoder']) + 'GRSErrorErasureDecoder', + 'GRSGaoDecoder', + 'GRSKeyEquationSyndromeDecoder']) from .guruswami_sudan.gs_decoder import GRSGuruswamiSudanDecoder lazy_import('sage.coding.linear_code', ['LinearCodeNearestNeighborDecoder', - 'LinearCodeSyndromeDecoder', - 'LinearCodeInformationSetDecoder']) + 'LinearCodeSyndromeDecoder', + 'LinearCodeInformationSetDecoder']) lazy_import('sage.coding.punctured_code', 'PuncturedCodeOriginalCodeDecoder') lazy_import('sage.coding.subfield_subcode', 'SubfieldSubcodeOriginalCodeDecoder') @@ -77,4 +82,7 @@ lazy_import('sage.coding.linear_rank_metric', 'LinearRankMetricCodeNearestNeighborDecoder') lazy_import('sage.coding.gabidulin_code', 'GabidulinGaoDecoder') +lazy_import('sage.coding.ag_code_decoders', ['EvaluationAGCodeUniqueDecoder', + 'DifferentialAGCodeUniqueDecoder']) + del lazy_import diff --git a/src/sage/coding/encoders_catalog.py b/src/sage/coding/encoders_catalog.py index 6006fcdb453..9435f72ee33 100644 --- a/src/sage/coding/encoders_catalog.py +++ b/src/sage/coding/encoders_catalog.py @@ -8,7 +8,7 @@ - :class:`cyclic_code.CyclicCodePolynomialEncoder ` - :class:`cyclic_code.CyclicCodeVectorEncoder ` -**Extended code encoders** +**Extended code encoder** - :class:`extended_code.ExtendedCodeExtendedMatrixEncoder ` @@ -22,7 +22,7 @@ - :class:`grs_code.GRSEvaluationVectorEncoder ` - :class:`grs_code.GRSEvaluationPolynomialEncoder ` -**Punctured codes encoders** +**Punctured code encoder** - :class:`punctured_code.PuncturedCodePuncturedMatrixEncoder ` @@ -48,12 +48,18 @@ _lazy_import('sage.coding.cyclic_code', ['CyclicCodePolynomialEncoder', 'CyclicCodeVectorEncoder']) _lazy_import('sage.coding.extended_code', 'ExtendedCodeExtendedMatrixEncoder') -_lazy_import('sage.coding.grs_code', ['GRSEvaluationVectorEncoder', 'GRSEvaluationPolynomialEncoder']) +_lazy_import('sage.coding.grs_code', ['GRSEvaluationVectorEncoder', + 'GRSEvaluationPolynomialEncoder']) _lazy_import('sage.coding.linear_code', 'LinearCodeGeneratorMatrixEncoder') _lazy_import('sage.coding.linear_code_no_metric', 'LinearCodeSystematicEncoder') _lazy_import('sage.coding.punctured_code', 'PuncturedCodePuncturedMatrixEncoder') -_lazy_import('sage.coding.reed_muller_code', ['ReedMullerVectorEncoder', 'ReedMullerPolynomialEncoder']) +_lazy_import('sage.coding.reed_muller_code', ['ReedMullerVectorEncoder', + 'ReedMullerPolynomialEncoder']) _lazy_import('sage.coding.subfield_subcode', 'SubfieldSubcodeParityCheckEncoder') -_lazy_import('sage.coding.parity_check_code', ['ParityCheckCodeGeneratorMatrixEncoder','ParityCheckCodeStraightforwardEncoder']) +_lazy_import('sage.coding.parity_check_code', ['ParityCheckCodeGeneratorMatrixEncoder', + 'ParityCheckCodeStraightforwardEncoder']) _lazy_import('sage.coding.goppa_code', ['GoppaCodeEncoder']) -_lazy_import('sage.coding.gabidulin_code', ['GabidulinVectorEvaluationEncoder', 'GabidulinPolynomialEvaluationEncoder']) +_lazy_import('sage.coding.gabidulin_code', ['GabidulinVectorEvaluationEncoder', + 'GabidulinPolynomialEvaluationEncoder']) +_lazy_import('sage.coding.ag_code_decoders', ['EvaluationAGCodeEncoder', + 'DifferentialAGCodeEncoder']) diff --git a/src/sage/coding/linear_code.py b/src/sage/coding/linear_code.py index 5342aad0fbc..9944228ba7b 100644 --- a/src/sage/coding/linear_code.py +++ b/src/sage/coding/linear_code.py @@ -2115,7 +2115,7 @@ def e(i): for v in C_basis: i = v.support()[0] U_basis.remove(i) # swap e_{i+1} with v - U_basis = [e(i+1) for i in U_basis] + U_basis = [e(i + 1) for i in U_basis] V = VectorSpace(F, self.length()) U = V.span(U_basis) @@ -2128,16 +2128,16 @@ def e(i): Ainv = A.inverse() Pei = [] # projection of e_i on U - for i in range(1, self.length()+1): + for i in range(1, self.length() + 1): ei = e(i) if ei in U: Pei.append(ei) else: a = Ainv * ei # get zero vector and sum a[i]u_i to it - v = vector(F, [0]*self.length()) - for i in range(len(U_basis)): - v += a[i]*U_basis[i] + v = vector(F, [0] * self.length()) + for ai, Ui in zip(a, U_basis): + v += ai * Ui if not v.is_zero(): # don't care about 0 vectors v.set_immutable() Pei.append(v) diff --git a/src/sage/combinat/all.py b/src/sage/combinat/all.py index 50fd5b1961a..4e4e002ce6a 100644 --- a/src/sage/combinat/all.py +++ b/src/sage/combinat/all.py @@ -7,11 +7,16 @@ from .combinat import bell_number, catalan_number, euler_number, fibonacci, \ lucas_number1, lucas_number2, stirling_number1, stirling_number2, \ polygonal_number, CombinatorialObject, CombinatorialClass, \ - FilteredCombinatorialClass, UnionCombinatorialClass, \ - MapCombinatorialClass, InfiniteAbstractCombinatorialClass, \ + MapCombinatorialClass, \ tuples, number_of_tuples, unordered_tuples, number_of_unordered_tuples, \ bell_polynomial, fibonacci_sequence, fibonacci_xrange, bernoulli_polynomial +lazy_import('sage.combinat.combinat', + ('InfiniteAbstractCombinatorialClass', 'UnionCombinatorialClass', + 'FilteredCombinatorialClass'), + deprecation=(31545, 'this class is deprecated, do not use')) + + from .expnums import expnums from sage.combinat.chas.all import * @@ -208,9 +213,10 @@ lazy_import('sage.combinat.finite_state_machine_generators', ['automata', 'transducers']) -# Binary Recurrence Sequences +# Sequences lazy_import('sage.combinat.binary_recurrence_sequences', 'BinaryRecurrenceSequence') +lazy_import('sage.combinat.recognizable_series', 'RecognizableSeriesSpace') # Six Vertex Model lazy_import('sage.combinat.six_vertex_model', 'SixVertexModel') diff --git a/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py b/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py index 62512f1549f..b459b645422 100644 --- a/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py +++ b/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py @@ -1329,7 +1329,6 @@ def cluster_variable(self, k): return catchup.cluster_variable(k) else: raise ValueError('Clusters not being tracked') - return None def cluster(self): r""" @@ -4128,7 +4127,7 @@ def greedy(self, a1, a2, algorithm='by_recursion'): ans = 0 if a1 >= a2: PS = PathSubset(a1, a2) - elif a1 < a2: + else: PS = PathSubset(a2, a1) from sage.combinat.subset import Subsets for T in Subsets(PS): @@ -4137,7 +4136,7 @@ def greedy(self, a1, a2, algorithm='by_recursion'): oddT = set(T).intersection(PathSubset(a1, 0)) evenT = set(T).symmetric_difference(oddT) ans = ans + S.x(0)**(b*len(evenT)) * S.x(1)**(c*len(oddT)) - elif a1 < a2: + else: if is_LeeLiZel_allowable(T, a2, a1, c, b): oddT = set(T).intersection(PathSubset(a2, 0)) evenT = set(T).symmetric_difference(oddT) diff --git a/src/sage/combinat/combinat.py b/src/sage/combinat/combinat.py index ee309fd1ba6..9d43e70f244 100644 --- a/src/sage/combinat/combinat.py +++ b/src/sage/combinat/combinat.py @@ -2501,6 +2501,10 @@ def cardinality(self): EXAMPLES:: sage: R = InfiniteAbstractCombinatorialClass() + doctest:warning... + DeprecationWarning: this class is deprecated, do not use + See https://trac.sagemath.org/31545 for details. + sage: R.cardinality() +Infinity """ diff --git a/src/sage/combinat/crystals/littelmann_path.py b/src/sage/combinat/crystals/littelmann_path.py index 5e3d0ad50ce..187d5fd0b92 100644 --- a/src/sage/combinat/crystals/littelmann_path.py +++ b/src/sage/combinat/crystals/littelmann_path.py @@ -308,12 +308,12 @@ def compress(self): return self q = [] curr = self.value[0] - for i in range(1,len(self.value)): - if positively_parallel_weights(curr,self.value[i]): - curr = curr + self.value[i] + for v in self.value[1:]: + if positively_parallel_weights(curr, v): + curr = curr + v else: q.append(curr) - curr = self.value[i] + curr = v q.append(curr) return self.parent()(tuple(q)) @@ -333,9 +333,9 @@ def split_step(self, which_step, r): sage: b.split_step(0,1/3) (1/3*Lambda[1] + 1/3*Lambda[2], 2/3*Lambda[1] + 2/3*Lambda[2]) """ - assert which_step in range(len(self.value)) + assert 0 <= which_step and which_step <= len(self.value) v = self.value[which_step] - return self.parent()(self.value[:which_step]+tuple([r*v,(1-r)*v])+self.value[which_step+1:]) + return self.parent()(self.value[:which_step] + (r*v,(1-r)*v) + self.value[which_step+1:]) def reflect_step(self, which_step, i): r""" @@ -351,7 +351,7 @@ def reflect_step(self, which_step, i): (2*Lambda[1] - Lambda[2],) """ assert i in self.index_set() - assert which_step in range(len(self.value)) + assert 0 <= which_step and which_step <= len(self.value) return self.parent()(self.value[:which_step]+tuple([self.value[which_step].simple_reflection(i)])+self.value[which_step+1:]) def _string_data(self, i): @@ -371,7 +371,7 @@ def _string_data(self, i): sage: b.f(1).f(2)._string_data(2) ((0, -1, -1),) """ - if len(self.value) == 0: + if not self.value: return () # get the i-th simple coroot alv = self.value[0].parent().alphacheck()[i] @@ -381,10 +381,10 @@ def _string_data(self, i): minima_pos = [] ps = 0 psmin = 0 - for ix in range(len(steps)): - ps = ps + steps[ix] + for ix, step in enumerate(steps): + ps = ps + step if ps < psmin: - minima_pos.append((ix,ps,steps[ix])) + minima_pos.append((ix,ps,step)) psmin = ps return tuple(minima_pos) @@ -454,7 +454,7 @@ def e(self, i, power=1, to_string_end=False, length_only=False): assert i in self.index_set() data = self._string_data(i) # compute the minimum i-height M on the path - if len(data) == 0: + if not data: M = 0 else: M = data[-1][1] @@ -512,10 +512,9 @@ def dualize(self): (-Lambda[1] + 1/2*Lambda[2], Lambda[1] - 1/2*Lambda[2]) (-Lambda[1] + 1/2*Lambda[2], Lambda[1] - 1/2*Lambda[2]) (-2*Lambda[1] + Lambda[2],) (2*Lambda[1] - Lambda[2],) """ - if len(self.value) == 0: + if not self.value: return self - dual_path = [-v for v in self.value] - dual_path.reverse() + dual_path = [-v for v in reversed(self.value)] return self.parent()(tuple(dual_path)) def f(self, i, power=1, to_string_end=False, length_only=False): diff --git a/src/sage/combinat/designs/incidence_structures.py b/src/sage/combinat/designs/incidence_structures.py index ece5186b5fe..89006e331f9 100644 --- a/src/sage/combinat/designs/incidence_structures.py +++ b/src/sage/combinat/designs/incidence_structures.py @@ -1434,7 +1434,7 @@ def packing(self, solver=None, verbose=0): from sage.numerical.mip import MixedIntegerLinearProgram # List of blocks containing a given point x - d = [[] for x in self._points] + d = [[] for _ in self._points] for i, B in enumerate(self._blocks): for x in B: d[x].append(i) @@ -1947,7 +1947,7 @@ def is_resolvable(self, certificate=False, solver=None, verbose=0, check=True): domain = list(range(self.num_points())) # Lists of blocks containing i for every i - dual = [[] for i in domain] + dual = [[] for _ in domain] for i,B in enumerate(self._blocks): for x in B: dual[x].append(i) @@ -2078,7 +2078,7 @@ def coloring(self, k=None, solver=None, verbose=0): except MIPSolverException: raise ValueError("This hypergraph is not {}-colorable".format(k)) - col = [[] for i in range(k)] + col = [[] for _ in range(k)] for (x,i),v in p.get_values(b).items(): if v: diff --git a/src/sage/combinat/fqsym.py b/src/sage/combinat/fqsym.py index 6156bddc442..fc29f0f0a06 100644 --- a/src/sage/combinat/fqsym.py +++ b/src/sage/combinat/fqsym.py @@ -180,7 +180,7 @@ def an_element(self): sage: M.an_element() M[1] + 2*M[1, 2] + 4*M[2, 1] """ - o = self([1]) + o = self.monomial(Permutation([1])) return o + 2 * o * o @@ -642,8 +642,8 @@ def coproduct_on_basis(self, x): if not len(x): return self.one().tensor(self.one()) return sum(self(Word(x[:i]).standard_permutation()).tensor( - self(Word(x[i:]).standard_permutation())) - for i in range(len(x) + 1)) + self(Word(x[i:]).standard_permutation())) + for i in range(len(x) + 1)) class Element(FQSymBasis_abstract.Element): def to_symmetric_group_algebra(self, n=None): @@ -1187,9 +1187,9 @@ def coproduct_on_basis(self, x): if not n: return self.one().tensor(self.one()) return sum(self(Word(x[:i]).standard_permutation()).tensor( - self(Word(x[i:]).standard_permutation())) - for i in range(n + 1) - if (i == 0 or i == n or min(x[:i]) > max(x[i:]))) + self(Word(x[i:]).standard_permutation())) + for i in range(n + 1) + if (i == 0 or i == n or min(x[:i]) > max(x[i:]))) class Element(FQSymBasis_abstract.Element): def star_involution(self): @@ -1385,7 +1385,7 @@ def some_elements(self): [M[], M[1], M[1, 2] + 2*M[2, 1], M[] + M[1, 2] + 2*M[2, 1]] """ u = self.one() - o = self([1]) + o = self.monomial(Permutation([1])) x = o * o y = u + x return [u, o, x, y] diff --git a/src/sage/combinat/parking_functions.py b/src/sage/combinat/parking_functions.py index 65ac5d45877..8a515a29c33 100644 --- a/src/sage/combinat/parking_functions.py +++ b/src/sage/combinat/parking_functions.py @@ -63,16 +63,16 @@ # https://www.gnu.org/licenses/ # **************************************************************************** from typing import NewType, Iterator, Tuple -from copy import copy from sage.rings.integer import Integer from sage.rings.rational_field import QQ -from sage.rings.semirings.non_negative_integer_semiring import NN -from sage.combinat.combinat import CombinatorialObject +from sage.structure.list_clone import ClonableArray from sage.combinat.permutation import Permutation, Permutations +from sage.combinat.non_decreasing_parking_function import is_a as check_NDPF from sage.combinat.dyck_word import DyckWord from sage.combinat.combinatorial_map import combinatorial_map from sage.misc.prandom import randint +from sage.misc.inherit_comparison import InheritComparisonClasscallMetaclass from sage.rings.finite_rings.integer_mod_ring import Zmod from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets from sage.categories.infinite_enumerated_sets import InfiniteEnumeratedSets @@ -81,94 +81,7 @@ from sage.structure.unique_representation import UniqueRepresentation -PF = NewType('PF', 'ParkingFunction_class') - - -def ParkingFunctions(n=None): - r""" - Return the combinatorial class of Parking Functions. - - A *parking function* of size `n` is a sequence `(a_1, \ldots,a_n)` - of positive integers such that if `b_1 \leq b_2 \leq \cdots \leq b_n` is - the increasing rearrangement of `a_1, \ldots, a_n`, then `b_i \leq i`. - - A *parking function* of size `n` is a pair `(L, D)` of two sequences - `L` and `D` where `L` is a permutation and `D` is an area sequence - of a Dyck Path of size n such that `D[i] \geq 0`, `D[i+1] \leq D[i]+1` - and if `D[i+1] = D[i]+1` then `L[i+1] > L[i]`. - - The number of parking functions of size `n` is equal to the number - of rooted forests on `n` vertices and is equal to `(n+1)^{n-1}`. - - EXAMPLES: - - Here are all parking functions of size 3:: - - sage: from sage.combinat.parking_functions import ParkingFunctions - sage: ParkingFunctions(3).list() - [[1, 1, 1], [1, 1, 2], [1, 2, 1], [2, 1, 1], [1, 1, 3], [1, 3, 1], [3, 1, 1], - [1, 2, 2], [2, 1, 2], [2, 2, 1], [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], - [3, 1, 2], [3, 2, 1]] - - If no size is specified, then ParkingFunctions returns the - combinatorial class of all parking functions. :: - - sage: PF = ParkingFunctions(); PF - Parking functions - sage: [] in PF - True - sage: [1] in PF - True - sage: [2] in PF - False - sage: [1,3,1] in PF - True - sage: [1,4,1] in PF - False - - If the size `n` is specified, then ParkingFunctions returns - the combinatorial class of all parking functions of size `n`. - - :: - - sage: PF = ParkingFunctions(0) - sage: PF.list() - [[]] - sage: PF = ParkingFunctions(1) - sage: PF.list() - [[1]] - sage: PF = ParkingFunctions(3) - sage: PF.list() - [[1, 1, 1], [1, 1, 2], [1, 2, 1], [2, 1, 1], [1, 1, 3], - [1, 3, 1], [3, 1, 1], [1, 2, 2], [2, 1, 2], [2, 2, 1], - [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]] - - :: - - sage: PF3 = ParkingFunctions(3); PF3 - Parking functions of size 3 - sage: [] in PF3 - False - sage: [1] in PF3 - False - sage: [1,3,1] in PF3 - True - sage: [1,4,1] in PF3 - False - - TESTS:: - - sage: PF = ParkingFunctions(5) - sage: TestSuite(PF).run() - sage: len(PF.list()) == PF.cardinality() - True - """ - if n is None: - return ParkingFunctions_all() - - if not isinstance(n, (Integer, int)) or n < 0: - raise ValueError("%s is not a non-negative integer." % n) - return ParkingFunctions_n(n) +PF = NewType('PF', 'ParkingFunction') def is_a(x, n=None) -> bool: @@ -193,81 +106,11 @@ def is_a(x, n=None) -> bool: if not isinstance(x, list): # from Florent Hivert non_decreasing_parking_function return False A = sorted(x) - from sage.combinat.non_decreasing_parking_function import is_a - return is_a(A, n) - - -class ParkingFunctions_all(Parent, UniqueRepresentation): - def __init__(self): - """ - TESTS:: - - sage: PF = ParkingFunctions() - sage: TestSuite(PF).run() - """ - cat = InfiniteEnumeratedSets() & SetsWithGrading() - Parent.__init__(self, category=cat) - - def __repr__(self) -> str: - """ - TESTS:: - - sage: repr(ParkingFunctions()) - 'Parking functions' - """ - return "Parking functions" - - def __contains__(self, x) -> bool: - """ - TESTS:: - - sage: [] in ParkingFunctions() - True - sage: [1] in ParkingFunctions() - True - sage: [2] in ParkingFunctions() - False - sage: [1,3,1] in ParkingFunctions() - True - sage: [1,4,1] in ParkingFunctions() - False - """ - if isinstance(x, ParkingFunction_class): - return True - return is_a(x) - - def graded_component(self, n): - """ - Return the graded component. - - EXAMPLES:: - - sage: PF = ParkingFunctions() - sage: PF.graded_component(4) == ParkingFunctions(4) - True - sage: it = iter(ParkingFunctions()) # indirect doctest - sage: [next(it) for i in range(8)] - [[], [1], [1, 1], [1, 2], [2, 1], [1, 1, 1], [1, 1, 2], [1, 2, 1]] - """ - return ParkingFunctions_n(n) - - def __iter__(self) -> Iterator: - """ - Return an iterator. - - TESTS:: + return check_NDPF(A, n) - sage: it = iter(ParkingFunctions()) # indirect doctest - sage: [next(it) for i in range(8)] - [[], [1], [1, 1], [1, 2], [2, 1], [1, 1, 1], [1, 1, 2], [1, 2, 1]] - """ - for n in NN: - yield from ParkingFunctions_n(n).__iter__() - - -class ParkingFunctions_n(Parent, UniqueRepresentation): +class ParkingFunction(ClonableArray, metaclass=InheritComparisonClasscallMetaclass): r""" - The combinatorial class of parking functions of size `n`. + A Parking Function. A *parking function* of size `n` is a sequence `(a_1, \ldots,a_n)` of positive integers such that if `b_1 \leq b_2 \leq \cdots \leq b_n` is @@ -281,320 +124,162 @@ class ParkingFunctions_n(Parent, UniqueRepresentation): The number of parking functions of size `n` is equal to the number of rooted forests on `n` vertices and is equal to `(n+1)^{n-1}`. - EXAMPLES:: + INPUT: - sage: PF = ParkingFunctions(3) - sage: PF.list() - [[1, 1, 1], [1, 1, 2], [1, 2, 1], [2, 1, 1], [1, 1, 3], - [1, 3, 1], [3, 1, 1], [1, 2, 2], [2, 1, 2], [2, 2, 1], - [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]] + - ``pf`` -- (default: ``None``) a list whose increasing rearrangement + satisfies `b_i \leq i` - sage: [ParkingFunctions(i).cardinality() for i in range(6)] - [1, 1, 3, 16, 125, 1296] + - ``labelling`` -- (default: ``None``) a labelling of the Dyck path - .. warning:: + - ``area_sequence`` -- (default: ``None``) an area sequence of a Dyck path - The precise order in which the parking function are generated or - listed is not fixed, and may change in the future. + - ``labelled_dyck_word`` -- (default: ``None``) a Dyck word with 1's + replaced by labelling + + OUTPUT: + + A parking function + + EXAMPLES:: + + sage: ParkingFunction([]) + [] + sage: ParkingFunction([1]) + [1] + sage: ParkingFunction([2]) + Traceback (most recent call last): + ... + ValueError: [2] is not a parking function + sage: ParkingFunction([1,2]) + [1, 2] + sage: ParkingFunction([1,1,2]) + [1, 1, 2] + sage: ParkingFunction([1,4,1]) + Traceback (most recent call last): + ... + ValueError: [1, 4, 1] is not a parking function + sage: ParkingFunction(labelling=[3,1,2], area_sequence=[0,0,1]) + [2, 2, 1] + sage: ParkingFunction([2,2,1]).to_labelled_dyck_word() + [3, 0, 1, 2, 0, 0] + sage: ParkingFunction(labelled_dyck_word=[3,0,1,2,0,0]) + [2, 2, 1] + sage: ParkingFunction(labelling=[3,1,2], area_sequence=[0,1,1]) + Traceback (most recent call last): + ... + ValueError: [3, 1, 2] is not a valid labeling of area sequence [0, 1, 1] """ - def __init__(self, n): + @staticmethod + def __classcall_private__(cls, pf=None, labelling=None, area_sequence=None, + labelled_dyck_word=None): """ + Construct a parking function based on the input. + TESTS:: - sage: PF = ParkingFunctions(3) - sage: PF == loads(dumps(PF)) + sage: PF = ParkingFunction([1,2]) + sage: isinstance(PF, ParkingFunctions().element_class) True """ - self.n = n - Parent.__init__(self, category=FiniteEnumeratedSets()) - - def __repr__(self) -> str: + if isinstance(pf, ParkingFunction): + return pf + if pf is not None: + PF = ParkingFunctions() + return PF.element_class(PF, pf) + elif labelling is not None: + if (area_sequence is None): + raise ValueError("must also provide area sequence along with labelling.") + if (len(area_sequence) != len(labelling)): + raise ValueError("%s must be the same size as the labelling %s" % (area_sequence, labelling)) + if any(area_sequence[i] < area_sequence[i + 1] and labelling[i] > labelling[i + 1] for i in range(len(labelling) - 1)): + raise ValueError("%s is not a valid labeling of area sequence %s" % (labelling, area_sequence)) + return from_labelling_and_area_sequence(labelling, area_sequence) + elif labelled_dyck_word is not None: + return from_labelled_dyck_word(labelled_dyck_word) + elif area_sequence is not None: + DW = DyckWord(area_sequence) + return ParkingFunction(labelling=list(range(1, DW.size() + 1)), + area_sequence=DW) + + raise ValueError("did not manage to make this into a parking function") + + def __init__(self, parent, lst): """ TESTS:: - sage: repr(ParkingFunctions(3)) - 'Parking functions of size 3' + sage: ParkingFunction([1, 1, 2, 2, 5, 6]) + [1, 1, 2, 2, 5, 6] + + sage: PF = ParkingFunction([1, 1, 2, 2, 5, 6]) + sage: PF[0] + 1 + sage: PF[2] + 2 + + sage: PF4 = ParkingFunctions(4) + sage: a = PF4.list()[36] + sage: b = PF4([1,3,2,1]) + sage: type(a) + + sage: type(b) + """ - return "Parking functions of size %s" % self.n + if isinstance(lst, ParkingFunction): + lst = list(lst) + if not isinstance(lst, list): + raise TypeError('input must be a list') + if parent is None: + parent = ParkingFunctions_n(len(lst)) + ClonableArray.__init__(self, parent, lst) + + def check(self): + """ + Check that ``self`` is a valid parking function. - def __contains__(self, x) -> bool: + EXAMPLES:: + + sage: PF = ParkingFunction([1, 1, 2, 2, 5, 6]) + sage: PF.check() """ - TESTS:: + if not check_NDPF(sorted(self), len(self)): + raise ValueError(f'{list(self)} is not a parking function') - sage: PF3 = ParkingFunctions(3); PF3 - Parking functions of size 3 - sage: [] in PF3 - False - sage: [1] in PF3 - False - sage: [1,3,1] in PF3 - True - sage: [1,1,1] in PF3 - True - sage: [1,4,1] in PF3 - False - sage: all(p in PF3 for p in PF3) - True + def grade(self): """ - if isinstance(x, ParkingFunction_class): - return True - return is_a(x, self.n) + Return the length of the parking function. - def cardinality(self) -> Integer: - r""" - Return the number of parking functions of size ``n``. + EXAMPLES:: - The cardinality is equal to `(n+1)^{n-1}`. + sage: PF = ParkingFunction([1, 1, 2, 2, 5, 6]) + sage: PF.grade() + 6 + """ + return len(self) + + def __call__(self, n): + """ + Return the image of ``n`` under the parking function. EXAMPLES:: - sage: [ParkingFunctions(i).cardinality() for i in range(6)] - [1, 1, 3, 16, 125, 1296] + sage: PF = ParkingFunction([1, 1, 2, 2, 5, 6]) + sage: PF(3) + 2 + sage: PF(6) + 6 """ - return Integer((self.n + 1) ** (self.n - 1)) + return self[n - 1] - def __iter__(self) -> Iterator: - """ - Return an iterator for parking functions of size `n`. + def diagonal_reading_word(self) -> Permutation: + r""" + Return a diagonal word of the labelled Dyck path corresponding to parking + function (see [Hag08]_ p. 75). - .. warning:: + OUTPUT: - The precise order in which the parking function are - generated is not fixed, and may change in the future. - - EXAMPLES:: - - sage: PF = ParkingFunctions(0) - sage: [e for e in PF] # indirect doctest - [[]] - sage: PF = ParkingFunctions(1) - sage: [e for e in PF] # indirect doctest - [[1]] - sage: PF = ParkingFunctions(2) - sage: [e for e in PF] # indirect doctest - [[1, 1], [1, 2], [2, 1]] - sage: PF = ParkingFunctions(3) - sage: [e for e in PF] # indirect doctest - [[1, 1, 1], [1, 1, 2], [1, 2, 1], [2, 1, 1], [1, 1, 3], - [1, 3, 1], [3, 1, 1], [1, 2, 2], [2, 1, 2], [2, 2, 1], - [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]] - - TESTS:: - - sage: PF = ParkingFunctions(5) - sage: [e for e in PF] == PF.list() - True - sage: PF = ParkingFunctions(6) - sage: [e for e in PF] == PF.list() - True - """ - def iterator_rec(n): - """ - TESTS:: - - sage: PF = ParkingFunctions(2) - sage: [e for e in PF] # indirect doctest - [[1, 1], [1, 2], [2, 1]] - """ - if n == 0: - yield [] - return - if n == 1: - yield [1] - return - for res1 in iterator_rec(n - 1): - for i in range(res1[-1], n + 1): - res = copy(res1) - res.append(i) - yield res - return - for res in iterator_rec(self.n): - for pi in Permutations(res): - yield ParkingFunction(list(pi)) - return - - def random_element(self) -> PF: - r""" - Return a random parking function of size `n`. - - The algorithm uses a circular parking space with `n+1` - spots. Then all `n` cars can park and there remains one empty - spot. Spots are then renumbered so that the empty spot is `0`. - - The probability distribution is uniform on the set of - `(n+1)^{n-1}` parking functions of size `n`. - - EXAMPLES:: - - sage: pf = ParkingFunctions(8) - sage: a = pf.random_element(); a # random - [5, 7, 2, 4, 2, 5, 1, 3] - sage: a in pf - True - """ - n = self.n - Zm = Zmod(n + 1) - fun = [Zm(randint(0, n)) for i in range(n)] - free = [Zm(j) for j in range(n + 1)] - for car in fun: - position = car - while not(position in free): - position += Zm.one() - free.remove(position) - return ParkingFunction([(i - free[0]).lift() for i in fun]) - - -def ParkingFunction(pf=None, labelling=None, area_sequence=None, - labelled_dyck_word=None): - r""" - Return the combinatorial class of Parking Functions. - - A *parking function* of size `n` is a sequence `(a_1, \ldots,a_n)` - of positive integers such that if `b_1 \leq b_2 \leq \cdots \leq b_n` is - the increasing rearrangement of `a_1, \ldots, a_n`, then `b_i \leq i`. - - A *parking function* of size `n` is a pair `(L, D)` of two sequences - `L` and `D` where `L` is a permutation and `D` is an area sequence - of a Dyck Path of size `n` such that `D[i] \geq 0`, `D[i+1] \leq D[i]+1` - and if `D[i+1] = D[i]+1` then `L[i+1] > L[i]`. - - The number of parking functions of size `n` is equal to the number - of rooted forests on `n` vertices and is equal to `(n+1)^{n-1}`. - - INPUT: - - - ``pf`` -- (default: None) a list whose increasing rearrangement satisfies `b_i \leq i` - - - ``labelling`` -- (default: None) a labelling of the Dyck path - - - ``area_sequence`` -- (default: None) an area sequence of a Dyck path - - - ``labelled_dyck_word`` -- (default: None) a Dyck word with 1's replaced by labelling - - OUTPUT: - - - A parking function - - EXAMPLES:: - - sage: ParkingFunction([]) - [] - sage: ParkingFunction([1]) - [1] - sage: ParkingFunction([2]) - Traceback (most recent call last): - ... - ValueError: [2] is not a parking function. - sage: ParkingFunction([1,2]) - [1, 2] - sage: ParkingFunction([1,1,2]) - [1, 1, 2] - sage: ParkingFunction([1,4,1]) - Traceback (most recent call last): - ... - ValueError: [1, 4, 1] is not a parking function. - sage: ParkingFunction(labelling=[3,1,2], area_sequence=[0,0,1]) - [2, 2, 1] - sage: ParkingFunction([2,2,1]).to_labelled_dyck_word() - [3, 0, 1, 2, 0, 0] - sage: ParkingFunction(labelled_dyck_word = [3,0,1,2,0,0]) - [2, 2, 1] - sage: ParkingFunction(labelling=[3,1,2], area_sequence=[0,1,1]) - Traceback (most recent call last): - ... - ValueError: [3, 1, 2] is not a valid labeling of area sequence [0, 1, 1] - """ - if pf is not None: - return ParkingFunction_class(pf) - elif labelling is not None: - if (area_sequence is None): - raise ValueError("must also provide area sequence along with labelling.") - if (len(area_sequence) != len(labelling)): - raise ValueError("%s must be the same size as the labelling %s" % (area_sequence, labelling)) - if any(area_sequence[i] < area_sequence[i + 1] and labelling[i] > labelling[i + 1] for i in range(len(labelling) - 1)): - raise ValueError("%s is not a valid labeling of area sequence %s" % (labelling, area_sequence)) - return from_labelling_and_area_sequence(labelling, area_sequence) - elif labelled_dyck_word is not None: - return from_labelled_dyck_word(labelled_dyck_word) - elif area_sequence is not None: - DW = DyckWord(area_sequence) - return ParkingFunction(labelling=list(range(1, DW.size() + 1)), - area_sequence=DW) - - raise ValueError("did not manage to make this into a parking function") - - -class ParkingFunction_class(CombinatorialObject): - def __init__(self, lst): - """ - TESTS:: - - sage: ParkingFunction([1, 1, 2, 2, 5, 6]) - [1, 1, 2, 2, 5, 6] - """ - if not is_a(lst): - raise ValueError("%s is not a parking function." % lst) - CombinatorialObject.__init__(self, lst) - - def __getitem__(self, n): - """ - Return the `n^{th}` item in the underlying list. - - .. NOTE:: - - Note that this is different than the image of ``n`` under - function. It is "off by one" in that it agrees with sage - indexing starting at 0. - - EXAMPLES:: - - sage: PF = ParkingFunction([1, 1, 2, 2, 5, 6]) - sage: PF[0] - 1 - sage: PF[2] - 2 - """ - return self._list[n] - - def grade(self): - """ - Return the length of the parking function. - - EXAMPLES:: - - sage: PF = ParkingFunction([1, 1, 2, 2, 5, 6]) - sage: PF.grade() - 6 - """ - return len(self) - - def __call__(self, n): - """ - Return the image of ``n`` under the parking function. - - EXAMPLES:: - - sage: PF = ParkingFunction([1, 1, 2, 2, 5, 6]) - sage: PF(3) - 2 - sage: PF(6) - 6 - """ - return self._list[n - 1] - - def diagonal_reading_word(self) -> Permutation: - r""" - Return a diagonal word of the labelled Dyck path corresponding to parking - function (see [Hag08]_ p. 75). - - INPUT: - - - ``self`` -- parking function word - - OUTPUT: - - - returns a word, read diagonally from NE to SW of the pretty print of the - labelled Dyck path that corresponds to ``self`` and the same size as ``self`` + - returns a word, read diagonally from NE to SW of the pretty + print of the labelled Dyck path that corresponds to ``self`` + and the same size as ``self`` EXAMPLES:: @@ -639,10 +324,6 @@ def parking_permutation(self) -> Permutation: spot 5 by car 3, spot 2 is taken by car 4, spot 3 is taken by car 5, spot 4 is taken by car 6 and spot 7 is taken by car 7. - INPUT: - - - ``self`` -- parking function word - OUTPUT: - the permutation of parking spots that corresponds to @@ -674,12 +355,9 @@ def cars_permutation(self) -> Permutation: and corresponding to the parking function. For example, ``cars_permutation(PF) = [2, 4, 5, 6, 3, 1, 7]`` - means that car 2 takes spots 1, car 4 takes spot 2, ..., car 1 takes spot 6 and - car 7 takes spot 7. - - INPUT: + means that car 2 takes spots 1, car 4 takes spot 2, ..., car 1 + takes spot 6 and car 7 takes spot 7. - - ``self`` -- parking function word OUTPUT: @@ -715,12 +393,9 @@ def jump_list(self) -> list: # cars displacements For example, ``jump_list(PF) = [0, 0, 0, 0, 1, 3, 2]`` means that car 1 through 4 parked in their preferred spots, - car 5 had to park one spot farther (jumped or was displaced by one spot), - car 6 had to jump 3 spots, and car 7 had to jump two spots. - - INPUT: + car 5 had to park one spot farther (jumped or was displaced by one + spot), car 6 had to jump 3 spots, and car 7 had to jump two spots. - - ``self`` -- parking function word OUTPUT: @@ -752,10 +427,6 @@ def jump(self) -> Integer: # sum of all jumps, sum of all displacements See [Shin]_ p. 18. - INPUT: - - - ``self`` -- a parking function word - OUTPUT: - the sum of the differences between the parked and preferred parking @@ -781,12 +452,9 @@ def jump(self) -> Integer: # sum of all jumps, sum of all displacements def lucky_cars(self): # the set of cars that can park in their preferred spots r""" Return the cars that can park in their preferred spots. For example, - ``lucky_cars(PF) = [1, 2, 7]`` means that cars 1, 2 and 7 parked in their - preferred spots and all the other cars did not. + ``lucky_cars(PF) = [1, 2, 7]`` means that cars 1, 2 and 7 parked in + their preferred spots and all the other cars did not. - INPUT: - - - ``self`` -- parking function word OUTPUT: @@ -815,10 +483,6 @@ def luck(self) -> Integer: # the number of lucky cars Return the number of cars that parked in their preferred parking spots (see [Shin]_ p. 33). - INPUT: - - - ``self`` -- parking function word - OUTPUT: - the number of cars that parked in their preferred parking spots @@ -842,12 +506,8 @@ def luck(self) -> Integer: # the number of lucky cars def primary_dinversion_pairs(self): r""" - Return the primary descent inversion pairs of a labelled Dyck path corresponding - to the parking function. - - INPUT: - - - ``self`` -- parking function word + Return the primary descent inversion pairs of a labelled Dyck path + corresponding to the parking function. OUTPUT: @@ -879,10 +539,6 @@ def secondary_dinversion_pairs(self): Return the secondary descent inversion pairs of a labelled Dyck path corresponding to the parking function. - INPUT: - - - ``self`` -- parking function word - OUTPUT: - the pairs `(i, j)` such that `i < j`, and `i^{th}` area = `j^{th}` area +1, @@ -913,10 +569,6 @@ def dinversion_pairs(self) -> list: Return the descent inversion pairs of a labelled Dyck path corresponding to the parking function. - INPUT: - - - ``self`` -- parking function word - OUTPUT: - the primary and secondary diversion pairs @@ -945,10 +597,6 @@ def dinv(self) -> Integer: Same as the cardinality of :meth:`dinversion_pairs`. - INPUT: - - - ``self`` -- parking function word - OUTPUT: - the number of dinversion pairs @@ -975,10 +623,6 @@ def area(self) -> Integer: Return the area of the labelled Dyck path corresponding to the parking function. - INPUT: - - - ``self`` -- parking function word - OUTPUT: - the sum of squares under and over the main diagonal the Dyck Path, @@ -1014,10 +658,6 @@ def ides_composition(self): :meth:`diagonal_reading_word` of the parking function with word ``PF`` are at the 4th and 6th positions. - INPUT: - - - ``self`` -- parking function word - OUTPUT: - the descents composition of the inverse of the @@ -1042,8 +682,9 @@ def ides_composition(self): def ides(self): r""" - Return the :meth:`~sage.combinat.permutation.Permutation.descents` sequence - of the inverse of the :meth:`diagonal_reading_word` of ``self``. + Return the :meth:`~sage.combinat.permutation.Permutation.descents` + sequence of the inverse of the :meth:`diagonal_reading_word` + of ``self``. .. WARNING:: @@ -1051,14 +692,10 @@ def ides(self): start at `1`. This behaviour has been changed in :trac:`20555`. - For example, ``ides(PF) = [2, 3, 4, 6]`` means that descents are at the 2nd, 3rd, - 4th and 6th positions in the inverse of the + For example, ``ides(PF) = [2, 3, 4, 6]`` means that descents are at + the 2nd, 3rd, 4th and 6th positions in the inverse of the :meth:`diagonal_reading_word` of the parking function (see [GXZ]_ p. 2). - INPUT: - - - ``self`` -- parking function word - OUTPUT: - the descents sequence of the inverse of the @@ -1083,17 +720,13 @@ def ides(self): def touch_points(self): r""" - Return the sequence of touch points which corresponds to the labelled Dyck path - after initial step. + Return the sequence of touch points which corresponds to the + labelled Dyck path after initial step. For example, ``touch_points(PF) = [4, 7]`` means that after the initial step, the path touches the main diagonal at points `(4, 4)` and `(7, 7)`. - INPUT: - - - ``self`` -- parking function word - OUTPUT: - the sequence of touch points after the initial step of the @@ -1126,10 +759,6 @@ def touch_composition(self): first touch is four diagonal units from the starting point, and the second is three units further (see [GXZ]_ p. 2). - INPUT: - - - ``self`` -- parking function word - OUTPUT: - the length between the corresponding touch points which @@ -1159,10 +788,6 @@ def to_labelling_permutation(self) -> Permutation: r""" Return the labelling of the support Dyck path of the parking function. - INPUT: - - - ``self`` -- parking function word - OUTPUT: - the labelling of the Dyck path @@ -1185,15 +810,11 @@ def to_labelling_permutation(self) -> Permutation: from sage.combinat.words.word import Word return Word(self).standard_permutation().inverse() - def to_area_sequence(self): + def to_area_sequence(self) -> list: r""" Return the area sequence of the support Dyck path of the parking function. - INPUT: - - - ``self`` -- parking function word - OUTPUT: - the area sequence of the Dyck path @@ -1222,10 +843,6 @@ def to_labelling_area_sequence_pair(self): of a Dyck path which corresponds to the given parking function. - INPUT: - - - ``self`` -- the parking function word - OUTPUT: - returns a pair ``(L, D)`` where ``L`` is a labelling and ``D`` is the @@ -1255,10 +872,6 @@ def to_dyck_word(self) -> DyckWord: r""" Return the support Dyck word of the parking function. - INPUT: - - - ``self`` -- parking function word - OUTPUT: - the Dyck word of the corresponding parking function @@ -1290,10 +903,6 @@ def to_labelled_dyck_word(self): where the entries of 1 in the Dyck word are replaced with the corresponding label. - INPUT: - - - ``self`` -- parking function word - OUTPUT: - the labelled Dyck word of the corresponding parking function @@ -1315,7 +924,7 @@ def to_labelled_dyck_word(self): [2, 4, 0, 1, 0, 0, 3, 0] """ dw = self.to_dyck_word() - out = list(copy(self.to_labelling_permutation())) + out = list(self.to_labelling_permutation()) for i in range(2 * len(out)): if dw[i] == 0: out.insert(i, 0) @@ -1326,10 +935,6 @@ def to_labelling_dyck_word_pair(self) -> Tuple[Permutation, DyckWord]: Return the pair ``(L, D)`` where ``L`` is a labelling and ``D`` is the Dyck word of the parking function. - INPUT: - - - ``self`` -- parking function word - OUTPUT: - the pair ``(L, D)``, where ``L`` is the labelling and ``D`` is @@ -1360,10 +965,6 @@ def to_NonDecreasingParkingFunction(self) -> PF: Return the non-decreasing parking function which underlies the parking function. - INPUT: - - - ``self`` -- parking function word - OUTPUT: - a sorted parking function @@ -1440,7 +1041,7 @@ def characteristic_quasisymmetric_function(self, q=None, if q not in R: raise ValueError("q=%s must be an element of the base ring %s" % (q, R)) F = QuasiSymmetricFunctions(R).Fundamental() - return q ** self.dinv() * F(self.ides_composition()) + return q**self.dinv() * F(self.ides_composition()) def pretty_print(self, underpath=True): r""" @@ -1574,9 +1175,17 @@ def from_labelling_and_area_sequence(L, D) -> PF: [1, 1, 2] sage: from_labelling_and_area_sequence([1, 2, 4, 3], [0, 1, 2, 1]) [1, 1, 3, 1] + + TESTS:: + + sage: PF = from_labelling_and_area_sequence([1, 2, 4, 3], [0, 1, 2, 1]) + sage: isinstance(PF, ParkingFunctions().element_class) + True """ - return ParkingFunction_class([L.index(i) + 1 - D[L.index(i)] - for i in range(1, len(L) + 1)]) + PF = ParkingFunctions_all() + return PF.element_class(PF, + [L.index(i) + 1 - D[L.index(i)] + for i in range(1, len(L) + 1)]) def from_labelled_dyck_word(LDW) -> PF: @@ -1611,3 +1220,390 @@ def from_labelled_dyck_word(LDW) -> PF: L = [ell for ell in LDW if ell != 0] D = DyckWord([Integer(not x.is_zero()) for x in LDW]) return from_labelling_and_area_sequence(L, D.to_area_sequence()) + +class ParkingFunctions(UniqueRepresentation, Parent): + r""" + Return the combinatorial class of Parking Functions. + + A *parking function* of size `n` is a sequence `(a_1, \ldots,a_n)` + of positive integers such that if `b_1 \leq b_2 \leq \cdots \leq b_n` is + the increasing rearrangement of `a_1, \ldots, a_n`, then `b_i \leq i`. + + A *parking function* of size `n` is a pair `(L, D)` of two sequences + `L` and `D` where `L` is a permutation and `D` is an area sequence + of a Dyck Path of size n such that `D[i] \geq 0`, `D[i+1] \leq D[i]+1` + and if `D[i+1] = D[i]+1` then `L[i+1] > L[i]`. + + The number of parking functions of size `n` is equal to the number + of rooted forests on `n` vertices and is equal to `(n+1)^{n-1}`. + + EXAMPLES: + + Here are all parking functions of size 3:: + + sage: from sage.combinat.parking_functions import ParkingFunctions + sage: ParkingFunctions(3).list() + [[1, 1, 1], [1, 1, 2], [1, 2, 1], [2, 1, 1], [1, 1, 3], [1, 3, 1], + [3, 1, 1], [1, 2, 2], [2, 1, 2], [2, 2, 1], [1, 2, 3], [1, 3, 2], + [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]] + + If no size is specified, then ParkingFunctions returns the + combinatorial class of all parking functions. :: + + sage: PF = ParkingFunctions(); PF + Parking functions + sage: [] in PF + True + sage: [1] in PF + True + sage: [2] in PF + False + sage: [1,3,1] in PF + True + sage: [1,4,1] in PF + False + + If the size `n` is specified, then ParkingFunctions returns + the combinatorial class of all parking functions of size `n`. + + :: + + sage: PF = ParkingFunctions(0) + sage: PF.list() + [[]] + sage: PF = ParkingFunctions(1) + sage: PF.list() + [[1]] + sage: PF = ParkingFunctions(3) + sage: PF.list() + [[1, 1, 1], [1, 1, 2], [1, 2, 1], [2, 1, 1], [1, 1, 3], + [1, 3, 1], [3, 1, 1], [1, 2, 2], [2, 1, 2], [2, 2, 1], + [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]] + + :: + + sage: PF3 = ParkingFunctions(3); PF3 + Parking functions of size 3 + sage: [] in PF3 + False + sage: [1] in PF3 + False + sage: [1,3,1] in PF3 + True + sage: [1,4,1] in PF3 + False + + TESTS:: + + sage: PF = ParkingFunctions(5) + sage: TestSuite(PF).run() + sage: len(PF.list()) == PF.cardinality() + True + """ + @staticmethod + def __classcall_private__(cls, n=None): + """ + Return the correct parent based on input. + + TESTS:: + + sage: type(ParkingFunctions(5)) + + sage: type(ParkingFunctions()) + + """ + if n is None: + return ParkingFunctions_all() + + if not isinstance(n, (Integer, int)) or n < 0: + raise ValueError("%s is not a non-negative integer" % n) + return ParkingFunctions_n(n) + +class ParkingFunctions_all(ParkingFunctions): + def __init__(self): + """ + TESTS:: + + sage: PF = ParkingFunctions() + sage: TestSuite(PF).run() + """ + cat = InfiniteEnumeratedSets() & SetsWithGrading() + Parent.__init__(self, category=cat) + + Element = ParkingFunction + + def _repr_(self) -> str: + """ + TESTS:: + + sage: repr(ParkingFunctions()) + 'Parking functions' + """ + return "Parking functions" + + def __contains__(self, x) -> bool: + """ + TESTS:: + + sage: [] in ParkingFunctions() + True + sage: [1] in ParkingFunctions() + True + sage: [2] in ParkingFunctions() + False + sage: [1,3,1] in ParkingFunctions() + True + sage: [1,4,1] in ParkingFunctions() + False + """ + if isinstance(x, ParkingFunction): + return True + return is_a(x) + + def graded_component(self, n): + """ + Return the graded component. + + EXAMPLES:: + + sage: PF = ParkingFunctions() + sage: PF.graded_component(4) == ParkingFunctions(4) + True + sage: it = iter(ParkingFunctions()) # indirect doctest + sage: [next(it) for i in range(8)] + [[], [1], [1, 1], [1, 2], [2, 1], [1, 1, 1], [1, 1, 2], [1, 2, 1]] + """ + return ParkingFunctions_n(n) + + def __iter__(self) -> Iterator: + """ + Return an iterator. + + TESTS:: + + sage: it = iter(ParkingFunctions()) # indirect doctest + sage: [next(it) for i in range(8)] + [[], [1], [1, 1], [1, 2], [2, 1], [1, 1, 1], [1, 1, 2], [1, 2, 1]] + """ + n = 0 + while True: + for pf in ParkingFunctions_n(n): + yield self.element_class(self, list(pf)) + n += 1 + + def _coerce_map_from_(self, S): + """ + Coercion from the homogenous component to the graded set. + + EXAMPLES:: + + sage: PF = ParkingFunctions() + sage: PF3 = ParkingFunctions(3) + sage: x = PF([1,3,2]) + sage: y = PF3([1,3,2]) + sage: PF(y).parent() + Parking functions + sage: x == y + True + """ + if isinstance(S, ParkingFunctions_n): + return True + return False + + +class ParkingFunctions_n(ParkingFunctions): + r""" + The combinatorial class of parking functions of size `n`. + + A *parking function* of size `n` is a sequence `(a_1, \ldots,a_n)` + of positive integers such that if `b_1 \leq b_2 \leq \cdots \leq b_n` is + the increasing rearrangement of `a_1, \ldots, a_n`, then `b_i \leq i`. + + A *parking function* of size `n` is a pair `(L, D)` of two sequences + `L` and `D` where `L` is a permutation and `D` is an area sequence + of a Dyck Path of size `n` such that `D[i] \geq 0`, `D[i+1] \leq D[i]+1` + and if `D[i+1] = D[i]+1` then `L[i+1] > L[i]`. + + The number of parking functions of size `n` is equal to the number + of rooted forests on `n` vertices and is equal to `(n+1)^{n-1}`. + + EXAMPLES:: + + sage: PF = ParkingFunctions(3) + sage: PF.list() + [[1, 1, 1], [1, 1, 2], [1, 2, 1], [2, 1, 1], [1, 1, 3], + [1, 3, 1], [3, 1, 1], [1, 2, 2], [2, 1, 2], [2, 2, 1], + [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]] + + sage: [ParkingFunctions(i).cardinality() for i in range(6)] + [1, 1, 3, 16, 125, 1296] + + .. WARNING:: + + The precise order in which the parking function are generated or + listed is not fixed, and may change in the future. + + TESTS: + + Check that :trac:`15216` is fixed:: + + sage: PF = ParkingFunctions() + sage: PF3 = ParkingFunctions(3) + sage: [1,1,1] in PF3 + True + sage: PF3([1,1,1]) in PF3 + True + sage: PF3([1,1,1]) in PF + True + sage: PF([1,1,1]) in PF + True + sage: PF3(PF3([1,1,1])) + [1, 1, 1] + """ + def __init__(self, n): + """ + TESTS:: + + sage: PF = ParkingFunctions(3) + sage: TestSuite(PF).run() + """ + self.n = n + Parent.__init__(self, category=FiniteEnumeratedSets()) + + Element = ParkingFunction + + def _repr_(self) -> str: + """ + TESTS:: + + sage: repr(ParkingFunctions(3)) + 'Parking functions of size 3' + """ + return "Parking functions of size %s" % self.n + + def __contains__(self, x) -> bool: + """ + TESTS:: + + sage: PF3 = ParkingFunctions(3); PF3 + Parking functions of size 3 + sage: [] in PF3 + False + sage: [1] in PF3 + False + sage: [1,3,1] in PF3 + True + sage: [1,1,1] in PF3 + True + sage: [1,4,1] in PF3 + False + sage: ParkingFunction([1,2]) in PF3 + False + sage: all(p in PF3 for p in PF3) + True + """ + if isinstance(x, ParkingFunction) and len(x) == self.n: + return True + return is_a(x, self.n) + + def cardinality(self) -> Integer: + r""" + Return the number of parking functions of size ``n``. + + The cardinality is equal to `(n+1)^{n-1}`. + + EXAMPLES:: + + sage: [ParkingFunctions(i).cardinality() for i in range(6)] + [1, 1, 3, 16, 125, 1296] + """ + return Integer((self.n + 1)**(self.n - 1)) + + def __iter__(self) -> Iterator: + """ + Return an iterator for parking functions of size `n`. + + .. WARNING:: + + The precise order in which the parking function are + generated is not fixed, and may change in the future. + + EXAMPLES:: + + sage: PF = ParkingFunctions(0) + sage: [e for e in PF] # indirect doctest + [[]] + sage: PF = ParkingFunctions(1) + sage: [e for e in PF] # indirect doctest + [[1]] + sage: PF = ParkingFunctions(2) + sage: [e for e in PF] # indirect doctest + [[1, 1], [1, 2], [2, 1]] + sage: PF = ParkingFunctions(3) + sage: [e for e in PF] # indirect doctest + [[1, 1, 1], [1, 1, 2], [1, 2, 1], [2, 1, 1], [1, 1, 3], + [1, 3, 1], [3, 1, 1], [1, 2, 2], [2, 1, 2], [2, 2, 1], + [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]] + + TESTS:: + + sage: PF = ParkingFunctions(5) + sage: [e for e in PF] == PF.list() + True + sage: PF = ParkingFunctions(6) + sage: [e for e in PF] == PF.list() + True + """ + def iterator_rec(n): + """ + TESTS:: + + sage: PF = ParkingFunctions(2) + sage: [e for e in PF] # indirect doctest + [[1, 1], [1, 2], [2, 1]] + """ + if n == 0: + yield [] + return + if n == 1: + yield [1] + return + for res1 in iterator_rec(n - 1): + for i in range(res1[-1], n + 1): + yield res1 + [i] + return + for res in iterator_rec(self.n): + for pi in Permutations(res): + yield self.element_class(self, list(pi)) + return + + def random_element(self) -> PF: + r""" + Return a random parking function of size `n`. + + The algorithm uses a circular parking space with `n+1` + spots. Then all `n` cars can park and there remains one empty + spot. Spots are then renumbered so that the empty spot is `0`. + + The probability distribution is uniform on the set of + `(n+1)^{n-1}` parking functions of size `n`. + + EXAMPLES:: + + sage: pf = ParkingFunctions(8) + sage: a = pf.random_element(); a # random + [5, 7, 2, 4, 2, 5, 1, 3] + sage: a in pf + True + """ + n = self.n + Zm = Zmod(n + 1) + fun = [Zm(randint(0, n)) for i in range(n)] + free = [Zm(j) for j in range(n + 1)] + for car in fun: + position = car + while position not in free: + position += Zm.one() + free.remove(position) + return self.element_class(self, [(i - free[0]).lift() for i in fun]) + diff --git a/src/sage/combinat/posets/posets.py b/src/sage/combinat/posets/posets.py index 7ff9e74c82d..2d385834ff6 100644 --- a/src/sage/combinat/posets/posets.py +++ b/src/sage/combinat/posets/posets.py @@ -135,6 +135,8 @@ :meth:`~FinitePoset.antichains` | Return the antichains of the poset. :meth:`~FinitePoset.maximal_chains` | Return the maximal chains of the poset. :meth:`~FinitePoset.maximal_antichains` | Return the maximal antichains of the poset. + :meth:`~FinitePoset.maximal_chains_iterator` | Return an iterator over the maximal chains of the poset. + :meth:`~FinitePoset.maximal_chain_length` | Return the maximum length of maximal chains of the poset. :meth:`~FinitePoset.antichains_iterator` | Return an iterator over the antichains of the poset. :meth:`~FinitePoset.random_maximal_chain` | Return a random maximal chain. :meth:`~FinitePoset.random_maximal_antichain` | Return a random maximal antichain. @@ -3312,7 +3314,7 @@ def is_EL_labelling(self, f, return_raising_chains=False): P = self.subposet(self.interval(a, b)) max_chains = sorted([[label_dict[(chain[i], chain[i + 1])] for i in range(len(chain) - 1)] - for chain in P.maximal_chains()]) + for chain in P.maximal_chains_iterator()]) if max_chains[0] != sorted(max_chains[0]) or any(max_chains[i] == sorted(max_chains[i]) for i in range(1, len(max_chains))): return False elif return_raising_chains: @@ -6549,7 +6551,7 @@ def closed_interval(self, x, y): [] """ return [self._vertex_to_element(_) for _ in self._hasse_diagram.interval( - self._element_to_vertex(x),self._element_to_vertex(y))] + self._element_to_vertex(x), self._element_to_vertex(y))] def open_interval(self, x, y): """ @@ -6579,7 +6581,7 @@ def open_interval(self, x, y): [] """ return [self._vertex_to_element(_) for _ in self._hasse_diagram.open_interval( - self._element_to_vertex(x),self._element_to_vertex(y))] + self._element_to_vertex(x), self._element_to_vertex(y))] def comparability_graph(self): r""" @@ -6750,20 +6752,73 @@ def maximal_chains(self, partial=None): .. SEEALSO:: :meth:`maximal_antichains`, :meth:`chains` """ - if partial is None or len(partial) == 0: + return list(self.maximal_chains_iterator(partial=partial)) + + def maximal_chains_iterator(self, partial=None): + """ + Return an iterator over maximal chains. + + Each chain is listed in increasing order. + + INPUT: + + - ``partial`` -- list (optional); if present, yield all maximal + chains starting with the elements in partial + + EXAMPLES:: + + sage: P = posets.BooleanLattice(3) + sage: it = P.maximal_chains_iterator() + sage: next(it) + [0, 1, 3, 7] + + .. SEEALSO:: + + :meth:`antichains_iterator` + """ + if partial is None or not partial: start = self.minimal_elements() partial = [] else: start = self.upper_covers(partial[-1]) - if len(start) == 0: - return [partial] - if len(start) == 1: - return self.maximal_chains(partial=partial + start) - parts = [partial + [x] for x in start] - answer = [] - for new in parts: - answer += self.maximal_chains(partial=new) - return answer + if not start: + yield partial + elif len(start) == 1: + yield from self.maximal_chains_iterator(partial=partial + start) + else: + parts = (partial + [x] for x in start) + for new in parts: + yield from self.maximal_chains_iterator(partial=new) + + def maximal_chain_length(self): + """ + Return the maximum length of a maximal chain in the poset. + + The length here is the number of vertices. + + EXAMPLES:: + + sage: P = posets.TamariLattice(5) + sage: P.maximal_chain_length() + 11 + + TESTS:: + + sage: Poset().maximal_chain_length() + 0 + + .. SEEALSO:: :meth:`maximal_chains`, :meth:`maximal_chains_iterator` + """ + if not self.cardinality(): + return 0 + store = {} + for x in self: + below = self.lower_covers(x) + if not below: + store[x] = 1 + else: + store[x] = 1 + max(store[y] for y in below) + return max(store.values()) def order_complex(self, on_ints=False): r""" @@ -6809,7 +6864,7 @@ def order_complex(self, on_ints=False): iso = dict([(L[i], i) for i in range(len(L))]) facets = [] - for f in self.maximal_chains(): + for f in self.maximal_chains_iterator(): # TODO: factor out the logic for on_ints / facade / ... # We will want to do similar things elsewhere if on_ints: @@ -6858,12 +6913,12 @@ def order_polytope(self): True """ from sage.geometry.polyhedron.constructor import Polyhedron - ineqs = [[0] + [ZZ(j==v) - ZZ(j==u) for j in self] + ineqs = [[0] + [ZZ(j == v) - ZZ(j == u) for j in self] for u, v, w in self.hasse_diagram().edges()] for i in self.maximal_elements(): - ineqs += [[1] + [-ZZ(j==i) for j in self]] + ineqs += [[1] + [-ZZ(j == i) for j in self]] for i in self.minimal_elements(): - ineqs += [[0] + [ZZ(j==i) for j in self]] + ineqs += [[0] + [ZZ(j == i) for j in self]] return Polyhedron(ieqs=ineqs, base_ring=ZZ) def chain_polytope(self): @@ -6898,9 +6953,9 @@ def chain_polytope(self): """ from sage.geometry.polyhedron.constructor import Polyhedron ineqs = [[1] + [-ZZ(j in chain) for j in self] - for chain in self.maximal_chains()] + for chain in self.maximal_chains_iterator()] for i in self: - ineqs += [[0] + [ZZ(j==i) for j in self]] + ineqs += [[0] + [ZZ(j == i) for j in self]] return Polyhedron(ieqs=ineqs, base_ring=ZZ) def zeta_polynomial(self): @@ -7999,12 +8054,12 @@ def frank_network(self): """ from sage.graphs.digraph import DiGraph P0 = [(0, i) for i in self] - pdict = { (-1, 0): P0, (2, 0): [] } + pdict = {(-1, 0): P0, (2, 0): []} for i in self: pdict[(0, i)] = [(1, j) for j in self if self.ge(i, j)] pdict[(1, i)] = [(2, 0)] G = DiGraph(pdict, format="dict_of_lists") - a = { (u, v): 0 for (u, v, l) in G.edge_iterator() } + a = {(u, v): 0 for (u, v, l) in G.edge_iterator()} for i in self: a[((0, i), (1, i))] = 1 return (G, a) @@ -8733,19 +8788,19 @@ def _ford_fulkerson_chronicle(G, s, t, a): from sage.graphs.digraph import DiGraph # pi: potential function as a dictionary. - pi = { v: 0 for v in G.vertex_iterator() } + pi = {v: 0 for v in G.vertex_iterator()} # p: value of the potential pi. p = 0 # f: flow function as a dictionary. - f = { (u, v): 0 for (u, v, l) in G.edge_iterator() } + f = {(u, v): 0 for (u, v, l) in G.edge_iterator()} # val: value of the flow f. (Cannot call it v due to Python's asinine # handling of for loops.) val = 0 # capacity: capacity function as a dictionary. Here, just the # indicator function of the set of arcs of G. - capacity = { (u, v): 1 for (u, v, l) in G.edge_iterator() } + capacity = {(u, v): 1 for (u, v, l) in G.edge_iterator()} while True: diff --git a/src/sage/combinat/recognizable_series.py b/src/sage/combinat/recognizable_series.py new file mode 100644 index 00000000000..50219d3abac --- /dev/null +++ b/src/sage/combinat/recognizable_series.py @@ -0,0 +1,1287 @@ +r""" +Recognizable Series + +Let `A` be an alphabet and `K` a semiring. Then a formal series `S` +with coefficients in `K` and indices in the words `A^*` is called +recognizable if it has a linear representation, i.e., there exists + +- a nonnegative integer `n` + +and there exist + +- two vectors `\mathit{left}` and `\mathit{right}` of dimension `n` and + +- a morphism of monoids `\mu` from `A^*` to `n\times n` matrices over `K` + +such that the coefficient corresponding to a word `w\in A^*` equals + +.. MATH:: + + \mathit{left} \, \mu(w) \, \mathit{right}. + +.. NOTE:: + + Whenever a minimization (:meth:`~RecognizableSeries.minimized`) of + a series needs to be computed, it is required that `K` is a field. + In particular, minimization is called before checking if a series is + nonzero. + +.. WARNING:: + + As this code is experimental, warnings are thrown when a + recognizable series space is created for the first time in a + session (see :class:`sage.misc.superseded.experimental`). + + TESTS:: + + sage: Rec = RecognizableSeriesSpace(ZZ, [0, 1]) + doctest:...: FutureWarning: This class/method/function is + marked as experimental. It, its functionality or its interface + might change without a formal deprecation. + See http://trac.sagemath.org/21202 for details. + + +Various +======= + +AUTHORS: + +- Daniel Krenn (2016) + +ACKNOWLEDGEMENT: + +- Daniel Krenn is supported by the + Austrian Science Fund (FWF): P 24644-N26. + + +Classes and Methods +=================== +""" +#***************************************************************************** +# Copyright (C) 2016 Daniel Krenn +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** +from __future__ import absolute_import + +from sage.misc.cachefunc import cached_method +from sage.misc.superseded import experimental +from sage.structure.element import Element +from sage.structure.parent import Parent +from sage.structure.unique_representation import UniqueRepresentation + + +class PrefixClosedSet(object): + def __init__(self, words): + r""" + A prefix-closed set. + + Creation of this prefix-closed set is interactive + iteratively. + + INPUT: + + - ``words`` -- a class of words + (instance of :class:`~sage.combinat.words.words.Words`) + + EXAMPLES:: + + sage: from sage.combinat.recognizable_series import PrefixClosedSet + sage: P = PrefixClosedSet(Words([0, 1], infinite=False)); P + [word: ] + + sage: P = PrefixClosedSet.create_by_alphabet([0, 1]); P + [word: ] + + See :meth:`iterate_possible_additions` for further examples. + """ + self.words = words + self.elements = [self.words([])] + + @classmethod + def create_by_alphabet(cls, alphabet): + r""" + A prefix-closed set + + This is a convenience method for the + creation of prefix-closed sets by specifying an alphabet. + + INPUT: + + - ``alphabet`` -- finite words over this ``alphabet`` + will used + + EXAMPLES:: + + sage: from sage.combinat.recognizable_series import PrefixClosedSet + sage: P = PrefixClosedSet.create_by_alphabet([0, 1]); P + [word: ] + """ + from sage.combinat.words.words import Words + return cls(Words(alphabet, infinite=False)) + + def __repr__(self): + r""" + A representation string of this prefix-closed set + + OUTPUT: + + A string + + EXAMPLES:: + + sage: from sage.combinat.recognizable_series import PrefixClosedSet + sage: P = PrefixClosedSet.create_by_alphabet([0, 1]) + sage: repr(P) # indirect doctest + '[word: ]' + """ + return repr(self.elements) + + def add(self, w, check=True): + r""" + Add a word to this prefix-closed set. + + INPUT: + + - ``w`` -- a word + + - ``check`` -- boolean (default: ``True``). If set, then it is verified + whether all proper prefixes of ``w`` are already in this + prefix-closed set. + + OUTPUT: + + Nothing, but a + :python:`RuntimeError` + is raised if the check fails. + + EXAMPLES:: + + sage: from sage.combinat.recognizable_series import PrefixClosedSet + sage: P = PrefixClosedSet.create_by_alphabet([0, 1]) + sage: W = P.words + sage: P.add(W([0])); P + [word: , word: 0] + sage: P.add(W([0, 1])); P + [word: , word: 0, word: 01] + sage: P.add(W([1, 1])) + Traceback (most recent call last): + ... + ValueError: Cannot add as not all prefixes of 11 are included yet. + """ + if check and any(p not in self.elements + for p in w.prefixes_iterator() + if p != w): + raise ValueError('Cannot add as not all prefixes of ' + '{} are included yet.'.format(w)) + self.elements.append(w) + + def iterate_possible_additions(self): + r""" + Return an iterator over all elements including possible new elements. + + OUTPUT: + + An iterator + + EXAMPLES:: + + sage: from sage.combinat.recognizable_series import PrefixClosedSet + sage: P = PrefixClosedSet.create_by_alphabet([0, 1]); P + [word: ] + sage: for n, p in enumerate(P.iterate_possible_additions()): + ....: print('{}?'.format(p)) + ....: if n in (0, 2, 3, 5): + ....: P.add(p) + ....: print('...added') + 0? + ...added + 1? + 00? + ...added + 01? + ...added + 000? + 001? + ...added + 010? + 011? + 0010? + 0011? + sage: P.elements + [word: , word: 0, word: 00, word: 01, word: 001] + + Calling the iterator once more, returns all elements:: + + sage: list(P.iterate_possible_additions()) + [word: 0, + word: 1, + word: 00, + word: 01, + word: 000, + word: 001, + word: 010, + word: 011, + word: 0010, + word: 0011] + + The method :meth:`iterate_possible_additions` is roughly equivalent to + :: + + sage: list(p + a + ....: for p in P.elements + ....: for a in P.words.iterate_by_length(1)) + [word: 0, + word: 1, + word: 00, + word: 01, + word: 000, + word: 001, + word: 010, + word: 011, + word: 0010, + word: 0011] + + However, the above does not allow to add elements during iteration, + whereas :meth:`iterate_possible_additions` does. + """ + n = 0 + it = self.words.iterate_by_length(1) + while n < len(self.elements): + try: + nn = next(it) + yield self.elements[n] + nn #next(it) + except StopIteration: + n += 1 + it = self.words.iterate_by_length(1) + + def prefix_set(self): + r""" + Return the set of minimal (with respect to prefix ordering) elements + of the complement of this prefix closed set. + + See also Proposition 2.3.1 of [BR2010a]_. + + OUTPUT: + + A list + + EXAMPLES:: + + sage: from sage.combinat.recognizable_series import PrefixClosedSet + sage: P = PrefixClosedSet.create_by_alphabet([0, 1]); P + [word: ] + sage: for n, p in enumerate(P.iterate_possible_additions()): + ....: if n in (0, 1, 2, 4, 6): + ....: P.add(p) + sage: P + [word: , word: 0, word: 1, word: 00, word: 10, word: 000] + sage: P.prefix_set() + [word: 01, word: 11, word: 001, word: 100, + word: 101, word: 0000, word: 0001] + """ + return [p + a + for p in self.elements + for a in self.words.iterate_by_length(1) + if p + a not in self.elements] + + +class RecognizableSeries(Element): + def __init__(self, parent, mu, left, right): + r""" + A recognizable series. + + - ``parent`` -- an instance of :class:`RecognizableSeriesSpace` + + - ``mu`` -- a family of square matrices, all of which have the + same dimension. + The indices of this family are the elements of the alphabet. + ``mu`` may be a list or tuple of the same cardinality as the + alphabet as well. See also :meth:`mu `. + + - ``left`` -- a vector. When evaluating a + coefficient, this vector is multiplied from the left to the + matrix obtained from :meth:`mu ` applying on a word. + See also :meth:`left `. + + - ``right`` -- a vector. When evaluating a + coefficient, this vector is multiplied from the right to the + matrix obtained from :meth:`mu ` applying on a word. + See also :meth:`right `. + + When created via the parent :class:`RecognizableSeriesSpace`, then + the following option is available. + + EXAMPLES:: + + sage: Rec = RecognizableSeriesSpace(ZZ, [0, 1]) + sage: S = Rec((Matrix([[3, 6], [0, 1]]), Matrix([[0, -6], [1, 5]])), + ....: vector([0, 1]), vector([1, 0])).transposed(); S + [1] + 3*[01] + [10] + 5*[11] + 9*[001] + 3*[010] + ... + + We can access coefficients by + :: + + sage: W = Rec.indices() + sage: S[W([0, 0, 1])] + 9 + + .. SEEALSO:: + + :doc:`recognizable series `, + :class:`RecognizableSeriesSpace`. + + TESTS:: + + sage: Rec = RecognizableSeriesSpace(ZZ, (0,1)) + sage: M0 = Matrix([[1, 0], [0, 1]]) + sage: M1 = Matrix([[0, -1], [1, 2]]) + sage: Rec((M0, M1), (0, 1), (1, 1)) + [] + [0] + 3*[1] + [00] + 3*[01] + 3*[10] + 5*[11] + [000] + 3*[001] + 3*[010] + ... + """ + super(RecognizableSeries, self).__init__(parent=parent) + + from sage.modules.free_module_element import vector + from sage.sets.family import Family + + A = self.parent().alphabet() + if isinstance(mu, (list, tuple)): + mu = dict(zip(A, mu)) + mu = Family(mu) + if not mu.is_finite(): + raise NotImplementedError('mu is not a finite family of matrices.') + + self._left_ = vector(left) + self._mu_ = mu + self._right_ = vector(right) + + @property + def mu(self): + r""" + When evaluating a coefficient, this is applied on each letter + of a word; the result is a matrix. + This extends :meth:`mu ` to words over the parent's + :meth:`~RecognizableSeriesSpace.alphabet`. + + TESTS:: + + sage: Rec = RecognizableSeriesSpace(ZZ, [0, 1]) + sage: M0 = Matrix([[1, 0], [0, 1]]) + sage: M1 = Matrix([[0, -1], [1, 2]]) + sage: S = Rec((M0, M1), [0, 1], [1, 1]) + sage: S.mu[0] == M0 and S.mu[1] == M1 + True + """ + return self._mu_ + + @property + def left(self): + r""" + When evaluating a coefficient, this vector is multiplied from + the left to the matrix obtained from :meth:`mu ` applied on a + word. + + TESTS:: + + sage: Rec = RecognizableSeriesSpace(ZZ, [0, 1]) + sage: Rec((Matrix([[3, 6], [0, 1]]), Matrix([[0, -6], [1, 5]])), + ....: vector([0, 1]), vector([1, 0])).transposed().left + (1, 0) + """ + return self._left_ + + @property + def right(self): + r""" + When evaluating a coefficient, this vector is multiplied from + the right to the matrix obtained from :meth:`mu ` applied on a + word. + + TESTS:: + + sage: Rec = RecognizableSeriesSpace(ZZ, [0, 1]) + sage: Rec((Matrix([[3, 6], [0, 1]]), Matrix([[0, -6], [1, 5]])), + ....: vector([0, 1]), vector([1, 0])).transposed().right + (0, 1) + """ + return self._right_ + + def linear_representation(self): + r""" + Return the linear representation of this series. + + OUTPUT: + + A triple ``(left, mu, right)`` containing + the vectors :meth:`left ` and :meth:`right `, + and the family of matrices :meth:`mu `. + + EXAMPLES:: + + sage: Rec = RecognizableSeriesSpace(ZZ, [0, 1]) + sage: Rec((Matrix([[3, 6], [0, 1]]), Matrix([[0, -6], [1, 5]])), + ....: vector([0, 1]), vector([1, 0]) + ....: ).transposed().linear_representation() + ((1, 0), + Finite family {0: [3 0] + [6 1], + 1: [ 0 1] + [-6 5]}, + (0, 1)) + """ + return (self.left, self.mu, self.right) + + def _repr_(self, latex=False): + r""" + A representation string for this recognizable series. + + INPUT: + + - ``latex`` -- (default: ``False``) a boolean. If set, then + LaTeX-output is returned. + + OUTPUT: + + A string + + EXAMPLES:: + + sage: Rec = RecognizableSeriesSpace(ZZ, [0, 1]) + sage: S = Rec((Matrix([[3, 6], [0, 1]]), Matrix([[0, -6], [1, 5]])), + ....: vector([0, 1]), vector([1, 0])).transposed() + sage: repr(S) # indirect doctest + '[1] + 3*[01] + [10] + 5*[11] + 9*[001] + 3*[010] + ...' + + TESTS:: + + sage: S = Rec((Matrix([[0]]), Matrix([[0]])), + ....: vector([1]), vector([1])) + sage: repr(S) # indirect doctest + '[] + ...' + + sage: S = Rec((Matrix([[0, 1], [0, 0]]), Matrix([[0, 0], [0, 0]])), + ....: vector([0, 1]), vector([1, 0])) + sage: repr(S) # indirect doctest + '0 + ...' + """ + if self.is_trivial_zero(): + return '0' + + from itertools import islice + + if latex: + from sage.misc.latex import latex as latex_repr + fr = latex_repr + fs = latex_repr + times = ' ' + else: + fr = repr + fs = str + times = '*' + + def summand(w, c): + if c == 1: + return '[{w}]'.format(w=fs(w)) + return '{c}{times}[{w}]'.format(c=fr(c), times=times, w=fs(w)) + + def all_coefficients(): + number_of_zeros = 0 + for w in self.parent().indices(): + c = self[w] + if c != 0: + number_of_zeros = 0 + yield (w, self[w]) + else: + number_of_zeros += 1 + if number_of_zeros >= 100: + return + + coefficients = islice(all_coefficients(), 10) + + s = ' + '.join(summand(w, c) + for w, c in coefficients) + s = s.replace('+ -', '- ') + if not s: + s = '0' + return s + ' + ...' + + def _latex_(self): + r""" + A LaTeX-representation string for this recognizable series. + + OUTPUT: + + A string + + TESTS:: + + sage: Rec = RecognizableSeriesSpace(ZZ, [0, 1]) + sage: S = Rec((Matrix([[3, 6], [0, 1]]), Matrix([[0, -6], [1, 5]])), + ....: vector([0, 1]), vector([1, 0])).transposed() + sage: latex(S) # indirect doctest + [1] + 3 [01] + [10] + 5 [11] + 9 [001] + 3 [010] + + 15 [011] + [100] + 11 [101] + 5 [110] + ... + """ + return self._repr_(latex=True) + + @cached_method + def __getitem__(self, w): + r""" + Return the coefficient to word `w` of this series. + + INPUT: + + - ``w`` -- a word over the parent's + :meth:`~RecognizableSeriesSpace.alphabet` + + OUTPUT: + + An element in the parent's + :meth:`~RecognizableSeriesSpace.coefficient_ring` + + EXAMPLES:: + + sage: Rec = RecognizableSeriesSpace(ZZ, [0, 1]) + sage: W = Rec.indices() + sage: S = Rec((Matrix([[1, 0], [0, 1]]), Matrix([[0, -1], [1, 2]])), + ....: left=vector([0, 1]), right=vector([1, 0])) + sage: S[W(7.digits(2))] + 3 + """ + return self.left * self._mu_of_word_(w) * self.right + + @cached_method + def _mu_of_empty_word_(self): + r""" + Return :meth:`mu ` applied on the empty word. + + OUTPUT: + + A matrix + + TESTS:: + + sage: Rec = RecognizableSeriesSpace(ZZ, [0, 1]) + sage: W = Rec.indices() + sage: M0 = Matrix([[1, 0], [0, 1]]) + sage: M1 = Matrix([[0, -1], [1, 2]]) + sage: S = Rec({W([0]): M0, W([1]): M1}, [0, 1], [1, 1]) + sage: S._mu_of_empty_word_() + [1 0] + [0 1] + sage: I = Matrix([[1, 0], [0, 1]]) + sage: T = Rec({W([]): I, W([0]): M0, W([1]): M1}, [0, 1], [1, 1]) + sage: T._mu_of_empty_word_() + [1 0] + [0 1] + sage: _ is I + True + """ + eps = self.parent().indices()() + try: + return self.mu[eps] + except KeyError: + return next(iter(self.mu)).parent().one() + + @cached_method + def _mu_of_word_(self, w): + r""" + Return :meth:`mu ` applied on the word `w`. + + INPUT: + + - ``w`` -- a word over the parent's + :meth:`~RecognizableSeriesSpace.alphabet` + + OUTPUT: + + A matrix + + TESTS:: + + sage: Rec = RecognizableSeriesSpace(ZZ, [0, 1]) + sage: W = Rec.indices() + sage: M0 = Matrix([[1, 0], [0, 1]]) + sage: M1 = Matrix([[0, -1], [1, 2]]) + sage: S = Rec((M0, M1), [0, 1], [1, 1]) + sage: S._mu_of_word_(W([0])) == M0 + True + sage: S._mu_of_word_(W([1])) == M1 + True + sage: S._mu_of_word_(W(3.digits(2))) == M1^2 + True + + :: + + sage: S._mu_of_word_(-1) + Traceback (most recent call last): + ... + ValueError: Index -1 is not in Finite words over {0, 1}. + """ + W = self.parent().indices() + if w not in W: + raise ValueError('Index {} is not in {}.'.format(w, W)) + from sage.misc.misc_c import prod + return prod((self.mu[a] for a in w), z=self._mu_of_empty_word_()) + + def __iter__(self): + r""" + Return an iterator over pairs ``(index, coefficient)``. + + EXAMPLES:: + + sage: Rec = RecognizableSeriesSpace(ZZ, [0, 1]) + sage: S = Rec((Matrix([[1, 0], [0, 1]]), Matrix([[0, -1], [1, 2]])), + ....: left=vector([0, 1]), right=vector([1, 0])) + sage: from itertools import islice + sage: list(islice(S, 10)) + [(word: , 0), + (word: 0, 0), + (word: 1, 1), + (word: 00, 0), + (word: 01, 1), + (word: 10, 1), + (word: 11, 2), + (word: 000, 0), + (word: 001, 1), + (word: 010, 1)] + sage: list(islice((s for s in S if s[1] != 0), 10)) + [(word: 1, 1), + (word: 01, 1), + (word: 10, 1), + (word: 11, 2), + (word: 001, 1), + (word: 010, 1), + (word: 011, 2), + (word: 100, 1), + (word: 101, 2), + (word: 110, 2)] + + sage: S = Rec((Matrix([[1, 0], [0, 1]]), Matrix([[0, -1], [1, 2]])), + ....: left=vector([1, 0]), right=vector([1, 0])) + sage: list(islice((s for s in S if s[1] != 0), 10)) + [(word: , 1), + (word: 0, 1), + (word: 00, 1), + (word: 11, -1), + (word: 000, 1), + (word: 011, -1), + (word: 101, -1), + (word: 110, -1), + (word: 111, -2), + (word: 0000, 1)] + + TESTS:: + + sage: it = iter(S) + sage: iter(it) is it + True + sage: iter(S) is not it + True + """ + return iter((w, self[w]) for w in self.parent().indices()) + + def is_trivial_zero(self): + r""" + Return whether this recognizable series is trivially equal to + zero (without any :meth:`minimization `). + + EXAMPLES:: + + sage: Rec = RecognizableSeriesSpace(ZZ, [0, 1]) + sage: Rec((Matrix([[1, 0], [0, 1]]), Matrix([[1, 0], [0, 1]])), + ....: left=vector([0, 1]), right=vector([1, 0])).is_trivial_zero() + False + sage: Rec((Matrix([[1, 0], [0, 1]]), Matrix([[1, 0], [0, 1]])), + ....: left=vector([0, 0]), right=vector([1, 0])).is_trivial_zero() + True + sage: Rec((Matrix([[1, 0], [0, 1]]), Matrix([[1, 0], [0, 1]])), + ....: left=vector([0, 1]), right=vector([0, 0])).is_trivial_zero() + True + + The following two differ in the coefficient of the empty word:: + + sage: Rec((Matrix([[0, 0], [0, 0]]), Matrix([[0, 0], [0, 0]])), + ....: left=vector([0, 1]), right=vector([1, 0])).is_trivial_zero() + True + sage: Rec((Matrix([[0, 0], [0, 0]]), Matrix([[0, 0], [0, 0]])), + ....: left=vector([1, 1]), right=vector([1, 1])).is_trivial_zero() + False + + TESTS:: + + sage: Rec.zero().is_trivial_zero() + True + + The following is zero, but not trivially zero:: + + sage: S = Rec((Matrix([[1, 0], [0, 0]]), Matrix([[1, 0], [0, 0]])), + ....: left=vector([0, 1]), right=vector([1, 0])) + sage: S.is_trivial_zero() + False + sage: S.is_zero() + True + """ + return not self.left or not self.right or \ + (all(not self.mu[a] for a in self.parent().alphabet()) and + not self[self.parent().indices()()]) + + def __bool__(self): + r""" + Return whether this recognizable series is nonzero. + + TESTS:: + + sage: Rec = RecognizableSeriesSpace(ZZ, [0, 1]) + sage: bool(Rec((Matrix([[1, 0], [0, 1]]), Matrix([[1, 0], [0, 1]])), + ....: left=vector([0, 1]), right=vector([1, 0]))) + False + sage: bool(Rec((Matrix([[0, 0], [0, 0]]), Matrix([[0, 0], [0, 0]])), + ....: left=vector([0, 1]), right=vector([1, 0]))) + False + sage: bool(Rec((Matrix([[1, 0], [0, 1]]), Matrix([[1, 0], [0, 1]])), + ....: left=vector([0, 0]), right=vector([1, 0]))) + False + sage: bool(Rec((Matrix([[1, 0], [0, 1]]), Matrix([[1, 0], [0, 1]])), + ....: left=vector([0, 1]), right=vector([0, 0]))) + False + + :: + + sage: S = Rec((Matrix([[1, 0], [0, 0]]), Matrix([[1, 0], [0, 0]])), + ....: left=vector([0, 1]), right=vector([1, 0])) + sage: bool(S) + False + """ + if self.is_trivial_zero(): + return False + try: + M = self.minimized() + except ValueError: + pass + else: + if M.is_trivial_zero(): + return False + return True + + __nonzero__ = __bool__ + + def transposed(self): + r""" + Return the transposed series. + + OUTPUT: + + A :class:`RecognizableSeries` + + Each of the matrices in :meth:`mu ` is transposed. Additionally + the vectors :meth:`left ` and :meth:`right ` are switched. + + EXAMPLES:: + + sage: Rec = RecognizableSeriesSpace(ZZ, [0, 1]) + sage: S = Rec((Matrix([[3, 6], [0, 1]]), Matrix([[0, -6], [1, 5]])), + ....: vector([0, 1]), vector([1, 0])).transposed() + sage: S + [1] + 3*[01] + [10] + 5*[11] + 9*[001] + 3*[010] + + 15*[011] + [100] + 11*[101] + 5*[110] + ... + sage: S.mu[0], S.mu[1], S.left, S.right + ( + [3 0] [ 0 1] + [6 1], [-6 5], (1, 0), (0, 1) + ) + sage: T = S.transposed() + sage: T + [1] + [01] + 3*[10] + 5*[11] + [001] + 3*[010] + + 5*[011] + 9*[100] + 11*[101] + 15*[110] + ... + sage: T.mu[0], T.mu[1], T.left, T.right + ( + [3 6] [ 0 -6] + [0 1], [ 1 5], (0, 1), (1, 0) + ) + """ + return self.parent()(self.mu.map(lambda M: M.transpose()), + left=self.right, + right=self.left) + + @cached_method + def minimized(self): + r""" + Return a recognizable series equivalent to this series, but + with a minimized linear representation. + + The coefficients of the involved matrices need be in a field. + If this is not the case, then the coefficients are + automatically coerced to their fraction field. + + OUTPUT: + + A :class:`RecognizableSeries` + + ALOGRITHM: + + This method implements the minimization algorithm presented in + Chapter 2 of [BR2010a]_. + + EXAMPLES:: + + sage: from itertools import islice + sage: from six.moves import zip + sage: Rec = RecognizableSeriesSpace(ZZ, [0, 1]) + + sage: S = Rec((Matrix([[3, 6], [0, 1]]), Matrix([[0, -6], [1, 5]])), + ....: vector([0, 1]), vector([1, 0])).transposed() + sage: S + [1] + 3*[01] + [10] + 5*[11] + 9*[001] + 3*[010] + + 15*[011] + [100] + 11*[101] + 5*[110] + ... + sage: M = S.minimized() + sage: M.mu[0], M.mu[1], M.left, M.right + ( + [3 0] [ 0 1] + [6 1], [-6 5], (1, 0), (0, 1) + ) + sage: all(c == d and v == w + ....: for (c, v), (d, w) in islice(zip(iter(S), iter(M)), 20)) + True + + sage: S = Rec((Matrix([[2, 0], [1, 1]]), Matrix([[2, 0], [2, 1]])), + ....: vector([1, 0]), vector([1, 1])) + sage: S + [] + 2*[0] + 2*[1] + 4*[00] + 4*[01] + 4*[10] + 4*[11] + + 8*[000] + 8*[001] + 8*[010] + ... + sage: M = S.minimized() + sage: M.mu[0], M.mu[1], M.left, M.right + ([2], [2], (1), (1)) + sage: all(c == d and v == w + ....: for (c, v), (d, w) in islice(zip(iter(S), iter(M)), 20)) + True + """ + return self._minimized_right_()._minimized_left_() + + def _minimized_right_(self): + r""" + Return a recognizable series equivalent to this series, but + with a right minimized linear representation. + + OUTPUT: + + A :class:`RecognizableSeries` + + See :meth:`minimized` for details. + + TESTS:: + + sage: Rec = RecognizableSeriesSpace(ZZ, [0, 1]) + sage: S = Rec((Matrix([[0, 0], [0, 0]]), Matrix([[0, 0], [0, 0]])), + ....: vector([1, 1]), vector([1, 1])) + sage: M = S._minimized_right_() + sage: M.mu[0], M.mu[1], M.left, M.right + ([0], [0], (2), (1)) + """ + return self.transposed()._minimized_left_().transposed() + + def _minimized_left_(self): + r""" + Return a recognizable series equivalent to this series, but + with a left minimized linear representation. + + OUTPUT: + + A :class:`RecognizableSeries` + + See :meth:`minimized` for details. + + TESTS:: + + sage: Rec = RecognizableSeriesSpace(ZZ, [0, 1]) + sage: S = Rec((Matrix([[0, 0], [0, 0]]), Matrix([[0, 0], [0, 0]])), + ....: vector([1, 1]), vector([1, 1])) + sage: M = S._minimized_left_() + sage: M.mu[0], M.mu[1], M.left, M.right + ([0], [0], (1), (2)) + sage: M = S.minimized() + sage: M.mu[0], M.mu[1], M.left, M.right + ([0], [0], (1), (2)) + + :: + + sage: S = Rec((Matrix([[1, 0], [0, 1]]), Matrix([[1, 0], [0, 1]])), + ....: vector([1, -1]), vector([1, 1]))._minimized_left_() + sage: S.mu[0], S.mu[1], S.left, S.right + ([1], [1], (1), (0)) + sage: M = S.minimized() + sage: M.mu[0], M.mu[1], M.left, M.right + ([], [], (), ()) + + sage: S = Rec((Matrix([[1, 0], [0, 1]]), Matrix([[1, 0], [0, 1]])), + ....: vector([1, 1]), vector([1, -1])) + sage: M = S._minimized_left_() + sage: M.mu[0], M.mu[1], M.left, M.right + ([1], [1], (1), (0)) + sage: M = S.minimized() + sage: M.mu[0], M.mu[1], M.left, M.right + ([], [], (), ()) + + sage: S = Rec((Matrix([[1, 0], [0, 1]]), Matrix([[1, 0], [0, 1]])), + ....: left=vector([0, 1]), right=vector([1, 0])) + sage: M = S._minimized_left_() + sage: M.mu[0], M.mu[1], M.left, M.right + ([1], [1], (1), (0)) + sage: M = S.minimized() + sage: M.mu[0], M.mu[1], M.left, M.right + ([], [], (), ()) + """ + from sage.matrix.constructor import Matrix + from sage.modules.free_module_element import vector + from sage.rings.integer_ring import ZZ + + pcs = PrefixClosedSet(self.parent().indices()) + left = self.left * self._mu_of_word_(pcs.elements[0]) + if left.is_zero(): + return self.parent().zero() + Left = [left] + for p in pcs.iterate_possible_additions(): + left = self.left * self._mu_of_word_(p) + try: + Matrix(Left).solve_left(left) + except ValueError: + # no solution found + pcs.add(p) + Left.append(left) + P = pcs.elements + C = pcs.prefix_set() + + ML = Matrix(Left) + + def alpha(c): + return ML.solve_left(self.left * self._mu_of_word_(c)) + + mu_prime = [] + for a in self.parent().alphabet(): + a = self.parent().indices()([a]) + M = Matrix([alpha(c) if c in C else tuple(ZZ(c==q) for q in P) + for c in (p + a for p in P)]) + mu_prime.append(M) + + left_prime = vector([ZZ(1)] + (len(P)-1)*[ZZ(0)]) + right_prime = vector(self[p] for p in P) + + return self.parent().element_class( + self.parent(), mu_prime, left_prime, right_prime) + + +class RecognizableSeriesSpace(UniqueRepresentation, Parent): + r""" + The space of recognizable series on the given alphabet and + with the given coefficients. + + INPUT: + + - ``coefficient_ring`` -- a (semi-)ring + + - ``alphabet`` -- a tuple, list or + :class:`~sage.sets.totally_ordered_finite_set.TotallyOrderedFiniteSet`. + If specified, then the ``indices`` are the + finite words over this ``alphabet``. + ``alphabet`` and ``indices`` cannot be specified + at the same time. + + - ``indices`` -- a SageMath-parent of finite words over an alphabet. + ``alphabet`` and ``indices`` cannot be specified + at the same time. + + - ``category`` -- (default: ``None``) the category of this + space + + EXAMPLES: + + We create a recognizable series that counts the number of ones in each word:: + + sage: Rec = RecognizableSeriesSpace(ZZ, [0, 1]) + sage: Rec + Space of recognizable series on {0, 1} with coefficients in Integer Ring + sage: Rec((Matrix([[1, 0], [0, 1]]), Matrix([[1, 1], [0, 1]])), + ....: vector([1, 0]), vector([0, 1])) + [1] + [01] + [10] + 2*[11] + [001] + [010] + 2*[011] + [100] + 2*[101] + 2*[110] + ... + + All of the following examples create the same space:: + + sage: Rec1 = RecognizableSeriesSpace(ZZ, [0, 1]) + sage: Rec1 + Space of recognizable series on {0, 1} with coefficients in Integer Ring + sage: Rec2 = RecognizableSeriesSpace(coefficient_ring=ZZ, alphabet=[0, 1]) + sage: Rec2 + Space of recognizable series on {0, 1} with coefficients in Integer Ring + sage: Rec3 = RecognizableSeriesSpace(ZZ, indices=Words([0, 1], infinite=False)) + sage: Rec3 + Space of recognizable series on {0, 1} with coefficients in Integer Ring + + .. SEEALSO:: + + :doc:`recognizable series `, + :class:`RecognizableSeries`. + """ + Element = RecognizableSeries + + @staticmethod + def __classcall__(cls, *args, **kwds): + r""" + Prepare normalizing the input in order to ensure a + unique representation. + + For more information see :class:`RecognizableSeriesSpace` + and :meth:`__normalize__`. + + TESTS:: + + sage: Rec1 = RecognizableSeriesSpace(ZZ, [0, 1]) + sage: Rec1 + Space of recognizable series on {0, 1} with coefficients in Integer Ring + sage: Rec2 = RecognizableSeriesSpace(coefficient_ring=ZZ, alphabet=[0, 1]) + sage: Rec2 + Space of recognizable series on {0, 1} with coefficients in Integer Ring + sage: Rec3 = RecognizableSeriesSpace(ZZ, indices=Words([0, 1], infinite=False)) + sage: Rec3 + Space of recognizable series on {0, 1} with coefficients in Integer Ring + sage: Rec1 is Rec2 is Rec3 + True + """ + return super(RecognizableSeriesSpace, cls).__classcall__( + cls, *cls.__normalize__(*args, **kwds)) + + @classmethod + def __normalize__(cls, + coefficient_ring=None, + alphabet=None, indices=None, + category=None): + r""" + Normalizes the input in order to ensure a unique + representation. + + For more information see :class:`RecognizableSeriesSpace`. + + TESTS:: + + sage: Rec = RecognizableSeriesSpace(ZZ, [0, 1]) # indirect doctest + sage: Rec.category() + Category of sets + sage: RecognizableSeriesSpace([0, 1], [0, 1]) + Traceback (most recent call last): + ... + ValueError: Coefficient ring [0, 1] is not a semiring. + + :: + + sage: W = Words([0, 1], infinite=False) + sage: RecognizableSeriesSpace(ZZ) + Traceback (most recent call last): + ... + ValueError: Specify either 'alphabet' or 'indices'. + sage: RecognizableSeriesSpace(ZZ, alphabet=[0, 1], indices=W) + Traceback (most recent call last): + ... + ValueError: Specify either 'alphabet' or 'indices'. + sage: RecognizableSeriesSpace(alphabet=[0, 1]) + Traceback (most recent call last): + ... + ValueError: No coefficient ring specified. + sage: RecognizableSeriesSpace(ZZ, indices=Words(ZZ)) + Traceback (most recent call last): + ... + NotImplementedError: Alphabet is not finite. + """ + if (alphabet is None) == (indices is None): + raise ValueError("Specify either 'alphabet' or 'indices'.") + + if indices is None: + from sage.combinat.words.words import Words + indices = Words(alphabet, infinite=False) + if not indices.alphabet().is_finite(): + raise NotImplementedError('Alphabet is not finite.') + + if coefficient_ring is None: + raise ValueError('No coefficient ring specified.') + from sage.categories.semirings import Semirings + if coefficient_ring not in Semirings: + raise ValueError( + 'Coefficient ring {} is not a semiring.'.format(coefficient_ring)) + + from sage.categories.sets_cat import Sets + category = category or Sets() + + return (coefficient_ring, indices, category) + + @experimental(trac_number=21202) + def __init__(self, coefficient_ring, indices, category): + r""" + See :class:`RecognizableSeriesSpace` for details. + + INPUT: + + - ``coefficients`` -- a (semi-)ring + + - ``indices`` -- a SageMath-parent of finite words over an alphabet + + - ``category`` -- (default: ``None``) the category of this + space + + TESTS:: + + sage: RecognizableSeriesSpace(ZZ, [0, 1]) + Space of recognizable series on {0, 1} with coefficients in Integer Ring + """ + self._indices_ = indices + super(RecognizableSeriesSpace, self).__init__( + category=category, base=coefficient_ring) + + def alphabet(self): + r""" + Return the alphabet of this recognizable series space. + + OUTPUT: + + A totally ordered set + + EXAMPLES:: + + sage: RecognizableSeriesSpace(ZZ, [0, 1]).alphabet() + {0, 1} + + TESTS:: + + sage: type(RecognizableSeriesSpace(ZZ, [0, 1]).alphabet()) + + """ + return self.indices().alphabet() + + def indices(self): + r""" + Return the indices of the recognizable series. + + OUTPUT: + + The set of finite words over the alphabet + + EXAMPLES:: + + sage: RecognizableSeriesSpace(ZZ, [0, 1]).indices() + Finite words over {0, 1} + """ + return self._indices_ + + def coefficient_ring(self): + r""" + Return the coefficients of this recognizable series space. + + OUTPUT: + + A (semi-)ring + + EXAMPLES:: + + sage: RecognizableSeriesSpace(ZZ, [0, 1]).coefficient_ring() + Integer Ring + """ + return self.base() + + def _repr_(self): + r""" + Return a representation string of this recognizable sequence + space. + + OUTPUT: + + A string + + TESTS:: + + sage: repr(RecognizableSeriesSpace(ZZ, [0, 1])) # indirect doctest + 'Space of recognizable series on {0, 1} with coefficients in Integer Ring' + """ + return 'Space of recognizable series on {} ' \ + 'with coefficients in {}'.format(self.alphabet(), + self.coefficient_ring()) + + def zero(self): + """ + Return the zero of this recognizable series space. + + This can be removed once this recognizable series space is + at least an additive magma. + + EXAMPLES:: + + sage: Rec = RecognizableSeriesSpace(ZZ, [0, 1]) + sage: Rec.zero() + 0 + """ + return self(0) + + def _element_constructor_(self, data, + left=None, right=None): + r""" + Return a recognizable series. + + See :class:`RecognizableSeriesSpace` for details. + + TESTS:: + + sage: Rec = RecognizableSeriesSpace(ZZ, [0, 1]) + + sage: Rec.zero() + 0 + sage: type(_) + + + :: + + sage: M0 = Matrix([[1, 0], [0, 1]]) + sage: M1 = Matrix([[0, -1], [1, 2]]) + sage: S = Rec((M0, M1), [0, 1], [1, 1]) + sage: Rec(S) is S + True + + sage: Rec((M0, M1)) + Traceback (most recent call last): + ... + ValueError: Left or right vector is None. + sage: Rec((M0, M1), [0, 1]) + Traceback (most recent call last): + ... + ValueError: Left or right vector is None. + sage: Rec((M0, M1), left=[0, 1]) + Traceback (most recent call last): + ... + ValueError: Left or right vector is None. + sage: Rec((M0, M1), right=[0, 1]) + Traceback (most recent call last): + ... + ValueError: Left or right vector is None. + """ + if isinstance(data, int) and data == 0: + from sage.matrix.constructor import Matrix + from sage.modules.free_module_element import vector + from sage.sets.family import Family + + return self.element_class( + self, Family(self.alphabet(), lambda a: Matrix()), + vector([]), vector([])) + + if type(data) == self.element_class and data.parent() == self: + element = data + + elif isinstance(data, RecognizableSeries): + element = self.element_class(self, data.mu, data.left, data.right) + + else: + mu = data + if left is None or right is None: + raise ValueError('Left or right vector is None.') + + element = self.element_class(self, mu, left, right) + + return element diff --git a/src/sage/combinat/root_system/root_lattice_realizations.py b/src/sage/combinat/root_system/root_lattice_realizations.py index 77c278c6aba..1eaf908e63c 100644 --- a/src/sage/combinat/root_system/root_lattice_realizations.py +++ b/src/sage/combinat/root_system/root_lattice_realizations.py @@ -1262,13 +1262,14 @@ def simple_coroots(self): # break some doctests return self.cache_simple_coroots + @cached_method def alphacheck(self): r""" - Returns the family `( \alpha^\vee_i)_{i\in I}` of the simple - coroots, with the extra feature that, for simple irreducible + Return the family `(\alpha^\vee_i)_{i \in I}` of the simple + coroots, with the extra feature that, for simple irreducible root systems, `\alpha^\vee_0` yields the coroot associated to - the opposite of the highest root (caveat: for non simply laced - root systems, this is not the opposite of the highest coroot!) + the opposite of the highest root (caveat: for non-simply-laced + root systems, this is not the opposite of the highest coroot!). EXAMPLES:: @@ -1292,8 +1293,8 @@ def alphacheck(self): """ if self.root_system.is_finite() and self.root_system.is_irreducible(): - return Family(self.index_set(), self.simple_coroot, \ - hidden_keys = [0], hidden_function = lambda i: - self.cohighest_root()) + return Family(self.index_set(), self.simple_coroot, + hidden_keys=[0], hidden_function=lambda i: - self.cohighest_root()) else: return self.simple_coroots() diff --git a/src/sage/combinat/subset.py b/src/sage/combinat/subset.py index 9500636f508..057e5a36a56 100644 --- a/src/sage/combinat/subset.py +++ b/src/sage/combinat/subset.py @@ -353,17 +353,10 @@ def cardinality(self): 8 sage: Subsets(3).cardinality() 8 - """ - return Integer(1) << self._s.cardinality() - - def __len__(self): - r""" - Equivalent to ``self.cardinality()``. TESTS:: - ``__len__`` should return a Python int; in Python 3.7+ this happens - automatically, but not on Python 3.6. + ``__len__`` should return a Python int. sage: S = Subsets(Set([1,2,3])) sage: len(S) @@ -371,7 +364,9 @@ def __len__(self): sage: type(len(S)) is int True """ - return int(self.cardinality()) + return Integer(1) << self._s.cardinality() + + __len__ = cardinality def first(self): """ diff --git a/src/sage/combinat/words/paths.py b/src/sage/combinat/words/paths.py index 7bad614af5f..480fdf2506b 100644 --- a/src/sage/combinat/words/paths.py +++ b/src/sage/combinat/words/paths.py @@ -1203,8 +1203,7 @@ def is_simple(self): n += 1 if len(s) != n: return False - else: - return True + return True def tikz_trajectory(self): r""" diff --git a/src/sage/combinat/words/words.py b/src/sage/combinat/words/words.py index af92279b1d9..852fca48876 100644 --- a/src/sage/combinat/words/words.py +++ b/src/sage/combinat/words/words.py @@ -51,6 +51,7 @@ from sage.categories.sets_cat import Sets from sage.combinat.combinat import CombinatorialObject +from sage.structure.list_clone import ClonableElement from sage.combinat.words.alphabet import build_alphabet from sage.rings.all import Infinity @@ -829,7 +830,7 @@ def __call__(self, data=None, length=None, datatype=None, caching=True, check=Tr elif isinstance(data, tuple): w = self._element_classes['tuple'](self, data) - elif isinstance(data, CombinatorialObject): + elif isinstance(data, (CombinatorialObject, ClonableElement)): w = self._element_classes['list'](self, list(data)) elif callable(data): diff --git a/src/sage/databases/findstat.py b/src/sage/databases/findstat.py index 16e2757101b..6ba94aed722 100644 --- a/src/sage/databases/findstat.py +++ b/src/sage/databases/findstat.py @@ -251,7 +251,7 @@ def mapping(sigma): from sage.combinat.composition import Composition, Compositions from sage.combinat.partition import Partition, Partitions from sage.combinat.ordered_tree import OrderedTree, OrderedTrees -from sage.combinat.parking_functions import ParkingFunction, ParkingFunction_class, ParkingFunctions +from sage.combinat.parking_functions import ParkingFunction, ParkingFunctions from sage.combinat.perfect_matching import PerfectMatching, PerfectMatchings from sage.combinat.permutation import Permutation, Permutations from sage.combinat.posets.posets import Poset, FinitePoset @@ -4452,7 +4452,7 @@ def name(self, style="singular"): str, ParkingFunctions, len, - lambda x: isinstance(x, ParkingFunction_class)), + lambda x: isinstance(x, ParkingFunction)), "PerfectMatchings": _SupportedFindStatCollection(lambda x: PerfectMatching(literal_eval(x)), str, diff --git a/src/sage/env.py b/src/sage/env.py index 2908f5d04fa..b1cae78754d 100644 --- a/src/sage/env.py +++ b/src/sage/env.py @@ -169,6 +169,7 @@ def var(key: str, *fallbacks: Optional[str], force: bool = False) -> Optional[st SAGE_VENV = var("SAGE_VENV", os.path.abspath(sys.prefix)) SAGE_LIB = var("SAGE_LIB", os.path.dirname(os.path.dirname(sage.__file__))) SAGE_EXTCODE = var("SAGE_EXTCODE", join(SAGE_LIB, "sage", "ext_data")) +SAGE_VENV_SPKG_INST = var("SAGE_VENV_SPKG_INST", join(SAGE_VENV, "var", "lib", "sage", "installed")) # prefix hierarchy where non-Python packages are installed SAGE_LOCAL = var("SAGE_LOCAL", SAGE_VENV) @@ -214,6 +215,10 @@ def var(key: str, *fallbacks: Optional[str], force: bool = False) -> Optional[st NTL_INCDIR = var("NTL_INCDIR") NTL_LIBDIR = var("NTL_LIBDIR") +# OpenMP +OPENMP_CFLAGS = var("OPENMP_CFLAGS", "") +OPENMP_CXXFLAGS = var("OPENMP_CXXFLAGS", "") + # misc SAGE_BANNER = var("SAGE_BANNER", "") SAGE_IMPORTALL = var("SAGE_IMPORTALL", "yes") @@ -285,7 +290,7 @@ def _get_shared_lib_path(*libnames: str) -> Optional[str]: search_directories.append(libdir) multiarchlib = sysconfig.get_config_var('MULTIARCH') - if multiarchlib is not None: + if multiarchlib is not None: search_directories.append(libdir / multiarchlib), patterns = [f'lib{libname}.{ext}'] @@ -385,11 +390,19 @@ def get_cblas_pc_module_name() -> str: cblas_pc_modules = CBLAS_PC_MODULES.split(':') return next((blas_lib for blas_lib in cblas_pc_modules if pkgconfig.exists(blas_lib))) -def cython_aliases(): +def cython_aliases(required_modules=('fflas-ffpack', 'givaro', 'gsl', 'linbox', 'Singular', + 'libpng', 'gdlib', 'm4ri', 'zlib', 'cblas'), + optional_modules=('lapack',)): """ Return the aliases for compiling Cython code. These aliases are macros which can occur in ``# distutils`` headers. + INPUT: + + - ``required_modules`` -- iterable of ``str`` values. + + - ``optional_modules`` -- iterable of ``str`` values. + EXAMPLES:: sage: from sage.env import cython_aliases @@ -400,13 +413,41 @@ def cython_aliases(): 'CBLAS_CFLAGS', ..., 'ZLIB_LIBRARIES'] + sage: cython_aliases(required_modules=('module-that-is-assumed-to-not-exist')) + Traceback (most recent call last): + ... + PackageNotFoundError: ... + sage: cython_aliases(required_modules=(), optional_modules=('module-that-is-assumed-to-not-exist')) + {...} + + TESTS: + + We can use ``cython.parallel`` regardless of whether OpenMP is supported. + This will run in parallel, if OpenMP is supported:: + + sage: cython(''' + ....: #distutils: extra_compile_args = OPENMP_CFLAGS + ....: #distutils: extra_link_args = OPENMP_CFLAGS + ....: from cython.parallel import prange + ....: + ....: cdef int i + ....: cdef int n = 30 + ....: cdef int sum = 0 + ....: + ....: for i in prange(n, num_threads=4, nogil=True): + ....: sum += i + ....: + ....: print(sum) + ....: ''') + 435 """ import pkgconfig + import itertools aliases = {} - for lib in ['fflas-ffpack', 'givaro', 'gsl', 'linbox', 'Singular', - 'libpng', 'gdlib', 'm4ri', 'zlib', 'cblas', 'lapack']: + for lib, required in itertools.chain(((lib, True) for lib in required_modules), + ((lib, False) for lib in optional_modules)): var = lib.upper().replace("-", "") + "_" if lib == 'cblas': lib = get_cblas_pc_module_name() @@ -420,9 +461,16 @@ def cython_aliases(): pc = defaultdict(list, {'libraries': ['z']}) libs = "-lz" else: - aliases[var + "CFLAGS"] = pkgconfig.cflags(lib).split() - pc = pkgconfig.parse(lib) - libs = pkgconfig.libs(lib) + try: + aliases[var + "CFLAGS"] = pkgconfig.cflags(lib).split() + pc = pkgconfig.parse(lib) + libs = pkgconfig.libs(lib) + except pkgconfig.PackageNotFoundError: + if required: + raise + else: + continue + # It may seem that INCDIR is redundant because the -I options are also # passed in CFLAGS. However, "extra_compile_args" are put at the end # of the compiler command line. "include_dirs" go to the front; the @@ -454,7 +502,9 @@ def uname_specific(name, value, alternative): # file (possibly because of confusion between CFLAGS and CXXFLAGS?). # This is not a problem in practice since LinBox depends on # fflas-ffpack and fflas-ffpack does add such a C++11 flag. - aliases["LINBOX_CFLAGS"].append("-std=gnu++11") + if "LINBOX_CFLAGS" in aliases: + aliases["LINBOX_CFLAGS"].append("-std=gnu++11") + aliases["ARB_LIBRARY"] = ARB_LIBRARY # TODO: Remove Cygwin hack by installing a suitable cblas.pc @@ -463,7 +513,7 @@ def uname_specific(name, value, alternative): try: aliases["M4RI_CFLAGS"].remove("-pedantic") - except ValueError: + except (ValueError, KeyError): pass # Determine ecl-specific compiler arguments using the ecl-config script @@ -482,4 +532,8 @@ def uname_specific(name, value, alternative): aliases["NTL_LIBRARIES"] = ['ntl'] aliases["NTL_LIBEXTRA"] = [] + # OpenMP + aliases["OPENMP_CFLAGS"] = OPENMP_CFLAGS.split() + aliases["OPENMP_CXXFLAGS"] = OPENMP_CXXFLAGS.split() + return aliases diff --git a/src/sage/ext_data/threejs/threejs_template.html b/src/sage/ext_data/threejs/threejs_template.html index 1f94e841cb0..78ba00c5dec 100644 --- a/src/sage/ext_data/threejs/threejs_template.html +++ b/src/sage/ext_data/threejs/threejs_template.html @@ -540,6 +540,28 @@ } + function getCamera() { + + function roundTo( x, n ) { return +x.toFixed(n); } + + var pos = camera.position; + var pos_r = [ roundTo( pos.x, 4 ), roundTo( pos.y, 4 ), roundTo( pos.z, 4 ) ]; + // var up = camera.up; // up is always (0,0,1) + var textArea = document.createElement('textarea'); + var cam_position = JSON.stringify(pos_r); + textArea.textContent = ',camera_position=' + cam_position; + textArea.style.csstext = 'position: absolute; top: -100%'; + document.body.append( textArea ); + textArea.select(); + document.execCommand( 'copy' ); + + var m = document.getElementById( 'menu-message' ); + m.innerHTML = 'Camera position '+ cam_position+' copied to clipboard'; + m.style.display = 'block'; + setTimeout( function() { m.style.display = 'none'; }, 2000 ); + + } + diff --git a/src/sage/geometry/fan.py b/src/sage/geometry/fan.py index c5c3eb646e2..4a28419762a 100644 --- a/src/sage/geometry/fan.py +++ b/src/sage/geometry/fan.py @@ -253,7 +253,7 @@ from sage.graphs.digraph import DiGraph from sage.matrix.all import matrix from sage.misc.all import cached_method, walltime, prod -from sage.modules.all import vector +from sage.modules.all import vector, span from sage.rings.all import QQ, ZZ @@ -284,7 +284,8 @@ def is_Fan(x): def Fan(cones, rays=None, lattice=None, check=True, normalize=True, - is_complete=None, virtual_rays=None, discard_faces=False): + is_complete=None, virtual_rays=None, discard_faces=False, + allow_arrangement=False): r""" Construct a rational polyhedral fan. @@ -303,7 +304,7 @@ def Fan(cones, rays=None, lattice=None, check=True, normalize=True, :class:`Cone` objects or lists of integers interpreted as indices of generating rays in ``rays``. These must be only **maximal** cones of the fan, unless - ``discard_faces=True`` option is specified; + ``discard_faces=True`` or ``allow_arrangement=True`` option is specified; - ``rays`` -- list of rays given as list or vectors convertible to the rational extension of ``lattice``. If ``cones`` are given by @@ -320,7 +321,8 @@ def Fan(cones, rays=None, lattice=None, check=True, normalize=True, be made to determine an appropriate toric lattice automatically; - ``check`` -- by default the input data will be checked for correctness - (e.g. that intersection of any two given cones is a face of each). If you + (e.g. that intersection of any two given cones is a face of each), + unless ``allow_arrangement=True`` option is specified. If you know for sure that the input is correct, you may significantly decrease construction time using ``check=False`` option; @@ -345,14 +347,23 @@ def Fan(cones, rays=None, lattice=None, check=True, normalize=True, ray generators to be used for :meth:`virtual_rays`; - ``discard_faces`` -- by default, the fan constructor expects the list of - **maximal** cones. If you provide "extra" ones and leave ``check=True`` - (default), an exception will be raised. If you provide "extra" cones and - set ``check=False``, you may get wrong results as assumptions on internal + **maximal** cones, unless ``allow_arrangement=True`` option is specified. + If you provide "extra" ones and leave ``allow_arrangement=False`` (default) + and ``check=True`` (default), an exception will be raised. + If you provide "extra" cones and set ``allow_arrangement=False`` (default) + and ``check=False``, you may get wrong results as assumptions on internal data structures will be invalid. If you want the fan constructor to select the maximal cones from the given input, you may provide ``discard_faces=True`` option (it works both for ``check=True`` and ``check=False``). + - ``allow_arrangement`` -- by default (``allow_arrangement=False``), + the fan constructor expects that the intersection of any two given cones is + a face of each. If ``allow_arrangement=True`` option is specified, then + construct a rational polyhedralfan from the cone arrangement, so that the + union of the cones in the polyhedral fan equals to the union of the given + cones, and each given cone is the union of some cones in the polyhedral fan. + OUTPUT: - a :class:`fan `. @@ -457,6 +468,70 @@ def Fan(cones, rays=None, lattice=None, check=True, normalize=True, 2 sage: F.dim() 0 + + In the following examples, we test the ``allow_arrangement=True`` option. + See :trac:`25122`. + + The intersection of the two cones is not a face of each. Therefore, + they do not belong to the same rational polyhedral fan:: + + sage: c1 = Cone([(-2,-1,1), (-2,1,1), (2,1,1), (2,-1,1)]) + sage: c2 = Cone([(-1,-2,1), (-1,2,1), (1,2,1), (1,-2,1)]) + sage: c1.intersection(c2).is_face_of(c1) + False + sage: c1.intersection(c2).is_face_of(c2) + False + sage: Fan([c1, c2]) + Traceback (most recent call last): + ... + ValueError: these cones cannot belong to the same fan! + ... + + Let's construct the fan using ``allow_arrangement=True`` option:: + + sage: fan = Fan([c1, c2], allow_arrangement=True) + sage: fan.ngenerating_cones() + 5 + + Another example where cone c2 is inside cone c1:: + + sage: c1 = Cone([(4, 0, 0), (0, 4, 0), (0, 0, 4)]) + sage: c2 = Cone([(2, 1, 1), (1, 2, 1), (1, 1, 2)]) + sage: fan = Fan([c1, c2], allow_arrangement=True) + sage: fan.ngenerating_cones() + 7 + sage: fan.plot() + Graphics3d Object + + Cones of different dimension:: + + sage: c1 = Cone([(1,0),(0,1)]) + sage: c2 = Cone([(2,1)]) + sage: c3 = Cone([(-1,-2)]) + sage: fan = Fan([c1, c2, c3], allow_arrangement=True) + sage: for cone in sorted(fan.generating_cones()): print(sorted(cone.rays())) + [N(-1, -2)] + [N(0, 1), N(1, 2)] + [N(1, 0), N(2, 1)] + [N(1, 2), N(2, 1)] + + A 3-d cone and a 1-d cone:: + + sage: c3 = Cone([[0, 1, 1], [1, 0, 1], [0, -1, 1], [-1, 0, 1]]) + sage: c1 = Cone([[0, 0, 1]]) + sage: fan1 = Fan([c1, c3], allow_arrangement=True) + sage: fan1.plot() + Graphics3d Object + + A 3-d cone and two 2-d cones:: + + sage: c2v = Cone([[0, 1, 1], [0, -1, 1]]) + sage: c2h = Cone([[1, 0, 1], [-1, 0, 1]]) + sage: fan2 = Fan([c2v, c2h, c3], allow_arrangement=True) + sage: fan2.is_simplicial() + True + sage: fan2.is_equivalent(fan1) + True """ def result(): # "global" does not work here... @@ -473,7 +548,7 @@ def result(): "independent and with other rays span the ambient space.") return RationalPolyhedralFan(cones, R, lattice, is_complete, V) - if not check and not normalize and not discard_faces: + if not check and not normalize and not discard_faces and not allow_arrangement: return result() if not isinstance(cones, list): try: @@ -520,22 +595,10 @@ def result(): rays = cone.rays() is_complete = lattice.dimension() == 0 return result() - ray_set = set([]) - for cone in cones: - ray_set.update(cone.rays()) - if rays: # Preserve the initial order of rays, if they were given - rays = normalize_rays(rays, lattice) - new_rays = [] - for ray in rays: - if ray in ray_set and ray not in new_rays: - new_rays.append(ray) - if len(new_rays) != len(ray_set): - raise ValueError( - "if rays are given, they must include all rays of the fan!") - rays = new_rays - else: - rays = tuple(sorted(ray_set)) - if check: + if allow_arrangement: + cones = _refine_arrangement_to_fan(cones) + cones = _discard_faces(cones) + elif check: # Maybe we should compute all faces of all cones and save them for # later if we are doing this check? generating_cones = [] @@ -565,6 +628,21 @@ def result(): (len(cones), len(generating_cones))) elif discard_faces: cones = _discard_faces(cones) + ray_set = set([]) + for cone in cones: + ray_set.update(cone.rays()) + if rays: # Preserve the initial order of rays, if they were given + rays = normalize_rays(rays, lattice) + new_rays = [] + for ray in rays: + if ray in ray_set and ray not in new_rays: + new_rays.append(ray) + if len(new_rays) != len(ray_set): + raise ValueError( + "if rays are given, they must include all rays of the fan!") + rays = new_rays + else: + rays = tuple(sorted(ray_set)) cones = (tuple(sorted(rays.index(ray) for ray in cone.rays())) for cone in cones) return result() @@ -575,12 +653,13 @@ def result(): cones[n] = sorted(cone) except TypeError: raise TypeError("cannot interpret %s as a cone!" % cone) - if not check and not discard_faces: + if not check and not discard_faces and not allow_arrangement: return result() # If we do need to make all the check, build explicit cone objects first return Fan((Cone([rays[n] for n in cone], lattice) for cone in cones), rays, lattice, is_complete=is_complete, - virtual_rays=virtual_rays, discard_faces=discard_faces) + virtual_rays=virtual_rays, discard_faces=discard_faces, + allow_arrangement=allow_arrangement) def FaceFan(polytope, lattice=None): @@ -3069,7 +3148,7 @@ def primitive_collections(self): OUTPUT: - Returns the subsets `\{i_1,\dots,i_k\} \subset \{ 1,\dots,n\}` + Return the subsets `\{i_1,\dots,i_k\} \subset \{ 1,\dots,n\}` such that * The points `\{p_{i_1},\dots,p_{i_k}\}` do not span a cone of @@ -3169,7 +3248,7 @@ def linear_equivalence_ideal(self, ring): OUTPUT: - Returns the ideal, in the given ``ring``, generated by the + Return the ideal, in the given ``ring``, generated by the linear relations of the rays. In toric geometry, this corresponds to rational equivalence of divisors. @@ -3523,3 +3602,93 @@ def discard_faces(cones): _discard_faces = discard_faces # Due to a name conflict in Fan constructor + + +def _refine_arrangement_to_fan(cones): + """ + Refine the cones of the given list so that they can belong to the same fan. + + INPUT: + + - ``cones`` -- a list of rational cones that are possibly overlapping. + + OUTPUT: + + - a list of refined cones. + + EXAMPLES:: + + sage: from sage.geometry.fan import _refine_arrangement_to_fan + sage: c1 = Cone([(-2,-1,1), (-2,1,1), (2,1,1), (2,-1,1)]) + sage: c2 = Cone([(-1,-2,1), (-1,2,1), (1,2,1), (1,-2,1)]) + sage: refined_cones = _refine_arrangement_to_fan([c1, c2]) + sage: for cone in refined_cones: print(cone.rays()) + N(-1, 1, 1), + N(-1, -1, 1), + N( 1, -1, 1), + N( 1, 1, 1) + in 3-d lattice N + N(1, -1, 1), + N(1, 1, 1), + N(2, -1, 1), + N(2, 1, 1) + in 3-d lattice N + N(-2, 1, 1), + N(-1, -1, 1), + N(-1, 1, 1), + N(-2, -1, 1) + in 3-d lattice N + N(-1, 1, 1), + N(-1, 2, 1), + N( 1, 1, 1), + N( 1, 2, 1) + in 3-d lattice N + N(-1, -1, 1), + N(-1, -2, 1), + N( 1, -2, 1), + N( 1, -1, 1) + in 3-d lattice N + """ + dual_lattice = cones[0].dual_lattice() + is_face_to_face = True + for i in range(len(cones)): + ci = cones[i] + for j in range(i): + cj = cones[j] + c = ci.intersection(cj) + if not (c.is_face_of(ci)) or not (c.is_face_of(cj)): + is_face_to_face = False + break + if not is_face_to_face: + break + if is_face_to_face: + return cones + facet_normal_vectors = [] + for c in cones: + for l in c.polyhedron().Hrepresentation(): + v = l[1::] + is_new = True + for fnv in facet_normal_vectors: + if span([v, fnv]).dimension() < 2: + is_new = False + break + if is_new: + facet_normal_vectors.append(v) + for v in facet_normal_vectors: + halfspace1 = Cone([v], lattice=dual_lattice).dual() + halfspace2 = Cone([-v], lattice=dual_lattice).dual() + subcones = [] + for c in cones: + subc1 = c.intersection(halfspace1) + subc2 = c.intersection(halfspace2) + for subc in [subc1, subc2]: + if subc.dim() == c.dim(): + is_new = True + for subcone in subcones: + if subc.dim() == subcone.dim() and subc.is_equivalent(subcone): + is_new = False + break + if is_new: + subcones.append(subc) + cones = subcones + return cones diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py index 8f4e8503d8f..d8778307934 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py @@ -1397,15 +1397,36 @@ def perpendicular_bisector(self): # UHP ... ValueError: the length must be finite - """ + TESTS: + Check the result is independent of the order (:trac:`29936`):: + + sage: def bisector_gets_midpoint(a, b): + ....: UHP = HyperbolicPlane().UHP() + ....: g = UHP.get_geodesic(a, b) + ....: p = g.perpendicular_bisector() + ....: x = g.intersection(p)[0] + ....: m = g.midpoint() + ....: return bool(x.dist(m) < 1e-9) + sage: c, d, e = CC(1, 1), CC(2, 1), CC(2, 0.5) + sage: pairs = [(c, d), (d, c), (c, e), (e, c), (d, e), (e, d)] + sage: all(bisector_gets_midpoint(a, b) for a, b in pairs) + True + """ if self.length() == infinity: raise ValueError("the length must be finite") start = self._start.coordinates() - d = self._model._dist_points(start, self._end.coordinates()) / 2 + end = self._end.coordinates() + # The complete geodesic p1 -> p2 always returns p1 < p2, + # so we might need to swap start and end + if ((real(start - end) > EPSILON) or + (abs(real(start - end)) < EPSILON and + imag(start - end) > 0)): + start, end = end, start S = self.complete()._to_std_geod(start) + d = self._model._dist_points(start, end) / 2 T1 = matrix([[exp(d/2), 0], [0, exp(-d/2)]]) - s2 = sqrt(2) * 0.5 + s2 = sqrt(2) / 2 T2 = matrix([[s2, -s2], [s2, s2]]) isom_mtrx = S.inverse() * (T1 * T2) * S # We need to clean this matrix up. @@ -1413,7 +1434,7 @@ def perpendicular_bisector(self): # UHP # Imaginary part is small. isom_mtrx = (isom_mtrx + isom_mtrx.conjugate()) / 2 # Set it to its real part. - H = self._model.get_isometry(isom_mtrx) + H = self._model._Isometry(self._model, isom_mtrx, check=False) return self._model.get_geodesic(H(self._start), H(self._end)) def midpoint(self): # UHP @@ -1445,11 +1466,11 @@ def midpoint(self): # UHP sage: g=HyperbolicPlane().UHP().get_geodesic(-1+I,1+I) sage: point = g.midpoint(); point Point in UHP -1/2*(sqrt(2)*... - sage: QQbar(point.coordinates()).radical_expression() + sage: QQbar(point.coordinates()).radical_expression() # long time I*sqrt(2) Check that floating points remain floating points - in :meth:`midpoint` :: + in :meth:`midpoint`:: sage: UHP = HyperbolicPlane().UHP() sage: g = UHP.get_geodesic(CC(0,1), CC(2,2)) @@ -1458,8 +1479,18 @@ def midpoint(self): # UHP sage: parent(g.midpoint().coordinates()) Complex Field with 53 bits of precision - """ + Check that the midpoint is independent of the order (:trac:`29936`):: + sage: g = UHP.get_geodesic(1+I, 2+0.5*I) + sage: h = UHP.get_geodesic(2+0.5*I, 1+I) + sage: abs(g.midpoint().coordinates() - h.midpoint().coordinates()) < 1e-9 + True + + sage: g = UHP.get_geodesic(2+I, 2+0.5*I) + sage: h = UHP.get_geodesic(2+0.5*I, 2+I) + sage: abs(g.midpoint().coordinates() - h.midpoint().coordinates()) < 1e-9 + True + """ from sage.matrix.matrix_symbolic_dense import Matrix_symbolic_dense if self.length() == infinity: raise ValueError("the length must be finite") @@ -1467,6 +1498,12 @@ def midpoint(self): # UHP start = self._start.coordinates() end = self._end.coordinates() d = self._model._dist_points(start, end) / 2 + # The complete geodesic p1 -> p2 always returns p1 < p2, + # so we might need to swap start and end + if ((real(start - end) > EPSILON) or + (abs(real(start - end)) < EPSILON and + imag(start - end) > 0)): + start, end = end, start S = self.complete()._to_std_geod(start) # If the matrix is symbolic then needs to be simplified in order to @@ -1476,13 +1513,7 @@ def midpoint(self): # UHP S_1 = S.inverse() T = matrix([[exp(d), 0], [0, 1]]) M = S_1 * T * S - if ((real(start - end) < EPSILON) or - (abs(real(start - end)) < EPSILON and - imag(start - end) < EPSILON)): - end_p = start - else: - end_p = end - P_3 = moebius_transform(M, end_p) + P_3 = moebius_transform(M, start) return self._model.get_point(P_3) def angle(self, other): # UHP diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py index 418e3eccbc5..08beb8fc76a 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py @@ -656,7 +656,13 @@ def _call_(self, p): #UHP sage: bool(UHP.dist(I2(p), p) < 10**-9) True """ - return self.codomain().get_point(moebius_transform(self._matrix, p.coordinates())) + coords = p.coordinates() + # We apply complex conjugation to the point for negative determinants + # We check the coordinate is not equal to infinity. If we use !=, then + # it cannot determine it is not infinity, so it also returns False. + if not (coords == infinity) and bool(self._matrix.det() < 0): + coords = coords.conjugate() + return self.codomain().get_point(moebius_transform(self._matrix, coords)) def preserves_orientation(self): #UHP r""" @@ -913,7 +919,11 @@ def _call_(self, p): #PD sage: bool(PD.dist(I2(q), q) < 10**-9) True """ - _image = moebius_transform(self._matrix, p.coordinates()) + coords = p.coordinates() + # We apply complex conjugation to the point for negative determinants + if bool(self._matrix.det() < 0): + coords = coords.conjugate() + _image = moebius_transform(self._matrix, coords) return self.codomain().get_point(_image) def __mul__(self, other): #PD @@ -1040,7 +1050,7 @@ def moebius_transform(A, z): sage: from sage.geometry.hyperbolic_space.hyperbolic_model import moebius_transform sage: moebius_transform(matrix(2,[1,2,3,4]),2 + I) - 2/109*I + 43/109 + -2/109*I + 43/109 sage: y = var('y') sage: moebius_transform(matrix(2,[1,0,0,1]),x + I*y) x + I*y @@ -1076,12 +1086,9 @@ def moebius_transform(A, z): if c == 0: return infinity return a / c - if a * d - b * c < 0: - w = z.conjugate() # Reverses orientation - else: - w = z if c * z + d == 0: return infinity - return (a * w + b) / (c * w + d) + return (a * z + b) / (c * z + d) raise TypeError("A must be an invertible 2x2 matrix over the" " complex numbers or a symbolic ring") + diff --git a/src/sage/geometry/polyhedron/base.py b/src/sage/geometry/polyhedron/base.py index 843fe204cb2..23ffed867da 100644 --- a/src/sage/geometry/polyhedron/base.py +++ b/src/sage/geometry/polyhedron/base.py @@ -3,10 +3,25 @@ """ # **************************************************************************** -# Copyright (C) 2008 Marshall Hampton -# Copyright (C) 2011 Volker Braun -# Copyright (C) 2015 Jean-Philippe Labbe -# Copyright (C) 2020 Jonathan Kliem +# Copyright (C) 2008-2012 Marshall Hampton +# Copyright (C) 2011-2015 Volker Braun +# Copyright (C) 2012-2018 Frederic Chapoton +# Copyright (C) 2013 Andrey Novoseltsev +# Copyright (C) 2014-2017 Moritz Firsching +# Copyright (C) 2014-2019 Thierry Monteil +# Copyright (C) 2015 Nathann Cohen +# Copyright (C) 2015-2017 Jeroen Demeyer +# Copyright (C) 2015-2017 Vincent Delecroix +# Copyright (C) 2015-2018 Dima Pasechnik +# Copyright (C) 2015-2020 Jean-Philippe Labbe +# Copyright (C) 2015-2021 Matthias Koeppe +# Copyright (C) 2016-2019 Daniel Krenn +# Copyright (C) 2017 Marcelo Forets +# Copyright (C) 2017-2018 Mark Bell +# Copyright (C) 2019 Julian Ritter +# Copyright (C) 2019-2020 Laith Rastanawi +# Copyright (C) 2019-2020 Sophia Elia +# Copyright (C) 2019-2021 Jonathan Kliem # # 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 @@ -16,7 +31,10 @@ # **************************************************************************** +from dataclasses import dataclass +from typing import Any import itertools + from sage.structure.element import Element, coerce_binop, is_Vector, is_Matrix from sage.structure.richcmp import rich_to_bool, op_NE from sage.cpython.string import bytes_to_str @@ -127,6 +145,7 @@ class Polyhedron_base(Element): sage: TestSuite(Polyhedron([[]])).run() sage: TestSuite(Polyhedron([[0]])).run() + sage: TestSuite(Polyhedron([[1]])).run() :: @@ -137,6 +156,17 @@ class Polyhedron_base(Element): sage: P = polytopes.permutahedron(3)*Polyhedron(rays=[[0,0,1],[0,1,1]], lines=[[1,0,0]]) sage: TestSuite(P).run() + + :: + + sage: M = random_matrix(ZZ, 5, 5, distribution='uniform') + sage: while True: + ....: M = random_matrix(ZZ, 5, 5, distribution='uniform') + ....: if M.rank() != 5: + ....: break + ....: + sage: P = Polyhedron(M) + sage: TestSuite(P).run() """ def __init__(self, parent, Vrep, Hrep, Vrep_minimal=None, Hrep_minimal=None, pref_rep=None, **kwds): @@ -1024,10 +1054,10 @@ def plot(self, sage: fcube = polytopes.hypercube(4) sage: tfcube = fcube.face_truncation(fcube.faces(0)[0]) sage: sp = tfcube.schlegel_projection() - sage: for face in tfcube.faces(2): - ....: vertices = face.ambient_Vrepresentation() - ....: indices = [sp.coord_index_of(vector(x)) for x in vertices] - ....: projected_vertices = [sp.transformed_coords[i] for i in indices] + sage: for face in tfcube.faces(2): + ....: vertices = face.ambient_Vrepresentation() + ....: indices = [sp.coord_index_of(vector(x)) for x in vertices] + ....: projected_vertices = [sp.transformed_coords[i] for i in indices] ....: assert Polyhedron(projected_vertices).dim() == 2 """ def merge_options(*opts): @@ -8167,9 +8197,10 @@ def volume(self, measure='ambient', engine='auto', **kwds): if engine == 'normaliz': return self._volume_normaliz(measure='euclidean') # use an orthogonal transformation, which preserves volume up to a factor provided by the transformation matrix - A, b = self.affine_hull_projection(orthogonal=True, as_affine_map=True) - Adet = (A.matrix().transpose() * A.matrix()).det() - scaled_volume = self.affine_hull_projection(orthogonal=True).volume(measure='ambient', engine=engine, **kwds) + affine_hull_data = self.affine_hull_projection(orthogonal=True, as_polyhedron=True, as_affine_map=True) + A = affine_hull_data.projection_linear_map.matrix() + Adet = (A.transpose() * A).det() + scaled_volume = affine_hull_data.polyhedron.volume(measure='ambient', engine=engine, **kwds) if Adet.is_square(): sqrt_Adet = Adet.sqrt() else: @@ -9861,28 +9892,58 @@ def _test_is_combinatorially_isomorphic(self, tester=None, **options): if self.n_vertices(): tester.assertTrue(self.is_combinatorially_isomorphic(self + self.center(), algorithm='face_lattice')) - def affine_hull_projection(self, as_affine_map=False, orthogonal=False, - orthonormal=False, extend=False, minimal=False): - """ - Return the polyhedron projected into its affine hull. + @dataclass + class AffineHullProjectionData: + polyhedron: Any = None + projection_linear_map: Any = None + projection_translation: Any = None + section_linear_map: Any = None + section_translation: Any = None + + @cached_method + def affine_hull_projection(self, as_polyhedron=None, as_affine_map=False, orthogonal=False, + orthonormal=False, extend=False, minimal=False, return_all_data=False): + """Return the polyhedron projected into its affine hull. Each polyhedron is contained in some smallest affine subspace - (possibly the entire ambient space) -- its affine hull. - We provide a projection of the ambient - space of the polyhedron to Euclidean space of dimension of the - polyhedron. Then the image of the polyhedron under this - projection (or, depending on the parameter ``as_affine_map``, - the projection itself) is returned. + (possibly the entire ambient space) -- its affine hull. We + provide an affine linear map that projects the ambient space of + the polyhedron to the standard Euclidean space of dimension of + the polyhedron, which restricts to a bijection from the affine + hull. + + The projection map is not unique; some parameters control the + choice of the map. Other parameters control the output of the + function. INPUT: - - ``as_affine_map`` -- boolean (default: ``False``); if ``False``, return - a polyhedron. If ``True``, return the affine transformation, - that sends the embedded polytope to a fulldimensional one. + - ``as_polyhedron`` -- (boolean or the default ``None``) and + + - ``as_affine_map`` -- (boolean, default ``False``) control the output + + The default ``as_polyhedron=None`` translates to + ``as_polyhedron=not as_affine_map``, + therefore to ``as_polyhedron=True`` if nothing is specified. + + If exactly one of either ``as_polyhedron`` or ``as_affine_map`` is + set, then either a polyhedron or the affine transformation + is returned. The affine transformation + sends the embedded polytope to a fulldimensional one. It is given as a pair ``(A, b)``, where A is a linear transformation - and ``b`` is a vector, and the affine transformation sends ``v`` to + and `b` is a vector, and the affine transformation sends ``v`` to ``A(v)+b``. + If both ``as_polyhedron`` and ``as_affine_map`` are set, then + both are returned, encapsulated in an instance of ``AffineHullProjectionData``. + + - ``return_all_data`` -- (boolean, default ``False``) + + If set, then ``as_polyhedron`` and ``as_affine_map`` will set + (possibly overridden) and additional (internal) data concerning + the transformation is returned. Everything is encapsulated + in an instance of ``AffineHullProjectionData`` in this case. + - ``orthogonal`` -- boolean (default: ``False``); if ``True``, provide an orthogonal transformation. @@ -9902,9 +9963,27 @@ def affine_hull_projection(self, as_affine_map=False, orthogonal=False, OUTPUT: A full-dimensional polyhedron or an affine transformation, - depending on the parameter ``as_affine_map``. + depending on the parameters ``as_polyhedron`` and ``as_affine_map``, + or an instance of ``AffineHullProjectionData`` containing all data + (parameter ``return_all_data``). - .. TODO:: + If the output is an instance of ``AffineHullProjectionData``, the + following fields may be set: + + - ``polyhedron`` -- the projection of the original polyhedron + + - ``projection_map`` -- the affine map as a pair whose first component + is a linear transformation and its second component a shift; + see above. + + - ``section_map`` -- an affine map as a pair whose first component + is a linear transformation and its second component a shift. + It maps the codomain of ``affine_map`` to the affine hull of + ``self``. It is a right inverse of ``projection_map``. + + Note that all of these data are compatible. + + .. TODO:: - make the parameters ``orthogonal`` and ``orthonormal`` work with unbounded polyhedra. @@ -10125,6 +10204,85 @@ def affine_hull_projection(self, as_affine_map=False, orthogonal=False, Codomain: Vector space of dimension 3 over Rational Field, (0, 0, 0)) + Return polyhedron and affine map:: + + sage: S = polytopes.simplex(2) + sage: data = S.affine_hull_projection(orthogonal=True, + ....: as_polyhedron=True, + ....: as_affine_map=True); data + Polyhedron_base.AffineHullProjectionData(polyhedron=A 2-dimensional polyhedron in QQ^2 + defined as the convex hull of 3 vertices, + projection_linear_map=Vector space morphism represented by the matrix: + [ -1 -1/2] + [ 1 -1/2] + [ 0 1] + Domain: Vector space of dimension 3 over Rational Field + Codomain: Vector space of dimension 2 over Rational Field, + projection_translation=(1, 1/2), + section_linear_map=None, + section_translation=None) + + Return all data:: + + sage: data = S.affine_hull_projection(orthogonal=True, return_all_data=True); data + Polyhedron_base.AffineHullProjectionData(polyhedron=A 2-dimensional polyhedron in QQ^2 + defined as the convex hull of 3 vertices, + projection_linear_map=Vector space morphism represented by the matrix: + [ -1 -1/2] + [ 1 -1/2] + [ 0 1] + Domain: Vector space of dimension 3 over Rational Field + Codomain: Vector space of dimension 2 over Rational Field, + projection_translation=(1, 1/2), + section_linear_map=Vector space morphism represented by the matrix: + [-1/2 1/2 0] + [-1/3 -1/3 2/3] + Domain: Vector space of dimension 2 over Rational Field + Codomain: Vector space of dimension 3 over Rational Field, section_translation=(1, 0, 0)) + + The section map is a right inverse of the projection map:: + + sage: data.polyhedron.linear_transformation(data.section_linear_map.matrix().transpose()) + data.section_translation == S + True + + Same without ``orthogonal=True``:: + + sage: data = S.affine_hull_projection(return_all_data=True); data + Polyhedron_base.AffineHullProjectionData(polyhedron=A 2-dimensional polyhedron in ZZ^2 + defined as the convex hull of 3 vertices, + projection_linear_map=Vector space morphism represented by the matrix: + [1 0] + [0 1] + [0 0] + Domain: Vector space of dimension 3 over Rational Field + Codomain: Vector space of dimension 2 over Rational Field, projection_translation=(0, 0), + section_linear_map=Vector space morphism represented by the matrix: + [ 1 0 -1] + [ 0 1 -1] + Domain: Vector space of dimension 2 over Rational Field + Codomain: Vector space of dimension 3 over Rational Field, section_translation=(0, 0, 1)) + sage: data.polyhedron.linear_transformation(data.section_linear_map.matrix().transpose()) + data.section_translation == S + True + + :: + + sage: P0 = Polyhedron( + ....: ieqs=[(0, -1, 0, 1, 1, 1), (0, 1, 1, 0, -1, -1), (0, -1, 1, 1, 0, 0), + ....: (0, 1, 0, 0, 0, 0), (0, 0, 1, 1, -1, -1), (0, 0, 0, 0, 0, 1), + ....: (0, 0, 0, 0, 1, 0), (0, 0, 0, 1, 0, -1), (0, 0, 1, 0, 0, 0)]) + sage: P = P0.intersection(Polyhedron(eqns=[(-1, 1, 1, 1, 1, 1)])) + sage: P.dim() + 4 + sage: P.affine_hull_projection(orthogonal=True, as_affine_map=True)[0] + Vector space morphism represented by the matrix: + [ 0 0 0 1/3] + [ -2/3 -1/6 0 -1/12] + [ 1/3 -1/6 1/2 -1/12] + [ 0 1/2 0 -1/12] + [ 1/3 -1/6 -1/2 -1/12] + Domain: Vector space of dimension 5 over Rational Field + Codomain: Vector space of dimension 4 over Rational Field + TESTS: Check that :trac:`23355` is fixed:: @@ -10183,30 +10341,47 @@ def affine_hull_projection(self, as_affine_map=False, orthogonal=False, doctest:...: DeprecationWarning: affine_hull is deprecated. Please use affine_hull_projection instead. See https://trac.sagemath.org/29326 for details. """ + if as_polyhedron is None: + as_polyhedron = not as_affine_map + if not as_affine_map and not as_polyhedron: + raise ValueError('combining "as_affine_map=False" and ' + '"as_polyhedron=False" not allowed') + if return_all_data: + as_polyhedron = True + as_affine_map = True + + result = self.AffineHullProjectionData() + + if self.is_empty(): + raise ValueError('affine hull projection of an empty polyhedron is undefined') + # handle trivial full-dimensional case if self.ambient_dim() == self.dim(): - if as_affine_map: - return linear_transformation(matrix(self.base_ring(), - self.dim(), - self.dim(), - self.base_ring().one())), self.ambient_space().zero() - return self - - if orthogonal or orthonormal: + if as_polyhedron: + result.polyhedron = self + if as_affine_map: + identity = linear_transformation(matrix(self.base_ring(), + self.dim(), + self.dim(), + self.base_ring().one())) + result.projection_linear_map = result.section_linear_map = identity + result.projection_translation = result.section_translation = self.ambient_space().zero() + elif orthogonal or orthonormal: # see TODO if not self.is_compact(): raise NotImplementedError('"orthogonal=True" and "orthonormal=True" work only for compact polyhedra') affine_basis = self.an_affine_basis() + v0 = affine_basis[0].vector() # We implicitly translate the first vertex of the affine basis to zero. - M = matrix(self.base_ring(), self.dim(), self.ambient_dim(), - [v.vector() - affine_basis[0].vector() for v in affine_basis[1:]]) + vi = tuple(v.vector() - v0 for v in affine_basis[1:]) + M = matrix(self.base_ring(), self.dim(), self.ambient_dim(), vi) # Switch base_ring to AA if necessary, # since gram_schmidt needs to be able to take square roots. # Pick orthonormal basis and transform all vertices accordingly # if the orthonormal transform makes it necessary, change base ring. try: - A = M.gram_schmidt(orthonormal=orthonormal)[0] + A, G = M.gram_schmidt(orthonormal=orthonormal) except TypeError: if not extend: raise ValueError('the base ring needs to be extended; try with "extend=True"') @@ -10216,39 +10391,131 @@ def affine_hull_projection(self, as_affine_map=False, orthogonal=False, from sage.rings.qqbar import number_field_elements_from_algebraics new_ring = number_field_elements_from_algebraics(A.list(), embedded=True, minimal=True)[0] A = A.change_ring(new_ring) - if as_affine_map: - return linear_transformation(A, side='right'), -A*vector(A.base_ring(), affine_basis[0]) - - translate_vector = vector(A.base_ring(), affine_basis[0]) - - # Note the order. We compute ``A*self`` and then subtract the translation vector. + L = linear_transformation(A, side='right') + ambient_translation = -vector(A.base_ring(), affine_basis[0]) + image_translation = A * ambient_translation + # Note the order. We compute ``A*self`` and then translate the image. # ``A*self`` uses the incidence matrix and we avoid recomputation. # Also, if the new base ring is ``AA``, we want to avoid computing the incidence matrix in that ring. - # ``convert=True`` takes care of the case, where there might be no coercion (``AA`` and quadratic field). - return self.linear_transformation(A, new_base_ring=A.base_ring()) - A*translate_vector - - # translate one vertex to the origin - v0 = self.vertices()[0].vector() - gens = [] - for v in self.vertices()[1:]: - gens.append(v.vector() - v0) - for r in self.rays(): - gens.append(r.vector()) - for l in self.lines(): - gens.append(l.vector()) - - # Pick subset of coordinates to coordinatize the affine span - pivots = matrix(gens).pivots() - - A = matrix([[1 if j == i else 0 for j in range(self.ambient_dim())] for i in pivots]) - if as_affine_map: - return linear_transformation(A, side='right'), vector(self.base_ring(), self.dim()) + if as_polyhedron: + result.polyhedron = self.linear_transformation(A, new_base_ring=A.base_ring()) + image_translation + if as_affine_map: + result.projection_linear_map = L + result.projection_translation = image_translation + if return_all_data: + L_dagger = linear_transformation(A.transpose() * (A * A.transpose()).inverse(), side='right') + result.section_linear_map = L_dagger + result.section_translation = v0.change_ring(A.base_ring()) else: - return A*self + # translate one vertex to the origin + v0 = self.vertices()[0].vector() + gens = [] + for v in self.vertices()[1:]: + gens.append(v.vector() - v0) + for r in self.rays(): + gens.append(r.vector()) + for l in self.lines(): + gens.append(l.vector()) + + # Pick subset of coordinates to coordinatize the affine span + M = matrix(gens) + pivots = M.pivots() + + A = matrix(self.base_ring(), len(pivots), self.ambient_dim(), + [[1 if j == i else 0 for j in range(self.ambient_dim())] for i in pivots]) + if as_affine_map: + image_translation = vector(self.base_ring(), self.dim()) + L = linear_transformation(A, side='right') + result.projection_linear_map = L + result.projection_translation = image_translation + if as_polyhedron: + result.polyhedron = A*self + if return_all_data: + if self.dim(): + B = M.transpose()/(A*M.transpose()) + else: + B = matrix(self.ambient_dim(), 0) + L_section = linear_transformation(B, side='right') + result.section_linear_map = L_section + result.section_translation = v0 - L_section(L(v0) + image_translation) + + # assemble result + if return_all_data or (as_polyhedron and as_affine_map): + return result + elif as_affine_map: + return (result.projection_linear_map, result.projection_translation) + else: + return result.polyhedron affine_hull = deprecated_function_alias(29326, affine_hull_projection) + def _test_affine_hull_projection(self, tester=None, verbose=False, **options): + """ + Run tests on the method :meth:`.affine_hull_projection`. + + TESTS:: + + sage: D = polytopes.dodecahedron() + sage: D.facets()[0].as_polyhedron()._test_affine_hull_projection() + """ + if tester is None: + tester = self._tester(**options) + + if self.is_empty(): + # Undefined, nothing to test + return + + if self.n_vertices() > 30 or self.n_facets() > 30 or self.dim() > 6: + # Avoid very long doctests. + return + + data_sets = [None]*4 + data_sets[0] = self.affine_hull_projection(return_all_data=True) + if self.is_compact(): + data_sets[1] = self.affine_hull_projection(return_all_data=True, + orthogonal=True, + extend=True) + data_sets[2] = self.affine_hull_projection(return_all_data=True, + orthonormal=True, + extend=True) + data_sets[3] = self.affine_hull_projection(return_all_data=True, + orthonormal=True, + extend=True, + minimal=True) + else: + data_sets = data_sets[:1] + + for i, data in enumerate(data_sets): + if verbose: + print("Running test number {}".format(i)) + M = data.projection_linear_map.matrix().transpose() + tester.assertEqual(self.linear_transformation(M, new_base_ring=M.base_ring()) + + data.projection_translation, + data.polyhedron) + + M = data.section_linear_map.matrix().transpose() + if M.base_ring() is AA: + self_extend = self.change_ring(AA) + else: + self_extend = self + tester.assertEqual(data.polyhedron.linear_transformation(M) + + data.section_translation, + self_extend) + if i == 0: + tester.assertEqual(data.polyhedron.base_ring(), self.base_ring()) + else: + # Test whether the map is orthogonal. + M = data.projection_linear_map.matrix() + tester.assertTrue((M.transpose() * M).is_diagonal()) + if i > 1: + # Test whether the map is orthonormal. + tester.assertTrue((M.transpose() * M).is_one()) + if i == 3: + # Test that the extension is indeed minimal. + if self.base_ring() is not AA: + tester.assertFalse(data.polyhedron.base_ring() is AA) + def _polymake_init_(self): """ Return a polymake "Polytope" object corresponding to ``self``. diff --git a/src/sage/geometry/polyhedron/plot.py b/src/sage/geometry/polyhedron/plot.py index a38a0a7db15..7b4eace75ef 100644 --- a/src/sage/geometry/polyhedron/plot.py +++ b/src/sage/geometry/polyhedron/plot.py @@ -988,7 +988,7 @@ def render_wireframe_3d(self, **kwds): sage: cube_proj = cube.projection() sage: wire = cube_proj.render_wireframe_3d() sage: print(wire.tachyon().split('\n')[77]) # for testing - FCylinder base 1.0 1.0 -1.0 apex 1.0 1.0 1.0 rad 0.005 texture... + FCylinder base 1.0 1.0 -1.0 apex -1.0 1.0 -1.0 rad 0.005 texture... """ wireframe = [] for l in self.lines: diff --git a/src/sage/graphs/generators/families.py b/src/sage/graphs/generators/families.py index 555946afecf..e3da579d10a 100644 --- a/src/sage/graphs/generators/families.py +++ b/src/sage/graphs/generators/families.py @@ -1145,26 +1145,42 @@ def CirculantGraph(n, adjacency): return G -def CubeGraph(n): +def CubeGraph(n, embedding=1): r""" - Returns the hypercube in `n` dimensions. + Return the `n`-cube graph, also called the hypercube in `n` dimensions. - The hypercube in `n` dimension is build upon the binary - strings on `n` bits, two of them being adjacent if - they differ in exactly one bit. Hence, the distance - between two vertices in the hypercube is the Hamming - distance. + The hypercube in `n` dimension is build upon the binary strings on `n` bits, + two of them being adjacent if they differ in exactly one bit. Hence, the + distance between two vertices in the hypercube is the Hamming distance. + + INPUT: + + - ``n`` -- integer; the dimension of the cube graph + + - ``embedding`` -- integer (default: ``1``); two embeddings of the `n`-cube + are available: + + - ``1``: the `n`-cube is projected inside a regular `2n`-gonal polygon by + a skew orthogonal projection. See the :wikipedia:`Hypercube` for more + details. + + - ``2``: orthogonal projection of the `n`-cube. This orientation shows + columns of independent vertices such that the neighbors of a vertex are + located in the columns on the left and on the right. The number of + vertices in each column represents rows in Pascal's triangle. See for + instance the :wikipedia:`10-cube` for more details. + + - ``None`` or ``O``: no embedding is provided EXAMPLES: - The distance between `0100110` and `1011010` is - `5`, as expected :: + The distance between `0100110` and `1011010` is `5`, as expected:: sage: g = graphs.CubeGraph(7) sage: g.distance('0100110','1011010') 5 - Plot several `n`-cubes in a Sage Graphics Array :: + Plot several `n`-cubes in a Sage Graphics Array:: sage: g = [] sage: j = [] @@ -1179,55 +1195,84 @@ def CubeGraph(n): ....: j.append(n) ... sage: G = graphics_array(j) - sage: G.show(figsize=[6,4]) # long time - - Use the plot options to display larger `n`-cubes + sage: G.show(figsize=[6,4]) # long time - :: + Use the plot options to display larger `n`-cubes:: - sage: g = graphs.CubeGraph(9) - sage: g.show(figsize=[12,12],vertex_labels=False, vertex_size=20) # long time + sage: g = graphs.CubeGraph(9, embedding=1) + sage: g.show(figsize=[12,12],vertex_labels=False, vertex_size=20) # long time + sage: g = graphs.CubeGraph(9, embedding=2) + sage: g.show(figsize=[12,12],vertex_labels=False, vertex_size=20) # long time AUTHORS: - Robert Miller + - David Coudert """ - theta = float(pi/n) + if embedding == 1: + # construct recursively the adjacency dict and the embedding + theta = float(pi/n) + d = {'': []} + dn = {} + p = {'': (float(0), float(0))} + pn = {} - d = {'':[]} - dn={} - p = {'':(float(0),float(0))} - pn={} + for i in range(n): + ci = float(cos(i*theta)) + si = float(sin(i*theta)) + for v, e in d.items(): + v0 = v + '0' + v1 = v + '1' + l0 = [v1] + l1 = [v0] + for m in e: + l0.append(m + '0') + l1.append(m + '1') + dn[v0] = l0 + dn[v1] = l1 + x,y = p[v] + pn[v0] = (x, y) + pn[v1] = (x + ci, y + si) + d, dn = dn, {} + p, pn = pn, {} + + # construct the graph + G = Graph(d, format='dict_of_lists', pos=p, name="%d-Cube"%n) - # construct recursively the adjacency dict and the positions - for i in range(n): - ci = float(cos(i*theta)) - si = float(sin(i*theta)) - for v,e in d.items(): - v0 = v+'0' - v1 = v+'1' - l0 = [v1] - l1 = [v0] - for m in e: - l0.append(m+'0') - l1.append(m+'1') - dn[v0] = l0 - dn[v1] = l1 - x,y = p[v] - pn[v0] = (x, y) - pn[v1] = (x+ci, y+si) - d,dn = dn,{} - p,pn = pn,{} - - # construct the graph - r = Graph(name="%d-Cube"%n) - r.add_vertices(d.keys()) - for u,L in d.items(): - for v in L: - r.add_edge(u,v) - r.set_pos(p) - - return r + else: + # construct recursively the adjacency dict + d = {'': []} + dn = {} + + for i in range(n): + for v, e in d.items(): + v0 = v + '0' + v1 = v + '1' + l0 = [v1] + l1 = [v0] + for m in e: + l0.append(m + '0') + l1.append(m + '1') + dn[v0] = l0 + dn[v1] = l1 + d, dn = dn, {} + + # construct the graph + G = Graph(d, name="%d-Cube"%n, format='dict_of_lists') + + if embedding == 2: + # Orthogonal projection + s = '0'*n + L = [[] for _ in range(n + 1)] + for u, d in G.breadth_first_search(s, report_distance=True): + L[d].append(u) + + p = G._circle_embedding(list(range(2*n)), radius=(n + 1)//2, angle=pi, return_dict=True) + for i in range(n + 1): + y = p[i][1] / 1.5 + G._line_embedding(L[i], first=(i, y), last=(i, -y), return_dict=False) + + return G def GoethalsSeidelGraph(k,r): r""" diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index c504ec13a0e..3df1de937fa 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -19523,17 +19523,17 @@ def graphplot(self, **options): ....: (0,1,'e'),(0,1,'f'),(0,1,'f'),(2,1,'g'),(2,2,'h')]) sage: GP = g.graphplot(edge_labels=True, color_by_label=True, edge_style='dashed') sage: GP.plot() - Graphics object consisting of 26 graphics primitives + Graphics object consisting of 22 graphics primitives We can modify the :class:`~sage.graphs.graph_plot.GraphPlot` object. Notice that the changes are cumulative:: sage: GP.set_edges(edge_style='solid') sage: GP.plot() - Graphics object consisting of 26 graphics primitives + Graphics object consisting of 22 graphics primitives sage: GP.set_vertices(talk=True) sage: GP.plot() - Graphics object consisting of 26 graphics primitives + Graphics object consisting of 22 graphics primitives """ from sage.graphs.graph_plot import GraphPlot return GraphPlot(graph=self, options=options) @@ -19866,7 +19866,7 @@ def plot(self, **options): sage: g.add_edges([(0, 0, 'a'), (0, 0, 'b'), (0, 1, 'c'), (0, 1, 'd'), ....: (0, 1, 'e'), (0, 1, 'f'), (0, 1, 'f'), (2, 1, 'g'), (2, 2, 'h')]) sage: g.plot(edge_labels=True, color_by_label=True, edge_style='dashed') - Graphics object consisting of 26 graphics primitives + Graphics object consisting of 22 graphics primitives :: diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index b4e0c2da499..7fea475c191 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -428,6 +428,7 @@ lazy_import('sage.graphs.mcqd', ['mcqd'], feature=PythonModule('sage.graphs.mcqd', spkg='mcqd')) + class Graph(GenericGraph): r""" Undirected graph. @@ -1162,9 +1163,7 @@ def __init__(self, data=None, pos=None, loops=None, format=None, from_incidence_matrix(self, data, loops=loops, multiedges=multiedges, weighted=weighted) elif format == 'seidel_adjacency_matrix': - multiedges = False weighted = False - loops = False self.allow_loops(False) self.allow_multiple_edges(False) from .graph_input import from_seidel_adjacency_matrix diff --git a/src/sage/graphs/graph_generators.py b/src/sage/graphs/graph_generators.py index de16509c4b7..a28054af226 100644 --- a/src/sage/graphs/graph_generators.py +++ b/src/sage/graphs/graph_generators.py @@ -1392,7 +1392,11 @@ def fusenes(self, hexagon_count, benzenoids=False): def planar_graphs(self, order, minimum_degree=None, minimum_connectivity=None, - exact_connectivity=False, only_bipartite=False, + exact_connectivity=False, + minimum_edges=None, + maximum_edges=None, + maximum_face_size=None, + only_bipartite=False, dual=False): r""" An iterator over connected planar graphs using the plantri generator. @@ -1429,6 +1433,15 @@ def planar_graphs(self, order, minimum_degree=None, This option cannot be used with ``minimum_connectivity=3``, or if the minimum connectivity is not explicitly set. + - ``minimum_edges`` -- integer (default: ``None``); lower bound on the + number of edges + + - ``maximum_edges`` -- integer (default: ``None``); upper bound on the + number of edges + + - ``maximum_face_size`` -- integer (default: ``None``); upper bound on + the size of a face and so on the maximum degree of the dual graph + - ``only_bipartite`` - default: ``False`` - if ``True`` only bipartite graphs will be generated. This option cannot be used for graphs with a minimum degree larger than 3. @@ -1492,6 +1505,24 @@ def planar_graphs(self, order, minimum_degree=None, sage: list(graphs.planar_graphs(1, minimum_degree=1)) # optional plantri [] + Specifying lower and upper bounds on the number of edges:: + + sage: len(list(graphs.planar_graphs(4))) # optional plantri + 6 + sage: len(list(graphs.planar_graphs(4, minimum_edges=4))) # optional plantri + 4 + sage: len(list(graphs.planar_graphs(4, maximum_edges=4))) # optional plantri + 4 + sage: len(list(graphs.planar_graphs(4, minimum_edges=4, maximum_edges=4))) # optional plantri + 2 + + Specifying the maximum size of a face:: + + sage: len(list(graphs.planar_graphs(4, maximum_face_size=3))) # optional plantri + 1 + sage: len(list(graphs.planar_graphs(4, maximum_face_size=4))) # optional plantri + 3 + TESTS: The number of edges in a planar graph is equal to the number of edges in @@ -1546,6 +1577,31 @@ def planar_graphs(self, order, minimum_degree=None, if only_bipartite and minimum_degree > 3: raise NotImplementedError("Generation of bipartite planar graphs with minimum degree 4 or 5 is not implemented.") + edges = '' + if minimum_edges is None: + if maximum_edges is not None: + if maximum_edges < order - 1: + raise ValueError("the number of edges cannot be less than order - 1") + edges = '-e:{}'.format(maximum_edges) + else: + if minimum_edges > 3*order - 6: + raise ValueError("the number of edges cannot be more than 3*order - 6") + if maximum_edges is None: + edges = '-e{}:'.format(minimum_edges) + elif minimum_edges > maximum_edges: + raise ValueError("the maximum number of edges must be larger " + "or equal to the minimum number of edges") + elif minimum_edges == maximum_edges: + edges = '-e{}'.format(minimum_edges) + else: + edges = '-e{}:{}'.format(minimum_edges, maximum_edges) + + faces = '' + if maximum_face_size is not None: + if maximum_face_size < 3: + raise ValueError("the upper bound on the size of a face must be at least 3") + faces = '-f{}'.format(maximum_face_size) + if order == 0: return @@ -1564,12 +1620,13 @@ def planar_graphs(self, order, minimum_degree=None, from sage.features.graph_generators import Plantri Plantri().require() - cmd = 'plantri -p{}m{}c{}{}{} {}' + cmd = 'plantri -p{}m{}c{}{}{} {} {} {}' command = cmd.format('b' if only_bipartite else '', minimum_degree, minimum_connectivity, 'x' if exact_connectivity else '', 'd' if dual else '', + edges, faces, order) sp = subprocess.Popen(command, shell=True, diff --git a/src/sage/graphs/graph_plot.py b/src/sage/graphs/graph_plot.py index d2d353e4920..073d36fd56e 100644 --- a/src/sage/graphs/graph_plot.py +++ b/src/sage/graphs/graph_plot.py @@ -370,10 +370,10 @@ def set_vertices(self, **vertex_options): ....: edge_style='dashed') sage: GP.set_vertices(talk=True) sage: GP.plot() - Graphics object consisting of 26 graphics primitives + Graphics object consisting of 22 graphics primitives sage: GP.set_vertices(vertex_color='green', vertex_shape='^') sage: GP.plot() - Graphics object consisting of 26 graphics primitives + Graphics object consisting of 22 graphics primitives .. PLOT:: @@ -505,7 +505,7 @@ def set_edges(self, **edge_options): ....: edge_style='dashed') sage: GP.set_edges(edge_style='solid') sage: GP.plot() - Graphics object consisting of 26 graphics primitives + Graphics object consisting of 22 graphics primitives .. PLOT:: @@ -521,7 +521,7 @@ def set_edges(self, **edge_options): sage: GP.set_edges(edge_color='black') sage: GP.plot() - Graphics object consisting of 26 graphics primitives + Graphics object consisting of 22 graphics primitives .. PLOT:: @@ -543,7 +543,7 @@ def set_edges(self, **edge_options): ....: edge_style='dashed') sage: GP.set_edges(edge_style='solid') sage: GP.plot() - Graphics object consisting of 28 graphics primitives + Graphics object consisting of 24 graphics primitives .. PLOT:: @@ -559,7 +559,7 @@ def set_edges(self, **edge_options): sage: GP.set_edges(edge_color='black') sage: GP.plot() - Graphics object consisting of 28 graphics primitives + Graphics object consisting of 24 graphics primitives .. PLOT:: @@ -601,6 +601,15 @@ def set_edges(self, **edge_options): sage: G = Graph([(0, 1), (0, 1)], multiedges=True) sage: G.plot(edge_colors={"red": [(1, 0)]}) Graphics object consisting of 5 graphics primitives + + Ticket :trac:`31542` is fixed:: + + sage: stnc = 'ABCCCCDABCDABCDA' + sage: g = DiGraph({}, loops=True, multiedges=True) + sage: for a, b in [(stnc[i], stnc[i + 1]) for i in range(len(stnc) - 1)]: + ....: g.add_edge(a, b, b) + sage: g.plot(color_by_label=True, edge_style='solid', layout='circular') + Graphics object consisting of 23 graphics primitives """ for arg in edge_options: self._options[arg] = edge_options[arg] @@ -634,7 +643,7 @@ def append_or_set(key, label, color, head): else: edges_to_draw[key] = [(label, color, head)] - v_to_int = {v: i for i,v in enumerate(self._graph)} + v_to_int = {v: i for i, v in enumerate(self._graph)} if self._options['color_by_label'] or isinstance(self._options['edge_colors'], dict): if self._options['color_by_label']: @@ -663,16 +672,8 @@ def append_or_set(key, label, color, head): edges_drawn.append((edge[0], edge[1], label)) else: label = edge[2] - labelList = self._graph.edge_label(edge[0], edge[1]) - if isinstance(labelList, list): - for l in labelList: - if l == label: - append_or_set(key, label, color, head) - edges_drawn.append((edge[0], edge[1], label)) - else: - if labelList == label: - append_or_set(key, label, color, head) - edges_drawn.append((edge[0], edge[1], label)) + append_or_set(key, label, color, head) + edges_drawn.append((edge[0], edge[1], label)) # Add unspecified edges (default color black set in DEFAULT_PLOT_OPTIONS) for edge in self._graph.edge_iterator(): @@ -1208,7 +1209,7 @@ def plot(self, **kwds): sage: g.add_edges([(0,0,'a'),(0,0,'b'),(0,1,'c'),(0,1,'d'), ....: (0,1,'e'),(0,1,'f'),(0,1,'f'),(2,1,'g'),(2,2,'h')]) sage: g.graphplot(edge_labels=True, color_by_label=True, edge_style='dashed').plot() - Graphics object consisting of 26 graphics primitives + Graphics object consisting of 22 graphics primitives .. PLOT:: @@ -1220,7 +1221,7 @@ def plot(self, **kwds): The ``edge_style`` option may be provided in the short format too:: sage: g.graphplot(edge_labels=True, color_by_label=True, edge_style='--').plot() - Graphics object consisting of 26 graphics primitives + Graphics object consisting of 22 graphics primitives TESTS: diff --git a/src/sage/graphs/views.pyx b/src/sage/graphs/views.pyx index 201e89455fa..33e22f3ea27 100644 --- a/src/sage/graphs/views.pyx +++ b/src/sage/graphs/views.pyx @@ -626,7 +626,6 @@ cdef class EdgesView: elif i < 0: return list(self)[i] else: - i = int(i) # For Python < 3.7 where islice doesn't support non-int try: return next(islice(self, i, i + 1, 1)) except StopIteration: diff --git a/src/sage/groups/abelian_gps/abelian_group.py b/src/sage/groups/abelian_gps/abelian_group.py index e6d8e169038..ea5e8289e43 100644 --- a/src/sage/groups/abelian_gps/abelian_group.py +++ b/src/sage/groups/abelian_gps/abelian_group.py @@ -1106,6 +1106,7 @@ def order(self): cardinality = order + @cached_method def permutation_group(self): r""" Return the permutation group isomorphic to this abelian group. @@ -1646,6 +1647,12 @@ def __init__(self, ambient, gens, names="f"): (a, b^2, c) sage: F.order() +Infinity + + Testing ticket :trac:`18863`:: + + sage: G = AbelianGroup(5,[2]) + sage: G.subgroup([prod(g^k for g,k in zip(G.gens(),[1,-2,3,-4,5]))]) + Multiplicative Abelian subgroup isomorphic to Z generated by {f0*f1^-2*f2^3*f3^-4*f4} """ from sage.interfaces.all import gap if not isinstance(ambient, AbelianGroup_class): @@ -1663,15 +1670,14 @@ def __init__(self, ambient, gens, names="f"): if invs0!=[]: Gfgens = [x for x in ambient.variable_names() if ambient_invs[Ggens.index(x)] != 0] - Ggens0 = [x for x in ambient.variable_names() if - ambient_invs[Ggens.index(x)] == 0] - ## ^^ only look at "finite" names Gf = AbelianGroup(invsf, names=Gfgens) s1 = "G:= %s; gens := GeneratorsOfGroup(G)"%Gf._gap_init_() gap.eval(s1) - Hgensf = [x for x in Hgens if len(set(Ggens0).intersection(set(list(str(x)))))==0] - # computes the gens of H which do not occur ^^ in the infinite part of G - Hgens0 = [x for x in Hgens if not(x in Hgensf)] + Hgens0 = [ + x for x in Hgens if + any(e!=0 and (g.order() not in ZZ) for e,g in zip(x.exponents(),ambient.gens())) + ] + Hgensf = [x for x in Hgens if x not in Hgens0] # the "infinite" generators of H for i in range(len(Gfgens)): cmd = ("%s := gens["+str(i+1)+"]")%Gfgens[i] diff --git a/src/sage/groups/additive_abelian/additive_abelian_wrapper.py b/src/sage/groups/additive_abelian/additive_abelian_wrapper.py index cfbd1dac9c1..b27c8a8f1a0 100644 --- a/src/sage/groups/additive_abelian/additive_abelian_wrapper.py +++ b/src/sage/groups/additive_abelian/additive_abelian_wrapper.py @@ -53,6 +53,7 @@ from sage.rings.all import ZZ from sage.categories.morphism import Morphism from sage.structure.element import parent +from sage.modules.free_module_element import vector class UnwrappingMorphism(Morphism): @@ -228,13 +229,102 @@ def _discrete_exp(self, v): # DUMB IMPLEMENTATION! return sum([self._gen_elements[i] * ZZ(v[i]) for i in range(len(v))], self.universe()(0)) - def _discrete_log(self,x): + def _discrete_log_pgroup(self, p, aa, b): r""" - Given an element of the ambient group, attempt to express it in terms of the - generators of self. + Attempt to express an element of p-power order in terms of + generators of a p-subgroup of this group. + + Used as a subroutine in the _discrete_log() method. + + ALGORITHM: + + This implements a basic version of the recursive algorithm + from [Suth2008]_. + The base cases are handled using a variant of Shanks' + baby-step giant-step algorithm for products of cyclic groups. + + EXAMPLES:: + + sage: G = AdditiveAbelianGroup([5, 5**2, 5**4, 5**4]) + sage: (a, b, c, d) = gs = G.gens() + sage: A = AdditiveAbelianGroupWrapper(a.parent(), gs, [g.order() for g in gs]) + sage: A._discrete_log_pgroup(5, gs, a + 17 * b + 123 * c + 456 * d) + (1, 17, 123, 456) + """ + from sage.arith.misc import valuation + from sage.functions.other import ceil, sqrt + from itertools import product as iproduct + + vals = [valuation(a.order(), p) for a in aa] + qq = lambda j, k: vector(p ** (j + max(0, v - k)) for a, v in zip(aa, vals)) + subbasis = lambda j, k: [q * a for q, a in zip(qq(j, k), aa)] + dotprod = lambda xs, ys: sum(x * y for x, y in zip(xs, ys)) + + def _base(j, k, c): + + assert k - j == 1 + aajk = subbasis(j, k) + assert all(a.order() in (1, p) for a in aajk) + idxs = [i for i, a in enumerate(aajk) if a.order() == p] + + rs = [([0], [0]) for i in range(len(aajk))] + for i in range(len(idxs)): + rs[idxs[i]] = (range(p), [0]) if i % 2 else ([0], range(p)) + if len(idxs) % 2: + m = ceil(sqrt(p)) + rs[idxs[-1]] = range(0, p, m), range(m) + + tab = {} + for x in iproduct(*(r for r, _ in rs)): + key = dotprod(x, aajk) + if hasattr(key, 'set_immutable'): + key.set_immutable() + tab[key] = vector(x) + for y in iproduct(*(r for _, r in rs)): + key = c - dotprod(y, aajk) + if hasattr(key, 'set_immutable'): + key.set_immutable() + if key in tab: + return tab[key] + vector(y) + + raise TypeError('Not in group') + + def _rec(j, k, c): + + assert 0 <= j < k + + if k - j <= 1: # base case + return _base(j, k, c) + + w = 2 + js = list(range(j, k, (k-j+w-1) // w)) + [k] + assert len(js) == w + 1 + + x = vector([0] * len(aa)) + for i in reversed(range(w)): + + gamma = p ** (js[i] - j) * c - dotprod(x, subbasis(js[i], k)) + + v = _rec(js[i], js[i+1], gamma) + + assert not any(q1 % q2 for q1, q2 in zip(qq(js[i], js[i+1]), qq(js[i], k))) + x += vector(q1 // q2 * r for q1, q2, r in zip(qq(js[i], js[i+1]), qq(js[i], k), v)) + + return x + + return _rec(0, max(vals), b) + + def _discrete_log(self, x, gens=None): + r""" + Given an element of the ambient group, attempt to express it in terms + of the generators of this group or the given generators of a subgroup. EXAMPLES:: + sage: G = AdditiveAbelianGroup([2, 2*3, 2*3*5, 2*3*5*7, 2*3*5*7*11]) + sage: A = AdditiveAbelianGroupWrapper(G.0.parent(), G.gens(), [g.order() for g in G.gens()]) + sage: A._discrete_log(G.0 + 5 * G.1 + 23 * G.2 + 127 * G.3 + 539 * G.4) + (1, 5, 23, 127, 539) sage: V = Zmod(8)**2; G = AdditiveAbelianGroupWrapper(V, [[2,2],[4,0]], [4, 2]) sage: G._discrete_log(V([6, 2])) (1, 1) @@ -242,20 +332,42 @@ def _discrete_log(self,x): Traceback (most recent call last): ... TypeError: Not in group + sage: F. = GF(1009**2, modulus=x**2+11); E = EllipticCurve(j=F(940)) + sage: P, Q = E(900*t + 228, 974*t + 185), E(1007*t + 214, 865*t + 802) + sage: E.abelian_group()._discrete_log(123 * P + 777 * Q, [P, Q]) + (123, 777) sage: G = AdditiveAbelianGroupWrapper(QQbar, [sqrt(2)], [0]) sage: G._discrete_log(QQbar(2*sqrt(2))) Traceback (most recent call last): ... NotImplementedError: No black-box discrete log for infinite abelian groups """ - # EVEN DUMBER IMPLEMENTATION! + from sage.arith.misc import CRT_list from sage.rings.infinity import Infinity + if self.order() == Infinity: raise NotImplementedError("No black-box discrete log for infinite abelian groups") - u = [y for y in self.list() if y.element() == x] - if len(u) == 0: raise TypeError("Not in group") - if len(u) > 1: raise NotImplementedError - return u[0].vector() + + if gens is None: + gens = self.gens() + + gens = [g if parent(g) is self.universe() else g.element() for g in gens] + x = x if parent(x) is self.universe() else x.element() + + crt_data = [[] for _ in gens] + for p, e in self.order().factor(): + cofactor = self.order() // p ** e + pgens = [cofactor * g for g in gens] + y = cofactor * x + + plog = self._discrete_log_pgroup(p, pgens, y) + + for i, (r, g) in enumerate(zip(plog, pgens)): + crt_data[i].append((r, ZZ(g.order()))) + + res = vector(CRT_list(*map(list, zip(*l))) for l in crt_data) + assert x == sum(r * g for r, g in zip(res, gens)) + return res def _element_constructor_(self, x, check=False): r""" diff --git a/src/sage/groups/galois_group.py b/src/sage/groups/galois_group.py index d019082dc6d..218138e13de 100644 --- a/src/sage/groups/galois_group.py +++ b/src/sage/groups/galois_group.py @@ -1,15 +1,22 @@ r""" -Galois groups of field extensions +Galois groups of field extensions. + +We don't necessarily require extensions to be normal, but we do require them to be separable. +When an extension is not normal, the Galois group refers to +the automorphism group of the normal closure. AUTHORS: - David Roe (2019): initial version """ -from sage.groups.perm_gps.permgroup import PermutationGroup_generic +from sage.groups.perm_gps.permgroup import PermutationGroup, PermutationGroup_generic +from sage.groups.abelian_gps.abelian_group import AbelianGroup_class from sage.sets.finite_enumerated_set import FiniteEnumeratedSet from sage.misc.lazy_attribute import lazy_attribute +from sage.misc.cachefunc import cached_method from sage.structure.category_object import normalize_names +from sage.rings.integer_ring import ZZ def _alg_key(self, algorithm=None, recompute=False): r""" @@ -32,70 +39,22 @@ def _alg_key(self, algorithm=None, recompute=False): algorithm = self._get_algorithm(algorithm) return algorithm -class GaloisGroup(PermutationGroup_generic): +class _GaloisMixin: r""" - The group of automorphisms of a Galois closure of a given field. - - INPUT: - - - ``field`` -- a field, separable over its base - - - ``names`` -- a string or tuple of length 1, giving a variable name for the splitting field - - - ``gc_numbering`` -- boolean, whether to express permutations in terms of the - roots of the defining polynomial of the splitting field (versus the defining polynomial - of the original extension). The default value may vary based on the type of field. + This class provides some methods for Galois groups to be used for both permutation groups + and abelian groups. """ - # Subclasses should implement the following methods and lazy attributes - - # methods (taking algorithm and recompute as arguments): - # * transitive_number - # * order - # * _element_constructor_ -- for creating elements - - # lazy_attributes - # * _gcdata -- a pair, the Galois closure and an embedding of the top field into it - # * _gens -- the list of generators of this group, as elements. This is not computed during __init__ for speed - # * _elts -- the list of all elements of this group. - - # * Element (for coercion) - - def __init__(self, field, algorithm=None, names=None, gc_numbering=False): - r""" - EXAMPLES:: - - sage: R. = ZZ[] - sage: K. = NumberField(x^3 + 2*x + 2) - sage: G = K.galois_group() - sage: TestSuite(G).run() - """ - self._field = field - self._default_algorithm = algorithm - self._base = field.base_field() - self._gc_numbering = gc_numbering - if names is None: - # add a c for Galois closure - names = field.variable_name() + 'c' - self._gc_names = normalize_names(1, names) - # We do only the parts of the initialization of PermutationGroup_generic - # that don't depend on _gens - from sage.categories.permutation_groups import PermutationGroups - category = PermutationGroups().FinitelyGenerated().Finite() - # Note that we DON'T call the __init__ method for PermutationGroup_generic - # Instead, the relevant attributes are computed lazily - super(PermutationGroup_generic, self).__init__(category=category) - def _repr_(self): """ String representation of this Galois group EXAMPLES:: - sage: from sage.groups.galois_group import GaloisGroup + sage: from sage.groups.galois_group import GaloisGroup_perm sage: R. = ZZ[] sage: K. = NumberField(x^3 + 2*x + 2) sage: G = K.galois_group() - sage: GaloisGroup._repr_(G) + sage: GaloisGroup_perm._repr_(G) 'Galois group of x^3 + 2*x + 2' """ f = self._field.defining_polynomial() @@ -137,6 +96,42 @@ def top_field(self): """ return self._field + @lazy_attribute + def _field_degree(self): + """ + Degree of the top field over its base. + + EXAMPLES:: + + sage: R. = ZZ[] + sage: K. = NumberField(x^3 + 2*x + 2) + sage: L. = K.extension(x^2 + 3*a^2 + 8) + sage: GK = K.galois_group() + sage: GL = L.galois_group() + doctest:warning + ... + DeprecationWarning: Use .absolute_field().galois_group() if you want the Galois group of the absolute field + See https://trac.sagemath.org/28782 for details. + sage: GK._field_degree + 3 + + Despite the fact that `L` is a relative number field, the Galois group + is computed for the corresponding absolute extension of the rationals. + + This behavior may change in the future:: + + sage: GL._field_degree + 6 + sage: GL.transitive_label() + '6T2' + sage: GL + Galois group 6T2 ([3]2) with order 6 of x^2 + 3*a^2 + 8 + """ + try: + return self._field.degree() + except NotImplementedError: # relative number fields don't support degree + return self._field.absolute_degree() + def transitive_label(self): r""" Return the transitive label for the action of this Galois group on the roots of @@ -150,10 +145,7 @@ def transitive_label(self): sage: G.transitive_label() '8T44' """ - try: - return "%sT%s" % (self._field.degree(), self.transitive_number()) - except NotImplementedError: # relative number fields don't support degree - return "%sT%s" % (self._field.relative_degree(), self.transitive_number()) + return "%sT%s" % (self._field_degree, self.transitive_number()) def is_galois(self): r""" @@ -164,11 +156,11 @@ def is_galois(self): sage: R. = ZZ[] sage: K. = NumberField(x^8 - x^5 + x^4 - x^3 + 1) sage: G = K.galois_group() - sage: from sage.groups.galois_group import GaloisGroup - sage: GaloisGroup.is_galois(G) + sage: from sage.groups.galois_group import GaloisGroup_perm + sage: GaloisGroup_perm.is_galois(G) False """ - return self.order() == self._field.degree() + return self.order() == self._field_degree @lazy_attribute def _galois_closure(self): @@ -217,6 +209,59 @@ def _gc_map(self): """ return self._gcdata[1] +class GaloisGroup_perm(_GaloisMixin, PermutationGroup_generic): + r""" + The group of automorphisms of a Galois closure of a given field. + + INPUT: + + - ``field`` -- a field, separable over its base + + - ``names`` -- a string or tuple of length 1, giving a variable name for the splitting field + + - ``gc_numbering`` -- boolean, whether to express permutations in terms of the + roots of the defining polynomial of the splitting field (versus the defining polynomial + of the original extension). The default value may vary based on the type of field. + """ + # Subclasses should implement the following methods and lazy attributes + + # methods (taking algorithm and recompute as arguments): + # * transitive_number + # * order + # * _element_constructor_ -- for creating elements + + # lazy_attributes + # * _gcdata -- a pair, the Galois closure and an embedding of the top field into it + # * _gens -- the list of generators of this group, as elements. This is not computed during __init__ for speed + # * _elts -- the list of all elements of this group. + + # * Element (for coercion) + + def __init__(self, field, algorithm=None, names=None, gc_numbering=False): + r""" + EXAMPLES:: + + sage: R. = ZZ[] + sage: K. = NumberField(x^3 + 2*x + 2) + sage: G = K.galois_group() + sage: TestSuite(G).run() + """ + self._field = field + self._default_algorithm = algorithm + self._base = field.base_field() + self._gc_numbering = gc_numbering + if names is None: + # add a c for Galois closure + names = field.variable_name() + 'c' + self._gc_names = normalize_names(1, names) + # We do only the parts of the initialization of PermutationGroup_generic + # that don't depend on _gens + from sage.categories.permutation_groups import PermutationGroups + category = PermutationGroups().FinitelyGenerated().Finite() + # Note that we DON'T call the __init__ method for PermutationGroup_generic + # Instead, the relevant attributes are computed lazily + super(PermutationGroup_generic, self).__init__(category=category) + @lazy_attribute def _deg(self): r""" @@ -302,3 +347,118 @@ def ngens(self): 1 """ return len(self._gens) + +class GaloisGroup_ab(_GaloisMixin, AbelianGroup_class): + r""" + Abelian Galois groups + """ + def __init__(self, field, generator_orders, algorithm=None, gen_names='sigma'): + r""" + Initialize this Galois group. + + TESTS:: + + sage: TestSuite(GF(9).galois_group()).run() + """ + self._field = field + self._default_algorithm = algorithm + AbelianGroup_class.__init__(self, generator_orders, gen_names) + + def is_galois(self): + r""" + Abelian extensions are Galois. + + For compatibility with Galois groups of number fields. + + EXAMPLES:: + + sage: GF(9).galois_group().is_galois() + True + """ + return True + + @lazy_attribute + def _gcdata(self): + r""" + Return the Galois closure (ie, the finite field itself) together with the identity + + EXAMPLES:: + + sage: GF(3^2).galois_group()._gcdata + (Finite Field in z2 of size 3^2, + Identity endomorphism of Finite Field in z2 of size 3^2) + """ + k = self._field + return k, k.Hom(k).identity() + + @cached_method + def permutation_group(self): + r""" + Return a permutation group giving the action on the roots of a defining polynomial. + + This is the regular representation for the abelian group, which is not necessarily the smallest degree permutation representation. + + EXAMPLES:: + + sage: GF(3^10).galois_group().permutation_group() + Permutation Group with generators [(1,2,3,4,5,6,7,8,9,10)] + """ + return PermutationGroup(gap_group=self._gap_().RegularActionHomomorphism().Image()) + + @cached_method(key=_alg_key) + def transitive_number(self, algorithm=None, recompute=False): + r""" + Return the transitive number for the action on the roots of the defining polynomial. + + For abelian groups, there is only one transitive action up to isomorphism + (left multiplication of the group on itself), so we identify that action. + + EXAMPLES:: + + sage: from sage.groups.galois_group import GaloisGroup_ab + sage: Gtest = GaloisGroup_ab(field=None, generator_orders=(2,2,4)) + sage: Gtest.transitive_number() + 2 + """ + return ZZ(self.permutation_group()._gap_().TransitiveIdentification()) + +class GaloisGroup_cyc(GaloisGroup_ab): + r""" + Cyclic Galois groups + """ + def transitive_number(self, algorithm=None, recompute=False): + r""" + Return the transitive number for the action on the roots of the defining polynomial. + + EXAMPLES:: + + sage: GF(2^8).galois_group().transitive_number() + 1 + sage: GF(3^32).galois_group().transitive_number() + 33 + sage: GF(2^60).galois_group().transitive_number() + Traceback (most recent call last): + ... + NotImplementedError: transitive database only computed up to degree 47 + """ + d = self.order() + if d > 47: + raise NotImplementedError("transitive database only computed up to degree 47") + elif d == 32: + # I don't know why this case is special, but you can check this in Magma (GAP only goes up to 22) + return ZZ(33) + else: + return ZZ(1) + + def signature(self): + r""" + Return 1 if contained in the alternating group, -1 otherwise. + + EXAMPLES:: + + sage: GF(3^2).galois_group().signature() + -1 + sage: GF(3^3).galois_group().signature() + 1 + """ + return ZZ(1) if (self._field.degree() % 2) else ZZ(-1) diff --git a/src/sage/interacts/test_jupyter.rst b/src/sage/interacts/test_jupyter.rst index 3f307d2c97c..12bda4a4615 100644 --- a/src/sage/interacts/test_jupyter.rst +++ b/src/sage/interacts/test_jupyter.rst @@ -39,107 +39,107 @@ Test all interacts from the Sage interact library:: sage: test(interacts.algebra.polar_prime_spiral) # long time Interactive function with 6 widgets - interval: IntRangeSlider(value=(1, 1000), description=u'range', max=4000, min=1, step=10) - show_factors: Checkbox(value=True, description=u'show_factors') - highlight_primes: Checkbox(value=True, description=u'highlight_primes') - show_curves: Checkbox(value=True, description=u'show_curves') - n: IntSlider(value=89, description=u'number $n$', max=200, min=1) - dpi: IntSlider(value=100, description=u'dpi', max=300, min=10, step=10) + interval: IntRangeSlider(value=(1, 1000), description='range', max=4000, min=1, step=10) + show_factors: Checkbox(value=True, description='show_factors') + highlight_primes: Checkbox(value=True, description='highlight_primes') + show_curves: Checkbox(value=True, description='show_curves') + n: IntSlider(value=89, description='number $n$', max=200, min=1) + dpi: IntSlider(value=100, description='dpi', max=300, min=10, step=10)

Polar Prime Spiral

- - Pink Curve: - Green Curve: + \(n = 89\) + Pink Curve: \(n^2 + 8\) + Green Curve: \(n^2 + n + -1\) sage: test(interacts.calculus.taylor_polynomial) Interactive function with 3 widgets - title: HTMLText(value=u'

Taylor polynomial

') - f: EvalText(value=u'e^(-x)*sin(x)', description=u'$f(x)=$', layout=Layout(max_width=u'81em')) - order: SelectionSlider(description=u'order', options=(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), value=1) - - + title: HTMLText(value='

Taylor polynomial

') + f: EvalText(value='e^(-x)*sin(x)', description='$f(x)=$', layout=Layout(max_width='81em')) + order: SelectionSlider(description='order', options=(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), value=1) + \(f(x)\;=\;e^{\left(-x\right)} \sin\left(x\right)\) + \(\hat{f}(x;0)\;=\;x+\mathcal{O}(x^{2})\) sage: test(interacts.calculus.definite_integral) Interactive function with 6 widgets - title: HTMLText(value=u'

Definite integral

') - f: EvalText(value=u'3*x', description=u'$f(x)=$', layout=Layout(max_width=u'81em')) - g: EvalText(value=u'x^2', description=u'$g(x)=$', layout=Layout(max_width=u'81em')) - interval: IntRangeSlider(value=(0, 3), description=u'Interval', max=10, min=-10) - x_range: IntRangeSlider(value=(0, 3), description=u'plot range (x)', max=10, min=-10) - selection: Dropdown(description=u'Select', index=2, options=('f', 'g', 'f and g', 'f - g'), value='f and g') -
+ title: HTMLText(value='

Definite integral

') + f: EvalText(value='3*x', description='$f(x)=$', layout=Layout(max_width='81em')) + g: EvalText(value='x^2', description='$g(x)=$', layout=Layout(max_width='81em')) + interval: IntRangeSlider(value=(0, 3), description='Interval', max=10, min=-10) + x_range: IntRangeSlider(value=(0, 3), description='plot range (x)', max=10, min=-10) + selection: Dropdown(description='Select', index=2, options=('f', 'g', 'f and g', 'f - g'), value='f and g') + \(\int_{0.00}^{3.00}(\color{Blue}{f(x)})\,\mathrm{d}x=\int_{0.00}^{3.00}(3 \, x)\,\mathrm{d}x=13.50\)
\(\int_{0.00}^{3.00}(\color{Green}{g(x)})\,\mathrm{d}x=\int_{0.00}^{3.00}(x^{2})\,\mathrm{d}x=9.00\) sage: test(interacts.calculus.function_derivative) Interactive function with 4 widgets - title: HTMLText(value=u'

Derivative grapher

') - function: EvalText(value=u'x^5-3*x^3+1', description=u'Function:', layout=Layout(max_width=u'81em')) - x_range: FloatRangeSlider(value=(-2.0, 2.0), description=u'Range (x)', max=15.0, min=-15.0) - y_range: FloatRangeSlider(value=(-8.0, 6.0), description=u'Range (y)', max=15.0, min=-15.0) -
-
-
+ title: HTMLText(value='

Derivative grapher

') + function: EvalText(value='x^5-3*x^3+1', description='Function:', layout=Layout(max_width='81em')) + x_range: FloatRangeSlider(value=(-2.0, 2.0), description='Range (x)', max=15.0, min=-15.0) + y_range: FloatRangeSlider(value=(-8.0, 6.0), description='Range (y)', max=15.0, min=-15.0) +
\(\color{Blue}{f(x) = x^{5} - 3 \, x^{3} + 1}\)
+
\(\color{Green}{f'(x) = 5 \, x^{4} - 9 \, x^{2}}\)
+
\(\color{Red}{f''(x) = 20 \, x^{3} - 18 \, x}\)
sage: test(interacts.calculus.difference_quotient) Interactive function with 5 widgets - title: HTMLText(value=u'

Difference quotient

') - f: EvalText(value=u'sin(x)', description=u'f(x)', layout=Layout(max_width=u'81em')) - interval: FloatRangeSlider(value=(0.0, 10.0), description=u'Range', max=10.0) - a: IntSlider(value=5, description=u'$a$', max=10) - x0: IntSlider(value=2, description=u'$x_0$ (start point)', max=10) + title: HTMLText(value='

Difference quotient

') + f: EvalText(value='sin(x)', description='f(x)', layout=Layout(max_width='81em')) + interval: FloatRangeSlider(value=(0.0, 10.0), description='Range', max=10.0) + a: IntSlider(value=5, description='$a$', max=10) + x0: IntSlider(value=2, description='$x_0$ (start point)', max=10)

Difference Quotient

Difference Quotient

-
-
- -
+
\(\text{Line's equation:}\) + \(y = 1/3*(x - 5)*(sin(5) - sin(2)) + sin(5)\)
+ \(\text{Slope:}\) + \(k = \frac{f(x_0)-f(a)}{x_0-a} = -0.62274\)
sage: test(interacts.calculus.quadratic_equation) Interactive function with 3 widgets - A: IntSlider(value=1, description=u'A', max=7, min=-7) - B: IntSlider(value=1, description=u'B', max=7, min=-7) - C: IntSlider(value=-2, description=u'C', max=7, min=-7) + A: IntSlider(value=1, description='A', max=7, min=-7) + B: IntSlider(value=1, description='B', max=7, min=-7) + C: IntSlider(value=-2, description='C', max=7, min=-7)

The Solutions of the Quadratic Equation

- - - + \(x^2 + x - 2 = 0\) + \(Ax^2 + Bx + C = 0\) + \(x = \frac{-B\pm\sqrt{B^2-4AC}}{2A} = \frac{-1\pm\sqrt{1^2-4*1*-2}}{2*1} = \frac{-1\pm\sqrt{\color{Green}{9}}}{2} = \begin{cases}1\\-2\end{cases}\) sage: test(interacts.calculus.secant_method) Interactive function with 5 widgets - title: HTMLText(value=u'

Secant method for numerical root finding

') - f: EvalText(value=u'x^2-2', description=u'f(x)', layout=Layout(max_width=u'81em')) - interval: IntRangeSlider(value=(0, 4), description=u'range', max=5, min=-5) - d: IntSlider(value=3, description=u'10^-d precision', max=16, min=1) - maxn: IntSlider(value=10, description=u'max iterations', max=15) - - - - + title: HTMLText(value='

Secant method for numerical root finding

') + f: EvalText(value='x^2-2', description='f(x)', layout=Layout(max_width='81em')) + interval: IntRangeSlider(value=(0, 4), description='range', max=5, min=-5) + d: IntSlider(value=3, description='10^-d precision', max=16, min=1) + maxn: IntSlider(value=10, description='max iterations', max=15) + \(\text{Precision }h = 10^{-d}=10^{-3}=0.00100\) + \({c = }1.4144038097709382\) + \({f(c) = }0.0005381370945443109\) + \(6 \text{ iterations}\) sage: test(interacts.calculus.newton_method) Interactive function with 7 widgets - title: HTMLText(value=u'

Newton method

') - f: EvalText(value=u'x^2 - 2', description=u'f', layout=Layout(max_width=u'81em')) - c: IntSlider(value=6, description=u'Start ($x$)', max=10, min=-10) - d: IntSlider(value=3, description=u'$10^{-d}$ precision', max=16, min=1) - maxn: IntSlider(value=10, description=u'max iterations', max=15) - interval: IntRangeSlider(value=(0, 6), description=u'Interval', max=10, min=-10) - list_steps: Checkbox(value=False, description=u'List steps') - - - - + title: HTMLText(value='

Newton method

') + f: EvalText(value='x^2 - 2', description='f', layout=Layout(max_width='81em')) + c: IntSlider(value=6, description='Start ($x$)', max=10, min=-10) + d: IntSlider(value=3, description='$10^{-d}$ precision', max=16, min=1) + maxn: IntSlider(value=10, description='max iterations', max=15) + interval: IntRangeSlider(value=(0, 6), description='Interval', max=10, min=-10) + list_steps: Checkbox(value=False, description='List steps') + \(\text{Precision } 2h = 0.001\) + \({c = }1.4142141576301823\) + \({f(c) = }1.6836416460996873 \times 10^{-06}\) + \(6 \text{ iterations}\) sage: test(interacts.calculus.trapezoid_integration) Interactive function with 7 widgets - title: HTMLText(value=u'

Trapezoid integration

') - f: EvalText(value=u'x^2-5*x + 10', description=u'$f(x)=$', layout=Layout(max_width=u'81em')) - n: IntSlider(value=5, description=u'# divisions', min=1) - interval_input: ToggleButtons(description=u'Integration interval', options=('from slider', 'from keyboard'), value='from slider') - interval_s: IntRangeSlider(value=(0, 8), description=u'slider: ', max=10, min=-10) - interval_g: Grid(value=[[0, 8]], children=(Label(value=u'keyboard: '), VBox(children=(EvalText(value=u'0', layout=Layout(max_width=u'5em')),)), VBox(children=(EvalText(value=u'8', layout=Layout(max_width=u'5em')),)))) - output_form: ToggleButtons(description=u'Computations form', options=('traditional', 'table', 'none'), value='traditional') - Function - Integral value to seven decimal places is: + title: HTMLText(value='

Trapezoid integration

') + f: EvalText(value='x^2-5*x + 10', description='$f(x)=$', layout=Layout(max_width='81em')) + n: IntSlider(value=5, description='# divisions', min=1) + interval_input: ToggleButtons(description='Integration interval', options=('from slider', 'from keyboard'), value='from slider') + interval_s: IntRangeSlider(value=(0, 8), description='slider: ', max=10, min=-10) + interval_g: Grid(value=[[0, 8]], children=(Label(value='keyboard: '), VBox(children=(EvalText(value='0', layout=Layout(max_width='5em')),)), VBox(children=(EvalText(value='8', layout=Layout(max_width='5em')),)))) + output_form: ToggleButtons(description='Computations form', options=('traditional', 'table', 'none'), value='traditional') + Function \(f(x)=x^{2} - 5 \, x + 10\) + Integral value to seven decimal places is: \(\displaystyle\int_{0.00}^{8.00} {f(x) \, \mathrm{d}x} = 90.666667\)
\begin{align*} @@ -154,15 +154,15 @@ Test all interacts from the Sage interact library:: sage: test(interacts.calculus.simpson_integration) Interactive function with 7 widgets - title: HTMLText(value=u'

Simpson integration

') - f: EvalText(value=u'x*sin(x)+x+1', description=u'$f(x)=$', layout=Layout(max_width=u'81em')) - n: IntSlider(value=6, description=u'# divisions', min=2, step=2) - interval_input: ToggleButtons(description=u'Integration interval', options=('from slider', 'from keyboard'), value='from slider') - interval_s: IntRangeSlider(value=(0, 10), description=u'slider: ', max=10, min=-10) - interval_g: Grid(value=[[0, 10]], children=(Label(value=u'keyboard: '), VBox(children=(EvalText(value=u'0', layout=Layout(max_width=u'5em')),)), VBox(children=(EvalText(value=u'10', layout=Layout(max_width=u'5em')),)))) - output_form: ToggleButtons(description=u'Computations form', options=('traditional', 'table', 'none'), value='traditional') - Function - Integral value to seven decimal places is: + title: HTMLText(value='

Simpson integration

') + f: EvalText(value='x*sin(x)+x+1', description='$f(x)=$', layout=Layout(max_width='81em')) + n: IntSlider(value=6, description='# divisions', min=2, step=2) + interval_input: ToggleButtons(description='Integration interval', options=('from slider', 'from keyboard'), value='from slider') + interval_s: IntRangeSlider(value=(0, 10), description='slider: ', max=10, min=-10) + interval_g: Grid(value=[[0, 10]], children=(Label(value='keyboard: '), VBox(children=(EvalText(value='0', layout=Layout(max_width='5em')),)), VBox(children=(EvalText(value='10', layout=Layout(max_width='5em')),)))) + output_form: ToggleButtons(description='Computations form', options=('traditional', 'table', 'none'), value='traditional') + Function \(f(x)=x \sin\left(x\right) + x + 1\) + Integral value to seven decimal places is: \(\displaystyle\int_{0.00}^{10.00} {f(x) \, \mathrm{d}x} = 67.846694\)
\begin{align*} @@ -177,110 +177,109 @@ Test all interacts from the Sage interact library:: sage: test(interacts.calculus.bisection_method) Interactive function with 5 widgets - title: HTMLText(value=u'

Bisection method

') - f: EvalText(value=u'x^2-2', description=u'f(x)', layout=Layout(max_width=u'81em')) - interval: IntRangeSlider(value=(0, 4), description=u'range', max=5, min=-5) - d: IntSlider(value=3, description=u'$10^{-d}$ precision', max=8, min=1) - maxn: IntSlider(value=10, description=u'max iterations', max=50) - - - - + title: HTMLText(value='

Bisection method

') + f: EvalText(value='x^2-2', description='f(x)', layout=Layout(max_width='81em')) + interval: IntRangeSlider(value=(0, 4), description='range', max=5, min=-5) + d: IntSlider(value=3, description='$10^{-d}$ precision', max=8, min=1) + maxn: IntSlider(value=10, description='max iterations', max=50) + \(\text{Precision }h = 10^{-d}=10^{-3}=0.00100\) + \({c = }1.4140625\) + \({f(c) = }-0.00042724609375\) + \(9 \text{ iterations}\) sage: test(interacts.calculus.riemann_sum) Manual interactive function with 9 widgets - title: HTMLText(value=u'

Riemann integral with random sampling

') - f: EvalText(value=u'x^2+1', description=u'$f(x)=$', layout=Layout(max_width=u'41em')) - n: IntSlider(value=5, description=u'# divisions', max=30, min=1) - hr1: HTMLText(value=u'
') - interval_input: ToggleButtons(description=u'Integration interval', options=('from slider', 'from keyboard'), value='from slider') - interval_s: IntRangeSlider(value=(0, 2), description=u'slider: ', max=10, min=-5) - interval_g: Grid(value=[[0, 2]], children=(Label(value=u'keyboard: '), VBox(children=(EvalText(value=u'0', layout=Layout(max_width=u'5em')),)), VBox(children=(EvalText(value=u'2', layout=Layout(max_width=u'5em')),)))) - hr2: HTMLText(value=u'
') - list_table: Checkbox(value=False, description=u'List table') + title: HTMLText(value='

Riemann integral with random sampling

') + f: EvalText(value='x^2+1', description='$f(x)=$', layout=Layout(max_width='41em')) + n: IntSlider(value=5, description='# divisions', max=30, min=1) + hr1: HTMLText(value='
') + interval_input: ToggleButtons(description='Integration interval', options=('from slider', 'from keyboard'), value='from slider') + interval_s: IntRangeSlider(value=(0, 2), description='slider: ', max=10, min=-5) + interval_g: Grid(value=[[0, 2]], children=(Label(value='keyboard: '), VBox(children=(EvalText(value='0', layout=Layout(max_width='5em')),)), VBox(children=(EvalText(value='2', layout=Layout(max_width='5em')),)))) + hr2: HTMLText(value='
') + list_table: Checkbox(value=False, description='List table') Adjust your data and click Update button. Click repeatedly for another random values. - Riemann sum: - Exact value of the integral + Riemann sum: \(\displaystyle\sum_{i=1}^{5} f(\eta_i)(x_i-x_{i-1})=...\) + Exact value of the integral \(\displaystyle\int_{0}^{2}x^{2} + + 1\,\mathrm{d}x=4.666666666666668\) sage: test(interacts.calculus.function_tool) Interactive function with 7 widgets - f: EvalText(value=u'sin(x)', description=u'f') - g: EvalText(value=u'cos(x)', description=u'g') - xrange: IntRangeSlider(value=(0, 1), description=u'x-range', max=3, min=-3) - yrange: Text(value=u'auto', description=u'yrange') - a: IntSlider(value=1, description=u'a', max=3, min=-1) - action: ToggleButtons(description=u'h = ', options=('f', 'df/dx', 'int f', 'num f', 'den f', '1/f', 'finv', 'f+a', 'f-a', 'f*a', 'f/a', 'f^a', 'f(x+a)', 'f(x*a)', 'f+g', 'f-g', 'f*g', 'f/g', 'f(g)'), value='f') - do_plot: Checkbox(value=True, description=u'Draw Plots') -
-
-
+ f: EvalText(value='sin(x)', description='f') + g: EvalText(value='cos(x)', description='g') + xrange: IntRangeSlider(value=(0, 1), description='x-range', max=3, min=-3) + yrange: Text(value='auto', description='yrange') + a: IntSlider(value=1, description='a', max=3, min=-1) + action: ToggleButtons(description='h = ', options=('f', 'df/dx', 'int f', 'num f', 'den f', '1/f', 'finv', 'f+a', 'f-a', 'f*a', 'f/a', 'f^a', 'f(x+a)', 'f(x*a)', 'f+g', 'f-g', 'f*g', 'f/g', 'f(g)'), value='f') + do_plot: Checkbox(value=True, description='Draw Plots') +
\(f = \sin\left(x\right)\)
+
\(g = \cos\left(x\right)\)
+
\(h = f = \sin\left(x\right)\)
sage: test(interacts.fractals.mandelbrot) Interactive function with 6 widgets - expo: FloatSlider(value=2.0, description=u'expo', max=10.0, min=-10.0) - iterations: IntSlider(value=20, description=u'# iterations', min=1) - zoom_x: FloatRangeSlider(value=(-2.0, 1.0), description=u'Zoom X', max=2.0, min=-2.0, step=0.01) - zoom_y: FloatRangeSlider(value=(-1.5, 1.5), description=u'Zoom Y', max=2.0, min=-2.0, step=0.01) - plot_points: IntSlider(value=150, description=u'plot points', max=400, min=20, step=20) - dpi: IntSlider(value=80, description=u'dpi', max=200, min=20, step=10) + expo: FloatSlider(value=2.0, description='expo', max=10.0, min=-10.0) + iterations: IntSlider(value=20, description='# iterations', min=1) + zoom_x: FloatRangeSlider(value=(-2.0, 1.0), description='Zoom X', max=2.0, min=-2.0, step=0.01) + zoom_y: FloatRangeSlider(value=(-1.5, 1.5), description='Zoom Y', max=2.0, min=-2.0, step=0.01) + plot_points: IntSlider(value=150, description='plot points', max=400, min=20, step=20) + dpi: IntSlider(value=80, description='dpi', max=200, min=20, step=10)

Mandelbrot Fractal

- Recursive Formula: for + Recursive Formula: \(z \leftarrow z^{2.00} + c\) for \(c \in \mathbb{C}\) sage: test(interacts.fractals.julia) Interactive function with 8 widgets - expo: FloatSlider(value=2.0, description=u'expo', max=10.0, min=-10.0) - c_real: FloatSlider(value=0.5, description=u'real part const.', max=2.0, min=-2.0, step=0.01) - c_imag: FloatSlider(value=0.5, description=u'imag part const.', max=2.0, min=-2.0, step=0.01) - iterations: IntSlider(value=20, description=u'# iterations', min=1) - zoom_x: FloatRangeSlider(value=(-1.5, 1.5), description=u'Zoom X', max=2.0, min=-2.0, step=0.01) - zoom_y: FloatRangeSlider(value=(-1.5, 1.5), description=u'Zoom Y', max=2.0, min=-2.0, step=0.01) - plot_points: IntSlider(value=150, description=u'plot points', max=400, min=20, step=20) - dpi: IntSlider(value=80, description=u'dpi', max=200, min=20, step=10) + expo: FloatSlider(value=2.0, description='expo', max=10.0, min=-10.0) + c_real: FloatSlider(value=0.5, description='real part const.', max=2.0, min=-2.0, step=0.01) + c_imag: FloatSlider(value=0.5, description='imag part const.', max=2.0, min=-2.0, step=0.01) + iterations: IntSlider(value=20, description='# iterations', min=1) + zoom_x: FloatRangeSlider(value=(-1.5, 1.5), description='Zoom X', max=2.0, min=-2.0, step=0.01) + zoom_y: FloatRangeSlider(value=(-1.5, 1.5), description='Zoom Y', max=2.0, min=-2.0, step=0.01) + plot_points: IntSlider(value=150, description='plot points', max=400, min=20, step=20) + dpi: IntSlider(value=80, description='dpi', max=200, min=20, step=10)

Julia Fractal

- Recursive Formula: + Recursive Formula: \(z \leftarrow z^{2.00} + (0.50+0.50*\mathbb{I})\) sage: test(interacts.fractals.cellular_automaton) Interactive function with 3 widgets - N: IntSlider(value=100, description=u'Number of iterations', max=500, min=1) - rule_number: IntSlider(value=110, description=u'Rule number', max=255) - size: IntSlider(value=6, description=u'size of graphic', max=11, min=1) + N: IntSlider(value=100, description='Number of iterations', max=500, min=1) + rule_number: IntSlider(value=110, description='Rule number', max=255) + size: IntSlider(value=6, description='size of graphic', max=11, min=1)

Cellular Automaton

"A cellular automaton is a collection of "colored" cells on a grid of specified shape that evolves through a number of discrete time steps according to a set of rules based on the states of neighboring cells." — Mathworld, Cellular Automaton
Rule 110 expands to 01110110
sage: test(interacts.geometry.unit_circle) Interactive function with 2 widgets - function: Dropdown(description=u'function', options=(('sin(x)', 0), ('cos(x)', 1), ('tan(x)', 2)), value=0) - x: TransformFloatSlider(value=0.0, description=u'x', max=6.283185307179586, step=0.015707963267948967) + function: Dropdown(description='function', options=(('sin(x)', 0), ('cos(x)', 1), ('tan(x)', 2)), value=0) + x: TransformFloatSlider(value=0.0, description='x', max=6.283185307179586, step=0.015707963267948967)
Lines of the same color have the same length
sage: test(interacts.geometry.trigonometric_properties_triangle) Interactive function with 3 widgets - a0: IntSlider(value=30, description=u'A', max=360) - a1: IntSlider(value=180, description=u'B', max=360) - a2: IntSlider(value=300, description=u'C', max=360) + a0: IntSlider(value=30, description='A', max=360) + a1: IntSlider(value=180, description='B', max=360) + a2: IntSlider(value=300, description='C', max=360)

Trigonometric Properties of a Triangle

- - , , - Area of triangle + \(\angle A = {60.000}^{\circ},\) \(\angle B = {45.000}^{\circ},\) \(\angle C = {75.000}^{\circ}\) + \(AB = 1.931852\), \(BC = 1.732051\), \(CA = 1.414214\) + Area of triangle \(ABC = 1.183013\) sage: test(interacts.geometry.special_points) Interactive function with 10 widgets - title: HTMLText(value=u'

Special points in triangle

') - a0: IntSlider(value=30, description=u'A', max=360) - a1: IntSlider(value=180, description=u'B', max=360) - a2: IntSlider(value=300, description=u'C', max=360) - show_median: Checkbox(value=False, description=u'Medians') - show_pb: Checkbox(value=False, description=u'Perpendicular Bisectors') - show_alt: Checkbox(value=False, description=u'Altitudes') - show_ab: Checkbox(value=False, description=u'Angle Bisectors') - show_incircle: Checkbox(value=False, description=u'Incircle') + title: HTMLText(value='

Special points in triangle

') + a0: IntSlider(value=30, description='A', max=360) + a1: IntSlider(value=180, description='B', max=360) + a2: IntSlider(value=300, description='C', max=360) + show_median: Checkbox(value=False, description='Medians') + show_pb: Checkbox(value=False, description='Perpendicular Bisectors') + show_alt: Checkbox(value=False, description='Altitudes') + show_ab: Checkbox(value=False, description='Angle Bisectors') + show_incircle: Checkbox(value=False, description='Incircle') show_euler: Checkbox(value=False, description=u"Euler's Line") sage: test(interacts.statistics.coin) Interactive function with 2 widgets - n: IntSlider(value=1000, description=u'Number of Tosses', max=10000, min=2, step=100) - interval: IntRangeSlider(value=(0, 0), description=u'Plotting range (y)', max=1) + n: IntSlider(value=1000, description='Number of Tosses', max=10000, min=2, step=100) + interval: IntRangeSlider(value=(0, 0), description='Plotting range (y)', max=1) doctest:...: UserWarning: Attempting to set identical bottom == top == 0.0 results in singular transformations; automatically expanding. Test matrix control (see :trac:`27735`):: @@ -291,7 +290,7 @@ Test matrix control (see :trac:`27735`):: ....: print(parent(A)) sage: test(matrix_test) Interactive function with 1 widget - A: Grid(value=[[0, 1], [2, 3]], children=(Label(value=u'A'), VBox(children=(EvalText(value=u'0', layout=Layout(max_width=u'5em')), EvalText(value=u'2', layout=Layout(max_width=u'5em')))), VBox(children=(EvalText(value=u'1', layout=Layout(max_width=u'5em')), EvalText(value=u'3', layout=Layout(max_width=u'5em')))))) + A: Grid(value=[[0, 1], [2, 3]], children=(Label(value='A'), VBox(children=(EvalText(value='0', layout=Layout(max_width='5em')), EvalText(value='2', layout=Layout(max_width='5em')))), VBox(children=(EvalText(value='1', layout=Layout(max_width='5em')), EvalText(value='3', layout=Layout(max_width='5em')))))) [0 1] [2 3] Full MatrixSpace of 2 by 2 dense matrices over Rational Field diff --git a/src/sage/interfaces/mathematica.py b/src/sage/interfaces/mathematica.py index 45fa98179d3..030e894355f 100644 --- a/src/sage/interfaces/mathematica.py +++ b/src/sage/interfaces/mathematica.py @@ -232,7 +232,7 @@ sage: F[4] # optional - mathematica {541, 1} -Mathematica's ECM package is no longer available. +Mathematica's ECM package is no longer available. Long Input ---------- @@ -1000,7 +1000,7 @@ def show(self, ImageSize=600): sage: Q = mathematica('Sin[x Cos[y]]/Sqrt[1-x^2]') # optional - mathematica sage: show(Q) # optional - mathematica - + \(\frac{\sin (x \cos (y))}{\sqrt{1-x^2}}\) The following example starts a Mathematica frontend to do the rendering (:trac:`28819`):: diff --git a/src/sage/libs/linkages/padics/relaxed/API.pxi b/src/sage/libs/linkages/padics/relaxed/API.pxi new file mode 100644 index 00000000000..29a051b808c --- /dev/null +++ b/src/sage/libs/linkages/padics/relaxed/API.pxi @@ -0,0 +1,517 @@ +r""" +This file defines the common API for relaxed `p`-adic numbers. + +Let `K` be a given `p`-adic ring/field with uniformizer `\pi`. +In the relaxed model, we represent elements of `K` as (Laurent) polynomials over an +explicit exact subring of `K` (whose evaluation at `\pi` are good approximations +of the element we want to represent). + +The coefficients of the aforementioned polynomials are called the *digits*. +They are intended to be chosen in a fixed set of small representatives of elements +of the residue field of `K`, but we do not impose this condition because we want +to keep stability under addition and multiplication. +On the contrary, we let the digits be arbitrary elements in the exact subring +but provide functions to reduce digits (i.e. replace them by the representative +of its reduction modulo `\pi`) and propagate the carry accordingly. + +Our API is based on two template types (which have to be instantiated in +implementations): + +- ``cdigit``: the type of a digit + +- ``celement``: the type of an element (which is then a polynomial whose + coefficients are elements of type ``cdigit``) + +The remainder of the file gives the function signatures. + +AUTHOR: + +- Xavier Caruso (2021-01): initial version +""" + +# **************************************************************************** +# Copyright (C) 2021 Xavier Caruso +# +# 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/ +# **************************************************************************** + + +# Operations on digits +###################### + +cdef cdigit digit_zero +digit_init(digit_zero) + +cdef inline void digit_init(cdigit a): + r""" + Initialize a digit and set to it the value `0`. + + INPUT: + + - ``a`` -- the ``cdigit`` to initialize + """ + pass + +cdef inline void digit_clear(cdigit a): + r""" + Deallocate memory assigned to a digit. + + INPUT: + + - ``a`` -- the ``cdigit`` to deallocate + """ + pass + +# get and set + +cdef inline Element digit_get_sage(cdigit a): + r""" + Convert a digit to a Sage element. + + INPUT: + + - ``a`` -- a ``cdigit`` + + OUTPUT: + + A Sage element representing the same digit. + """ + pass + +cdef inline void digit_set(cdigit a, cdigit b): + r""" + Set up a digit. + + INPUT: + + - ``a`` -- the ``cdigit`` to set up + - ``b`` -- the ``cdigit`` containing the value to assign + """ + pass + +cdef inline void digit_set_ui(cdigit a, long b): + r""" + Set an integral value of a digit. + + INPUT: + + - ``a`` -- the ``cdigit`` to set up + - ``b`` -- an integer, the value of assign + """ + pass + +cdef inline void digit_set_sage(cdigit a, Element b): + r""" + Set the value of a digit. + + INPUT: + + - ``a`` -- the ``cdigit`` to be assigned + - ``b`` -- a Sage element containing the value to assign + """ + pass + +# comparisons + +cdef inline bint digit_equal(cdigit a, cdigit b): + r""" + Comparison of two digits. + + INPUT: + + - ``a`` -- a ``cdigit`` + - ``b`` -- a ``cdigit`` + + OUTPUT: + + `1` of `a` is equal to `b`, `0` otherwise + """ + pass + +cdef inline bint digit_equal_ui(cdigit a, long b): + r""" + Comparison of a digit and an integer + + INPUT: + + - ``a`` -- a ``cdigit`` + - ``b`` -- an integer + + OUTPUT: + + `1` of `a` is equal to `b`, `0` otherwise + """ + pass + +cdef inline bint digit_is_zero(cdigit a): + r""" + Comparison to zero + + INPUT: + + - ``a`` -- a ``cdigit`` + + OUTPUT: + + `1` of `a` vanishes, `0` otherwise + """ + pass + +cdef inline void digit_random_init(randgen generator, long seed): + r""" + Initialize the random generator with a new seed + + INPUT: + + - ``generator`` -- the generator to initialize + - ``seed`` -- the seed + """ + pass + +cdef inline void digit_random(cdigit res, PowComputer_class prime_pow): + r""" + Set a digit to a random value in the distinguished set of representatives. + + INPUT: + + - ``res`` -- the ``cdigit`` to be assigned + - ``prime_pow`` -- the PowComputer for the ring + """ + pass + +# operations + +cdef inline void digit_add(cdigit res, cdigit a, cdigit b): + r""" + Add two digits. + + INPUT: + + - ``res`` -- a ``cdigit`` to store the result + - ``a`` -- a ``cdigit``, the first summand + - ``b`` -- a ``cdigit``, the second summand + """ + pass + +cdef inline void digit_sub(cdigit res, cdigit a, cdigit b): + r""" + Subtract two digits. + + INPUT: + + - ``res`` -- a ``cdigit`` to store the result + - ``a`` -- a ``cdigit``, the minuend + - ``b`` -- a ``cdigit``, the subtrahend + """ + pass + +cdef inline void digit_mul(cdigit res, cdigit a, cdigit b): + r""" + Multiply two digits. + + INPUT: + + - ``res`` -- a ``cdigit`` to store the result + - ``a`` -- a ``cdigit``, the first factor + - ``b`` -- a ``cdigit``, the second factor + """ + pass + +cdef inline void digit_mod(cdigit res, cdigit a, PowComputer_class prime_pow): + r""" + Reduce a digit modulo the uniformizer. + + INPUT: + + - ``res`` -- a ``cdigit`` to store the result + - ``a`` -- a ``cdigit``, the digit to reduce + - ``prime_pow`` -- the PowComputer for the ring + """ + pass + +cdef inline void digit_quorem(cdigit quo, cdigit rem, cdigit a, PowComputer_class prime_pow): + r""" + Reduce a digit modulo the uniformizer and keep the carry. + + INPUT: + + - ``quo`` -- a ``cdigit`` to store the carry + - ``rem`` -- a ``cdigit`` to store the reduction + - ``a`` -- a ``cdigit``, the digit to reduce + - ``prime_pow`` -- the PowComputer for the ring + """ + pass + +cdef inline void digit_smallest(cdigit res, cdigit carry, cdigit a, PowComputer_class prime_pow): + r""" + Compute the smallest representative of a digit. + + INPUT: + + - ``res`` -- a ``cdigit`` to store the smallest representative + - ``carry`` -- a ``cdigit`` to store the carry + - ``a`` -- a ``cdigit``, the digit to reduce + - ``prime_pow`` -- the PowComputer for the ring + """ + pass + +cdef inline void digit_inv(cdigit res, cdigit a, PowComputer_class prime_pow): + r""" + Compute the multiplicative inverse of a digit modulo the uniformizer. + + INPUT: + + - ``res`` -- a ``cdigit`` to store the result + - ``a`` -- a ``cdigit``, the digit to invert + - ``prime_pow`` -- the PowComputer for the ring + """ + pass + +cdef bint digit_sqrt(cdigit ans, cdigit x, PowComputer_class prime_pow): + r""" + Compute the square root of a digit modulo the uniformizer. + + INPUT: + + - ``res`` -- a ``cdigit`` to store the result + - ``a`` -- a ``cdigit``, the digit to square root + - ``prime_pow`` -- the PowComputer for the ring + """ + pass + + +# Operations on elements +######################## + +cdef inline void element_init(celement x): + r""" + Initialize an element. + + INPUT: + + - ``x`` -- the ``celement`` to initialize + """ + pass + +cdef inline void element_clear(celement x): + r""" + Deallocate memory assigned to an element. + + INPUT: + + - ``x`` -- the ``celement`` to deallocate + """ + pass + +# get and set + +cdef inline Element element_get_sage(celement x, PowComputer_class prime_pow): + r""" + Convert a digit to a Sage element. + + INPUT: + + - ``x`` -- a ``celement`` + - ``prime_pow`` -- the PowComputer for the ring + + OUTPUT: + + A Sage `p`-adic representing the same element. + """ + pass + +cdef inline void element_set(celement x, celement y): + r""" + Set an element + + INPUT: + + - ``x`` -- the ``celement`` to be assigned + - ``y`` -- the ``celement`` containing the value to assign + """ + pass + +# get and set digits + +cdef inline cdigit element_get_digit(celement x, long i): + r""" + Return the `i`-th coefficient of `x`. + + INPUT: + + - ``x`` -- a ``celement`` + - ``i`` -- an integer + """ + pass + +cdef inline void element_get_slice(celement res, celement x, long start, long length): + r""" + Select a slice of an element. + + INPUT: + + - ``res`` -- a ``celement`` to store the slice + - ``x`` -- a ``celement``, the element from which the slice is extracted + - ``start`` -- an integer, the start position of the slice + - ``length`` -- an integer, the length of the slice + + .. NOTE:: + + This function only sets up a pointer to the requested slice + (the slice is not copied). Hence any future modification + of the slice ``res`` will affect the container ``x``. + """ + pass + +cdef inline void element_set_digit(celement x, cdigit a, long i): + r""" + Set `i`-th coefficient of `x` to the value `a`. + + INPUT: + + - ``x`` -- a ``celement`` + - ``a`` -- a ``cdigit`` + - ``i`` -- an integer + """ + pass + +cdef inline void element_set_digit_ui(celement x, long a, long i): + r""" + Set `i`-th coefficient of `x` to the value `a`. + + INPUT: + + - ``x`` -- a ``celement`` + - ``a`` -- an integer + - ``i`` -- an integer + """ + pass + +cdef inline void element_set_digit_sage(celement x, Element a, long i): + r""" + Set `i`-th coefficient of `x` to the value `a`. + + INPUT: + + - ``x`` -- a ``celement`` + - ``a`` -- a Sage element + - ``i`` -- an integer + """ + pass + +# operations + +cdef inline void element_iadd_digit(celement x, cdigit a, long i): + r""" + Inplace addition: + add `a` to the `i`-th coefficient of `x`. + + INPUT: + + - ``x`` -- a ``celement`` + - ``a`` -- a ``cdigit`` + - ``i`` -- an integer + """ + pass + +cdef inline void element_isub_digit(celement x, cdigit a, long i): + r""" + Inplace subtraction: + subtract `a` to the `i`-th coefficient of `x`. + + INPUT: + + - ``x`` -- a ``celement`` + - ``a`` -- a ``cdigit`` + - ``i`` -- an integer + """ + pass + +cdef inline void element_iadd_slice(celement x, celement slice, long start): + r""" + Inplace addition: + add a slice to an element + + INPUT: + + - ``x`` -- a ``celement``, the element to update + - ``slice`` -- a ``celement``, the slice to be added + - ``start`` -- the position from which the slice will be added + """ + pass + +cdef inline void element_scalarmul(celement res, celement x, cdigit a): + r""" + Scalar multiplication. + + INPUT: + + - ``res`` -- a ``celement``, the element to store the result + - ``x`` -- a ``celement``, the multiplicand + - ``a`` -- a ``cdigit``, the multiplier + """ + pass + +cdef inline void element_mul(celement res, celement x, celement y): + r""" + Multiplication. + + INPUT: + + - ``res`` -- a ``celement``, the element to store the result + - ``x`` -- a ``celement``, the first factor + - ``y`` -- a ``celement``, the second factor + """ + pass + +cdef inline void element_reduce_digit(celement x, long i, PowComputer_class prime_pow): + r""" + Reduce the `i`-th digit of `x` and propagate carry. + + INPUT: + + - ``x`` -- a ``celement``, the element to update + - ``i`` -- an integer + - ``prime_pow`` -- the PowComputer for the ring + """ + pass + +cdef inline void element_reducesmall_digit(fmpz_poly_t x, long i, PowComputer_flint prime_pow): + r""" + Reduce the `i`-th digit of `x` and propagate carry, + assuming that `x` is between `0` and `2*p - 1`. + + INPUT: + + - ``x`` -- a ``celement``, the element to update + - ``i`` -- an integer + - ``prime_pow`` -- the PowComputer for the ring + """ + pass + +cdef inline void element_reduceneg_digit(fmpz_poly_t x, long i, PowComputer_flint prime_pow): + r""" + Reduce the `i`-th digit of `x` and propagate carry, + assuming that `x` is between `-p` and `p-1`. + + INPUT: + + - ``x`` -- a ``celement``, the element to update + - ``i`` -- an integer + - ``prime_pow`` -- the PowComputer for the ring + """ + pass + +cdef inline void element_shift_right(celement x): + r""" + Remove the first digit of ``x``. + + INPUT: + + - ``x`` -- a ``celement`` + """ + pass diff --git a/src/sage/libs/linkages/padics/relaxed/flint.pxi b/src/sage/libs/linkages/padics/relaxed/flint.pxi new file mode 100644 index 00000000000..3422f3ff328 --- /dev/null +++ b/src/sage/libs/linkages/padics/relaxed/flint.pxi @@ -0,0 +1,541 @@ +r""" +This linkage file implements the relaxed padics API using flint. + +AUTHOR: + +- Xavier Caruso (2021-01): initial version +""" + +# **************************************************************************** +# Copyright (C) 2021 Xavier Caruso +# +# 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.libs.flint.types cimport flint_rand_t +from sage.libs.flint.fmpz cimport * +from sage.libs.flint.fmpz_poly cimport * + +cdef extern from "sage/libs/linkages/padics/relaxed/flint_helper.c": + cdef void flint_randseed(flint_rand_t state, ulong seed1, ulong seed2) + cdef fmpz* get_coeff(fmpz_poly_t poly, slong i) + cdef void get_slice(fmpz_poly_t slice, fmpz_poly_t poly, slong start, slong length) + cdef void iadd_coeff(fmpz_poly_t poly, fmpz_t summand, slong i) + cdef void isub_coeff(fmpz_poly_t poly, fmpz_t summand, slong i) + cdef void iadd_shifted(fmpz_poly_t poly, fmpz_poly_t summand, slong shift) + cdef void reduce_coeff(fmpz_poly_t poly, slong i, fmpz_t modulus) + cdef void reducesmall_coeff(fmpz_poly_t poly, slong i, fmpz_t modulus) + cdef void reduceneg_coeff(fmpz_poly_t poly, slong i, fmpz_t modulus) + +from sage.rings.padics.pow_computer_flint cimport PowComputer_flint + +from sage.ext.stdsage cimport PY_NEW + +from sage.rings.finite_rings.finite_field_constructor import GF + + +# Operations on digits (intended to be small elements in the exact subring) +########################################################################### + +cdef fmpz_t digit_zero +digit_init(digit_zero) + +cdef inline void digit_init(fmpz_t a): + r""" + Initialize a digit and set to it the value `0`. + + INPUT: + + - ``a`` -- the ``cdigit`` to initialize + """ + fmpz_init(a) + +cdef inline void digit_clear(fmpz_t a): + r""" + Deallocate memory assigned to a digit. + + INPUT: + + - ``a`` -- the ``cdigit`` to deallocate + """ + fmpz_clear(a) + +# get and set + +cdef inline Integer digit_get_sage(fmpz_t a): + r""" + Convert a digit to a Sage element. + + INPUT: + + - ``a`` -- a ``cdigit`` + + OUTPUT: + + A Sage element representing the same digit. + """ + cdef Integer elt = PY_NEW(Integer) + fmpz_get_mpz(elt.value, a) + return elt + +cdef inline void digit_set(fmpz_t a, fmpz_t b): + r""" + Set up a digit. + + INPUT: + + - ``a`` -- the ``cdigit`` to set up + - ``b`` -- the ``cdigit`` containing the value to assign + """ + fmpz_set(a, b) + +cdef inline void digit_set_ui(fmpz_t a, slong b): + r""" + Set an integral value of a digit. + + INPUT: + + - ``a`` -- the ``cdigit`` to set up + - ``b`` -- an integer, the value of assign + """ + fmpz_set_ui(a, b) + +cdef inline void digit_set_sage(fmpz_t a, Integer elt): + r""" + Set the value of a digit. + + INPUT: + + - ``a`` -- the ``cdigit`` to be assigned + - ``b`` -- a Sage element containing the value to assign + """ + fmpz_set_mpz(a, elt.value) + +# comparisons + +cdef inline bint digit_equal(fmpz_t a, fmpz_t b): + r""" + Comparison of two digits. + + INPUT: + + - ``a`` -- a ``cdigit`` + - ``b`` -- a ``cdigit`` + + OUTPUT: + + `1` of `a` is equal to `b`, `0` otherwise + """ + return fmpz_equal(a, b) + +cdef inline bint digit_equal_ui(fmpz_t a, slong b): + r""" + Comparison of a digit and an integer + + INPUT: + + - ``a`` -- a ``cdigit`` + - ``b`` -- an integer + + OUTPUT: + + `1` of `a` is equal to `b`, `0` otherwise + """ + return fmpz_equal_ui(a, b) + +cdef inline bint digit_is_zero(fmpz_t a): + r""" + Comparison to zero + + INPUT: + + - ``a`` -- a ``cdigit`` + + OUTPUT: + + `1` of `a` vanishes, `0` otherwise + """ + return fmpz_is_zero(a) + +# random + +cdef inline void digit_random_init(flint_rand_t generator, slong seed): + r""" + Initialize the random generator with a new seed + + INPUT: + + - ``generator`` -- the generator to initialize + - ``seed`` -- the seed + """ + flint_randseed(generator, seed, seed*seed + 1) + +cdef inline void digit_random(fmpz_t res, PowComputer_flint prime_pow, flint_rand_t generator): + r""" + Set a digit to a random value in the distinguished set of representatives. + + INPUT: + + - ``res`` -- the ``cdigit`` to be assigned + - ``prime_pow`` -- the PowComputer for the ring + """ + fmpz_randm(res, generator, prime_pow.fprime) + +# operations + +cdef inline void digit_add(fmpz_t res, fmpz_t a, fmpz_t b): + r""" + Add two digits. + + INPUT: + + - ``res`` -- a ``cdigit`` to store the result + - ``a`` -- a ``cdigit``, the first summand + - ``b`` -- a ``cdigit``, the second summand + """ + fmpz_add(res, a, b) + +cdef inline void digit_sub(fmpz_t res, fmpz_t a, fmpz_t b): + r""" + Subtract two digits. + + INPUT: + + - ``res`` -- a ``cdigit`` to store the result + - ``a`` -- a ``cdigit``, the minuend + - ``b`` -- a ``cdigit``, the subtrahend + """ + fmpz_sub(res, a, b) + +cdef inline void digit_mul(fmpz_t res, fmpz_t a, fmpz_t b): + r""" + Multiply two digits. + + INPUT: + + - ``res`` -- a ``cdigit`` to store the result + - ``a`` -- a ``cdigit``, the first factor + - ``b`` -- a ``cdigit``, the second factor + """ + fmpz_mul(res, a, b) + +cdef inline void digit_mod(fmpz_t res, fmpz_t a, PowComputer_flint prime_pow): + r""" + Reduce a digit modulo the uniformizer. + + INPUT: + + - ``res`` -- a ``cdigit`` to store the result + - ``a`` -- a ``cdigit``, the digit to reduce + - ``prime_pow`` -- the PowComputer for the ring + """ + fmpz_mod(res, a, prime_pow.fprime) + +cdef inline void digit_quorem(fmpz_t quo, fmpz_t rem, fmpz_t a, PowComputer_flint prime_pow): + r""" + Reduce a digit modulo the uniformizer and keep the carry. + + INPUT: + + - ``quo`` -- a ``cdigit`` to store the carry + - ``rem`` -- a ``cdigit`` to store the reduction + - ``a`` -- a ``cdigit``, the digit to reduce + - ``prime_pow`` -- the PowComputer for the ring + """ + fmpz_tdiv_qr(quo, rem, a, prime_pow.fprime) + +cdef inline void digit_smallest(cdigit res, cdigit carry, cdigit a, PowComputer_flint prime_pow): + r""" + Compute the smallest representative of a digit. + + INPUT: + + - ``res`` -- a ``cdigit`` to store the smallest representative + - ``carry`` -- a ``cdigit`` to store the carry + - ``a`` -- a ``cdigit``, the digit to reduce + - ``prime_pow`` -- the PowComputer for the ring + + NOTE:: + + This function assumes that ``a`` is always reduced in the + usual sense, that is belongs to the range `[0, p-1]`. + """ + cdef fmpz_t b + fmpz_init(b) + fmpz_mul_ui(b, a, 2) + if fmpz_cmp(b, prime_pow.fprime) > 0: + fmpz_sub(res, a, prime_pow.fprime) + fmpz_set_ui(carry, 1) + else: + fmpz_set(res, a) + fmpz_set_ui(carry, 0) + fmpz_clear(b) + +cdef inline void digit_inv(fmpz_t res, fmpz_t a, PowComputer_flint prime_pow): + r""" + Compute the multiplicative inverse of a digit modulo the uniformizer. + + INPUT: + + - ``res`` -- a ``cdigit`` to store the result + - ``a`` -- a ``cdigit``, the digit to invert + - ``prime_pow`` -- the PowComputer for the ring + """ + cdef fmpz_t gcd + fmpz_init(gcd) + fmpz_gcdinv(gcd, res, a, prime_pow.fprime) + fmpz_clear(gcd) + +cdef bint digit_sqrt(fmpz_t ans, fmpz_t x, PowComputer_flint prime_pow): + r""" + Compute the square root of a digit modulo the uniformizer. + + INPUT: + + - ``res`` -- a ``cdigit`` to store the result + - ``a`` -- a ``cdigit``, the digit to square root + - ``prime_pow`` -- the PowComputer for the ring + """ + return not fmpz_sqrtmod(ans, x, prime_pow.fprime) + + +# Operations on elements (represented as series of digits) +########################################################## + +cdef inline void element_init(fmpz_poly_t x): + r""" + Initialize an element. + + INPUT: + + - ``x`` -- the ``celement`` to initialize + """ + fmpz_poly_init(x) + +cdef inline void element_clear(fmpz_poly_t x): + r""" + Deallocate memory assigned to an element. + + INPUT: + + - ``x`` -- the ``celement`` to deallocate + """ + fmpz_poly_clear(x) + +# get and set + +cdef inline Integer element_get_sage(fmpz_poly_t x, PowComputer_flint prime_pow): + r""" + Convert a digit to a Sage element. + + INPUT: + + - ``x`` -- a ``celement`` + - ``prime_pow`` -- the PowComputer for the ring + + OUTPUT: + + A Sage integer representing the same element modulo the known precision + """ + cdef fmpz_t value + fmpz_init(value) + fmpz_poly_evaluate_fmpz(value, x, prime_pow.fprime) + cdef Integer ans = digit_get_sage(value) + fmpz_clear(value) + return ans + +cdef inline void element_set(fmpz_poly_t x, fmpz_poly_t y): + r""" + Set an element + + INPUT: + + - ``x`` -- the ``celement`` to be assigned + - ``y`` -- the ``celement`` containing the value to assign + """ + fmpz_poly_set(x, y) + +# get and set digits + +cdef inline fmpz* element_get_digit(fmpz_poly_t x, slong i): + r""" + Return the `i`-th coefficient of `x`. + + INPUT: + + - ``x`` -- a ``celement`` + - ``i`` -- an integer + """ + return get_coeff(x, i) + +cdef inline void element_get_slice(fmpz_poly_t res, fmpz_poly_t x, slong start, slong length): + r""" + Select a slice of an element. + + INPUT: + + - ``res`` -- a ``celement`` to store the slice + - ``x`` -- a ``celement``, the element from which the slice is extracted + - ``start`` -- an integer, the start position of the slice + - ``length`` -- an integer, the length of the slice + + NOTE:: + + The function only sets up a pointer to the requested slice + (the slice is not copied). Hence any future modification + of the slice ``res`` will affect the container ``x``. + """ + get_slice(res, x, start, length) + +cdef inline void element_set_digit(fmpz_poly_t x, fmpz_t a, slong i): + r""" + Set `i`-th coefficient of `x` to the value `a`. + + INPUT: + + - ``x`` -- a ``celement`` + - ``a`` -- a ``cdigit`` + - ``i`` -- an integer + """ + fmpz_poly_set_coeff_fmpz(x, i, a) + +cdef inline void element_set_digit_ui(fmpz_poly_t x, slong a, slong i): + r""" + Set `i`-th coefficient of `x` to the value `a`. + + INPUT: + + - ``x`` -- a ``celement`` + - ``a`` -- an integer + - ``i`` -- an integer + """ + fmpz_poly_set_coeff_ui(x, i, a) + +cdef inline void element_set_digit_sage(fmpz_poly_t x, Integer a, slong i): + r""" + Set `i`-th coefficient of `x` to the value `a`. + + INPUT: + + - ``x`` -- a ``celement`` + - ``a`` -- a Sage element + - ``i`` -- an integer + """ + fmpz_poly_set_coeff_mpz(x, i, a.value) + +# operations + +cdef inline void element_iadd_digit(fmpz_poly_t x, fmpz_t a, slong i): + r""" + Inplace addition: + add `a` to the `i`-th coefficient of `x`. + + INPUT: + + - ``x`` -- a ``celement`` + - ``a`` -- a ``cdigit`` + - ``i`` -- an integer + """ + iadd_coeff(x, a, i) + +cdef inline void element_isub_digit(fmpz_poly_t x, fmpz_t a, slong i): + r""" + Inplace subtraction: + subtract `a` to the `i`-th coefficient of `x`. + + INPUT: + + - ``x`` -- a ``celement`` + - ``a`` -- a ``cdigit`` + - ``i`` -- an integer + """ + isub_coeff(x, a, i) + +cdef inline void element_iadd_slice(fmpz_poly_t x, fmpz_poly_t slice, slong start): + r""" + Inplace addition: + add a slice to an element + + INPUT: + + - ``x`` -- a ``celement``, the element to update + - ``slice`` -- a ``celement``, the slice to be added + - ``start`` -- the position from which the slice will be added + """ + iadd_shifted(x, slice, start) + +cdef inline void element_scalarmul(fmpz_poly_t res, fmpz_poly_t x, fmpz_t a): + r""" + Scalar multiplication. + + INPUT: + + - ``res`` -- a ``celement``, the element to store the result + - ``x`` -- a ``celement``, the multiplicand + - ``a`` -- a ``cdigit``, the multiplier + """ + fmpz_poly_scalar_mul_fmpz(res, x, a) + +cdef inline void element_mul(fmpz_poly_t res, fmpz_poly_t x, fmpz_poly_t y): + r""" + Multiplication. + + INPUT: + + - ``res`` -- a ``celement``, the element to store the result + - ``x`` -- a ``celement``, the first factor + - ``y`` -- a ``celement``, the second factor + """ + fmpz_poly_mul(res, x, y) + +cdef inline void element_reduce_digit(fmpz_poly_t x, slong i, PowComputer_flint prime_pow): + r""" + Reduce the `i`-th digit of `x` and propagate carry. + + INPUT: + + - ``x`` -- a ``celement``, the element to update + - ``i`` -- an integer + - ``prime_pow`` -- the PowComputer for the ring + """ + reduce_coeff(x, i, prime_pow.fprime) + +cdef inline void element_reducesmall_digit(fmpz_poly_t x, slong i, PowComputer_flint prime_pow): + r""" + Reduce the `i`-th digit of `x` and propagate carry, + assuming that `x` is between `0` and `2*p - 1`. + + INPUT: + + - ``x`` -- a ``celement``, the element to update + - ``i`` -- an integer + - ``prime_pow`` -- the PowComputer for the ring + """ + reducesmall_coeff(x, i, prime_pow.fprime) + +cdef inline void element_reduceneg_digit(fmpz_poly_t x, slong i, PowComputer_flint prime_pow): + r""" + Reduce the `i`-th digit of `x` and propagate carry, + assuming that `x` is between `-p` and `p-1`. + + INPUT: + + - ``x`` -- a ``celement``, the element to update + - ``i`` -- an integer + - ``prime_pow`` -- the PowComputer for the ring + """ + reduceneg_coeff(x, i, prime_pow.fprime) + +cdef inline void element_shift_right(fmpz_poly_t x): + r""" + Remove the first digit of ``x``. + + INPUT: + + - ``x`` -- a ``celement`` + """ + fmpz_poly_shift_right(x, x, 1) diff --git a/src/sage/libs/linkages/padics/relaxed/flint_helper.c b/src/sage/libs/linkages/padics/relaxed/flint_helper.c new file mode 100644 index 00000000000..f8221519d29 --- /dev/null +++ b/src/sage/libs/linkages/padics/relaxed/flint_helper.c @@ -0,0 +1,171 @@ +/***************************************************************************** + Copyright (C) 2021 Xavier Caruso + + 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/ + *****************************************************************************/ + + +#include "flint/flint.h" +#include "flint/fmpz.h" +#include "flint/fmpz_poly.h" + +#define slong mp_limb_signed_t + + +fmpz* get_coeff (fmpz_poly_t poly, slong i) +{ + static fmpz* zero; + if (i >= 0 && i < poly->length) + return poly->coeffs + i; + else + { + if (zero == NULL) + { + zero = malloc(sizeof(fmpz)); + fmpz_zero(zero); + } + return zero; + } +} + + +void get_slice(fmpz_poly_t slice, fmpz_poly_t poly, slong start, slong length) +{ + slong len = FLINT_MIN(length, poly->length - start); + if (len < 0) + { + slice->coeffs = NULL; + slice->alloc = 0; + slice->length = 0; + } + else + { + slice->coeffs = poly->coeffs + start; + slice->alloc = len; + slice->length = len; + } +} + + +void iadd_coeff(fmpz_poly_t poly, const fmpz_t summand, slong i) +{ + fmpz *coeff; + slong len = i + 1; + if (fmpz_is_zero(summand)) + return; + + if (poly->length < len) + fmpz_poly_set_coeff_fmpz(poly, i, summand); + else + { + coeff = poly->coeffs + i; + fmpz_add(coeff, coeff, summand); + } +} + + +void isub_coeff(fmpz_poly_t poly, const fmpz_t summand, slong i) +{ + fmpz *coeff; + slong len = i + 1; + if (fmpz_is_zero(summand)) + return; + + if (poly->length < len) + { + fmpz_poly_set_coeff_fmpz(poly, i, summand); + coeff = poly->coeffs + i; + fmpz_neg(coeff, coeff); + } + else + { + coeff = poly->coeffs + i; + fmpz_sub(coeff, coeff, summand); + } +} + + +void iadd_shifted(fmpz_poly_t poly, const fmpz_poly_t summand, slong shift) +{ + slong len = shift + summand->length; + fmpz *cpoly, *last; + fmpz* csummand = summand->coeffs; + + if (poly->length < len) + { + fmpz_poly_fit_length(poly, len); + cpoly = poly->coeffs + shift; + last = poly->coeffs + poly->length; + for ( ; cpoly < last; cpoly++, csummand++) + fmpz_add(cpoly, cpoly, csummand); + last = poly->coeffs + shift + summand->length; + for ( ; cpoly < last; cpoly++, csummand++) + fmpz_set(cpoly, csummand); + poly->length = len; + } + else + { + cpoly = poly->coeffs + shift; + last = cpoly + summand->length; + for ( ; cpoly < last; cpoly++, csummand++) + fmpz_add(cpoly, cpoly, csummand); + } +} + + +void reduce_coeff(fmpz_poly_t poly, slong i, const fmpz_t modulus) +{ + if (i < poly->length) + { + fmpz_t quo, rem; + fmpz_init(quo); fmpz_init(rem); + fmpz_tdiv_qr(quo, rem, poly->coeffs + i, modulus); + if (fmpz_cmp_si(rem, 0) < 0) + { + fmpz_add(rem, rem, modulus); + fmpz_sub_ui(quo, quo, 1); + } + iadd_coeff(poly, quo, i + 1); + fmpz_poly_set_coeff_fmpz(poly, i, rem); + } +} + + +void reducesmall_coeff(fmpz_poly_t poly, slong i, const fmpz_t modulus) +{ + fmpz* coeff = poly->coeffs + i; + if (i < poly->length && fmpz_cmp(coeff, modulus) >= 0) + { + fmpz_sub(coeff, coeff, modulus); + if (poly->length < i + 2) { + fmpz_poly_set_coeff_ui(poly, i + 1, 1); + } + else + { + coeff++; + fmpz_add_ui(coeff, coeff, 1); + } + } +} + + +void reduceneg_coeff(fmpz_poly_t poly, slong i, const fmpz_t modulus) +{ + fmpz* coeff = poly->coeffs + i; + if (i < poly->length && fmpz_cmp_si(coeff, 0) < 0) + { + fmpz_add(coeff, coeff, modulus); + if (poly->length < i + 2) { + fmpz_poly_set_coeff_si(poly, i + 1, -1); + } + else + { + coeff++; + fmpz_sub_ui(coeff, coeff, 1); + } + } +} diff --git a/src/sage/libs/ntl/ntl_mat_GF2E.pyx b/src/sage/libs/ntl/ntl_mat_GF2E.pyx index 8240a10fef5..85fd9d6d46f 100644 --- a/src/sage/libs/ntl/ntl_mat_GF2E.pyx +++ b/src/sage/libs/ntl/ntl_mat_GF2E.pyx @@ -678,21 +678,39 @@ cdef class ntl_mat_GF2E(object): sage: k. = GF(2^4) sage: ctx = ntl.GF2EContext(k) sage: ntl.GF2XHexOutput(1) - sage: A = ntl.mat_GF2E(ctx, 100,100) - sage: A.randomize() - sage: len([e for e in A.list() if e!=0]) # rel tol 1e-1 - 9346 + sage: A = ntl.mat_GF2E(ctx, 100, 100) + sage: expected_non_zeros = 100 * 100 * (1 - 1.0/2^4) + sage: observed = lambda : len([e for e in A.list() if e!=0]) + sage: n = 0; s = 0 + sage: def add_samples(): + ....: global n, s, A + ....: for i in range(10): + ....: A.randomize() + ....: n += 1 + ....: s += observed() - expected_non_zeros + + sage: add_samples() + sage: while abs(s*1.0/n) > 10: add_samples() + sage: while abs(s*1.0/n) > 5: add_samples() # long time sage: A = ntl.mat_GF2E(ctx, 100,100) sage: A.randomize(nonzero=True) sage: len([e for e in A.list() if e!=0]) 10000 - sage: A = ntl.mat_GF2E(ctx, 100,100) - sage: A.randomize(nonzero=True, density=0.1) - sage: len([e for e in A.list() if e!=0]) # rel tol 2e-1 - 1000 - + sage: expected_non_zeros = 1000 + sage: n = 0; s = 0 + sage: def add_samples(): + ....: global n, s, A + ....: for i in range(10): + ....: A = ntl.mat_GF2E(ctx, 100,100) + ....: A.randomize(nonzero=True, density=0.1) + ....: n += 1 + ....: s += observed() - expected_non_zeros + + sage: add_samples() + sage: while abs(s*1.0/n) > 10: add_samples() + sage: while abs(s*1.0/n) > 5: add_samples() # long time """ cdef long i,j cdef GF2E_c tmp diff --git a/src/sage/manifolds/differentiable/characteristic_class.py b/src/sage/manifolds/differentiable/characteristic_class.py index 72972152f0f..f4155b2577c 100644 --- a/src/sage/manifolds/differentiable/characteristic_class.py +++ b/src/sage/manifolds/differentiable/characteristic_class.py @@ -326,8 +326,10 @@ #****************************************************************************** from sage.structure.unique_representation import UniqueRepresentation +from sage.misc.cachefunc import cached_method from sage.structure.sage_object import SageObject from sage.symbolic.ring import SR +from sage.rings.rational_field import QQ ################################################################################ ## Separate functions @@ -479,7 +481,7 @@ def __init__(self, vbundle, func, class_type='multiplicative', name=None, self._coeff_list = self._get_coeff_list() self._init_derived() - def _get_coeff_list(self): + def _get_coeff_list(self, distinct_real=True): r""" Return the list of coefficients of the Taylor expansion at zero of the function. @@ -497,7 +499,7 @@ def _get_coeff_list(self): def_var = self._func.default_variable() # Use a complex variable without affecting the old one: new_var = SR.symbol('x_char_class_', domain='complex') - if self._vbundle._field_type == 'real': + if self._vbundle._field_type == 'real' and distinct_real: if self._class_type == 'additive': func = self._func.subs({def_var: new_var ** 2}) / 2 elif self._class_type == 'multiplicative': @@ -514,6 +516,9 @@ def _get_coeff_list(self): else: func = self._func.subs({def_var: new_var}) + if self._vbundle._field_type == 'real' and not distinct_real: + pow_range = pow_range // 2 + return func.taylor(new_var, 0, pow_range).coefficients(sparse=False) def _init_derived(self): @@ -629,6 +634,106 @@ def function(self): """ return self._func + @cached_method + def sequence(self, ring=QQ): + r""" + Return the multiplicative/additive sequence (depending on the class + type of ``self``) of ``self.function`` in terms of elementary symmetric + functions `e_i`. + + If `f(x)` is the function with respect to ``self`` then its + multiplicative sequence is given by + + .. MATH:: + + \Pi_{i = 1}^n f(x_i) = \sum^n_{i=0} c_i \, e_i(x_1, \ldots, x_n) + + whereas its additive sequence is given by + + .. MATH:: + + \sum_{i = 1}^n f(x_i) = \sum^n_{i=0} c_i \, e_i(x_1, \ldots, x_n). + + Here, `e_i` denotes the `i`-th elementary symmetric function. + + INPUT: + + - ``ring`` -- (default: ``QQ``) the base ring of the symmetric + function ring; in most cases, one can assume ``QQ`` which is + supposed to work faster, if it doesn't work, try ``SR`` instead. + + OUTPUT: + + - a symmetric function in the elementary symmetric basis represented + by an instance of + :class:`~sage.combinat.sf.elementary.SymmetricFunctionAlgebra_elementary` + + EXAMPLES: + + Consider the multiplicative sequence of the `\hat{A}` class:: + + sage: M = Manifold(8, 'M') + sage: A = M.tangent_bundle().characteristic_class('AHat') + sage: A.sequence() + e[] - 1/24*e[1] + 7/5760*e[1, 1] - 1/1440*e[2] + + This is an element of the symmetric functions over the rational field:: + + sage: A.sequence().parent() + Symmetric Functions over Rational Field in the elementary basis + + To get the sequence as an element of usual polynomial ring, we can do + the following:: + + sage: P = PolynomialRing(QQ, 'e', 3) + sage: poly = P(sum(c * prod(P.gens()[i] for i in p) + ....: for p, c in A.sequence())) + sage: poly + 7/5760*e1^2 - 1/24*e1 - 1/1440*e2 + 1 + + Get an additive sequence:: + + sage: E = M.vector_bundle(2, 'E', field='complex') + sage: ch = E.characteristic_class('ChernChar') + sage: ch.sequence() + 2*e[] + e[1] + 1/2*e[1, 1] + 1/6*e[1, 1, 1] + 1/24*e[1, 1, 1, 1] + - e[2] - 1/2*e[2, 1] - 1/6*e[2, 1, 1] + 1/12*e[2, 2] + 1/2*e[3] + + 1/6*e[3, 1] - 1/6*e[4] + + .. SEEALSO:: + + See :class:`~sage.combinat.sf.elementary.SymmetricFunctionAlgebra_elementary` + for detailed information about elementary symmetric functions. + + """ + if self._class_type == 'Pfaffian': + return NotImplementedError('this functionality is not supported ' + 'for characteristic classes of ' + 'Pfaffian type') + + from sage.combinat.sf.sf import SymmetricFunctions + from sage.misc.misc_c import prod + + Sym = SymmetricFunctions(ring) + + coeff = self._get_coeff_list(distinct_real=False) + from sage.combinat.partition import Partitions + m = Sym.m() + if self._class_type == 'multiplicative': + # Get the multiplicative sequence in the monomial basis: + mon_pol = m._from_dict({p: prod(ring(coeff[i]) for i in p) + for k in range(len(coeff)) + for p in Partitions(k)}) + elif self._class_type == 'additive': + # Express the additive sequence in the monomial basis, the 0th + # order term must be treated separately: + + m_dict = {Partitions(0)([]): self._vbundle._rank * ring(coeff[0])} + m_dict.update({Partitions(k)([k]): ring(coeff[k]) for k in range(1, len(coeff))}) + mon_pol = m._from_dict(m_dict) + # Convert to elementary symmetric polynomials: + return Sym.e()(mon_pol) + def get_form(self, connection, cmatrices=None): r""" Return the form representing ``self`` with respect to the given diff --git a/src/sage/manifolds/differentiable/examples/euclidean.py b/src/sage/manifolds/differentiable/examples/euclidean.py index de84da10c48..61acb9de3fb 100644 --- a/src/sage/manifolds/differentiable/examples/euclidean.py +++ b/src/sage/manifolds/differentiable/examples/euclidean.py @@ -675,10 +675,6 @@ def __classcall_private__(cls, n=None, name=None, latex_name=None, if names is not None and symbols is None: if n == 2: names = list(names) - symbols = '' - for x in names: - symbols += x + ' ' - symbols = symbols[:-1] if coordinates == 'polar': if names[1] in ['p', 'ph', 'phi']: names[1] += ':\\phi' @@ -698,16 +694,13 @@ def __classcall_private__(cls, n=None, name=None, latex_name=None, if names[1] in ['p', 'ph', 'phi']: names[1] = names[1] + ':\\phi' - symbols = '' - for x in names: - symbols += x + ' ' - symbols = symbols[:-1] + symbols = ' '.join(x for x in names) # Technical bit for UniqueRepresentation from sage.misc.prandom import getrandbits from time import time if unique_tag is None: - unique_tag = getrandbits(128)*time() + unique_tag = getrandbits(128) * time() if n == 2: return EuclideanPlane(name=name, latex_name=latex_name, diff --git a/src/sage/manifolds/scalarfield.py b/src/sage/manifolds/scalarfield.py index e741e67169a..98e931e212e 100644 --- a/src/sage/manifolds/scalarfield.py +++ b/src/sage/manifolds/scalarfield.py @@ -3605,6 +3605,8 @@ def set_immutable(self): """ for rst in self._restrictions.values(): rst.set_immutable() + for func in self._express.values(): + func.set_immutable() super().set_immutable() @cached_method diff --git a/src/sage/matrix/matrix_double_dense.pyx b/src/sage/matrix/matrix_double_dense.pyx index 848d4aa6b69..37d11743955 100644 --- a/src/sage/matrix/matrix_double_dense.pyx +++ b/src/sage/matrix/matrix_double_dense.pyx @@ -371,14 +371,25 @@ cdef class Matrix_double_dense(Matrix_dense): sage: matrix.identity(QQ, 4) * matrix(RDF, 4, 0) [] + + Check that an empty matrix is initialized correctly; see :trac:`27366`: + + sage: A = matrix(RDF, 3, 0) + sage: A*A.transpose() + [0.0 0.0 0.0] + [0.0 0.0 0.0] + [0.0 0.0 0.0] """ if self._ncols != right._nrows: raise IndexError("Number of columns of self must equal number of rows of right") + cdef Matrix_double_dense M, _right, _left + if self._nrows == 0 or self._ncols == 0 or right._nrows == 0 or right._ncols == 0: - return self._new(self._nrows, right._ncols) + M = self._new(self._nrows, right._ncols) + M._matrix_numpy.fill(0) + return M - cdef Matrix_double_dense M, _right, _left M = self._new(self._nrows, right._ncols) _right = right _left = self diff --git a/src/sage/misc/html.py b/src/sage/misc/html.py index b9f67c566af..c4a6a32c71a 100644 --- a/src/sage/misc/html.py +++ b/src/sage/misc/html.py @@ -64,10 +64,8 @@ def _rich_repr_(self, display_manager, **kwds): def math_parse(s): r""" - Replace TeX-``$`` with Mathjax equations. - - Turn the HTML-ish string s that can have \$\$ and \$'s in it into - pure HTML. See below for a precise definition of what this means. + Transform the string ``s`` with TeX maths to an HTML string renderable by + MathJax. INPUT: @@ -77,48 +75,25 @@ def math_parse(s): A :class:`HtmlFragment` instance. - Do the following: + Specifically this method does the following: - * Replace all ``\$ text \$``\'s by - ```` - * Replace all ``\$\$ text \$\$``\'s by - ```` - * Replace all ``\ \$``\'s by ``\$``\'s. Note that in - the above two cases nothing is done if the ``\$`` - is preceeded by a backslash. - * Replace all ``\[ text \]``\'s by - ```` + * Replace all ``\$text\$``\'s by ``\(text\)`` + * Replace all ``\$\$text\$\$``\'s by ``\[text\]`` + * Replace all ``\\\$``\'s by ``\$``\'s. Note that this has precedence over + the above two cases. EXAMPLES:: - sage: pretty_print(sage.misc.html.math_parse('This is $2+2$.')) - This is . - sage: pretty_print(sage.misc.html.math_parse('This is $$2+2$$.')) - This is . - sage: pretty_print(sage.misc.html.math_parse('This is \\[2+2\\].')) - This is . - sage: pretty_print(sage.misc.html.math_parse(r'This is \[2+2\].')) - This is . - - TESTS:: + sage: print(sage.misc.html.math_parse('This is $2+2$.')) + This is \(2+2\). + sage: print(sage.misc.html.math_parse('This is $$2+2$$.')) + This is \[2+2\]. + sage: print(sage.misc.html.math_parse('This is \\[2+2\\].')) + This is \[2+2\]. + sage: print(sage.misc.html.math_parse(r'\$2+2\$ is rendered to $2+2$.')) + $2+2$ is rendered to \(2+2\). - sage: sage.misc.html.math_parse(r'This \$\$is $2+2$.') - This $$is . """ - # first replace \\[ and \\] by , respectively. - while True: - i = s.find('\\[') - if i == -1: - break - else: - s = s[:i] + '' - else: - s = s[:j] + '' + s[j+2:] - # Below t always has the "parsed so far" version of s, and s is # just the part of the original input s that hasn't been parsed. t = '' @@ -128,18 +103,19 @@ def math_parse(s): # No dollar signs -- definitely done. return HtmlFragment(t + s) elif i > 0 and s[i-1] == '\\': - # A dollar sign with a backslash right before it, so - # we ignore it by sticking it in the parsed string t - # and skip to the next iteration. - t += s[:i-1] + '$' + # A dollar sign with a backslash right before it, so this is a + # normal dollar sign. If processEscapes is enabled in MathJax, "\$" + # will do the job. But as we do not assume that, we use the span + # tag safely. + t += s[:i-1] + '$' s = s[i+1:] continue elif i+1 < len(s) and s[i+1] == '$': # Found a math environment. Double dollar sign so display mode. - disp = '; mode=display' + disp = True else: # Found math environment. Single dollar sign so default mode. - disp = '' + disp = False # Now find the matching $ sign and form the html string. @@ -160,8 +136,10 @@ def math_parse(s): j += i + 2 txt = s[i+1:j] - t += s[:i] + '' % (disp, - ' '.join(txt.splitlines())) + if disp: + t += s[:i] + r'\[{0}\]'.format(' '.join(txt.splitlines())) + else: + t += s[:i] + r'\({0}\)'.format(' '.join(txt.splitlines())) s = s[j+1:] if disp: s = s[1:] @@ -249,9 +227,9 @@ class MathJax: sage: from sage.misc.html import MathJax sage: MathJax()(3) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}3\] sage: MathJax()(ZZ) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}\Bold{Z}\] """ def __call__(self, x, combine_all=False): @@ -275,7 +253,7 @@ def __call__(self, x, combine_all=False): sage: from sage.misc.html import MathJax sage: MathJax()(3) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}3\] sage: str(MathJax().eval(ZZ['x'], mode='display')) == str(MathJax()(ZZ['x'])) True """ @@ -312,13 +290,11 @@ def eval(self, x, globals=None, locals=None, mode='display', combine_all=False): sage: from sage.misc.html import MathJax sage: MathJax().eval(3, mode='display') - + \[\newcommand{\Bold}[1]{\mathbf{#1}}3\] sage: MathJax().eval(3, mode='inline') - - sage: MathJax().eval(type(3), mode='inline') # py2 - - sage: MathJax().eval(type(3), mode='inline') # py3 - + \(\newcommand{\Bold}[1]{\mathbf{#1}}3\) + sage: MathJax().eval(type(3), mode='inline') + \(\newcommand{\Bold}[1]{\mathbf{#1}}\verb||\) """ # Get a regular LaTeX representation of x x = latex(x, combine_all=combine_all) @@ -376,9 +352,9 @@ def eval(self, x, globals=None, locals=None, mode='display', combine_all=False): parts ) if mode == 'display': - html = '' + html = r'\[{0}\]' elif mode == 'inline': - html = '' + html = r'\({0}\)' elif mode == 'plain': return latex_string else: @@ -427,7 +403,7 @@ def __call__(self, obj, concatenate=True): sage: html(1/2) - + \[\newcommand{\Bold}[1]{\mathbf{#1}}\frac{1}{2}\] sage: html('sagemath') sagemath @@ -470,9 +446,9 @@ def eval(self, s, locals=None): sage: a = 123 sage: html.eval('a') - + \(123\) sage: html.eval('a', locals={'a': 456}) - + \(456\) """ if locals is None: from sage.repl.user_globals import get_globals @@ -489,8 +465,7 @@ def eval(self, s, locals=None): if j == -1: t += s break - t += s[:i] + ''%\ - latex(sage_eval(s[6+i:j], locals=locals)) + t += s[:i] + r'\({}\)'.format(latex(sage_eval(s[6+i:j], locals=locals))) s = s[j+7:] return HtmlFragment(t) diff --git a/src/sage/misc/package.py b/src/sage/misc/package.py index 9e335a09bc0..51d0ae61bf7 100644 --- a/src/sage/misc/package.py +++ b/src/sage/misc/package.py @@ -47,6 +47,7 @@ import os import subprocess import sys +from pathlib import Path from urllib.request import urlopen from urllib.error import URLError @@ -317,6 +318,28 @@ def list_packages(*pkg_types, **opts): return pkgs +def _spkg_inst_dirs(): + """ + Generator for the installation manifest directories as resolved paths. + + It yields first ``SAGE_SPKG_INST``, then ``SAGE_VENV_SPKG_INST``, + if defined; but it both resolve to the same directory, it only yields + one element. + + EXAMPLES:: + + sage: from sage.misc.package import _spkg_inst_dirs + sage: list(_spkg_inst_dirs()) + [...] + + """ + last_inst_dir = None + for inst_dir in (sage.env.SAGE_SPKG_INST, sage.env.SAGE_VENV_SPKG_INST): + if inst_dir: + inst_dir = Path(inst_dir).resolve() + if inst_dir.is_dir() and inst_dir != last_inst_dir: + yield inst_dir + last_inst_dir = inst_dir def installed_packages(exclude_pip=True): """ @@ -344,10 +367,10 @@ def installed_packages(exclude_pip=True): if not exclude_pip: installed.update(pip_installed_packages(normalization='spkg')) # Sage packages should override pip packages (Trac #23997) - SAGE_SPKG_INST = sage.env.SAGE_SPKG_INST - if SAGE_SPKG_INST: + + for inst_dir in _spkg_inst_dirs(): try: - lp = os.listdir(SAGE_SPKG_INST) + lp = os.listdir(inst_dir) installed.update(pkgname_split(pkgname) for pkgname in lp if not pkgname.startswith('.')) except FileNotFoundError: @@ -558,12 +581,15 @@ def package_manifest(package): KeyError: 'dummy-package' """ version = installed_packages()[package] - stamp_file = os.path.join(sage.env.SAGE_SPKG_INST, - '{}-{}'.format(package, version)) - with open(stamp_file) as f: - spkg_meta = json.load(f) - return spkg_meta - + for inst_dir in _spkg_inst_dirs(): + stamp_file = os.path.join(inst_dir, + '{}-{}'.format(package, version)) + try: + with open(stamp_file) as f: + return json.load(f) + except FileNotFoundError: + pass + raise RuntimeError('package manifest directory changed at runtime') class PackageNotFoundError(RuntimeError): """ diff --git a/src/sage/misc/persist.pyx b/src/sage/misc/persist.pyx index d0dfa6dffc1..e93b4a0d77a 100644 --- a/src/sage/misc/persist.pyx +++ b/src/sage/misc/persist.pyx @@ -43,6 +43,36 @@ import bz2; comp_other = bz2 from .sage_unittest import TestSuite +# We define two global dictionaries `already_pickled` and +# `already_unpickled`, which are intended to help you to implement +# pickling when cyclic definitions could happen. +# +# You have the guarantee that the dictionary `already_pickled` +# (resp. `already_unpickled`) will be cleared after the complete +# pickling (resp. unpickling) process has been completed (and not +# before!). +# Apart from this, you are free to use these variables as you like. +# +# However, the standard utilisation is the following. +# The pickling method (namely `__reduce__`) checks if the id of the +# current element appears in the dictionary `already_pickled`. If it +# does not, the methods records that this element is about to be +# pickled by adding the entry { id: True } to `already_pickled`. +# In all cases, the pickling method pickles the element and includes +# the id in the tuple of arguments that will be passed in afterwards +# to the unpickling function. +# The unpickling function then receives all the necessary information +# to reconstruct the element, together with an id. +# If this id appears as a key of the dictionary `already_unpickled`, it +# returns the corresponding value. +# Otherwise, it builds the element, adds the entry { id: element } to +# `already_unpickled` and finally returns the element. +# +# For a working example, see sage.rings.padics.lazy_template.LazyElement_unknown +already_pickled = { } +already_unpickled = { } + + cdef _normalize_filename(s): """ Append the .sobj extension to a filename if it doesn't already have it. @@ -260,7 +290,9 @@ def _base_dumps(obj, compress=True): method, in which case that is tried first. """ + global already_pickled gherkin = SagePickler.dumps(obj) + already_pickled = { } if compress: return comp.compress(gherkin) @@ -284,12 +316,15 @@ def dumps(obj, compress=True): sage: a2 2/3 """ + global already_pickled if make_pickle_jar: picklejar(obj) try: - return obj.dumps(compress) + ans = obj.dumps(compress) except (AttributeError, RuntimeError, TypeError): - return _base_dumps(obj, compress=compress) + ans = _base_dumps(obj, compress=compress) + already_pickled = { } + return ans # This is used below, and also by explain_pickle.py @@ -764,10 +799,11 @@ class SagePickler(_BasePickler): sage: pickle.loads(gherkin) 1 """ - + global already_pickled buf = io.BytesIO() pickler = cls(buf, **kwargs) pickler.dump(obj) + already_pickled = { } return buf.getvalue() @@ -932,7 +968,10 @@ def loads(s, compress=True, **kwargs): pass unpickler = SageUnpickler(io.BytesIO(s), **kwargs) - return unpickler.load() + global already_unpickled + ans = unpickler.load() + already_unpickled = { } + return ans cdef bint make_pickle_jar = 'SAGE_PICKLE_JAR' in os.environ @@ -998,7 +1037,9 @@ def picklejar(obj, dir=None): if not err.errno == errno.EEXIST: raise + global already_pickled s = comp.compress(SagePickler.dumps(obj)) + already_pickled = { } typ = str(type(obj)) name = ''.join([x if (x.isalnum() or x == '_') else '_' for x in typ]) diff --git a/src/sage/misc/table.py b/src/sage/misc/table.py index 089ce8437ec..8610f06df0d 100644 --- a/src/sage/misc/table.py +++ b/src/sage/misc/table.py @@ -179,24 +179,24 @@ class table(SageObject): - - + + - - + + - - + + - - + + - - + +
\(x\)\(\sin(x)\)
\(0\)\(0.00\)
\(1\)\(0.84\)
\(2\)\(0.91\)
\(3\)\(0.14\)
@@ -648,22 +648,22 @@ def _html_(self): - - - + + + - - - + + + - - - + \end{array}\right)\) + +
text\(\sin(x)\)\(x\)text
\(1\)\(34342\)\(3\)
\(5\)\(6\)
@@ -690,24 +690,24 @@ def _html_(self): - - + + - - + + - - + + - - + + - - + +
\(x\)\(\sin(x)\)
\(0\)\(0.00\)
\(1\)\(0.84\)
\(2\)\(0.91\)
\(3\)\(0.14\)
@@ -776,9 +776,9 @@ def _html_table_row(self, file, row, header=False): sage: s = StringIO() sage: T._html_table_row(s, ['a', 2, '$x$']) sage: print(s.getvalue()) - a - - + a + \(2\) + \(x\) """ from sage.plot.all import Graphics from .latex import latex @@ -790,27 +790,40 @@ def _html_table_row(self, file, row, header=False): elif not isinstance(row, (list, tuple)): row = [row] - column_tag = "%s\n" if header else "%s\n" + align_char = self._options['align'][0] # 'l', 'c', 'r' + + if align_char == 'l': + style = 'text-align:left' + elif align_char == 'c': + style = 'text-align:center' + elif align_char == 'r': + style = 'text-align:right' + else: + style = '' + + style_attr = f' style="{style}"' if style else '' + + column_tag = f'%s\n' if header else f'%s\n' if self._options['header_column']: - first_column_tag = '%s\n' if header else '%s\n' + first_column_tag = '%s\n' if header else '%s\n' else: first_column_tag = column_tag - # First entry of row: + # first entry of row entry = row[0] if isinstance(entry, Graphics): file.write(first_column_tag % entry.show(linkmode = True)) elif isinstance(entry, str): file.write(first_column_tag % math_parse(entry)) else: - file.write(first_column_tag % ('' % latex(entry))) + file.write(first_column_tag % (r'\(%s\)' % latex(entry))) - # Other entries: + # other entries for column in range(1, len(row)): if isinstance(row[column], Graphics): file.write(column_tag % row[column].show(linkmode = True)) elif isinstance(row[column], str): file.write(column_tag % math_parse(row[column])) else: - file.write(column_tag % ('' % latex(row[column]))) + file.write(column_tag % (r'\(%s\)' % latex(row[column]))) diff --git a/src/sage/numerical/backends/cvxopt_backend.pyx b/src/sage/numerical/backends/cvxopt_backend.pyx index 865f058b502..8273744c883 100644 --- a/src/sage/numerical/backends/cvxopt_backend.pyx +++ b/src/sage/numerical/backends/cvxopt_backend.pyx @@ -38,11 +38,6 @@ cdef class CVXOPTBackend(GenericBackend): sage: p Mixed Integer Program (no objective, 0 variables, 0 constraints) - - General backend testsuite:: - - sage: p = MixedIntegerLinearProgram(solver="CVXOPT") - sage: TestSuite(p.get_backend()).run(skip=("_test_pickling","_test_solve","_test_solve_trac_18572")) """ cdef list objective_function #c_matrix diff --git a/src/sage/numerical/backends/cvxopt_backend_test.py b/src/sage/numerical/backends/cvxopt_backend_test.py new file mode 100644 index 00000000000..0e7227940c1 --- /dev/null +++ b/src/sage/numerical/backends/cvxopt_backend_test.py @@ -0,0 +1,16 @@ +import pytest +from sage.structure.sage_object import SageObject +from sage.numerical.backends.generic_backend_test import GenericBackendTests +from sage.numerical.backends.generic_backend import GenericBackend +from sage.numerical.mip import MixedIntegerLinearProgram + +class TestCVXOPTBackend(GenericBackendTests): + + @pytest.fixture + def backend(self) -> GenericBackend: + return MixedIntegerLinearProgram(solver="CVXOPT").get_backend() + + def test_sage_unittest_testsuite(self, sage_object: SageObject): + # TODO: Remove this test as soon as all old test methods are migrated + from sage.misc.sage_unittest import TestSuite + TestSuite(sage_object).run(verbose=True, raise_on_failure=True, skip=("_test_pickling","_test_solve","_test_solve_trac_18572")) diff --git a/src/sage/numerical/backends/generic_backend.pyx b/src/sage/numerical/backends/generic_backend.pyx index 5639c8886ec..75d98d60b95 100644 --- a/src/sage/numerical/backends/generic_backend.pyx +++ b/src/sage/numerical/backends/generic_backend.pyx @@ -872,6 +872,9 @@ cdef class GenericBackend: raise NotImplementedError() def _test_ncols_nonnegative(self, **options): + # Trac #31103: This method has already been migrated to pytest (generic_backend_test) + # and should be removed as soon as the external sage_numerical_backends packages + # are updated to invoke pytest as part of their testsuite. tester = self._tester(**options) p = self tester.assertGreaterEqual(self.ncols(), 0) diff --git a/src/sage/numerical/backends/generic_backend_test.py b/src/sage/numerical/backends/generic_backend_test.py new file mode 100644 index 00000000000..3c5416eb63c --- /dev/null +++ b/src/sage/numerical/backends/generic_backend_test.py @@ -0,0 +1,22 @@ +import pytest +from sage.numerical.backends.generic_backend import GenericBackend +from sage.structure.sage_object import SageObject +from sage.structure.sage_object_test import SageObjectTests + +class GenericBackendTests(SageObjectTests): + + @pytest.fixture + def backend(self, *args, **kwargs) -> GenericBackend: + raise NotImplementedError + + @pytest.fixture + def sage_object(self, backend) -> SageObject: + return backend + + def test_ncols_nonnegative(self, backend: GenericBackend): + assert backend.ncols() >= 0 + + def test_sage_unittest_testsuite(self, sage_object: SageObject): + # TODO: Remove this test as soon as all old test methods are migrated + from sage.misc.sage_unittest import TestSuite + TestSuite(sage_object).run(verbose=True, raise_on_failure=True, skip="_test_pickling") diff --git a/src/sage/numerical/backends/glpk_backend.pyx b/src/sage/numerical/backends/glpk_backend.pyx index e4ec46b93ff..d490818ce75 100644 --- a/src/sage/numerical/backends/glpk_backend.pyx +++ b/src/sage/numerical/backends/glpk_backend.pyx @@ -36,13 +36,6 @@ cdef class GLPKBackend(GenericBackend): """ MIP Backend that uses the GLPK solver. - - TESTS: - - General backend testsuite:: - - sage: p = MixedIntegerLinearProgram(solver="GLPK") - sage: TestSuite(p.get_backend()).run(skip="_test_pickling") """ def __cinit__(self, maximization = True): diff --git a/src/sage/numerical/backends/glpk_backend_test.py b/src/sage/numerical/backends/glpk_backend_test.py new file mode 100644 index 00000000000..ec37cd25f36 --- /dev/null +++ b/src/sage/numerical/backends/glpk_backend_test.py @@ -0,0 +1,10 @@ +import pytest +from sage.numerical.backends.generic_backend_test import GenericBackendTests +from sage.numerical.backends.generic_backend import GenericBackend +from sage.numerical.mip import MixedIntegerLinearProgram + +class TestGLPKBackend(GenericBackendTests): + + @pytest.fixture + def backend(self) -> GenericBackend: + return MixedIntegerLinearProgram(solver="GLPK").get_backend() diff --git a/src/sage/numerical/backends/glpk_exact_backend.pyx b/src/sage/numerical/backends/glpk_exact_backend.pyx index 078f72ac4be..0da98460382 100644 --- a/src/sage/numerical/backends/glpk_exact_backend.pyx +++ b/src/sage/numerical/backends/glpk_exact_backend.pyx @@ -24,13 +24,6 @@ cdef class GLPKExactBackend(GLPKBackend): as doubles. There is no support for integer variables. - - TESTS: - - General backend testsuite:: - - sage: p = MixedIntegerLinearProgram(solver="GLPK/exact") - sage: TestSuite(p.get_backend()).run(skip="_test_pickling") """ def __cinit__(self, maximization = True): """ diff --git a/src/sage/numerical/backends/glpk_exact_backend_test.py b/src/sage/numerical/backends/glpk_exact_backend_test.py new file mode 100644 index 00000000000..cffc87e3844 --- /dev/null +++ b/src/sage/numerical/backends/glpk_exact_backend_test.py @@ -0,0 +1,10 @@ +import pytest +from sage.numerical.backends.generic_backend_test import GenericBackendTests +from sage.numerical.backends.generic_backend import GenericBackend +from sage.numerical.mip import MixedIntegerLinearProgram + +class TestGLPKExactBackend(GenericBackendTests): + + @pytest.fixture + def backend(self) -> GenericBackend: + return MixedIntegerLinearProgram(solver="GLPK/exact").get_backend() diff --git a/src/sage/numerical/backends/interactivelp_backend.pyx b/src/sage/numerical/backends/interactivelp_backend.pyx index 2a7432a9ddb..42a035a3230 100644 --- a/src/sage/numerical/backends/interactivelp_backend.pyx +++ b/src/sage/numerical/backends/interactivelp_backend.pyx @@ -40,14 +40,6 @@ cdef class InteractiveLPBackend: sage: from sage.numerical.backends.generic_backend import get_solver sage: p = get_solver(solver = "InteractiveLP") - - TESTS: - - General backend testsuite:: - - sage: p = MixedIntegerLinearProgram(solver="InteractiveLP") - sage: TestSuite(p.get_backend()).run(skip="_test_pickling") - """ def __cinit__(self, maximization = True, base_ring = None): diff --git a/src/sage/numerical/backends/interactivelp_backend_test.py b/src/sage/numerical/backends/interactivelp_backend_test.py new file mode 100644 index 00000000000..0f0af51250d --- /dev/null +++ b/src/sage/numerical/backends/interactivelp_backend_test.py @@ -0,0 +1,10 @@ +import pytest +from sage.numerical.backends.generic_backend_test import GenericBackendTests +from sage.numerical.backends.generic_backend import GenericBackend +from sage.numerical.mip import MixedIntegerLinearProgram + +class TestInteractiveLPBackend(GenericBackendTests): + + @pytest.fixture + def backend(self) -> GenericBackend: + return MixedIntegerLinearProgram(solver="InteractiveLP").get_backend() diff --git a/src/sage/numerical/backends/ppl_backend.pyx b/src/sage/numerical/backends/ppl_backend.pyx index ec453136dbf..8f7154137ad 100644 --- a/src/sage/numerical/backends/ppl_backend.pyx +++ b/src/sage/numerical/backends/ppl_backend.pyx @@ -30,12 +30,6 @@ cdef class PPLBackend(GenericBackend): """ MIP Backend that uses the exact MIP solver from the Parma Polyhedra Library. - - General backend testsuite:: - - sage: from sage.numerical.backends.generic_backend import get_solver - sage: p = get_solver(solver = "PPL") - sage: TestSuite(p).run(skip="_test_pickling") """ cdef object mip diff --git a/src/sage/numerical/backends/ppl_backend_test.py b/src/sage/numerical/backends/ppl_backend_test.py new file mode 100644 index 00000000000..ac241275ee2 --- /dev/null +++ b/src/sage/numerical/backends/ppl_backend_test.py @@ -0,0 +1,10 @@ +import pytest +from sage.numerical.backends.generic_backend_test import GenericBackendTests +from sage.numerical.backends.generic_backend import GenericBackend +from sage.numerical.mip import MixedIntegerLinearProgram + +class TestPPLBackend(GenericBackendTests): + + @pytest.fixture + def backend(self) -> GenericBackend: + return MixedIntegerLinearProgram(solver="PPL").get_backend() diff --git a/src/sage/numerical/interactive_simplex_method.py b/src/sage/numerical/interactive_simplex_method.py index aa59b0e7e81..8edecb5ed5c 100644 --- a/src/sage/numerical/interactive_simplex_method.py +++ b/src/sage/numerical/interactive_simplex_method.py @@ -2015,6 +2015,44 @@ def __init__(self, A, b, c, x="x", problem_type="max", "primal objective" if is_primal else "dual objective") self._objective_name = SR(objective_name) + @staticmethod + def random_element(m, n, bound=5, special_probability=0.2, + **kwds): + r""" + Construct a random ``InteractiveLPProblemStandardForm``. + + INPUT: + + - ``m`` -- the number of constraints/basic variables + + - ``n`` -- the number of decision/non-basic variables + + - ``bound`` -- (default: 5) a bound on coefficients + + - ``special_probability`` -- (default: 0.2) probability of + constructing a problem whose initial dictionary is allowed + to be primal infeasible or dual feasible + + All other keyword arguments are passed to the constructor. + + EXAMPLES:: + + sage: InteractiveLPProblemStandardForm.random_element(3, 4) + LP problem (use 'view(...)' or '%display typeset' for details) + """ + if not kwds.pop('is_primal', True): + raise NotImplementedError('only random primal problems are implemented') + A = random_matrix(ZZ, m, n, x=-bound, y=bound).change_ring(QQ) + if special_probability < random(): + b = random_vector(ZZ, m, x=0, y=bound).change_ring(QQ) + else: # Allow infeasible dictionary + b = random_vector(ZZ, m, x=-bound, y=bound).change_ring(QQ) + if special_probability < random(): + c = random_vector(ZZ, n, x=-bound, y=bound).change_ring(QQ) + else: # Make dual feasible dictionary + c = random_vector(ZZ, n, x=-bound, y=0).change_ring(QQ) + return InteractiveLPProblemStandardForm(A, b, c, **kwds) + def add_constraint(self, coefficients, constant_term, slack_variable=None): r""" Return a new LP problem by adding a constraint to``self``. @@ -3865,6 +3903,49 @@ def __init__(self, A, b, c, objective_value, N = vector(nonbasic_variables) self._AbcvBNz = [A, b, c, objective_value, B, N, SR(objective_name)] + @staticmethod + def random_element(m, n, bound=5, special_probability=0.2): + r""" + Construct a random dictionary. + + INPUT: + + - ``m`` -- the number of constraints/basic variables + + - ``n`` -- the number of decision/non-basic variables + + - ``bound`` -- (default: 5) a bound on dictionary entries + + - ``special_probability`` -- (default: 0.2) probability of constructing a + potentially infeasible or potentially optimal dictionary + + OUTPUT: + + - an :class:`LP problem dictionary ` + + EXAMPLES:: + + sage: from sage.numerical.interactive_simplex_method \ + ....: import random_dictionary + sage: random_dictionary(3, 4) # indirect doctest + LP problem dictionary (use 'view(...)' or '%display typeset' for details) + """ + A = random_matrix(ZZ, m, n, x=-bound, y=bound).change_ring(QQ) + if special_probability < random(): + b = random_vector(ZZ, m, x=0, y=bound).change_ring(QQ) + else: # Allow infeasible dictionary + b = random_vector(ZZ, m, x=-bound, y=bound).change_ring(QQ) + if special_probability < random(): + c = random_vector(ZZ, n, x=-bound, y=bound).change_ring(QQ) + else: # Make dual feasible dictionary + c = random_vector(ZZ, n, x=-bound, y=0).change_ring(QQ) + x_N = list(PolynomialRing(QQ, "x", m + n + 1, order="neglex").gens()) + x_N.pop(0) + x_B = [] + for i in range(m): + x_B.append(x_N.pop(randint(0, n + m - i - 1))) + return LPDictionary(A, b, c, randint(-bound, bound), x_B, x_N, "z") + def __eq__(self, other): r""" Check if two LP problem dictionaries are equal. @@ -4286,48 +4367,7 @@ def update(self): self._leaving = None -def random_dictionary(m, n, bound=5, special_probability=0.2): - r""" - Construct a random dictionary. - - INPUT: - - - ``m`` -- the number of constraints/basic variables - - - ``n`` -- the number of decision/non-basic variables - - - ``bound`` -- (default: 5) a bound on dictionary entries - - - ``special_probability`` -- (default: 0.2) probability of constructing a - potentially infeasible or potentially optimal dictionary - - OUTPUT: - - - an :class:`LP problem dictionary ` - - EXAMPLES:: - - sage: from sage.numerical.interactive_simplex_method \ - ....: import random_dictionary - sage: random_dictionary(3, 4) - LP problem dictionary (use ...) - """ - A = random_matrix(ZZ, m, n, x=-bound, y=bound).change_ring(QQ) - if special_probability < random(): - b = random_vector(ZZ, m, x=0, y=bound).change_ring(QQ) - else: # Allow infeasible dictionary - b = random_vector(ZZ, m, x=-bound, y=bound).change_ring(QQ) - if special_probability < random(): - c = random_vector(ZZ, n, x=-bound, y=bound).change_ring(QQ) - else: # Make dual feasible dictionary - c = random_vector(ZZ, n, x=-bound, y=0).change_ring(QQ) - x_N = list(PolynomialRing(QQ, "x", m + n + 1, order="neglex").gens()) - x_N.pop(0) - x_B = [] - for i in range(m): - x_B.append(x_N.pop(randint(0, n + m - i - 1))) - return LPDictionary(A, b, c, randint(-bound, bound), x_B, x_N, "z") - +random_dictionary = LPDictionary.random_element class LPRevisedDictionary(LPAbstractDictionary): r""" diff --git a/src/sage/plot/plot3d/base.pyx b/src/sage/plot/plot3d/base.pyx index b2dce79d675..3d972f79751 100644 --- a/src/sage/plot/plot3d/base.pyx +++ b/src/sage/plot/plot3d/base.pyx @@ -1,6 +1,16 @@ r""" Base Classes for 3D Graphics Objects and Plotting +The most important facts about these classes are +that you can simply add graphics objects +together (``G1+G2``, see :meth:`Graphics3d.__add__`), +and the :meth:`Graphics3d.show` method with its options for +choosing a viewer and setting +various parameters for displaying the graphics. + +Most of the other methods of these classes are technical and +for special usage. + AUTHORS: - Robert Bradshaw (2007-02): initial version @@ -13,9 +23,15 @@ AUTHORS: - Joshua Campbell (2020): Three.js animation support +- Günter Rote (2021): camera and light parameters for tachyon + .. TODO:: - finish integrating tachyon -- good default lights, camera + finish integrating tachyon -- good default lights + + full documentation of three.js viewer parameters + + zoom by changing camera parameters instead of scaling objects """ # **************************************************************************** @@ -203,13 +219,13 @@ cdef class Graphics3d(SageObject): opts = self._process_viewing_options(kwds) T = self._prepare_for_tachyon( opts['frame'], opts['axes'], opts['frame_aspect_ratio'], - opts['aspect_ratio'], opts['zoom'] + opts['aspect_ratio'], + 1 # opts['zoom']. Let zoom be handled by tachyon. + # We don't want the perspective to change by zooming ) - x, y = opts['figsize'][0]*100, opts['figsize'][1]*100 - if DOCTEST_MODE: - x, y = 10, 10 - tachyon_rt(T.tachyon(), filename, opts['verbosity'], - '-res %s %s' % (x, y)) + + tachyon_args = dict((key,val) for key,val in opts.items() if key in Graphics3d.tachyon_keywords) + tachyon_rt(T.tachyon(**tachyon_args), filename, opts['verbosity']) from sage.repl.rich_output.buffer import OutputBuffer import sage.repl.rich_output.output_catalog as catalog import PIL.Image as Image @@ -604,7 +620,7 @@ cdef class Graphics3d(SageObject): sage: sum(point3d((cos(n), sin(n), n)) for n in [0..10, step=.1]) Graphics3d Object - A Graphics 3d object can also be added a 2d graphic object:: + A Graphics 3d object and a 2d object can also be added:: sage: A = sphere((0, 0, 0), 1) + circle((0, 0), 1.5) sage: A.show(aspect_ratio=1) @@ -635,7 +651,7 @@ cdef class Graphics3d(SageObject): def aspect_ratio(self, v=None): """ - Set or get the preferred aspect ratio of ``self``. + Set or get the preferred aspect ratio. INPUT: @@ -669,7 +685,7 @@ cdef class Graphics3d(SageObject): def frame_aspect_ratio(self, v=None): """ - Set or get the preferred frame aspect ratio of ``self``. + Set or get the preferred frame aspect ratio. INPUT: @@ -730,14 +746,14 @@ cdef class Graphics3d(SageObject): def bounding_box(self): """ - Return the lower and upper corners of a 3d bounding box for ``self``. + Return the lower and upper corners of a 3d bounding box. - This is used for rendering and ``self`` should fit entirely + This is used for rendering, and the scene should fit entirely within this box. - Specifically, the first point returned should have x, y, and z - coordinates should be the respective infimum over all points - in ``self``, and the second point is the supremum. + Specifically, the first point returned has x, y, and z + coordinates that are the respective minimum over all points + in the graphics, and the second point is the maximum. The default return value is simply the box containing the origin. @@ -753,7 +769,7 @@ cdef class Graphics3d(SageObject): def transform(self, **kwds): """ - Apply a transformation to ``self``, where the inputs are + Apply a transformation, where the inputs are passed onto a TransformGroup object. Mostly for internal use; see the translate, scale, and rotate @@ -768,7 +784,7 @@ cdef class Graphics3d(SageObject): def translate(self, *x): """ - Return ``self`` translated by the given vector (which can be + Return the object translated by the given vector (which can be given either as a 3-iterable or via positional arguments). EXAMPLES:: @@ -794,7 +810,7 @@ cdef class Graphics3d(SageObject): def scale(self, *x): """ - Return ``self`` scaled in the x, y, and z directions. + Return the object scaled in the x, y, and z directions. EXAMPLES:: @@ -818,7 +834,7 @@ cdef class Graphics3d(SageObject): def rotate(self, v, theta): r""" - Return ``self`` rotated about the vector `v` by `\theta` radians. + Return the object rotated about the vector `v` by `\theta` radians. EXAMPLES:: @@ -838,7 +854,7 @@ cdef class Graphics3d(SageObject): def rotateX(self, theta): """ - Return ``self`` rotated about the `x`-axis by the given angle. + Return the object rotated about the `x`-axis by the given angle. EXAMPLES:: @@ -850,7 +866,7 @@ cdef class Graphics3d(SageObject): def rotateY(self, theta): """ - Return ``self`` rotated about the `y`-axis by the given angle. + Return the object rotated about the `y`-axis by the given angle. EXAMPLES:: @@ -862,7 +878,7 @@ cdef class Graphics3d(SageObject): def rotateZ(self, theta): """ - Return ``self`` rotated about the `z`-axis by the given angle. + Return the object rotated about the `z`-axis by the given angle. EXAMPLES:: @@ -965,29 +981,75 @@ cdef class Graphics3d(SageObject): """%(self.viewpoint().x3d_str(), self.x3d_str()) - def tachyon(self): - """ - An tachyon input file (as a string) containing the this object. + + ################ TACHYON ################ + + ####### insertion of camera parameters + + tachyon_keywords = ( + "antialiasing", + # "aspectratio", + "zoom", # zoom was previously handled directly by scaling the scene. + # This has now been disabled, and zoom is handled by tachyon. + "raydepth", "figsize", "light_position", + "camera_position","updir", + # "look_at", # omit look_at. viewdir is sufficient for most purposes + "viewdir") + + # The tachyon "aspectratio" parameter is outdated for normal users: + # From the tachyon documentation: + # "By using the aspect ratio parameter, one can produce images which look + # correct on any screen. Aspect ratio alters the relative width of the image, + # while keeping plane the height of the image plane constant. In general, + # most workstation displays have an aspect ratio of 1.0." + + # Instead, the tachyon aspectratio is set to match nonsquare + # drawing area in "figsize". + + # Parameters are mostly taken from tachyion.py, + # but camera_center is renamed camera_position. + # Apparently reST strips () from default parameters in the automatic documentation. + # Thus, I replaced () by [] as default values. + + def tachyon(self, + zoom=1.0, + antialiasing=False, + figsize=[5,5], # resolution = 100*figsize + raydepth=8, + camera_position=[2.3, 2.4, 2.0], # old default values + updir=[0, 0, 1], + # look_at=(0, 0, 0), # could be nice to have, but viewdir is good enough + light_position=[4.0, 3.0, 2.0], + viewdir=None, + # projection='PERSPECTIVE', # future extension, allow different projection types + ): + """ + A tachyon input file (as a string) containing the this object. EXAMPLES:: sage: print(sphere((1, 2, 3), 5, color='yellow').tachyon()) + begin_scene - resolution 400 400 + resolution 500 500 + camera ... plane - center -2000 -1000 -500 - normal 2.3 2.4 2.0 + center -592.870151560437 618.647114671761 -515.539262226467 + normal -2.3 2.4 -2.0 TEXTURE - AMBIENT 1.0 DIFFUSE 1.0 SPECULAR 1.0 OPACITY 1.0 + AMBIENT 1.0 DIFFUSE 0.0 SPECULAR 0.0 OPACITY 1.0 COLOR 1.0 1.0 1.0 TEXFUNC 0 + Texdef texture... Ambient 0.3333333333333333 Diffuse 0.6666666666666666 Specular 0.0 Opacity 1.0 - Color 1.0 1.0 0.0 - TexFunc 0 + Color 1.0 1.0 0.0 + TexFunc 0 + Sphere center 1.0 -2.0 3.0 Rad 5.0 texture... + end_scene sage: G = icosahedron(color='red') + sphere((1,2,3), 0.5, color='yellow') @@ -1006,40 +1068,67 @@ cdef class Graphics3d(SageObject): render_params = self.default_render_params() # switch from LH to RH coords to be consistent with java rendition render_params.push_transform(Transformation(scale=[1,-1,1])) + + if len(camera_position)!=3: + raise ValueError('Camera center must consist of three numbers') + + if viewdir is None: + viewdir = [float(- camera_position[i]) for i in range(3)] + if viewdir == [0.0,0.0,0.0]: + viewdir = (1,0,0) # issue a Warning? "camera_position at origin" + # switch from LH to RH coords to be consistent with java rendition + viewdir = _flip_orientation(viewdir) + updir = _flip_orientation(updir) + camera_position = _flip_orientation(camera_position) + light_position = _flip_orientation(light_position) + return """ begin_scene -resolution 400 400 +resolution {resolution_x:d} {resolution_y:d} camera - zoom 1.0 - aspectratio 1.0 - antialiasing %s - raydepth 8 - center 2.3 2.4 2.0 - viewdir -2.3 -2.4 -2.0 - updir 0.0 0.0 1.0 + zoom {zoom:f} + aspectratio {aspectratio:f} + antialiasing {antialiasing:d} + raydepth {raydepth:d} + center {camera_position} + viewdir {viewdir} + updir {updir} end_camera - - light center 4.0 3.0 2.0 + light center {light_position} rad 0.2 color 1.0 1.0 1.0 plane - center -2000 -1000 -500 - normal 2.3 2.4 2.0 + center {viewdir1000} + normal {viewdir} TEXTURE - AMBIENT 1.0 DIFFUSE 1.0 SPECULAR 1.0 OPACITY 1.0 + AMBIENT 1.0 DIFFUSE 0.0 SPECULAR 0.0 OPACITY 1.0 COLOR 1.0 1.0 1.0 TEXFUNC 0 - %s - - %s - -end_scene""" % (render_params.antialiasing, - "\n".join(sorted([t.tachyon_str() for t in self.texture_set()])), - "\n".join(flatten_list(self.tachyon_repr(render_params)))) + {scene} + + {render_parameters} + +end_scene""".format( + #render_params.antialiasing, this only provided the default value of 8 + scene = "\n".join(sorted([t.tachyon_str() for t in self.texture_set()])), + render_parameters = + "\n".join(flatten_list(self.tachyon_repr(render_params))), + viewdir1000=self._tostring(1000*vector(viewdir).normalized().n()), + viewdir=self._tostring(viewdir), + camera_position=self._tostring(camera_position), + updir=self._tostring(updir), + light_position=self._tostring(light_position), + zoom=zoom, + antialiasing=antialiasing, + resolution_x=figsize[0]*100, + resolution_y=figsize[1]*100, + aspectratio=float(figsize[1])/float(figsize[0]), + raydepth=raydepth, + ) def obj(self): """ @@ -1070,6 +1159,18 @@ end_scene""" % (render_params.antialiasing, """ return "\n".join(flatten_list([self.obj_repr(self.default_render_params()), ""])) + @staticmethod + def _tostring(s): + r""" + Converts vector information to a space-separated string. + + EXAMPLES:: + + sage: sage.plot.plot3d.base.Graphics3d._tostring((1.0,1.2,-1.3)) + '1.00000000000000 1.20000000000000 -1.30000000000000' + """ + return ' '.join(map(str,s)) + def export_jmol(self, filename='jmol_shape.jmol', force_reload=False, zoom=1, spin=False, background=(1,1,1), stereo=False, mesh=False, dots=False, @@ -1187,7 +1288,7 @@ end_scene""" % (render_params.antialiasing, A (possibly nested) list of strings. Each entry is formatted as JSON, so that a JavaScript client could eval it and get an object. Each object has fields to encapsulate the faces and - vertices of ``self``. This representation is intended to be + vertices of the object. This representation is intended to be consumed by the canvas3d viewer backend. EXAMPLES:: @@ -1201,7 +1302,7 @@ end_scene""" % (render_params.antialiasing, def jmol_repr(self, render_params): r""" A (possibly nested) list of strings which will be concatenated and - used by jmol to render ``self``. + used by jmol to render the object. (Nested lists of strings are used because otherwise all the intermediate concatenations can kill performance). This may @@ -1222,7 +1323,7 @@ end_scene""" % (render_params.antialiasing, def tachyon_repr(self, render_params): r""" A (possibly nested) list of strings which will be concatenated and - used by tachyon to render ``self``. + used by tachyon to render the object. (Nested lists of strings are used because otherwise all the intermediate concatenations can kill performance). This may @@ -1243,7 +1344,7 @@ end_scene""" % (render_params.antialiasing, def obj_repr(self, render_params): """ A (possibly nested) list of strings which will be concatenated and - used to construct an .obj file of ``self``. + used to construct an .obj file of the object. (Nested lists of strings are used because otherwise all the intermediate concatenations can kill performance). This may @@ -1521,7 +1622,7 @@ end_scene""" % (render_params.antialiasing, return opts def show(self, **kwds): - """ + r""" Display graphics immediately This method attempts to display the graphics immediately, @@ -1540,7 +1641,9 @@ end_scene""" % (render_params.antialiasing, * ``'jmol'``: interactive 3D viewer using Java - * ``'tachyon'``: ray tracer generating a static PNG image + * ``'tachyon'``: ray tracer generating a static PNG image; + can produce high-resolution graphics, but does + not show any text labels * ``'canvas3d'``: web-based 3D viewer using JavaScript and a canvas renderer (Sage notebook only) @@ -1549,12 +1652,12 @@ end_scene""" % (render_params.antialiasing, the figure - ``figsize`` -- (default: 5); x or pair [x,y] for - numbers, e.g., [5,5]; controls the size of the output figure. E.g., - with Tachyon the number of pixels in each direction is 100 times - figsize[0]. This is ignored for the jmol embedded renderer. + numbers, e.g., [5,5]; controls the size of the output figure. + With 'tachyon', the resolution (in number of pixels) is 100 times + ``figsize``. This is ignored for the jmol embedded renderer. - ``aspect_ratio`` -- (default: ``'automatic'``) -- aspect - ratio of the coordinate system itself. Give [1,1,1] to make spheres + ratio of the coordinate system itself. Give [1,1,1] or 1 to make spheres look round. - ``frame_aspect_ratio`` -- (default: ``'automatic'``) @@ -1568,13 +1671,48 @@ end_scene""" % (render_params.antialiasing, - ``axes`` -- (default: False) if True, draw coordinate axes + - ``camera_position`` (for tachyon) -- (default: (2.3, 2.4, 2.0)) + the viewpoint, with respect to the cube + $[-1,1]\\times[-1,1]\\times[-1,1]$, + into which the bounding box of the scene + is scaled and centered. + The default viewing direction is towards the origin. + + - ``viewdir`` (for tachyon) -- (default: None) three coordinates + specifying the viewing direction. + + - ``updir`` (for tachyon) -- (default: (0,0,1)) the "upward" + direction of the camera + + - ``light_position`` (for tachyon) -- (default: (4,3,2)) the position + of the single light source in the scene (in addition to ambient light) + + - ``antialiasing`` (for tachyon) -- (default: False) + + - ``raydepth`` (for tachyon) -- (default: 8) + see the :class:`sage.plot.plot3d.tachyon.Tachyon` class + - ``**kwds`` -- other options, which make sense for particular rendering engines OUTPUT: This method does not return anything. Use :meth:`save` if you - want to save the figure as an image. + want to save the figure as an image file. + + .. WARNING:: + + By default, the jmol and tachyon viewers perform + some non-uniform scaling of the axes. + + If this is not desired, one can set ``aspect_ratio=1``:: + + sage: p = plot3d(lambda u,v:(cos(u)-cos(v)), (-0.2,0.2),(-0.2,0.2)) + sage: p.show(viewer="threejs") + sage: p.show(viewer="jmol") + sage: p.show(viewer="jmol",aspect_ratio=1) + sage: p.show(viewer="tachyon",camera_position=(4,0,0)) + sage: p.show(viewer="tachyon",camera_position=(2,2,0.3),aspect_ratio=1) CHANGING DEFAULTS: Defaults can be uniformly changed by importing a dictionary and changing it. For example, here we change the default @@ -1727,7 +1865,7 @@ end_scene""" % (render_params.antialiasing, - ``filename`` -- string. Where to save the image or object. - ``**kwds`` -- When specifying an image file to be rendered by Tachyon - or Jmol, any of the viewing options accepted by show() are valid as + or Jmol, any of the viewing options accepted by :meth:`show` are valid as keyword arguments to this function and they will behave in the same way. Accepted keywords include: ``viewer``, ``verbosity``, ``figsize``, ``aspect_ratio``, ``frame_aspect_ratio``, ``zoom``, @@ -2067,7 +2205,7 @@ end_scene""" % (render_params.antialiasing, """ Draw a 3D plot of this graphics object, which just returns this object since this is already a 3D graphics object. - Needed to support PLOT in doctrings, see :trac:`17498` + Needed to support PLOT in docstrings, see :trac:`17498` EXAMPLES:: @@ -2141,7 +2279,7 @@ class Graphics3dGroup(Graphics3d): def bounding_box(self): """ Box that contains the bounding boxes of - all the objects that make up ``self``. + the objects. EXAMPLES:: @@ -2424,8 +2562,8 @@ class TransformGroup(Graphics3dGroup): def bounding_box(self): """ - Return the bounding box of ``self``, i.e., the box containing the - contents of ``self`` after applying the transformation. + Return the bounding box, i.e., the box containing the + contents of the object after applying the transformation. EXAMPLES:: @@ -2589,7 +2727,7 @@ class TransformGroup(Graphics3dGroup): def get_transformation(self): """ - Return the actual transformation object associated with ``self``. + Return the current transformation object. EXAMPLES:: @@ -3159,8 +3297,15 @@ def point_list_bounding_box(v): def optimal_aspect_ratios(ratios): """ + Average the aspect ratios. + compute the elementwise maximum of triples. + + TESTS:: + + sage: from sage.plot.plot3d.base import optimal_aspect_ratios + sage: optimal_aspect_ratios([(2,4,6), (5,4,4), (1,2,7)]) + [5, 4, 7] """ - # average the aspect ratios n = len(ratios) if n > 0: return [max([z[i] for z in ratios]) for i in range(3)] @@ -3169,13 +3314,28 @@ def optimal_aspect_ratios(ratios): def optimal_extra_kwds(v): """ - Given a list v of dictionaries, this function merges them such that - later dictionaries have precedence. + Merge a list v of dictionaries such that later + dictionaries have precedence. + + TESTS:: + + sage: from sage.plot.plot3d.base import optimal_extra_kwds + sage: optimal_extra_kwds([{1:2, 2:3}, {2:4, 3:5}]) + {1: 2, 2: 4, 3: 5} """ - if len(v) == 0: - return {} - a = dict(v[0]) # make a copy! - for b in v[1:]: - for k, w in b.iteritems(): - a[k] = w + a = {} + for b in v: + a.update(b) return a + +def _flip_orientation(v): + """ + Switch from LH to RH coords to be consistent with Java rendition + + TESTS:: + + sage: from sage.plot.plot3d.base import _flip_orientation + sage: _flip_orientation((1, 2, 3)) + (1, -2, 3) + """ + return (v[0],-v[1],v[2]) diff --git a/src/sage/plot/plot3d/tachyon.py b/src/sage/plot/plot3d/tachyon.py index 3829e311f03..08caf38d676 100644 --- a/src/sage/plot/plot3d/tachyon.py +++ b/src/sage/plot/plot3d/tachyon.py @@ -1,8 +1,8 @@ r""" The Tachyon 3D Ray Tracer -Given any 3D graphics object one can compute a raytraced -representation by typing ``show(viewer='tachyon')``. +Given any 3D graphics object ``M`` one can compute a raytraced +representation by typing ``M.show(viewer='tachyon')``. For example, we draw two translucent spheres that contain a red tube, and render the result using Tachyon. @@ -13,11 +13,28 @@ sage: M = S + S.translate((2,0,0)) + L sage: M.show(viewer='tachyon') -One can also directly control Tachyon, which gives a huge amount of +A number of options can be given to the +:meth:`~sage.plot.plot3d.base.Graphics3d.show` method and +correspondingly to the +:meth:`~sage.plot.plot3d.base.Graphics3d.save` method for saving +the generated image to a file:: + + sage: M.show(viewer='tachyon', + ....: antialiasing=True, raydepth=3, + ....: figsize=[12,8], # the image resolution is 100*figsize + ....: camera_position=[4, 4.4, 1], # a distant camera position combined with + ....: zoom=3, # a large zoom factor will decrease perspective distortion. + ....: updir=(0, -0.1, 1), # the camera is slightly tilted + ....: viewdir=(-2.,-2.,-0.5), # slightly off-center + ....: light_position=(4.0, -3.0, 2.0), + ....: ) + +One can also directly control Tachyon by creating a ``Tachyon`` object +and adding elements of the scene one by one, which gives a huge amount of flexibility. For example, here we directly use Tachyon to draw 3 spheres on the coordinate axes:: - sage: t = Tachyon(xres=500,yres=500, camera_center=(2,0,0)) + sage: t = Tachyon(xres=500,yres=500, camera_position=(2,0,0)) sage: t.light((4,3,2), 0.2, (1,1,1)) sage: t.texture('t2', ambient=0.1, diffuse=0.9, specular=0.5, opacity=1.0, color=(1,0,0)) sage: t.texture('t3', ambient=0.1, diffuse=0.9, specular=0.5, opacity=1.0, color=(0,1,0)) @@ -29,7 +46,7 @@ For scenes with many reflections it is helpful to increase the raydepth option, and turn on antialiasing. The following scene is an extreme case with many reflections between four cotangent spheres:: - sage: t = Tachyon(camera_center=(0,-4,1), xres = 800, yres = 600, raydepth = 12, aspectratio=.75, antialiasing = 4) + sage: t = Tachyon(camera_position=(0,-4,1), xres = 800, yres = 600, raydepth = 12, aspectratio=.75, antialiasing = 4) sage: t.light((0.02,0.012,0.001), 0.01, (1,0,0)) sage: t.light((0,0,10), 0.01, (0,0,1)) sage: t.texture('s', color = (.8,1,1), opacity = .9, specular = .95, diffuse = .3, ambient = 0.05) @@ -53,7 +70,7 @@ The default projection is ``'perspective'``:: - sage: t = Tachyon(xres=800, yres=600, camera_center=(-1.5,0.0,0.0), zoom=.2) + sage: t = Tachyon(xres=800, yres=600, camera_position=(-1.5,0.0,0.0), zoom=.2) sage: t.texture('t1', color=(0,0,1)) sage: for ed in cedges: ....: t.fcylinder(ed[0], ed[1], .05, 't1') @@ -64,7 +81,7 @@ information. The frustum data is (bottom angle, top angle, left angle, right angle):: - sage: t = Tachyon(xres=800, yres=600, camera_center=(-1.5,0.0,0.0), + sage: t = Tachyon(xres=800, yres=600, camera_position=(-1.5,0.0,0.0), ....: projection='fisheye', frustum=(-1.2, 1.2, -1.2, 1.2)) sage: t.texture('t1', color=(0,0,1)) sage: for ed in cedges: @@ -96,7 +113,7 @@ cylinders or spheres. In this example an image is created and then used to tile the plane:: - sage: T = Tachyon(xres=800, yres=600, camera_center=(-2.0,-.1,.3), projection='fisheye', frustum=(-1.0, 1.0, -1.0, 1.0)) + sage: T = Tachyon(xres=800, yres=600, camera_position=(-2.0,-.1,.3), projection='fisheye', frustum=(-1.0, 1.0, -1.0, 1.0)) sage: T.texture('t1',color=(0,0,1)) sage: for ed in cedges: ....: T.fcylinder(ed[0], ed[1], .05, 't1') @@ -106,7 +123,7 @@ sage: T.save(fname_png) sage: r2 = os.system('convert '+fname_png+' '+fname_ppm) # optional -- ImageMagick - sage: T = Tachyon(xres=800, yres=600, camera_center=(-2.0,-.1,.3), projection='fisheye', frustum=(-1.0, 1.0, -1.0, 1.0)) # optional -- ImageMagick + sage: T = Tachyon(xres=800, yres=600, camera_position=(-2.0,-.1,.3), projection='fisheye', frustum=(-1.0, 1.0, -1.0, 1.0)) # optional -- ImageMagick sage: T.texture('t1', color=(1,0,0), specular=.9) # optional -- ImageMagick sage: T.texture('p1', color=(1,1,1), opacity=.1, imagefile=fname_ppm, texfunc=9) # optional -- ImageMagick sage: T.sphere((0,0,0), .5, 't1') # optional -- ImageMagick @@ -160,8 +177,8 @@ class Tachyon(WithEqualityById, SageObject): - ``zoom`` - (default 1.0) - ``antialiasing`` - (default ``False``) - ``aspectratio`` - (default 1.0) - - ``raydepth`` - (default 5) - - ``camera_center`` - (default (-3, 0, 0)) + - ``raydepth`` - (default 8) + - ``camera_position`` - (default (-3, 0, 0)) - ``updir`` - (default (0, 0, 1)) - ``look_at`` - (default (0,0,0)) - ``viewdir`` - (default ``None``), otherwise list of three numbers @@ -184,7 +201,7 @@ class Tachyon(WithEqualityById, SageObject): :: - sage: t = Tachyon(xres=512,yres=512, camera_center=(3,0.3,0)) + sage: t = Tachyon(xres=512,yres=512, camera_position=(3,0.3,0)) sage: t.light((4,3,2), 0.2, (1,1,1)) sage: t.texture('t0', ambient=0.1, diffuse=0.9, specular=0.5, opacity=1.0, color=(1.0,0,0)) sage: t.texture('t1', ambient=0.1, diffuse=0.9, specular=0.3, opacity=1.0, color=(0,1.0,0)) @@ -200,7 +217,7 @@ class Tachyon(WithEqualityById, SageObject): :: - sage: t = Tachyon(xres=512,yres=512, camera_center=(3,0.3,0), raydepth=8) + sage: t = Tachyon(xres=512,yres=512, camera_position=(3,0.3,0), raydepth=8) sage: t.light((4,3,2), 0.2, (1,1,1)) sage: t.texture('t0', ambient=0.1, diffuse=0.9, specular=0.5, opacity=1.0, color=(1.0,0,0)) sage: t.texture('t1', ambient=0.1, diffuse=0.9, specular=0.3, opacity=1.0, color=(0,1.0,0)) @@ -221,7 +238,7 @@ class Tachyon(WithEqualityById, SageObject): Many random spheres:: - sage: t = Tachyon(xres=512,yres=512, camera_center=(2,0.5,0.5), look_at=(0.5,0.5,0.5), raydepth=4) + sage: t = Tachyon(xres=512,yres=512, camera_position=(2,0.5,0.5), look_at=(0.5,0.5,0.5), raydepth=4) sage: t.light((4,3,2), 0.2, (1,1,1)) sage: t.texture('t0', ambient=0.1, diffuse=0.9, specular=0.5, opacity=1.0, color=(1.0,0,0)) sage: t.texture('t1', ambient=0.1, diffuse=0.9, specular=0.3, opacity=1.0, color=(0,1.0,0)) @@ -235,7 +252,7 @@ class Tachyon(WithEqualityById, SageObject): Points on an elliptic curve, their height indicated by their height above the axis:: - sage: t = Tachyon(camera_center=(5,2,2), look_at=(0,1,0)) + sage: t = Tachyon(camera_position=(5,2,2), look_at=(0,1,0)) sage: t.light((10,3,2), 0.2, (1,1,1)) sage: t.texture('t0', ambient=0.1, diffuse=0.9, specular=0.5, opacity=1.0, color=(1,0,0)) sage: t.texture('t1', ambient=0.1, diffuse=0.9, specular=0.5, opacity=1.0, color=(0,1,0)) @@ -253,7 +270,7 @@ class Tachyon(WithEqualityById, SageObject): :: - sage: t = Tachyon(xres=1000, yres=800, camera_center=(2,7,4), look_at=(2,0,0), raydepth=4) + sage: t = Tachyon(xres=1000, yres=800, camera_position=(2,7,4), look_at=(2,0,0), raydepth=4) sage: t.light((10,3,2), 1, (1,1,1)) sage: t.light((10,-3,2), 1, (1,1,1)) sage: t.texture('black', color=(0,0,0)) @@ -277,7 +294,7 @@ class Tachyon(WithEqualityById, SageObject): :: - sage: t = Tachyon(xres=800,yres=800, camera_center=(2,5,2), look_at=(2.5,0,0)) + sage: t = Tachyon(xres=800,yres=800, camera_position=(2,5,2), look_at=(2.5,0,0)) sage: t.light((0,0,100), 1, (1,1,1)) sage: t.texture('r', ambient=0.1, diffuse=0.9, specular=0.5, opacity=1.0, color=(1,0,0)) sage: for i in srange(0,50,0.1): @@ -290,14 +307,14 @@ class Tachyon(WithEqualityById, SageObject): center should not coincide with the point which is looked at (see :trac:`7232`):: - sage: t = Tachyon(xres=80,yres=80, camera_center=(2,5,2), look_at=(2,5,2)) + sage: t = Tachyon(xres=80,yres=80, camera_position=(2,5,2), look_at=(2,5,2)) Traceback (most recent call last): ... - ValueError: camera_center and look_at coincide + ValueError: camera_position and look_at coincide Use of a fisheye lens perspective. :: - sage: T = Tachyon(xres=800, yres=600, camera_center=(-1.5,-1.5,.3), projection='fisheye', frustum=(-1.0, 1.0, -1.0, 1.0)) + sage: T = Tachyon(xres=800, yres=600, camera_position=(-1.5,-1.5,.3), projection='fisheye', frustum=(-1.0, 1.0, -1.0, 1.0)) sage: T.texture('t1', color=(0,0,1)) sage: cedges = [[[1, 1, 1], [-1, 1, 1]], [[1, 1, 1], [1, -1, 1]], ....: [[1, 1, 1], [1, 1, -1]], [[-1, 1, 1], [-1, -1, 1]], [[-1, 1, 1], @@ -342,9 +359,10 @@ def __init__(self, antialiasing=False, aspectratio=1.0, raydepth=8, - camera_center=(-3, 0, 0), - updir=(0, 0, 1), - look_at=(0, 0, 0), + camera_position=None, # default value (-3, 0, 0), + camera_center=None, # alternative equivalent name + updir=[0, 0, 1], + look_at=[0, 0, 0], viewdir=None, projection='PERSPECTIVE', focallength='', @@ -365,7 +383,12 @@ def __init__(self, self._aspectratio = aspectratio self._antialiasing = antialiasing self._raydepth = raydepth - self._camera_center = camera_center + if camera_position is not None: + self._camera_position = camera_position + elif camera_center is not None: # make sure that old programs continue to work + self._camera_position = camera_center + else: + self._camera_position = (-3, 0, 0) # default value self._updir = updir self._projection = projection self._focallength = focallength @@ -373,11 +396,11 @@ def __init__(self, self._frustum = frustum self._objects = [] if viewdir is None: - if look_at != camera_center: - self._viewdir = [look_at[i] - camera_center[i] + if look_at != self._camera_position: + self._viewdir = [look_at[i] - self._camera_position[i] for i in range(3)] else: - raise ValueError('camera_center and look_at coincide') + raise ValueError('camera_position and look_at coincide') else: self._viewdir = viewdir @@ -521,7 +544,7 @@ def show(self, **kwds): :: - sage: h = Tachyon(xres=512,yres=512, camera_center=(4,-4,3),viewdir=(-4,4,-3), raydepth=4) + sage: h = Tachyon(xres=512,yres=512, camera_position=(4,-4,3),viewdir=(-4,4,-3), raydepth=4) sage: h.light((4.4,-4.4,4.4), 0.2, (1,1,1)) sage: def f(x,y): return float(sin(x*y)) sage: h.texture('t0', ambient=0.1, diffuse=0.9, specular=0.1, opacity=1.0, color=(1.0,0,0)) @@ -536,7 +559,7 @@ def show(self, **kwds): :: - sage: s = Tachyon(xres=512,yres=512, camera_center=(4,-4,3),viewdir=(-4,4,-3), raydepth=4) + sage: s = Tachyon(xres=512,yres=512, camera_position=(4,-4,3),viewdir=(-4,4,-3), raydepth=4) sage: s.light((4.4,-4.4,4.4), 0.2, (1,1,1)) sage: def f(x,y): return float(sin(x*y)) sage: s.texture('t0', ambient=0.1, diffuse=0.9, specular=0.1, opacity=1.0, color=(1.0,0,0)) @@ -556,7 +579,7 @@ def show(self, **kwds): :: sage: set_verbose(0) - sage: d = Tachyon(xres=512,yres=512, camera_center=(4,-4,3),viewdir=(-4,4,-3), raydepth=4) + sage: d = Tachyon(xres=512,yres=512, camera_position=(4,-4,3),viewdir=(-4,4,-3), raydepth=4) sage: d.light((4.4,-4.4,4.4), 0.2, (1,1,1)) sage: def f(x,y): return float(sin(x*y)) sage: d.texture('t0', ambient=0.1, diffuse=0.9, specular=0.1, opacity=1.0, color=(1.0,0,0)) @@ -618,7 +641,7 @@ def _camera(self): float(self._aspectratio), int(self._antialiasing), int(self._raydepth), - tostr(self._camera_center), + tostr(self._camera_position), tostr(self._viewdir), tostr(self._updir)) if self._frustum != '': @@ -634,7 +657,7 @@ def str(self): EXAMPLES:: - sage: t = Tachyon(xres=500,yres=500, camera_center=(2,0,0)) + sage: t = Tachyon(xres=500,yres=500, camera_position=(2,0,0)) sage: t.light((4,3,2), 0.2, (1,1,1)) sage: t.texture('t2', ambient=0.1, diffuse=0.9, specular=0.5, opacity=1.0, color=(1,0,0)) sage: t.texture('t3', ambient=0.1, diffuse=0.9, specular=0.5, opacity=1.0, color=(0,1,0)) @@ -694,7 +717,7 @@ def texfunc(self, type=0, center=(0, 0, 0), rotate=(0, 0, 0), EXAMPLES: We draw an infinite checkerboard:: - sage: t = Tachyon(camera_center=(2,7,4), look_at=(2,0,0)) + sage: t = Tachyon(camera_position=(2,7,4), look_at=(2,0,0)) sage: t.texture('black', color=(0,0,0), texfunc=1) sage: t.plane((0,0,0),(0,0,1),'black') sage: t.show() @@ -739,7 +762,7 @@ def texture(self, name, ambient=0.2, diffuse=0.8, We draw a scene with 4 spheres that illustrates various uses of the texture command:: - sage: t = Tachyon(camera_center=(2,5,4), look_at=(2,0,0), raydepth=6) + sage: t = Tachyon(camera_position=(2,5,4), look_at=(2,0,0), raydepth=6) sage: t.light((10,3,4), 1, (1,1,1)) sage: t.texture('mirror', ambient=0.05, diffuse=0.05, specular=.9, opacity=0.9, color=(.8,.8,.8)) sage: t.texture('grey', color=(.8,.8,.8), texfunc=3) @@ -964,7 +987,7 @@ def plot(self, f, xmin_xmax, ymin_ymax, texture, grad_f=None, Flat Triangles:: - sage: t = Tachyon(xres=512,yres=512, camera_center=(4,-4,3),viewdir=(-4,4,-3), raydepth=4) + sage: t = Tachyon(xres=512,yres=512, camera_position=(4,-4,3),viewdir=(-4,4,-3), raydepth=4) sage: t.light((4.4,-4.4,4.4), 0.2, (1,1,1)) sage: def f(x,y): return float(sin(x*y)) sage: t.texture('t0', ambient=0.1, diffuse=0.9, specular=0.1, opacity=1.0, color=(1.0,0,0)) @@ -977,7 +1000,7 @@ def plot(self, f, xmin_xmax, ymin_ymax, texture, grad_f=None, Plotting with Smooth Triangles (requires explicit gradient function):: - sage: t = Tachyon(xres=512,yres=512, camera_center=(4,-4,3),viewdir=(-4,4,-3), raydepth=4) + sage: t = Tachyon(xres=512,yres=512, camera_position=(4,-4,3),viewdir=(-4,4,-3), raydepth=4) sage: t.light((4.4,-4.4,4.4), 0.2, (1,1,1)) sage: def f(x,y): return float(sin(x*y)) sage: def g(x,y): return ( float(y*cos(x*y)), float(x*cos(x*y)), 1 ) @@ -1017,7 +1040,7 @@ def parametric_plot(self, f, t_0, t_f, tex, r=.1, cylinders=True, Example (twisted cubic) :: sage: f = lambda t: (t,t^2,t^3) - sage: t = Tachyon(camera_center=(5,0,4)) + sage: t = Tachyon(camera_position=(5,0,4)) sage: t.texture('t') sage: t.light((-20,-20,40), 0.2, (1,1,1)) sage: t.parametric_plot(f,-5,5,'t',min_depth=6) @@ -1051,8 +1074,8 @@ def __init__(self, center, radius, color): sage: from sage.plot.plot3d.tachyon import Light sage: q = Light((1,1,1), 1, (1,1,1)) - sage: q._color - (1.0, 1.0, 1.0) + sage: print(q._center, q._color, q._radius) + (1.0, 1.0, 1.0) (1.0, 1.0, 1.0) 1.0 """ x, y, z = center self._center = (float(x), float(y), float(z)) @@ -1068,8 +1091,11 @@ def str(self): sage: from sage.plot.plot3d.tachyon import Light sage: q = Light((1,1,1), 1, (1,1,1)) - sage: q._radius - 1.0 + sage: print(q.str()) + light center 1.0 1.0 1.0 + rad 1.0 + color 1.0 1.0 1.0 + """ return r""" light center %s diff --git a/src/sage/quadratic_forms/special_values.py b/src/sage/quadratic_forms/special_values.py index 828306c276e..47f8ec3008f 100644 --- a/src/sage/quadratic_forms/special_values.py +++ b/src/sage/quadratic_forms/special_values.py @@ -67,15 +67,15 @@ def gamma__exact(n): if denominator(n) == 1: if n <= 0: return infinity - if n > 0: - return factorial(n-1) + return factorial(n - 1) elif denominator(n) == 2: + # now n = 1/2 + an integer ans = QQ.one() while n != QQ((1, 2)): if n < 0: ans /= n n += 1 - elif n > 0: + else: n += -1 ans *= n @@ -243,6 +243,7 @@ def quadratic_L_function__exact(n, d): if delta == 1: raise TypeError("n must be a critical value (i.e. odd > 0 or even <= 0)") + def quadratic_L_function__numerical(n, d, num_terms=1000): """ Evaluate the Dirichlet L-function (for quadratic character) numerically diff --git a/src/sage/repl/rich_output/backend_base.py b/src/sage/repl/rich_output/backend_base.py index 152530ab383..d62ab376a19 100644 --- a/src/sage/repl/rich_output/backend_base.py +++ b/src/sage/repl/rich_output/backend_base.py @@ -454,23 +454,23 @@ def latex_formatter(self, obj, **kwds): sage: out OutputHtml container sage: out.html - buffer containing 105 bytes + buffer containing 62 bytes sage: out.html.get_str() - '' + '\\[\\newcommand{\\Bold}[1]{\\mathbf{#1}}\\frac{1}{2}\\]' sage: out = backend.latex_formatter([1/2, x, 3/4, ZZ], concatenate=False) sage: out.html.get_str() - '' + '\\[\\newcommand{\\Bold}[1]{\\mathbf{#1}}\\left[\\frac{1}{2}, x, \\frac{3}{4}, \\Bold{Z}\\right]\\]' sage: out = backend.latex_formatter([1/2, x, 3/4, ZZ], concatenate=True) sage: out.html.get_str() - '' + '\\[\\newcommand{\\Bold}[1]{\\mathbf{#1}}\\frac{1}{2} x \\frac{3}{4} \\Bold{Z}\\]' TESTS:: sage: backend.latex_formatter([], concatenate=False).html.get_str() - '' + '\\[\\newcommand{\\Bold}[1]{\\mathbf{#1}}\\left[\\right]\\]' sage: backend.latex_formatter([], concatenate=True).html.get_str() - '' + '\\[\\newcommand{\\Bold}[1]{\\mathbf{#1}}\\]' """ concatenate = kwds.get('concatenate', False) from sage.misc.html import html diff --git a/src/sage/repl/rich_output/output_browser.py b/src/sage/repl/rich_output/output_browser.py index 0c7bd7f5b7e..2746e3dff8e 100644 --- a/src/sage/repl/rich_output/output_browser.py +++ b/src/sage/repl/rich_output/output_browser.py @@ -8,8 +8,9 @@ from sage.repl.rich_output.output_basic import OutputBase from sage.repl.rich_output.buffer import OutputBuffer -latex_re = re.compile(r'', - flags=re.DOTALL) +# regex to match "\[...\]" or "\(...\)" +latex_re = re.compile(r'(?P\\\[|\\\()(?P.*)(?P\\\]|\\\))', + flags=re.DOTALL) class OutputHtml(OutputBase): @@ -20,10 +21,9 @@ def __init__(self, html): INPUT: - ``html`` -- - :class:`~sage.repl.rich_output.buffer.OutputBuffer`. Alternatively, - a string (bytes) can be passed directly which will then be - converted into an - :class:`~sage.repl.rich_output.buffer.OutputBuffer`. String + :class:`~sage.repl.rich_output.buffer.OutputBuffer`. Alternatively, a + string (bytes) can be passed directly which will then be converted + into an :class:`~sage.repl.rich_output.buffer.OutputBuffer`. String containing the html fragment code. Excludes the surrounding ```` and ```` tag. @@ -40,7 +40,7 @@ def __init__(self, html): # pdf export of a notebook m = latex_re.match(html) if m: - if m.group('mode') == 'display': + if m.group('mathstart') == r'\[' and m.group('mathend') == r'\]': self.latex = OutputBuffer('$$' + m.group('latex') + '$$') else: self.latex = OutputBuffer('$' + m.group('latex') + '$') diff --git a/src/sage/repl/rich_output/pretty_print.py b/src/sage/repl/rich_output/pretty_print.py index 64b1a281723..8c82b9c0b06 100644 --- a/src/sage/repl/rich_output/pretty_print.py +++ b/src/sage/repl/rich_output/pretty_print.py @@ -208,9 +208,9 @@ def pretty_print(*args, **kwds): sage: plt = plot(sin) sage: pretty_print(plt) # graphics output - sage: pretty_print(ZZ, 123, plt) # optional - latex - sage: pretty_print(plt, plt) # graphics output + sage: pretty_print(ZZ, 123, plt) + Integer Ring 123 Graphics object consisting of 1 graphics primitive """ dm = get_display_manager() old_preferences_text = dm.preferences.text diff --git a/src/sage/rings/finite_rings/element_base.pyx b/src/sage/rings/finite_rings/element_base.pyx index b076ee0de07..5bf186b145e 100755 --- a/src/sage/rings/finite_rings/element_base.pyx +++ b/src/sage/rings/finite_rings/element_base.pyx @@ -1,12 +1,24 @@ """ Base class for finite field elements -AUTHORS:: +AUTHORS: + +- David Roe (2010-1-14): factored out of sage.structure.element + +- Sebastian Oehms (2018-7-19): added :meth:`conjugate` (see :trac:`26761`) -- David Roe (2010-1-14) -- factored out of sage.structure.element -- Sebastian Oehms (2018-7-19) -- add :meth:`conjugate` (see :trac:`26761`) """ +# **************************************************************************** +# Copyright (C) 2010 David Roe +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + from sage.structure.element cimport Element from sage.structure.parent cimport Parent from sage.rings.integer import Integer diff --git a/src/sage/rings/finite_rings/finite_field_base.pyx b/src/sage/rings/finite_rings/finite_field_base.pyx index 972f07e6de8..a49373492eb 100644 --- a/src/sage/rings/finite_rings/finite_field_base.pyx +++ b/src/sage/rings/finite_rings/finite_field_base.pyx @@ -1919,6 +1919,24 @@ cdef class FiniteField(Field): from sage.rings.finite_rings.hom_finite_field import FrobeniusEndomorphism_finite_field return FrobeniusEndomorphism_finite_field(self, n) + def galois_group(self): + r""" + Return the Galois group of this finite field, a cyclic group generated by Frobenius. + + EXAMPLES:: + + sage: G = GF(3^6).galois_group() + sage: G + Galois group C6 of GF(3^6) + sage: F = G.gen() + sage: F^2 + Frob^2 + sage: F^6 + 1 + """ + from sage.rings.finite_rings.galois_group import GaloisGroup_GF + return GaloisGroup_GF(self) + def dual_basis(self, basis=None, check=True): r""" Return the dual basis of ``basis``, or the dual basis of the power @@ -2056,9 +2074,10 @@ register_unpickle_override( 'sage.rings.ring', 'unpickle_FiniteField_prm', unpickle_FiniteField_prm) -def is_FiniteField(x): - """ - Return ``True`` if ``x`` is of type finite field, and ``False`` otherwise. +def is_FiniteField(R): + r""" + Return whether the implementation of ``R`` has the interface provided by + the standard finite field implementation. EXAMPLES:: @@ -2068,10 +2087,9 @@ def is_FiniteField(x): sage: is_FiniteField(GF(next_prime(10^10))) True - Note that the integers modulo n are not of type finite field, - so this function returns ``False``:: + Note that the integers modulo n are not backed by the finite field type:: sage: is_FiniteField(Integers(7)) False """ - return isinstance(x, FiniteField) + return isinstance(R, FiniteField) diff --git a/src/sage/rings/finite_rings/finite_field_constructor.py b/src/sage/rings/finite_rings/finite_field_constructor.py index 04073fc52a3..e05b7fc9fa7 100644 --- a/src/sage/rings/finite_rings/finite_field_constructor.py +++ b/src/sage/rings/finite_rings/finite_field_constructor.py @@ -166,6 +166,7 @@ # https://www.gnu.org/licenses/ # **************************************************************************** +from collections import defaultdict from sage.structure.category_object import normalize_names from sage.rings.integer import Integer @@ -467,7 +468,23 @@ class FiniteFieldFactory(UniqueFactory): sage: GF(next_prime(2^63)^6) Finite Field in z6 of size 9223372036854775837^6 + Check that :trac:`31547` has been fixed:: + + sage: q=2**152 + sage: GF(q,'a',modulus='primitive') == GF(q,'a',modulus='primitive') + True """ + def __init__(self, *args, **kwds): + """ + Initialization. + + EXAMPLES:: + + sage: TestSuite(GF).run() + """ + self._modulus_cache = defaultdict(dict) + super().__init__(*args, **kwds) + def create_key_and_extra_args(self, order, name=None, modulus=None, names=None, impl=None, proof=None, check_irreducible=True, prefix=None, repr=None, elem_cache=None, @@ -575,7 +592,10 @@ def create_key_and_extra_args(self, order, name=None, modulus=None, names=None, modulus = R.irreducible_element(n) if isinstance(modulus, str): # A string specifies an algorithm to find a suitable modulus. - modulus = R.irreducible_element(n, algorithm=modulus) + if modulus != "random" and modulus in self._modulus_cache[order]: + modulus = self._modulus_cache[order][modulus] + else: + self._modulus_cache[order][modulus] = modulus = R.irreducible_element(n, algorithm=modulus) else: if sage.rings.polynomial.polynomial_element.is_Polynomial(modulus): modulus = modulus.change_variable_name('x') diff --git a/src/sage/rings/finite_rings/galois_group.py b/src/sage/rings/finite_rings/galois_group.py new file mode 100644 index 00000000000..cd953380011 --- /dev/null +++ b/src/sage/rings/finite_rings/galois_group.py @@ -0,0 +1,153 @@ +r""" +Galois groups of Finite Fields +""" + +from sage.groups.abelian_gps.abelian_group_element import AbelianGroupElement +from sage.groups.galois_group import GaloisGroup_cyc +from sage.rings.integer_ring import ZZ +from sage.rings.finite_rings.hom_finite_field import FiniteFieldHomomorphism_generic, FrobeniusEndomorphism_finite_field + +class GaloisGroup_GFElement(AbelianGroupElement): + def as_hom(self): + r""" + Return the automorphism of the finite field corresponding to this element. + + EXAMPLES:: + + sage: GF(3^6).galois_group()([4]).as_hom() + Frobenius endomorphism z6 |--> z6^(3^4) on Finite Field in z6 of size 3^6 + """ + n = self.exponents()[0] + return self.parent()._field.frobenius_endomorphism(n) + + def __call__(self, x): + r""" + Return the action of this automorphism on an element `x` of the finite field. + + EXAMPLES:: + + sage: k. = GF(3^6) + sage: g = k.galois_group()([4]) + sage: g(a) == a^(3^4) + True + """ + return self.as_hom()(x) + + def fixed_field(self): + r""" + The fixed field of this automorphism. + + EXAMPLES:: + + sage: k. = GF(3^12) + sage: g = k.galois_group()([8]) + sage: k0, embed = g.fixed_field() + sage: k0.cardinality() + 81 + sage: embed.domain() is k0 + True + sage: embed.codomain() is k + True + """ + return self.as_hom().fixed_field() + +class GaloisGroup_GF(GaloisGroup_cyc): + r""" + The Galois group of a finite field. + """ + Element = GaloisGroup_GFElement + + def __init__(self, field): + r""" + Create a Galois group. + + TESTS:: + + sage: TestSuite(GF(9).galois_group()).run() + """ + self._field = field + GaloisGroup_cyc.__init__(self, field, (field.degree(),), gen_names="Frob") + + def _repr_(self): + r""" + String representation of this Galois group + + EXAMPLES:: + + sage: GF(9).galois_group() + Galois group C2 of GF(3^2) + """ + return "Galois group C{0} of GF({1}^{0})".format(self._field.degree(), self._field.characteristic()) + + def _element_constructor_(self, x, check=True): + r""" + Create an element of this Galois group from ``x``. + + INPUT: + + - ``x`` -- one of the following (`G` is this Galois group): + + - the integer 1, denoting the identity of `G`; + + - an element of `G`; + + - a list of length 1, giving the exponent of Frobenius + + - a permutation of the right length that defines an element of `G`, + or anything that coerces into such a permutation; + + - an automorphism of the finite field. + + - ``check`` -- check that automorphisms have the correct domain and codomain + + EXAMPLES:: + + sage: k = GF(3^3) + sage: G = k.galois_group() + sage: G(1) + 1 + sage: G([2]) + Frob^2 + sage: G(G.gens()[0]) + Frob + sage: G([(1,3,2)]) + Frob^2 + sage: G(k.hom(k.gen()^3, k)) + Frob + sage: G(k.frobenius_endomorphism()) + Frob + """ + if x == 1: + return self.element_class(self, [0]) + + k = self._field + d = k.degree() + n = None + if isinstance(x, GaloisGroup_GFElement) and x.parent() is self: + n = x.exponents()[0] + elif isinstance(x, FiniteFieldHomomorphism_generic): + if check and not (x.domain() is k and x.codomain() is k): + raise ValueError("Not an automorphism of the correct finite field") + a = k.gen() + b = x(a) + q = k.base_ring().cardinality() + n = 0 + while n < d: + if a == b: + break + n += 1 + a = a**q + else: + raise RuntimeError("Automorphism was not a power of Frobenius") + elif isinstance(x, FrobeniusEndomorphism_finite_field): + if check and not x.domain() is k: + raise ValueError("Not an automorphism of the correct finite field") + n = x.power() + elif isinstance(x, list) and len(x) == 1 and x[0] in ZZ: + n = x[0] + else: + g = self.permutation_group()(x) + n = g(1) - 1 + return self.element_class(self, [n]) + + diff --git a/src/sage/rings/finite_rings/hom_finite_field.pyx b/src/sage/rings/finite_rings/hom_finite_field.pyx index a469bb5e5f9..62f42e6a451 100644 --- a/src/sage/rings/finite_rings/hom_finite_field.pyx +++ b/src/sage/rings/finite_rings/hom_finite_field.pyx @@ -217,20 +217,20 @@ cdef class FiniteFieldHomomorphism_generic(RingHomomorphism_im_gens): sage: FiniteFieldHomomorphism_generic(Hom(ZZ, QQ)) Traceback (most recent call last): ... - TypeError: The domain is not a finite field + TypeError: The domain is not a finite field or does not provide the required interface for finite fields sage: R. = k[] sage: FiniteFieldHomomorphism_generic(Hom(k, R)) Traceback (most recent call last): ... - TypeError: The codomain is not a finite field + TypeError: The codomain is not a finite field or does not provide the required interface for finite fields """ domain = parent.domain() codomain = parent.codomain() if not is_FiniteField(domain): - raise TypeError("The domain is not a finite field") + raise TypeError("The domain is not a finite field or does not provide the required interface for finite fields") if not is_FiniteField(codomain): - raise TypeError("The codomain is not a finite field") + raise TypeError("The codomain is not a finite field or does not provide the required interface for finite fields") if domain.characteristic() != codomain.characteristic() or codomain.degree() % domain.degree() != 0: raise ValueError("No embedding of %s into %s" % (domain, codomain)) if im_gens is None: @@ -421,7 +421,7 @@ cdef class FiniteFieldHomomorphism_generic(RingHomomorphism_im_gens): sage: Frob = k.frobenius_endomorphism() sage: embed = Frob.fixed_field()[1] sage: hash(embed) # random - -2441354824160407762 + -2441354824160407762 """ return Morphism.__hash__(self) @@ -520,10 +520,10 @@ cdef class FrobeniusEndomorphism_finite_field(FrobeniusEndomorphism_generic): sage: FrobeniusEndomorphism_finite_field(k['x']) Traceback (most recent call last): ... - TypeError: The domain must be a finite field + TypeError: The domain is not a finite field or does not provide the required interface for finite fields """ if not is_FiniteField(domain): - raise TypeError("The domain must be a finite field") + raise TypeError("The domain is not a finite field or does not provide the required interface for finite fields") try: n = Integer(n) except TypeError: @@ -865,7 +865,7 @@ cdef class FrobeniusEndomorphism_finite_field(FrobeniusEndomorphism_generic): sage: phi = copy(Frob) sage: phi Frobenius endomorphism t |--> t^(5^2) on Finite Field in t of size 5^3 - sage: Frob == phi + sage: Frob == phi True """ FrobeniusEndomorphism_generic._update_slots(self, slots) diff --git a/src/sage/rings/finite_rings/homset.py b/src/sage/rings/finite_rings/homset.py index 561a8680285..ca4bd31571b 100644 --- a/src/sage/rings/finite_rings/homset.py +++ b/src/sage/rings/finite_rings/homset.py @@ -37,7 +37,9 @@ from sage.rings.homset import RingHomset_generic from sage.rings.finite_rings.hom_finite_field import FiniteFieldHomomorphism_generic +from sage.rings.finite_rings.finite_field_base import is_FiniteField from sage.rings.integer import Integer +from sage.rings.morphism import RingHomomorphism_im_gens from sage.structure.sequence import Sequence class FiniteFieldHomset(RingHomset_generic): @@ -94,7 +96,14 @@ def __call__(self, im_gens, base_map=None, check=True): if self.domain().degree() == 1: from sage.rings.finite_rings.hom_prime_finite_field import FiniteFieldHomomorphism_prime return FiniteFieldHomomorphism_prime(self, im_gens, base_map=base_map, check=check) - return FiniteFieldHomomorphism_generic(self, im_gens, base_map=base_map, check=check) + if is_FiniteField(self.codomain()): + return FiniteFieldHomomorphism_generic(self, im_gens, base_map=base_map, check=check) + # Currently, FiniteFieldHomomorphism_generic does not work if + # the codomain is not derived from the finite field base class; + # in that case, we have to fall back to the generic + # implementation for rings + else: + return RingHomomorphism_im_gens(self, im_gens, base_map=base_map, check=check) except (NotImplementedError, ValueError): try: return self._coerce_impl(im_gens) diff --git a/src/sage/rings/integer.pyx b/src/sage/rings/integer.pyx index a2f913df7fd..56cf1161d0e 100644 --- a/src/sage/rings/integer.pyx +++ b/src/sage/rings/integer.pyx @@ -3468,12 +3468,21 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement): sage: 5.quo_rem(2/3) (15/2, 0) + Check that :trac:`29009` is fixed: + + sage: divmod(1, sys.maxsize+1r) # should not raise OverflowError: Python int too large to convert to C long + (0, 1) + sage: import mpmath + sage: mpmath.mp.prec = 1000 + sage: root = mpmath.findroot(lambda x: x^2 - 3, 2) + sage: len(str(root)) + 301 """ cdef Integer q = PY_NEW(Integer) cdef Integer r = PY_NEW(Integer) cdef long d, res - if type(other) is int: + if is_small_python_int(other): d = PyInt_AS_LONG(other) if d > 0: mpz_fdiv_qr_ui(q.value, r.value, self.value, d) diff --git a/src/sage/rings/number_field/galois_group.py b/src/sage/rings/number_field/galois_group.py index b4b41003891..0335450b224 100644 --- a/src/sage/rings/number_field/galois_group.py +++ b/src/sage/rings/number_field/galois_group.py @@ -17,7 +17,7 @@ """ from sage.structure.sage_object import SageObject -from sage.groups.galois_group import _alg_key, GaloisGroup as GaloisGroup_base +from sage.groups.galois_group import _alg_key, GaloisGroup_perm from sage.groups.perm_gps.permgroup import PermutationGroup_generic, standardize_generator from sage.groups.perm_gps.permgroup_element import PermutationGroupElement @@ -200,7 +200,7 @@ def number_field(self): return self.__number_field -class GaloisGroup_v2(GaloisGroup_base): +class GaloisGroup_v2(GaloisGroup_perm): r""" The Galois group of an (absolute) number field. @@ -262,7 +262,7 @@ def __init__(self, number_field, algorithm='pari', names=None, gc_numbering=None self._type = _type super(GaloisGroup_v2, self).__init__(number_field, algorithm, names, gc_numbering) - @cached_method(key=GaloisGroup_base._get_algorithm) + @cached_method(key=GaloisGroup_perm._get_algorithm) def _pol_galgp(self, algorithm=None): """ Return the Galois group object associated to the defining polynomial of this field extension. diff --git a/src/sage/rings/number_field/number_field.py b/src/sage/rings/number_field/number_field.py index f4cdb9e4f31..fe02c6e8242 100644 --- a/src/sage/rings/number_field/number_field.py +++ b/src/sage/rings/number_field/number_field.py @@ -107,7 +107,9 @@ from sage.misc.cachefunc import cached_method -from sage.misc.superseded import deprecation +from sage.misc.superseded import (deprecation, + deprecated_function_alias) + import sage.libs.ntl.all as ntl import sage.interfaces.gap @@ -4830,9 +4832,9 @@ def _S_class_group_quotient_matrix(self, S): assert A[:c] == 1 and A[c:] == 0 return Q[:c, :a] - def selmer_group(self, S, m, proof=True, orders=False): + def selmer_generators(self, S, m, proof=True, orders=False): r""" - Compute the group `K(S,m)`. + Compute generators of the group `K(S,m)`. INPUT: @@ -4868,14 +4870,22 @@ def selmer_group(self, S, m, proof=True, orders=False): outside of `S`, but may contain it properly when not all primes dividing `m` are in `S`. + .. SEEALSO:: + + :meth:`NumberField_generic.selmer_space`, which gives + additional output when `m=p` is prime: as well as generators, + it gives an abstract vector space over `GF(p)` isomorphic to + `K(S,p)` and maps implementing the isomorphism between this + space and `K(S,p)` as a subgroup of `K^*/(K^*)^p`. + EXAMPLES:: sage: K. = QuadraticField(-5) - sage: K.selmer_group((), 2) + sage: K.selmer_generators((), 2) [-1, 2] The previous example shows that the group generated by the - output may be strictly larger than the 'true' Selmer group of + output may be strictly larger than the group of elements giving extensions unramified outside `S`, since that has order just 2, generated by `-1`:: @@ -4889,31 +4899,31 @@ def selmer_group(self, S, m, proof=True, orders=False): sage: K. = QuadraticField(-5) sage: P2 = K.ideal(2, -a+1) sage: P3 = K.ideal(3, a+1) - sage: K.selmer_group((), 2, orders=True) + sage: K.selmer_generators((), 2, orders=True) ([-1, 2], [2, 2]) - sage: K.selmer_group((), 4, orders=True) + sage: K.selmer_generators((), 4, orders=True) ([-1, 4], [2, 2]) - sage: K.selmer_group([P2], 2) + sage: K.selmer_generators([P2], 2) [2, -1] - sage: K.selmer_group((P2,P3), 4) + sage: K.selmer_generators((P2,P3), 4) [2, -a - 1, -1] - sage: K.selmer_group((P2,P3), 4, orders=True) + sage: K.selmer_generators((P2,P3), 4, orders=True) ([2, -a - 1, -1], [4, 4, 2]) - sage: K.selmer_group([P2], 3) + sage: K.selmer_generators([P2], 3) [2] - sage: K.selmer_group([P2, P3], 3) + sage: K.selmer_generators([P2, P3], 3) [2, -a - 1] - sage: K.selmer_group([P2, P3, K.ideal(a)], 3) # random signs + sage: K.selmer_generators([P2, P3, K.ideal(a)], 3) # random signs [2, a + 1, a] Example over `\QQ` (as a number field):: sage: K. = NumberField(polygen(QQ)) - sage: K.selmer_group([],5) + sage: K.selmer_generators([],5) [] - sage: K.selmer_group([K.prime_above(p) for p in [2,3,5]],2) + sage: K.selmer_generators([K.prime_above(p) for p in [2,3,5]],2) [2, 3, 5, -1] - sage: K.selmer_group([K.prime_above(p) for p in [2,3,5]],6, orders=True) + sage: K.selmer_generators([K.prime_above(p) for p in [2,3,5]],6, orders=True) ([2, 3, 5, -1], [6, 6, 6, 2]) TESTS:: @@ -4922,7 +4932,7 @@ def selmer_group(self, S, m, proof=True, orders=False): sage: P2 = K.ideal(2, -a+1) sage: P3 = K.ideal(3, a+1) sage: P5 = K.ideal(a) - sage: S = K.selmer_group([P2, P3, P5], 3) + sage: S = K.selmer_generators([P2, P3, P5], 3) sage: S in ([2, a + 1, a], [2, a + 1, -a], [2, -a - 1, a], [2, -a - 1, -a]) or S True @@ -4930,7 +4940,7 @@ def selmer_group(self, S, m, proof=True, orders=False): the representation depends on the PARI version:: sage: K. = NumberField(x^3 - 381 * x + 127) - sage: gens = K.selmer_group(K.primes_above(13), 2) + sage: gens = K.selmer_generators(K.primes_above(13), 2) sage: len(gens) == 8 True sage: gens[:5] @@ -4950,9 +4960,10 @@ def selmer_group(self, S, m, proof=True, orders=False): sage: K. = QuadraticField(-5) sage: p = K.primes_above(2)[0] - sage: S = K.selmer_group((), 4) + sage: S = K.selmer_generators((), 4) sage: all(4.divides(x.valuation(p)) for x in S) True + """ units, clgp_gens = self._S_class_group_and_units(tuple(S), proof=proof) gens = [] @@ -4998,6 +5009,9 @@ def selmer_group(self, S, m, proof=True, orders=False): else: return gens + # For backwards compatibility: + selmer_group = deprecated_function_alias(31345, selmer_generators) + def selmer_group_iterator(self, S, m, proof=True): r""" Return an iterator through elements of the finite group `K(S,m)`. @@ -5013,7 +5027,7 @@ def selmer_group_iterator(self, S, m, proof=True): OUTPUT: An iterator yielding the distinct elements of `K(S,m)`. See - the docstring for :meth:`NumberField_generic.selmer_group` for + the docstring for :meth:`NumberField_generic.selmer_generators` for more information. EXAMPLES:: @@ -5038,12 +5052,117 @@ def selmer_group_iterator(self, S, m, proof=True): sage: list(K.selmer_group_iterator([K.prime_above(p) for p in [11,13]],2)) [1, -1, 13, -13, 11, -11, 143, -143] """ - KSgens, ords = self.selmer_group(S=S, m=m, proof=proof, orders=True) + KSgens, ords = self.selmer_generators(S=S, m=m, proof=proof, orders=True) one = self.one() from sage.misc.all import cartesian_product_iterator for ev in cartesian_product_iterator([range(o) for o in ords]): yield prod([p ** e for p, e in zip(KSgens, ev)], one) + def selmer_space(self, S, p, proof=None): + r""" + Compute the group `K(S,p)` as a vector space with maps to and from `K^*`. + + INPUT: + + - ``S`` -- a set of primes ideals of ``self`` + + - ``p`` -- a prime number + + - ``proof`` -- if False, assume the GRH in computing the class group + + OUTPUT: + + (tuple) ``KSp``, ``KSp_gens``, ``from_KSp``, ``to_KSp`` where + + - ``KSp`` is an abstract vector space over `GF(p)` isomorphic to `K(S,p)`; + + - ``KSp_gens`` is a list of elements of `K^*` generating `K(S,p)`; + + - ``from_KSp`` is a function from ``KSp`` to `K^*` + implementing the isomorphism from the abstract `K(S,p)` to + `K(S,p)` as a subgroup of `K^*/(K^*)^p`; + + - ``to_KSP`` is a partial function from `K^*` to ``KSp``, + defined on elements `a` whose image in `K^*/(K^*)^p` lies in + `K(S,p)`, mapping them via the inverse isomorphism to the + abstract vector space ``KSp``. + + The group `K(S,p)` is the finite subgroup of `K^*/(K^*)^p$ + consisting of elements whose valuation at all primes not in + `S` is a multiple of `p`. It contains the subgroup of those + `a\in K^*` such that `K(\sqrt[p]{a})/K` is unramified at all + primes of `K` outside of `S`, but may contain it properly when + not all primes dividing `p` are in `S`. + + EXAMPLES: + + A real quadratic field with class number 2, where the fundamental + unit is a generator, and the class group provides another + generator when `p=2`:: + + sage: K. = QuadraticField(-5) + sage: K.class_number() + 2 + sage: P2 = K.ideal(2, -a+1) + sage: P3 = K.ideal(3, a+1) + sage: P5 = K.ideal(a) + sage: KS2, gens, fromKS2, toKS2 = K.selmer_space([P2, P3, P5], 2) + sage: KS2 + Vector space of dimension 4 over Finite Field of size 2 + sage: gens + [a + 1, a, 2, -1] + + Each generator must have even valuation at primes not in `S`:: + + sage: [K.ideal(g).factor() for g in gens] + [(Fractional ideal (2, a + 1)) * (Fractional ideal (3, a + 1)), + Fractional ideal (-a), + (Fractional ideal (2, a + 1))^2, + 1] + + sage: toKS2(10) + (0, 0, 1, 1) + sage: fromKS2([0,0,1,1]) + -2 + sage: K(10/(-2)).is_square() + True + + sage: KS3, gens, fromKS3, toKS3 = K.selmer_space([P2, P3, P5], 3) + sage: KS3 + Vector space of dimension 3 over Finite Field of size 3 + sage: gens + [1/2, 1/4*a + 1/4, a] + + An example to show that the group `K(S,2)` may be strictly + larger than the group of elements giving extensions unramified + outside `S`. In this case, with `K` of class number `2` and + `S` empty, there is only one quadratic extension of `K` + unramified outside `S`, the Hilbert Class Field + `K(\sqrt{-1})`:: + + sage: K. = QuadraticField(-5) + sage: KS2, gens, fromKS2, toKS2 = K.selmer_space([], 2) + sage: KS2 + Vector space of dimension 2 over Finite Field of size 2 + sage: gens + [2, -1] + sage: for v in KS2: + ....: if not v: + ....: continue + ....: a = fromKS2(v) + ....: print((a,K.extension(x^2-a, 'roota').relative_discriminant().factor())) + ....: + (2, (Fractional ideal (2, a + 1))^4) + (-1, 1) + (-2, (Fractional ideal (2, a + 1))^4) + + sage: K.hilbert_class_field('b') + Number Field in b with defining polynomial x^2 + 1 over its base field + + """ + from sage.rings.number_field.selmer_group import pSelmerGroup + return pSelmerGroup(self, S, p, proof) + def composite_fields(self, other, names=None, both_maps=False, preserve_embedding=True): """ Return the possible composite number fields formed from @@ -5570,7 +5689,7 @@ def elements_of_norm(self, n, proof=None): - ``n`` -- integer in this number field - ``proof`` -- boolean (default: ``True``, unless you called - ``number_field_proof`` and set it otherwise) + :meth:`proof.number_field` and set it otherwise) OUTPUT: @@ -6900,6 +7019,7 @@ def S_unit_group(self, proof=None, S=None): INPUT: - ``proof`` (bool, default True) flag passed to ``pari``. + - ``S`` - list or tuple of prime ideals, or an ideal, or a single ideal or element from which an ideal can be constructed, in which case the support is used. If None, the global unit @@ -11660,9 +11780,13 @@ def class_number(self, proof=None): r""" Return the size of the class group of self. - If proof = False (*not* the default!) and the discriminant of the - field is negative, then the following warning from the PARI manual - applies: + INPUT: + + - ``proof`` -- boolean (default: ``True``, unless you called + :meth:`proof.number_field` and set it otherwise). If + ``proof`` is ``False`` (*not* the default!), and the + discriminant of the field is negative, then the following + warning from the PARI manual applies: .. warning:: @@ -11701,6 +11825,7 @@ def class_number(self, proof=None): sage: type(CyclotomicField(10).class_number()) + """ proof = proof_flag(proof) try: diff --git a/src/sage/rings/number_field/number_field_element_quadratic.pyx b/src/sage/rings/number_field/number_field_element_quadratic.pyx index d212e106165..203f7520fe1 100644 --- a/src/sage/rings/number_field/number_field_element_quadratic.pyx +++ b/src/sage/rings/number_field/number_field_element_quadratic.pyx @@ -2410,17 +2410,29 @@ cdef class NumberFieldElement_gaussian(NumberFieldElement_quadratic): Traceback (most recent call last): ... ValueError: unable to convert i to an element of Algebraic Real Field + + TESTS: + + Check that :trac:`31808` is fixed:: + + sage: C. = QuadraticField(-1) + sage: AA(C.one()) + 1 + sage: AA(C.zero()) + 0 """ import sage.rings.qqbar as qqbar + cdef tuple coeffs if parent is qqbar.QQbar: # AlgebraicNumber.__init__ does a better job than # NumberFieldElement._algebraic_ in this case, but # QQbar._element_constructor_ calls the latter first. return qqbar.AlgebraicNumber(self) - elif parent is qqbar.AA and self[1].is_zero(): - return qqbar.AlgebraicReal(self) - else: - raise ValueError(f"unable to convert {self!r} to an element of {parent!r}") + if parent is qqbar.AA: + coeffs = self.parts() + if coeffs[1].is_zero(): + return qqbar.AlgebraicReal(coeffs[0]) + raise ValueError(f"unable to convert {self!r} to an element of {parent!r}") cpdef real_part(self): r""" diff --git a/src/sage/rings/number_field/number_field_ideal.py b/src/sage/rings/number_field/number_field_ideal.py index 01e5d479024..619529f0eab 100644 --- a/src/sage/rings/number_field/number_field_ideal.py +++ b/src/sage/rings/number_field/number_field_ideal.py @@ -1861,6 +1861,8 @@ def prime_factors(self): """ return [x[0] for x in self.factor()] + support = prime_factors + def _div_(self, other): """ Return the quotient self / other. diff --git a/src/sage/rings/number_field/selmer_group.py b/src/sage/rings/number_field/selmer_group.py new file mode 100644 index 00000000000..0e296497051 --- /dev/null +++ b/src/sage/rings/number_field/selmer_group.py @@ -0,0 +1,715 @@ +# -*- coding: utf-8 -*- +r""" +`p`-Selmer groups of number fields + +This file contains code to compute `K(S,p)` where + +- `K` is a number field +- `S` is a finite set of primes of `K` +- `p` is a prime number + +For `m\ge2`, `K(S,m)` is defined to be the finite subgroup of +`K^*/(K^*)^m` consisting of elements represented by `a\in K^*` whose +valuation at all primes not in `S` is a multiple of `m`. It fits in +the short exact sequence + +.. MATH:: + + 1 \rightarrow O^*_{K,S}/(O^*_{K,S})^m \rightarrow K(S,m) \rightarrow Cl_{K,S}[m] \rightarrow 1 + +where `O^*_{K,S}` is the group of `S`-units of `K` and `Cl_{K,S}` the +`S`-class group. When `m=p` is prime, `K(S,p)` is a +finite-dimensional vector space over `GF(p)`. Its generators come +from three sources: units (modulo `p`'th powers); generators of the +`p`'th powers of ideals which are not principal but whose `p`'the +powers are principal; and generators coming from the prime ideals in +`S`. + +The main function here is :meth:`pSelmerGroup`. This will not +normally be used by users, who instead will access it through a method +of the NumberField class. + + +AUTHORS: + +- John Cremona (2005-2021) + +""" + +# **************************************************************************** +# Copyright (C) 2021 John Cremona +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + + +from sage.rings.finite_rings.finite_field_constructor import GF +from sage.rings.rational_field import QQ +from sage.misc.misc_c import prod + +# A utility function to allow the same code to be used over QQ and +# over number fields: + +def _ideal_generator(I): + r""" + Return the generator of a principal ideal. + + INPUT: + + - ``I`` (fractional ideal or integer) -- either a fractional ideal of a + number field, which must be principal, or a rational integer. + + OUTPUT: + + A generator of `I` when `I` is a principal ideal, else `I` itself. + + EXAMPLES:: + + sage: from sage.rings.number_field.selmer_group import _ideal_generator + sage: _ideal_generator(5) + 5 + + sage: K. = QuadraticField(-11) + sage: [_ideal_generator(K.prime_above(p)) for p in primes(25)] + [2, 1/2*a - 1/2, -1/2*a - 3/2, 7, -a, 13, 17, 19, 1/2*a + 9/2] + + """ + try: + return I.gens_reduced()[0] + except AttributeError: + return I.abs() + +def _coords_in_C_p(I, C, p): + r""" + Return coordinates of the ideal ``I`` with respect to a basis of + the ``p``-torsion of the ideal class group ``C``. + + INPUT: + + - ``I`` (ideal) -- a fractional ideal of a number field ``K``, + whose ``p``'th power is principal. + + - ``C`` (class group) -- the ideal class group of ``K``. + + - ``p`` (prime) -- a prime number. + + OUTPUT: + + The coordinates of the ideal class `[I]` in the `p`-torsion + subgroup `C[p]`. An error is raised if `I^p` is not principal. + + ALGORITHM: + + Find the coordinates of `[I]` with respect to generators of `C` as + an abelian group, check that coordidates are 0 in cyclic factors + of order prime to `p`, and return the list of `c/(n/p)` (mod `p`) + for coordinates `c` for each cyclic factor of order `n` which is a + multiple of `p`. + + EXAMPLES:: + + sage: from sage.rings.number_field.selmer_group import _coords_in_C_p + sage: K. = QuadraticField(-5) + sage: C = K.class_group() + sage: C.order() + 2 + sage: P = K.prime_above(2) + sage: C(P).order() + 2 + sage: _coords_in_C_p(P,C,2) + [1] + sage: _coords_in_C_p(P,C,3) + Traceback (most recent call last): + ... + ValueError: The 3rd power of Fractional ideal (2, a + 1) is not principal + + """ + cyclic_orders = C.gens_orders() + non_p_indices = [i for i,n in enumerate(cyclic_orders) if not p.divides(n)] + p_indices = [(i, n // p) for i,n in enumerate(cyclic_orders) if p.divides(n)] + + coords = C(I).exponents() + if all(coords[i] == 0 for i in non_p_indices) and all(coords[i] % n == 0 for i, n in p_indices): + return [(coords[i] // n) % p for i, n in p_indices] + raise ValueError("The {} power of {} is not principal".format(p.ordinal_str(),I)) + +def _coords_in_C_mod_p(I,C,p): + r""" + Return coordinates of the ideal ``I`` with respect to a basis of + the ``p``-cotorsion of the ideal class group ``C``. + + INPUT: + + - ``I`` (ideal) -- a fractional ideal of a number field ``K``. + + - ``C`` (class group) -- the ideal class group of ``K``. + + - ``p`` (prime) -- a prime number. + + OUTPUT: + + The coordinates of the ideal class `[I]` in the `p`-cotorsion group `C/C^p`. + + ALGORITHM: + + Find the coordinates of `[I]` with respect to generators of `C` as + an abelian group, and return the list of `c` (mod `p`) + for coordinates `c` for each cyclic factor of order `n` which is a + multiple of `p`. + + EXAMPLES:: + + sage: from sage.rings.number_field.selmer_group import _coords_in_C_mod_p + sage: K. = QuadraticField(-5) + sage: C = K.class_group() + sage: [_coords_in_C_mod_p(K.prime_above(p), C, 2) for p in primes(25)] + [[1], [1], [0], [1], [0], [0], [0], [0], [1]] + + An example where the class group has two primary components, one + of which is not cyclic:: + + sage: from sage.rings.number_field.selmer_group import _coords_in_C_mod_p + sage: K. = NumberField(x^2 - x + 58) + sage: C = K.class_group() + sage: C.gens_orders() + (6, 2) + sage: [_coords_in_C_mod_p(K.prime_above(p), C, 2) for p in primes(25)] + [[1, 0], [1, 1], [1, 1], [0, 1], [1, 0], [0, 1], [0, 0], [0, 1], [0, 0]] + sage: [_coords_in_C_mod_p(K.prime_above(p), C, 3) for p in primes(25)] + [[2], [0], [1], [0], [0], [1], [0], [2], [0]] + + """ + cyclic_orders = C.gens_orders() + p_indices = [i for i, n in enumerate(cyclic_orders) if p.divides(n)] + coords = C(I).exponents() + return [coords[i] % p for i in p_indices] + +def _root_ideal(I, C, p): + r""" + Return a ``p``'th root of an ideal with respect to the class group. + + INPUT: + + - ``I`` (ideal) -- a fractional ideal of a number field ``K``, + whose ideal class is a ``p``'th power. + + - ``C`` (class group) -- the ideal class group of ``K``. + + - ``p`` (prime) -- a prime number. + + OUTPUT: + + An ideal `J` such that `J^p` is in the same ideal class as `I`. + + EXAMPLES:: + + sage: from sage.rings.number_field.selmer_group import _root_ideal + sage: K. = NumberField(x^2 - x + 58) + sage: C = K.class_group() + sage: cyclic_gens = C.gens_ideals() + sage: [C(I).order() for I in cyclic_gens] + [6, 2] + sage: C.gens_orders() + (6, 2) + sage: I = cyclic_gens[0]^2 + sage: J = _root_ideal(I, C, 2) + sage: C(J^2) == C(I) + True + sage: I = cyclic_gens[0]^3 + sage: J = _root_ideal(I, C, 3) + sage: C(J^3) == C(I) + True + + """ + cyclic_orders = C.gens_orders() + cyclic_gens = C.gens_ideals() + coords = C(I).exponents() + + # In the next line, e=(ci/p)%n should satisfy p*e=ci (mod n): we + # are dividing the coordinate vector by p in the appropriate sense + + if not all(p.divides(ci) for ci, n in zip(coords, cyclic_orders) if p.divides(n)): + raise ValueError("The ideal class of {} is not a {} power".format(I,p.ordinal_str())) + + w = [ci // p if p.divides(n) else (ci / p) % n for ci, n in zip(coords, cyclic_orders)] + + return prod([gen ** wi for wi, gen in zip(w, cyclic_gens)], C.number_field().ideal(1)) + +def coords_in_U_mod_p(u, U, p): + r""" + Return coordinates of a unit ``u`` with respect to a basis of the + ``p``-cotorsion `U/U^p` of the unit group ``U``. + + INPUT: + + - ``u`` (algebraic unit) -- a unit in a number field ``K``. + + - ``U`` (unit group) -- the unit group of ``K``. + + - ``p`` (prime) -- a prime number. + + OUTPUT: + + The coordinates of the unit `u` in the `p`-cotorsion group `U/U^p`. + + ALGORITHM: + + Take the coordinate vector of `u` with respect to the generators + of the unit group, drop the coordinate of the roots of unity + factor if it is prime to `p`, and reduce the vector mod `p`. + + EXAMPLES:: + + sage: from sage.rings.number_field.selmer_group import coords_in_U_mod_p + sage: K. = NumberField(x^4 - 5*x^2 + 1) + sage: U = K.unit_group() + sage: U + Unit group with structure C2 x Z x Z x Z of Number Field in a with defining polynomial x^4 - 5*x^2 + 1 + sage: u0, u1, u2, u3 = U.gens_values() + sage: u = u1*u2^2*u3^3 + sage: coords_in_U_mod_p(u,U,2) + [0, 1, 0, 1] + sage: coords_in_U_mod_p(u,U,3) + [1, 2, 0] + sage: u*=u0 + sage: coords_in_U_mod_p(u,U,2) + [1, 1, 0, 1] + sage: coords_in_U_mod_p(u,U,3) + [1, 2, 0] + + """ + coords = U.log(u) + start = 1 - int(p.divides(U.zeta_order())) # 0 or 1 + return [c%p for c in coords[start:]] + +def basis_for_p_cokernel(S, C, p): + r""" + Return a basis for the group of ideals supported on ``S`` (mod + ``p``'th-powers) whose class in the class group ``C`` is a ``p``'th power, + together with a function which takes the ``S``-exponents of such an + ideal and returns its coordinates on this basis. + + INPUT: + + - ``S`` (list) -- a list of prime ideals in a number field ``K``. + + - ``C`` (class group) -- the ideal class group of ``K``. + + - ``p`` (prime) -- a prime number. + + OUTPUT: + + (tuple) (``b``, ``f``) where + + - ``b`` is a list of ideals which is a basis for the group of + ideals supported on ``S`` (modulo ``p``'th powers) whose ideal + class is a ``p``'th power; + + - ``f`` is a function which takes such an ideal and returns its + coordinates with respect to this basis. + + EXAMPLES:: + + sage: from sage.rings.number_field.selmer_group import basis_for_p_cokernel + sage: K. = NumberField(x^2 - x + 58) + sage: S = K.ideal(30).support(); S + [Fractional ideal (2, a), + Fractional ideal (2, a + 1), + Fractional ideal (3, a + 1), + Fractional ideal (5, a + 1), + Fractional ideal (5, a + 3)] + sage: C = K.class_group() + sage: C.gens_orders() + (6, 2) + sage: [C(P).exponents() for P in S] + [(5, 0), (1, 0), (3, 1), (1, 1), (5, 1)] + sage: b, f = basis_for_p_cokernel(S, C, 2); b + [Fractional ideal (2), Fractional ideal (15, a + 13), Fractional ideal (5)] + sage: b, f = basis_for_p_cokernel(S, C, 3); b + [Fractional ideal (50, a + 18), + Fractional ideal (10, a + 3), + Fractional ideal (3, a + 1), + Fractional ideal (5)] + sage: b, f = basis_for_p_cokernel(S, C, 5); b + [Fractional ideal (2, a), + Fractional ideal (2, a + 1), + Fractional ideal (3, a + 1), + Fractional ideal (5, a + 1), + Fractional ideal (5, a + 3)] + + """ + from sage.matrix.constructor import Matrix + M = Matrix(GF(p), [_coords_in_C_mod_p(P, C, p) for P in S]) + k = M.left_kernel() + bas = [prod([P ** bj.lift() for P, bj in zip(S, b.list())], + C.number_field().ideal(1)) for b in k.basis()] + f = lambda v: k.coordinate_vector(v) + return bas, f + +# The main function + +def pSelmerGroup(K, S, p, proof=None, debug=False): + r""" + Return the ``p``-Selmer group `K(S,p)` of the number field ``K`` + with respect to the prime ideals in ``S`` + + INPUT: + + - ``K`` (number field) -- a number field, or `\QQ`. + + - ``S`` (list) -- a list of prime ideals in ``K``, or prime + numbers when ``K`` is `\QQ`. + + - ``p`` (prime) -- a prime number. + + - ``proof`` - if True then compute the class group provably + correctly. Default is True. Call :meth:`proof.number_field` to + change this default globally. + + - ``debug`` (boolean, default ``False``) -- debug flag. + + OUTPUT: + + (tuple) ``KSp``, ``KSp_gens``, ``from_KSp``, ``to_KSp`` where + + - ``KSp`` is an abstract vector space over `GF(p)` isomorphic to `K(S,p)`; + + - ``KSp_gens`` is a list of elements of `K^*` generating `K(S,p)`; + + - ``from_KSp`` is a function from ``KSp`` to `K^*` implementing + the isomorphism from the abstract `K(S,p)` to `K(S,p)` as a + subgroup of `K^*/(K^*)^p`; + + - ``to_KSP`` is a partial function from `K^*` to ``KSp``, defined + on elements `a` whose image in `K^*/(K^*)^p` lies in `K(S,p)`, + mapping them via the inverse isomorphism to the abstract vector + space ``KSp``. + + ALGORITHM: + + The list of generators of `K(S,p)` is the concatenation of three + sublists, called ``alphalist``, ``betalist`` and ``ulist`` in the + code. Only ``alphalist`` depends on the primes in `S`. + + - ``ulist`` is a basis for `U/U^p` where `U` is the unit group. + This is the list of fundamental units, including the generator + of the group of roots of unity if its order is divisible by `p`. + These have valuation `0` at all primes. + + - ``betalist`` is a list of the generators of the `p`'th powers of + ideals which generate the `p`-torsion in the class group (so is + empty if the class number is prime to `p`). These have + valuation divisible by `p` at all primes. + + - ``alphalist`` is a list of generators for each ideal `A` in a + basis of those ideals supported on `S` (modulo `p`'th powers of + ideals) which are `p`'th powers in the class group. We find `B` + such that `A/B^p` is principal and take a generator of it, for + each `A` in a generating set. As a special case, if all the + ideals in `S` are principal then ``alphalist`` is a list of + their generators. + + The map from the abstract space to `K^*` is easy: we just take the + product of the generators to powers given by the coefficient + vector. No attempt is made to reduce the resulting product modulo + `p`'th powers. + + The reverse map is more complicated. Given `a\in K^*`: + + - write the principal ideal `(a)` in the form `AB^p` with `A` + supported by `S` and `p`'th power free. If this fails, then `a` + does not represent an element of `K(S,p)` and an error is + raised. + + - set `I_S` to be the group of ideals spanned by `S` mod `p`'th + powers, and `I_{S,p}` the subgroup of `I_S` which maps to `0` in + `C/C^p`. + + - Convert `A` to an element of `I_{S,p}`, hence find the + coordinates of `a` with respect to the generators in + ``alphalist``. + + - after dividing out by `A`, now `(a)=B^p` (with a different `a` + and `B`). Write the ideal class `[B]`, whose `p`'th power is + trivial, in terms of the generators of `C[p]`; then `B=(b)B_1`, + where the coefficients of `B_1` with respect to generators of + `C[p]` give the coordinates of the result with respect to the + generators in ``betalist``. + + - after dividing out by `B`, and by `b^p`, we now have `(a)=(1)`, + so `a` is a unit, which can be expressed in terms of the unit + generators. + + EXAMPLES: + + Over `\QQ` the the unit contribution is trivial unless `p=2` and + the class group is trivial:: + + sage: from sage.rings.number_field.selmer_group import pSelmerGroup + sage: QS2, gens, fromQS2, toQS2 = pSelmerGroup(QQ, [2,3], 2) + sage: QS2 + Vector space of dimension 3 over Finite Field of size 2 + sage: gens + [2, 3, -1] + sage: a = fromQS2([1,1,1]); a.factor() + -1 * 2 * 3 + sage: toQS2(-6) + (1, 1, 1) + + sage: QS3, gens, fromQS3, toQS3 = pSelmerGroup(QQ, [2,13], 3) + sage: QS3 + Vector space of dimension 2 over Finite Field of size 3 + sage: gens + [2, 13] + sage: a = fromQS3([5,4]); a.factor() + 2^5 * 13^4 + sage: toQS3(a) + (2, 1) + sage: toQS3(a) == QS3([5,4]) + True + + A real quadratic field with class number 2, where the fundamental + unit is a generator, and the class group provides another + generator when `p=2`:: + + sage: K. = QuadraticField(-5) + sage: K.class_number() + 2 + sage: P2 = K.ideal(2, -a+1) + sage: P3 = K.ideal(3, a+1) + sage: P5 = K.ideal(a) + sage: KS2, gens, fromKS2, toKS2 = pSelmerGroup(K, [P2, P3, P5], 2) + sage: KS2 + Vector space of dimension 4 over Finite Field of size 2 + sage: gens + [a + 1, a, 2, -1] + + Each generator must have even valuation at primes not in `S`:: + + sage: [K.ideal(g).factor() for g in gens] + [(Fractional ideal (2, a + 1)) * (Fractional ideal (3, a + 1)), + Fractional ideal (-a), + (Fractional ideal (2, a + 1))^2, + 1] + + sage: toKS2(10) + (0, 0, 1, 1) + sage: fromKS2([0,0,1,1]) + -2 + sage: K(10/(-2)).is_square() + True + + sage: KS3, gens, fromKS3, toKS3 = pSelmerGroup(K, [P2, P3, P5], 3) + sage: KS3 + Vector space of dimension 3 over Finite Field of size 3 + sage: gens + [1/2, 1/4*a + 1/4, a] + + The ``to`` and ``from`` maps are inverses of each other:: + + sage: K. = QuadraticField(-5) + sage: S = K.ideal(30).support() + sage: KS2, gens, fromKS2, toKS2 = pSelmerGroup(K, S, 2) + sage: KS2 + Vector space of dimension 5 over Finite Field of size 2 + sage: assert all(toKS2(fromKS2(v))==v for v in KS2) + sage: KS3, gens, fromKS3, toKS3 = pSelmerGroup(K, S, 3) + sage: KS3 + Vector space of dimension 4 over Finite Field of size 3 + sage: assert all(toKS3(fromKS3(v))==v for v in KS3) + """ + from sage.rings.number_field.number_field import proof_flag + from sage.modules.free_module import VectorSpace + from sage.sets.set import Set + + proof = proof_flag(proof) + + # Input check: p and all P in S must be prime. Remove any repeats in S. + + S = list(Set(S)) + if not all(P.is_prime() for P in S): + raise ValueError("elements of S must all be prime") + if not p.is_prime(): + raise ValueError("p must be prime") + + F = GF(p) + + # Step 1. The unit contribution: all fundamental units, and also the + # generating root of unity if its order is a multiple of p; we just + # take generators of U/U^p. These have valuation 0 everywhere. + + hK = 1 if K == QQ else K.class_number(proof=proof) + C = K.class_group() if K == QQ else K.class_group(proof=proof) + + hKp = (hK%p == 0) # flag whether the class number is divisible by p + + if K == QQ: + if p == 2: + ulist = [QQ(-1)] + else: + ulist = [] + else: + U = K.unit_group(proof=proof) + ulist = U.gens_values() + if U.zeta_order() % p: + ulist = ulist[1:] + + if debug: + print("{} generators in ulist = {}".format(len(ulist),ulist)) + + # Step 2. The class group contribution: generators of the p'th + # powers of ideals generating the p-torsion in the class group. + # These have valuation divisible by p everywhere. + + if hKp: + betalist = [_ideal_generator(c ** n) + for c, n in zip(C.gens_ideals(), C.gens_orders()) + if n % p == 0] + else: + betalist = [] + + if debug: + print("{} generators in betalist = {}".format(len(betalist),betalist)) + + # Step 3. The part depending on S: one generator for each ideal A + # in a basis of those ideals supported on S (modulo p'th powers of + # ideals) which is a p'th power in the class group. We find B + # such that A/B^p is principal and take a generator of that, for + # each A in a generating set. + + # As a special case, when the class number is 1 we just take + # generators of the primes in S. + + if hK > 1: + T, f = basis_for_p_cokernel(S, C, p) + alphalist = [_ideal_generator(I / _root_ideal(I, C, p) ** p) for I in T] + else: + f = lambda x:x + alphalist = [_ideal_generator(P) for P in S] + + if debug: + print("{} generators in alphalist = {}".format(len(alphalist), alphalist)) + + # Now we have the generators of K(S,p), and define K(S,p) as an + # abstract vector space: + + KSp_gens = alphalist + betalist + ulist + KSp = VectorSpace(GF(p), len(KSp_gens)) + + if debug: + print("Generators of K(S,p) = {} (dimension {})".format(KSp_gens, len(KSp_gens))) + + # Now we define maps in each direction between the abstract space and K^*. + + # Define the easy map from KSp into K^*: + + def from_KSp(v): + return prod([g ** vi for g, vi in zip(KSp_gens, v)], K(1)) + + # Define the hard map from (a subgroup of) K^* to KSp: + + def to_KSp(a): + # Check that a is in K(S,p): + + if not a: + raise ValueError("argument {} should be nonzero".format(a)) + try: + a = K(a) + except ValueError: + raise ValueError("argument {} should be in {}".format(a, K)) + + if not all(P in S or a.valuation(P) % p == 0 for P in a.support()): + raise ValueError("argument {} should have valuations divisible by {} at all primes in {}".format(a, p, S)) + + # 1. (a) is a p'th power mod ideals in S, say (a)=AB^p, where + # A is supported on S and is a linear combination of the + # ideals T above. Find the exponents of the P_i in S in A: + + S_vals = [F(a.valuation(P)) for P in S] + avec = list(f(S_vals)) # coordinates of A w.r.t ideals in T (mod p'th powers) + a1 = prod((alpha ** e for alpha, e in zip(alphalist,avec)), K(1)) + a /= a1 + if debug: + print("alpha component is {} with coords {}".format(a1,avec)) + if K == QQ: + print("continuing with quotient {} whose ideal should be a {}'th power: {}".format(a,p,a.factor())) + else: + print("continuing with quotient {} whose ideal should be a {}'th power: {}".format(a,p,K.ideal(a).factor())) + + # 2. Now (a) is a p'th power, say (a)=B^p. + # Find B and the exponents of [B] w.r.t. basis of C[p]: + + supp = a.support() + vals = [a.valuation(P) for P in supp] + if debug: + assert all(v % p == 0 for v in vals) + one = K(1) if K == QQ else K.ideal(1) + aa = a.abs() if K == QQ else K.ideal(a) + B = prod((P ** (v // p) for P, v in zip(supp,vals)), one) + if debug: + assert B ** p == aa + print("B={}".format(B)) + print("a={}".format(a)) + + if hKp: + bvec = _coords_in_C_p(B, C, p) + a2 = prod((beta ** e for beta, e in zip(betalist, bvec)), K(1)) + a /= a2 + supp = a.support() + vals = [a.valuation(P) for P in supp] + if debug: + assert all(v % p == 0 for v in vals) + B = prod((P ** (v // p) for P, v in zip(supp, vals)), one) + if debug: + assert B ** p == aa + else: + bvec = [] + a2 = 1 + + if debug: + print("beta component is {} with coords {}".format(a2,bvec)) + print("continuing with quotient {} which should be a p'th power times a unit".format(a)) + + # 3. Now (a) = (c)^p for some c, so a/c^p is a unit + + if K != QQ: + assert B.is_principal() + + if debug: + print("B={}".format(B)) + a3 = B if K==QQ else _ideal_generator(B) + if debug: + print("a3={}".format(a3)) + a /= a3 ** p + if debug: + print("dividing by {}th power of {}".format(p,a3)) + print("continuing with quotient {} which should be a unit".format(a)) + + #4. Now a is a unit + + # NB not a.is_unit which is true for all a in K^*. One could + # also test K.ring_of_integers()(a).is_unit(). + + if debug: + if K == QQ: + assert a.abs()==1 + else: + assert K.ideal(a).is_one() + + if K == QQ: + if p == 2: + cvec = [1] if a == -1 else [0] + else: + cvec = [] + else: + cvec = coords_in_U_mod_p(a,U,p) + + if debug: + print("gamma component has coords {}".format(cvec)) + + return KSp(avec + bvec + cvec) + + return KSp, KSp_gens, from_KSp, to_KSp diff --git a/src/sage/rings/padics/README.txt b/src/sage/rings/padics/README.txt index e5a8057473e..2fc16002482 100644 --- a/src/sage/rings/padics/README.txt +++ b/src/sage/rings/padics/README.txt @@ -7,149 +7,101 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -This document has the following goals: - -- to explain the file structure and class hierarchy for p-adics in Sage - -- to serve as a location to collect priorities for action items on p-adics - -- to serve as a collection point for tips about altering and adding to these files. - -The parent hierarchy uses multiple inheritance. The numbers before each class name give the superclasses immediately above that class. - -LocalGeneric [1] (local_generic.py) -1 CappedAbsoluteGeneric [2] (capped_absolute_generic.py) -1 CappedRelativeGeneric [3] (capped_relative_generic.py) -3 CappedRelativeFieldGeneric [4] (capped_relative_field_generic.py) -3 CappedRelativeRingGeneric [5] (capped_relative_ring_generic.py) -1 FixedModGeneric [6] (fixed_mod_generic.py) -1 LazyGeneric [7] (lazy_generic.py) -7 LazyFieldGeneric [8] (lazy_field_generic.py) -7 LazyRingGeneric [9] (lazy_ring_generic.py) - -1 pAdicGeneric [A] (padic_generic.py) -A pAdicRingGeneric [B] (padic_ring_generic.py) -B, 5 pAdicCappedRelativeRingGeneric [C] (padic_capped_relative_ring_generic.py) -B, 2 pAdicCappedAbsoluteRingGeneric [D] (padic_capped_absolute_ring_generic.py) -B, 6 pAdicFixedModRingGeneric [E] (padic_fixed_mod_ring_generic.py) -B, 9 pAdicLazyRingGeneric [F] (padic_lazy_ring_generic.py) -A pAdicFieldGeneric [G] (padic_field_generic.py) -G, 4 pAdicCappedRelativeFieldGeneric [H] (padic_capped_relative_field_generic.py) -G, 8 pAdicLazyFieldGeneric [I] (padic_lazy_field_generic.py) - -A pAdicBaseGeneric [J] (padic_base_generic.py) -B, J pAdicRingBaseGeneric [K] (padic_ring_base_generic.py) -K, C pAdicRingCappedRelative [L] (padic_ring_capped_relative.py) -K, D pAdicRingCappedAbsolute [M] (padic_ring_capped_absolute.py) -K, E pAdicRingFixedMod [N] (padic_ring_fixed_mod.py) -K, F pAdicRingLazy [O] (padic_ring_lazy.py) -G, J pAdicFieldBaseGeneric [P] (padic_field_base_generic.py) -P, H pAdicFieldCappedRelative [Q] (padic_field_capped_relative.py) -P, I pAdicFieldLazy [R] (padic_field_lazy.py) - -A pAdicExtensionGeneric [S] (padic_extension_generic.py) -S EisensteinExtensionGeneric [T] (eisenstein_extension_generic.py) -T, C EisensteinExtensionRingCappedRelative [U] (padic_extension_leaves.py) -T, H EisensteinExtensionFieldCappedRelative [V] (padic_extension_leaves.py) -T, D EisensteinExtensionRingCappedAbsolute [W] (padic_extension_leaves.py) -T, E EisensteinExtensionRingFixedMod [X] (padic_extension_leaves.py) -T, F EisensteinExtensionRingLazy [Y] (padic_extension_leaves.py, does not yet exist) -T, I EisensteinExtensionFieldLazy [Z] (padic_extension_leaves.py, does not yet exist) -S UnramifiedExtensionGeneric [a] (unramified_extension_generic.py) -a, C UnramifiedExtensionRingCappedRelative [b] (padic_extension_leaves.py) -a, H UnramifiedExtensionFieldCappedRelative [c] (padic_extension_leaves.py) -a, D UnramifiedExtensionRingCappedAbsolute [d] (padic_extension_leaves.py) -a, E UnramifiedExtensionRingFixedMod [e] (padic_extension_leaves.py) -a, F UnramifiedExtensionRingLazy [f] (padic_extension_leaves.py, does not yet exist) -a, I UnramifiedExtensionFieldLazy [g] (padic_extension_leaves.py, does not yet exist) -S TwoStepExtensionGeneric [h] (two_step_extension_generic.py, does not yet exist) -h, C TwoStepExtensionRingCappedRelative [i] (padic_extension_leaves.py, does not yet exist) -h, H TwoStepExtensionFieldCappedRelative [j] (padic_extension_leaves.py, does not yet exist) -h, D TwoStepExtensionRingCappedAbsolute [k] (padic_extension_leaves.py, does not yet exist) -h, E TwoStepExtensionRingFixedMod [l] (padic_extension_leaves.py, does not yet exist) -h, F TwoStepExtensionRingLazy [m] (padic_extension_leaves.py, does not yet exist) -h, I TwoStepExtensionFieldLazy [n] (padic_extension_leaves.py, does not yet exist) -S pAdicRelativeExtensionGeneric [h] (padic_relative_extension_generic.py, does not yet exist) -h, C pAdicRelativeExtensionRingCappedRelative [i] (padic_extension_leaves.py, does not yet exist) -h, H pAdicRelativeExtensionFieldCappedRelative [j] (padic_extension_leaves.py, does not yet exist) -h, D pAdicRelativeExtensionRingCappedAbsolute [k] (padic_extension_leaves.py, does not yet exist) -h, E pAdicRelativeExtensionRingFixedMod [l] (padic_extension_leaves.py, does not yet exist) -h, F pAdicRelativeExtensionRingLazy [m] (padic_extension_leaves.py, does not yet exist) -h, I pAdicRelativeExtensionFieldLazy [n] (padic_extension_leaves.py, does not yet exist) - - -The element hierarchy is simpler (it's a tree), because Cython does not allow multiple inheritance. -Hopefully it will eventually include power series rings. - -LocalGenericElement (local_generic_element.pyx) : - pAdicGenericElement (padic_generic_element.pyx) : - pAdicBaseGenericElement (padic_base_generic_element.pyx) : the base elements are implemented using GMP mpz_t - pAdicCappedRelativeElement (padic_capped_relative_element.pyx) : - pAdicCappedAbsoluteElement (padic_capped_absolute_element.pyx) : - pAdicFixedModElement (padic_fixed_mod_element.pyx) : - pAdicLazyElement (padic_lazy_element.py, currently not supported) : - pAdicExtElement (padic_ext_element.pyx) : - pAdicZZpXElement (padic_ZZ_pX_element.pyx) : unramified and eisenstein extensions of Qp and Zp, using ntl ZZ_pX - pAdicZZpXCRElement (padic_ZZ_pX_CR_element.pyx) : - pAdicZZpXCAElement (padic_ZZ_pX_CA_element.pyx) : - pAdicZZpXFMElement (padic_ZZ_pX_FM_element.pyx) : - pAdicZZpXLElement (padic_ZZ_pX_L_element.pyx, does not yet exist) : - pAdicZZpEXElement (padic_ZZ_pEX_element.pyx, does not yet exist) : generic absolute extensions of Qp and Zp, using ntl ZZ_pEX - pAdicZZpEXCRElement (padic_ZZ_pEX_CR_element.pyx, does not yet exist) : - pAdicZZpEXCAElement (padic_ZZ_pEX_CA_element.pyx, does not yet exist) : - pAdicZZpEXFMElement (padic_ZZ_pEX_FM_element.pyx, does not yet exist) : - pAdicZZpEXLElement (padic_ZZ_pEX_L_element.pyx, does not yet exist) : - pAdicRelExtElement (padic_rel_ext_element.pyx, does not yet exist) : relative extensions of base and extension rings and fields. - pAdicRelExtCRElement (padic_rel_ext_CR_element.pyx, does not yet exist) : - pAdicRelExtCAElement (padic_rel_ext_CA_element.pyx, does not yet exist) : - pAdicRelExtFMElement (padic_rel_ext_FM_element.pyx, does not yet exist) : - pAdicRelExtLElement (padic_rel_ext_L_element.pyx, does not yet exist) : - -To Do: - Residue Fields - log in high ramification - exp - residue systems - sqrt - hensel lifting - norms and traces - degree - Documentation (file, priority, assigned to): - padic_printing.pyx (10/10) - padic_capped_relative_element.pyx (8/10) - padic_capped_absolute_element.pyx (8/10) - padic_fixed_mod_element.pyx (8/10) - factory.py (7/10) - padic_generic_element.pyx (7/10) - padic_ZZ_pX_CR_element.pyx (5/10) - padic_ZZ_pX_CA_element.pyx (5/10) - padic_ZZ_pX_FM_element.pyx (5/10) - padic_ZZ_pX_element.pyx (5/10) - padic_ext_element.pyx (5/10) - padic_extension_generic.py (5/10) - local_generic.py (5/10) - padic_generic.py (5/10) - local_generic_element.pyx (4/10) - padic_extension_leaves.py (4/10) - pow_computer.pyx (4/10) - pow_computer_ext.pyx (4/10) - eisenstein_extension_generic.py (3/10) - unramified_extension_generic.py (3/10) - padic_field_generic.py (2/10) - padic_ring_generic.py (2/10) - padic_base_generic_element.pyx (2/10) - padic_base_generic.py (2/10) - padic_field_capped_relative.py (2/10) - padic_printing_defaults.py (2/10) - padic_ring_base_generic.py (1/10) - padic_ring_capped_absolute.py (1/10) - padic_ring_capped_relative.py (1/10) - padic_ring_fixed_mod.py (1/10) - capped_absolute_generic.py (1/10) - capped_relative_generic.py (1/10) - fixed_mod_generic.py (1/10) - padic_field_lazy.py (0/10) - lazy_generic.py (0/10) - padic_lazy_element.py (0/10) - padic_lazy_field_generic.py (0/10) - padic_lazy_generic.py (0/10) - padic_lazy_ring_generic.py (0/10) - padic_ring_lazy.py (0/10) - valuation.py (0/10) +This document aims at explaining the (somehow complicated) file structure and +class hierarchy for p-adics in Sage. + + +Below is a representation of the parent hierarchy. +The representation is simplified is the sense that multiple inheritance does not +appear; we assume however that these relationships are obvious from the names of +the classes (e.g. pAdicRingBaseGeneric derives from pAdicBaseGeneric). +The file indicated between brackets is the file where the class is defined. + + +pAdicGeneric ......................................................... [padic_generic.py] + + pAdicRingGeneric ................................................. [generic_nodes.py] + pAdicFieldGeneric ................................................ [generic_nodes.py] + pAdicLatticeGeneric .............................................. [generic_nodes.py] + pAdicRelaxedGeneric .............................................. [generic_nodes.py] + + pAdicBaseGeneric ................................................. [padic_base_generic.py] + + pAdicRingBaseGeneric ......................................... [generic_nodes.py] + pAdicRingCappedRelative .................................. [padic_base_leaves.py] + pAdicRingCappedAbsolute .................................. [padic_base_leaves.py] + pAdicRingFloatingPoint ................................... [padic_base_leaves.py] + pAdicRingFixedMod ........................................ [padic_base_leaves.py] + pAdicRingLattice ......................................... [padic_base_leaves.py] + pAdicRingRelaxed ......................................... [padic_base_leaves.py] + + pAdicFieldBaseGeneric ........................................ [generic_nodes.py] + pAdicFieldCappedRelative ................................. [padic_base_leaves.py] + pAdicFieldFloatingPoint .................................. [padic_base_leaves.py] + pAdicFieldLattice ........................................ [padic_base_leaves.py] + pAdicFieldRelaxed ........................................ [padic_base_leaves.py] + + pAdicExtensionGeneric ............................................ [padic_extension_generic.py] + + UnramifiedExtensionGeneric ................................... [unramified_extension_generic.py] + UnramifiedExtensionRingCappedRelative .................... [padic_extension_leaves.py] + UnramifiedExtensionRingCappedAbsolute .................... [padic_extension_leaves.py] + UnramifiedExtensionRingFixedMod .......................... [padic_extension_leaves.py] + UnramifiedExtensionRingFloatingPoint ..................... [padic_extension_leaves.py] + UnramifiedExtensionFieldCappedRelative ................... [padic_extension_leaves.py] + UnramifiedExtensionFieldFloatingPoint .................... [padic_extension_leaves.py] + + EisensteinExtensionGeneric ................................... [eisenstein_extension_generic.py] + EisensteinExtensionRingCappedRelative .................... [padic_extension_leaves.py] + EisensteinExtensionRingCappedAbsolute .................... [padic_extension_leaves.py] + EisensteinExtensionRingFixedMod .......................... [padic_extension_leaves.py] + EisensteinExtensionFieldCappedRelative ................... [padic_extension_leaves.py] + # Eisenstein extensions on top of an unramified extension + RelativeRamifiedExtensionRingCappedRelative .............. [relative_extension_leaves.py] + RelativeRamifiedExtensionRingCappedAbsolute .............. [relative_extension_leaves.py] + RelativeRamifiedExtensionRingFixedMod .................... [relative_extension_leaves.py] + RelativeRamifiedExtensionRingFloatingPoint ............... [relative_extension_leaves.py] + RelativeRamifiedExtensionFieldCappedRelative ............. [relative_extension_leaves.py] + RelativeRamifiedExtensionFieldFloatingPoint .............. [relative_extension_leaves.py] + + + +Below is the hierarchy for element classes. +Here all dependencies are represented since Cython does not allow multiple inheritance. + + +pAdicGenericElement .................................................. [padic_generic_element.pyx] + pAdicTemplateElement ............................................. [padic_template_element.pxi] + CAElement .................................................... [CA_template.pxi] + pAdicCappedAbsoluteElement ............................... [padic_capped_absolute_element.pyx] + qAdicCappedAbsoluteElement ............................... [qadic_flint_CA.pyx] + RelativeRamifiedCappedAbsoluteElement .................... [relative_ramified_CA.pyx] + + CRElement .................................................... [CR_template.pxi] + pAdicCappedRelativeElement ............................... [padic_capped_relative_element.pyx] + qAdicCappedRelativeElement ............................... [qadic_flint_CR.pyx] + RelativeRamifiedCappedRelativeElement .................... [relative_ramified_CR.pyx] + + FMElement .................................................... [FM_template.pxi] + pAdicFixedModElement ..................................... [padic_fixed_mod_element.pyx] + qAdicFixedModElement ..................................... [qadic_flint_FM.pyx] + RelativeRamifiedFixedModElement .......................... [relative_ramified_FM.pyx] + + FPElement .................................................... [FP_template.pxi] + pAdicFloatingPointElement ................................ [padic_floating_point_element.pyx] + qAdicFloatingPointElement ................................ [qadic_flint_FP.pyx] + RelativeRamifiedFloatingPointElement ..................... [relative_ramified_FP.pyx] + + pAdicExtElement .................................................. [padic_ext_element.pyx] + pAdicZZpXElement ............................................. [padic_ZZ_pX_element.pyx] + pAdicZZpXCAElement ....................................... [padic_ZZ_pX_CA_element.pyx] + pAdicZZpXCRElement ....................................... [padic_ZZ_pX_CR_element.pyx] + pAdicZZpXFMElement ....................................... [padic_ZZ_pX_FM_element.pyx] + + pAdicLatticeElement .............................................. [padic_lattice_element.py] + pAdicLatticeCapElement ....................................... [padic_lattice_element.py] + pAdicLatticeFloatElement ..................................... [padic_lattice_element.py] + + RelaxedElement ................................................... [relaxed_template.pxi] + RelaxedElement_* ............................................. [relaxed_template.pxi] + pAdicRelaxedElement_* .................................... [padic_relaxed_element.pxd] diff --git a/src/sage/rings/padics/all.py b/src/sage/rings/padics/all.py index 758fb2f69c5..b90b7da1d8a 100644 --- a/src/sage/rings/padics/all.py +++ b/src/sage/rings/padics/all.py @@ -1,6 +1,6 @@ from .generic_nodes import is_pAdicField, is_pAdicRing -from .factory import Zp, Zq, Zp as pAdicRing, ZpCR, ZpCA, ZpFM, ZpFP, ZpLC, ZpLF, ZqCR, ZqCA, ZqFM, ZqFP #, ZpL, ZqL -from .factory import Qp, Qq, Qp as pAdicField, QpCR, QpFP, QpLC, QpLF, QqCR, QqFP #, QpL, QqL +from .factory import Zp, Zq, Zp as pAdicRing, ZpCR, ZpCA, ZpFM, ZpFP, ZpLC, ZpLF, ZqCR, ZqCA, ZqFM, ZqFP, ZpER +from .factory import Qp, Qq, Qp as pAdicField, QpCR, QpFP, QpLC, QpLF, QqCR, QqFP, QpER from .factory import pAdicExtension from .padic_generic import local_print_mode from .pow_computer import PowComputer diff --git a/src/sage/rings/padics/common_conversion.pyx b/src/sage/rings/padics/common_conversion.pyx index a256e554f61..7ad0554072d 100644 --- a/src/sage/rings/padics/common_conversion.pyx +++ b/src/sage/rings/padics/common_conversion.pyx @@ -129,6 +129,8 @@ cdef long get_ordp(x, PowComputer_class prime_pow) except? -10000: # We don't want to multiply by e again. return k elif isinstance(x, pAdicGenericElement): + if x.parent().is_relaxed(): + return x.valuation() k = (x).valuation_c() if not (x)._is_base_elt(prime_pow.prime): # We have to be careful with overflow @@ -209,6 +211,8 @@ cdef long get_preccap(x, PowComputer_class prime_pow) except? -10000: if (x)._is_exact_zero(): return maxordp prec = x.precision_absolute() + if prec is infinity: + return maxordp k = mpz_get_si(prec.value) if not (x)._is_base_elt(prime_pow.prime): # since x lives in a subfield, the ramification index of x's parent will divide e. @@ -405,7 +409,9 @@ cdef inline int cconv_shared(mpz_t out, x, long prec, long valshift, PowComputer x = Integer(x) elif isinstance(x, pari_gen): x = x.sage() - if isinstance(x, pAdicGenericElement) or sage.rings.finite_rings.integer_mod.is_IntegerMod(x): + if isinstance(x, pAdicGenericElement) and x.parent().is_relaxed(): + x = x.lift(valshift + prec) + elif isinstance(x, pAdicGenericElement) or sage.rings.finite_rings.integer_mod.is_IntegerMod(x): x = x.lift() if isinstance(x, Integer): if valshift > 0: diff --git a/src/sage/rings/padics/factory.py b/src/sage/rings/padics/factory.py index 6990884468e..38546c6d262 100644 --- a/src/sage/rings/padics/factory.py +++ b/src/sage/rings/padics/factory.py @@ -42,9 +42,11 @@ pAdicRingFixedMod, pAdicRingFloatingPoint, pAdicRingLattice, + pAdicRingRelaxed, pAdicFieldCappedRelative, pAdicFieldFloatingPoint, - pAdicFieldLattice) + pAdicFieldLattice, + pAdicFieldRelaxed) from . import padic_printing ###################################################### @@ -214,6 +216,26 @@ def get_key_base(p, prec, type, print_mode, names, ram_name, print_pos, print_se elif absolute_cap is None: absolute_cap = 2 * relative_cap prec = (relative_cap, absolute_cap) + elif type == 'relaxed': + default_prec = halting_prec = None + secure = False + if isinstance(prec, (list, tuple)): + if len(prec) == 1: + default_prec = prec + elif len(prec) == 2: + default_prec, halting_prec = prec + else: + default_prec = prec[0] + halting_prec = prec[1] + secure = prec[2] + else: + default_prec = prec + if default_prec is None: + default_prec = DEFAULT_PREC + if halting_prec is None: + halting_prec = 2 * default_prec + halting_prec = max(default_prec, halting_prec) + prec = (default_prec, halting_prec, secure) else: if prec is not None: prec = Integer(prec) @@ -306,6 +328,7 @@ def get_key_base(p, prec, type, print_mode, names, ram_name, print_pos, print_se padic_field_cache = {} DEFAULT_PREC = Integer(20) + class Qp_class(UniqueFactory): r""" A creation function for `p`-adic fields. @@ -318,6 +341,9 @@ class Qp_class(UniqueFactory): In the lattice capped case, ``prec`` can either be a pair (``relative_cap``, ``absolute_cap``) or an integer (understood at relative cap). + In the relaxed case, ``prec`` can be either a + pair (``default_prec``, ``halting_prec``) or an integer + (understood at default precision). Except in the floating point case, individual elements keep track of their own precision. See TYPES and PRECISION below. @@ -346,7 +372,7 @@ class Qp_class(UniqueFactory): - ``print_max_terms`` -- integer (default ``None``) The maximum number of terms shown. See PRINTING below. - - ``show_prec`` -- a boolean or a string (default ``None``) Specify how + - ``show_prec`` -- a boolean or a string (default ``None``) Specify how the precision is printed. See PRINTING below. - ``check`` -- bool (default ``True``) whether to check if `p` is prime. @@ -362,9 +388,9 @@ class Qp_class(UniqueFactory): TYPES AND PRECISION: - There are two types of precision for a `p`-adic element. The first - is relative precision, which gives the number of known `p`-adic - digits:: + There are two main types of precision for a `p`-adic element. + The first is relative precision, which gives the number of known + `p`-adic digits:: sage: R = Qp(5, 20, 'capped-rel', 'series'); a = R(675); a 2*5^2 + 5^4 + O(5^22) @@ -377,8 +403,20 @@ class Qp_class(UniqueFactory): sage: a.precision_absolute() 22 - There are three types of `p`-adic fields: capped relative fields, - floating point fields and lattice precision fields. + There are several types of `p`-adic fields, depending on the methods + used for tracking precision. Namely, we have: + + - capped relative fields (``type='capped-rel'``) + + - capped absolute fields (``type='capped-abs'``) + + - fixed modulus fields (``type='fixed-mod'``) + + - floating point fields (``type='floating-point'``) + + - lattice precision fields (``type='lattice-cap'`` or ``type='lattice-float'``) + + - exact fields with relaxed arithmetics (``type='relaxed'``) In the capped relative case, the relative precision of an element is restricted to be at most a certain value, specified at the @@ -402,6 +440,17 @@ class Qp_class(UniqueFactory): In the lattice case, precision on elements is tracked by a global lattice that is updated after every operation, yielding better precision behavior at the cost of higher memory and runtime usage. + We refer to the documentation of the function :func:`ZpLC` for a + small demonstration of the capabilities of this precision model. + + Finally, the model for relaxed `p`-adics is quite different from any of + the other types. In addition to storing a finite approximation, one + also stores a method for increasing the precision. + A quite interesting feature with relaxed `p`-adics is the possibility to + create (in some cases) self-referent numbers, that are numbers whose + `n`-th digit is defined by the previous ones. + We refer to the documentation of the function :func:`ZpL` for a + small demonstration of the capabilities of this precision model. PRINTING: @@ -696,7 +745,7 @@ def create_key(self, p, prec = None, type = 'capped-rel', print_mode = None, check = True if label is not None and type not in ['lattice-cap','lattice-float']: raise ValueError("label keyword only supported for lattice precision") - return get_key_base(p, prec, type, print_mode, names, ram_name, print_pos, print_sep, print_alphabet, print_max_terms, show_prec, check, ['capped-rel', 'floating-point', 'lattice-cap', 'lattice-float'], label) + return get_key_base(p, prec, type, print_mode, names, ram_name, print_pos, print_sep, print_alphabet, print_max_terms, show_prec, check, ['capped-rel', 'floating-point', 'lattice-cap', 'lattice-float', 'relaxed'], label) def create_object(self, version, key): r""" @@ -746,6 +795,13 @@ def create_object(self, version, key): else: return pAdicFieldFloatingPoint(p, prec, {'mode': print_mode, 'pos': print_pos, 'sep': print_sep, 'alphabet': print_alphabet, 'ram_name': name, 'max_ram_terms': print_max_terms, 'show_prec': show_prec}, name) + elif type == 'relaxed': + if print_mode == 'terse': + return pAdicFieldRelaxed(p, prec, {'mode': print_mode, 'pos': print_pos, 'sep': print_sep, 'alphabet': print_alphabet, + 'ram_name': name, 'max_terse_terms': print_max_terms, 'show_prec': show_prec}, name) + else: + return pAdicFieldRelaxed(p, prec, {'mode': print_mode, 'pos': print_pos, 'sep': print_sep, 'alphabet': print_alphabet, + 'ram_name': name, 'max_ram_terms': print_max_terms, 'show_prec': show_prec}, name) elif type[:8] == 'lattice-': subtype = type[8:] if print_mode == 'terse': @@ -1376,7 +1432,6 @@ def QpLC(p, prec = None, *args, **kwds): sage: R = QpLC(2) sage: R 2-adic Field with lattice-cap precision - """ return Qp(p, prec, 'lattice-cap', *args, **kwds) @@ -1395,6 +1450,19 @@ def QpLF(p, prec = None, *args, **kwds): """ return Qp(p, prec, 'lattice-float', *args, **kwds) +def QpER(p, prec=None, halt=None, secure=False, *args, **kwds): + r""" + A shortcut function to create relaxed `p`-adic fields. + + See :func:`ZpER` for more information about this model of precision. + + EXAMPLES:: + + sage: R = QpER(2) + sage: R + 2-adic Field handled with relaxed arithmetics + """ + return Qp(p, (prec, halt, secure), 'relaxed', *args, **kwds) ####################################################################################################### # @@ -1417,13 +1485,16 @@ class Zp_class(UniqueFactory): ring. In the lattice capped case, ``prec`` can either be a pair (``relative_cap``, ``absolute_cap``) or an integer (understood at relative cap). + In the relaxed case, ``prec`` can be either a + pair (``default_prec``, ``halting_prec``) or an integer + (understood at default precision). Except for the fixed modulus and floating point cases, individual elements keep track of their own precision. See TYPES and PRECISION below. - ``type`` -- string (default: ``'capped-rel'``) Valid types are ``'capped-rel'``, ``'capped-abs'``, ``'fixed-mod'``, - ``'floating-point'``, ``'lattice-cap'``, ``'lattice-float'`` + ``'floating-point'``, ``'lattice-cap'``, ``'lattice-float'``, ``'relaxed'`` See TYPES and PRECISION below - ``print_mode`` -- string (default: ``None``). Valid modes are @@ -1462,7 +1533,7 @@ class Zp_class(UniqueFactory): TYPES AND PRECISION: - There are three types of precision. + There are two main types of precision. The first is relative precision; it gives the number of known `p`-adic digits:: @@ -1477,27 +1548,20 @@ class Zp_class(UniqueFactory): sage: a.precision_absolute() 22 - The third one is lattice precision. - It is not attached to a single `p`-adic number but is a unique - object modeling the precision on a set of `p`-adics, which is - typically the set of all elements within the same parent:: + There are several types of `p`-adic rings, depending on the methods + used for tracking precision. Namely, we have: - sage: R = ZpLC(17) - sage: x = R(1,10); y = R(1,5) - sage: R.precision() - Precision lattice on 2 objects - sage: R.precision().precision_lattice() - [2015993900449 0] - [ 0 1419857] + - capped relative rings (``type='capped-rel'``) - We refer to the documentation of the function :func:`ZpLC` for - more information about this precision model. + - capped absolute rings (``type='capped-abs'``) - There are many types of `p`-adic rings: capped relative rings - (``type='capped-rel'``), capped absolute rings - (``type='capped-abs'``), fixed modulus rings (``type='fixed-mod'``), - floating point rings (``type='floating-point'``), lattice capped rings - (``type='lattice-cap'``) and lattice float rings (``type='lattice-float'``). + - fixed modulus rings (``type='fixed-mod'``) + + - floating point rings (``type='floating-point'``) + + - lattice precision rings (``type='lattice-cap'`` or ``type='lattice-float'``) + + - exact fields with relaxed arithmetics (``type='relaxed'``) In the capped relative case, the relative precision of an element is restricted to be at most a certain value, specified at the @@ -1550,6 +1614,15 @@ class Zp_class(UniqueFactory): We refer to the documentation of the function :func:`ZpLC` for a small demonstration of the capabilities of this precision model. + Finally, the model for relaxed `p`-adics is quite different from any of + the other types. In addition to storing a finite approximation, one + also stores a method for increasing the precision. + A quite interesting feature with relaxed `p`-adics is the possibility to + create (in some cases) self-referent numbers, that are numbers whose + `n`-th digit is defined by the previous ones. + We refer to the documentation of the function :func:`ZpL` for a + small demonstration of the capabilities of this precision model. + PRINTING: There are many different ways to print `p`-adic elements. The @@ -1873,7 +1946,7 @@ def create_key(self, p, prec = None, type = 'capped-rel', print_mode = None, raise ValueError("label keyword only supported for lattice precision") return get_key_base(p, prec, type, print_mode, names, ram_name, print_pos, print_sep, print_alphabet, print_max_terms, show_prec, check, - ['capped-rel', 'fixed-mod', 'capped-abs', 'floating-point', 'lattice-cap', 'lattice-float'], + ['capped-rel', 'fixed-mod', 'capped-abs', 'floating-point', 'lattice-cap', 'lattice-float', 'relaxed'], label=label) def create_object(self, version, key): @@ -1902,7 +1975,7 @@ def create_object(self, version, key): # keys changed in order to reduce irrelevant duplications: e.g. two Zps with print_mode 'series' # that are identical except for different 'print_alphabet' now return the same object. key = get_key_base(p, prec, type, print_mode, name, None, print_pos, print_sep, print_alphabet, - print_max_terms, None, False, ['capped-rel', 'fixed-mod', 'capped-abs', 'lattice-cap', 'lattice-float']) + print_max_terms, None, False, ['capped-rel', 'fixed-mod', 'capped-abs', 'lattice-cap', 'lattice-float', 'relaxed']) try: obj = self._cache[version, key]() if obj is not None: @@ -1922,6 +1995,9 @@ def create_object(self, version, key): elif type == 'floating-point': return pAdicRingFloatingPoint(p, prec, {'mode': print_mode, 'pos': print_pos, 'sep': print_sep, 'alphabet': print_alphabet, 'ram_name': name, 'max_ram_terms': print_max_terms, 'show_prec': show_prec}, name) + elif type == 'relaxed': + return pAdicRingRelaxed(p, prec, {'mode': print_mode, 'pos': print_pos, 'sep': print_sep, 'alphabet': print_alphabet, + 'ram_name': name, 'max_ram_terms': print_max_terms, 'show_prec': show_prec}, name) elif type[:8] == 'lattice-': subtype = type[8:] return pAdicRingLattice(p, prec, subtype, {'mode': print_mode, 'pos': print_pos, 'sep': print_sep, 'alphabet': print_alphabet, @@ -2008,6 +2084,7 @@ def Zq(q, prec = None, type = 'capped-rel', modulus = None, names=None, TYPES AND PRECISION: + There are two types of precision for a `p`-adic element. The first is relative precision (default), which gives the number of known `p`-adic digits:: @@ -2889,6 +2966,225 @@ def ZpLF(p, prec=None, *args, **kwds): """ return Zp(p, prec, 'lattice-float', *args, **kwds) +def ZpER(p, prec=None, halt=None, secure=False, *args, **kwds): + r""" + A shortcut function to create relaxed `p`-adic rings. + + INPUT: + + - ``prec`` -- an integer (default: ``20``), the default + precision + + - ``halt`` -- an integer (default: twice ``prec``), the + halting precision + + - ``secure`` -- a boolean (default: ``False``); if ``False``, + consider indistinguishable elements at the working precision + as equal; otherwise, raise an error. + + See documentation for :func:`Zp` for a description of the other + input parameters. + + A SHORT INTRODUCTION TO RELAXED `p`-ADICS: + + The model for relaxed `p`-adics is quite different from any of the + other types of `p`-adics. In addition to storing a finite + approximation, one also stores a method for increasing the + precision. + + Relaxed `p`-adic rings are created by the constructor :func:`ZpER`:: + + sage: R = ZpER(5, print_mode="digits") + sage: R + 5-adic Ring handled with relaxed arithmetics + + The precision is not capped in `R`:: + + sage: R.precision_cap() + +Infinity + + However, a default precision is fixed. This is the precision + at which the elements will be printed:: + + sage: R.default_prec() + 20 + + A default halting precision is also set. It is the default absolute + precision at which the elements will be compared. By default, it is + twice the default precision:: + + sage: R.halting_prec() + 40 + + However, both the default precision and the halting precision can be + customized at the creation of the parent as follows: + + sage: S = ZpER(5, prec=10, halt=100) + sage: S.default_prec() + 10 + sage: S.halting_prec() + 100 + + One creates elements as usual:: + + sage: a = R(17/42) + sage: a + ...00244200244200244201 + + sage: R.random_element() # random + ...21013213133412431402 + + Here we notice that 20 digits (that is the default precision) are printed. + However, the computation model is designed in order to guarantee that more + digits of `a` will be available on demand. + This feature is reflected by the fact that, when we ask for the precision + of `a`, the software answers `+\infty`:: + + sage: a.precision_absolute() + +Infinity + + Asking for more digits is achieved by the methods :meth:`at_precision_absolute` + and :meth:`at_precision_relative`:: + + sage: a.at_precision_absolute(30) + ...?244200244200244200244200244201 + + As a shortcut, one can use the bracket operator:: + + sage: a[:30] + ...?244200244200244200244200244201 + + Of course, standard operations are supported:: + + sage: b = R(42/17) + sage: a + b + ...03232011214322140002 + sage: a - b + ...42311334324023403400 + sage: a * b + ...00000000000000000001 + sage: a / b + ...12442142113021233401 + sage: sqrt(a) + ...20042333114021142101 + + We observe again that only 20 digits are printed but, as before, + more digits are available on demand:: + + sage: sqrt(a)[:30] + ...?142443342120042333114021142101 + + .. RUBRIC:: Equality tests + + Checking equalities between relaxed `p`-adics is a bit subtle and can + sometimes be puzzling at first glance. + + When the parent is created with ``secure=False`` (which is the + default), elements are compared at the current precision, or at the + default halting precision if it is higher:: + + sage: a == b + False + + sage: a == sqrt(a)^2 + True + sage: a == sqrt(a)^2 + 5^50 + True + + In the above example, the halting precision is `40`; it is the + reason why a congruence modulo `5^50` is considered as an equality. + However, if both sides of the equalities have been previously + computed with more digits, those digits are taken into account. + Hence comparing two elements at different times can produce + different results:: + + sage: aa = sqrt(a)^2 + 5^50 + sage: a == aa + True + sage: a[:60] + ...?244200244200244200244200244200244200244200244200244200244201 + sage: aa[:60] + ...?244200244300244200244200244200244200244200244200244200244201 + sage: a == aa + False + + This annoying situation, where the output of `a == aa` may change + depending on previous computations, cannot occur when the parent is + created with ``secure=True``. + Indeed, in this case, if the equality cannot be decided, an error + is raised:: + + sage: S = ZpER(5, secure=True) + sage: u = S.random_element() + sage: uu = u + 5^50 + sage: u == uu + Traceback (most recent call last): + ... + PrecisionError: unable to decide equality; try to bound precision + + sage: u[:60] == uu + False + + .. RUBRIC:: Self-referent numbers + + A quite interesting feature with relaxed `p`-adics is the possibility to + create (in some cases) self-referent numbers. Here is an example. + We first declare a new variable as follows:: + + sage: x = R.unknown() + sage: x + ...?.0 + + We then use the method :meth:`set` to define `x` by writing down an equation + it satisfies:: + + sage: x.set(1 + 5*x^2) + True + + The variable `x` now contains the unique solution of the equation + `x = 1 + 5 x^2`:: + + sage: x + ...04222412141121000211 + + This works because the `n`-th digit of the right hand size of the + defining equation only involves the `i`-th digits of `x` with `i < n` + (this is due to the factor `5`). + + As a comparison, the following does not work:: + + sage: y = R.unknown() + sage: y.set(1 + 3*y^2) + True + sage: y + ...?.0 + sage: y[:20] + Traceback (most recent call last): + ... + RecursionError: definition looks circular + + Self-referent definitions also work with systems of equations:: + + sage: u = R.unknown() + sage: v = R.unknown() + sage: w = R.unknown() + + sage: u.set(1 + 2*v + 3*w^2 + 5*u*v*w) + True + sage: v.set(2 + 4*w + sqrt(1 + 5*u + 10*v + 15*w)) + True + sage: w.set(3 + 25*(u*v + v*w + u*w)) + True + + sage: u + ...31203130103131131433 + sage: v + ...33441043031103114240 + sage: w + ...30212422041102444403 + """ + return Zp(p, (prec, halt, secure), 'relaxed', *args, **kwds) + ####################################################################################################### # diff --git a/src/sage/rings/padics/generic_nodes.py b/src/sage/rings/padics/generic_nodes.py index 8c6c1c5ef92..6a1da3b4747 100644 --- a/src/sage/rings/padics/generic_nodes.py +++ b/src/sage/rings/padics/generic_nodes.py @@ -36,7 +36,7 @@ class CappedAbsoluteGeneric(LocalGeneric): def is_capped_absolute(self): """ - Returns whether this `p`-adic ring bounds precision in a + Return whether this `p`-adic ring bounds precision in a capped absolute fashion. The absolute precision of an element is the power of `p` modulo @@ -61,7 +61,7 @@ def is_capped_absolute(self): def _prec_type(self): """ - Returns the precision handling type. + Return the precision handling type. EXAMPLES:: @@ -73,7 +73,7 @@ def _prec_type(self): class CappedRelativeGeneric(LocalGeneric): def is_capped_relative(self): """ - Returns whether this `p`-adic ring bounds precision in a capped + Return whether this `p`-adic ring bounds precision in a capped relative fashion. The relative precision of an element is the power of p modulo @@ -98,7 +98,7 @@ def is_capped_relative(self): def _prec_type(self): """ - Returns the precision handling type. + Return the precision handling type. EXAMPLES:: @@ -110,7 +110,7 @@ def _prec_type(self): class FixedModGeneric(LocalGeneric): def is_fixed_mod(self): """ - Returns whether this `p`-adic ring bounds precision in a fixed + Return whether this `p`-adic ring bounds precision in a fixed modulus fashion. The absolute precision of an element is the power of p modulo @@ -136,7 +136,7 @@ def is_fixed_mod(self): def _prec_type(self): """ - Returns the precision handling type. + Return the precision handling type. EXAMPLES:: @@ -148,7 +148,7 @@ def _prec_type(self): class FloatingPointGeneric(LocalGeneric): def is_floating_point(self): """ - Returns whether this `p`-adic ring uses a floating point precision model. + Return whether this `p`-adic ring uses a floating point precision model. Elements in the floating point model are stored by giving a valuation and a unit part. Arithmetic is done where the unit @@ -172,7 +172,7 @@ def is_floating_point(self): def _prec_type(self): """ - Returns the precision handling type. + Return the precision handling type. EXAMPLES:: @@ -359,7 +359,7 @@ def _prec_type(self): def is_lattice_prec(self): """ - Returns whether this `p`-adic ring bounds precision using + Return whether this `p`-adic ring bounds precision using a lattice model. In lattice precision, relationships between elements @@ -699,9 +699,488 @@ def convert_multiple(self, *elts): # We return the created elements return ans +class pAdicRelaxedGeneric(pAdicGeneric): + r""" + Generic class for relaxed `p`-adics. + + INPUT: + + - `p` -- the underlying prime number + + - ``prec`` -- the default precision + + TESTS:: + + sage: R = ZpER(17) # indirect doctest + sage: R._prec_type() + 'relaxed' + """ + def _get_element_class(self, name=None): + r""" + Return the class handling an element of type ``name``. + + INPUT: + + - ``name`` -- a string or ``None`` (default: ``None``); if ``None``, + return the generic class from which all the others derive + + TESTS:: + + sage: R = ZpER(5) + sage: R._get_element_class() + + + sage: R._get_element_class("add") + + + sage: R._get_element_class("unknown") + + + sage: R._get_element_class("foobar") + Traceback (most recent call last): + ... + AttributeError: module 'sage.rings.padics.padic_relaxed_element' has no attribute 'pAdicRelaxedElement_foobar' + """ + if name is None: + return self.Element + clsname = self._element_class_prefix + name + cls = getattr(self._element_class_module, clsname) + return cls + + def _prec_type(self): + r""" + Return the precision handling type. + + EXAMPLES:: + + sage: ZpER(5)._prec_type() + 'relaxed' + """ + return 'relaxed' + + def is_relaxed(self): + r""" + Return whether this `p`-adic ring is relaxed. + + EXAMPLES:: + + sage: R = Zp(5) + sage: R.is_relaxed() + False + sage: S = ZpER(5) + sage: S.is_relaxed() + True + """ + return True + + def is_secure(self): + r""" + Return ``False`` if this `p`-adic relaxed ring is not secure + (i.e. if indistinguishable elements at the working precision + are considered as equal); ``True`` otherwise (in which case, + an error is raised when equality cannot be decided). + + EXAMPLES:: + + sage: R = ZpER(5) + sage: R.is_secure() + False + sage: x = R(20/21) + sage: y = x + 5^50 + sage: x == y + True + + sage: S = ZpER(5, secure=True) + sage: S.is_secure() + True + sage: x = S(20/21) + sage: y = x + 5^50 + sage: x == y + Traceback (most recent call last): + ... + PrecisionError: unable to decide equality; try to bound precision + """ + return self._secure + + def default_prec(self): + r""" + Return the default precision of this relaxed `p`-adic ring. + + The default precision is mostly used for printing: it is the + number of digits which are printed for unbounded elements + (that is elements having infinite absolute precision). + + EXAMPLES:: + + sage: R = ZpER(5, print_mode="digits") + sage: R.default_prec() + 20 + sage: R(1/17) + ...34024323104201213403 + + sage: S = ZpER(5, prec=10, print_mode="digits") + sage: S.default_prec() + 10 + sage: S(1/17) + ...4201213403 + """ + return self._default_prec + + def halting_prec(self): + r""" + Return the default halting precision of this relaxed `p`-adic ring. + + The halting precision is the precision at which elements of this + parent are compared (unless more digits have been previously + computed). + By default, it is twice the default precision. + + EXAMPLES:: + + sage: R = ZpER(5, print_mode="digits") + sage: R.halting_prec() + 40 + """ + return self._halting_prec + + def precision_cap(self): + r""" + Return the precision cap of this `p`-adic ring, which is infinite + in the case of relaxed rings. + + EXAMPLES:: + + sage: R = ZpER(5) + sage: R.precision_cap() + +Infinity + """ + return infinity + + def _coerce_map_from_(self, R): + r""" + Return ``True`` if there is a coercion map from ``R`` to this ring. + + EXAMPLES:: + + sage: R = ZpER(5) + sage: K = R.fraction_field() + sage: K.has_coerce_map_from(R) # indirect doctest + True + sage: R.has_coerce_map_from(K) # indirect doctest + False + """ + if isinstance(R, pAdicRelaxedGeneric) and self is R.fraction_field(): + return True + + def _element_constructor_(self, x, prec=None): + r""" + Return an element of this ring. + + INPUT: + + - ``x`` -- the datum from which the element is created + + - ``prec`` -- an integer or ``None`` (default: ``None``); + if given, bound the precision of the element to ``prec`` + + EXAMPLES:: + + sage: R = ZpER(7, prec=5) + + sage: a = R(17/71) + sage: a + 3 + 3*7^2 + 4*7^3 + 4*7^4 + ... + sage: a.precision_absolute() + +Infinity + + sage: b = R(17/71, prec=10) + sage: b + 3 + 3*7^2 + 4*7^3 + 4*7^4 + 2*7^5 + 7^6 + 5*7^8 + 5*7^9 + O(7^10) + sage: b.precision_absolute() + 10 + + TESTS:: + + sage: R(1/7) + Traceback (most recent call last): + ... + ValueError: negative valuation + + We check that conversion from other types of `p`-adics works:: + + sage: S = Qp(7) + sage: c = S(7^5) + sage: c + 7^5 + O(7^25) + sage: R(c) + 7^5 + O(7^25) + """ + parent = x.parent() + if parent is self and prec is None: + return x + elif isinstance(parent, pAdicRelaxedGeneric): + if parent.Element is self.Element: + if not self.is_field() and x.valuation() < 0: + raise ValueError("negative valuation") + return self._get_element_class('bound')(self, x, prec) + raise NotImplementedError + elif isinstance(parent, pAdicGeneric): + if not self.is_field() and x.valuation() < 0: + raise ValueError("negative valuation") + if prec is None: + prec = x.precision_absolute() + else: + prec = min(prec, x.precision_absolute()) + return self._get_element_class('value')(self, x.lift(), precbound=prec) + elif x == 0 and prec is None: + return self._get_element_class('zero')(self) + elif x == 1 and prec is None: + return self._get_element_class('one')(self) + else: + try: + x = self.exact_ring()(x) + return self._get_element_class('value')(self, x, precbound=prec) + except (TypeError, ValueError): + pass + try: + x = self.exact_field()(x) + num = x.numerator() + denom = x.denominator() + except (TypeError, ValueError, AttributeError): + pass + else: + if not self.is_field() and denom % self.prime() == 0: + raise ValueError("negative valuation") + num = self._get_element_class('value')(self, num) + denom = self._get_element_class('value')(self, denom) + return self._get_element_class('div')(self, num, denom, precbound=prec) + raise TypeError("unable to convert '%s' to a relaxed %s-adic integer" % (x, self.prime())) + + def an_element(self, unbounded=False): + r""" + Return an element in this ring. + + EXAMPLES:: + + sage: R = ZpER(7, prec=5) + sage: R.an_element() + 7 + O(7^5) + sage: R.an_element(unbounded=True) + 7 + ... + """ + p = self(self.prime()) + if not unbounded: + p = p.at_precision_absolute() + return p + + def some_elements(self, unbounded=False): + r""" + Return a list of elements in this ring. + + This is typically used for running generic tests (see :class:`TestSuite`). + + EXAMPLES:: + + sage: R = ZpER(7, prec=5) + sage: R.some_elements() + [O(7^5), + 1 + O(7^5), + 7 + O(7^5), + 7 + O(7^5), + 1 + 5*7 + 3*7^2 + 6*7^3 + O(7^5), + 7 + 6*7^2 + 6*7^3 + 6*7^4 + O(7^5)] + + sage: R.some_elements(unbounded=True) + [0, + 1 + ..., + 7 + ..., + 7 + ..., + 1 + 5*7 + 3*7^2 + 6*7^3 + ..., + 7 + 6*7^2 + 6*7^3 + 6*7^4 + ...] + """ + p = self(self.prime()) + a = self.gen() + one = self.one() + L = [self.zero(), one, p, a, (one+p+p).inverse_of_unit(), p-p**2] + if self.is_field(): + L.extend([~(p-p-a),p**(-20)]) + if not unbounded: + L = [ x.at_precision_absolute() for x in L ] + return L + + def unknown(self, start_val=0, digits=None): + r""" + Return a self-referent number in this ring. + + INPUT: + + - ``start_val`` -- an integer (default: 0); a lower bound on the + valuation of the returned element + + - ``digits`` -- an element, a list or ``None`` (default: ``None``); + the first digit or the list of the digits of the returned element + + NOTE: + + Self-referent numbers are numbers whose digits are defined in terms + of the previous ones. This method is used to declare a self-referent + number (and optionally, to set its first digits). + The definition of the number itself will be given afterwords using + to method meth:`sage.rings.padics.relaxed_template.RelaxedElement_unknown.set` + of the element. + + EXAMPLES: + + sage: R = ZpER(5, prec=10) + + We declare a self-referent number:: + + sage: a = R.unknown() + + So far, we do not know anything on `a` (except that it has nonnegative + valuation):: + + sage: a + O(5^0) + + We can now use the method meth:`sage.rings.padics.relaxed_template.RelaxedElement_unknown.set` + to define `a`. Below, for example, we say that the digits of `a` have to + agree with the digits of `1 + 5 a`. Note that the factor `5` shifts the + digits; the `n`-th digit of `a` is then defined by the previous ones:: + + sage: a.set(1 + 5*a) + True + + After this, `a` contains the solution of the equation `a = 1 + 5 a`, that + is `a = -1/4`:: + + sage: a + 1 + 5 + 5^2 + 5^3 + 5^4 + 5^5 + 5^6 + 5^7 + 5^8 + 5^9 + ... + + Here is another example with an equation of degree `2`:: + + sage: b = R.unknown() + sage: b.set(1 - 5*b^2) + True + sage: b + 1 + 4*5 + 5^2 + 3*5^4 + 4*5^6 + 4*5^8 + 2*5^9 + ... + sage: (sqrt(R(21)) - 1) / 10 + 1 + 4*5 + 5^2 + 3*5^4 + 4*5^6 + 4*5^8 + 2*5^9 + ... + + Cross self-referent definitions are also allowed:: + + sage: u = R.unknown() + sage: v = R.unknown() + sage: w = R.unknown() + + sage: u.set(1 + 2*v + 3*w^2 + 5*u*v*w) + True + sage: v.set(2 + 4*w + sqrt(1 + 5*u + 10*v + 15*w)) + True + sage: w.set(3 + 25*(u*v + v*w + u*w)) + True + + sage: u + 3 + 3*5 + 4*5^2 + 5^3 + 3*5^4 + 5^5 + 5^6 + 3*5^7 + 5^8 + 3*5^9 + ... + sage: v + 4*5 + 2*5^2 + 4*5^3 + 5^4 + 5^5 + 3*5^6 + 5^8 + 5^9 + ... + sage: w + 3 + 4*5^2 + 4*5^3 + 4*5^4 + 4*5^5 + 2*5^6 + 5^8 + 5^9 + ... + + TESTS:: + + sage: a = R.unknown() + sage: a.set(1 + 3*a) + True + sage: a + O(5^0) + sage: a.at_precision_absolute(10) + Traceback (most recent call last): + ... + RecursionError: definition looks circular + """ + valuation = ZZ(start_val) + if (not self.is_field()) and valuation < 0: + raise ValueError("valuation must be nonnegative") + if digits is not None and not isinstance(digits, (list, tuple)): + digits = [digits] + return self._get_element_class('unknown')(self, valuation, digits) + + def random_element(self, integral=False, prec=None): + r""" + Return a random element in this ring. + + INPUT: + + - ``integral`` -- a boolean (default: ``False``); if ``True``, + return a random element in the ring of integers of this ring + + - ``prec`` -- an integer or ``None`` (default: ``None``); + if given, bound the precision of the output to ``prec`` + + EXAMPLES:: + + sage: R = ZpER(5, prec=10) + + By default, this method returns a unbounded element:: + + sage: a = R.random_element() + sage: a # random + 4 + 3*5 + 3*5^2 + 5^3 + 3*5^4 + 2*5^5 + 2*5^6 + 5^7 + 5^9 + ... + sage: a.precision_absolute() + +Infinity + + The precision can be bounded by passing in a precision:: + + sage: b = R.random_element(prec=15) + sage: b # random + 2 + 3*5^2 + 5^3 + 3*5^4 + 5^5 + 3*5^6 + 3*5^8 + 3*5^9 + 4*5^10 + 5^11 + 4*5^12 + 5^13 + 2*5^14 + O(5^15) + sage: b.precision_absolute() + 15 + """ + if integral or (not self.is_field()): + return self._get_element_class('random')(self, 0, prec) + else: + return self._get_element_class('random')(self, None, prec) + + def teichmuller(self, x): + r""" + Return the Teichmuller representative of `x`. + + EXAMPLES:: + + sage: R = ZpER(5, print_mode="digits") + sage: R.teichmuller(2) + ...40423140223032431212 + """ + x = self(x) + if x.valuation() < 0: + raise ValueError("negative valuation") + return self._get_element_class('teichmuller')(self, self.exact_ring()(x.residue())) + + def teichmuller_system(self): + r""" + Return a set of teichmuller representatives for the invertible elements + of `\ZZ / p\ZZ`. + + EXAMPLES:: + + sage: R = ZpER(7, print_mode="digits") + sage: R.teichmuller_system() + [...00000000000000000001, + ...16412125443426203642, + ...16412125443426203643, + ...50254541223240463024, + ...50254541223240463025, + ...66666666666666666666] + """ + R = self.residue_class_field() + return [ self.teichmuller(ZZ(i)) for i in R if i != 0 ] + + def is_pAdicRing(R): """ - Returns ``True`` if and only if ``R`` is a `p`-adic ring (not a + Return ``True`` if and only if ``R`` is a `p`-adic ring (not a field). EXAMPLES:: @@ -716,7 +1195,7 @@ def is_pAdicRing(R): class pAdicRingGeneric(pAdicGeneric, EuclideanDomain): def is_field(self, proof = True): """ - Returns whether this ring is actually a field, ie ``False``. + Return whether this ring is actually a field, ie ``False``. EXAMPLES:: @@ -728,7 +1207,7 @@ def is_field(self, proof = True): def krull_dimension(self): r""" - Returns the Krull dimension of self, i.e. 1 + Return the Krull dimension of self, i.e. 1 INPUT: @@ -842,7 +1321,7 @@ def _gcd_univariate_polynomial(self, f, g): def is_pAdicField(R): """ - Returns ``True`` if and only if ``R`` is a `p`-adic field. + Return ``True`` if and only if ``R`` is a `p`-adic field. EXAMPLES:: @@ -903,7 +1382,7 @@ class pAdicFloatingPointFieldGeneric(pAdicFieldGeneric, FloatingPointFieldGeneri class pAdicRingBaseGeneric(pAdicBaseGeneric, pAdicRingGeneric): def construction(self, forbid_frac_field=False): """ - Returns the functorial construction of self, namely, + Return the functorial construction of self, namely, completion of the rational numbers with respect a given prime. Also preserves other information that makes this field unique @@ -937,11 +1416,15 @@ def construction(self, forbid_frac_field=False): extras = {'print_mode':self._printer.dict(), 'type':self._prec_type(), 'names':self._names} if hasattr(self, '_label'): extras['label'] = self._label - return (CompletionFunctor(self.prime(), self._precision_cap(), extras), ZZ) + if self._prec_type() == "relaxed": + prec = (self._default_prec, self._halting_prec) + else: + prec = self._precision_cap() + return (CompletionFunctor(self.prime(), prec, extras), ZZ) def random_element(self, algorithm='default'): r""" - Returns a random element of self, optionally using the + Return a random element of self, optionally using the algorithm argument to decide how it generates the element. Algorithms currently implemented: @@ -984,7 +1467,7 @@ def random_element(self, algorithm='default'): class pAdicFieldBaseGeneric(pAdicBaseGeneric, pAdicFieldGeneric): def composite(self, subfield1, subfield2): r""" - Returns the composite of two subfields of self, i.e., the + Return the composite of two subfields of self, i.e., the largest subfield containing both INPUT: @@ -1009,7 +1492,7 @@ def composite(self, subfield1, subfield2): def subfields_of_degree(self, n): r""" - Returns the number of subfields of self of degree `n` + Return the number of subfields of self of degree `n` INPUT: @@ -1033,7 +1516,7 @@ def subfields_of_degree(self, n): def subfield(self, list): r""" - Returns the subfield generated by the elements in list + Return the subfield generated by the elements in list INPUT: @@ -1056,7 +1539,7 @@ def subfield(self, list): def construction(self, forbid_frac_field=False): """ - Returns the functorial construction of ``self``, namely, + Return the functorial construction of ``self``, namely, completion of the rational numbers with respect a given prime. Also preserves other information that makes this field unique @@ -1106,6 +1589,10 @@ def construction(self, forbid_frac_field=False): extras = {'print_mode':self._printer.dict(), 'type':self._prec_type(), 'names':self._names} if hasattr(self, '_label'): extras['label'] = self._label - return (CompletionFunctor(self.prime(), self._precision_cap(), extras), QQ) + if self._prec_type() == "relaxed": + prec = (self._default_prec, self._halting_prec) + else: + prec = self._precision_cap() + return (CompletionFunctor(self.prime(), prec, extras), QQ) else: return FractionField(), self.integer_ring() diff --git a/src/sage/rings/padics/local_generic.py b/src/sage/rings/padics/local_generic.py index 03681a10338..63960d81fd6 100644 --- a/src/sage/rings/padics/local_generic.py +++ b/src/sage/rings/padics/local_generic.py @@ -76,7 +76,7 @@ def __init__(self, base, prec, names, element_class, category=None): def is_capped_relative(self): """ - Returns whether this `p`-adic ring bounds precision in a capped + Return whether this `p`-adic ring bounds precision in a capped relative fashion. The relative precision of an element is the power of `p` @@ -101,7 +101,7 @@ def is_capped_relative(self): def is_capped_absolute(self): """ - Returns whether this `p`-adic ring bounds precision in a + Return whether this `p`-adic ring bounds precision in a capped absolute fashion. The absolute precision of an element is the power of `p` @@ -126,7 +126,7 @@ def is_capped_absolute(self): def is_fixed_mod(self): """ - Returns whether this `p`-adic ring bounds precision in a fixed + Return whether this `p`-adic ring bounds precision in a fixed modulus fashion. The absolute precision of an element is the power of `p` @@ -153,7 +153,7 @@ def is_fixed_mod(self): def is_floating_point(self): """ - Returns whether this `p`-adic ring bounds precision in a floating + Return whether this `p`-adic ring bounds precision in a floating point fashion. The relative precision of an element is the power of `p` @@ -178,7 +178,7 @@ def is_floating_point(self): def is_lattice_prec(self): """ - Returns whether this `p`-adic ring bounds precision using + Return whether this `p`-adic ring bounds precision using a lattice model. In lattice precision, relationships between elements @@ -205,18 +205,18 @@ def is_lattice_prec(self): """ return False - def is_lazy(self): + def is_relaxed(self): """ - Returns whether this `p`-adic ring bounds precision in a lazy + Return whether this `p`-adic ring bounds precision in a relaxed fashion. - In a lazy ring, elements have mechanisms for computing + In a relaxed ring, elements have mechanisms for computing themselves to greater precision. EXAMPLES:: sage: R = Zp(5) - sage: R.is_lazy() + sage: R.is_relaxed() False """ return False @@ -528,7 +528,7 @@ def get_unramified_modulus(q, res_name): def precision_cap(self): r""" - Returns the precision cap for this ring. + Return the precision cap for this ring. EXAMPLES:: @@ -556,7 +556,7 @@ def precision_cap(self): def _precision_cap(self): r""" - Returns the precision cap for this ring, in the format + Return the precision cap for this ring, in the format used by the factory methods to create the ring. For most `p`-adic types, this is the same as :meth:`precision_cap`, @@ -571,18 +571,10 @@ def _precision_cap(self): def is_exact(self): r""" - Returns whether this p-adic ring is exact, i.e. False. - - INPUT: - self -- a p-adic ring - - OUTPUT: - boolean -- whether self is exact, i.e. False. + Return whether this `p`-adic ring is exact, i.e. ``False``. EXAMPLES:: - #sage: R = Zp(5, 3, 'lazy'); R.is_exact() - #False sage: R = Zp(5, 3, 'fixed-mod'); R.is_exact() False """ @@ -590,7 +582,7 @@ def is_exact(self): def residue_characteristic(self): r""" - Returns the characteristic of ``self``'s residue field. + Return the characteristic of ``self``'s residue field. INPUT: @@ -609,7 +601,7 @@ def residue_characteristic(self): def defining_polynomial(self, var='x', exact=False): r""" - Returns the defining polynomial of this local ring + Return the defining polynomial of this local ring INPUT: @@ -644,7 +636,7 @@ def defining_polynomial(self, var='x', exact=False): def ground_ring(self): r""" - Returns ``self``. + Return ``self``. Will be overridden by extensions. @@ -669,7 +661,7 @@ def ground_ring(self): def ground_ring_of_tower(self): r""" - Returns ``self``. + Return ``self``. Will be overridden by extensions. @@ -963,7 +955,7 @@ def inertia_degree(self): def inertia_subring(self): r""" - Returns the inertia subring, i.e. ``self``. + Return the inertia subring, i.e. ``self``. INPUT: @@ -983,7 +975,7 @@ def inertia_subring(self): def maximal_unramified_subextension(self): r""" - Returns the maximal unramified subextension. + Return the maximal unramified subextension. INPUT: @@ -1003,13 +995,13 @@ def maximal_unramified_subextension(self): # def get_extension(self): # r""" -# Returns the trivial extension of self. +# Return the trivial extension of self. # """ # raise NotImplementedError def uniformiser(self): """ - Returns a uniformiser for ``self``, ie a generator for the unique maximal ideal. + Return a uniformiser for ``self``, ie a generator for the unique maximal ideal. EXAMPLES:: @@ -1026,7 +1018,7 @@ def uniformiser(self): def uniformiser_pow(self, n): """ - Returns the `n`th power of the uniformiser of ``self`` (as an element of ``self``). + Return the `n`th power of the uniformiser of ``self`` (as an element of ``self``). EXAMPLES:: @@ -1076,8 +1068,6 @@ def _test_add_bigoh(self, **options): tester.assertLessEqual(y.precision_absolute(), 0) elif self.is_fixed_mod() or self.is_floating_point(): tester.assertGreaterEqual((x-y).valuation(), 0) - else: - raise NotImplementedError # if absprec < 0, then the result is in the fraction field (see #13591) y = x.add_bigoh(-1) @@ -1086,7 +1076,7 @@ def _test_add_bigoh(self, **options): tester.assertLessEqual(y.precision_absolute(), -1) # make sure that we handle very large values correctly - if self._prec_type() != 'lattice-float': # in the lattice-float model, there is no cap + if self._prec_type() not in [ 'lattice-float', 'relaxed' ]: # no cap in these models absprec = Integer(2)**1000 tester.assertEqual(x.add_bigoh(absprec), x) diff --git a/src/sage/rings/padics/local_generic_element.pyx b/src/sage/rings/padics/local_generic_element.pyx index 71dfbc923c1..73e4125c09b 100644 --- a/src/sage/rings/padics/local_generic_element.pyx +++ b/src/sage/rings/padics/local_generic_element.pyx @@ -849,7 +849,7 @@ cdef class LocalGenericElement(CommutativeRingElement): r""" Returns the valuation of this local ring element. - This function only differs from valuation for lazy elements. + This function only differs from valuation for relaxed elements. INPUT: @@ -997,7 +997,7 @@ cdef class LocalGenericElement(CommutativeRingElement): if self.valuation() is not infinity: shift = shift << v - if self.parent().is_lattice_prec(): + if self.parent().is_lattice_prec() or self.parent().is_relaxed(): modes = ['simple'] else: modes = ['simple', 'smallest', 'teichmuller'] diff --git a/src/sage/rings/padics/misc.py b/src/sage/rings/padics/misc.py index e92da6b1ec4..b7a7c8e3839 100644 --- a/src/sage/rings/padics/misc.py +++ b/src/sage/rings/padics/misc.py @@ -206,7 +206,8 @@ def precprint(prec_type, prec_cap, p): 'floating-point':'with floating precision %s'%prec_cap, 'fixed-mod':'of fixed modulus %s^%s'%(p, prec_cap), 'lattice-cap':'with lattice-cap precision', - 'lattice-float':'with lattice-float precision'} + 'lattice-float':'with lattice-float precision', + 'relaxed':'handled with relaxed arithmetics'} return precD[prec_type] def trim_zeros(L): diff --git a/src/sage/rings/padics/padic_base_generic.py b/src/sage/rings/padics/padic_base_generic.py index 1c3fd85dad4..8672e8ee17e 100644 --- a/src/sage/rings/padics/padic_base_generic.py +++ b/src/sage/rings/padics/padic_base_generic.py @@ -40,7 +40,11 @@ def __init__(self, p, prec, print_mode, names, element_class): sage: R = Zp(5) #indirect doctest """ - self.prime_pow = PowComputer(p, max(min(prec - 1, 30), 1), prec, self.is_field(), self._prec_type()) + if self.is_relaxed(): + from sage.rings.padics.pow_computer_flint import PowComputer_flint + self.prime_pow = PowComputer_flint(p, 1, 1, 1, self.is_field()) + else: + self.prime_pow = PowComputer(p, max(min(prec - 1, 30), 1), prec, self.is_field(), self._prec_type()) pAdicGeneric.__init__(self, self, p, prec, print_mode, names, element_class) if self.is_field(): if self.is_capped_relative(): @@ -52,6 +56,9 @@ def __init__(self, p, prec, print_mode, names, element_class): elif self.is_lattice_prec(): coerce_list = [QQ] convert_list = [] + elif self.is_relaxed(): + coerce_list = [QQ] + convert_list = [] else: raise RuntimeError elif self.is_capped_relative(): @@ -69,6 +76,9 @@ def __init__(self, p, prec, print_mode, names, element_class): elif self.is_lattice_prec(): coerce_list = [ZZ] convert_list = [QQ] + elif self.is_relaxed(): + coerce_list = [ZZ] + convert_list = [QQ] else: raise RuntimeError self.Element = element_class diff --git a/src/sage/rings/padics/padic_base_leaves.py b/src/sage/rings/padics/padic_base_leaves.py index a8eba1f3bf2..cbf71588bfd 100644 --- a/src/sage/rings/padics/padic_base_leaves.py +++ b/src/sage/rings/padics/padic_base_leaves.py @@ -197,7 +197,8 @@ class names.:: pAdicCappedAbsoluteRingGeneric, \ pAdicFloatingPointRingGeneric, \ pAdicFloatingPointFieldGeneric, \ - pAdicLatticeGeneric + pAdicLatticeGeneric, \ + pAdicRelaxedGeneric from .padic_capped_relative_element import pAdicCappedRelativeElement from .padic_capped_absolute_element import pAdicCappedAbsoluteElement from .padic_fixed_mod_element import pAdicFixedModElement @@ -274,7 +275,7 @@ def _coerce_map_from_(self, R): sage: K.has_coerce_map_from(ZpCA(17,40)) False """ - #if isinstance(R, pAdicRingLazy) and R.prime() == self.prime(): + #if isinstance(R, pAdicRingRelaxed) and R.prime() == self.prime(): # return True if isinstance(R, pAdicRingCappedRelative) and R.prime() == self.prime(): if R.precision_cap() < self.precision_cap(): @@ -370,7 +371,7 @@ def _coerce_map_from_(self, R): sage: K.has_coerce_map_from(Zp(17,40)) True """ - #if isinstance(R, pAdicRingLazy) and R.prime() == self.prime(): + #if isinstance(R, pAdicRingRelaxed) and R.prime() == self.prime(): # return True if isinstance(R, pAdicRingCappedRelative) and R.prime() == self.prime(): return True @@ -571,7 +572,7 @@ def _coerce_map_from_(self, R): sage: K.has_coerce_map_from(Zp(17,40)) False """ - #if isinstance(R, pAdicRingLazy) and R.prime() == self.prime(): + #if isinstance(R, pAdicRingRelaxed) and R.prime() == self.prime(): # return True if isinstance(R, pAdicRingFixedMod) and R.prime() == self.prime(): if R.precision_cap() > self.precision_cap(): @@ -677,7 +678,7 @@ def _coerce_map_from_(self, R): True """ - #if isinstance(R, pAdicRingLazy) or isinstance(R, pAdicFieldLazy) and R.prime() == self.prime(): + #if isinstance(R, pAdicRingRelaxed) or isinstance(R, pAdicFieldRelaxed) and R.prime() == self.prime(): # return True if isinstance(R, (pAdicRingCappedRelative, pAdicRingCappedAbsolute)) and R.prime() == self.prime(): return True @@ -1095,3 +1096,76 @@ def random_element(self, prec=None, integral=False): if relcap < prec: prec = relcap return self._element_class(self, x*(p**val), prec=prec) + +# Relaxed +######### + +class pAdicRingRelaxed(pAdicRelaxedGeneric, pAdicRingBaseGeneric): + """ + An implementation of relaxed arithmetics over `\ZZ_p`. + + INPUT: + + - ``p`` -- prime + + - ``prec`` -- default precision + + - ``print_mode`` -- dictionary with print options + + - ``names`` -- how to print the prime + + EXAMPLES:: + + sage: R = ZpER(5) # indirect doctest + sage: type(R) + + """ + def __init__(self, p, prec, print_mode, names): + """ + Initialization. + + TESTS:: + + sage: R = ZpER(7) + sage: TestSuite(R).run(skip=['_test_log', '_test_matrix_smith']) + """ + from sage.rings.padics import padic_relaxed_element + self._default_prec, self._halting_prec, self._secure = prec + pAdicRingBaseGeneric.__init__(self, p, self._default_prec, print_mode, names, padic_relaxed_element.pAdicRelaxedElement) + self._element_class_module = padic_relaxed_element + self._element_class_prefix = "pAdicRelaxedElement_" + +class pAdicFieldRelaxed(pAdicRelaxedGeneric, pAdicFieldBaseGeneric): + """ + An implementation of relaxed arithmetics over `\QQ_p`. + + INPUT: + + - ``p`` -- prime + + - ``prec`` -- default precision + + - ``print_mode`` -- dictionary with print options + + - ``names`` -- how to print the prime + + EXAMPLES:: + + sage: R = QpER(5) # indirect doctest + sage: type(R) + + """ + def __init__(self, p, prec, print_mode, names): + """ + Initialization. + + TESTS:: + + sage: K = QpER(7) + sage: TestSuite(K).run(skip=['_test_log', '_test_matrix_smith']) + """ + from sage.rings.padics import padic_relaxed_element + self._default_prec, self._halting_prec, self._secure = prec + pAdicFieldBaseGeneric.__init__(self, p, self._default_prec, print_mode, names, padic_relaxed_element.pAdicRelaxedElement) + self._element_class_module = padic_relaxed_element + self._element_class_prefix = "pAdicRelaxedElement_" diff --git a/src/sage/rings/padics/padic_extension_leaves.py b/src/sage/rings/padics/padic_extension_leaves.py index cd7018b982e..0adba13ba38 100644 --- a/src/sage/rings/padics/padic_extension_leaves.py +++ b/src/sage/rings/padics/padic_extension_leaves.py @@ -39,13 +39,13 @@ #from unramified_extension_absolute_element import UnramifiedExtensionAbsoluteElement #from unramified_extension_capped_relative_element import UnramifiedExtensionCappedRelativeElement -#from unramified_extension_lazy_element import UnramifiedExtensionLazyElement +#from unramified_extension_lazy_element import UnramifiedExtensionRelaxedElement #from eisenstein_extension_absolute_element import EisensteinExtensionAbsoluteElement #from eisenstein_extension_capped_relative_element import EisensteinExtensionCappedRelativeElement -#from eisenstein_extension_lazy_element import EisensteinExtensionLazyElement +#from eisenstein_extension_lazy_element import EisensteinExtensionRelaxedElement #from padic_general_extension_absolute_element import pAdicGeneralExtensionAbsoluteElement #from padic_general_extension_capped_relative_element import pAdicGeneralExtensionCappedRelativeElement -#from padic_general_extension_lazy_element import pAdicGeneralExtensionLazyElement +#from padic_general_extension_lazy_element import pAdicGeneralExtensionRelaxedElement from .padic_ZZ_pX_FM_element import pAdicZZpXFMElement from .padic_ZZ_pX_CR_element import pAdicZZpXCRElement diff --git a/src/sage/rings/padics/padic_generic.py b/src/sage/rings/padics/padic_generic.py index 650b5137bf5..ae15159be84 100644 --- a/src/sage/rings/padics/padic_generic.py +++ b/src/sage/rings/padics/padic_generic.py @@ -964,7 +964,10 @@ def _test_shift(self, **options): :class:`TestSuite` """ tester = self._tester(**options) - cap = self.precision_cap() + if self.is_relaxed(): + cap = self.default_prec() + else: + cap = self.precision_cap() k = self.residue_field() for v in range(min(cap,10)): if self.is_capped_absolute() or self.is_fixed_mod(): @@ -1068,7 +1071,10 @@ def _test_teichmuller(self, **options): tester.assertEqual(x.residue(), y.residue()) except (NotImplementedError, AttributeError): pass - tester.assertEqual(y**self.residue_field().order(), y) + if self.is_relaxed(): + tester.assertTrue(y.is_equal_at_precision(y**self.residue_field().order(), self.default_prec())) + else: + tester.assertEqual(y**self.residue_field().order(), y) def _test_convert_residue_field(self, **options): r""" diff --git a/src/sage/rings/padics/padic_generic_element.pyx b/src/sage/rings/padics/padic_generic_element.pyx index deede842333..b1988778707 100644 --- a/src/sage/rings/padics/padic_generic_element.pyx +++ b/src/sage/rings/padics/padic_generic_element.pyx @@ -3522,10 +3522,27 @@ cdef class pAdicGenericElement(LocalGenericElement): except ValueError: pass if ans is not None: - if list(ans.expansion()) > list((-ans).expansion()): - ans = -ans + ans2 = -ans + E1 = ans.expansion() + E2 = ans2.expansion() + if ans.parent().is_field(): + i = 0 + else: + i = ans.valuation() + while True: + try: + d1 = E1[i] + d2 = E2[i] + except (PrecisionError, IndexError): + break + if d1 > d2: + ans, ans2 = ans2, ans + break + if d1 < d2: + break + i += 1 if all: - return [ans, -ans] + return [ans, ans2] else: return ans if extend: diff --git a/src/sage/rings/padics/padic_relaxed_element.pxd b/src/sage/rings/padics/padic_relaxed_element.pxd new file mode 100644 index 00000000000..991af951643 --- /dev/null +++ b/src/sage/rings/padics/padic_relaxed_element.pxd @@ -0,0 +1,55 @@ +from sage.libs.flint.types cimport fmpz, fmpz_t, fmpz_poly_t, flint_rand_t + +ctypedef fmpz_t cdigit +ctypedef fmpz* cdigit_ptr +ctypedef fmpz_poly_t celement +ctypedef flint_rand_t randgen + + +include "relaxed_template_header.pxi" + + +cdef class pAdicRelaxedElement(RelaxedElement): + pass + +cdef class pAdicRelaxedElement_zero(RelaxedElement_zero): + pass + +cdef class pAdicRelaxedElement_one(RelaxedElement_one): + pass + +cdef class pAdicRelaxedElement_bound(RelaxedElement_bound): + pass + +cdef class pAdicRelaxedElement_value(RelaxedElement_value): + pass + +cdef class pAdicRelaxedElement_random(RelaxedElement_random): + pass + +cdef class pAdicRelaxedElement_slice(RelaxedElement_slice): + pass + +cdef class pAdicRelaxedElement_add(RelaxedElement_add): + pass + +cdef class pAdicRelaxedElement_sub(RelaxedElement_sub): + pass + +cdef class pAdicRelaxedElement_mul(RelaxedElement_mul): + pass + +cdef class pAdicRelaxedElement_muldigit(RelaxedElement_muldigit): + pass + +cdef class pAdicRelaxedElement_div(RelaxedElement_div): + pass + +cdef class pAdicRelaxedElement_sqrt(RelaxedElement_sqrt): + pass + +cdef class pAdicRelaxedElement_teichmuller(RelaxedElement_teichmuller): + pass + +cdef class pAdicRelaxedElement_unknown(RelaxedElement_unknown): + pass diff --git a/src/sage/rings/padics/padic_relaxed_element.pyx b/src/sage/rings/padics/padic_relaxed_element.pyx new file mode 100644 index 00000000000..88b5003b0eb --- /dev/null +++ b/src/sage/rings/padics/padic_relaxed_element.pyx @@ -0,0 +1,17 @@ +cdef inline type element_class_zero = pAdicRelaxedElement_zero +cdef inline type element_class_one = pAdicRelaxedElement_one +cdef inline type element_class_bound = pAdicRelaxedElement_bound +cdef inline type element_class_value = pAdicRelaxedElement_value +cdef inline type element_class_random = pAdicRelaxedElement_random +cdef inline type element_class_slice = pAdicRelaxedElement_slice +cdef inline type element_class_add = pAdicRelaxedElement_add +cdef inline type element_class_sub = pAdicRelaxedElement_sub +cdef inline type element_class_mul = pAdicRelaxedElement_mul +cdef inline type element_class_muldigit = pAdicRelaxedElement_muldigit +cdef inline type element_class_div = pAdicRelaxedElement_div +cdef inline type element_class_sqrt = pAdicRelaxedElement_sqrt +cdef inline type element_class_teichmuller = pAdicRelaxedElement_teichmuller +cdef inline type element_class_unknown = pAdicRelaxedElement_unknown + +include "sage/libs/linkages/padics/relaxed/flint.pxi" +include "relaxed_template.pxi" diff --git a/src/sage/rings/padics/padic_relaxed_errors.pxd b/src/sage/rings/padics/padic_relaxed_errors.pxd new file mode 100644 index 00000000000..bfe375b8eee --- /dev/null +++ b/src/sage/rings/padics/padic_relaxed_errors.pxd @@ -0,0 +1,10 @@ +cdef inline int ERROR_ABANDON +cdef inline int ERROR_NOTDEFINED +cdef inline int ERROR_PRECISION +cdef inline int ERROR_OVERFLOW +cdef inline int ERROR_NOTSQUARE +cdef inline int ERROR_INTEGRAL +cdef inline int ERROR_DIVISION +cdef inline int ERROR_CIRCULAR + +cdef inline int ERROR_UNEXPECTED diff --git a/src/sage/rings/padics/padic_relaxed_errors.pyx b/src/sage/rings/padics/padic_relaxed_errors.pyx new file mode 100644 index 00000000000..dff08758100 --- /dev/null +++ b/src/sage/rings/padics/padic_relaxed_errors.pyx @@ -0,0 +1,71 @@ +# **************************************************************************** +# Copyright (C) 2021 Xavier Caruso +# +# 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.rings.padics.precision_error import PrecisionError + + +cdef inline int ERROR_ABANDON = 1 +cdef inline int ERROR_NOTDEFINED = 1 << 1 +cdef inline int ERROR_PRECISION = 1 << 2 +cdef inline int ERROR_OVERFLOW = 1 << 3 +cdef inline int ERROR_NOTSQUARE = 1 << 4 # maybe we should have something more generic here +cdef inline int ERROR_INTEGRAL = 1 << 5 +cdef inline int ERROR_DIVISION = 1 << 6 +cdef inline int ERROR_CIRCULAR = 1 << 7 + +cdef inline int ERROR_UNEXPECTED = 1 << 30 + + +def raise_error(error, permissive=False): + r""" + Raise an error according to the given error code. + + INPUT: + + - ``error`` -- an integer; the error code + + - ``permissive`` -- a boolean (default: ``False``); if ``True``, + do not raise weak errors (precision, abandon). + + TESTS:: + + sage: from sage.rings.padics.padic_relaxed_errors import * + sage: raise_error(0) + sage: raise_error(64) + Traceback (most recent call last): + ... + ZeroDivisionError: denominator is not invertible + + sage: raise_error(1) + Traceback (most recent call last): + ... + PrecisionError: computation has been abandonned; try to increase precision + + sage: raise_error(1, permissive=True) + + """ + if error & ERROR_UNEXPECTED: + raise RuntimeError("error code = %s" % error) + if error & ERROR_CIRCULAR: + raise RecursionError("definition looks circular") + if error & ERROR_DIVISION: + raise ZeroDivisionError("denominator is not invertible") + if error & ERROR_INTEGRAL: + raise ValueError("not in the ring of integers") + if error & ERROR_NOTSQUARE: + raise ValueError("not a square") + if not permissive: + if error & ERROR_OVERFLOW: + raise OverflowError + if error & (ERROR_PRECISION | ERROR_NOTDEFINED): + raise PrecisionError("not enough precision") + if error & ERROR_ABANDON: + raise PrecisionError("computation has been abandonned; try to increase precision") diff --git a/src/sage/rings/padics/padic_template_element.pxi b/src/sage/rings/padics/padic_template_element.pxi index ba96f3d1142..31e89a824b3 100644 --- a/src/sage/rings/padics/padic_template_element.pxi +++ b/src/sage/rings/padics/padic_template_element.pxi @@ -938,7 +938,7 @@ cdef class ExpansionIter(object): def __iter__(self): """ - Chracteristic property of an iterator: ``__iter__`` returns itself. + Characteristic property of an iterator: ``__iter__`` returns itself. TESTS:: diff --git a/src/sage/rings/padics/relaxed_template.pxi b/src/sage/rings/padics/relaxed_template.pxi new file mode 100644 index 00000000000..ba15d4f5d3c --- /dev/null +++ b/src/sage/rings/padics/relaxed_template.pxi @@ -0,0 +1,4241 @@ +r""" +Template for relaxed `p`-adic rings and fields. + +In order to use this template you need to write a linkage file and +gluing file. + +The linkage file implements a common API that is then used in the +class FMElement defined here. +See sage/libs/linkages/padics/relaxed/API.pxi for the functions needed. + +The gluing file does the following: +- ctypedef's ``cdigit``, ``cdigit_ptr``, ``celement`` and ``randgen`` + to be the appropriate types +- includes the linkage file +- includes this template +- defines classes inheriting for classes defined in this file, + and implements any desired extra methods +See padic_relaxed_element.pxd and padic_relaxed_element.pyx for an example. + +AUTHOR: + +- Xavier Caruso (2021-02): initial version +""" + +# **************************************************************************** +# Copyright (C) 2021 Xavier Caruso +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +# **************************************************************************** + +import re +from sage.misc import persist + +from libc.stdlib cimport malloc, free +from sage.libs.gmp.mpz cimport mpz_sizeinbase, mpz_tstbit + +from sage.structure.element import coerce_binop +from sage.structure.element cimport have_same_parent +from sage.structure.coerce cimport coercion_model +from sage.misc.prandom import randint + +from sage.rings.all import ZZ +from sage.rings.integer cimport Integer +from sage.rings.infinity import Infinity + +from sage.rings.padics.pow_computer cimport PowComputer_class +from sage.rings.padics.padic_generic_element cimport pAdicGenericElement +from sage.rings.padics.precision_error import PrecisionError +from sage.rings.padics.padic_relaxed_errors cimport * +from sage.rings.padics.padic_relaxed_errors import raise_error + +cdef long maxordp = (1L << (sizeof(long) * 8 - 2)) - 1 + + +cdef cdigit tmp_digit +cdef celement tmp_poly +digit_init(tmp_digit) +element_init(tmp_poly) + + +cdef class RelaxedElement(pAdicGenericElement): + r""" + Template class for relaxed `p`-adic elements. + + EXAMPLES: + + sage: from sage.rings.padics.padic_relaxed_element import RelaxedElement + sage: R = ZpER(5) + sage: a = R(1) + sage: isinstance(a, RelaxedElement) + True + """ + def __init__(self, parent): + r""" + Initialize this element. + + TESTS:: + + sage: R = ZpER(5) + sage: a = R(1/2) # indirect doctest + sage: TestSuite(a).run() + """ + pAdicGenericElement.__init__(self, parent) + self.prime_pow = self._parent.prime_pow + self._valuation = 0 + self._precrel = 0 + self._precbound = maxordp + + def __reduce__(self): + r""" + Return a tuple of a function and data that can be used to unpickle + this element. + + TESTS:: + + sage: R = ZpER(5) + sage: a = R(0) + sage: loads(dumps(a)) == a + True + """ + raise NotImplementedError("must be implemented in subclasses") + + cpdef bint _is_base_elt(self, p) except -1: + r""" + Return ``True`` if this element is an element of Zp or Qp (rather than + an extension). + + INPUT: + + - ``p`` -- a prime, which is compared with the parent of this element. + + EXAMPLES:: + + sage: a = ZpER(5)(3) + sage: a._is_base_elt(5) + True + sage: a._is_base_elt(17) + False + """ + return p == self._parent.prime() + + cdef cdigit_ptr _getdigit_relative(self, long i): + r""" + Return a pointer on the `i`-th significant digit of this number. + + .. NOTE: + + This function does not check that the requested digit + has been already computed. + + INPUT: + + - ``i`` -- a positive integer + + """ + pass + + cdef cdigit_ptr _getdigit_absolute(self, long i): + r""" + Return a pointer on the digit in position `i` of + this number. + + .. NOTE: + + This function do not check that the requested digit + has been already computed. + + INPUT: + + - ``i`` -- an integer + + """ + pass + + cdef void _getslice_relative(self, celement slice, long start, long length): + r""" + Select a slice of the digits of this number. + + INPUT: + + - ``slice`` -- a ``celement`` to store the slice + + - ``start`` -- a positive integer, the starting position of the slice + in relative precision + + - ``length`` -- a positive integer, the length of the slice + + .. NOTE:: + + This methods only sets up a pointer to the requested slice + (the slice is not copied). Hence any future modification + of the slice ``slice`` will affect this number. + """ + pass + + cdef int _next_c(self): + r""" + Compute the next digit of this number. + + OUTPUT: + + An error code which is a superposition of the following: + + - ``0`` -- no error + - ``ERROR_ABANDON = 1`` -- computation has been abandonned + - ``ERROR_NOTDEFINED = 2`` -- a number is not defined + - ``ERROR_PRECISION = 4`` -- not enough precision + - ``ERROR_OVERFLOW = 8`` -- overflow + - ``ERROR_NOTSQUARE = 16`` -- not a square + - ``ERROR_INTEGRAL = 32`` -- not in the integer ring + - ``ERROR_DIVISION = 64`` -- division by zero (or something indistinguishable from zero) + - ``ERROR_CIRCULAR = 128`` -- circular definition + """ + raise NotImplementedError("must be implemented in subclasses") + + cdef int _jump_c(self, long prec): + r""" + Compute the digits of this number until the absolute precision ``prec``. + + INPUT: + + - ``prec`` -- an integer + + OUTPUT: + + An error code (see :meth:`_next_c` for details). + """ + cdef int error + cdef long pr = min(prec, self._precbound) + while self._precrel + self._valuation < pr: + error = self._next_c() + if error: + return error + if prec > self._precbound: + return ERROR_PRECISION + return 0 + + cdef int _jump_relative_c(self, long prec, long halt): + r""" + Compute the digits of this number until the relative precision + ``prec``. + + INPUT: + + - ``prec`` -- an integer + + - ``halt`` -- an integer; the absolute precision after which the + computation is abandonned if the first significant digit has not + been found yet + + OUTPUT: + + An error code (see :meth:`_next_c` for details). + + """ + if self._valuation >= maxordp: + return 0 + cdef int error = 0 + if self._valuation <= -maxordp: + error = self._next_c() + cdef long valhalt = min(halt, self._precbound) + while not error and self._valuation < valhalt and self._precrel == 0: + error = self._next_c() + if self._valuation >= self._precbound: + error |= ERROR_PRECISION + elif self._valuation >= halt: + error |= ERROR_ABANDON + if not error: + error = self._jump_c(self._valuation + prec) + return error + + cdef int _init_jump(self) except -1: + r""" + Start the computation of the digits of this number. + This method should be called at initialization. + + OUTPUT: + + An error code (see :meth:`_next_c` for details). + """ + cdef int error = 0 + if self._precbound < maxordp: + error = self._jump_c(self._precbound) + raise_error(error, True) + return error + + def digit(self, i): + r""" + Return the coefficient of `p^i` in the `p`-adic expansion + of this number. + + INPUT: + + - ``i`` -- an integer + + EXAMPLES:: + + sage: R = ZpER(5, prec=10) + sage: a = R(20/21) + sage: a + 4*5 + 4*5^2 + 5^4 + 4*5^6 + 3*5^7 + 4*5^8 + ... + sage: a.digit(0) + 0 + sage: a.digit(1) + 4 + sage: a.digit(7) + 3 + sage: a.digit(100) + 1 + + As a shortcut, one can use the bracket operator:: + + sage: a[100] + 1 + + If the digit is not known, an error is raised:: + + sage: b = a.add_bigoh(10) + sage: b.digit(20) + Traceback (most recent call last): + ... + PrecisionError: not enough precision + + TESTS:: + + sage: a.digit(-1) + 0 + """ + cdef int error = self._jump_c(i+1) + raise_error(error) + cdef cdigit_ptr coeff = self._getdigit_absolute(i) + return digit_get_sage(coeff) + + def expansion(self, n=None, lift_mode='simple', start_val=None): + r""" + Return an iterator over the list of coefficients in a `p`-adic + expansion of this element, that is the list of `a_i` so that + this element can be expressed as + + .. MATH:: + + \pi^v \cdot \sum_{i=0}^\infty a_i \pi^i + + where `v` is the valuation of this element when the parent is + a field, and `v = 0` otherwise and the `a_i` are between `0` + and `p - 1`. + + INPUT: + + - ``n`` -- an integer or ``None`` (default ``None``); if + given, return the corresponding entries in the expansion. + + - ``lift_mode`` -- ``'simple'``, ``'smallest'`` or + ``'teichmuller'`` (default: ``'simple'``) + + - ``start_val`` -- start at this valuation rather than the + default (`0` or the valuation of this element). + + OUTPUT: + + - If ``n`` is ``None``, an iterable giving the `p`-adic expansion + of this element. + + - If ``n`` is an integer, the coefficient of `p^n` in the + `p`-adic expansion of this element. + + EXAMPLES:: + + sage: R = ZpER(7, print_mode="digits") + sage: a = R(1/2021); a + ...23615224635636163463 + + Without any argument, this method returns an iterator over the + digits of this number:: + + sage: E = a.expansion() + sage: E + 7-adic expansion of ...23615224635636163463 + sage: next(E) + 3 + sage: next(E) + 6 + sage: next(E) + 4 + + On the contrary, passing in an integer returns the digit at the + given position:: + + sage: a.expansion(5) + 1 + + Over a field, the expansion starts at the valuation of the element:: + + sage: K = R.fraction_field() + sage: b = K(20/21); b + ...2222222222222222223.2 + sage: E = b.expansion() + sage: next(E) + 2 + sage: next(E) + 3 + + sage: c = 1/b; c + ...564356435643564356440 + sage: E = c.expansion() + sage: next(E) + 4 + sage: next(E) + 4 + + When ``start_val`` is given, the expansion starts at this position + instead: + + sage: E = c.expansion(start_val=0) + sage: next(E) + 0 + sage: next(E) + 4 + + sage: E = c.expansion(start_val=5) + sage: next(E) + 3 + sage: next(E) + 4 + """ + if lift_mode == 'simple': + mode = simple_mode + elif lift_mode == 'smallest': + mode = smallest_mode + elif lift_mode == 'teichmuller': + mode = teichmuller_mode + else: + raise ValueError("unknown lift mode") + if n is None: + if start_val is not None: + start = start_val + elif self.prime_pow.in_field: + start = self.valuation() + else: + start = 0 + return ExpansionIter(self, mode, start, self._precbound) + else: + n = Integer(n) + if n >= self._precbound: + raise_error(ERROR_PRECISION) + if n >= maxordp: + raise OverflowError("beyond maximum precision (which is %s)" % maxordp) + if mode == simple_mode: + return self.digit(n) + else: + E = ExpansionIter(self, mode, n, n+1) + return next(E) + + def __getitem__(self, n): + r""" + Return the `n`-th digit of this relaxed `p`-adic number if `n` is an integer + or return a bounded relaxed `p`-adic corresponding to the given slice if `n` is a slice. + + INPUT: + + - ``n`` -- an integer or a slice + + EXAMPLES:: + + sage: R = ZpER(7, 10) + sage: a = R(1/2021); a + 3 + 6*7 + 4*7^2 + 3*7^3 + 6*7^4 + 7^5 + 6*7^6 + 3*7^7 + 6*7^8 + 5*7^9 + ... + + sage: a[3] + 3 + sage: a[3:6] + 3*7^3 + 6*7^4 + 7^5 + O(7^6) + + Unbounded slices are allowed:: + + sage: a[:3] + 3 + 6*7 + 4*7^2 + O(7^3) + sage: a[3:] + 3*7^3 + 6*7^4 + 7^5 + 6*7^6 + 3*7^7 + 6*7^8 + 5*7^9 + ... + + .. SEEALSO:: + + :meth:`digit`, :meth:`slice` + + TESTS:: + + sage: a[3:6:2] + Traceback (most recent call last): + ... + NotImplementedError: step is not allowed + """ + if isinstance(n, slice): + if n.step is not None: + raise NotImplementedError("step is not allowed") + return self.slice(n.start, n.stop, True) + else: + n = Integer(n) + if n >= maxordp: + raise OverflowError("beyond maximum precision (which is %s)" % maxordp) + return self.digit(n) + + def slice(self, start=None, stop=None, bound=False): + r""" + Return a slice of this number. + + INPUT: + + - ``start`` -- an integer or ``None`` (default: ``None``), + the first position of the slice + + - ``stop`` -- an integer or ``None`` (default: ``None``), + the first position not included in the slice + + - ``bound`` -- a boolean (default: ``False``); whether the + precision on the output should be bounded or unbounded + + EXAMPLES:: + + sage: K = QpER(7, prec=10) + sage: a = K(1/2021); a + 3 + 6*7 + 4*7^2 + 3*7^3 + 6*7^4 + 7^5 + 6*7^6 + 3*7^7 + 6*7^8 + 5*7^9 + ... + + sage: s = a.slice(3, 6) + sage: s + 3*7^3 + 6*7^4 + 7^5 + ... + + In the above example, the precision on `b` remains unbounded:: + + sage: s.precision_absolute() + +Infinity + + Passing in ``bound=True`` changes this behaviour:: + + sage: a.slice(3, 6, bound=True) + 3*7^3 + 6*7^4 + 7^5 + O(7^6) + + When ``start`` is omitted, the slice starts at the beginning of the number: + + sage: a.slice(stop=6) + 3 + 6*7 + 4*7^2 + 3*7^3 + 6*7^4 + 7^5 + ... + + sage: b = K(20/21); b + 2*7^-1 + 3 + 2*7 + 2*7^2 + 2*7^3 + 2*7^4 + 2*7^5 + 2*7^6 + 2*7^7 + 2*7^8 + ... + sage: b.slice(stop=6) + 2*7^-1 + 3 + 2*7 + 2*7^2 + 2*7^3 + 2*7^4 + 2*7^5 + ... + + As a shortcut, one can use the bracket operator. + However, in this case, the precision is bounded:: + + sage: a[3:6] + 3*7^3 + 6*7^4 + 7^5 + O(7^6) + sage: b[:6] + 2*7^-1 + 3 + 2*7 + 2*7^2 + 2*7^3 + 2*7^4 + 2*7^5 + O(7^6) + + TESTS:: + + sage: x = a[:3] + sage: x.slice(stop=3) + 3 + 6*7 + 4*7^2 + ... + sage: x.slice(stop=4) + 3 + 6*7 + 4*7^2 + O(7^3) + + Taking slices of slices work as expected:: + + sage: a1 = a.slice(5, 10); a1 + 7^5 + 6*7^6 + 3*7^7 + 6*7^8 + 5*7^9 + ... + sage: a2 = a1.slice(3, 8); a2 + 7^5 + 6*7^6 + 3*7^7 + ... + sage: a2 == a.slice(5, 8) + True + """ + if start is None: + start = -maxordp + elif start >= maxordp: + raise OverflowError("beyond maximum precision (which is %s)" % maxordp) + if stop is None: + stop = maxordp + elif stop >= maxordp: + raise OverflowError("beyond maximum precision (which is %s)" % maxordp) + cdef RelaxedElement x = element_class_slice(self._parent, self, start, stop, 0) + if bound and x._precbound > stop: + x = element_class_bound(self._parent, x, stop) + return x + + def _repr_(self): + r""" + Return a string representation of this element. + + EXAMPLES: + + For unbounded elements, the number of printed terms is given by + the default precision of the ring:: + + sage: R = ZpER(5, 10) + sage: a = R(1/2021) + sage: a # indirect doctest + 1 + 5 + 3*5^3 + 3*5^4 + 5^5 + 4*5^6 + 4*5^7 + 2*5^8 + 3*5^9 + ... + + sage: S = ZpER(5, 5) + sage: b = S(1/2021) + sage: b # indirect doctest + 1 + 5 + 3*5^3 + 3*5^4 + ... + + On the contrary, bounded elements are printed until their own + precision, regardless the default precision of the parent:: + + sage: a[:15] # indirect doctest + 1 + 5 + 3*5^3 + 3*5^4 + 5^5 + 4*5^6 + 4*5^7 + 2*5^8 + 3*5^9 + 2*5^10 + 5^11 + 5^12 + 5^13 + O(5^15) + + sage: b[:15] # indirect doctest + 1 + 5 + 3*5^3 + 3*5^4 + 5^5 + 4*5^6 + 4*5^7 + 2*5^8 + 3*5^9 + 2*5^10 + 5^11 + 5^12 + 5^13 + O(5^15) + + """ + # This code should be integrated to the p-adic printer + if self._valuation <= -maxordp: + return "O(%s^-Infinity)" % self._parent.prime() + if self._is_exact_zero(): + return "0" + if self._precbound >= maxordp: + unbounded = True + try: + if self.prime_pow.in_field: + x = self.at_precision_relative(permissive=False) + else: + x = self.at_precision_absolute(permissive=False) + except (ValueError, PrecisionError, RecursionError): + unbounded = False + x = element_class_bound(self._parent, self, self._valuation + self._precrel) + else: + unbounded = False + x = self + s = pAdicGenericElement._repr_(x) + mode = self._parent._printer.dict()['mode'] + if unbounded: + s = re.sub(r'O\(.*\)$', '...', s) + elif mode == "digits": + s = re.sub(r'^\.\.\.\??', '...?', s) + elif mode == "bars": + s = re.sub(r'^\.\.\.\|?', '...?|', s) + if s == "" or s == "...": + s = "0 + ..." + return s + + cdef bint _is_equal(self, RelaxedElement right, long prec, bint permissive) except -1: + r""" + C function for checking equality at a given precision. + + INPUT: + + - ``right`` -- the second element involved in the comparison + + - ``prec`` -- an integer, the precision at which the equality is checked + + - ``permissive`` -- a boolean; if ``True``, be silent if the precision + on one input is less than ``prec``; otherwise, raise an error + + """ + cdef int error + cdef long i + if self._valuation <= -maxordp: + error = self._next_c() + raise_error(error) + if right._valuation <= -maxordp: + error = right._next_c() + raise_error(error) + for i in range(min(self._valuation, right._valuation), prec): + error = self._jump_c(i+1) + raise_error(error, permissive) + if error: # not enough precision + return True + error = right._jump_c(i+1) + raise_error(error, permissive) + if error: # not enough precision + return True + if not digit_equal(self._getdigit_absolute(i), right._getdigit_absolute(i)): + return False + return True + + @coerce_binop + def is_equal_at_precision(self, right, prec): + r""" + Compare this element with ``right`` at precision ``prec``. + + INPUT: + + - ``right`` -- a relaxed `p`-adic number + + - ``prec`` -- an integer + + EXAMPLES:: + + sage: R = ZpER(7, prec=10) + sage: a = R(1/2); a + 4 + 3*7 + 3*7^2 + 3*7^3 + 3*7^4 + 3*7^5 + 3*7^6 + 3*7^7 + 3*7^8 + 3*7^9 + ... + sage: b = R(99/2); b + 4 + 3*7 + 4*7^2 + 3*7^3 + 3*7^4 + 3*7^5 + 3*7^6 + 3*7^7 + 3*7^8 + 3*7^9 + ... + + sage: a.is_equal_at_precision(b, 1) + True + sage: a.is_equal_at_precision(b, 2) + True + sage: a.is_equal_at_precision(b, 3) + False + """ + return self._is_equal(right, min(prec, maxordp), False) + + @coerce_binop + def is_equal_to(self, RelaxedElement right, prec=None, secure=None): + r""" + Compare this element with ``right``. + + INPUT: + + - ``right`` -- a relaxed `p`-adic number + + - ``prec`` -- an integer or ``None`` (default: ``None``); if + given, compare the two elements at this precision; otherwise + use the default halting precision of the parent + + - ``secure`` -- a boolean (default: ``False`` if ``prec`` is given, + ``True`` otherwise); when the elements cannot be distingiushed + at the given precision, raise an error if ``secure`` is ``True``, + return ``True`` otherwise. + + EXAMPLES:: + + sage: R = ZpER(7) + sage: a = R(1/2) + sage: b = R(1/3) + sage: c = R(1/6) + + sage: a.is_equal_to(b) + False + + When equality indeed holds, it is not possible to conclude by + comparing more and more accurate approximations. + In this case, an error is raised:: + + sage: a.is_equal_to(b + c) + Traceback (most recent call last): + ... + PrecisionError: unable to decide equality; try to bound precision + + You can get around this behaviour by passing ``secure=False``:: + + sage: a.is_equal_to(b + c, secure=False) + True + + Another option (which is actually recommended) is to provide an explicit + bound on the precision:: + + sage: s = b + c + 7^50 + sage: a.is_equal_to(s, prec=20) + True + sage: a.is_equal_to(s, prec=100) + False + """ + cdef long halt + if self is right: + return True + if self._valuation >= maxordp and right._valuation >= maxordp: + return True + if prec is None: + if secure is None: + secure = True + prec = min(self._precbound, right._precbound) + else: + if secure is None: + secure = False + prec = Integer(prec) + if prec < maxordp: + return self._is_equal(right, prec, True) + prec = min(self._valuation + self._precrel, right._valuation + right._precrel) + halt = min(self._parent.halting_prec(), maxordp) + eq = self._is_equal(right, max(prec, halt), True) + if secure and eq: + raise PrecisionError("unable to decide equality; try to bound precision") + return eq + + def __eq__(self, other): + r""" + Return ``True`` of this element is equal to ``other``. + + TESTS:: + + sage: R = ZpER(5) + sage: x = R(1/2) + sage: y = R(1/3) + sage: z = R(1/6) + + sage: x == y + z + True + + We illustrate the effect of the keyword ``secure``:: + + sage: R.is_secure() + False + sage: s = y + z + 5^50 + sage: x == s + True + + sage: S = ZpER(5, secure=True) + sage: S(x) == S(s) + Traceback (most recent call last): + ... + PrecisionError: unable to decide equality; try to bound precision + + Note that, when ``secure=False``, once more digits have been + computed, the answer can change:: + + sage: x[:100] == s + False + sage: x == s + False + + .. SEEALSO:: + + :meth:`is_equal_to` + """ + if not have_same_parent(self, other): + try: + a, b = coercion_model.canonical_coercion(self, other) + except TypeError: + return False + return a == b + return self.is_equal_to(other, secure=self._parent.is_secure()) + + def __nonzero__(self): + r""" + Return ``True`` if this element is indistinguishable from zero. + + TESTS:: + + sage: R = ZpER(5) + sage: x = R(1) + sage: bool(x) + True + + In the next example, only `40` digits (which is the default halting + precision) are computed so `x` is considered as indistinguishable from + `0`:: + + sage: x = R(5^41) + sage: bool(x) + False + """ + cdef int error = 0 + cdef long prec, halt + if self._precrel: + return True + prec = self._precbound + if prec >= maxordp: + halt = min(self._parent.halting_prec(), maxordp) + prec = max(self._valuation + self._precrel, halt) + while not error and self._precrel == 0 and self._valuation < prec: + error = self._next_c() + return bool(self._precrel) + + cpdef bint _is_exact_zero(self) except -1: + r""" + Return ``True`` if this element is an exact zero. + + EXAMPLES:: + + sage: R = ZpER(5) + sage: a = R(0); a + 0 + sage: a._is_exact_zero() + True + + sage: b = a.add_bigoh(20) + sage: b + O(5^20) + sage: b._is_exact_zero() + False + + """ + return self._valuation >= maxordp + + cpdef bint _is_inexact_zero(self) except -1: + r""" + Return ``True`` if, at the current stage of computations, this + number cannot be distinguished from zero. + + EXAMPLES:: + + sage: R = ZpER(5, print_mode="digits") + sage: a = R(20/21) + + Computations have not started yet; hence we are not able + to distinguish `a` from zero so far:: + + sage: a._is_inexact_zero() + True + + When we print `a`, the first digits are computed, after what + we know that `a` is not zero:: + + sage: a + ...34010434010434010440 + sage: a._is_inexact_zero() + False + """ + return self._precrel == 0 + + def is_exact(self): + r""" + Return ``True`` if this element is exact, that is if its + precision is unbounded. + + EXAMPLES:: + + sage: R = ZpER(5, prec=10) + sage: a = R(20/21) + sage: a + 4*5 + 4*5^2 + 5^4 + 4*5^6 + 3*5^7 + 4*5^8 + ... + sage: a.is_exact() + True + + sage: b = a.add_bigoh(10) + sage: b + 4*5 + 4*5^2 + 5^4 + 4*5^6 + 3*5^7 + 4*5^8 + O(5^10) + sage: b.is_exact() + False + """ + return self._precbound >= maxordp + + def precision_absolute(self): + r""" + Return the absolute precision of this element. + + This is the power of `p` modulo which this element is known. + For unbounded elements, this methods return `+\infty`. + + EXAMPLES:: + + sage: R = ZpER(5, prec=10) + sage: a = R(20/21) + sage: a + 4*5 + 4*5^2 + 5^4 + 4*5^6 + 3*5^7 + 4*5^8 + ... + sage: a.precision_absolute() + +Infinity + + sage: b = a.add_bigoh(10) + sage: b + 4*5 + 4*5^2 + 5^4 + 4*5^6 + 3*5^7 + 4*5^8 + O(5^10) + sage: b.precision_absolute() + 10 + + TESTS:: + + sage: s = R.unknown() + sage: (1/s).precision_absolute() + Traceback (most recent call last): + ... + PrecisionError: no lower bound on the valuation is known + """ + if self._is_exact_zero(): + return Infinity + if self._valuation <= -maxordp: + raise PrecisionError("no lower bound on the valuation is known") + if self._precbound >= maxordp: + return Infinity + return Integer(self._precrel + self._valuation) + + def precision_relative(self): + """ + Return the relative precision of this element. + + This is the power of `p` modulo which the unit part of this + element is known. + For unbounded nonzero elements, this methods return `+\infty`. + + EXAMPLES:: + + sage: R = ZpER(5, prec=10) + sage: a = R(20/21) + sage: a + 4*5 + 4*5^2 + 5^4 + 4*5^6 + 3*5^7 + 4*5^8 + ... + sage: a.precision_relative() + +Infinity + + sage: b = a.add_bigoh(10) + sage: b + 4*5 + 4*5^2 + 5^4 + 4*5^6 + 3*5^7 + 4*5^8 + O(5^10) + sage: b.precision_relative() + 9 + + The relative precision of (exact and inexact) `0` is `0`:: + + sage: x = R(0); x + 0 + sage: x.precision_relative() + 0 + + sage: y = R(0, 10); y + O(5^10) + sage: y.precision_relative() + 0 + + TESTS:: + + sage: s = R.unknown() + sage: (1/s).precision_relative() + Traceback (most recent call last): + ... + PrecisionError: no lower bound on the valuation is known + """ + if self._valuation <= -maxordp: + raise PrecisionError("no lower bound on the valuation is known") + if self._precbound >= maxordp and self._valuation < maxordp: + return Infinity + return Integer(self._precrel) + + def precision_current(self): + r""" + Return the internal absolute precision at which this relaxed `p`-adic + number is known at the current stage of the computation. + + EXAMPLES:: + + sage: R = ZpER(5, prec=10) + sage: x = R(20/21) + sage: y = R(21/22) + sage: z = x + y + + When the elements are just defined, the computation has not started:: + + sage: x.precision_current() + 0 + sage: y.precision_current() + 0 + sage: z.precision_current() + 0 + + When elements are printed, the relevant digits are computed:: + + sage: x + 4*5 + 4*5^2 + 5^4 + 4*5^6 + 3*5^7 + 4*5^8 + ... + sage: x.precision_current() + 10 + + If we ask for more digits of `z`, the current precision of `z` + increases accordingly:: + + sage: z[:15] + 3 + 2*5 + 2*5^3 + 5^4 + 2*5^5 + 2*5^6 + 4*5^7 + 5^9 + 3*5^10 + 3*5^11 + 4*5^12 + 4*5^13 + 4*5^14 + O(5^15) + sage: z.precision_current() + 15 + + and similarly the current precision of `x` and `y` increases because + those digits are needed to carry out the computation:: + + sage: x.precision_current() + 15 + sage: y.precision_current() + 15 + """ + if self._valuation <= -maxordp: + return -Infinity + if self._valuation >= maxordp: + return Infinity + return Integer(self._valuation + self._precrel) + + def at_precision_absolute(self, prec=None, permissive=None): + r""" + Return this element bounded at the given precision. + + INPUT: + + - ``prec`` -- an integer or ``None`` (default: ``None``); + if ``None``, use the default precision of the parent + + - ``permissive`` -- a boolean (default: ``False`` if ``prec`` + is given, ``True`` otherwise); if ``False``, raise an error + if the precision of this element is not sufficient + + EXAMPLES:: + + sage: R = ZpER(7, prec=10) + sage: a = R(1/2021); a + 3 + 6*7 + 4*7^2 + 3*7^3 + 6*7^4 + 7^5 + 6*7^6 + 3*7^7 + 6*7^8 + 5*7^9 + ... + sage: a.at_precision_absolute(5) + 3 + 6*7 + 4*7^2 + 3*7^3 + 6*7^4 + O(7^5) + sage: a.at_precision_absolute(10) + 3 + 6*7 + 4*7^2 + 3*7^3 + 6*7^4 + 7^5 + 6*7^6 + 3*7^7 + 6*7^8 + 5*7^9 + O(7^10) + + sage: b = a.add_bigoh(5) + sage: b.at_precision_absolute(10) + Traceback (most recent call last): + ... + PrecisionError: not enough precision + + sage: b.at_precision_absolute(10, permissive=True) + 3 + 6*7 + 4*7^2 + 3*7^3 + 6*7^4 + O(7^5) + + Bounding at a negative precision is not permitted over `\ZZ_p`:: + + sage: a.at_precision_absolute(-1) + Traceback (most recent call last): + ... + ValueError: precision must be nonnegative + + but, of course, it is over `\QQ_p`:: + + sage: K = R.fraction_field() + sage: K(a).at_precision_absolute(-1) + O(7^-1) + + .. SEEALSO:: + + :meth:`at_precision_relative`, :meth:`add_bigoh` + """ + if prec is Infinity: + if not permissive and self._precbound < maxordp: + raise_error(ERROR_PRECISION) + return self + if prec is None: + if permissive is None: + permissive = True + prec = self._parent.default_prec() + else: + prec = Integer(prec) + if not self.prime_pow.in_field and prec < 0: + raise ValueError("precision must be nonnegative") + if prec > maxordp: + raise OverflowError("beyond maximal precision (which is %s)" % maxordp) + error = self._jump_c(prec) + if permissive is None: + permissive = False + raise_error(error, permissive) + return element_class_bound((self)._parent, self, prec) + + def add_bigoh(self, absprec): + r""" + Return a new element with absolute precision decreased to ``absprec``. + + INPUT: + + - ``absprec`` -- an integer or infinity + + EXAMPLES:: + + sage: R = ZpER(7, prec=10) + sage: a = R(1/2021); a + 3 + 6*7 + 4*7^2 + 3*7^3 + 6*7^4 + 7^5 + 6*7^6 + 3*7^7 + 6*7^8 + 5*7^9 + ... + sage: a.add_bigoh(5) + 3 + 6*7 + 4*7^2 + 3*7^3 + 6*7^4 + O(7^5) + + When ``absprec`` is negative, we return an element in the fraction + field:: + + sage: b = a.add_bigoh(-1) + sage: b + O(7^-1) + sage: b.parent() + 7-adic Field handled with relaxed arithmetics + + .. SEEALSO:: + + :meth:`at_precision_absolute`, :meth:`at_precision_relative` + """ + if absprec < 0 and (not self.prime_pow.in_field): + self = element_class_bound(self._parent.fraction_field(), self) + return self.at_precision_absolute(absprec, True) + + def at_precision_relative(self, prec=None, halt=True, permissive=None): + r""" + Return this element bounded at the given precision. + + INPUT: + + - ``prec`` -- an integer or ``None`` (default: ``None``); + if ``None``, use the default precision of the parent + + - ``halt`` -- an integer or a boolean (default: ``True``); + the absolute precision after which the computation is abandonned + if the first significant digit has not been found yet; + if ``True``, the default halting precision of the parent is used; + if ``False``, the computation is never abandonned + + - ``permissive`` -- a boolean (default: ``False`` if ``prec`` + is given, ``True`` otherwise); if ``False``, raise an error + if the precision of this element is not sufficient + + EXAMPLES:: + + sage: R = ZpER(5, prec=10, halt=10) + sage: a = R(20/21); a + 4*5 + 4*5^2 + 5^4 + 4*5^6 + 3*5^7 + 4*5^8 + ... + sage: a.at_precision_relative(5) + 4*5 + 4*5^2 + 5^4 + O(5^6) + + We illustrate the behaviour of the parameter ``halt``. + We create a very small number whose first significant is far beyond + the default precision:: + + sage: b = R(5^20) + sage: b + 0 + ... + + Without any help, Sage does not run the computation far enough to determine + the valuation and an error is raised:: + + sage: b.at_precision_relative(5) + Traceback (most recent call last): + ... + PrecisionError: computation has been abandonned; try to increase precision + + By setting the argument ``halt``, one can force the computation to continue + until a prescribed limit:: + + sage: b.at_precision_relative(5, halt=20) # not enough to find the valuation + Traceback (most recent call last): + ... + PrecisionError: computation has been abandonned; try to increase precision + + sage: b.at_precision_relative(5, halt=21) # now, we're okay + 5^20 + O(5^25) + + .. NOTE: + + It is also possible to pass in ``halt=False`` but it is not recommended + because the computation can hang forever if this element is `0`. + + .. SEEALSO:: + + :meth:`at_precision_absolute`, :meth:`add_bigoh` + + TESTS:: + + sage: a.at_precision_relative(-1) + Traceback (most recent call last): + ... + ValueError: precision must be nonnegative + """ + if prec is Infinity: + if not permissive and self._precbound < maxordp: + raise_error(ERROR_PRECISION) + return self + default_prec = self._parent.default_prec() + if prec is None: + if permissive is None: + permissive = True + prec = default_prec + else: + prec = Integer(prec) + if prec < 0: + raise ValueError("precision must be nonnegative") + if prec > maxordp: + raise OverflowError("beyond maximal precision (which is %s)" % maxordp) + if halt is True: + halt = self._parent.halting_prec() + elif halt is False: + halt = maxordp + else: + halt = min(maxordp, halt) + error = self._jump_relative_c(prec, halt) + if permissive is None: + permissive = False + raise_error(error, permissive) + return element_class_bound((self)._parent, self, self._valuation + prec) + + def lift_to_precision(self, absprec=None): + """ + Return another element of the same parent, lifting this element + and having absolute precision at least ``absprec``. + + INPUT: + + - ``absprec`` -- an integer or ``None`` (default: ``None``), the + absolute precision of the result. If ``None``, the default + precision of the parent is used. + + EXAMPLES:: + + sage: R = ZpER(5, prec=10) + sage: a = R(20/21, 5); a + 4*5 + 4*5^2 + 5^4 + O(5^5) + + sage: a.lift_to_precision(20) + 4*5 + 4*5^2 + 5^4 + O(5^20) + + When the precision is omitted, the default precision of the parent + is used:: + + sage: a.lift_to_precision() + 4*5 + 4*5^2 + 5^4 + O(5^10) + + When the parent is a field, the behaviour is slightly different since + the default precision of the parent becomes the relative precision + of the lifted element:: + + sage: K = R.fraction_field() + sage: K(a).lift_to_precision() + 4*5 + 4*5^2 + 5^4 + O(5^11) + + Note that the precision never decreases:: + + sage: a.lift_to_precision(2) + 4*5 + 4*5^2 + 5^4 + O(5^5) + + In particular, unbounded element are not affected by this method:: + + sage: b = R(20/21); b + 4*5 + 4*5^2 + 5^4 + 4*5^6 + 3*5^7 + 4*5^8 + ... + sage: b.lift_to_precision() + 4*5 + 4*5^2 + 5^4 + 4*5^6 + 3*5^7 + 4*5^8 + ... + sage: b.lift_to_precision(2) + 4*5 + 4*5^2 + 5^4 + 4*5^6 + 3*5^7 + 4*5^8 + ... + """ + if self._precbound >= maxordp: + return self + cdef long prec + cdef long default_prec = self._parent.default_prec() + if absprec is None: + if self.prime_pow.in_field: + self._jump_relative_c(1, self._precbound) + if self._precrel == 0: + return self._parent.zero() + prec = self._valuation + default_prec + else: + prec = default_prec + else: + if absprec > maxordp: + raise OverflowError("beyond the maximal precision (which is %s)" % maxordp) + prec = absprec + if prec <= self._precbound: + return self + cdef RelaxedElement ans = element_class_slice(self._parent, self, -maxordp, self._precbound, 0) + ans._precbound = prec + ans._init_jump() + return ans + + cdef long valuation_c(self, long halt=-maxordp): + r""" + Return the best lower bound we have on the valuation of + this element at the current stage of the computation. + + INPUT: + + - ``halt`` -- an integer; if given, allow to increase the + absolute precision on this element up to ``halt`` in order + to get a better lower bound. + """ + cdef int error = 0 + while not error and self._precrel == 0 and self._valuation < halt: + error = self._next_c() + return self._valuation + + def valuation(self, halt=True, secure=None): + r""" + Return the valuation of this element. + + INPUT: + + - ``halt`` -- an integer or a boolean (default: ``True``); + the absolute precision after which the computation is abandonned + if the first significant digit has not been found yet; + if ``True``, the default halting precision of the parent is used; + if ``False``, the computation is never abandonned + + - ``secure`` -- a boolean (default: the value given at the creation + of the parent); when the valuation cannot be determined for sure, + raise an error if ``secure`` is ``True``, return the best known + lower bound on the valuation otherwise. + + EXAMPLES:: + + sage: R = ZpER(5, prec=10, halt=10) + sage: a = R(2001); a + 1 + 5^3 + 3*5^4 + ... + sage: a.valuation() + 0 + + sage: b = a - 1/a; b + 2*5^3 + 5^4 + 5^5 + 4*5^6 + 3*5^7 + 4*5^8 + 3*5^9 + 3*5^10 + 3*5^11 + 5^12 + ... + sage: b.valuation() + 3 + + The valuation of an exact zero is `+\infty`:: + + sage: R(0).valuation() + +Infinity + + The valuation of an inexact zero is its absolute precision:: + + sage: R(0, 20).valuation() + 20 + + We illustrate the behaviour of the parameter ``halt``. + We create a very small number whose first significant is far beyond + the default precision:: + + sage: z = R(5^20) + sage: z + 0 + ... + + Without any help, Sage does not run the computation far enough to determine + the valuation and outputs only a lower bound:: + + sage: z.valuation() + 10 + + With ``secure=True``, an error is raised:: + + sage: z.valuation(secure=True) + Traceback (most recent call last): + ... + PrecisionError: cannot determine the valuation; try to increase the halting precision + + By setting the argument ``halt``, one can force the computation to continue + until a prescribed limit:: + + sage: z.valuation(halt=15) # not enough to find the correct valuation + 15 + sage: z.valuation(halt=20, secure=True) + Traceback (most recent call last): + ... + PrecisionError: cannot determine the valuation; try to increase the halting precision + + sage: z.valuation(halt=21) # now, we're okay + 20 + + .. NOTE: + + It is also possible to pass in ``halt=False`` but it is not recommended + because the computation can hang forever if this element is `0`. + + TESTS:: + + sage: x = R.unknown() + sage: (~x).valuation() + Traceback (most recent call last): + ... + PrecisionError: no lower bound on the valuation is known + """ + if self._is_exact_zero(): + return Infinity + if self._valuation <= -maxordp: + raise PrecisionError("no lower bound on the valuation is known") + if halt is True: + halt = self._parent.halting_prec() + elif halt is False: + halt = maxordp + else: + halt = min(maxordp, halt) + cdef val = self.valuation_c(halt) + if secure is None: + secure = self._parent.is_secure() + if secure and self._precbound >= maxordp and self._precrel == 0: + raise PrecisionError("cannot determine the valuation; try to increase the halting precision") + return Integer(val) + + def unit_part(self, halt=True): + r""" + Return the unit part of this element. + + INPUT: + + - ``halt`` -- an integer or a boolean (default: ``True``); + the absolute precision after which the computation is abandonned + if the first significant digit has not been found yet; + if ``True``, the default halting precision of the parent is used; + if ``False``, the computation is never abandonned + + EXAMPLES:: + + sage: R = ZpER(5, prec=10, halt=10) + sage: a = R(20/21); a + 4*5 + 4*5^2 + 5^4 + 4*5^6 + 3*5^7 + 4*5^8 + ... + sage: a.unit_part() + 4 + 4*5 + 5^3 + 4*5^5 + 3*5^6 + 4*5^7 + 5^9 + ... + + sage: b = 1/a; b + 4*5^-1 + 4 + 3*5 + 3*5^2 + 3*5^3 + 3*5^4 + 3*5^5 + 3*5^6 + 3*5^7 + 3*5^8 + ... + sage: b.unit_part() + 4 + 4*5 + 3*5^2 + 3*5^3 + 3*5^4 + 3*5^5 + 3*5^6 + 3*5^7 + 3*5^8 + 3*5^9 + ... + + The unit part of `0` is not defined:: + + sage: R(0).unit_part() + Traceback (most recent call last): + ... + ValueError: unit part of 0 not defined + + sage: R(0, 20).unit_part() + Traceback (most recent call last): + ... + ValueError: unit part of 0 not defined + + See :meth:`valuation` for more details on the paramter ``halt``. + + """ + val = self.valuation(halt) + if self._valuation >= self._precbound: + raise ValueError("unit part of 0 not defined") + return self >> val + + def val_unit(self, halt=True): + r""" + Return the valuation and the unit part of this element. + + INPUT: + + - ``halt`` -- an integer or a boolean (default: ``True``); + the absolute precision after which the computation is abandonned + if the first significant digit has not been found yet; + if ``True``, the default halting precision of the parent is used; + if ``False``, the computation is never abandonned + + EXAMPLES:: + + sage: R = ZpER(5, 10) + sage: a = R(20/21); a + 4*5 + 4*5^2 + 5^4 + 4*5^6 + 3*5^7 + 4*5^8 + ... + sage: a.val_unit() + (1, 4 + 4*5 + 5^3 + 4*5^5 + 3*5^6 + 4*5^7 + 5^9 + ...) + + sage: b = 1/a; b + 4*5^-1 + 4 + 3*5 + 3*5^2 + 3*5^3 + 3*5^4 + 3*5^5 + 3*5^6 + 3*5^7 + 3*5^8 + ... + sage: b.val_unit() + (-1, 4 + 4*5 + 3*5^2 + 3*5^3 + 3*5^4 + 3*5^5 + 3*5^6 + 3*5^7 + 3*5^8 + 3*5^9 + ...) + + If this element is indistinguishable from zero, an error is raised + since the unit part of `0` is not defined:: + + sage: R(0).unit_part() + Traceback (most recent call last): + ... + ValueError: unit part of 0 not defined + + sage: R(0, 20).unit_part() + Traceback (most recent call last): + ... + ValueError: unit part of 0 not defined + + See :meth:`valuation` for more details on the paramter ``halt``. + + """ + val = self.valuation(halt) + if self._valuation >= self._precbound: + raise ValueError("unit part of 0 not defined") + return val, self >> val + + def residue(self, absprec=1, field=True, check_prec=True): + r""" + Return the image of this element in the quotient + `\ZZ/p^\mathrm{absprec}\ZZ`. + + INPUT: + + - ``absprec`` -- a non-negative integer (default: ``1``) + + - ``field`` -- boolean (default ``True``); when ``absprec`` is ``1``, + whether to return an element of GF(p) or Zmod(p). + + - ``check_prec`` -- boolean (default ``True``); whether to raise an error + if this element has insufficient precision to determine the reduction. + + EXAMPLES:: + + sage: R = ZpER(7, 10) + sage: a = R(1/2021); a + 3 + 6*7 + 4*7^2 + 3*7^3 + 6*7^4 + 7^5 + 6*7^6 + 3*7^7 + 6*7^8 + 5*7^9 + ... + sage: a.residue() + 3 + sage: a.residue(2) + 45 + + If this element has negative valuation, an error is raised:: + + sage: K = R.fraction_field() + sage: b = K(20/21) + sage: b.residue() + Traceback (most recent call last): + ... + ValueError: element must have non-negative valuation in order to compute residue + """ + if absprec >= maxordp: + raise OverflowError + if absprec < 0: + raise ValueError("cannot reduce modulo a negative power of p") + error = self._jump_c(absprec) + raise_error(error, not check_prec) + if self._valuation < 0: + raise ValueError("element must have non-negative valuation in order to compute residue") + cdef celement digits + cdef Integer ans + if absprec <= self._valuation: + ans = ZZ(0) + else: + self._getslice_relative(digits, 0, min(self._precrel, absprec - self._valuation)) + ans = element_get_sage(digits, self.prime_pow) * self.prime_pow(self._valuation) + if field and absprec == 1: + return self._parent.residue_class_field()(ans) + else: + return self._parent.residue_ring(absprec)(ans) + + def lift(self, absprec=None): + r""" + Return a rational number which is congruent to this element modulo + `p^\mathrm{prec}`. + + INPUT: + + - ``absprec`` -- an integer or ``None`` (default: ``None``); if ``None``, + the absolute precision of this element is used + + EXAMPLES:: + + sage: R = ZpER(7, 10) + sage: a = R(1/2021, 5); a + 3 + 6*7 + 4*7^2 + 3*7^3 + 6*7^4 + O(7^5) + sage: a.lift() + 15676 + sage: a.lift(2) + 45 + + Here is another example with an element of negative valuation:: + + sage: K = R.fraction_field() + sage: b = K(20/21, 5); b + 2*7^-1 + 3 + 2*7 + 2*7^2 + 2*7^3 + 2*7^4 + O(7^5) + sage: b.lift() + 39223/7 + + For unbounded elements, we must specify a precision:: + + sage: c = R(1/2021) + sage: c.lift() + Traceback (most recent call last): + ... + ValueError: you must specify a precision for unbounded elements + + sage: c.lift(5) + 15676 + """ + if absprec is None: + if self._precbound < maxordp: + absprec = self._precbound + else: + raise ValueError("you must specify a precision for unbounded elements") + else: + absprec = Integer(absprec) + cdef int error = self._jump_c(absprec) + raise_error(error) + if absprec < self._valuation: + return Integer(0) + cdef celement digits + self._getslice_relative(digits, 0, absprec - self._valuation) + ans = element_get_sage(digits, self.prime_pow) + if self._valuation: + ans *= self._parent.prime() ** self._valuation + return ans + + def __rshift__(self, s): + r""" + Return this element divided by `\pi^s`, and truncated + if the parent is not a field. + + EXAMPLES:: + + sage: R = ZpER(997) + sage: K = R.fraction_field() + sage: a = R(123456878908); a + 964*997 + 572*997^2 + 124*997^3 + ... + + Shifting to the right divides by a power of `p`, but drops + terms with negative valuation:: + + sage: a >> 3 + 124 + ... + + If the parent is a field no truncation is performed:: + + sage: K(a) >> 3 + 964*997^-2 + 572*997^-1 + 124 + ... + + A negative shift multiplies by that power of `p`:: + + sage: a >> -3 + 964*997^4 + 572*997^5 + 124*997^6 + ... + """ + cdef long start + cdef long shift = long(s) + if shift: + if (self)._parent.is_field(): + start = -maxordp + else: + start = shift + return element_class_slice((self)._parent, self, start, maxordp, shift) + else: + return self + + def __lshift__(self, s): + r""" + Return this element multiplied by `\pi^s`. + + If `s` is negative and this element does not lie in a field, + digits may be truncated. See ``__rshift__`` for details. + + EXAMPLES:: + + sage: R = ZpER(997) + sage: K = R.fraction_field() + sage: a = R(123456878908); a + 964*997 + 572*997^2 + 124*997^3 + ... + + Shifting to the right divides by a power of `p`, but drops + terms with negative valuation:: + + sage: a << 2 + 964*997^3 + 572*997^4 + 124*997^5 + ... + + A negative shift may result in a truncation when the base + ring is not a field:: + + sage: a << -3 + 124 + ... + + sage: K(a) << -3 + 964*997^-2 + 572*997^-1 + 124 + ... + """ + return self.__rshift__(-s) + + cpdef _add_(self, other): + r""" + Return the sum of this element with ``other``. + + TESTS:: + + sage: R = ZpER(7, 10) + sage: R(1/3) + R(1/6) + 4 + 3*7 + 3*7^2 + 3*7^3 + 3*7^4 + 3*7^5 + 3*7^6 + 3*7^7 + 3*7^8 + 3*7^9 + ... + + sage: R(1/3, 5) + R(1/6, 10) + 4 + 3*7 + 3*7^2 + 3*7^3 + 3*7^4 + O(7^5) + """ + if isinstance(self, RelaxedElement_zero): + return other + if isinstance(other, RelaxedElement_zero): + return self + return element_class_add(self._parent, self, other) + + cpdef _sub_(self, other): + r""" + Return the difference of this element and ``other``. + + EXAMPLES:: + + sage: R = ZpER(7, 10) + sage: R(1/3) - R(1/6) + 6 + 5*7 + 5*7^2 + 5*7^3 + 5*7^4 + 5*7^5 + 5*7^6 + 5*7^7 + 5*7^8 + 5*7^9 + ... + + sage: R(1/3, 5) - R(1/6, 10) + 6 + 5*7 + 5*7^2 + 5*7^3 + 5*7^4 + O(7^5) + """ + if self is other: + ans = self._parent.zero() + if self._precbound < maxordp: + ans = element_class_bound(self._parent, ans, self._precbound) + return ans + if isinstance(other, RelaxedElement_zero): + return self + return element_class_sub(self._parent, self, other) + + cpdef _neg_(self): + r""" + Return the opposite of this element. + + EXAMPLES:: + + sage: R = ZpER(7, 10) + sage: -R(1) + 6 + 6*7 + 6*7^2 + 6*7^3 + 6*7^4 + 6*7^5 + 6*7^6 + 6*7^7 + 6*7^8 + 6*7^9 + ... + + sage: -R(1,5) + 6 + 6*7 + 6*7^2 + 6*7^3 + 6*7^4 + O(7^5) + """ + if isinstance(self, RelaxedElement_zero): + return self + return element_class_sub(self._parent, self._parent.zero(), self) + + cpdef _mul_(self, other): + r""" + Return the product of this element with ``other``. + + EXAMPLES:: + + sage: R = ZpER(7, 10) + sage: R(1/2) * R(2/3) + 5 + 4*7 + 4*7^2 + 4*7^3 + 4*7^4 + 4*7^5 + 4*7^6 + 4*7^7 + 4*7^8 + 4*7^9 + ... + + sage: R(1/2, 5) * R(2/3, 10) + 5 + 4*7 + 4*7^2 + 4*7^3 + 4*7^4 + O(7^5) + + sage: R(49, 5) * R(14, 4) + 2*7^3 + O(7^6) + """ + if isinstance(self, RelaxedElement_zero) or isinstance(other, RelaxedElement_one): + return self + if isinstance(self, RelaxedElement_one) or isinstance(other, RelaxedElement_zero): + return other + return element_class_mul(self._parent, self, other) + + cpdef _div_(self, other): + r""" + Return the quotient if this element by ``other``. + + .. NOTE:: + + The result always lives in the fraction field, even if ``other`` is + a unit. + + EXAMPLES:: + + sage: R = ZpER(7, 10) + sage: x = R(2) / R(3) + sage: x + 3 + 2*7 + 2*7^2 + 2*7^3 + 2*7^4 + 2*7^5 + 2*7^6 + 2*7^7 + 2*7^8 + 2*7^9 + ... + sage: x.parent() + 7-adic Field handled with relaxed arithmetics + + TESTS:: + + sage: x / R(0) + Traceback (most recent call last): + ... + ZeroDivisionError: cannot divide by zero + + sage: x / R(0, 10) + Traceback (most recent call last): + ... + ZeroDivisionError: cannot divide by something indistinguishable from zero + + sage: y = R.unknown() + sage: x / y + O(7^-Infinity) + """ + if isinstance(other, RelaxedElement_one): + if self.prime_pow.in_field: + return self + else: + return element_class_bound(self._parent.fraction_field(), self) + return element_class_div(self._parent.fraction_field(), self, other, -maxordp) + + def __invert__(self): + r""" + Return the multiplicative inverse of this element. + + .. NOTE:: + + The result always lives in the fraction field, even if this element + is a unit. + + EXAMPLES:: + + sage: R = ZpER(7, 10) + sage: x = ~R(3) + sage: x + 5 + 4*7 + 4*7^2 + 4*7^3 + 4*7^4 + 4*7^5 + 4*7^6 + 4*7^7 + 4*7^8 + 4*7^9 + ... + sage: x.parent() + 7-adic Field handled with relaxed arithmetics + + TESTS:: + + sage: ~R(0) + Traceback (most recent call last): + ... + ZeroDivisionError: cannot divide by zero + + sage: ~R(0, 10) + Traceback (most recent call last): + ... + ZeroDivisionError: cannot divide by something indistinguishable from zero + + sage: y = R.unknown() + sage: ~y + O(7^-Infinity) + """ + if isinstance(self, RelaxedElement_one): + return self + return element_class_div(self._parent.fraction_field(), self._parent.one(), self, -maxordp) + + def inverse_of_unit(self): + r""" + Return the multiplicative inverse of this element if + it is a unit. + + EXAMPLES:: + + sage: R = ZpER(3, 5) + sage: a = R(2) + sage: b = a.inverse_of_unit() + sage: b + 2 + 3 + 3^2 + 3^3 + 3^4 + ... + + A ``ZeroDivisionError`` is raised if an element has no inverse in the + ring:: + + sage: R(3).inverse_of_unit() + Traceback (most recent call last): + ... + ZeroDivisionError: denominator is not invertible + + Unlike the usual inverse of an element, the result is in the same ring + as this element and not in its fraction field (for fields this does of + course not make any difference):: + + sage: c = ~a; c + 2 + 3 + 3^2 + 3^3 + 3^4 + ... + sage: a.parent() + 3-adic Ring handled with relaxed arithmetics + sage: b.parent() + 3-adic Ring handled with relaxed arithmetics + sage: c.parent() + 3-adic Field handled with relaxed arithmetics + + This method also works for self-referent numbers + (see :meth:`sage.rings.padics.generic_nodes.pAdicRelaxedGeneric.unknown`):: + + sage: x = R.unknown(); x + O(3^0) + sage: x.inverse_of_unit() + O(3^0) + sage: x.set(1 + 3 * x.inverse_of_unit()) + True + sage: x + 1 + 3 + 2*3^2 + 3^3 + 3^4 + ... + + Actually, in many cases, it is preferable to use it than an actual + division. Indeed, compare:: + + sage: y = R.unknown() + sage: y.set(1 + 3/y) + Traceback (most recent call last): + ... + RecursionError: definition looks circular + """ + if isinstance(self, RelaxedElement_one): + return self + if self.prime_pow.in_field: + return element_class_div(self._parent, self._parent.one(), self, -maxordp) + else: + return element_class_div(self._parent, self._parent.one(), self, 0) + + def sqrt(self): + r""" + Return the square root of this element. + + EXAMPLES:: + + sage: R = ZpER(7, 10) + sage: x = R(8) + sage: x.sqrt() + 1 + 4*7 + 2*7^2 + 7^3 + 3*7^4 + 2*7^5 + 4*7^6 + 2*7^7 + 5*7^8 + ... + + When the element is not a square, an error is raised:: + + sage: x = R(10) + sage: x.sqrt() + Traceback (most recent call last): + ... + ValueError: not a square + + For bounded elements, the precision is tracked:: + + sage: x = R(8, 5); x + 1 + 7 + O(7^5) + sage: x.sqrt() + 1 + 4*7 + 2*7^2 + 7^3 + 3*7^4 + O(7^5) + + Note that, when `p = 2`, a digit of precision is lost:: + + sage: S = ZpER(2) + sage: x = S(17, 5) + sage: x.sqrt() + 1 + 2^3 + O(2^4) + + This method also work for self-referent numbers + (see :meth:`sage.rings.padics.generic_nodes.pAdicRelaxedGeneric.unknown`):: + + sage: x = R.unknown(); x + O(7^0) + sage: x.sqrt() + O(7^0) + sage: x.set(1 + 7*sqrt(x)) + True + sage: x + 1 + 7 + 4*7^2 + 4*7^3 + 2*7^4 + 3*7^8 + 3*7^9 + ... + + TESTS:: + + sage: for p in [ 7, 11, 1009 ]: + ....: R = ZpER(p) + ....: x = 1 + p * R.random_element() + ....: y = x.sqrt() + ....: assert(x == y^2) + """ + return element_class_sqrt(self._parent, self) + + def _test_pickling(self, **options): + r""" + Checks that this object can be pickled and unpickled properly. + + TESTS:: + + sage: R = ZpER(7) + sage: x = R.random_element() + sage: x._test_pickling() + + sage: x[:20]._test_pickling() + + .. SEEALSO:: + + :func:`dumps`, :func:`loads` + """ + tester = self._tester(**options) + from sage.misc.all import loads, dumps + if self._precbound >= maxordp: + tester.assertEqual(loads(dumps(self)), self.at_precision_relative()) + else: + tester.assertEqual(loads(dumps(self)), self) + + def _test_nonzero_equal(self, **options): + r""" + Test that ``.__bool__()`` behave consistently with `` == 0``. + + TESTS:: + + sage: R = ZpER(5) + sage: #R(0)._test_nonzero_equal() + sage: #R(5^30)._test_nonzero_equal() + sage: #R.unknown()._test_nonzero_equal() + """ + tester = self._tester(**options) + try: + tester.assertEqual(self != self.parent().zero(), bool(self)) + tester.assertEqual(self == self.parent().zero(), not self) + except PrecisionError: + pass + + +cdef class RelaxedElement_abandon(RelaxedElement): + r""" + A special class for relaxed p-adic with all digits unknown. + + This class is used for setting temporary definition of + some self-referent numbers. + """ + def __init__(self): + r""" + Initialize this element. + + TESTS:: + + sage: from sage.rings.padics.padic_relaxed_element import RelaxedElement_abandon + sage: x = RelaxedElement_abandon() + sage: x.valuation() + Traceback (most recent call last): + ... + PrecisionError: no lower bound on the valuation is known + + sage: x[0] + Traceback (most recent call last): + ... + PrecisionError: computation has been abandonned; try to increase precision + """ + self._valuation = -maxordp + + cdef int _next_c(self): + r""" + Compute the next digit of this element. + + Here, we just abandon the computation. + """ + return ERROR_ABANDON + +cdef relaxedelement_abandon = RelaxedElement_abandon() + + +cdef class RelaxedElementWithDigits(RelaxedElement): + r""" + A generic class for relaxed p-adic elements that stores + the sequence of its digits. + """ + def __cinit__(self): + r""" + Allocate memory for storing digits. + """ + element_init(self._digits) + + def __dealloc__(self): + r""" + Deallocate memory used for digits. + """ + element_clear(self._digits) + + cdef cdigit_ptr _getdigit_relative(self, long i): + r""" + Return a pointer on the `i`-th digit of this number + in relative precision. + """ + return element_get_digit(self._digits, i) + + cdef cdigit_ptr _getdigit_absolute(self, long i): + r""" + Return a pointer on the `i`-th digit of this number + in absolute precision. + """ + return element_get_digit(self._digits, i - self._valuation) + + cdef void _getslice_relative(self, celement slice, long start, long length): + r""" + Select a slice of the sequence of digits of this element. + + INPUT: + + - ``slice`` -- a ``celement`` to store the slice + + - ``start`` -- an integer, the start position of the slice + + - ``length`` -- an integer, the length of the slice + + .. NOTE:: + + This function only sets up a pointer to the requested slice + (the slice is not copied). Hence any future modification + of the slice will modify this element as well. + """ + element_get_slice(slice, self._digits, start, length) + + +# Assignment +############ + +# Zero + +cdef class RelaxedElement_zero(RelaxedElement): + r""" + A class for representation a relaxed p-adic number which is + exactly zero. + + TESTS:: + + sage: R = ZpER(7) + sage: a = R.zero() + sage: TestSuite(a).run() + + """ + def __init__(self, parent): + r""" + Initialize this element. + + INPUT: + + - ``parent`` -- the parent of this element + + TESTS:: + + sage: R = ZpER(5) + sage: x = R(0) # indirect doctest + sage: x + 0 + + sage: type(x) + + """ + RelaxedElement.__init__(self, parent) + self._valuation = maxordp + + def __reduce__(self): + r""" + Return a tuple of a function and data that can be used to unpickle this + element. + + TESTS:: + + sage: a = ZpER(5)(0) + sage: type(a) + + sage: loads(dumps(a)) == a # indirect doctest + True + """ + return self.__class__, (self._parent,) + + cdef cdigit_ptr _getdigit_relative(self, long i): + r""" + Return a pointer on the `i`-th digit of this number + in relative precision. + """ + return digit_zero + + cdef cdigit_ptr _getdigit_absolute(self, long i): + r""" + Return a pointer on the `i`-th digit of this number + in absolute precision. + """ + return digit_zero + + cdef void _getslice_relative(self, celement slice, long start, long length): + r""" + Select a slice of the sequence of digits of this element. + + INPUT: + + - ``slice`` -- a ``celement`` to store the slice + + - ``start`` -- an integer, the start position of the slice + + - ``length`` -- an integer, the length of the slice + """ + element_init(slice) + + cdef int _jump_c(self, long prec): + r""" + Compute the digits of this number until the absolute precision ``prec``. + + INPUT: + + - ``prec`` -- an integer + + OUTPUT: + + An error code (see :meth:`RelaxedElement._next_c` for details). + """ + return 0 + + cdef int _next_c(self): + r""" + Compute the next digit of this number. + + OUTPUT: + + An error code (see :meth:`RelaxedElement._next_c` for details). + """ + return 0 + + +# One + +cdef class RelaxedElement_one(RelaxedElementWithDigits): + r""" + A class for representation a relaxed p-adic number which is + exactly one. + + TESTS:: + + sage: R = ZpER(7) + sage: a = R.one() + sage: TestSuite(a).run() + + """ + def __init__(self, parent): + r""" + Initialize this element. + + INPUT: + + - ``parent`` -- the parent of this element + + TESTS:: + + sage: R = ZpER(5) + sage: x = R(1) # indirect doctest + sage: x + 1 + ... + + sage: type(x) + + """ + RelaxedElement.__init__(self, parent) + element_set_digit_ui(self._digits, 1, 0) + self._precrel = 1 + + def __reduce__(self): + r""" + Return a tuple of a function and data that can be used to unpickle this + element. + + TESTS:: + + sage: a = ZpER(5)(1) + sage: type(a) + + sage: a[:20] == loads(dumps(a)) # indirect doctest + True + """ + return self.__class__, (self._parent,) + + cdef int _jump_c(self, long prec): + r""" + Compute the digits of this number until the absolute precision ``prec``. + + INPUT: + + - ``prec`` -- an integer + + OUTPUT: + + An error code (see :meth:`RelaxedElement._next_c` for details). + """ + if prec > self._precrel: + self._precrel = prec + return 0 + + cdef int _next_c(self): + r""" + Compute the next digit of this number. + + OUTPUT: + + An error code (see :meth:`RelaxedElement._next_c` for details). + """ + self._precrel += 1 + return 0 + + +# Bound + +cdef class RelaxedElement_bound(RelaxedElement): + r""" + A class for p-adic relaxed elements which are defined by bounding the + precision of another p-adic relaxed element. + + TESTS:: + + sage: R = ZpER(5) + sage: x = R.random_element() + sage: y = x[:20] + sage: TestSuite(y).run() + """ + def __init__(self, parent, RelaxedElement x, precbound=None): + r""" + Initialize this element. + + INPUT: + + - ``parent`` -- the parent of this element + + - ``x`` -- a relaxed `p`-adics, the element to bound + + - ``precbound`` -- an integer or ``None`` (default: ``None``), + the bound on the precision + + .. NOTE:: + + The digits of ``x`` are not copied! + + TESTS:: + + sage: R = ZpER(5) + sage: x = R(20/21) + sage: y = x.add_bigoh(20) + sage: type(y) + + """ + RelaxedElement.__init__(self, parent) + self._x = x + if precbound is None: + self._precbound = x._precbound + else: + self._precbound = min(x._precbound, precbound) + self._valuation = min(x._valuation, self._precbound) + self._precrel = min(x._precrel, self._precbound - self._valuation) + self._init_jump() + + def __reduce__(self): + r""" + Return a tuple of a function and data that can be used to unpickle this + element. + + TESTS:: + + sage: a = ZpER(5)(1).add_bigoh(20) + sage: type(a) + + sage: a == loads(dumps(a)) # indirect doctest + True + """ + return self.__class__, (self._parent, self._x, self._precbound) + + cdef cdigit_ptr _getdigit_relative(self, long i): + r""" + Return a pointer on the `i`-th digit of this number + in relative precision. + """ + return self._x._getdigit_relative(i) + + cdef cdigit_ptr _getdigit_absolute(self, long i): + r""" + Return a pointer on the `i`-th digit of this number + in absolute precision. + """ + return self._x._getdigit_absolute(i) + + cdef void _getslice_relative(self, celement slice, long start, long length): + r""" + Select a slice of the digits of this number. + + INPUT: + + - ``slice`` -- a ``celement`` to store the slice + + - ``start`` -- a positive integer, the starting position of the slice + in relative precision + + - ``length`` -- a positive integer, the length of the slice + + .. NOTE:: + + This methods only sets up a pointer to the requested slice + (the slice is not copied). Hence any future modification + of the slice ``slice`` will affect this number. + """ + self._x._getslice_relative(slice, start, length) + + cdef int _jump_c(self, long prec): + r""" + Jump to the absolute precision ``prec``. + + INPUT: + + - ``prec`` -- an integer + + OUTPUT: + + An error code (see :meth:`RelaxedElement._next_c` for details). + """ + cdef RelaxedElement x = self._x + cdef int error + if prec > self._precbound: + error = ERROR_PRECISION | x._jump_c(self._precbound) + else: + error = x._jump_c(prec) + self._precbound = min(self._precbound, x._precbound) + self._valuation = min(x._valuation, self._precbound) + self._precrel = min(x._precrel, self._precbound - self._valuation) + return error + + cdef int _next_c(self): + r""" + Jump to the next digit. + + OUTPUT: + + An error code (see :meth:`RelaxedElement._next_c` for details). + """ + cdef RelaxedElement x = self._x + if self._valuation + self._precrel >= self._precbound: + return ERROR_PRECISION + cdef int error = x._next_c() + self._precbound = min(self._precbound, x._precbound) + self._valuation = min(x._valuation, self._precbound) + self._precrel = min(x._precrel, self._precbound - self._valuation) + return error + + +# Value + +cdef class RelaxedElement_value(RelaxedElementWithDigits): + r""" + A class for relaxed `p`-adics defined by the datum of a value in + the exact subring. + + TESTS:: + + sage: R = ZpER(5) + sage: x = R(2) + sage: TestSuite(x).run() + """ + def __init__(self, parent, value, long shift=0, precbound=None): + r""" + Initialize this element. + + INPUT: + + - ``parent`` -- the parent of this element + + - ``value`` -- the value in the exact subring + + - ``shift`` -- an integer (default: `0`), the position at which + the given value is written + + - ``precbound`` -- an integer or ``None`` (default: ``None``), + the bound on the precision + + TESTS:: + + sage: R = ZpER(5) + sage: x = R(2) + sage: type(x) + + + sage: y = R(2, 10) + sage: type(y) + + """ + RelaxedElement.__init__(self, parent) + element_set_digit_sage(self._digits, value, 0) + self._value = value + self._shift = shift + self._valuation = -shift + if precbound is not None and precbound is not Infinity: + self._precbound = min(maxordp, precbound) + self._valuebound = maxordp + self._init_jump() + + def __reduce__(self): + r""" + Return a tuple of a function and data that can be used to unpickle this + element. + + TESTS:: + + sage: a = ZpER(5)(2, 20) + sage: type(a) + + sage: a == loads(dumps(a)) # indirect doctest + True + """ + return self.__class__, (self._parent, self._value, self._shift, self._precbound) + + cdef int _jump_c(self, long prec): + r""" + Compute the digits of this number until the absolute precision ``prec``. + + INPUT: + + - ``prec`` -- an integer + + OUTPUT: + + An error code (see :meth:`RelaxedElement._next_c` for details). + """ + if self._valuebound >= maxordp: + return RelaxedElement._jump_c(self, prec) + if (self._precbound is not None) and (prec > self._precbound): + self._precrel = self._precbound - self._valuation + return ERROR_PRECISION + cdef long precrel = min(prec, maxordp) - self._valuation + if precrel > self._precrel: + self._precrel = precrel + if prec >= maxordp: + return ERROR_OVERFLOW + return 0 + + cdef int _next_c(self): + r""" + Compute the next digit of this number. + + OUTPUT: + + An error code (see :meth:`RelaxedElement._next_c` for details). + + .. NOTE:: + + The algorithm is not optimal (quadratic in the precision), + but it sounds okay... + """ + if (self._precbound is not None) and (self._valuation + self._precrel >= self._precbound): + return ERROR_PRECISION + element_reduce_digit(self._digits, self._precrel, self.prime_pow) + if self._precrel == 0 and digit_is_zero(self._getdigit_relative(0)): + self._valuation += 1 + element_shift_right(self._digits) + else: + self._precrel += 1 + if digit_is_zero(self._getdigit_relative(self._precrel)): + self._valuebound = self._valuation + self._precrel + if self._precrel == 0: + self._valuation = self._precbound + elif self._precbound < maxordp: + self._precrel = self._precbound - self._valuation + return 0 + + +# Random + +cdef class RelaxedElement_random(RelaxedElementWithDigits): + r""" + A class for random relaxed `p`-adic numbers. + + TESTS:: + + sage: R = ZpER(5) + sage: x = R.random_element() + sage: TestSuite(x).run() + """ + def __init__(self, parent, valuation, precbound=None, seed=None): + r""" + Initialize this element. + + INPUT: + + - ``parent`` -- the parent of this element + + - ``valuation`` -- an integer or ``None``, the position from which + random digits are picked; + if ``None``, it is randomly chosen if the parent is a field and + set to `0` otherwise + + - ``precbound`` -- an integer or ``None`` (default: ``None``), + the bound on the precision + + - ``seed`` -- an integer or ``None`` (default: ``None``), the + seed of the random generator + + .. NOTE:: + + The argument ``valuation`` can be different from the real + valuation of this number since the first randomly picked + digit could vanish. + + TESTS:: + + sage: R = ZpER(7) + sage: x = R.random_element() + sage: type(x) + + """ + RelaxedElement.__init__(self, parent) + if seed is None: + self._seed = randint(0, 2*maxordp) + else: + self._seed = seed + digit_random_init(self._generator, self._seed) + if valuation is None: + self._initialvaluation = ZZ.random_element() + else: + self._initialvaluation = valuation + self._valuation = self._initialvaluation + if precbound is not None: + self._precbound = min(maxordp, precbound) + self._init_jump() + + def __reduce__(self): + r""" + Return a tuple of a function and data that can be used to unpickle this + element. + + TESTS:: + + sage: R = ZpER(5, print_mode="digits") + sage: a = R.random_element() + sage: a # random + ...32220241412003314311 + + sage: b = loads(dumps(a)) + sage: b # random + ...32220241412003314311 + + It is guaranteed that `a` and `b` are equal at any precision:: + + sage: a[:30] + ...?343214211432220241412003314311 + sage: b[:30] + ...?343214211432220241412003314311 + + sage: a == b + True + """ + return self.__class__, (self._parent, self._initialvaluation, self._precbound, self._seed) + + cdef int _next_c(self): + r""" + Generate the next digit of this number at random. + + OUTPUT: + + An error code (see :meth:`RelaxedElement._next_c` for details). + """ + cdef cdigit r + digit_init(r) + digit_random(r, self.prime_pow, self._generator) + if self._precrel == 0 and digit_is_zero(r): + self._valuation += 1 + else: + element_set_digit(self._digits, r, self._precrel) + self._precrel += 1 + digit_clear(r) + return 0 + + +# Operations +############ + +# Slice and shift + +cdef class RelaxedElement_slice(RelaxedElement): + r""" + A class for relaxed `p`-adic numbers defined as slices. + + TESTS:: + + sage: R = ZpER(5) + sage: x = R(20/21) + sage: y = x.slice(3, 6) + sage: TestSuite(y).run() + """ + def __init__(self, parent, RelaxedElement x, long start, long stop, long shift): + r""" + Initialize this element. + + INPUT: + + - ``parent`` -- the parent of this element + + - ``x`` -- a relaxed `p`-adic element, the element from which the + slice is extracted + + - ``start`` -- an integer, the position of the first digit of `x` + in the slice + + - ``stop`` -- an integer, the position of the first digit of `x` + after the slice + + - ``shift`` -- an integer such that ``self[i] = x[i+shift]`` + + .. NOTE:: + + The digits of ``x`` are not copied! + + TESTS:: + + sage: R = ZpER(5) + sage: x = R(20/21) + sage: y = x.slice(3, 6) + sage: type(y) + + """ + RelaxedElement.__init__(self, parent) + self._x = x + self._start = start + self._shift = shift + self._valuation = max(x._valuation, start) - shift + self._precrel = min(x._precrel + x._valuation, stop) - self._valuation - shift + if x._precbound < stop: + self._precbound = min(maxordp, x._precbound - shift) + self._stop = stop + if self._precrel < 0: + self._precrel = 0 + while self._precrel > 0 and digit_is_zero(self._getdigit_relative(0)): + self._precrel -= 1 + self._valuation += 1 + self._init_jump() + + def __reduce__(self): + r""" + Return a tuple of a function and data that can be used to unpickle this + element. + + TESTS:: + + sage: R = ZpER(5, print_mode="digits") + sage: x = R(20/21) + sage: y = x.slice(3, 6) + sage: y == loads(dumps(y)) # indirect doctest + True + """ + return self.__class__, (self._parent, self._x, self._start, self._stop, self._shift) + + cdef cdigit_ptr _getdigit_relative(self, long i): + r""" + Return a pointer on the `i`-th digit of this number + in relative precision. + """ + return self._getdigit_absolute(i + self._valuation) + + cdef cdigit_ptr _getdigit_absolute(self, long i): + r""" + Return a pointer on the `i`-th digit of this number + in absolute precision. + """ + cdef long j = i + self._shift + if j < self._start or j >= self._stop: + return digit_zero + else: + return self._x._getdigit_absolute(j) + + cdef void _getslice_relative(self, celement slice, long start, long length): + r""" + Select a slice of the sequence of digits of this element. + + INPUT: + + - ``slice`` -- a ``celement`` to store the slice + + - ``start`` -- an integer, the start position of the slice + + - ``length`` -- an integer, the length of the slice + + .. NOTE:: + + This function only sets up a pointer to the requested slice + (the slice is not copied). Hence any future modification + of the slice will modify this element as well. + """ + cdef RelaxedElement x = self._x + cdef long s = start + self._valuation + self._shift + cdef long start_absolute = max(self._start, s) + cdef long stop_absolute = min(self._stop, s + length) + x._getslice_relative(slice, start_absolute - x._valuation, stop_absolute - start_absolute) + + cdef int _jump_c(self, long prec): + r""" + Jump to the absolute precision ``prec``. + + INPUT: + + - ``prec`` -- an integer + + OUTPUT: + + An error code (see :meth:`RelaxedElement._next_c` for details). + """ + cdef RelaxedElement x = self._x + cdef int error = 0 + cdef long pr + if prec <= self._valuation + self._precrel: + return 0 + if prec > self._precbound: + prec = self._precbound + error = ERROR_PRECISION + cdef int errorx = x._jump_c(min(prec + self._shift, self._stop)) + pr = max(self._valuation, x._valuation + x._precrel - self._shift) + if self._precrel == 0: + while self._valuation < pr and digit_is_zero(self._getdigit_relative(0)): + self._valuation += 1 + if errorx: + self._precrel = pr - self._valuation + return errorx + else: + self._precrel = prec - self._valuation + return error + + cdef int _next_c(self): + r""" + Jump to the next digit. + + OUTPUT: + + An error code (see :meth:`RelaxedElement._next_c` for details). + """ + cdef int error + cdef long n = self._precrel + self._valuation + n += self._shift + if n <= self._stop: + error = self._x._jump_c(n+1) + if error: + return error + if self._precrel == 0 and (n > self._stop or digit_is_zero(self._getdigit_relative(0))): + self._valuation += 1 + else: + self._precrel += 1 + return 0 + + +# Addition + +cdef class RelaxedElement_add(RelaxedElementWithDigits): + r""" + A class for relaxed `p`-adic numbers defined as sums. + + TESTS:: + + sage: R = ZpER(11) + sage: x = R.random_element() + R.random_element() + sage: TestSuite(x).run() + """ + def __init__(self, parent, RelaxedElement x, RelaxedElement y): + r""" + Initialize this element. + + INPUT: + + - ``parent`` -- the parent of this element + + - ``x`` -- a relaxed `p`-adic element, the first summand + + - ``y`` -- a relaxed `p`-adic element, the second summand + + TESTS:: + + sage: R = ZpER(11) + sage: x = R.random_element() + R.random_element() + sage: type(x) + + """ + RelaxedElement.__init__(self, parent) + self._x = x + self._y = y + cdef halt = self._parent.default_prec() + self._valuation = min(x.valuation_c(0), y.valuation_c(0)) + self._precbound = min(x._precbound, y._precbound) + self._init_jump() + + def __reduce__(self): + r""" + Return a tuple of a function and data that can be used to unpickle this + element. + + TESTS:: + + sage: R = ZpER(5) + sage: x = R.random_element() + R.random_element() + sage: x == loads(dumps(x)) # indirect doctest + True + """ + return self.__class__, (self._parent, self._x, self._y) + + cdef int _jump_c(self, long prec): + r""" + Compute the digits of this number until the absolute precision ``prec``. + + INPUT: + + - ``prec`` -- an integer + + OUTPUT: + + An error code (see :meth:`RelaxedElement._next_c` for details). + """ + # We reimplement _jump_c for better performance + cdef long n = self._valuation + self._precrel + cdef RelaxedElement x = self._x + cdef RelaxedElement y = self._y + cdef int error = x._jump_c(prec) | y._jump_c(prec) + prec = min(prec, x._valuation + x._precrel, y._valuation + y._precrel) + while n < prec: + element_iadd_digit(self._digits, x._getdigit_absolute(n), self._precrel) + element_iadd_digit(self._digits, y._getdigit_absolute(n), self._precrel) + element_reducesmall_digit(self._digits, self._precrel, self.prime_pow) + if self._precrel == 0 and digit_is_zero(self._getdigit_relative(0)): + self._valuation += 1 + element_shift_right(self._digits) + else: + self._precrel += 1 + n += 1 + return error + + cdef int _next_c(self): + r""" + Compute the next digit of this number. + + OUTPUT: + + An error code (see :meth:`RelaxedElement._next_c` for details). + """ + cdef long n = self._valuation + self._precrel + cdef RelaxedElement x = self._x + cdef RelaxedElement y = self._y + cdef int error = x._jump_c(n+1) | y._jump_c(n+1) + if error: + return error + element_iadd_digit(self._digits, x._getdigit_absolute(n), self._precrel) + element_iadd_digit(self._digits, y._getdigit_absolute(n), self._precrel) + element_reducesmall_digit(self._digits, self._precrel, self.prime_pow) + if self._precrel == 0 and digit_is_zero(self._getdigit_relative(0)): + self._valuation += 1 + element_shift_right(self._digits) + else: + self._precrel += 1 + return 0 + + +# Subtraction + +cdef class RelaxedElement_sub(RelaxedElementWithDigits): + r""" + A class for relaxed `p`-adic numbers defined as differences. + + TESTS:: + + sage: R = ZpER(11) + sage: x = R.random_element() - R.random_element() + sage: TestSuite(x).run() + """ + def __init__(self, parent, RelaxedElement x, RelaxedElement y): + r""" + Initialize this element. + + INPUT: + + - ``parent`` -- the parent of this element + + - ``x`` -- a relaxed `p`-adic element, the minuend + + - ``y`` -- a relaxed `p`-adic element, the subtrahend + + TESTS:: + + sage: R = ZpER(11) + sage: x = R.random_element() - R.random_element() + sage: type(x) + + """ + RelaxedElement.__init__(self, parent) + self._x = x + self._y = y + self._valuation = min(x.valuation_c(0), y.valuation_c(0)) + self._precbound = min(x._precbound, y._precbound) + self._init_jump() + + def __reduce__(self): + r""" + Return a tuple of a function and data that can be used to unpickle this + element. + + TESTS:: + + sage: R = ZpER(5) + sage: x = R.random_element() - R.random_element() + sage: x == loads(dumps(x)) # indirect doctest + True + """ + return self.__class__, (self._parent, self._x, self._y) + + cdef int _jump_c(self, long prec): + r""" + Compute the digits of this number until the absolute precision ``prec``. + + INPUT: + + - ``prec`` -- an integer + + OUTPUT: + + An error code (see :meth:`RelaxedElement._next_c` for details). + """ + # We reimplement _jump_c for better performances + cdef long n = self._valuation + self._precrel + cdef RelaxedElement x = self._x + cdef RelaxedElement y = self._y + cdef int error = x._jump_c(prec) | y._jump_c(prec) + prec = min(prec, x._valuation + x._precrel, y._valuation + y._precrel) + while n < prec: + element_iadd_digit(self._digits, x._getdigit_absolute(n), self._precrel) + element_isub_digit(self._digits, y._getdigit_absolute(n), self._precrel) + element_reduceneg_digit(self._digits, self._precrel, self.prime_pow) + if self._precrel == 0 and digit_is_zero(self._getdigit_relative(0)): + self._valuation += 1 + element_shift_right(self._digits) + else: + self._precrel += 1 + n += 1 + return error + + cdef int _next_c(self): + r""" + Compute the next digit of this number. + + OUTPUT: + + An error code (see :meth:`RelaxedElement._next_c` for details). + """ + cdef long n = self._valuation + self._precrel + cdef RelaxedElement x = self._x + cdef RelaxedElement y = self._y + cdef int error = x._jump_c(n+1) | y._jump_c(n+1) + if error: + return error + element_iadd_digit(self._digits, x._getdigit_absolute(n), self._precrel) + element_isub_digit(self._digits, y._getdigit_absolute(n), self._precrel) + element_reduceneg_digit(self._digits, self._precrel, self.prime_pow) + if self._precrel == 0 and digit_is_zero(self._getdigit_relative(0)): + self._valuation += 1 + element_shift_right(self._digits) + else: + self._precrel += 1 + return 0 + + + +# Multiplication + +cdef class RelaxedElement_mul(RelaxedElementWithDigits): + r""" + A class for relaxed `p`-adic numbers defined as products. + + ALGORITHM: + + We compute digits using relaxed arithmetic by var der Hoeven et al., + whose cost is quasi-linear with respect to the precision. + + The algorithm uses the entries behind the current position in the table + ``self._digits`` to store carries. + + TESTS:: + + sage: R = ZpER(11) + sage: x = R.random_element() * R.random_element() + sage: TestSuite(x).run() + """ + def __cinit__(self): + r""" + Allocate memory for temporary variables. + """ + digit_init(self._lastdigit_x) + digit_init(self._lastdigit_y) + + def __dealloc__(self): + r""" + Deallocate memory for temporary variables. + """ + digit_clear(self._lastdigit_x) + digit_clear(self._lastdigit_y) + + def __init__(self, parent, RelaxedElement x, RelaxedElement y): + r""" + Initialize this element. + + INPUT: + + - ``parent`` -- the parent of this element + + - ``x`` -- a relaxed `p`-adic element, the first factor + + - ``y`` -- a relaxed `p`-adic element, the second factor + + TESTS:: + + sage: R = ZpER(11) + sage: x = R.random_element() * R.random_element() + sage: type(x) + + """ + RelaxedElement.__init__(self, parent) + self._x = x + self._y = y + cdef halt = self._parent.default_prec() + self._valuation = min(maxordp, x.valuation_c(0) + y.valuation_c(0)) + if x._precbound < maxordp: + y._jump_relative_c(1, y._valuation + self._parent.default_prec()) + self._precbound = min(self._precbound, y._valuation + x._precbound) + if y._precbound < maxordp: + x._jump_relative_c(1, x._valuation + self._parent.default_prec()) + self._precbound = min(self._precbound, x._valuation + y._precbound) + self._init_jump() + + def __reduce__(self): + r""" + Return a tuple of a function and data that can be used to unpickle this + element. + + TESTS:: + + sage: R = ZpER(5) + sage: x = R.random_element() * R.random_element() + sage: x == loads(dumps(x)) # indirect doctest + True + """ + return self.__class__, (self._parent, self._x, self._y) + + cdef int _next_c(self): + r""" + Compute the next digit of this number. + + OUTPUT: + + An error code (see :meth:`RelaxedElement._next_c` for details). + """ + global tmp_digit, tmp_poly + cdef RelaxedElement x = self._x + cdef RelaxedElement y = self._y + cdef long n = self._valuation + self._precrel + + cdef int errorx = x._jump_c(n - y._valuation + 1) + cdef int errory = y._jump_c(n - x._valuation + 1) + cdef int error = errorx | errory + if self._precrel == 0: + self._valuation = x._valuation + y._valuation + if self._valuation > n: + return 0 + if self._valuation < n or x._precrel == 0 or y._precrel == 0: + return error | ERROR_PRECISION + elif error: + return error + + n = self._precrel + digit_set(self._lastdigit_x, x._getdigit_relative(n)) + digit_set(self._lastdigit_y, y._getdigit_relative(n)) + digit_mul(tmp_digit, x._getdigit_relative(0), self._lastdigit_y) + element_iadd_digit(self._digits, tmp_digit, n) + if n: + digit_mul(tmp_digit, self._lastdigit_x, y._getdigit_relative(0)) + element_iadd_digit(self._digits, tmp_digit, n) + + cdef long m = n + 2 + cdef long len = 1 + cdef celement slicex, slicey + while (m & 1 == 0) and (m > 3): + m >>= 1 + len <<= 1 + x._getslice_relative(slicex, len - 1, len) + y._getslice_relative(slicey, (m-1)*len - 1, len) + element_mul(tmp_poly, slicex, slicey) + element_iadd_slice(self._digits, tmp_poly, n) + if m > 2: + x._getslice_relative(slicex, (m-1)*len - 1, len) + y._getslice_relative(slicey, len - 1, len) + element_mul(tmp_poly, slicex, slicey) + element_iadd_slice(self._digits, tmp_poly, n) + + element_reduce_digit(self._digits, n, self.prime_pow) + self._precrel += 1 + return 0 + + cdef int _update_last_digit(self): + r""" + Redo the computation of the last digit and update carries + accordingly. + + This method is used for computing Teichmüller representatives. + """ + if self._precrel == 0: + return ERROR_UNEXPECTED + cdef RelaxedElement x = self._x + cdef RelaxedElement y = self._y + cdef long n = self._precrel - 1 + cdef long m = n + 2 + cdef long len = 2 + cdef celement slice + while (m & 1 == 0) and (m > 3): + m >>= 1 + len <<= 1 + len -= 1 + + digit_sub(tmp_digit, x._getdigit_relative(n), self._lastdigit_x) + y._getslice_relative(slice, 0, len) + element_scalarmul(tmp_poly, slice, tmp_digit) + element_iadd_slice(self._digits, tmp_poly, n) + if m == 2: + len -= 1 + digit_sub(tmp_digit, y._getdigit_relative(n), self._lastdigit_y) + x._getslice_relative(slice, 0, len) + element_scalarmul(tmp_poly, slice, tmp_digit) + element_iadd_slice(self._digits, tmp_poly, n) + if m == 2: + digit_mul(tmp_digit, tmp_digit, self._lastdigit_x) + element_iadd_digit(self._digits, tmp_digit, 2*len) + element_reduce_digit(self._digits, n, self.prime_pow) + + digit_set(self._lastdigit_x, x._getdigit_relative(n)) + digit_set(self._lastdigit_y, y._getdigit_relative(n)) + return 0 + + +cdef class RelaxedElement_muldigit(RelaxedElementWithDigits): + r""" + A class for relaxed `p`-adic numbers defined as products + of a relaxed `p`-adic number by a digit. + + This class is not exposed to the user; it is only used + internally for division. + """ + def __init__(self, parent, RelaxedElement_div x, RelaxedElement y): + r""" + Initialize this element. + + INPUT: + + - ``parent`` -- the parent of this element + + - ``x`` -- a relaxed `p`-adic element, whose first significant + digit is the first factor + + - ``y`` -- a relaxed `p`-adic element, the second factor + + """ + RelaxedElement.__init__(self, parent) + self._x = x._inverse + self._y = y + self._valuation = y._valuation + self._init_jump() + + cdef int _next_c(self): + r""" + Compute the next digit of this number. + + OUTPUT: + + An error code (see :meth:`RelaxedElement._next_c` for details). + """ + cdef long n = self._valuation + self._precrel + cdef int error = self._y._jump_c(n+1) + if error: + return error + digit_mul(tmp_digit, self._x, self._y._getdigit_absolute(n)) + element_iadd_digit(self._digits, tmp_digit, self._precrel) + element_reduce_digit(self._digits, self._precrel, self.prime_pow) + self._precrel += 1 + return 0 + + +# Division + +cdef class RelaxedElement_div(RelaxedElementWithDigits): + r""" + A class for relaxed `p`-adic numbers defined as quotients. + + ALGORITHM: + + We compute the quotient `x = a/b` as the self-referent number defined by + + .. MATH:: + + x = ac + (1 - bc) x + + where `c` is congruent to `b^{-1}` modulo the uniformizer. + + TESTS:: + + sage: R = ZpER(5) + sage: x = R(20) / R(21) + sage: TestSuite(x).run() + """ + def __cinit__(self): + r""" + Allocate memory for temporary variables. + """ + digit_init(self._inverse) + + def __dealloc__(self): + r""" + Deallocate memory for temporary variables. + """ + digit_clear(self._inverse) + + def __init__(self, parent, RelaxedElement num, RelaxedElement denom, long minval=-maxordp, precbound=None): + r""" + Initialize this element. + + INPUT: + + - ``parent`` -- the parent of this element + + - ``num`` -- a relaxed `p`-adic element, the dividend + + - ``denom`` -- a relaxed `p`-adic element, the divisor + + - ``minval`` -- an integer, the minimal valuation allowed for this element + + - ``precbound`` -- an integer or ``None`` (default: ``None``), + the bound on the precision + + TESTS:: + + sage: R = ZpER(5) + sage: x = R(20) / R(21) + sage: type(x) + + + sage: y = R.unknown() + sage: 1/y + O(5^-Infinity) + sage: y.inverse_of_unit() + O(5^0) + """ + RelaxedElement.__init__(self, parent) + if denom._valuation >= maxordp: + raise ZeroDivisionError("cannot divide by zero") + if denom._precbound < maxordp and denom._precrel == 0: + raise ZeroDivisionError("cannot divide by something indistinguishable from zero") + self._num = num + self._denom = denom + if denom._valuation <= -maxordp: + self._maxprec = maxordp + 1 + else: + self._maxprec = denom._valuation + max(1, self._parent.default_prec()) + self._valuation = minval + cdef int error = self._bootstrap_c() + if precbound is not None: + self._precbound = min(maxordp, precbound) + if num._precbound < maxordp: + self._precbound = min(self._precbound, num._precbound - denom._valuation) + if denom._precbound < maxordp: + self._precbound = min(self._precbound, denom._precbound + num._valuation - 2*denom._valuation) + raise_error(error, permissive=True) + if not error: + self._init_jump() + + def __reduce__(self): + r""" + Return a tuple of a function and data that can be used to unpickle this + element. + + TESTS:: + + sage: R = ZpER(5) + sage: x = R(20) / R(21) + sage: x == loads(dumps(x)) # indirect doctest + True + """ + return self.__class__, (self._parent, self._num, self._denom, self._valuation, self._precbound) + + cdef int _bootstrap_c(self): + r""" + Bootstrap the computation of the digits of this element, that is: + + - find the valuation + - compute the first digit, and + - set up the recursive definition of the next digits. + + OUTPUT: + + An error code (see :meth:`RelaxedElement._next_c` for details). + """ + cdef int error + cdef RelaxedElement num = self._num + cdef RelaxedElement denom = self._denom + + while denom._valuation < self._maxprec and denom._precrel == 0: + error = denom._next_c() + if error: + if error & ERROR_PRECISION: + error |= ERROR_DIVISION + return error + if self._maxprec < maxordp and denom._valuation > -maxordp: + self._maxprec = denom._valuation + max(1, self._parent.default_prec()) + if denom._precrel == 0: + return ERROR_ABANDON + + cdef long valuation = num._valuation - denom._valuation + if valuation < self._valuation: + error = num._jump_c(self._valuation + denom._valuation) + if error: + return error + valuation = num._valuation - denom._valuation + if valuation < self._valuation: + return ERROR_DIVISION + self._valuation = valuation + digit_inv(self._inverse, denom._getdigit_relative(0), self.prime_pow) + self._definition = relaxedelement_abandon + cdef parent = self._parent + cdef RelaxedElement a = element_class_muldigit(parent, self, num) + cdef RelaxedElement b = element_class_muldigit(parent, self, denom) + cdef RelaxedElement c = element_class_slice(parent, b, denom._valuation + 1, maxordp, 0) + cdef RelaxedElement d = element_class_mul(parent, c, self) + self._definition = element_class_sub(parent, a, d) + return 0 + + cdef int _next_c(self): + r""" + Compute the next digit of this number. + + OUTPUT: + + An error code (see :meth:`RelaxedElement._next_c` for details). + """ + cdef RelaxedElement definition = self._definition + if definition is None: + return self._bootstrap_c() + cdef long val = self._valuation + self._denom._valuation + cdef int error = definition._jump_c(val + self._precrel + 1) + if error: + return error + if definition._valuation > val: + self._valuation = min(self._precbound, definition._valuation - self._denom._valuation) + if definition._precbound < maxordp: + self._precbound = min(self._precbound, definition._precbound - self._denom._valuation) + else: + digit = definition._getdigit_relative(self._precrel) + element_set_digit(self._digits, digit, self._precrel) + self._precrel += 1 + return 0 + + +# Square root + +cdef class RelaxedElement_sqrt(RelaxedElementWithDigits): + r""" + A class for relaxed `p`-adic numbers defined as square roots. + + ALGORITHM: + + When `p \neq 2`, we compute `y = \sqrt{x}` as the self-referent number + defined by + + .. MATH:: + + y = \frac{x - (y-a)^2 + a^2}{2a} + + where `a^2` is congruent to `x` modulo the uniformizer. + + When `p = 2`, we use a variant of this construction. + + TESTS:: + + sage: R = ZpER(5) + sage: x = R(6).sqrt() + sage: TestSuite(x).run() + """ + def __init__(self, parent, RelaxedElement x): + r""" + Initialize this element. + + INPUT: + + - ``parent`` -- the parent of this element + + - ``x`` -- a relaxed `p`-adic element + + TESTS:: + + sage: R = ZpER(5) + sage: x = R(6).sqrt() + sage: type(x) + + """ + RelaxedElement.__init__(self, parent) + self._x = x + if x._valuation <= -maxordp: + self._valuation = -maxordp + else: + self._valuation = x._valuation >> 1 + cdef int error = self._bootstrap_c() + if error & ERROR_NOTSQUARE: + raise ValueError("not a square") + if not error: + if x._precbound < maxordp: + self._precbound = x._precbound - x._valuation / 2 + if self._parent.prime() == 2: + self._precbound -= 1 + self._precbound = min(maxordp, self._precbound) + self._init_jump() + + def __reduce__(self): + r""" + Return a tuple of a function and data that can be used to unpickle this + element. + + TESTS:: + + sage: R = ZpER(5) + sage: x = R(6).sqrt() + sage: x == loads(dumps(x)) # indirect doctest + True + """ + return self.__class__, (self._parent, self._x) + + cdef int _bootstrap_c(self): + r""" + Bootstrap the computation of the digits of this element, that is: + + - find the valuation + - compute the first digit, and + - set up the recursive definition of the next digits. + + OUTPUT: + + An error code (see :meth:`RelaxedElement._next_c` for details). + + .. NOTE:: + + This code does not work for nontrivial extensions of `\QQ_2`. + + """ + cdef RelaxedElement x = self._x + cdef long maxprec + if x._valuation <= -maxordp: + return ERROR_ABANDON + if self._valuation <= -maxordp: + maxprec = x._valuation + else: + maxprec = (self._valuation + 1) << 1 + while x._valuation < maxprec and x._precrel == 0: + error = x._next_c() + if error: + return error + if x._valuation & 1: + self._valuation = (x._valuation + 1) >> 1 + else: + self._valuation = x._valuation >> 1 + if x._precrel == 0: + return 0 + if x._valuation & 1 != 0: + return ERROR_NOTSQUARE + + cdef parent = self._parent + cdef long val = self._valuation + cdef cdigit digit + cdef Integer zd, p = self.prime_pow.prime + cdef RelaxedElement u, y, c, d + + if p == 2: + element_set_digit_ui(self._digits, 1, 0) + self._precrel = 1 + if x._precrel == 1: + error = x._next_c() + if error: + return error + if not digit_equal_ui(x._getdigit_relative(1), 0): + return ERROR_NOTSQUARE + if x._precrel == 2: + error = x._next_c() + if error: + return error + if not digit_equal_ui(x._getdigit_relative(2), 0): + return ERROR_NOTSQUARE + zd = Integer(1) + self._definition = relaxedelement_abandon + u = element_class_slice(parent, self, val + 2, maxordp, val) + y = element_class_slice(parent, x, -maxordp, maxordp, val + 1) + c = element_class_value(parent, zd, shift=-val+1) + d = element_class_slice(parent, u*u, -maxordp, maxordp, -val + 1) + self._definition = y + c - d + else: + digit_init(digit) + if digit_sqrt(digit, x._getdigit_relative(0), self.prime_pow): + digit_clear(digit) + return ERROR_NOTSQUARE + element_set_digit(self._digits, digit, 0) + self._precrel = 1 + zd = digit_get_sage(digit) + self._definition = relaxedelement_abandon + u = element_class_slice(parent, self, val + 1, maxordp, val) + y = element_class_slice(parent, x, -maxordp, maxordp, 2*val) + c = element_class_value(parent, zd*zd) + d = element_class_value(parent, 2*zd, shift=val) + self._definition = (y + c - u*u) / d + return 0 + + cdef int _next_c(self): + r""" + Compute the next digit of this number. + + OUTPUT: + + An error code (see :meth:`RelaxedElement._next_c` for details). + """ + cdef RelaxedElement definition = self._definition + if definition is None: + return self._bootstrap_c() + cdef long n = self._valuation + self._precrel + cdef int error = definition._jump_c(n+1) + if error: + return error + element_set_digit(self._digits, definition._getdigit_relative(self._precrel), self._precrel) + self._precrel += 1 + return 0 + + +# Teichmüller lifts + +cdef class RelaxedElement_teichmuller(RelaxedElementWithDigits): + r""" + A class for relaxed `p`-adic numbers defined as teichmüller representatives. + + ALGORITHM: + + We compute `x = [a]` as the unique self-referent number with last + digit `a` and `x = x^p`. + Note that `x^p` is known with one more digit than `x` itself. + + TESTS:: + + sage: R = ZpER(7) + sage: x = R.teichmuller(2) + sage: TestSuite(x).run() + """ + def __init__(self, parent, xbar): + r""" + Initialize this element. + + INPUT: + + - ``parent`` -- the parent of this element + + - ``xbar`` -- an element in the exact subring, which is congruent + to this Teichmüller modulo this uniformizer. + + TESTS:: + + sage: R = ZpER(7) + sage: x = R.teichmuller(2) + sage: type(x) + + """ + RelaxedElement.__init__(self, parent) + cdef cdigit digit + digit_init(digit) + digit_set_sage(digit, xbar) + digit_mod(digit, digit, self.prime_pow) + if digit_equal_ui(digit, 0): + digit_clear(digit) + self._trivial = True + self._valuation = maxordp + else: + element_set_digit(self._digits, digit, 0) + self._trivial = digit_equal_ui(digit, 1) + self._precrel += 1 + + cdef RelaxedElement xn + cdef Integer p + cdef int size, i + if not self._trivial: + xn = self + p = self.prime_pow.prime + size = mpz_sizeinbase(p.value, 2) + i = size - 2 + self._xns = [ ] + while i >= 0: + xn = element_class_mul(parent, xn, xn) + self._xns.append(xn) + if mpz_tstbit(p.value, i): + xn = element_class_mul(parent, xn, self) + self._xns.append(xn) + i -= 1 + self._xp = xn + self._ready = True + + def __reduce__(self): + r""" + Return a tuple of a function and data that can be used to unpickle this + element. + + TESTS:: + + sage: R = ZpER(7) + sage: x = R.teichmuller(2) + sage: x == loads(dumps(x)) # indirect doctest + True + """ + xbar = digit_get_sage(element_get_digit(self._digits, 0)) + return self.__class__, (self._parent, xbar) + + cdef int _jump_c(self, long prec): + r""" + Compute the digits of this number until the absolute precision ``prec``. + + INPUT: + + - ``prec`` -- an integer + + OUTPUT: + + An error code (see :meth:`RelaxedElement._next_c` for details). + """ + if not self._ready: + return ERROR_ABANDON + if self._trivial: + if self._valuation == 0 and self._precrel < prec: + self._precrel = prec + return 0 + return RelaxedElement._jump_c(self, prec) + + cdef int _next_c(self): + r""" + Compute the next digit of this number. + + OUTPUT: + + An error code (see :meth:`RelaxedElement._next_c` for details). + """ + if self._trivial: + if self._valuation: + self._precrel += 1 + return 0 + cdef int error + cdef RelaxedElement xp = self._xp + cdef RelaxedElement_mul xn + self._precrel += 1 + xp._jump_c(self._precrel) + element_set_digit(self._digits, xp._getdigit_relative(self._precrel - 1), self._precrel - 1) + for xn in self._xns: + error = xn._update_last_digit() + if error: + return error | ERROR_UNEXPECTED + return 0 + + +# Self-referent definitions +########################### + +cdef class RelaxedElement_unknown(RelaxedElementWithDigits): + r""" + A class for self-referent relaxed `p`-adic numbers. + + TESTS:: + + sage: R = ZpER(7) + sage: x = R.unknown() + sage: TestSuite(x).run() + + sage: x.set(1 + 7*x^2) + True + sage: TestSuite(x).run() + """ + def __init__(self, parent, long valuation, digits=None): + r""" + Initialize this element. + + INPUT: + + - ``parent`` -- the parent of this element + + - ``valuation`` -- an integer, a lower bound on the valuation of + this number + + - ``digits`` -- a list or ``None`` (default: ``None``), the first + significant digits of this number + + TESTS:: + + sage: R = ZpER(7) + sage: x = R.unknown() + sage: type(x) + + """ + RelaxedElement.__init__(self, parent) + if valuation >= maxordp: + raise OverflowError("valuation is too large (maximum is %s)" % maxordp) + self._valuation = valuation + self._definition = None + self._next = maxordp + cdef cdigit digit + if digits is not None: + digits = [ Integer(d) for d in digits ] + for d in digits: + digit_set_sage(digit, d) + element_iadd_digit(self._digits, digit, self._precrel) + element_reduce_digit(self._digits, self._precrel, self.prime_pow) + if self._precrel == 0 and digit_is_zero(element_get_digit(self._digits, 0)): + self._valuation += 1 + element_shift_right(self._digits) + else: + self._precrel += 1 + self._initialvaluation = self._valuation + self._initialprecrel = self._precrel + + def __reduce__(self): + r""" + Return a tuple of a function and data that can be used to unpickle this + element. + + TESTS:: + + sage: R = ZpER(7) + sage: x = R.unknown() + sage: x.set(1 + 7*x^2) + True + sage: x == loads(dumps(x)) # indirect doctest + True + """ + digits = [ ] + for i in range(self._initialprecrel): + digits.append(digit_get_sage(element_get_digit(self._digits, i))) + definition = None + if id(self) not in persist.already_pickled: + persist.already_pickled[id(self)] = True + definition = self._definition + return unpickle_unknown, (id(self), self.__class__, self._parent, self._initialvaluation, digits, definition) + + cpdef set(self, RelaxedElement definition): + r""" + Set the recursive definition of this self-referent number. + + INPUT: + + - ``definition`` -- a relaxed `p`-adic number, to which this + number is equal + + OUTPUT: + + A boolean indicating if the definition is coherent with the + already known digits of this number. + + EXAMPLES:: + + sage: R = ZpER(5, 10) + sage: x = R.unknown() + sage: x.set(1 + 5*x) + True + sage: x + 1 + 5 + 5^2 + 5^3 + 5^4 + 5^5 + 5^6 + 5^7 + 5^8 + 5^9 + ... + + The previous construction works because the relation we gave defines + the `n`-th digit of `x` in terms of its digits at precision strictly + less than `n` (this is due to the multiplication by `5`). + + On the contrary, the following does not work:: + + sage: y = R.unknown() + sage: y.set(1 + 3*y) + True + sage: y + O(5^0) + sage: y[:20] + Traceback (most recent call last): + ... + RecursionError: definition looks circular + + In the next example, we give explicit values for the first digits + and then a recursive definition for the next digits. However, the + recursive definition does not hold for the first digits; that is the + reason why the call to :meth:`set` returns ``False``:: + + sage: z = R.unknown(digits=[2]) + sage: z + 2 + O(5) + sage: z.set(1 + 5*z) + False + sage: z + 2 + 2*5 + 2*5^2 + 2*5^3 + 2*5^4 + 2*5^5 + 2*5^6 + 2*5^7 + 2*5^8 + 2*5^9 + ... + + .. SEEALSO:: + + :meth:`sage.rings.padics.generic_nodes.pAdicRelaxedGeneric.unknown` + """ + if self._definition is not None: + raise ValueError("this self-referent number is already defined") + self._definition = definition + self._precbound = max(self._valuation + self._precrel, definition._precbound) + eq = self._is_equal(definition, self._valuation + self._precrel, True) + self._init_jump() + return eq + + cdef int _next_c(self): + r""" + Compute the next digit of this number. + + OUTPUT: + + An error code (see :meth:`RelaxedElement._next_c` for details). + """ + cdef RelaxedElement definition = self._definition + cdef cdigit_ptr digit + cdef long n = self._valuation + self._precrel + cdef long diffval + cdef int error + + if definition is None: + return ERROR_NOTDEFINED + if n >= self._next: + return ERROR_CIRCULAR + + cdef long svenext = self._next + self._next = n + error = definition._jump_c(n+1) + if not error: + digit = definition._getdigit_absolute(n) + if self._precrel == 0 and digit_is_zero(digit): + self._valuation += 1 + else: + element_set_digit(self._digits, digit, self._precrel) + self._precrel += 1 + self._next = svenext + return error + +def unpickle_unknown(uid, cls, parent, valuation, digits, definition): + r""" + Unpickle a self-referent relaxed `p`-adic number. + + TESTS: + + Cross definitions involving several self-referent numbers are + handled correctly:: + + sage: R = ZpER(7) + sage: x = R.unknown() + sage: y = R.unknown() + sage: x.set(1 + 2*y + 7*x*y) + True + sage: y.set(3 + 14*x^2) + True + + sage: x == loads(dumps(x)) # indirect doctest + True + sage: y == loads(dumps(y)) # indirect doctest + True + """ + if uid in persist.already_unpickled: + elt = persist.already_unpickled[uid] + else: + elt = cls(parent, valuation, digits) + persist.already_unpickled[uid] = elt + if definition is not None: + elt.set(definition) + return elt + + +# Expansion +########### + +cdef class RelaxedElement_zeroone(RelaxedElementWithDigits): + r""" + A special class for `p`-adic relaxed elements with only + `0` and `1` as digits. + + This class is used for computing expansion in Teichmuller mode. + It is not supposed to be instantiated in other situations. + """ + def __init__(self, parent, long valuation): + r""" + Instantiate this element. + + INPUT: + + - ``parent`` -- the parent of this element + + - ``valuation`` -- the valuation of this number + + """ + RelaxedElement.__init__(self, parent) + self._valuation = valuation + + cdef void _setdigit_to_zero(self): + r""" + Append `0` to the list of digits of this element. + """ + self._precrel += 1 + + cdef void _setdigit_to_one(self): + r""" + Append `1` to the list of digits of this element. + """ + element_set_digit_ui(self._digits, 1, self._precrel) + self._precrel += 1 + + cdef int _jump_c(self, long prec): + r""" + Jump to the absolute precision ``prec``. + + INPUT: + + - ``prec`` -- an integer + + OUTPUT: + + An error code (see :meth:`RelaxedElement._next_c` for details). + """ + if prec > self._valuation + self._precrel: + return ERROR_NOTDEFINED + return 0 + + cdef int _next_c(self): + r""" + Jump to the next digit. + + OUTPUT: + + An error code (see :meth:`RelaxedElement._next_c` for details). + """ + return ERROR_NOTDEFINED + + +cdef class ExpansionIter(object): + """ + An iterator over a `p`-adic expansion. + + This class should not be instantiated directly, but instead using + :meth:`RelaxedElement.expansion`. + """ + def __cinit__(self): + r""" + Allocate memory for temporary variables. + """ + digit_init(self.carry) + + def __dealloc__(self): + r""" + Deallocate memory for temporary variables. + """ + digit_clear(self.carry) + + def __init__(self, RelaxedElement elt, expansion_mode mode, long start, long stop): + r""" + Initialize this iterator. + + INPUT: + + - ``elt`` -- a relaxed `p`-adic number + + - ``mode`` -- either ``simple_mode``, ``smallest_mode`` or ``teichmuller_mode`` + + - ``start`` -- an integer, the position where the expansion starts + + - ``stop`` -- an integer, the position where the expansion stops + + TESTS:: + + sage: E = ZpER(5,4)(373).expansion() + sage: I = iter(E) # indirect doctest + sage: type(I) + + """ + self.elt = elt + self.mode = mode + self.start = start + self.stop = stop + self.current = min(start, elt._valuation) + digit_init(self.digit) + # Compute first digits if needed + if self.mode == simple_mode: + self.current = self.start + elif self.mode == smallest_mode: + while self.current < self.start: + self._next_smallest() + elif self.mode == teichmuller_mode: + self.tail = elt + self.coefficients = { } + while self.current < self.start: + self._next_teichmuller() + + def __repr__(self): + r""" + Return a string representation of this iterator. + + EXAMPLES:: + + sage: R = ZpER(7, 5) + sage: x = R(1/2021) + sage: x.expansion() # indirect doctest + 7-adic expansion of 3 + 6*7 + 4*7^2 + 3*7^3 + 6*7^4 + ... + + sage: x.expansion(lift_mode='smallest') # indirect doctest + 7-adic expansion of 3 + 6*7 + 4*7^2 + 3*7^3 + 6*7^4 + ... (balanced) + + sage: x.expansion(lift_mode='teichmuller') # indirect doctest + 7-adic expansion of 3 + 6*7 + 4*7^2 + 3*7^3 + 6*7^4 + ... (teichmuller) + """ + s = "%s-adic expansion of %s" % (self.elt._parent.prime(), self.elt) + if self.mode == smallest_mode: + s += " (balanced)" + elif self.mode == teichmuller_mode: + s += " (teichmuller)" + return s + + def __len__(self): + r""" + Return the length of this expansion. + + EXAMPLES:: + + sage: R = ZpER(7) + sage: x = R(1/2021, 5) + sage: x + 3 + 6*7 + 4*7^2 + 3*7^3 + 6*7^4 + O(7^5) + sage: E = x.expansion() + sage: len(E) + 5 + + For unbounded elements, the expansion is infinite and this method + raises an error:: + + sage: y = R(1/2021) + sage: E = y.expansion() + sage: len(E) + Traceback (most recent call last): + ... + NotImplementedError: infinite sequence + """ + if self.stop >= maxordp: + raise NotImplementedError("infinite sequence") + return Integer(self.stop - self.start) + + def __iter__(self): + r""" + Return itself (as any iterator is supposed to do). + + TESTS:: + + sage: E = ZpER(5)(373).expansion() + sage: I = iter(E) + sage: I is iter(I) + True + """ + return self + + cdef _next_simple(self): + r""" + Return the next digit of this expansion (simple mode). + """ + cdef RelaxedElement elt = self.elt + elt._jump_c(self.current + 1) + digit_set(self.digit, elt._getdigit_absolute(self.current)) + self.current += 1 + return digit_get_sage(self.digit) + + cdef _next_smallest(self): + r""" + Return the next digit of this expansion (smallest mode). + """ + cdef RelaxedElement elt = self.elt + elt._jump_c(self.current + 1) + digit_add(self.digit, elt._getdigit_absolute(self.current), self.carry) + digit_smallest(self.digit, self.carry, self.digit, elt.prime_pow) + self.current += 1 + return digit_get_sage(self.digit) + + cdef _next_teichmuller(self): + r""" + Return the next digit of this expansion (Teichmüller mode). + """ + cdef RelaxedElement teichmuller, tail = self.tail + cdef RelaxedElement_zeroone coeff + cdef digit + tail._jump_c(self.current + 1) + digit_set(self.digit, tail._getdigit_absolute(self.current)) + digit = digit_get_sage(self.digit) + if digit != 0 and digit != 1 and digit not in self.coefficients: + parent = tail._parent + self.coefficients[digit] = coeff = RelaxedElement_zeroone(parent, self.current) + teichmuller = element_class_teichmuller(parent, digit) + self.tail = tail - coeff * element_class_slice(parent, teichmuller, 1, maxordp, 0) + for d, coeff in self.coefficients.items(): + if d == digit: + coeff._setdigit_to_one() + else: + coeff._setdigit_to_zero() + self.current += 1 + return digit + + def __next__(self): + r""" + Return the next digit of this expansion. + + EXAMPLES:: + + sage: R = ZpER(11, 10) + sage: x = R(20/21); x + 2 + 2*11 + 4*11^2 + 8*11^3 + 5*11^4 + 11^6 + 2*11^7 + 4*11^8 + 8*11^9 + ... + sage: E = x.expansion() + sage: next(E) + 2 + sage: next(E) + 2 + + TESTS:: + + sage: def check_expansion(x, mode): + ....: R = x.parent() + ....: E = x.expansion(lift_mode=mode) + ....: y = 0 + ....: for i in range(len(E)): + ....: digit = next(E) + ....: if mode == 'teichmuller': + ....: y += R.teichmuller(digit) << i + ....: else: + ....: y += R(digit) << i + ....: assert(x == y) + + sage: for p in primes(100): + ....: x = ZpER(p).random_element()[:20] + ....: for mode in [ 'simple', 'smallest', 'teichmuller' ]: + ....: check_expansion(x, mode) + """ + if self.current >= self.stop: + raise StopIteration + if self.mode == simple_mode: + return self._next_simple() + elif self.mode == smallest_mode: + return self._next_smallest() + elif self.mode == teichmuller_mode: + return self._next_teichmuller() diff --git a/src/sage/rings/padics/relaxed_template_header.pxi b/src/sage/rings/padics/relaxed_template_header.pxi new file mode 100644 index 00000000000..4e3f7f825df --- /dev/null +++ b/src/sage/rings/padics/relaxed_template_header.pxi @@ -0,0 +1,159 @@ +""" +This file provides the declaration for the RelaxedElement class, +which collects common functionality for the different relaxed p-adic +template classes. + +It is included in padic_relaxed_element.pxd and should be included +in any pxd file implementing relaxed `p`-adics. + +AUTHORS: + +- Xavier Caruso (2021-02) -- initial version +""" + +#***************************************************************************** +# Copyright (C) 2021 Xavier Caruso +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.rings.integer cimport Integer +from sage.rings.padics.pow_computer cimport PowComputer_class +from sage.rings.padics.padic_generic_element cimport pAdicGenericElement + +cdef enum expansion_mode: + simple_mode, smallest_mode, teichmuller_mode + +cdef class RelaxedElement(pAdicGenericElement): + cdef long _valuation + cdef long _precrel + cdef long _precbound + cdef PowComputer_class prime_pow + + cdef cdigit_ptr _getdigit_relative(self, long i) + cdef cdigit_ptr _getdigit_absolute(self, long i) + cdef void _getslice_relative(self, celement slice, long start, long length) + + cdef int _init_jump(self) except -1 + cdef int _jump_c(self, long prec) + cdef int _jump_relative_c(self, long prec, long halt) + cdef int _next_c(self) + + cdef long valuation_c(self, long halt=*) + cdef bint _is_equal(self, RelaxedElement right, long prec, bint permissive) except -1 + +cdef class RelaxedElement_abandon(RelaxedElement): + pass +cdef relaxedelement_abandon + +cdef class RelaxedElementWithDigits(RelaxedElement): + cdef celement _digits + + +# Assignment + +cdef class RelaxedElement_zero(RelaxedElement): + pass + +cdef class RelaxedElement_one(RelaxedElementWithDigits): + pass + +cdef class RelaxedElement_bound(RelaxedElement): + cdef RelaxedElement _x + +cdef class RelaxedElement_value(RelaxedElementWithDigits): + cdef long _valuebound + cdef long _shift + cdef _value + +cdef class RelaxedElement_random(RelaxedElementWithDigits): + cdef randgen _generator + # for pickling + cdef long _initialvaluation + cdef long _seed + + +# Operations + +cdef class RelaxedElement_slice(RelaxedElement): + cdef RelaxedElement _x + cdef long _start + cdef long _stop + cdef long _shift + +cdef class RelaxedElement_add(RelaxedElementWithDigits): + cdef RelaxedElement _x + cdef RelaxedElement _y + +cdef class RelaxedElement_sub(RelaxedElementWithDigits): + cdef RelaxedElement _x + cdef RelaxedElement _y + +cdef class RelaxedElement_mul(RelaxedElementWithDigits): + cdef RelaxedElement _x + cdef cdigit _lastdigit_x + cdef RelaxedElement _y + cdef cdigit _lastdigit_y + cdef int _update_last_digit(self) + +cdef class RelaxedElement_muldigit(RelaxedElementWithDigits): + cdef cdigit_ptr _x + cdef RelaxedElement _y + +cdef class RelaxedElement_div(RelaxedElementWithDigits): + cdef long _maxprec + cdef cdigit _inverse + cdef RelaxedElement _num + cdef RelaxedElement _denom + cdef RelaxedElement _definition + cdef int _bootstrap_c(self) + cdef bint _bootstraping + +cdef class RelaxedElement_sqrt(RelaxedElementWithDigits): + cdef RelaxedElement _x + cdef RelaxedElement _definition + cdef int _bootstrap_c(self) + +cdef class RelaxedElement_teichmuller(RelaxedElementWithDigits): + cdef bint _ready + cdef bint _trivial + cdef list _xns + cdef RelaxedElement _xbar + cdef RelaxedElement _xp + +# Self-referent numbers + +cdef class RelaxedElement_unknown(RelaxedElementWithDigits): + cdef RelaxedElement _definition + cdef long _next + cpdef set(self, RelaxedElement definition) + # for pickling + cdef long _initialvaluation + cdef long _initialprecrel + +# Expansion + +cdef class RelaxedElement_zeroone(RelaxedElementWithDigits): + cdef void _setdigit_to_zero(self) + cdef void _setdigit_to_one(self) + +cdef class ExpansionIter(object): + cdef RelaxedElement elt + cdef expansion_mode mode + cdef long start + cdef long stop + cdef long current + cdef cdigit digit + # simple mode + cdef _next_simple(self) + # smallest mode + cdef cdigit carry + cdef _next_smallest(self) + # teichmuller mode + cdef RelaxedElement tail + cdef dict coefficients + cdef _next_teichmuller(self) diff --git a/src/sage/rings/padics/tutorial.py b/src/sage/rings/padics/tutorial.py index cf8c3684383..d281ae663a9 100644 --- a/src/sage/rings/padics/tutorial.py +++ b/src/sage/rings/padics/tutorial.py @@ -338,74 +338,3 @@ Note that the precision cap increased by a factor of 5, since the ramification index of this extension over `\ZZ_p` is 5. """ - -# Lazy Rings and Fields -# --------------------- - -# The model for lazy elements is quite different from any of the -# other types of `p`-adics. In addition to storing a finite -# approximation, one also stores a method for increasing the -# precision. The interface supports two ways to do this: -# ``set_precision_relative`` and ``set_precision_absolute``. - -# :: - -# #sage: R = Zp(5, prec = 10, type = 'lazy', print_mode = 'series', halt = 30) -# #sage: R -# #Lazy 5-adic Ring -# #sage: R.precision_cap() -# #10 -# #sage: R.halting_parameter() -# #30 -# #sage: K = Qp(5, type = 'lazy') -# #sage: K.precision_cap() -# #20 -# #sage: K.halting_parameter() -# #40 - -# There are two parameters that are set at the creation of a lazy -# ring or field. The first is ``prec``, which controls the precision -# to which elements are initially computed. When computing with lazy -# rings, sometimes situations arise where the unsolvability of the -# halting problem gives us problems. For example, - -# :: - -# #sage: a = R(16) -# #sage: b = a.log().exp() - a -# #sage: b -# #O(5^10) -# #sage: b.valuation() -# #Traceback (most recent call last): -# #... -# #HaltingError: Stopped computing sum: set halting parameter higher if you want computation to continue - -# Setting the halting parameter controls to what absolute precision -# one computes in such a situation. - -# The interesting feature of lazy elements is that one can perform -# computations with them, discover that the answer does not have the -# desired precision, and then ask for more precision. For example, - -# :: - -# #sage: a = R(6).log() * 15 -# #sage: b = a.exp() -# #sage: c = b / R(15).exp() -# #sage: c -# #1 + 2*5 + 4*5^2 + 3*5^3 + 2*5^4 + 3*5^5 + 5^6 + 5^10 + O(5^11) -# #sage: c.set_precision_absolute(15) -# #sage: c -# #1 + 2*5 + 4*5^2 + 3*5^3 + 2*5^4 + 3*5^5 + 5^6 + 5^10 + 4*5^11 + 2*5^12 + 4*5^13 + 3*5^14 + O(5^15) - -# There can be a performance penalty to using lazy `p`-adics -# in this way. When one does computations with them, the computer -# constructs an expression tree. As you compute, values of these -# elements are cached, and the overhead is reasonably low (though -# obviously higher than for a fixed modulus element for example). But -# when you set the precision, the computer has to reset precision -# throughout the expression tree for that element, and thus setting -# precision can take the same order of magnitude of time as doing the -# initial computation. However, lazy `p`-adics can be quite -# useful when experimenting. - diff --git a/src/sage/rings/polynomial/polynomial_quotient_ring.py b/src/sage/rings/polynomial/polynomial_quotient_ring.py index 699e0ac2854..bc65b286e84 100644 --- a/src/sage/rings/polynomial/polynomial_quotient_ring.py +++ b/src/sage/rings/polynomial/polynomial_quotient_ring.py @@ -386,6 +386,18 @@ def __init__(self, ring, polynomial, name=None, category=None): sage: S == PolynomialQuotientRing_generic(R,x^2-4,'xbar') True + Check that :trac:`26161` has been resolved:: + + sage: R. = GF(2)[] + sage: S = R.quo(x) + sage: S in FiniteFields() + True + sage: type(S).mro() + [, + ... + , + ... + """ if not isinstance(ring, PolynomialRing_commutative): raise TypeError("R must be a univariate polynomial ring.") @@ -399,6 +411,11 @@ def __init__(self, ring, polynomial, name=None, category=None): self.__ring = ring self.__polynomial = polynomial category = CommutativeAlgebras(ring.base_ring().category()).Quotients().or_subcategory(category) + if self.is_finite(): + # We refine the category for finite quotients. + # Note that is_finite() is cheap so it does not seem to do a lazy + # _refine_category_() in is_finite() as we do for is_field() + category = category.Finite() CommutativeRing.__init__(self, ring, names=name, category=category) def _element_constructor_(self, x): @@ -1140,7 +1157,7 @@ def _S_decomposition(self, S): Compute the decomposition of self into a product of number fields. This is an internal function used by - :meth:`S_class_group`, :meth:`S_units` and :meth:`selmer_group`. + :meth:`S_class_group`, :meth:`S_units` and :meth:`selmer_generators`. EXAMPLES:: @@ -1635,7 +1652,7 @@ def units(self, proof=True): """ return self.S_units((), proof=proof) - def selmer_group(self, S, m, proof=True): + def selmer_generators(self, S, m, proof=True): r""" If self is an étale algebra `D` over a number field `K` (i.e. a quotient of `K[x]` by a squarefree polynomial) and `S` is a @@ -1661,19 +1678,19 @@ def selmer_group(self, S, m, proof=True): sage: K. = QuadraticField(-5) sage: R. = K[] sage: D. = R.quotient(x) - sage: D.selmer_group((), 2) + sage: D.selmer_generators((), 2) [-1, 2] - sage: D.selmer_group([K.ideal(2, -a+1)], 2) + sage: D.selmer_generators([K.ideal(2, -a+1)], 2) [2, -1] - sage: D.selmer_group([K.ideal(2, -a+1), K.ideal(3, a+1)], 2) + sage: D.selmer_generators([K.ideal(2, -a+1), K.ideal(3, a+1)], 2) [2, a + 1, -1] - sage: D.selmer_group((K.ideal(2, -a+1),K.ideal(3, a+1)), 4) + sage: D.selmer_generators((K.ideal(2, -a+1),K.ideal(3, a+1)), 4) [2, a + 1, -1] - sage: D.selmer_group([K.ideal(2, -a+1)], 3) + sage: D.selmer_generators([K.ideal(2, -a+1)], 3) [2] - sage: D.selmer_group([K.ideal(2, -a+1), K.ideal(3, a+1)], 3) + sage: D.selmer_generators([K.ideal(2, -a+1), K.ideal(3, a+1)], 3) [2, a + 1] - sage: D.selmer_group([K.ideal(2, -a+1), K.ideal(3, a+1), K.ideal(a)], 3) + sage: D.selmer_generators([K.ideal(2, -a+1), K.ideal(3, a+1), K.ideal(a)], 3) [2, a + 1, a] """ @@ -1682,7 +1699,7 @@ def selmer_group(self, S, m, proof=True): component_selmer_groups = [] for D_iso, S_iso in iso_classes: - sel = D_iso.selmer_group(S_iso, m, proof=proof) + sel = D_iso.selmer_generators(S_iso, m, proof=proof) component_selmer_groups.append(sel) gens = [] @@ -1699,6 +1716,9 @@ def selmer_group(self, S, m, proof=True): return gens + # For backwards compatibility: + selmer_group = selmer_generators + def _factor_multivariate_polynomial(self, f, proof=True): r""" Return the factorization of ``f`` over this ring. diff --git a/src/sage/rings/qqbar.py b/src/sage/rings/qqbar.py index 81e333be4f5..2b586491c61 100644 --- a/src/sage/rings/qqbar.py +++ b/src/sage/rings/qqbar.py @@ -2537,6 +2537,8 @@ def number_field_elements_from_algebraics(numbers, minimal=False, same_field=Fal To: Algebraic Real Field Defn: a |--> 1.259921049894873? + :: + sage: nf,nums,hom = number_field_elements_from_algebraics([2^(1/3),3^(1/5)],embedded=True) sage: nf Number Field in a with defining polynomial y^15 - 9*y^10 + 21*y^5 - 3 with a = 0.6866813218928813? @@ -2548,6 +2550,16 @@ def number_field_elements_from_algebraics(numbers, minimal=False, same_field=Fal To: Algebraic Real Field Defn: a |--> 0.6866813218928813? + Complex embeddings are possible as well:: + + sage: elems = [sqrt(5), 2^(1/3)+sqrt(3)*I, 3/4] + sage: nf, nums, hom = number_field_elements_from_algebraics(elems, embedded=True) + sage: nf + Number Field in a with defining polynomial y^24 - 6*y^23 ...- 9*y^2 + 1 + with a = 0.2598678911433438? + 0.0572892247058457?*I + sage: list(map(QQbar, nums)) == elems == list(map(hom, nums)) + True + TESTS:: sage: number_field_elements_from_algebraics(rt3) @@ -2640,8 +2652,6 @@ def mk_algebraic(x): real_case = True except: real_case = False - if embedded: - raise NotImplementedError # Make the numbers algebraic numbers = [mk_algebraic(_) for _ in numbers] @@ -2668,12 +2678,11 @@ def mk_algebraic(x): fld = gen._field nums = [gen(v._exact_value()) for v in numbers] - hom = fld.hom([gen.root_as_algebraic()]) + exact_generator = gen.root_as_algebraic() + hom = fld.hom([exact_generator]) if fld is not QQ and embedded: # creates the embedded field - assert real_case - exact_generator = hom(fld.gen(0)) embedded_field = NumberField(fld.defining_polynomial(),fld.variable_name(),embedding=exact_generator) # embeds the numbers @@ -2681,14 +2690,14 @@ def mk_algebraic(x): nums = [inter_hom(n) for n in nums] # get the field and homomorphism - hom = embedded_field.hom([gen.root_as_algebraic()]) + hom = embedded_field.hom([exact_generator]) fld = embedded_field if single_number: nums = nums[0] if same_field: - hom = fld.hom([gen.root_as_algebraic()], codomain=algebraic_field) + hom = fld.hom([exact_generator], codomain=algebraic_field) return (fld, nums, hom) @@ -4208,6 +4217,17 @@ def as_number_field_element(self, minimal=False, embedded=False, prec=53): sage: RR(elt) -2.80464272693274 + A complex algebraic number as an element of an embedded number field:: + + sage: num = QQbar(sqrt(2) + 3^(1/3)*I) + sage: nf, elt, hom = num.as_number_field_element(embedded=True) + sage: hom(elt).parent() is QQbar + True + sage: nf.coerce_embedding() is not None + True + sage: QQbar(elt) == num == hom(elt) + True + We see an example where we do not get the minimal number field unless we specify ``minimal=True``:: diff --git a/src/sage/rings/rational_field.py b/src/sage/rings/rational_field.py index b2152df48df..e7959e21a01 100644 --- a/src/sage/rings/rational_field.py +++ b/src/sage/rings/rational_field.py @@ -63,7 +63,7 @@ from sage.structure.sequence import Sequence import sage.rings.number_field.number_field_base as number_field_base from sage.misc.fast_methods import Singleton - +from sage.misc.superseded import deprecated_function_alias class RationalField(Singleton, number_field_base.NumberField): r""" @@ -1100,7 +1100,8 @@ def order(self): return Infinity def polynomial(self): - r"""Return a defining polynomial of `\QQ`, as for other number fields. + r""" + Return a defining polynomial of `\QQ`, as for other number fields. This is is also aliased to :meth:`self.defining_polynomial()` and :meth:`self.absolute_polynomial()`. @@ -1267,9 +1268,9 @@ def zeta(self, n=2): raise ValueError("no n-th root of unity in rational field") - def selmer_group(self, S, m, proof=True, orders=False): + def selmer_generators(self, S, m, proof=True, orders=False): r""" - Compute the group `\QQ(S,m)`. + Return generators of the group `\QQ(S,m)`. INPUT: @@ -1294,13 +1295,21 @@ def selmer_group(self, S, m, proof=True, orders=False): all primes of `\QQ` outside of `S`, but may contain it properly when not all primes dividing `m` are in `S`. + .. SEEALSO:: + + :meth:`RationalField.selmer_space`, which gives additional + output when `m=p` is prime: as well as generators, it gives an + abstract vector space over `GF(p)` isomorphic to `\QQ(S,p)` + and maps implementing the isomorphism between this space and + `\QQ(S,p)` as a subgroup of `\QQ^*/(\QQ^*)^p`. + EXAMPLES:: - sage: QQ.selmer_group((), 2) + sage: QQ.selmer_generators((), 2) [-1] - sage: QQ.selmer_group((3,), 2) + sage: QQ.selmer_generators((3,), 2) [-1, 3] - sage: QQ.selmer_group((5,), 2) + sage: QQ.selmer_generators((5,), 2) [-1, 5] The previous examples show that the group generated by the @@ -1309,10 +1318,11 @@ def selmer_group(self, S, m, proof=True, orders=False): When `m` is even, `-1` is a generator of order `2`:: - sage: QQ.selmer_group((2,3,5,7,), 2, orders=True) + sage: QQ.selmer_generators((2,3,5,7,), 2, orders=True) ([-1, 2, 3, 5, 7], [2, 2, 2, 2, 2]) - sage: QQ.selmer_group((2,3,5,7,), 3, orders=True) + sage: QQ.selmer_generators((2,3,5,7,), 3, orders=True) ([2, 3, 5, 7], [3, 3, 3, 3]) + """ gens = list(S) ords = [ZZ(m)] * len(S) @@ -1324,6 +1334,9 @@ def selmer_group(self, S, m, proof=True, orders=False): else: return gens + # For backwards compatibility: + selmer_group = deprecated_function_alias(31345, selmer_generators) + def selmer_group_iterator(self, S, m, proof=True): r""" Return an iterator through elements of the finite group `\QQ(S,m)`. @@ -1339,7 +1352,7 @@ def selmer_group_iterator(self, S, m, proof=True): OUTPUT: An iterator yielding the distinct elements of `\QQ(S,m)`. See - the docstring for :meth:`selmer_group` for more information. + the docstring for :meth:`selmer_generators` for more information. EXAMPLES:: @@ -1352,13 +1365,94 @@ def selmer_group_iterator(self, S, m, proof=True): sage: list(QQ.selmer_group_iterator((5,), 2)) [1, 5, -1, -5] """ - KSgens, ords = self.selmer_group(S=S, m=m, proof=proof, orders=True) + KSgens, ords = self.selmer_generators(S=S, m=m, proof=proof, orders=True) one = self.one() from sage.misc.all import prod from itertools import product for ev in product(*[range(o) for o in ords]): yield prod((p**e for p,e in zip(KSgens, ev)), one) + def selmer_space(self, S, p, proof=None): + r""" + Compute the group `\QQ(S,p)` as a vector space with maps to and from `\QQ^*`. + + INPUT: + + - ``S`` -- a list of prime numbers + + - ``p`` -- a prime number + + OUTPUT: + + (tuple) ``QSp``, ``QSp_gens``, ``from_QSp``, ``to_QSp`` where + + - ``QSp`` is an abstract vector space over `GF(p)` isomorphic to `\QQ(S,p)`; + + - ``QSp_gens`` is a list of elements of `\QQ^*` generating `\QQ(S,p)`; + + - ``from_QSp`` is a function from ``QSp`` to `\QQ^*` + implementing the isomorphism from the abstract `\QQ(S,p)` to + `\QQ(S,p)` as a subgroup of `\QQ^*/(\QQ^*)^p`; + + - ``to_QSP`` is a partial function from `\QQ^*` to ``QSp``, + defined on elements `a` whose image in `\QQ^*/(\QQ^*)^p` lies in + `\QQ(S,p)`, mapping them via the inverse isomorphism to the + abstract vector space ``QSp``. + + The group `\QQ(S,p)` is the finite subgroup of + `\QQ^*/(\QQ^*)^p$ consisting of elements whose valuation at + all primes not in `S` is a multiple of `p`. It contains the + subgroup of those `a\in \QQ^*` such that + `\QQ(\sqrt[p]{a})/\QQ` is unramified at all primes of `\QQ` + outside of `S`, but may contain it properly when `p` is not in `S`. + + EXAMPLES: + + When `S` is empty, `\QQ(S,p)` is only nontrivial for `p=2`:: + + sage: QS2, QS2gens, fromQS2, toQS2 = QQ.selmer_space([], 2) + sage: QS2 + Vector space of dimension 1 over Finite Field of size 2 + sage: QS2gens + [-1] + + sage: all(QQ.selmer_space([], p)[0].dimension() == 0 for p in primes(3,10)) + True + + In general there is one generator for each `p\in S`, and an + additional generator of `-1` when `p=2`:: + + sage: QS2, QS2gens, fromQS2, toQS2 = QQ.selmer_space([5,7], 2) + sage: QS2 + Vector space of dimension 3 over Finite Field of size 2 + sage: QS2gens + [5, 7, -1] + sage: toQS2(-7) + (0, 1, 1) + sage: fromQS2((0,1,1)) + -7 + + The map ``fromQS2`` is only well-defined modulo `p`'th powers + (in this case, modulo squares):: + + sage: toQS2(-5/7) + (1, 1, 1) + sage: fromQS2((1,1,1)) + -35 + sage: ((-5/7)/(-35)).is_square() + True + + The map ``toQS2`` is not defined on all of `\QQ^*`, only on + those numbers which are squares away from `5` and `7`:: + + sage: toQS2(210) + Traceback (most recent call last): + ... + ValueError: argument 210 should have valuations divisible by 2 at all primes in [5, 7] + + """ + from sage.rings.number_field.selmer_group import pSelmerGroup + return pSelmerGroup(self, S, p) def quadratic_defect(self, a, p, check=True): r""" diff --git a/src/sage/schemes/elliptic_curves/ec_database.py b/src/sage/schemes/elliptic_curves/ec_database.py index 0c98d286992..4c221715432 100644 --- a/src/sage/schemes/elliptic_curves/ec_database.py +++ b/src/sage/schemes/elliptic_curves/ec_database.py @@ -74,7 +74,7 @@ class EllipticCurves: def rank(self, rank, tors=0, n=10, labels=False): r""" - Return a list of at most `n` non-isogenous curves with given + Return a list of at most `n` curves with given rank and torsion order. INPUT: diff --git a/src/sage/schemes/elliptic_curves/ell_rational_field.py b/src/sage/schemes/elliptic_curves/ell_rational_field.py index eea4c883303..1ce8323e1b0 100644 --- a/src/sage/schemes/elliptic_curves/ell_rational_field.py +++ b/src/sage/schemes/elliptic_curves/ell_rational_field.py @@ -4439,17 +4439,17 @@ def minimal_quadratic_twist(self): if self.conductor().is_squarefree(): return self, Integer(1) j = self.j_invariant() - if j!=0 and j!=1728: + if j != 0 and j != 1728: # the constructor from j will give the minimal twist Et = constructor.EllipticCurve_from_j(j) else: - if j==0: # divide c6 by largest cube + if j == 0: # divide c6 by largest cube c = -2*self.c6() for p in c.support(): e = c.valuation(p)//3 c /= p**(3*e) E1 = constructor.EllipticCurve([0,0,0,0,c]) - elif j==1728: # divide c4 by largest square + else: # j=1728 ; divide c4 by largest square c = -3*self.c4() for p in c.support(): e = c.valuation(p)//2 diff --git a/src/sage/schemes/elliptic_curves/formal_group.py b/src/sage/schemes/elliptic_curves/formal_group.py index 07e809026d0..b7f007af459 100644 --- a/src/sage/schemes/elliptic_curves/formal_group.py +++ b/src/sage/schemes/elliptic_curves/formal_group.py @@ -732,33 +732,53 @@ def mult_by_n(self, n, prec=10): return result def sigma(self, prec=10): - """ + r""" + Return the Weierstrass sigma function as a formal power series + solution to the differential equation + + .. MATH:: + + \frac{d^2 \log \sigma}{dz^2} = - \wp(z) + + with initial conditions `\sigma(O)=0` and `\sigma'(O)=1`, + expressed in the variable `t=\log_E(z)` of the formal group. + + INPUT: + + - ``prec`` - integer (default 10) + + OUTPUT: a power series with given precision + + Other solutions can be obtained by multiplication with + a function of the form `\exp(c z^2)`. + If the curve has good ordinary reduction at a prime `p` + then there is a canonical choice of `c` that produces + the canonical `p`-adic sigma function. + To obtain that, please use ``E.padic_sigma(p)`` instead. + See :meth:`~sage.schemes.elliptic_curves.ell_rational_field.EllipticCurve_rational_field.padic_sigma` + EXAMPLES:: sage: E = EllipticCurve('14a') sage: F = E.formal_group() sage: F.sigma(5) - t + 1/2*t^2 + (1/2*c + 1/3)*t^3 + (3/4*c + 3/4)*t^4 + O(t^5) + t + 1/2*t^2 + 1/3*t^3 + 3/4*t^4 + O(t^5) """ a1,a2,a3,a4,a6 = self.curve().ainvs() k = self.curve().base_ring() fl = self.log(prec) - R = rings.PolynomialRing(k, 'c') - c = R.gen() F = fl.reverse() - S = rings.LaurentSeriesRing(R,'z') - c = S(c) + S = rings.LaurentSeriesRing(k,'z') z = S.gen() F = F(z + O(z**prec)) - wp = self.x()(F) - e2 = 12*c - a1**2 - 4*a2 - g = (1/z**2 - wp + e2/12).power_series() + wp = self.x()(F) + (a1**2 + 4*a2)/12 + g = (1/z**2 - wp).power_series() h = g.integral().integral() sigma_of_z = z.power_series() * h.exp() - T = rings.PowerSeriesRing(R,'t') + T = rings.PowerSeriesRing(k,'t') fl = fl(T.gen()+O(T.gen()**prec)) sigma_of_t = sigma_of_z(fl) return sigma_of_t diff --git a/src/sage/schemes/elliptic_curves/gal_reps_number_field.py b/src/sage/schemes/elliptic_curves/gal_reps_number_field.py index 2198ad5c387..2b356a1bd20 100644 --- a/src/sage/schemes/elliptic_curves/gal_reps_number_field.py +++ b/src/sage/schemes/elliptic_curves/gal_reps_number_field.py @@ -1021,12 +1021,12 @@ def _possible_normalizers(E, SA): K = E.base_field() SA = [K.ideal(I.gens()) for I in SA] - selmer_group = K.selmer_group(SA, 2) # Generators of the selmer group. + selmer_gens = K.selmer_generators(SA, 2) # Generators of the selmer group. - if not selmer_group: + if not selmer_gens: return [] - V = VectorSpace(GF(2), len(selmer_group)) + V = VectorSpace(GF(2), len(selmer_gens)) # We think of this as the character group of the selmer group. traces_list = [] @@ -1048,7 +1048,7 @@ def _possible_normalizers(E, SA): splitting_vector = [] # This will be the values of this # character on the generators of the Selmer group. - for a in selmer_group: + for a in selmer_gens: abar = k(a) if abar == 0: # Ramification. @@ -1091,9 +1091,9 @@ def _possible_normalizers(E, SA): # We find the element a of the selmer group corresponding to v: a = 1 - for i in range(len(selmer_group)): + for i in range(len(selmer_gens)): if v[i] == 1: - a *= selmer_group[i] + a *= selmer_gens[i] # Since we've already included the above bad primes, we can assume # that the quadratic character corresponding to the exceptional primes diff --git a/src/sage/stats/distributions/discrete_gaussian_integer.pyx b/src/sage/stats/distributions/discrete_gaussian_integer.pyx index 6ee3cdb4a00..328c34756d8 100644 --- a/src/sage/stats/distributions/discrete_gaussian_integer.pyx +++ b/src/sage/stats/distributions/discrete_gaussian_integer.pyx @@ -26,7 +26,16 @@ We construct a sampler for the distribution `D_{3,c}` with width `σ=3` and cent We ask for 100000 samples:: - sage: n=100000; l = [D() for _ in range(n)] + sage: from collections import defaultdict + sage: counter = defaultdict(Integer) + sage: n = 0 + sage: def add_samples(i): + ....: global counter, n + ....: for _ in range(i): + ....: counter[D()] += 1 + ....: n += 1 + + sage: add_samples(100000) These are sampled with a probability proportional to `\exp(-x^2/18)`. More precisely we have to normalise by dividing by the overall probability over all @@ -41,12 +50,13 @@ away is very unlikely and compute:: With this normalisation factor, we can now test if our samples follow the expected distribution:: - sage: x=0; l.count(x), ZZ(round(n*exp(-x^2/(2*sigma^2))/norm_factor)) - (13355, 13298) - sage: x=4; l.count(x), ZZ(round(n*exp(-x^2/(2*sigma^2))/norm_factor)) - (5479, 5467) - sage: x=-10; l.count(x), ZZ(round(n*exp(-x^2/(2*sigma^2))/norm_factor)) - (53, 51) + sage: expected = lambda x : ZZ(round(n*exp(-x^2/(2*sigma^2))/norm_factor)) + sage: observed = lambda x : counter[x] + + sage: add_samples(10000) + sage: while abs(observed(0)*1.0/expected(0) - 1.0) > 5e-2: add_samples(10000) + sage: while abs(observed(4)*1.0/expected(4) - 1.0) > 5e-2: add_samples(10000) + sage: while abs(observed(-10)*1.0/expected(-10) - 1.0) > 5e-2: add_samples(10000) # long time We construct an instance with a larger width:: @@ -56,23 +66,43 @@ We construct an instance with a larger width:: ask for 100000 samples:: - sage: n=100000; l = [D() for _ in range(n)] # long time + sage: from collections import defaultdict + sage: counter = defaultdict(Integer) + sage: n = 0 + sage: def add_samples(i): + ....: global counter, n + ....: for _ in range(i): + ....: counter[D()] += 1 + ....: n += 1 + + sage: add_samples(100000) and check if the proportions fit:: - sage: x=0; y=1; float(l.count(x))/l.count(y), exp(-x^2/(2*sigma^2))/exp(-y^2/(2*sigma^2)).n() # long time - (1.0, 1.00...) - sage: x=0; y=-100; float(l.count(x))/l.count(y), exp(-x^2/(2*sigma^2))/exp(-y^2/(2*sigma^2)).n() # long time - (1.32..., 1.36...) + sage: expected = lambda x, y: ( + ....: exp(-x^2/(2*sigma^2))/exp(-y^2/(2*sigma^2)).n()) + sage: observed = lambda x, y: float(counter[x])/counter[y] + + sage: while not all(v in counter for v in (0, 1, -100)): add_samples(10000) + + sage: while abs(expected(0, 1) - observed(0, 1)) > 2e-1: add_samples(10000) + sage: while abs(expected(0, -100) - observed(0, -100)) > 2e-1: add_samples(10000) We construct a sampler with `c\%1 != 0`:: sage: from sage.stats.distributions.discrete_gaussian_integer import DiscreteGaussianDistributionIntegerSampler sage: sigma = 3 sage: D = DiscreteGaussianDistributionIntegerSampler(sigma=sigma, c=1/2) - sage: n=100000; l = [D() for _ in range(n)] # long time - sage: mean(l).n() # long time - 0.486650000000000 + sage: s = 0 + sage: n = 0 + sage: def add_samples(i): + ....: global s, n + ....: for _ in range(i): + ....: s += D() + ....: n += 1 + ....: + sage: add_samples(100000) + sage: while abs(float(s)/n - 0.5) > 5e-2: add_samples(10000) REFERENCES: @@ -227,52 +257,70 @@ cdef class DiscreteGaussianDistributionIntegerSampler(SageObject): We are testing correctness for multi-precision:: + sage: def add_samples(i): + ....: global mini, maxi, s, n + ....: for _ in range(i): + ....: x = D() + ....: s += x + ....: maxi = max(maxi, x) + ....: mini = min(mini, x) + ....: n += 1 + sage: from sage.stats.distributions.discrete_gaussian_integer import DiscreteGaussianDistributionIntegerSampler sage: D = DiscreteGaussianDistributionIntegerSampler(1.0, c=0, tau=2) - sage: l = [D() for _ in range(2^16)] - sage: min(l) == 0-2*1.0, max(l) == 0+2*1.0, abs(mean(l)) < 0.01 - (True, True, True) + sage: mini = 1000; maxi = -1000; s = 0; n = 0 + sage: add_samples(2^16) + sage: while mini != 0 - 2*1.0 or maxi != 0 + 2*1.0 or abs(float(s)/n) >= 0.01: + ....: add_samples(2^16) - sage: from sage.stats.distributions.discrete_gaussian_integer import DiscreteGaussianDistributionIntegerSampler sage: D = DiscreteGaussianDistributionIntegerSampler(1.0, c=2.5, tau=2) - sage: l = [D() for _ in range(2^18)] - sage: min(l)==2-2*1.0, max(l)==2+2*1.0, mean(l).n() - (True, True, 2.45...) + sage: mini = 1000; maxi = -1000; s = 0; n = 0 + sage: add_samples(2^16) + sage: while mini != 2 - 2*1.0 or maxi != 2 + 2*1.0 or abs(float(s)/n - 2.45) >= 0.01: + ....: add_samples(2^16) - sage: from sage.stats.distributions.discrete_gaussian_integer import DiscreteGaussianDistributionIntegerSampler sage: D = DiscreteGaussianDistributionIntegerSampler(1.0, c=2.5, tau=6) - sage: l = [D() for _ in range(2^18)] - sage: min(l), max(l), abs(mean(l)-2.5) < 0.01 - (-2, 7, True) + sage: mini = 1000; maxi = -1000; s = 0; n = 0 + sage: add_samples(2^18) + sage: while mini > 2 - 4*1.0 or maxi < 2 + 5*1.0 or abs(float(s)/n - 2.5) >= 0.01: # long time + ....: add_samples(2^18) We are testing correctness for double precision:: + sage: def add_samples(i): + ....: global mini, maxi, s, n + ....: for _ in range(i): + ....: x = D() + ....: s += x + ....: maxi = max(maxi, x) + ....: mini = min(mini, x) + ....: n += 1 + sage: from sage.stats.distributions.discrete_gaussian_integer import DiscreteGaussianDistributionIntegerSampler sage: D = DiscreteGaussianDistributionIntegerSampler(1.0, c=0, tau=2, precision="dp") - sage: l = [D() for _ in range(2^16)] - sage: min(l) == 0-2*1.0, max(l) == 0+2*1.0, abs(mean(l)) < 0.05 - (True, True, True) + sage: mini = 1000; maxi = -1000; s = 0; n = 0 + sage: add_samples(2^16) + sage: while mini != 0 - 2*1.0 or maxi != 0 + 2*1.0 or abs(float(s)/n) >= 0.05: + ....: add_samples(2^16) - sage: from sage.stats.distributions.discrete_gaussian_integer import DiscreteGaussianDistributionIntegerSampler sage: D = DiscreteGaussianDistributionIntegerSampler(1.0, c=2.5, tau=2, precision="dp") - sage: l = [D() for _ in range(2^18)] - sage: min(l)==2-2*1.0, max(l)==2+2*1.0, mean(l).n() - (True, True, 2.4...) + sage: mini = 1000; maxi = -1000; s = 0; n = 0 + sage: add_samples(2^16) + sage: while mini != 2 - 2*1.0 or maxi != 2 + 2*1.0 or abs(float(s)/n - 2.45) >= 0.01: + ....: add_samples(2^16) - sage: from sage.stats.distributions.discrete_gaussian_integer import DiscreteGaussianDistributionIntegerSampler sage: D = DiscreteGaussianDistributionIntegerSampler(1.0, c=2.5, tau=6, precision="dp") - sage: l = [D() for _ in range(2^18)] - sage: min(l)<=-1, max(l)>=6, abs(mean(l)-2.5) < 0.1 - (True, True, True) - sage: tuple(l.count(i) for i in range(-2,8)) # output random - (7, 242, 4519, 34120, 92714, 91700, 33925, 4666, 246, 5) + sage: mini = 1000; maxi = -1000; s = 0; n = 0 + sage: add_samples(2^16) + sage: while mini > -1 or maxi < 6 or abs(float(s)/n - 2.5) >= 0.1: + ....: add_samples(2^16) We plot a histogram:: sage: from sage.stats.distributions.discrete_gaussian_integer import DiscreteGaussianDistributionIntegerSampler sage: D = DiscreteGaussianDistributionIntegerSampler(17.0) sage: S = [D() for _ in range(2^16)] - sage: list_plot([(v,S.count(v)) for v in set(S)]) # long time + sage: list_plot([(v,S.count(v)) for v in set(S)]) # long time Graphics object consisting of 1 graphics primitive These generators cache random bits for performance reasons. Hence, resetting @@ -411,9 +459,9 @@ cdef class DiscreteGaussianDistributionIntegerSampler(SageObject): EXAMPLES:: sage: from sage.stats.distributions.discrete_gaussian_integer import DiscreteGaussianDistributionIntegerSampler - sage: DiscreteGaussianDistributionIntegerSampler(3.0, algorithm="uniform+online")() + sage: DiscreteGaussianDistributionIntegerSampler(3.0, algorithm="uniform+online")() # random -3 - sage: DiscreteGaussianDistributionIntegerSampler(3.0, algorithm="uniform+table")() + sage: DiscreteGaussianDistributionIntegerSampler(3.0, algorithm="uniform+table")() # random 3 TESTS:: diff --git a/src/sage/stats/distributions/discrete_gaussian_lattice.py b/src/sage/stats/distributions/discrete_gaussian_lattice.py index 0650f1822d3..d45da80c1ec 100644 --- a/src/sage/stats/distributions/discrete_gaussian_lattice.py +++ b/src/sage/stats/distributions/discrete_gaussian_lattice.py @@ -16,11 +16,13 @@ EXAMPLES:: - sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler - sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^10, 3.0) - sage: D(), D(), D() - ((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: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler + sage: D = 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 """ #****************************************************************************** # @@ -197,17 +199,32 @@ def _normalisation_factor_zz(self, tau=3): EXAMPLES:: sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler - sage: n = 3; sigma = 1.0; m = 1000 + sage: n = 3; sigma = 1.0 sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^n, sigma) sage: f = D.f sage: c = D._normalisation_factor_zz(); c 15.528... - sage: l = [D() for _ in range(m)] + sage: from collections import defaultdict + sage: counter = defaultdict(Integer) + sage: m = 0 + sage: def add_samples(i): + ....: global counter, m + ....: for _ in range(i): + ....: counter[D()] += 1 + ....: m += 1 + sage: v = vector(ZZ, n, (0, 0, 0)) - sage: l.count(v), ZZ(round(m*f(v)/c)) - (57, 64) + 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: add_samples(1000) + sage: v = vector(ZZ, n, (-1, 2, 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.2: add_samples(1000) # long time """ if self.B != identity_matrix(ZZ, self.B.nrows()): raise NotImplementedError("This function is only implemented when B is an identity matrix.") @@ -238,20 +255,30 @@ def __init__(self, B, sigma=1, c=None, precision=None): EXAMPLES:: sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler - sage: n = 2; sigma = 3.0; m = 5000 + sage: n = 2; sigma = 3.0 sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^n, sigma) sage: f = D.f sage: c = D._normalisation_factor_zz(); c 56.2162803067524 - sage: l = [D() for _ in range(m)] - sage: v = vector(ZZ, n, (-3,-3)) - sage: l.count(v), ZZ(round(m*f(v)/c)) - (39, 33) - - sage: target = vector(ZZ, n, (0,0)) - sage: l.count(target), ZZ(round(m*f(target)/c)) - (116, 89) + sage: from collections import defaultdict + sage: counter = defaultdict(Integer) + sage: m = 0 + sage: def add_samples(i): + ....: global counter, m + ....: for _ in range(i): + ....: counter[D()] += 1 + ....: m += 1 + + 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: add_samples(1000) + + 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: 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])) @@ -261,8 +288,8 @@ def __init__(self, B, sigma=1, c=None, precision=None): [2 1 1] [1 2 1] [1 1 2] - sage: D() - (0, 1, -1) + sage: D().parent() is D.c.parent() + True """ precision = DiscreteGaussianDistributionLatticeSampler.compute_precision(precision, sigma) @@ -323,14 +350,13 @@ def __call__(self): sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1,0,0)) sage: L = [D() for _ in range(2^12)] - sage: abs(mean(L).n() - D.c) - 0.08303258... + sage: abs(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).n() - D.c # long time - (0.0607910156250000, -0.128417968750000, 0.0239257812500000) - + sage: L = [D() for _ in range(2^12)] # long time + sage: abs(mean(L).n() - D.c) < 0.25 # long time + True """ if self._c_in_lattice: v = self._call_in_lattice() @@ -402,8 +428,8 @@ def _call_in_lattice(self): sage: from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionLatticeSampler sage: D = DiscreteGaussianDistributionLatticeSampler(ZZ^3, 3.0, c=(1,0,0)) sage: L = [D._call_in_lattice() for _ in range(2^12)] - sage: abs(mean(L).n() - D.c) - 0.08303258... + sage: abs(mean(L).n() - D.c) < 0.25 + True .. note:: @@ -420,9 +446,9 @@ def _call(self): 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).n() - D.c # long time - (-0.049..., -0.034..., -0.026...) + sage: L = [D._call() for _ in range(2^12)] # long time + sage: abs(mean(L).n() - D.c) < 0.25 # long time + True .. note:: diff --git a/src/sage/stats/distributions/discrete_gaussian_polynomial.py b/src/sage/stats/distributions/discrete_gaussian_polynomial.py index b160338a54b..ed68e68ffd2 100644 --- a/src/sage/stats/distributions/discrete_gaussian_polynomial.py +++ b/src/sage/stats/distributions/discrete_gaussian_polynomial.py @@ -17,8 +17,8 @@ sage: sigma = 3.0; n=1000 sage: l = [DiscreteGaussianDistributionPolynomialSampler(ZZ['x'], 64, sigma)() for _ in range(n)] sage: l = [vector(f).norm().n() for f in l] - sage: mean(l), sqrt(64)*sigma - (23.83..., 24.0...) + sage: mean(l), sqrt(64)*sigma # abs tol 5e-1 + (24.0, 24.0) """ #****************************************************************************** @@ -65,10 +65,13 @@ class DiscreteGaussianDistributionPolynomialSampler(SageObject): EXAMPLES:: sage: from sage.stats.distributions.discrete_gaussian_polynomial import DiscreteGaussianDistributionPolynomialSampler - sage: DiscreteGaussianDistributionPolynomialSampler(ZZ['x'], 8, 3.0)() - 3*x^7 + 3*x^6 - 3*x^5 - x^4 - 5*x^2 + 3 + sage: p = DiscreteGaussianDistributionPolynomialSampler(ZZ['x'], 8, 3.0)() + sage: p.parent() + Univariate Polynomial Ring in x over Integer Ring + sage: p.degree() < 8 + True sage: gs = DiscreteGaussianDistributionPolynomialSampler(ZZ['x'], 8, 3.0) - sage: [gs() for _ in range(3)] + sage: [gs() for _ in range(3)] # random [4*x^7 + 4*x^6 - 4*x^5 + 2*x^4 + x^3 - 4*x + 7, -5*x^6 + 4*x^5 - 3*x^3 + 4*x^2 + x, 2*x^7 + 2*x^6 + 2*x^5 - x^4 - 2*x^2 + 3*x + 1] .. automethod:: __init__ @@ -92,10 +95,13 @@ def __init__(self, P, n, sigma): EXAMPLES:: sage: from sage.stats.distributions.discrete_gaussian_polynomial import DiscreteGaussianDistributionPolynomialSampler - sage: DiscreteGaussianDistributionPolynomialSampler(ZZ['x'], 8, 3.0)() - 3*x^7 + 3*x^6 - 3*x^5 - x^4 - 5*x^2 + 3 + sage: p = DiscreteGaussianDistributionPolynomialSampler(ZZ['x'], 8, 3.0)() + sage: p.parent() + Univariate Polynomial Ring in x over Integer Ring + sage: p.degree() < 8 + True sage: gs = DiscreteGaussianDistributionPolynomialSampler(ZZ['x'], 8, 3.0) - sage: [gs() for _ in range(3)] + sage: [gs() for _ in range(3)] # random [4*x^7 + 4*x^6 - 4*x^5 + 2*x^4 + x^3 - 4*x + 7, -5*x^6 + 4*x^5 - 3*x^3 + 4*x^2 + x, 2*x^7 + 2*x^6 + 2*x^5 - x^4 - 2*x^2 + 3*x + 1] """ if isinstance(sigma, DiscreteGaussianDistributionIntegerSampler): @@ -113,8 +119,10 @@ def __call__(self): sage: from sage.stats.distributions.discrete_gaussian_polynomial import DiscreteGaussianDistributionPolynomialSampler sage: sampler = DiscreteGaussianDistributionPolynomialSampler(ZZ['x'], 8, 12.0) - sage: sampler() - 8*x^7 - 11*x^5 - 19*x^4 + 6*x^3 - 34*x^2 - 21*x + 9 + sage: sampler().parent() + Univariate Polynomial Ring in x over Integer Ring + sage: sampler().degree() <= 7 + True """ coeffs = [self.D() for _ in range(self.n)] return self.P(coeffs) diff --git a/src/sage/stats/hmm/chmm.pyx b/src/sage/stats/hmm/chmm.pyx index 46458572747..bd6cf9159d6 100644 --- a/src/sage/stats/hmm/chmm.pyx +++ b/src/sage/stats/hmm/chmm.pyx @@ -104,31 +104,45 @@ cdef class GaussianHiddenMarkovModel(HiddenMarkovModel): [0.5000, 0.5000] We obtain a sample sequence with 10 entries in it, and compute the - logarithm of the probability of obtaining his sequence, given the + logarithm of the probability of obtaining this sequence, given the model:: - sage: obs = m.sample(10); obs - [-1.6835, 0.0635, -2.1688, 0.3043, -0.3188, -0.7835, 1.0398, -1.3558, 1.0882, 0.4050] - sage: m.log_likelihood(obs) - -15.2262338077988... + sage: obs = m.sample(5); obs # random + [-1.6835, 0.0635, -2.1688, 0.3043, -0.3188] + sage: log_likelihood = m.log_likelihood(obs) + sage: counter = 0 + sage: n = 0 + sage: def add_samples(i): + ....: global counter, n + ....: for _ in range(i): + ....: n += 1 + ....: obs2 = m.sample(5) + ....: if all(abs(obs2[i] - obs[i]) < 0.25 for i in range(5)): + ....: counter += 1 + + sage: add_samples(10000) + sage: while abs(log_likelihood - log(counter*1.0/n/0.5^5)) < 0.1: + ....: add_samples(10000) We compute the Viterbi path, and probability that the given path of states produced obs:: - sage: m.viterbi(obs) - ([1, 0, 1, 0, 1, 1, 0, 1, 0, 1], -16.67738270170788) + sage: m.viterbi(obs) # random + ([1, 0, 1, 0, 1], -8.714092684611794) We use the Baum-Welch iterative algorithm to find another model for which our observation sequence is more likely:: - sage: m.baum_welch(obs) - (-10.6103334957397..., 14) - sage: m.log_likelihood(obs) - -10.6103334957397... + sage: try: + ....: p, s = m.baum_welch(obs) + ....: assert p > log_likelihood + ....: assert (4 <= s < 200) + ....: except RuntimeError: + ....: pass Notice that running Baum-Welch changed our model:: - sage: m # rel tol 3e-14 + sage: m # random Gaussian Hidden Markov Model with 2 States Transition matrix: [ 0.4154981366185841 0.584501863381416] @@ -340,7 +354,7 @@ cdef class GaussianHiddenMarkovModel(HiddenMarkovModel): EXAMPLES:: sage: m = hmm.GaussianHiddenMarkovModel([[.1,.9],[.5,.5]], [(1,.5), (-1,3)], [.1,.9]) - sage: m.generate_sequence(5) + sage: m.generate_sequence(5) # random ([-3.0505, 0.5317, -4.5065, 0.6521, 1.0435], [1, 0, 1, 0, 1]) sage: m.generate_sequence(0) ([], []) @@ -349,6 +363,20 @@ cdef class GaussianHiddenMarkovModel(HiddenMarkovModel): ... ValueError: length must be nonnegative + Verify numerically that the starting state is 0 with probability about 0.1:: + + sage: counter = 0 + sage: n = 0 + sage: def add_samples(i): + ....: global counter, n + ....: for i in range(i): + ....: n += 1 + ....: if m.generate_sequence(1)[1][0] == 0: + ....: counter += 1 + + sage: add_samples(10^5) + sage: while abs(counter*1.0 / n - 0.1) > 0.01: add_samples(10^5) + Example in which the starting state is 0 (see :trac:`11452`):: sage: set_random_seed(23); m.generate_sequence(2) @@ -358,13 +386,6 @@ cdef class GaussianHiddenMarkovModel(HiddenMarkovModel): sage: set_random_seed(23); m.generate_sequence(2, starting_state=1) ([-3.1491, -1.0244], [1, 1]) - - Verify numerically that the starting state is 0 with probability about 0.1:: - - sage: set_random_seed(0) - sage: v = [m.generate_sequence(1)[1][0] for i in range(10^5)] - sage: 1.0 * v.count(int(0)) / len(v) - 0.0998200000000000 """ if length < 0: raise ValueError("length must be nonnegative") @@ -518,9 +539,9 @@ cdef class GaussianHiddenMarkovModel(HiddenMarkovModel): sage: m = hmm.GaussianHiddenMarkovModel([[.1,.9],[.5,.5]], [(1,.5), (-1,3)], [.1,.9]) sage: m.log_likelihood([1,1,1]) -4.297880766072486 - sage: set_random_seed(0); s = m.sample(20) - sage: m.log_likelihood(s) - -40.115714129484... + sage: s = m.sample(20) + sage: -80 < m.log_likelihood(s) < -20 + True """ if len(obs) == 0: return 1.0 @@ -881,7 +902,10 @@ cdef class GaussianHiddenMarkovModel(HiddenMarkovModel): sage: m = hmm.GaussianHiddenMarkovModel([[.1,.9],[.5,.5]], [(1,.5), (-1,3)], [.1,.9]) sage: v = m.sample(10) - sage: stats.TimeSeries([m.baum_welch(v,max_iter=1)[0] for _ in range(len(v))]) + sage: l = stats.TimeSeries([m.baum_welch(v,max_iter=1)[0] for _ in range(len(v))]) + sage: all(l[i] < l[i+1] for i in range(9)) + True + sage: l # random [-20.1167, -17.7611, -16.9814, -16.9364, -16.9314, -16.9309, -16.9309, -16.9309, -16.9309, -16.9309] We illustrate fixing emissions:: diff --git a/src/sage/stats/hmm/distributions.pyx b/src/sage/stats/hmm/distributions.pyx index 7c31b1032d5..eb744790558 100644 --- a/src/sage/stats/hmm/distributions.pyx +++ b/src/sage/stats/hmm/distributions.pyx @@ -397,14 +397,23 @@ cdef class GaussianMixtureDistribution(Distribution): EXAMPLES:: sage: P = hmm.GaussianMixtureDistribution([(.2,-10,.5),(.6,1,1),(.2,20,.5)]) - sage: P.sample() - 19.65824361087513 - sage: P.sample(1) - [-10.4683] - sage: P.sample(5) - [-0.1688, -10.3479, 1.6812, 20.1083, -9.9801] - sage: P.sample(0) - [] + sage: type(P.sample()) + + sage: l = P.sample(1) + sage: len(l) + 1 + sage: type(l) + + sage: l = P.sample(5) + sage: len(l) + 5 + sage: type(l) + + sage: l = P.sample(0) + sage: len(l) + 0 + sage: type(l) + sage: P.sample(-3) Traceback (most recent call last): ... diff --git a/src/sage/structure/sage_object_test.py b/src/sage/structure/sage_object_test.py new file mode 100644 index 00000000000..0922545a62c --- /dev/null +++ b/src/sage/structure/sage_object_test.py @@ -0,0 +1,17 @@ + +import pytest +from .sage_object import SageObject + +class SageObjectTests: + + @pytest.fixture + def sage_object(self, *args, **kwargs) -> SageObject: + raise NotImplementedError + + def test_sage_unittest_testsuite(self, sage_object: SageObject): + """ + Subclasses should override this method if they need to skip some tests. + """ + # TODO: Remove this test as soon as all old test methods are migrated + from sage.misc.sage_unittest import TestSuite + TestSuite(sage_object).run(verbose=True, raise_on_failure=True) diff --git a/src/sage/symbolic/relation.py b/src/sage/symbolic/relation.py index 0be478daff0..093086d2c53 100644 --- a/src/sage/symbolic/relation.py +++ b/src/sage/symbolic/relation.py @@ -1226,6 +1226,13 @@ def _solve_expression(f, x, explicit_solutions, multiplicities, x == (-0.809857800594 + 0.262869645851*I), x == (0.617093477784 + 0.900864951949*I), x == (-0.363623519329 + 0.952561195261*I)] + + :trac:`31452` fixed:: + + sage: solve([x==3], [x], solution_dict=True) + [{x: 3}] + sage: solve([x==3], [x], solution_dict=True, algorithm='sympy') + [{x: 3}] """ from sage.symbolic.ring import is_SymbolicVariable if f.is_relational(): @@ -1276,7 +1283,10 @@ def has_integer_assumption(v): ret = solveset(ex._sympy_(), sympy_vars[0], S.Reals) else: ret = solveset(ex._sympy_(), sympy_vars[0]) - return sympy_set_to_list(ret, sympy_vars) + ret = sympy_set_to_list(ret, sympy_vars) + if solution_dict: + ret = [{sol.left(): sol.right()} for sol in ret] + return ret # from here on, maxima is used for solution m = ex._maxima_() @@ -1364,10 +1374,10 @@ def has_integer_assumption(v): continue if solution_dict: - if isinstance(x, (list, tuple)): - X = [{sol.left():sol.right() for sol in b} for b in X] + if isinstance(x, (list, tuple)) and len(x) > 1: + X = [{sol.left(): sol.right() for sol in b} for b in X] else: - X = [dict([[sol.left(),sol.right()]]) for sol in X] + X = [{sol.left(): sol.right()} for sol in X] if multiplicities: return X, ret_multiplicities diff --git a/src/sage/tests/numpy.py b/src/sage/tests/numpy.py new file mode 100644 index 00000000000..8c1c42f385c --- /dev/null +++ b/src/sage/tests/numpy.py @@ -0,0 +1,19 @@ +r""" +TESTS: + +Sage integers can index NumPy matrices (see :trac:`10928`):: + + sage: import numpy as np + sage: m = np.matrix(np.arange(4).reshape(2, 2)) + sage: a = m[:, int(0)] + sage: b = m[:, Integer(0)] + sage: a.shape, b.shape + ((2, 1), (2, 1)) + sage: print('{}\n{}\n{}'.format(m, a, b)) + [[0 1] + [2 3]] + [[0] + [2]] + [[0] + [2]] +""" diff --git a/src/sage/version.py b/src/sage/version.py index fd6e15e31ff..3289093f8e6 100644 --- a/src/sage/version.py +++ b/src/sage/version.py @@ -1,5 +1,5 @@ # Sage version information for Python scripts # This file is auto-generated by the sage-update-version script, do not edit! -version = '9.3' -date = '2021-05-09' -banner = 'SageMath version 9.3, Release Date: 2021-05-09' +version = '9.4.beta0' +date = '2021-05-25' +banner = 'SageMath version 9.4.beta0, Release Date: 2021-05-25' diff --git a/src/setup.cfg b/src/setup.cfg deleted file mode 100644 index b062dc5c896..00000000000 --- a/src/setup.cfg +++ /dev/null @@ -1,26 +0,0 @@ -[metadata] -name = sagemath-standard -version = file: VERSION.txt -description = Sage: Open Source Mathematics Software: Standard Python Library -long_description = file: README.rst -long_description_content_type = text/x-rst -license = GNU General Public License (GPL) v2 or later -license_file = LICENSE.txt -author = The Sage Developers -author_email = sage-support@googlegroups.com -url = https://www.sagemath.org - -classifiers = - Development Status :: 6 - Mature - Intended Audience :: Education - Intended Audience :: Science/Research - License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+) - Operating System :: POSIX - Operating System :: MacOS :: MacOS X - Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: Implementation :: CPython - Topic :: Scientific/Engineering :: Mathematics diff --git a/src/setup.cfg.m4 b/src/setup.cfg.m4 new file mode 100644 index 00000000000..12039bf1d93 --- /dev/null +++ b/src/setup.cfg.m4 @@ -0,0 +1,72 @@ +# -*- conf-unix -*- +[metadata] +name = sagemath-standard +version = file: VERSION.txt +description = Sage: Open Source Mathematics Software: Standard Python Library +long_description = file: README.rst +long_description_content_type = text/x-rst +license = GNU General Public License (GPL) v2 or later +license_file = LICENSE.txt +author = The Sage Developers +author_email = sage-support@googlegroups.com +url = https://www.sagemath.org + +classifiers = + Development Status :: 6 - Mature + Intended Audience :: Education + Intended Audience :: Science/Research + License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+) + Operating System :: POSIX + Operating System :: MacOS :: MacOS X + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: Implementation :: CPython + Topic :: Scientific/Engineering :: Mathematics + +[options] +python_requires = >=3.7, <3.10 +install_requires = + sage_conf + esyscmd(`sage-get-system-packages install-requires \ + six \ + | sed "2,\$s/^/ /;"')dnl +dnl From build/pkgs/sagelib/dependencies + esyscmd(`sage-get-system-packages install-requires \ + cypari \ + cysignals \ + cython \ + gmpy2 \ + jinja2 \ + jupyter_core \ + numpy \ + pkgconfig \ + pplpy \ + | sed "2,\$s/^/ /;"')dnl +dnl From Makefile.in: SAGERUNTIME + esyscmd(`sage-get-system-packages install-requires \ + ipython \ + pexpect \ + psutil \ + | sed "2,\$s/^/ /;"')dnl +dnl From Makefile.in: DOC_DEPENDENCIES + esyscmd(`sage-get-system-packages install-requires \ + sphinx \ + networkx \ + scipy \ + sympy \ + matplotlib \ + pillow \ + mpmath \ + ipykernel \ + jupyter_client \ + ipywidgets \ + | sed "2,\$s/^/ /;"')dnl +dnl Other Python packages that are standard spkg, used in doctests + esyscmd(`sage-get-system-packages install-requires \ + cvxopt \ + rpy2 \ + fpylll \ + | sed "2,\$s/^/ /;"')dnl +dnl pycryptosat # Sage distribution installs it as part of cryptominisat. According to its README on https://pypi.org/project/pycryptosat/: "The pycryptosat python package compiles while compiling CryptoMiniSat. It cannot be compiled on its own, it must be compiled at the same time as CryptoMiniSat." diff --git a/tox.ini b/tox.ini index 702c8be311e..cc17f99d4c8 100644 --- a/tox.ini +++ b/tox.ini @@ -209,6 +209,14 @@ setenv = fedora-33: BASE_TAG=33 fedora-34: BASE_TAG=34 # + # https://hub.docker.com/r/scientificlinux/sl + # + scientificlinux: SYSTEM=fedora + scientificlinux: BASE_IMAGE=scientificlinux/sl + scientificlinux: IGNORE_MISSING_SYSTEM_PACKAGES=yes + scientificlinux-6: BASE_TAG=6 + scientificlinux-7: BASE_TAG=7 + # # https://hub.docker.com/_/centos # centos-6 only has autoconf 2.63 -- too old for bootstrap; download configure tarball instead. # @@ -261,7 +269,7 @@ setenv = # https://hub.docker.com/r/opensuse/leap # - OpenSUSE Leap 42 was superseded by the Leap 15 series. # - OpenSUSE Leap 15.2 released July 2, 2020. - # - As of 2020-11-16, latest = 15 = 15.2 + # - As of 2021-05-06, latest = 15 = 15.2 # - OpenSUSE Leap 15.3 planned to be released July 7, 2021 # https://hub.docker.com/r/opensuse/tumbleweed # - Rolling distribution @@ -272,6 +280,7 @@ setenv = opensuse-15.0: BASE_TAG=15.0 opensuse-15.1: BASE_TAG=15.1 opensuse-15.2: BASE_TAG=15.2 + opensuse-15.3: BASE_TAG=15.3 opensuse-15: BASE_TAG=15 opensuse-tumbleweed: BASE_IMAGE=opensuse/tumbleweed # @@ -365,10 +374,16 @@ setenv = local-sudo: __SUDO=--sudo local-root: CONFIG_CONFIGURE_ARGS_ROOT=--enable-build-as-root # brew caches downloaded files in ${HOME}/Library/Caches. We share it between different toxenvs. - local-homebrew: HOMEBREW={envdir}/homebrew - local-homebrew-usrlocal: HOMEBREW=/usr/local + local-homebrew: HOMEBREW={envdir}/homebrew + local-{homebrew-usrlocal,nohomebrew}: HOMEBREW=/usr/local + # local-macos-nohomebrew: "best effort" isolation to avoid using a homebrew installation in /usr/local + # We use liblzma from the macOS system - which is available but its headers are not (neither is the xz executable). + # So we use /usr/local/opt/xz/{bin,include} (but not lib!). + # This ensures that /usr/bin/python3 is accepted by configure - this is needed until #30948 is done. + local-macos-nohomebrew: PATH={env:HOMEBREW}/opt/xz/bin:{env:HOMEBREW}/opt/gpatch/bin:/usr/bin:/bin:/usr/sbin:/sbin + local-macos-nohomebrew: CPATH={env:HOMEBREW}/opt/xz/include local-homebrew: PATH={env:HOMEBREW}/bin:/usr/bin:/bin:/usr/sbin:/sbin - local-homebrew-nokegonly: BOOTSTRAP=ACLOCAL_PATH="$HOMEBREW/opt/gettext/share/aclocal:$ACLOCAL_PATH" PATH="$HOMEBREW/opt/gettext/bin/:$PATH" ./bootstrap + local-{homebrew-nokegonly,nohomebrew}: BOOTSTRAP=ACLOCAL_PATH="$HOMEBREW/opt/gettext/share/aclocal:$ACLOCAL_PATH" PATH="$HOMEBREW/opt/gettext/bin/:$HOMEBREW/bin:$PATH" ./bootstrap local-homebrew-!nokegonly: SETENV=. .homebrew-build-env # local-conda: CONDA_PREFIX={envdir}/conda @@ -392,6 +407,8 @@ setenv = python3: CONFIG_CONFIGURE_ARGS_1= python3_spkg: CONFIG_CONFIGURE_ARGS_1=--without-system-python3 macos-python3_xcode: CONFIG_CONFIGURE_ARGS_1=--with-system-python3=force --with-python=/usr/bin/python3 + macos-{python3_xcode,nohomebrew}-python3.7: CONFIG_CONFIGURE_ARGS_1=--with-system-python3=force --with-python=/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.7/bin/python3 + macos-{python3_xcode,nohomebrew}-python3.8: CONFIG_CONFIGURE_ARGS_1=--with-system-python3=force --with-python=/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.8/bin/python3 # Must manually download and install from https://www.python.org/ftp/python/3.7.7/python-3.7.7-macosx10.9.pkg macos-python3_pythonorg: CONFIG_CONFIGURE_ARGS_1=--with-system-python3=force --with-python=/Library/Frameworks/Python.framework/Versions/3.7/bin/python3 # Homebrew keg installs @@ -400,7 +417,6 @@ setenv = homebrew-python3.9: CONFIG_CONFIGURE_ARGS_1=--with-system-python3=force --with-python={env:HOMEBREW}/opt/python@3.9/bin/python3 # https://github.com/pypa/manylinux manylinux-standard: CONFIG_CONFIGURE_ARGS_1=--with-system-python3=force --with-python=/opt/python/cp38-cp38/bin/python3 - manylinux-python3.6: CONFIG_CONFIGURE_ARGS_1=--with-system-python3=force --with-python=/opt/python/cp36-cp36m/bin/python3 manylinux-python3.7: CONFIG_CONFIGURE_ARGS_1=--with-system-python3=force --with-python=/opt/python/cp37-cp37m/bin/python3 manylinux-python3.8: CONFIG_CONFIGURE_ARGS_1=--with-system-python3=force --with-python=/opt/python/cp38-cp38/bin/python3 manylinux-python3.9: CONFIG_CONFIGURE_ARGS_1=--with-system-python3=force --with-python=/opt/python/cp39-cp39/bin/python3 @@ -411,6 +427,23 @@ setenv = gcc_9: CONFIG_CONFIGURE_ARGS_2=--with-system-gcc=force CC=gcc-9 CXX=g++-9 FC=gfortran-9 gcc_10: CONFIG_CONFIGURE_ARGS_2=--with-system-gcc=force CC=gcc-10 CXX=g++-10 FC=gfortran-10 gcc_11: CONFIG_CONFIGURE_ARGS_2=--with-system-gcc=force CC=gcc-11 CXX=g++-11 FC=gfortran-11 + macos-nohomebrew: CONFIG_CONFIGURE_ARGS_2=--with-system-gcc=force CC="$CONFIGURED_CC" CXX="$CONFIGURED_CXX" --with-mp=gmp --without-system-mpfr --without-system-readline --without-system-boost --without-system-boost_cropped + macos-nohomebrew: CONFIGURED_CXX=g++ -isysroot {env:MACOS_SDK} + macos-nohomebrew: CONFIGURED_CC=gcc -isysroot {env:MACOS_SDK} + # Prevent /usr/local to leak in: + # - We use libgd only used in a very limited way, in {matrix,vector}_mod_2_dense. Disable search for other packages. + macos-nohomebrew: LIBGD_CONFIGURE=--without-freetype --without-raqm --without-fontconfig --without-jpeg --without-liq --without-xpm --without-tiff --without-webp --without-heif --without-avif + macos-nohomebrew: FREETYPE_CONFIGURE=--without-harfbuzz + macos-nohomebrew: PILLOW_BUILD_EXT=--disable-platform-guessing --disable-jpeg2000 --disable-imagequant --disable-tiff --disable-lcms --disable-webp --disable-webpmux --disable-xcb + macos-nohomebrew: ZLIB_ROOT={env:MACOS_SDK}/usr + macos: MACOS_SDK=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk + # python3 from XCode 12 has MACOSX_DEPLOYMENT_TARGET=10.14.6. Selecting a lower target would cause /usr/bin/python3 to be rejected by configure. + macos-10.14: MACOS_SDK=/Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk + macos-10.14: MACOSX_DEPLOYMENT_TARGET=10.14.6 + macos-10.15: MACOS_SDK=/Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk + macos-10.15: MACOSX_DEPLOYMENT_TARGET=10.15 + macos-11.1: MACOS_SDK=/Library/Developer/CommandLineTools/SDKs/MacOSX11.1.sdk + macos-11.1: MACOSX_DEPLOYMENT_TARGET=11.1 # # Resulting full configuration args, including EXTRA_CONFIGURE_ARGS from the user environment #