diff --git a/.homebrew-build-env b/.homebrew-build-env index a3d58b57ba9..29f0ba3b304 100644 --- a/.homebrew-build-env +++ b/.homebrew-build-env @@ -31,7 +31,7 @@ for l in readline bzip2 ntl; do CPATH="$HOMEBREW/opt/$l/include:$CPATH" fi done -for l in "gcc/lib/gcc/10 gcc/lib/gcc/9"; do +for l in "gcc/lib/gcc/11 gcc/lib/gcc/10 gcc/lib/gcc/9"; do if [ -d "$HOMEBREW/opt/$l" ]; then LIBRARY_PATH="$HOMEBREW/opt/$l:$LIBRARY_PATH" break diff --git a/.zenodo.json b/.zenodo.json index d0132ce162d..618a0985132 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.4.beta5", - "version": "9.4.beta5", + "title": "sagemath/sage: 9.4.beta6", + "version": "9.4.beta6", "upload_type": "software", - "publication_date": "2021-07-18", + "publication_date": "2021-07-24", "creators": [ { "affiliation": "SageMath.org", @@ -15,7 +15,7 @@ "related_identifiers": [ { "scheme": "url", - "identifier": "https://github.com/sagemath/sage/tree/9.4.beta5", + "identifier": "https://github.com/sagemath/sage/tree/9.4.beta6", "relation": "isSupplementTo" }, { diff --git a/VERSION.txt b/VERSION.txt index 9aeb663dea5..ba6c89c9a88 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -SageMath version 9.4.beta5, Release Date: 2021-07-18 +SageMath version 9.4.beta6, Release Date: 2021-07-24 diff --git a/build/bin/write-dockerfile.sh b/build/bin/write-dockerfile.sh index 93ba22a4dec..05cfe124966 100755 --- a/build/bin/write-dockerfile.sh +++ b/build/bin/write-dockerfile.sh @@ -8,13 +8,14 @@ shopt -s extglob SAGE_PACKAGE_LIST_ARGS="${2:- --has-file=spkg-configure.m4 :standard:}" WITH_SYSTEM_SPKG="${3:-yes}" IGNORE_MISSING_SYSTEM_PACKAGES="${4:-no}" +EXTRA_SAGE_PACKAGES="${5:-_bootstrap}" # STRIP_COMMENTS="sed s/#.*//;" SAGE_ROOT=. export PATH="$SAGE_ROOT"/build/bin:$PATH SYSTEM_PACKAGES= CONFIGURE_ARGS="--enable-option-checking " -for PKG_BASE in $($SAGE_ROOT/sage -package list --has-file=distros/$SYSTEM.txt $SAGE_PACKAGE_LIST_ARGS) _bootstrap; do +for PKG_BASE in $($SAGE_ROOT/sage -package list --has-file=distros/$SYSTEM.txt $SAGE_PACKAGE_LIST_ARGS) $EXTRA_SAGE_PACKAGES; do PKG_SCRIPTS="$SAGE_ROOT"/build/pkgs/$PKG_BASE if [ -d $PKG_SCRIPTS ]; then SYSTEM_PACKAGES_FILE=$PKG_SCRIPTS/distros/$SYSTEM.txt diff --git a/build/pkgs/_prereq/distros/opensuse.txt b/build/pkgs/_prereq/distros/opensuse.txt index fe5391d0314..f3bfb123e1c 100644 --- a/build/pkgs/_prereq/distros/opensuse.txt +++ b/build/pkgs/_prereq/distros/opensuse.txt @@ -15,7 +15,10 @@ perl python3 tar bc +which +glibc-locale-base gcc +gcc-c++ # Needed if we download some packages from a https upstream URL ca-certificates # gunzip needed for ppl spkg diff --git a/build/pkgs/arb/package-version.txt b/build/pkgs/arb/package-version.txt index ef0f38abe16..0d13ee7427e 100644 --- a/build/pkgs/arb/package-version.txt +++ b/build/pkgs/arb/package-version.txt @@ -1 +1 @@ -2.19.0 +2.19.0.p0 diff --git a/build/pkgs/arb/patches/arb-flint-2.8-compatibility-b6c8032e2da1b19eb7c5a5f5c2f3372643e3d170.patch b/build/pkgs/arb/patches/arb-flint-2.8-compatibility-b6c8032e2da1b19eb7c5a5f5c2f3372643e3d170.patch new file mode 100644 index 00000000000..5e5e1b0d19f --- /dev/null +++ b/build/pkgs/arb/patches/arb-flint-2.8-compatibility-b6c8032e2da1b19eb7c5a5f5c2f3372643e3d170.patch @@ -0,0 +1,46 @@ +From b6c8032e2da1b19eb7c5a5f5c2f3372643e3d170 Mon Sep 17 00:00:00 2001 +From: fredrik +Date: Mon, 15 Mar 2021 11:56:24 +0100 +Subject: [PATCH] compatibility fix for latest flint + +--- + acb_modular/epsilon_arg.c | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/acb_modular/epsilon_arg.c b/acb_modular/epsilon_arg.c +index e1332725..b42a64ad 100644 +--- a/acb_modular/epsilon_arg.c ++++ b/acb_modular/epsilon_arg.c +@@ -12,7 +12,7 @@ + #include "acb_modular.h" + + static int +-fmpz_kronecker(const fmpz_t a, const fmpz_t b) ++fmpz_kronecker1(const fmpz_t a, const fmpz_t b) + { + if (fmpz_sgn(b) < 0) + { +@@ -20,7 +20,7 @@ fmpz_kronecker(const fmpz_t a, const fmpz_t b) + fmpz_t t; + fmpz_init(t); + fmpz_neg(t, b); +- r = fmpz_kronecker(a, t); ++ r = fmpz_kronecker1(a, t); + fmpz_clear(t); + return r; + } +@@ -58,12 +58,12 @@ acb_modular_epsilon_arg(const psl2z_t g) + + if (cc % 2 == 1) + { +- u = fmpz_kronecker(a, c); ++ u = fmpz_kronecker1(a, c); + aa = aa*bb + 2*aa*cc - 3*cc + cc*dd*(1-aa*aa); + } + else + { +- u = fmpz_kronecker(c, a); ++ u = fmpz_kronecker1(c, a); + aa = aa*bb - aa*cc + 3*aa - 3 + cc*dd*(1-aa*aa); + } + diff --git a/build/pkgs/configure/checksums.ini b/build/pkgs/configure/checksums.ini index cef1bce4364..3bb9983d8ea 100644 --- a/build/pkgs/configure/checksums.ini +++ b/build/pkgs/configure/checksums.ini @@ -1,4 +1,4 @@ tarball=configure-VERSION.tar.gz -sha1=70b46d821bd601ddf3e6b6d6bfcfb288d81018d1 -md5=5d83741b640f1862df88c4d12bdc7220 -cksum=150019468 +sha1=c55867da552d5ed30328a35a480dc1ab14ec232c +md5=5cbace690cae40b89993e3a71fe4200c +cksum=912186647 diff --git a/build/pkgs/configure/package-version.txt b/build/pkgs/configure/package-version.txt index 2bae6d02a89..3e74f1098db 100644 --- a/build/pkgs/configure/package-version.txt +++ b/build/pkgs/configure/package-version.txt @@ -1 +1 @@ -5e648d88fead1ead94ca608e60c49c156e2291aa +25ff4f4daf3cd93053f7b8c1e39117ce67e96b6c diff --git a/build/pkgs/deformation/SPKG.rst b/build/pkgs/deformation/SPKG.rst index 2f9a2675dae..71b821459e2 100644 --- a/build/pkgs/deformation/SPKG.rst +++ b/build/pkgs/deformation/SPKG.rst @@ -16,4 +16,7 @@ GLPv3 Upstream Contact ---------------- -- Sebastian Pancratz: sebastian.pancratz@gmail.com +- Sebastian Pancratz: sebastian.pancratz@gmail.com, sage-devel@googlegroups.com + +- We use the fork at https://github.com/sagemath/deformation + the fork uses GMP instead of MPIR, and Flint 2.7+. diff --git a/build/pkgs/deformation/checksums.ini b/build/pkgs/deformation/checksums.ini index 993b9e6ff2e..a0996128077 100644 --- a/build/pkgs/deformation/checksums.ini +++ b/build/pkgs/deformation/checksums.ini @@ -1,4 +1,5 @@ tarball=deformation-VERSION.tar.bz2 -sha1=317fb76c884fa4b6b92ed0b171a0b9fdb3bdc90f -md5=e4af9b93ddc85ebb52d9fa1fedd75887 -cksum=4134074975 +sha1=0f5fd78a91da207d06b5be59bf466f16c2614eda +md5=e2c365e20778117d402fb664fc145d72 +cksum=3789646827 +upstream_url=https://github.com/sagemath/deformation/archive/refs/tags/VERSION.tar.gz diff --git a/build/pkgs/deformation/package-version.txt b/build/pkgs/deformation/package-version.txt index 036881eb8b0..891293d2df1 100644 --- a/build/pkgs/deformation/package-version.txt +++ b/build/pkgs/deformation/package-version.txt @@ -1 +1 @@ -d05941b.p0 +20210503 diff --git a/build/pkgs/flint/spkg-configure.m4 b/build/pkgs/flint/spkg-configure.m4 index de3ad59cc2f..7b6374eaf7d 100644 --- a/build/pkgs/flint/spkg-configure.m4 +++ b/build/pkgs/flint/spkg-configure.m4 @@ -1,14 +1,8 @@ SAGE_SPKG_CONFIGURE([flint], [ - AC_REQUIRE([SAGE_SPKG_CONFIGURE_MPFR]) - AC_REQUIRE([SAGE_SPKG_CONFIGURE_NTL]) - AC_MSG_CHECKING([installing mpfr or ntl? ]) - if test x$sage_spkg_install_mpfr = xyes -o x$sage_spkg_install_ntl = xyes; then - AC_MSG_RESULT([yes; install flint as well]) - sage_spkg_install_flint=yes - else + SAGE_SPKG_DEPCHECK([mpfr ntl], [ AC_CHECK_HEADER(flint/flint.h, [ - dnl fmpz_mat_is_hadamard appears in Flint 2.5.0 - AC_SEARCH_LIBS([fmpz_mat_is_hadamard], [flint], [ + dnl fmpz_mod_ctx_init appears in Flint 2.6.0 + AC_SEARCH_LIBS([fmpz_mod_ctx_init], [flint], [ dnl check that NTL is linked in AC_SEARCH_LIBS([fmpz_poly_get_ZZX], [flint], [ @@ -27,13 +21,11 @@ SAGE_SPKG_CONFIGURE([flint], [ ], [sage_spkg_install_flint=yes]) ], [sage_spkg_install_flint=yes]) ], [sage_spkg_install_flint=yes]) - fi + ]) ], [], [], [ if test x$sage_spkg_install_flint = xyes; then AC_SUBST(SAGE_FLINT_PREFIX, ['$SAGE_LOCAL']) - AC_MSG_RESULT([using Sage's flint SPKG]) else AC_SUBST(SAGE_FLINT_PREFIX, ['']) - AC_MSG_RESULT([using flint library from the system]) fi ]) diff --git a/build/pkgs/gcc/spkg-configure.m4 b/build/pkgs/gcc/spkg-configure.m4 index eb90e6e2c40..4ccbfbce6ca 100644 --- a/build/pkgs/gcc/spkg-configure.m4 +++ b/build/pkgs/gcc/spkg-configure.m4 @@ -149,8 +149,8 @@ SAGE_SPKG_CONFIGURE_BASE([gcc], [ # Install our own GCC if the system-provided one is older than gcc-4.8. SAGE_SHOULD_INSTALL_GCC([you have $CXX version $GXX_VERSION, which is quite old]) ], - [1[[1-9]].*], [ - # Install our own GCC if the system-provided one is newer than 10.x. + [1[[2-9]].*], [ + # Install our own GCC if the system-provided one is newer than 11.x. # See https://trac.sagemath.org/ticket/29456 SAGE_SHOULD_INSTALL_GCC([$CXX is g++ version $GXX_VERSION, which is too recent for this version of Sage]) ], diff --git a/build/pkgs/libxml2/spkg-install b/build/pkgs/libxml2/spkg-install old mode 100644 new mode 100755 diff --git a/build/pkgs/llvm/SPKG.rst b/build/pkgs/llvm/SPKG.rst new file mode 100644 index 00000000000..03cf6f1a7c6 --- /dev/null +++ b/build/pkgs/llvm/SPKG.rst @@ -0,0 +1,22 @@ +llvm: The LLVM Compiler Infrastructure, including the Clang C/C++/Objective-C compiler +====================================================================================== + +Description +----------- + +The LLVM Project is a collection of modular and reusable compiler and toolchain technologies. + +Clang is an "LLVM native" C/C++/Objective-C compiler. + +The libc++ and libc++ ABI projects provide a standard conformant and high-performance +implementation of the C++ Standard Library, including full support for C++11 and C++14. + +License +------- + +Apache 2.0 License with LLVM exceptions + +Upstream Contact +---------------- + +https://llvm.org/ diff --git a/build/pkgs/llvm/distros/alpine.txt b/build/pkgs/llvm/distros/alpine.txt new file mode 100644 index 00000000000..e671fa21003 --- /dev/null +++ b/build/pkgs/llvm/distros/alpine.txt @@ -0,0 +1 @@ +clang diff --git a/build/pkgs/llvm/distros/arch.txt b/build/pkgs/llvm/distros/arch.txt new file mode 100644 index 00000000000..e671fa21003 --- /dev/null +++ b/build/pkgs/llvm/distros/arch.txt @@ -0,0 +1 @@ +clang diff --git a/build/pkgs/llvm/distros/cygwin.txt b/build/pkgs/llvm/distros/cygwin.txt new file mode 100644 index 00000000000..e671fa21003 --- /dev/null +++ b/build/pkgs/llvm/distros/cygwin.txt @@ -0,0 +1 @@ +clang diff --git a/build/pkgs/llvm/distros/debian.txt b/build/pkgs/llvm/distros/debian.txt new file mode 100644 index 00000000000..d9c90515cfc --- /dev/null +++ b/build/pkgs/llvm/distros/debian.txt @@ -0,0 +1 @@ +llvm-toolchain diff --git a/build/pkgs/llvm/distros/fedora.txt b/build/pkgs/llvm/distros/fedora.txt new file mode 100644 index 00000000000..e671fa21003 --- /dev/null +++ b/build/pkgs/llvm/distros/fedora.txt @@ -0,0 +1 @@ +clang diff --git a/build/pkgs/llvm/distros/freebsd.txt b/build/pkgs/llvm/distros/freebsd.txt new file mode 100644 index 00000000000..0c56db703cc --- /dev/null +++ b/build/pkgs/llvm/distros/freebsd.txt @@ -0,0 +1 @@ +devel/llvm diff --git a/build/pkgs/llvm/distros/gentoo.txt b/build/pkgs/llvm/distros/gentoo.txt new file mode 100644 index 00000000000..fdca4430686 --- /dev/null +++ b/build/pkgs/llvm/distros/gentoo.txt @@ -0,0 +1 @@ +sys-devel/clang diff --git a/build/pkgs/llvm/distros/homebrew.txt b/build/pkgs/llvm/distros/homebrew.txt new file mode 100644 index 00000000000..453889a6bc6 --- /dev/null +++ b/build/pkgs/llvm/distros/homebrew.txt @@ -0,0 +1 @@ +llvm diff --git a/build/pkgs/llvm/distros/macports.txt b/build/pkgs/llvm/distros/macports.txt new file mode 100644 index 00000000000..e671fa21003 --- /dev/null +++ b/build/pkgs/llvm/distros/macports.txt @@ -0,0 +1 @@ +clang diff --git a/build/pkgs/llvm/distros/nix.txt b/build/pkgs/llvm/distros/nix.txt new file mode 100644 index 00000000000..e671fa21003 --- /dev/null +++ b/build/pkgs/llvm/distros/nix.txt @@ -0,0 +1 @@ +clang diff --git a/build/pkgs/llvm/distros/openbsd.txt b/build/pkgs/llvm/distros/openbsd.txt new file mode 100644 index 00000000000..0c56db703cc --- /dev/null +++ b/build/pkgs/llvm/distros/openbsd.txt @@ -0,0 +1 @@ +devel/llvm diff --git a/build/pkgs/llvm/distros/opensuse.txt b/build/pkgs/llvm/distros/opensuse.txt new file mode 100644 index 00000000000..453889a6bc6 --- /dev/null +++ b/build/pkgs/llvm/distros/opensuse.txt @@ -0,0 +1 @@ +llvm diff --git a/build/pkgs/llvm/distros/slackware.txt b/build/pkgs/llvm/distros/slackware.txt new file mode 100644 index 00000000000..453889a6bc6 --- /dev/null +++ b/build/pkgs/llvm/distros/slackware.txt @@ -0,0 +1 @@ +llvm diff --git a/build/pkgs/llvm/distros/void.txt b/build/pkgs/llvm/distros/void.txt new file mode 100644 index 00000000000..e671fa21003 --- /dev/null +++ b/build/pkgs/llvm/distros/void.txt @@ -0,0 +1 @@ +clang diff --git a/build/pkgs/llvm/spkg-configure.m4 b/build/pkgs/llvm/spkg-configure.m4 new file mode 100644 index 00000000000..26aceec5276 --- /dev/null +++ b/build/pkgs/llvm/spkg-configure.m4 @@ -0,0 +1,3 @@ +SAGE_SPKG_CONFIGURE([llvm], [ + sage_require_llvm=no +]) diff --git a/build/pkgs/llvm/spkg-install b/build/pkgs/llvm/spkg-install new file mode 100755 index 00000000000..4233e976c04 --- /dev/null +++ b/build/pkgs/llvm/spkg-install @@ -0,0 +1,4 @@ +#! /usr/bin/env bash +echo Error: llvm is not installed. +echo Please install it, for example using the system packages recommended by ./configure +exit 1 diff --git a/build/pkgs/llvm/type b/build/pkgs/llvm/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/llvm/type @@ -0,0 +1 @@ +optional diff --git a/build/pkgs/lrslib/checksums.ini b/build/pkgs/lrslib/checksums.ini index 88276625302..e296ecf4a74 100644 --- a/build/pkgs/lrslib/checksums.ini +++ b/build/pkgs/lrslib/checksums.ini @@ -1,4 +1,5 @@ tarball=lrslib-VERSION.tar.gz -sha1=c644600ca4a51eb08e61ba175e1d4ec999dc3e4b -md5=bb435ccdd5faf292102e246827926680 -cksum=3830123478 +sha1=4723f2b96e4b59d8366316b84214d6221b7ee7ce +md5=b379d2bdef0f5200c6274d9c50361b7c +cksum=1216904185 +upstream_url=https://github.com/mkoeppe/lrslib/releases/download/lrslib-VERSION/lrslib-VERSION.tar.gz diff --git a/build/pkgs/lrslib/package-version.txt b/build/pkgs/lrslib/package-version.txt index 54baf794ad3..d0c8ec5a9a1 100644 --- a/build/pkgs/lrslib/package-version.txt +++ b/build/pkgs/lrslib/package-version.txt @@ -1 +1 @@ -062+autotools-2017-03-03.p1 +071b+autotools-2021-07-13 diff --git a/build/pkgs/lrslib/spkg-configure.m4 b/build/pkgs/lrslib/spkg-configure.m4 index ec8e98af163..860cca451e0 100644 --- a/build/pkgs/lrslib/spkg-configure.m4 +++ b/build/pkgs/lrslib/spkg-configure.m4 @@ -2,6 +2,24 @@ SAGE_SPKG_CONFIGURE([lrslib], [ dnl System lrslib may already be 7.x, which may be compiled with FLINT SAGE_SPKG_DEPCHECK([gmp mpir flint], [ AC_CHECK_PROGS([LRSNASH], [lrsnash]) - AS_IF([test -z "$LRSNASH"], [sage_spkg_install_lrslib=yes]) + AS_IF([test -z "$LRSNASH"], [ + sage_spkg_install_lrslib=yes + ], [ + AC_MSG_CHECKING([whether $LRSNASH can handle the new input format]) + cat > conftest.lrsnash <& AS_MESSAGE_LOG_FD 2>&1], [ + AC_MSG_RESULT([yes]) + ], [ + AC_MSG_RESULT([no]) + sage_spkg_install_lrslib=yes + ]) + rm -f conftest.lrsnash + ]) ]) ]) diff --git a/build/pkgs/lrslib/spkg-install.in b/build/pkgs/lrslib/spkg-install.in index 14a634cc5e5..30dacb8b8e4 100644 --- a/build/pkgs/lrslib/spkg-install.in +++ b/build/pkgs/lrslib/spkg-install.in @@ -1,4 +1,4 @@ cd src/ -sdh_configure CPPFLAGS="-DLRS_QUIET $CPPFLAGS" +sdh_configure sdh_make sdh_make_install diff --git a/build/pkgs/ntl/spkg-configure.m4 b/build/pkgs/ntl/spkg-configure.m4 index 228c672cc4e..ba019d712de 100644 --- a/build/pkgs/ntl/spkg-configure.m4 +++ b/build/pkgs/ntl/spkg-configure.m4 @@ -47,8 +47,14 @@ SAGE_SPKG_CONFIGURE([ntl], [ ntl_inc_ntl_dir=`AS_DIRNAME(["$gl_cv_absolute_NTL_ZZ_h"])` ntl_inc_dir=`AS_DIRNAME(["$ntl_inc_ntl_dir"])` ntl_prefix=`AS_DIRNAME(["$ntl_inc_dir"])` - AC_SUBST(NTL_INCDIR, [$ntl_inc_dir]) - AC_SUBST(NTL_LIBDIR, [$ntl_prefix/lib]) + PKG_CHECK_VAR([ntl_includedir], [ntl], [includedir]) + AS_IF([test "x$ntl_includedir" = x], [AC_SUBST(NTL_INCDIR, [$ntl_inc_dir])], + [AC_SUBST(NTL_INCDIR, [$ntl_includedir])] + ) + PKG_CHECK_VAR([ntl_libdir], [ntl], [libdir]) + AS_IF([test "x$ntl_libdir" = x], [AC_SUBST(NTL_LIBDIR, [$ntl_prefix/lib])], + [AC_SUBST(NTL_LIBDIR, [$ntl_libdir])] + ) fi ]) diff --git a/build/pkgs/numpy/lapack_conf.py b/build/pkgs/numpy/lapack_conf.py index f69a12fa504..eeb9ffaf72c 100644 --- a/build/pkgs/numpy/lapack_conf.py +++ b/build/pkgs/numpy/lapack_conf.py @@ -1,4 +1,4 @@ -#!/usr/bin/env sage-bootstrap-python +#!/usr/bin/env python3 import pkgconfig, os diff --git a/build/pkgs/pip/install-requires.txt b/build/pkgs/pip/install-requires.txt index acce94d1684..326e51e0b08 100644 --- a/build/pkgs/pip/install-requires.txt +++ b/build/pkgs/pip/install-requires.txt @@ -1 +1,2 @@ -pip >=20.2.3 +pip >=21.1.0 +# for use of "pip --use-feature=in-tree-build" by the Sage distribution diff --git a/build/pkgs/polymake/SPKG.rst b/build/pkgs/polymake/SPKG.rst index 20bb07d0ec6..2f38f212b5c 100644 --- a/build/pkgs/polymake/SPKG.rst +++ b/build/pkgs/polymake/SPKG.rst @@ -51,7 +51,7 @@ you will need the local::lib Perl module installed:: cpan -i XML::Writer XML::LibXML XML::LibXSLT File::Slurp Term::ReadLine::Gnu JSON SVG MongoDB Several Sage packages should be installed before installing the polymake -package to give a more featureful Polymake installation: +package to give a more featureful Polymake installation:: sage -i 4ti2 latte_int topcom qhull @@ -65,6 +65,13 @@ Information on missing Polymake prerequisites after installing polymake:: (sage-sh) $ polymake polytope> show_unconfigured; +It is strongly recommended to also install JuPyMake:: + + sage -i jupymake + +When JuPyMake is present, Sage is able to use a more robust interface +to Polymake. + Debugging polymake install problems ----------------------------------- diff --git a/build/pkgs/polymake/checksums.ini b/build/pkgs/polymake/checksums.ini index d5dbf191a01..e059b0e2bbd 100644 --- a/build/pkgs/polymake/checksums.ini +++ b/build/pkgs/polymake/checksums.ini @@ -1,4 +1,5 @@ tarball=polymake-VERSION-minimal.tar.bz2 -sha1=e02bc7590910cff633c612cd95baa0c3aeade036 -md5=b0c6332c148060741d84b6f0eaed8343 -cksum=1380625742 +sha1=5370b16300ff8aed4eb5fa6bb5232d25935e6303 +md5=8f6a8e87e3b8bf5ccf22f26790a4dd1a +cksum=1398515202 +upstream_url=https://polymake.org/lib/exe/fetch.php/download/polymake-VERSION-minimal.tar.bz2 diff --git a/build/pkgs/polymake/package-version.txt b/build/pkgs/polymake/package-version.txt index 2f4b60750dc..515be8f918d 100644 --- a/build/pkgs/polymake/package-version.txt +++ b/build/pkgs/polymake/package-version.txt @@ -1 +1 @@ -3.4 +4.4 diff --git a/build/pkgs/polymake/patches/0001-bundled-libnormaliz-support-configure.pl-Conditional.patch b/build/pkgs/polymake/patches/0001-bundled-libnormaliz-support-configure.pl-Conditional.patch deleted file mode 100644 index 1c85673467f..00000000000 --- a/build/pkgs/polymake/patches/0001-bundled-libnormaliz-support-configure.pl-Conditional.patch +++ /dev/null @@ -1,27 +0,0 @@ -From ddef1df5781ad60d65de71e7689b56dfad7288b5 Mon Sep 17 00:00:00 2001 -From: Matthias Koeppe -Date: Thu, 25 Apr 2019 14:59:19 +0200 -Subject: [PATCH 1/3] bundled/libnormaliz/support/configure.pl: Conditionalize - - include of omp.h on _OPENMP ---- - bundled/libnormaliz/support/configure.pl | 2 ++ - 1 file changed, 2 insertions(+) - -diff --git a/bundled/libnormaliz/support/configure.pl b/bundled/libnormaliz/support/configure.pl -index 6d669270..6b512bd0 100644 ---- a/bundled/libnormaliz/support/configure.pl -+++ b/bundled/libnormaliz/support/configure.pl -@@ -74,7 +74,9 @@ int main() { - #include - #include - #include -+#ifdef _OPENMP - #include -+#endif - #include - #include - #include --- -2.19.0 - diff --git a/build/pkgs/polymake/patches/0002-Add-PERL_SET_CONTEXT-for-thread-correctness.patch b/build/pkgs/polymake/patches/0002-Add-PERL_SET_CONTEXT-for-thread-correctness.patch deleted file mode 100644 index a6b1c38614d..00000000000 --- a/build/pkgs/polymake/patches/0002-Add-PERL_SET_CONTEXT-for-thread-correctness.patch +++ /dev/null @@ -1,128 +0,0 @@ -From 51f36ef29e7d2482c23ea4da697e626cc61da611 Mon Sep 17 00:00:00 2001 -From: Matthias Koeppe -Date: Thu, 2 May 2019 13:31:00 +0200 -Subject: [PATCH 2/3] Add PERL_SET_CONTEXT for thread correctness - ---- - lib/callable/src/perl/methods.cc | 14 ++++++++++++++ - 1 file changed, 14 insertions(+) - -diff --git a/lib/callable/src/perl/methods.cc b/lib/callable/src/perl/methods.cc -index 11beefb2..fccf63b7 100644 ---- a/lib/callable/src/perl/methods.cc -+++ b/lib/callable/src/perl/methods.cc -@@ -40,6 +40,7 @@ const char Extension[]="Polymake::Core::Extension"; - void Main::set_application(const AnyString& appname) - { - dTHXa(pi); -+ PERL_SET_CONTEXT(pi); - PmStartFuncall(1); - mPUSHp(appname.ptr, appname.len); - PUTBACK; -@@ -49,6 +50,7 @@ void Main::set_application(const AnyString& appname) - void Main::set_application_of(const Object& x) - { - dTHXa(pi); -+ PERL_SET_CONTEXT(pi); - PmStartFuncall(1); - PUSHs(x.obj_ref); - PUTBACK; -@@ -58,6 +60,7 @@ void Main::set_application_of(const Object& x) - void Main::add_extension(const AnyString& path) - { - dTHXa(pi); -+ PERL_SET_CONTEXT(pi); - PmStartFuncall(2); - mPUSHp(Extension, sizeof(Extension)-1); - mPUSHp(path.ptr, path.len); -@@ -83,6 +86,7 @@ void Main::reset_preference(const AnyString& label_exp) - SV* Main::lookup_extension(const AnyString& path) - { - dTHXa(pi); -+ PERL_SET_CONTEXT(pi); - PmStartFuncall(2); - mPUSHp(Extension, sizeof(Extension)-1); - mPUSHp(path.ptr, path.len); -@@ -93,6 +97,7 @@ SV* Main::lookup_extension(const AnyString& path) - void Main::call_app_method(const char* method, const AnyString& arg) - { - dTHXa(pi); -+ PERL_SET_CONTEXT(pi); - PmStartFuncall(2); - SV* const app=glue::get_current_application(aTHX); - PUSHs(app); -@@ -104,6 +109,7 @@ void Main::call_app_method(const char* method, const AnyString& arg) - void Main::set_custom_var(const AnyString& name, const AnyString& key, Value& x) - { - dTHXa(pi); -+ PERL_SET_CONTEXT(pi); - PmStartFuncall(3); - mPUSHp(name.ptr, name.len); - if (key.ptr) mPUSHp(key.ptr, key.len); -@@ -115,6 +121,7 @@ void Main::set_custom_var(const AnyString& name, const AnyString& key, Value& x) - void Main::reset_custom(const AnyString& name, const AnyString& key) - { - dTHXa(pi); -+ PERL_SET_CONTEXT(pi); - PmStartFuncall(2); - mPUSHp(name.ptr, name.len); - if (key.ptr) mPUSHp(key.ptr, key.len); -@@ -125,6 +132,7 @@ void Main::reset_custom(const AnyString& name, const AnyString& key) - Scope Main::newScope() - { - dTHXa(pi); -+ PERL_SET_CONTEXT(pi); - PmStartFuncall(0); - return Scope(this, call_func_scalar(aTHX_ new_scope_cv)); - } -@@ -137,6 +145,7 @@ void Scope::prefer_now(const AnyString& labels) const - void Scope::set_custom_var(const AnyString& name, const AnyString& key, Value& x) const - { - dTHXa(pm_main->pi); -+ PERL_SET_CONTEXT(pm_main->pi); - PmStartFuncall(3); - mPUSHp(name.ptr, name.len); - if (key.ptr) mPUSHp(key.ptr, key.len); -@@ -148,6 +157,7 @@ void Scope::set_custom_var(const AnyString& name, const AnyString& key, Value& x - std::string Main::greeting(int verbose) - { - dTHXa(pi); -+ PERL_SET_CONTEXT(pi); - PmStartFuncall(1); - mPUSHi(verbose); - PUTBACK; -@@ -157,6 +167,7 @@ std::string Main::greeting(int verbose) - void Main::shell_enable() - { - dTHXa(pi); -+ PERL_SET_CONTEXT(pi); - PmStartFuncall(0); - glue::call_func_void(aTHX_ shell_enable_cv); - } -@@ -167,6 +178,7 @@ Main::shell_execute_t Main::shell_execute(const std::string& input) - return shell_execute_t(true, input, input, input); - - dTHXa(pi); -+ PERL_SET_CONTEXT(pi); - PmStartFuncall(1); - mPUSHp(input.c_str(), input.size()); - PUTBACK; -@@ -187,6 +199,7 @@ Main::shell_execute_t Main::shell_execute(const std::string& input) - Main::shell_complete_t Main::shell_complete(const std::string& input) - { - dTHXa(pi); -+ PERL_SET_CONTEXT(pi); - PmStartFuncall(1); - mPUSHp(input.c_str(), input.size()); - PUTBACK; -@@ -209,6 +222,7 @@ Main::shell_complete_t Main::shell_complete(const std::string& input) - std::vector Main::shell_context_help(const std::string& input, size_t pos, bool full, bool html) - { - dTHXa(pi); -+ PERL_SET_CONTEXT(pi); - PmStartFuncall(4); - mPUSHp(input.c_str(), input.size()); - if (pos == std::string::npos) --- -2.19.0 - diff --git a/build/pkgs/polymake/patches/0003-Shell-Mock-Add-compile_scope.patch b/build/pkgs/polymake/patches/0003-Shell-Mock-Add-compile_scope.patch deleted file mode 100644 index 0e51c353852..00000000000 --- a/build/pkgs/polymake/patches/0003-Shell-Mock-Add-compile_scope.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 50ce55eea115b74eae9421de43a4405e254ef579 Mon Sep 17 00:00:00 2001 -From: Matthias Koeppe -Date: Fri, 10 May 2019 14:59:44 +0200 -Subject: [PATCH 3/3] Shell::Mock: Add compile_scope - ---- - perllib/Polymake/Core/ShellMock.pm | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/perllib/Polymake/Core/ShellMock.pm b/perllib/Polymake/Core/ShellMock.pm -index 46d59654..137f0b24 100644 ---- a/perllib/Polymake/Core/ShellMock.pm -+++ b/perllib/Polymake/Core/ShellMock.pm -@@ -34,6 +34,7 @@ use Polymake::Struct ( - - sub term { shift } - sub interactive { 0 } -+sub compile_scope { undef } - - sub complete { - my ($self, $string)=@_; --- -2.19.0 - diff --git a/build/pkgs/polymake/spkg-install.in b/build/pkgs/polymake/spkg-install.in index 5a74ff5c609..ff2e6718363 100644 --- a/build/pkgs/polymake/spkg-install.in +++ b/build/pkgs/polymake/spkg-install.in @@ -26,5 +26,5 @@ esac --bindir="$SAGE_LOCAL"/bin \ --libdir="$SAGE_LOCAL"/lib \ $more_configure_options || sdh_die "Error configuring Polymake" -sdh_make +ninja -C build/Opt -j${SAGE_NUM_THREADS} sdh_make_install diff --git a/build/pkgs/sage_numerical_backends_gurobi/checksums.ini b/build/pkgs/sage_numerical_backends_gurobi/checksums.ini index f81be90befd..d5e6fd8e2b1 100644 --- a/build/pkgs/sage_numerical_backends_gurobi/checksums.ini +++ b/build/pkgs/sage_numerical_backends_gurobi/checksums.ini @@ -1,5 +1,5 @@ tarball=sage_numerical_backends_gurobi-VERSION.tar.gz -sha1=64253f735ee6779b6ded809643b455061a105dc4 -md5=3df7d18fe53af89e0a796974a2d788b5 -cksum=3824076545 +sha1=36a2bfa1cfa2f4fdcb9dc979e80177580bb7aac2 +md5=0a77377fad705950c4b7b14b366ebaa6 +cksum=478792673 upstream_url=https://pypi.io/packages/source/s/sage_numerical_backends_gurobi/sage_numerical_backends_gurobi-VERSION.tar.gz diff --git a/build/pkgs/sage_numerical_backends_gurobi/package-version.txt b/build/pkgs/sage_numerical_backends_gurobi/package-version.txt index f7ee06693c1..3b74042132a 100644 --- a/build/pkgs/sage_numerical_backends_gurobi/package-version.txt +++ b/build/pkgs/sage_numerical_backends_gurobi/package-version.txt @@ -1 +1 @@ -9.0.0 +9.3.1 diff --git a/build/pkgs/sagelib/package-version.txt b/build/pkgs/sagelib/package-version.txt index 0439ed38bee..2b12acd816c 100644 --- a/build/pkgs/sagelib/package-version.txt +++ b/build/pkgs/sagelib/package-version.txt @@ -1 +1 @@ -9.4.beta5 +9.4.beta6 diff --git a/build/pkgs/sagelib/src b/build/pkgs/sagelib/src deleted file mode 120000 index 0aa4dc12060..00000000000 --- a/build/pkgs/sagelib/src +++ /dev/null @@ -1 +0,0 @@ -../../../pkgs/sagemath-standard \ No newline at end of file diff --git a/build/pkgs/singular/patches/0001-factory-canonicalform.h-Add-more-FACTORY_PUBLIC.patch b/build/pkgs/singular/patches/0001-factory-canonicalform.h-Add-more-FACTORY_PUBLIC.patch new file mode 100644 index 00000000000..7f083e9e869 --- /dev/null +++ b/build/pkgs/singular/patches/0001-factory-canonicalform.h-Add-more-FACTORY_PUBLIC.patch @@ -0,0 +1,97 @@ +From 5830ce6deabefaeaec492f1054cc619af4e4e2d2 Mon Sep 17 00:00:00 2001 +From: Matthias Koeppe +Date: Tue, 20 Jul 2021 19:19:32 -0700 +Subject: [PATCH] factory/canonicalform.h: Add more FACTORY_PUBLIC + +--- + factory/canonicalform.h | 42 ++++++++++++++++++++--------------------- + 1 file changed, 21 insertions(+), 21 deletions(-) + +diff --git a/factory/canonicalform.h b/factory/canonicalform.h +index 82dfa57e1..8fd158385 100644 +--- a/factory/canonicalform.h ++++ b/factory/canonicalform.h +@@ -172,27 +172,27 @@ public: + int ilog2() const; + + // comparison operators +- friend bool operator == ( const CanonicalForm&, const CanonicalForm& ); +- friend bool operator != ( const CanonicalForm&, const CanonicalForm& ); +- friend bool operator > ( const CanonicalForm&, const CanonicalForm& ); +- friend bool operator < ( const CanonicalForm&, const CanonicalForm& ); ++ friend FACTORY_PUBLIC bool operator == ( const CanonicalForm&, const CanonicalForm& ); ++ friend FACTORY_PUBLIC bool operator != ( const CanonicalForm&, const CanonicalForm& ); ++ friend FACTORY_PUBLIC bool operator > ( const CanonicalForm&, const CanonicalForm& ); ++ friend FACTORY_PUBLIC bool operator < ( const CanonicalForm&, const CanonicalForm& ); + + // arithmetic operators +- friend CF_NO_INLINE CanonicalForm operator - ( const CanonicalForm& ); ++ friend CF_NO_INLINE FACTORY_PUBLIC CanonicalForm operator - ( const CanonicalForm& ); + +- friend void FACTORY_PUBLIC divrem ( const CanonicalForm&, const CanonicalForm&, CanonicalForm&, CanonicalForm& ); +- friend bool divremt ( const CanonicalForm&, const CanonicalForm&, CanonicalForm&, CanonicalForm& ); +- friend bool tryDivremt ( const CanonicalForm&, const CanonicalForm&, CanonicalForm&, CanonicalForm&, const CanonicalForm&, bool& ); ++ friend FACTORY_PUBLIC void divrem ( const CanonicalForm&, const CanonicalForm&, CanonicalForm&, CanonicalForm& ); ++ friend FACTORY_PUBLIC bool divremt ( const CanonicalForm&, const CanonicalForm&, CanonicalForm&, CanonicalForm& ); ++ friend FACTORY_PUBLIC bool tryDivremt ( const CanonicalForm&, const CanonicalForm&, CanonicalForm&, CanonicalForm&, const CanonicalForm&, bool& ); + +- friend CanonicalForm bgcd ( const CanonicalForm &, const CanonicalForm & ); +- friend CanonicalForm bextgcd ( const CanonicalForm &, const CanonicalForm &, CanonicalForm &, CanonicalForm & ); ++ friend FACTORY_PUBLIC CanonicalForm bgcd ( const CanonicalForm &, const CanonicalForm & ); ++ friend FACTORY_PUBLIC CanonicalForm bextgcd ( const CanonicalForm &, const CanonicalForm &, CanonicalForm &, CanonicalForm & ); + + // input/output + #ifndef NOSTREAMIO + void print( OSTREAM&, char * ) const; + void print( OSTREAM& ) const; +- friend OSTREAM& operator << ( OSTREAM&, const CanonicalForm& ); +- friend ISTREAM& operator >> ( ISTREAM&, CanonicalForm& ); ++ friend FACTORY_PUBLIC OSTREAM& operator << ( OSTREAM&, const CanonicalForm& ); ++ friend FACTORY_PUBLIC ISTREAM& operator >> ( ISTREAM&, CanonicalForm& ); + #endif /* NOSTREAMIO */ + + // obsolete methods +@@ -234,7 +234,7 @@ mod ( const CanonicalForm&, const CanonicalForm& ); + /*BEGINPUBLIC*/ + + //{{{ function declarations from canonicalform.cc +-CanonicalForm blcm ( const CanonicalForm & f, const CanonicalForm & g ); ++CanonicalForm FACTORY_PUBLIC blcm ( const CanonicalForm & f, const CanonicalForm & g ); + + CanonicalForm FACTORY_PUBLIC power ( const CanonicalForm & f, int n ); + +@@ -244,23 +244,23 @@ CanonicalForm FACTORY_PUBLIC power ( const Variable & v, int n ); + //{{{ function declarations from cf_gcd.cc + CanonicalForm FACTORY_PUBLIC gcd ( const CanonicalForm&, const CanonicalForm& ); + +-CanonicalForm gcd_poly ( const CanonicalForm & f, const CanonicalForm & g ); ++CanonicalForm FACTORY_PUBLIC gcd_poly ( const CanonicalForm & f, const CanonicalForm & g ); + +-CanonicalForm lcm ( const CanonicalForm&, const CanonicalForm& ); ++CanonicalForm FACTORY_PUBLIC lcm ( const CanonicalForm&, const CanonicalForm& ); + +-CanonicalForm pp ( const CanonicalForm& ); ++CanonicalForm FACTORY_PUBLIC pp ( const CanonicalForm& ); + +-CanonicalForm content ( const CanonicalForm& ); ++CanonicalForm FACTORY_PUBLIC content ( const CanonicalForm& ); + +-CanonicalForm content ( const CanonicalForm&, const Variable& ); ++CanonicalForm FACTORY_PUBLIC content ( const CanonicalForm&, const Variable& ); + +-CanonicalForm icontent ( const CanonicalForm & f ); ++CanonicalForm FACTORY_PUBLIC icontent ( const CanonicalForm & f ); + +-CanonicalForm vcontent ( const CanonicalForm & f, const Variable & x ); ++CanonicalForm FACTORY_PUBLIC vcontent ( const CanonicalForm & f, const Variable & x ); + //}}} + + //{{{ function declarations from cf_ops.cc +-CanonicalForm swapvar ( const CanonicalForm &, const Variable &, const Variable & ); ++CanonicalForm FACTORY_PUBLIC swapvar ( const CanonicalForm &, const Variable &, const Variable & ); + + CanonicalForm FACTORY_PUBLIC replacevar ( const CanonicalForm &, const Variable &, const Variable & ); + +-- +2.31.1 + diff --git a/build/pkgs/suitesparse/checksums.ini b/build/pkgs/suitesparse/checksums.ini index 9516ea039ee..fa6fbb6f9a4 100644 --- a/build/pkgs/suitesparse/checksums.ini +++ b/build/pkgs/suitesparse/checksums.ini @@ -1,4 +1,5 @@ tarball=SuiteSparse-VERSION.tar.gz -sha1=3de08b5ab02610ed0446225aad2445696616fae5 -md5=af8b97cbded4cd5c6672e878bc0c37c2 -cksum=3062366378 +sha1=83dd96b32701e12b7577acb7d9aea80138d7e46e +md5=68bb912f3cf3d2b01f30ebafef690302 +cksum=178881779 +upstream_url=https://github.com/DrTimothyAldenDavis/SuiteSparse/archive/refs/tags/vVERSION.tar.gz diff --git a/build/pkgs/suitesparse/dependencies b/build/pkgs/suitesparse/dependencies index 7580ea8e60f..c9663df28f5 100644 --- a/build/pkgs/suitesparse/dependencies +++ b/build/pkgs/suitesparse/dependencies @@ -1 +1 @@ -$(BLAS) gfortran +$(BLAS) gfortran mpfr $(MP_LIBRARY) diff --git a/build/pkgs/suitesparse/package-version.txt b/build/pkgs/suitesparse/package-version.txt index 1bc788d3b6d..4e32c7b1caf 100644 --- a/build/pkgs/suitesparse/package-version.txt +++ b/build/pkgs/suitesparse/package-version.txt @@ -1 +1 @@ -5.6.0 +5.10.1 diff --git a/build/pkgs/suitesparse/patches/01-no_cmake_project.patch b/build/pkgs/suitesparse/patches/01-no_cmake_project.patch index ccde30de327..8b2b1a2666c 100644 --- a/build/pkgs/suitesparse/patches/01-no_cmake_project.patch +++ b/build/pkgs/suitesparse/patches/01-no_cmake_project.patch @@ -1,24 +1,24 @@ diff --git a/Makefile b/Makefile -index 74941a6..70e7b68 100644 +index abd00d0f..10c31390 100644 --- a/Makefile +++ b/Makefile -@@ -12,7 +12,6 @@ include SuiteSparse_config/SuiteSparse_config.mk - # Compile the default rules for each package +@@ -16,7 +16,6 @@ include SuiteSparse_config/SuiteSparse_config.mk + # installs all libraries SuiteSparse/lib. go: metis ( cd SuiteSparse_config && $(MAKE) ) -- ( cd Mongoose && $(MAKE) CMAKE_OPTIONS='$(CMAKE_OPTIONS)' ) +- ( cd Mongoose && $(MAKE) CMAKE_OPTIONS='$(CMAKE_OPTIONS)' ) ( cd AMD && $(MAKE) ) ( cd BTF && $(MAKE) ) ( cd CAMD && $(MAKE) ) -@@ -30,7 +29,6 @@ ifneq ($(GPU_CONFIG),) +@@ -34,7 +33,6 @@ ifneq ($(GPU_CONFIG),) ( cd GPUQREngine && $(MAKE) ) endif ( cd SPQR && $(MAKE) ) - ( cd GraphBLAS && $(MAKE) JOBS=$(JOBS) CMAKE_OPTIONS='$(CMAKE_OPTIONS)' ) + ( cd SLIP_LU && $(MAKE) ) # ( cd PIRO_BAND && $(MAKE) ) # ( cd SKYLINE_SVD && $(MAKE) ) - -@@ -38,7 +36,6 @@ endif +@@ -46,7 +44,6 @@ endif # (note that CSparse is not installed; CXSparse is installed instead) install: metisinstall ( cd SuiteSparse_config && $(MAKE) install ) @@ -26,15 +26,15 @@ index 74941a6..70e7b68 100644 ( cd AMD && $(MAKE) install ) ( cd BTF && $(MAKE) install ) ( cd CAMD && $(MAKE) install ) -@@ -55,7 +52,6 @@ ifneq (,$(GPU_CONFIG)) +@@ -63,7 +60,6 @@ ifneq (,$(GPU_CONFIG)) ( cd GPUQREngine && $(MAKE) install ) endif ( cd SPQR && $(MAKE) install ) - ( cd GraphBLAS && $(MAKE) JOBS=$(JOBS) CMAKE_OPTIONS='$(CMAKE_OPTIONS)' install ) # ( cd PIRO_BAND && $(MAKE) install ) # ( cd SKYLINE_SVD && $(MAKE) install ) - $(CP) README.txt $(INSTALL_DOC)/SuiteSparse_README.txt -@@ -116,7 +112,6 @@ endif + ( cd SLIP_LU && $(MAKE) install ) +@@ -126,7 +122,6 @@ endif # the static library library: metis ( cd SuiteSparse_config && $(MAKE) ) @@ -42,15 +42,15 @@ index 74941a6..70e7b68 100644 ( cd AMD && $(MAKE) library ) ( cd BTF && $(MAKE) library ) ( cd CAMD && $(MAKE) library ) -@@ -134,7 +129,6 @@ ifneq (,$(GPU_CONFIG)) +@@ -144,7 +139,6 @@ ifneq (,$(GPU_CONFIG)) ( cd GPUQREngine && $(MAKE) library ) endif ( cd SPQR && $(MAKE) library ) - ( cd GraphBLAS && $(MAKE) JOBS=$(JOBS) CMAKE_OPTIONS='$(CMAKE_OPTIONS)' library ) + ( cd SLIP_LU && $(MAKE) library ) # ( cd PIRO_BAND && $(MAKE) library ) # ( cd SKYLINE_SVD && $(MAKE) library ) - -@@ -143,7 +137,6 @@ endif +@@ -154,7 +148,6 @@ endif # both the dynamic and static libraries. static: metis ( cd SuiteSparse_config && $(MAKE) static ) @@ -58,11 +58,33 @@ index 74941a6..70e7b68 100644 ( cd AMD && $(MAKE) static ) ( cd BTF && $(MAKE) static ) ( cd CAMD && $(MAKE) static ) -@@ -161,7 +154,6 @@ ifneq (,$(GPU_CONFIG)) +@@ -172,7 +165,6 @@ ifneq (,$(GPU_CONFIG)) ( cd GPUQREngine && $(MAKE) static ) endif ( cd SPQR && $(MAKE) static ) - ( cd GraphBLAS && $(MAKE) JOBS=$(JOBS) CMAKE_OPTIONS='$(CMAKE_OPTIONS)' static ) + ( cd SLIP_LU && $(MAKE) static ) # ( cd PIRO_BAND && $(MAKE) static ) # ( cd SKYLINE_SVD && $(MAKE) static ) +@@ -291,21 +283,3 @@ else + @echo 'Using pre-installed METIS 5.1.0 library at ' '[$(MY_METIS_LIB)]' + endif +-# just compile GraphBLAS +-gb: +- echo $(CMAKE_OPTIONS) +- ( cd GraphBLAS && $(MAKE) JOBS=$(JOBS) CMAKE_OPTIONS='$(CMAKE_OPTIONS)' ) +- +-# just install GraphBLAS +-gbinstall: +- echo $(CMAKE_OPTIONS) +- ( cd GraphBLAS && $(MAKE) JOBS=$(JOBS) CMAKE_OPTIONS='$(CMAKE_OPTIONS)' install ) +- +-# just compile Mongoose +-mon: +- ( cd Mongoose && $(MAKE) CMAKE_OPTIONS='$(CMAKE_OPTIONS)' ) +- +-# just install Mongoose +-moninstall: +- ( cd Mongoose && $(MAKE) CMAKE_OPTIONS='$(CMAKE_OPTIONS)' install ) +- diff --git a/build/pkgs/suitesparse/patches/02-darwin_blas.patch b/build/pkgs/suitesparse/patches/02-darwin_blas.patch index bfd0e0bc548..546bfc80642 100644 --- a/build/pkgs/suitesparse/patches/02-darwin_blas.patch +++ b/build/pkgs/suitesparse/patches/02-darwin_blas.patch @@ -1,15 +1,15 @@ diff --git a/SuiteSparse_config/SuiteSparse_config.mk b/SuiteSparse_config/SuiteSparse_config.mk -index 0a09188..90b2e57 100644 +index 2fe1ab09..352ff593 100644 --- a/SuiteSparse_config/SuiteSparse_config.mk +++ b/SuiteSparse_config/SuiteSparse_config.mk -@@ -370,8 +370,8 @@ SUITESPARSE_VERSION = 5.6.0 +@@ -370,8 +370,8 @@ SUITESPARSE_VERSION = 5.10.1 # command line in the Terminal, before doing 'make': # xcode-select --install CF += -fno-common -- BLAS = -framework Accelerate -- LAPACK = -framework Accelerate -+ #BLAS = -framework Accelerate -+ #LAPACK = -framework Accelerate +- BLAS ?= -framework Accelerate +- LAPACK ?= -framework Accelerate ++ #BLAS ?= -framework Accelerate ++ #LAPACK ?= -framework Accelerate # OpenMP is not yet supported by default in clang CFOPENMP = - endif + LDLIBS += -rpath $(INSTALL_LIB) diff --git a/build/pkgs/suitesparse/patches/03-buildflags.patch b/build/pkgs/suitesparse/patches/03-buildflags.patch index 20da6535d82..8a2c312be39 100644 --- a/build/pkgs/suitesparse/patches/03-buildflags.patch +++ b/build/pkgs/suitesparse/patches/03-buildflags.patch @@ -1,26 +1,8 @@ -Inspired by, but not identical to the following patch from debian: -https://sources.debian.org/patches/suitesparse/1:5.6.0+dfsg-2/buildflags.patch/ - -This prepends $(DESTDIR) directly to all of $(INSTALL_LIB), $(INSTALL_INCLUDE), -and $(INSTALL_DOC), as these variables only affect where built files are copied -to, and do not affect the contents of the files. - -However, we also remove some additional LDFLAGS added by this Makefile (for - -L$SAGE_LOCAL and -Wl,-rpath=$SAGE_LOCAL/lib) since we already add those -in the standard SPKG built environment. -Note that -L$(INSTALL_LIB) cannot be removed from LDFLAGS unless the package -is broken into components. It is necessary as the components are progressively -installed in INSTALL_LIB one after the other. -Because we don't install directly in the final location, install_name also becomes -wrong on OS X which means the libraries cannot be loaded. So we have to provide -a correct install_name as well. - -For https://trac.sagemath.org/ticket/28832 diff --git a/SuiteSparse_config/SuiteSparse_config.mk b/SuiteSparse_config/SuiteSparse_config.mk -index 0a09188..fd1c0b0 100644 +index 352ff593..61f0076e 100644 --- a/SuiteSparse_config/SuiteSparse_config.mk +++ b/SuiteSparse_config/SuiteSparse_config.mk -@@ -55,9 +55,9 @@ SUITESPARSE_VERSION = 5.6.0 +@@ -72,9 +72,9 @@ SUITESPARSE_VERSION = 5.10.1 # which puts the libraries in /yada/mylibs, include files in /yoda/myinc, # and documentation in /solo/mydox. INSTALL ?= $(SUITESPARSE) @@ -33,7 +15,7 @@ index 0a09188..fd1c0b0 100644 #--------------------------------------------------------------------------- # parallel make -@@ -358,7 +358,7 @@ SUITESPARSE_VERSION = 5.6.0 +@@ -358,7 +358,7 @@ SUITESPARSE_VERSION = 5.10.1 ifeq ($(UNAME),Linux) # add the realtime library, librt, and SuiteSparse/lib @@ -42,13 +24,12 @@ index 0a09188..fd1c0b0 100644 endif #--------------------------------------------------------------------------- -@@ -463,7 +463,8 @@ else +@@ -464,7 +464,7 @@ else SO_TARGET = $(LIBRARY).$(VERSION).dylib SO_OPTS += -dynamiclib -compatibility_version $(SO_VERSION) \ -current_version $(VERSION) \ -- -shared -undefined dynamic_lookup -+ -shared -undefined dynamic_lookup \ -+ -install_name $(INSTALL)/lib/$(SO_MAIN) +- -Wl,-install_name -Wl,@rpath/$(SO_MAIN) \ ++ -Wl,-install_name -Wl,$(INSTALL)/lib/$(SO_MAIN) \ + -shared -undefined dynamic_lookup # When a Mac *.dylib file is moved, this command is required # to change its internal name to match its location in the filesystem: - SO_INSTALL_NAME = install_name_tool -id diff --git a/build/pkgs/suitesparse/patches/04-cygwin.patch b/build/pkgs/suitesparse/patches/04-cygwin.patch index 803a395e86d..f4118f86670 100644 --- a/build/pkgs/suitesparse/patches/04-cygwin.patch +++ b/build/pkgs/suitesparse/patches/04-cygwin.patch @@ -1,21 +1,7 @@ -Patch to support installing correctly on Cygwin. - -This includes: - -* Installing DLLs under bin/ with a cyg- prefix instead of lib- -* Creating and installing import libs (.dll.a files) in lib/ - -For the time being versioning of import libs is not used, but it is -for DLLs. - -This unfortunately requires a patch bomb since all of SuiteSparse's -libraries have their own Makefiles even though they are near copies -of each other with minor differences. - -See https://trac.sagemath.org/ticket/28829 -diff -ruN a/AMD/Lib/Makefile b/AMD/Lib/Makefile ---- a/AMD/Lib/Makefile 2019-12-02 16:05:04.856283600 +0100 -+++ b/AMD/Lib/Makefile 2019-12-02 18:12:09.753790100 +0100 +diff --git a/AMD/Lib/Makefile b/AMD/Lib/Makefile +index 3b92f6a0..582bef2a 100644 +--- a/AMD/Lib/Makefile ++++ b/AMD/Lib/Makefile @@ -2,7 +2,7 @@ # AMD Lib/Makefile #------------------------------------------------------------------------------- @@ -25,7 +11,7 @@ diff -ruN a/AMD/Lib/Makefile b/AMD/Lib/Makefile VERSION = 2.4.6 SO_VERSION = 2 -@@ -81,19 +81,22 @@ +@@ -81,19 +81,22 @@ libamdf77.a: $(AMDF77) #------------------------------------------------------------------------------- # install AMD @@ -51,9 +37,10 @@ diff -ruN a/AMD/Lib/Makefile b/AMD/Lib/Makefile chmod 644 $(INSTALL_INCLUDE)/amd.h chmod 644 $(INSTALL_DOC)/AMD_UserGuide.pdf chmod 644 $(INSTALL_DOC)/AMD_README.txt -diff -ruN a/BTF/Lib/Makefile b/BTF/Lib/Makefile ---- a/BTF/Lib/Makefile 2019-12-02 16:04:51.542288600 +0100 -+++ b/BTF/Lib/Makefile 2019-12-02 18:12:16.908303300 +0100 +diff --git a/BTF/Lib/Makefile b/BTF/Lib/Makefile +index 85b7a264..6e4e2ec4 100644 +--- a/BTF/Lib/Makefile ++++ b/BTF/Lib/Makefile @@ -2,7 +2,7 @@ # BTF Lib/Makefile #------------------------------------------------------------------------------- @@ -63,7 +50,7 @@ diff -ruN a/BTF/Lib/Makefile b/BTF/Lib/Makefile VERSION = 1.2.6 SO_VERSION = 1 -@@ -66,18 +66,21 @@ +@@ -66,18 +66,21 @@ btf_l_strongcomp.o: ../Source/btf_strongcomp.c #------------------------------------------------------------------------------- # install BTF @@ -88,9 +75,10 @@ diff -ruN a/BTF/Lib/Makefile b/BTF/Lib/Makefile chmod 644 $(INSTALL_INCLUDE)/btf.h chmod 644 $(INSTALL_DOC)/BTF_README.txt -diff -ruN a/CAMD/Lib/Makefile b/CAMD/Lib/Makefile ---- a/CAMD/Lib/Makefile 2019-12-02 16:04:50.051276500 +0100 -+++ b/CAMD/Lib/Makefile 2019-12-02 18:12:07.722810200 +0100 +diff --git a/CAMD/Lib/Makefile b/CAMD/Lib/Makefile +index b3ae159e..27cb072a 100644 +--- a/CAMD/Lib/Makefile ++++ b/CAMD/Lib/Makefile @@ -2,7 +2,7 @@ # CAMD Lib/Makefile #------------------------------------------------------------------------------- @@ -100,7 +88,7 @@ diff -ruN a/CAMD/Lib/Makefile b/CAMD/Lib/Makefile VERSION = 2.4.6 SO_VERSION = 2 -@@ -62,19 +62,22 @@ +@@ -62,19 +62,22 @@ $(AR_TARGET): $(OBJ) #------------------------------------------------------------------------------- # install CAMD @@ -126,9 +114,10 @@ diff -ruN a/CAMD/Lib/Makefile b/CAMD/Lib/Makefile chmod 644 $(INSTALL_INCLUDE)/camd.h chmod 644 $(INSTALL_DOC)/CAMD_UserGuide.pdf chmod 644 $(INSTALL_DOC)/CAMD_README.txt -diff -ruN a/CCOLAMD/Lib/Makefile b/CCOLAMD/Lib/Makefile ---- a/CCOLAMD/Lib/Makefile 2019-12-02 16:04:49.279277800 +0100 -+++ b/CCOLAMD/Lib/Makefile 2019-12-02 18:12:22.610632700 +0100 +diff --git a/CCOLAMD/Lib/Makefile b/CCOLAMD/Lib/Makefile +index 52c52eb9..18190be0 100644 +--- a/CCOLAMD/Lib/Makefile ++++ b/CCOLAMD/Lib/Makefile @@ -2,7 +2,7 @@ # CCOLAMD Lib/Makefile #------------------------------------------------------------------------------- @@ -138,7 +127,7 @@ diff -ruN a/CCOLAMD/Lib/Makefile b/CCOLAMD/Lib/Makefile VERSION = 2.9.6 SO_VERSION = 2 -@@ -49,18 +49,21 @@ +@@ -49,18 +49,21 @@ distclean: clean - $(RM) -r $(PURGE) # install CCOLAMD @@ -163,19 +152,20 @@ diff -ruN a/CCOLAMD/Lib/Makefile b/CCOLAMD/Lib/Makefile chmod 644 $(INSTALL_INCLUDE)/ccolamd.h chmod 644 $(INSTALL_DOC)/CCOLAMD_README.txt -diff -ruN a/CHOLMOD/Lib/Makefile b/CHOLMOD/Lib/Makefile ---- a/CHOLMOD/Lib/Makefile 2019-12-02 16:04:51.453293800 +0100 -+++ b/CHOLMOD/Lib/Makefile 2019-12-02 18:12:16.627302900 +0100 +diff --git a/CHOLMOD/Lib/Makefile b/CHOLMOD/Lib/Makefile +index bc3eab69..d240b4cd 100644 +--- a/CHOLMOD/Lib/Makefile ++++ b/CHOLMOD/Lib/Makefile @@ -2,7 +2,7 @@ # CHOLMOD/Lib/Makefile: for compiling the CHOLMOD library #=============================================================================== -LIBRARY = libcholmod +LIBRARY = cholmod - VERSION = 3.0.13 + VERSION = 3.0.14 SO_VERSION = 3 -@@ -535,20 +535,23 @@ +@@ -535,20 +535,23 @@ cholmod_l_gpu.o: ../GPU/cholmod_gpu.c #------------------------------------------------------------------------------- # install CHOLMOD @@ -188,7 +178,7 @@ diff -ruN a/CHOLMOD/Lib/Makefile b/CHOLMOD/Lib/Makefile + @mkdir -p $(dir $(INSTALL_SO)) @mkdir -p $(INSTALL_INCLUDE) @mkdir -p $(INSTALL_DOC) - $(CC) $(SO_OPTS) $^ -o $@ $(LDLIBS) + $(CXX) $(SO_OPTS) $^ -o $@ $(LDLIBS) +ifneq ($(UNAME),Cygwin) ( cd $(INSTALL_LIB) ; ln -sf $(SO_TARGET) $(SO_PLAIN) ) ( cd $(INSTALL_LIB) ; ln -sf $(SO_TARGET) $(SO_MAIN) ) @@ -196,16 +186,16 @@ diff -ruN a/CHOLMOD/Lib/Makefile b/CHOLMOD/Lib/Makefile +endif $(CP) ../Include/cholmod*.h $(INSTALL_INCLUDE) $(RM) $(INSTALL_INCLUDE)/cholmod_internal.h -- $(CP) ../Doc/CHOLMOD_UserGuide.pdf $(INSTALL_DOC) + $(CP) ../Doc/CHOLMOD_UserGuide.pdf $(INSTALL_DOC) $(CP) ../README.txt $(INSTALL_DOC)/CHOLMOD_README.txt - chmod 755 $(INSTALL_LIB)/$(SO_TARGET) -+ $(CP) ../Doc/CHOLMOD_UserGuide.pdf $(INSTALL_DOC) chmod 644 $(INSTALL_INCLUDE)/cholmod*.h chmod 644 $(INSTALL_DOC)/CHOLMOD_UserGuide.pdf chmod 644 $(INSTALL_DOC)/CHOLMOD_README.txt -diff -ruN a/COLAMD/Lib/Makefile b/COLAMD/Lib/Makefile ---- a/COLAMD/Lib/Makefile 2019-12-02 16:04:58.545276600 +0100 -+++ b/COLAMD/Lib/Makefile 2019-12-02 18:12:17.545302000 +0100 +diff --git a/COLAMD/Lib/Makefile b/COLAMD/Lib/Makefile +index 2d0c5aa6..26720b62 100644 +--- a/COLAMD/Lib/Makefile ++++ b/COLAMD/Lib/Makefile @@ -2,7 +2,7 @@ # COLAMD Lib/Makefile #------------------------------------------------------------------------------- @@ -215,7 +205,7 @@ diff -ruN a/COLAMD/Lib/Makefile b/COLAMD/Lib/Makefile VERSION = 2.9.6 SO_VERSION = 2 -@@ -49,18 +49,21 @@ +@@ -49,18 +49,21 @@ distclean: clean - $(RM) -r $(PURGE) # install COLAMD @@ -240,9 +230,10 @@ diff -ruN a/COLAMD/Lib/Makefile b/COLAMD/Lib/Makefile chmod 644 $(INSTALL_INCLUDE)/colamd.h chmod 644 $(INSTALL_DOC)/COLAMD_README.txt -diff -ruN a/CSparse/Lib/Makefile b/CSparse/Lib/Makefile ---- a/CSparse/Lib/Makefile 2019-12-02 16:04:58.059294300 +0100 -+++ b/CSparse/Lib/Makefile 2019-12-02 18:12:09.158797700 +0100 +diff --git a/CSparse/Lib/Makefile b/CSparse/Lib/Makefile +index e3f32cec..468f7a47 100644 +--- a/CSparse/Lib/Makefile ++++ b/CSparse/Lib/Makefile @@ -14,7 +14,7 @@ # install' in this Makefile only installs a static compiled library in # CSparse/Lib. It does not install it for system-wide usage. @@ -252,9 +243,10 @@ diff -ruN a/CSparse/Lib/Makefile b/CSparse/Lib/Makefile CF = $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -O I = -I../Include -diff -ruN a/CXSparse/Lib/Makefile b/CXSparse/Lib/Makefile ---- a/CXSparse/Lib/Makefile 2019-12-02 16:04:49.462296500 +0100 -+++ b/CXSparse/Lib/Makefile 2019-12-02 18:12:23.167631600 +0100 +diff --git a/CXSparse/Lib/Makefile b/CXSparse/Lib/Makefile +index a21e3607..270b8027 100644 +--- a/CXSparse/Lib/Makefile ++++ b/CXSparse/Lib/Makefile @@ -2,7 +2,7 @@ # CXSparse Lib/Makefile #------------------------------------------------------------------------------- @@ -264,7 +256,7 @@ diff -ruN a/CXSparse/Lib/Makefile b/CXSparse/Lib/Makefile VERSION = 3.2.0 SO_VERSION = 3 -@@ -113,18 +113,21 @@ +@@ -113,18 +113,21 @@ distclean: clean - $(RM) -r $(PURGE) # install CXSparse @@ -289,9 +281,10 @@ diff -ruN a/CXSparse/Lib/Makefile b/CXSparse/Lib/Makefile chmod 644 $(INSTALL_INCLUDE)/cs.h chmod 644 $(INSTALL_DOC)/CXSPARSE_README.txt -diff -ruN a/CXSparse_newfiles/Lib/Makefile b/CXSparse_newfiles/Lib/Makefile ---- a/CXSparse_newfiles/Lib/Makefile 2019-12-02 16:05:04.505277300 +0100 -+++ b/CXSparse_newfiles/Lib/Makefile 2019-12-02 18:12:08.029790200 +0100 +diff --git a/CXSparse_newfiles/Lib/Makefile b/CXSparse_newfiles/Lib/Makefile +index a21e3607..270b8027 100644 +--- a/CXSparse_newfiles/Lib/Makefile ++++ b/CXSparse_newfiles/Lib/Makefile @@ -2,7 +2,7 @@ # CXSparse Lib/Makefile #------------------------------------------------------------------------------- @@ -301,7 +294,7 @@ diff -ruN a/CXSparse_newfiles/Lib/Makefile b/CXSparse_newfiles/Lib/Makefile VERSION = 3.2.0 SO_VERSION = 3 -@@ -113,18 +113,21 @@ +@@ -113,18 +113,21 @@ distclean: clean - $(RM) -r $(PURGE) # install CXSparse @@ -326,9 +319,10 @@ diff -ruN a/CXSparse_newfiles/Lib/Makefile b/CXSparse_newfiles/Lib/Makefile chmod 644 $(INSTALL_INCLUDE)/cs.h chmod 644 $(INSTALL_DOC)/CXSPARSE_README.txt -diff -ruN a/GPUQREngine/Lib/Makefile b/GPUQREngine/Lib/Makefile ---- a/GPUQREngine/Lib/Makefile 2019-12-02 16:04:50.027276700 +0100 -+++ b/GPUQREngine/Lib/Makefile 2019-12-02 18:12:07.617802700 +0100 +diff --git a/GPUQREngine/Lib/Makefile b/GPUQREngine/Lib/Makefile +index adf46039..7bb34807 100644 +--- a/GPUQREngine/Lib/Makefile ++++ b/GPUQREngine/Lib/Makefile @@ -2,7 +2,7 @@ # GPUQREngine/Lib/Makefile: for compiling the GPUQREngine library #------------------------------------------------------------------------------- @@ -338,7 +332,7 @@ diff -ruN a/GPUQREngine/Lib/Makefile b/GPUQREngine/Lib/Makefile VERSION = 1.0.5 SO_VERSION = 1 -@@ -129,17 +129,20 @@ +@@ -129,17 +129,20 @@ $(AR_TARGET): $(OBJS) #------------------------------------------------------------------------------- # install GPUQREngine. Note that the include files are not installed. @@ -362,9 +356,10 @@ diff -ruN a/GPUQREngine/Lib/Makefile b/GPUQREngine/Lib/Makefile chmod 644 $(INSTALL_DOC)/GPUQRENGINE_README.txt # uninstall GPUQREngine -diff -ruN a/KLU/Lib/Makefile b/KLU/Lib/Makefile ---- a/KLU/Lib/Makefile 2019-12-02 16:04:51.621283700 +0100 -+++ b/KLU/Lib/Makefile 2019-12-02 18:12:09.148789600 +0100 +diff --git a/KLU/Lib/Makefile b/KLU/Lib/Makefile +index 2dc62cb4..35e2ed3a 100644 +--- a/KLU/Lib/Makefile ++++ b/KLU/Lib/Makefile @@ -2,7 +2,7 @@ # KLU Lib/Makefile #------------------------------------------------------------------------------- @@ -374,7 +369,7 @@ diff -ruN a/KLU/Lib/Makefile b/KLU/Lib/Makefile VERSION = 1.3.8 SO_VERSION = 1 -@@ -263,19 +263,22 @@ +@@ -263,19 +263,22 @@ klu_l_memory.o: ../Source/klu_memory.c #------------------------------------------------------------------------------- # install KLU @@ -400,9 +395,10 @@ diff -ruN a/KLU/Lib/Makefile b/KLU/Lib/Makefile chmod 644 $(INSTALL_INCLUDE)/klu.h chmod 644 $(INSTALL_DOC)/KLU_UserGuide.pdf chmod 644 $(INSTALL_DOC)/KLU_README.txt -diff -ruN a/LDL/Lib/Makefile b/LDL/Lib/Makefile ---- a/LDL/Lib/Makefile 2019-12-02 16:04:51.803294200 +0100 -+++ b/LDL/Lib/Makefile 2019-12-02 18:12:17.500303700 +0100 +diff --git a/LDL/Lib/Makefile b/LDL/Lib/Makefile +index 604ffa27..da5ecd53 100644 +--- a/LDL/Lib/Makefile ++++ b/LDL/Lib/Makefile @@ -2,7 +2,7 @@ # LDL Lib/Makefile #------------------------------------------------------------------------------- @@ -412,7 +408,7 @@ diff -ruN a/LDL/Lib/Makefile b/LDL/Lib/Makefile VERSION = 2.2.6 SO_VERSION = 2 -@@ -46,19 +46,22 @@ +@@ -46,19 +46,22 @@ clean: - $(RM) -r $(CLEAN) # install LDL @@ -438,9 +434,10 @@ diff -ruN a/LDL/Lib/Makefile b/LDL/Lib/Makefile chmod 644 $(INSTALL_INCLUDE)/ldl.h chmod 644 $(INSTALL_DOC)/ldl_userguide.pdf chmod 644 $(INSTALL_DOC)/LDL_README.txt -diff -ruN a/RBio/Lib/Makefile b/RBio/Lib/Makefile ---- a/RBio/Lib/Makefile 2019-12-02 16:05:04.771295500 +0100 -+++ b/RBio/Lib/Makefile 2019-12-02 18:12:09.016789800 +0100 +diff --git a/RBio/Lib/Makefile b/RBio/Lib/Makefile +index 056715de..4ba42157 100644 +--- a/RBio/Lib/Makefile ++++ b/RBio/Lib/Makefile @@ -2,7 +2,7 @@ # RBio/Lib/Makefile: for compiling the RBio library #=============================================================================== @@ -450,7 +447,7 @@ diff -ruN a/RBio/Lib/Makefile b/RBio/Lib/Makefile VERSION = 2.2.6 SO_VERSION = 2 -@@ -60,18 +60,21 @@ +@@ -60,18 +60,21 @@ RBio_i.o: ../Source/RBio.c #------------------------------------------------------------------------------- # install RBio @@ -475,9 +472,49 @@ diff -ruN a/RBio/Lib/Makefile b/RBio/Lib/Makefile chmod 644 $(INSTALL_INCLUDE)/RBio.h chmod 644 $(INSTALL_DOC)/RBIO_README.txt -diff -ruN a/SPQR/Lib/Makefile b/SPQR/Lib/Makefile ---- a/SPQR/Lib/Makefile 2019-12-02 16:04:50.190277100 +0100 -+++ b/SPQR/Lib/Makefile 2019-12-02 18:12:07.955789500 +0100 +diff --git a/SLIP_LU/Lib/Makefile b/SLIP_LU/Lib/Makefile +index 8b2221f1..b2153824 100644 +--- a/SLIP_LU/Lib/Makefile ++++ b/SLIP_LU/Lib/Makefile +@@ -8,7 +8,7 @@ + # To run a demo using the library + # cd ../Demo ; make + +-LIBRARY = libsliplu ++LIBRARY = sliplu + VERSION = 1.0.2 + SO_VERSION = 1 + +@@ -63,19 +63,22 @@ $(AR_TARGET): $(OBJ) + #------------------------------------------------------------------------------- + + # install SLIP_LU +-install: $(AR_TARGET) $(INSTALL_LIB)/$(SO_TARGET) ++install: $(AR_TARGET) $(INSTALL_SO) + +-$(INSTALL_LIB)/$(SO_TARGET): $(OBJ) ++$(INSTALL_SO): $(OBJ) + @mkdir -p $(INSTALL_LIB) ++ @mkdir -p $(dir $(INSTALL_SO)) + @mkdir -p $(INSTALL_INCLUDE) + @mkdir -p $(INSTALL_DOC) + $(CC) $(SO_OPTS) $^ -o $@ $(LDLIBS) ++ifneq ($(UNAME),Cygwin) + ( cd $(INSTALL_LIB) ; ln -sf $(SO_TARGET) $(SO_PLAIN) ) + ( cd $(INSTALL_LIB) ; ln -sf $(SO_TARGET) $(SO_MAIN) ) ++ chmod 755 $(INSTALL_LIB)/$(SO_TARGET) ++endif + $(CP) ../Include/SLIP_LU.h $(INSTALL_INCLUDE) + $(CP) ../Doc/SLIP_LU_UserGuide.pdf $(INSTALL_DOC) + $(CP) ../README.md $(INSTALL_DOC)/SLIP_LU_README.md +- chmod 755 $(INSTALL_LIB)/$(SO_TARGET) + chmod 644 $(INSTALL_INCLUDE)/SLIP_LU.h + chmod 644 $(INSTALL_DOC)/SLIP_LU_UserGuide.pdf + chmod 644 $(INSTALL_DOC)/SLIP_LU_README.md +diff --git a/SPQR/Lib/Makefile b/SPQR/Lib/Makefile +index 06c142ae..10e32007 100644 +--- a/SPQR/Lib/Makefile ++++ b/SPQR/Lib/Makefile @@ -2,7 +2,7 @@ # SuiteSparseQR/Lib/Makefile #=============================================================================== @@ -487,7 +524,7 @@ diff -ruN a/SPQR/Lib/Makefile b/SPQR/Lib/Makefile VERSION = 2.0.9 SO_VERSION = 2 -@@ -242,22 +242,25 @@ +@@ -242,22 +242,25 @@ spqrgpu_computeFrontStaging.o: ../SPQRGPU/spqrgpu_computeFrontStaging.cpp #------------------------------------------------------------------------------- # install SPQR @@ -516,19 +553,57 @@ diff -ruN a/SPQR/Lib/Makefile b/SPQR/Lib/Makefile chmod 644 $(INSTALL_INCLUDE)/SuiteSparseQR.hpp chmod 644 $(INSTALL_INCLUDE)/SuiteSparseQR_C.h chmod 644 $(INSTALL_INCLUDE)/SuiteSparseQR_definitions.h -diff -ruN a/SuiteSparse_config/Makefile b/SuiteSparse_config/Makefile ---- a/SuiteSparse_config/Makefile 2019-12-02 16:05:03.939278100 +0100 -+++ b/SuiteSparse_config/Makefile 2019-12-02 18:12:16.275303600 +0100 -@@ -6,7 +6,7 @@ +diff --git a/SuiteSparse_GPURuntime/Lib/Makefile b/SuiteSparse_GPURuntime/Lib/Makefile +index 31f536a5..2f6c54d0 100644 +--- a/SuiteSparse_GPURuntime/Lib/Makefile ++++ b/SuiteSparse_GPURuntime/Lib/Makefile +@@ -2,7 +2,7 @@ + # SuiteSparse_GPURuntime/Lib/Makfile + #------------------------------------------------------------------------------- + +-LIBRARY = libSuiteSparse_GPURuntime ++LIBRARY = SuiteSparse_GPURuntime + VERSION = 1.0.5 + SO_VERSION = 1 + +@@ -70,17 +70,20 @@ SuiteSparseGPU_Workspace_transfer.o: ../Source/SuiteSparseGPU_Workspace_transfer + #------------------------------------------------------------------------------- + + # install SuiteSparse_GPURuntime (just the library, not the include files) +-install: $(AR_TARGET) $(INSTALL_LIB)/$(SO_TARGET) ++install: $(AR_TARGET) $(INSTALL_SO) + +-$(INSTALL_LIB)/$(SO_TARGET): $(OBJS) ++$(INSTALL_SO): $(OBJS) + @mkdir -p $(INSTALL_LIB) ++ @mkdir -p $(dir $(INSTALL_SO)) + @mkdir -p $(INSTALL_INCLUDE) + @mkdir -p $(INSTALL_DOC) + $(CXX) $(SO_OPTS) $^ -o $@ $(LDLIBS) ++ifneq ($(UNAME),Cygwin) + ( cd $(INSTALL_LIB) ; ln -sf $(SO_TARGET) $(SO_PLAIN) ) + ( cd $(INSTALL_LIB) ; ln -sf $(SO_TARGET) $(SO_MAIN) ) +- $(CP) ../README.txt $(INSTALL_DOC)/GPURUNTIME_README.txt + chmod 755 $(INSTALL_LIB)/$(SO_TARGET) ++endif ++ $(CP) ../README.txt $(INSTALL_DOC)/GPURUNTIME_README.txt + chmod 644 $(INSTALL_DOC)/GPURUNTIME_README.txt + + # uninstall SuiteSparse_GPURuntime +diff --git a/SuiteSparse_config/Makefile b/SuiteSparse_config/Makefile +index 6a5814d4..e2acc53e 100644 +--- a/SuiteSparse_config/Makefile ++++ b/SuiteSparse_config/Makefile +@@ -6,7 +6,7 @@ SUITESPARSE ?= $(realpath $(CURDIR)/..) export SUITESPARSE # version of SuiteSparse_config is also version of SuiteSparse meta-package -LIBRARY = libsuitesparseconfig +LIBRARY = suitesparseconfig - VERSION = 5.5.0 + VERSION = 5.10.1 SO_VERSION = 5 -@@ -44,19 +44,23 @@ +@@ -44,19 +44,23 @@ clean: - $(RM) -r $(CLEAN) # install SuiteSparse_config @@ -556,26 +631,13 @@ diff -ruN a/SuiteSparse_config/Makefile b/SuiteSparse_config/Makefile chmod 644 $(INSTALL_INCLUDE)/SuiteSparse_config.h chmod 644 $(INSTALL_DOC)/SUITESPARSECONFIG_README.txt -diff -ruN a/SuiteSparse_config/SuiteSparse_config.mk b/SuiteSparse_config/SuiteSparse_config.mk ---- a/SuiteSparse_config/SuiteSparse_config.mk 2019-12-02 16:05:03.944279100 +0100 -+++ b/SuiteSparse_config/SuiteSparse_config.mk 2019-12-02 18:12:16.277302200 +0100 -@@ -153,6 +153,11 @@ - # It places its shared *.so libraries in SuiteSparse/lib. - # Linux also requires the -lrt library (see below) - LDLIBS ?= -lm -+ # Note: Because suitesparse doesn't really do install staging properly -+ # (it just outputs final build artifacts directly to their install paths) -+ # we must add that path to our linker flags in order to link properly -+ # against built libraries; it might be better to fix the whole build -+ # system though). - LDFLAGS += -L$(INSTALL_LIB) - - # NOTE: Use of the Intel MKL BLAS is strongly recommended. The OpenBLAS can -@@ -340,16 +346,15 @@ - #--------------------------------------------------------------------------- - +diff --git a/SuiteSparse_config/SuiteSparse_config.mk b/SuiteSparse_config/SuiteSparse_config.mk +index 61f0076e..5caa89dd 100644 +--- a/SuiteSparse_config/SuiteSparse_config.mk ++++ b/SuiteSparse_config/SuiteSparse_config.mk +@@ -16,14 +16,14 @@ SUITESPARSE_VERSION = 5.10.1 # To disable these auto configurations, use 'make UNAME=custom' -- + ifndef UNAME - ifeq ($(OS),Windows_NT) - # Cygwin Make on Windows has an $(OS) variable, but not uname. @@ -595,8 +657,20 @@ diff -ruN a/SuiteSparse_config/SuiteSparse_config.mk b/SuiteSparse_config/SuiteS + UNAME := Cygwin endif - #--------------------------------------------------------------------------- -@@ -446,21 +451,23 @@ + #=============================================================================== +@@ -170,6 +170,11 @@ SUITESPARSE_VERSION = 5.10.1 + # It places its shared *.so libraries in SuiteSparse/lib. + # Linux also requires the -lrt library (see below) + LDLIBS ?= -lm ++ # Note: Because suitesparse doesn't really do install staging properly ++ # (it just outputs final build artifacts directly to their install paths) ++ # we must add that path to our linker flags in order to link properly ++ # against built libraries; it might be better to fix the whole build ++ # system though). + LDFLAGS += -L$(INSTALL_LIB) + + # NOTE: Use of the Intel MKL BLAS is strongly recommended. The OpenBLAS can +@@ -447,21 +452,23 @@ SUITESPARSE_VERSION = 5.10.1 SO_OPTS = $(LDFLAGS) @@ -630,8 +704,8 @@ diff -ruN a/SuiteSparse_config/SuiteSparse_config.mk b/SuiteSparse_config/SuiteS + SO_TARGET = lib$(LIBRARY).$(VERSION).dylib SO_OPTS += -dynamiclib -compatibility_version $(SO_VERSION) \ -current_version $(VERSION) \ - -shared -undefined dynamic_lookup -@@ -469,9 +476,9 @@ + -Wl,-install_name -Wl,$(INSTALL)/lib/$(SO_MAIN) \ +@@ -471,9 +478,9 @@ else SO_INSTALL_NAME = install_name_tool -id else # Linux and other variants of Unix @@ -644,10 +718,11 @@ diff -ruN a/SuiteSparse_config/SuiteSparse_config.mk b/SuiteSparse_config/SuiteS SO_OPTS += -shared -Wl,-soname -Wl,$(SO_MAIN) -Wl,--no-undefined # Linux/Unix *.so files can be moved without modification: SO_INSTALL_NAME = echo -diff -ruN a/SuiteSparse_config/xerbla/Makefile b/SuiteSparse_config/xerbla/Makefile ---- a/SuiteSparse_config/xerbla/Makefile 2019-12-02 16:05:03.949290500 +0100 -+++ b/SuiteSparse_config/xerbla/Makefile 2019-12-02 18:12:16.284303200 +0100 -@@ -16,9 +16,9 @@ +diff --git a/SuiteSparse_config/xerbla/Makefile b/SuiteSparse_config/xerbla/Makefile +index db68a2ea..c59820c2 100644 +--- a/SuiteSparse_config/xerbla/Makefile ++++ b/SuiteSparse_config/xerbla/Makefile +@@ -16,9 +16,9 @@ library: all: library ifeq ($(USE_FORTRAN),0) @@ -659,7 +734,7 @@ diff -ruN a/SuiteSparse_config/xerbla/Makefile b/SuiteSparse_config/xerbla/Makef endif include ../SuiteSparse_config.mk -@@ -44,19 +44,22 @@ +@@ -44,19 +44,22 @@ $(AR_TARGET): $(DEPENDS) - $(RM) xerbla.o # install libcerbla / libxerbla @@ -685,45 +760,10 @@ diff -ruN a/SuiteSparse_config/xerbla/Makefile b/SuiteSparse_config/xerbla/Makef chmod 644 $(INSTALL_INCLUDE)/xerbla.h # uninstall libcerbla / libxerbla -diff -ruN a/SuiteSparse_GPURuntime/Lib/Makefile b/SuiteSparse_GPURuntime/Lib/Makefile ---- a/SuiteSparse_GPURuntime/Lib/Makefile 2019-12-02 16:04:57.931276800 +0100 -+++ b/SuiteSparse_GPURuntime/Lib/Makefile 2019-12-02 18:12:08.912790400 +0100 -@@ -2,7 +2,7 @@ - # SuiteSparse_GPURuntime/Lib/Makfile - #------------------------------------------------------------------------------- - --LIBRARY = libSuiteSparse_GPURuntime -+LIBRARY = SuiteSparse_GPURuntime - VERSION = 1.0.5 - SO_VERSION = 1 - -@@ -70,17 +70,20 @@ - #------------------------------------------------------------------------------- - - # install SuiteSparse_GPURuntime (just the library, not the include files) --install: $(AR_TARGET) $(INSTALL_LIB)/$(SO_TARGET) -+install: $(AR_TARGET) $(INSTALL_SO) - --$(INSTALL_LIB)/$(SO_TARGET): $(OBJS) -+$(INSTALL_SO): $(OBJS) - @mkdir -p $(INSTALL_LIB) -+ @mkdir -p $(dir $(INSTALL_SO)) - @mkdir -p $(INSTALL_INCLUDE) - @mkdir -p $(INSTALL_DOC) - $(CXX) $(SO_OPTS) $^ -o $@ $(LDLIBS) -+ifneq ($(UNAME),Cygwin) - ( cd $(INSTALL_LIB) ; ln -sf $(SO_TARGET) $(SO_PLAIN) ) - ( cd $(INSTALL_LIB) ; ln -sf $(SO_TARGET) $(SO_MAIN) ) -- $(CP) ../README.txt $(INSTALL_DOC)/GPURUNTIME_README.txt - chmod 755 $(INSTALL_LIB)/$(SO_TARGET) -+endif -+ $(CP) ../README.txt $(INSTALL_DOC)/GPURUNTIME_README.txt - chmod 644 $(INSTALL_DOC)/GPURUNTIME_README.txt - - # uninstall SuiteSparse_GPURuntime -diff -ruN a/UMFPACK/Lib/Makefile b/UMFPACK/Lib/Makefile ---- a/UMFPACK/Lib/Makefile 2019-12-02 16:05:03.957276500 +0100 -+++ b/UMFPACK/Lib/Makefile 2019-12-02 18:12:17.206308800 +0100 +diff --git a/UMFPACK/Lib/Makefile b/UMFPACK/Lib/Makefile +index 78436de1..0f291068 100644 +--- a/UMFPACK/Lib/Makefile ++++ b/UMFPACK/Lib/Makefile @@ -2,7 +2,7 @@ # UMFPACK Makefile for compiling on Unix systems #------------------------------------------------------------------------------- @@ -733,7 +773,7 @@ diff -ruN a/UMFPACK/Lib/Makefile b/UMFPACK/Lib/Makefile VERSION = 5.7.9 SO_VERSION = 5 -@@ -288,20 +288,23 @@ +@@ -288,20 +288,23 @@ clean: #------------------------------------------------------------------------------- # install UMFPACK diff --git a/build/pkgs/suitesparse/spkg-install.in b/build/pkgs/suitesparse/spkg-install.in index 85acff88db1..b6944a409ba 100644 --- a/build/pkgs/suitesparse/spkg-install.in +++ b/build/pkgs/suitesparse/spkg-install.in @@ -1,5 +1,8 @@ +# -*- shell-script -*- cd src +export CC="${CC-gcc} -std=c99" + echo "print configuration" $MAKE MY_METIS_LIB=none CHOLMOD_CONFIG=-DNPARTITION \ BLAS="$(pkg-config --libs blas)" LAPACK="$(pkg-config --libs lapack)" \ diff --git a/configure.ac b/configure.ac index e331c30435d..7156bf4aecf 100644 --- a/configure.ac +++ b/configure.ac @@ -428,6 +428,14 @@ AS_IF([test "x$enable_download_from_upstream_url" = "xyes"], [ ]) AC_SUBST([SAGE_SPKG_OPTIONS]) +AC_ARG_ENABLE([cvxopt], + AS_HELP_STRING([--disable-cvxopt], + [disable build of the cvxopt package and its prerequisite suitesparse]), [ + for pkg in cvxopt suitesparse; do + AS_VAR_SET([SAGE_ENABLE_$pkg], [$enableval]) + done + ]) + AC_ARG_ENABLE([notebook], AS_HELP_STRING([--disable-notebook], [disable build of the Jupyter notebook and related packages]), [ diff --git a/src/VERSION.txt b/src/VERSION.txt index 0439ed38bee..2b12acd816c 100644 --- a/src/VERSION.txt +++ b/src/VERSION.txt @@ -1 +1 @@ -9.4.beta5 +9.4.beta6 diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index 4595e6aee5d..901e9d7e877 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.4.beta5' -SAGE_RELEASE_DATE='2021-07-18' -SAGE_VERSION_BANNER='SageMath version 9.4.beta5, Release Date: 2021-07-18' +SAGE_VERSION='9.4.beta6' +SAGE_RELEASE_DATE='2021-07-24' +SAGE_VERSION_BANNER='SageMath version 9.4.beta6, Release Date: 2021-07-24' diff --git a/src/doc/de/tutorial/latex.rst b/src/doc/de/tutorial/latex.rst index 7cf8e650784..ac186600cec 100644 --- a/src/doc/de/tutorial/latex.rst +++ b/src/doc/de/tutorial/latex.rst @@ -387,7 +387,7 @@ eingestellt sind). LaTeX example for testing display of a commutative diagram produced by xypic. - To use, try to view this object -- it won't work. Now try + To use, try to view this object -- it will not work. Now try 'latex.add_to_preamble("\\usepackage[matrix,arrow,curve,cmtip]{xy}")', and try viewing again. You should get a picture (a part of the diagram arising from a filtered chain complex). diff --git a/src/doc/en/developer/doctesting.rst b/src/doc/en/developer/doctesting.rst index 87ed66c3bdc..f5fec4590f3 100644 --- a/src/doc/en/developer/doctesting.rst +++ b/src/doc/en/developer/doctesting.rst @@ -1010,8 +1010,8 @@ a Python exception occurs. As an example, I modified 152 ainvs = [K(0),K(0),K(0)] + ainvs 153 self.__ainvs = tuple(ainvs) 154 if self.discriminant() == 0: - 155 raise ArithmeticError, \ - 156 -> "Invariants %s define a singular curve."%ainvs + 155 raise ArithmeticError( + 156 -> "Invariants %s define a singular curve."%ainvs) 157 PP = projective_space.ProjectiveSpace(2, K, names='xyz'); 158 x, y, z = PP.coordinate_ring().gens() 159 a1, a2, a3, a4, a6 = ainvs diff --git a/src/doc/en/reference/algebras/index.rst b/src/doc/en/reference/algebras/index.rst index 1a415c7c337..b0eb4ad2edb 100644 --- a/src/doc/en/reference/algebras/index.rst +++ b/src/doc/en/reference/algebras/index.rst @@ -54,9 +54,10 @@ Named associative algebras sage/combinat/posets/moebius_algebra sage/algebras/orlik_terao sage/algebras/orlik_solomon - sage/algebras/quantum_matrix_coordinate_algebra sage/combinat/partition_algebra + sage/algebras/quantum_clifford sage/algebras/quantum_groups/quantum_group_gap + sage/algebras/quantum_matrix_coordinate_algebra sage/algebras/quatalg/quaternion_algebra sage/algebras/rational_cherednik_algebra sage/algebras/schur_algebra diff --git a/src/doc/en/reference/discrete_geometry/index.rst b/src/doc/en/reference/discrete_geometry/index.rst index 6975fa144a7..a5d9663c5d7 100644 --- a/src/doc/en/reference/discrete_geometry/index.rst +++ b/src/doc/en/reference/discrete_geometry/index.rst @@ -47,6 +47,14 @@ Lattice polyhedra sage/geometry/polyhedron/ppl_lattice_polygon sage/geometry/polyhedron/ppl_lattice_polytope +Polyhedral complexes +~~~~~~~~~~~~~~~~~~~~ + +.. toctree:: + :maxdepth: 1 + + sage/geometry/polyhedral_complex + Toric geometry ~~~~~~~~~~~~~~ diff --git a/src/doc/en/reference/game_theory/index.rst b/src/doc/en/reference/game_theory/index.rst index 3e43301834e..1bee7a0d46d 100644 --- a/src/doc/en/reference/game_theory/index.rst +++ b/src/doc/en/reference/game_theory/index.rst @@ -9,5 +9,6 @@ Game Theory sage/game_theory/normal_form_game sage/game_theory/catalog_normal_form_games sage/game_theory/gambit_docs + sage/game_theory/parser .. include:: ../footer.txt diff --git a/src/doc/en/reference/modules/index.rst b/src/doc/en/reference/modules/index.rst index 0975f276ffb..d5ff3a70986 100644 --- a/src/doc/en/reference/modules/index.rst +++ b/src/doc/en/reference/modules/index.rst @@ -33,6 +33,7 @@ Modules sage/modules/with_basis/__init__ sage/modules/with_basis/cell_module + sage/modules/with_basis/invariant sage/modules/with_basis/morphism sage/modules/with_basis/subquotient sage/modules/with_basis/representation diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 0021e5b9d2e..6bf9c440b62 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -2781,6 +2781,10 @@ REFERENCES: .. [Hat2002] Allen Hatcher, "Algebraic Topology", Cambridge University Press (2002). +.. [Hayashi1990] \T. Hayashi. `q`-*analogues of Clifford and Weyl algebras - + spinor and oscillator representations of quantum enveloping + algebras*. Comm. Math. Phys. **127** (1990) pp. 129-144. + .. [HC2006] Mark van Hoeij and John Cremona, Solving Conics over function fields. J. Théor. Nombres Bordeaux, 2006. @@ -3644,6 +3648,11 @@ REFERENCES: International Mathematics Research Notices. Vol. 2014, No. 2, pp. 512-550 (2012) +.. [Kwon2014] Jae-Hoon Kwon. `q`-*deformed Clifford algebra and level zero + fundamental representations of quantum affine algebras*. + J. Algebra, **399** (2014), pp. 927--947. + :doi:`10.1016/j.jalgebra.2013.10.026`. + .. [KX1998] \S. König and C. Xi. *On the structure of cellular algebras*. Algebras and modules, II (Geiranger, 1996), 365–386, diff --git a/src/doc/en/reference/sets/index.rst b/src/doc/en/reference/sets/index.rst index 8631e55850a..5f8eb46a83e 100644 --- a/src/doc/en/reference/sets/index.rst +++ b/src/doc/en/reference/sets/index.rst @@ -15,6 +15,7 @@ Set Constructions sage/sets/set_from_iterator sage/sets/finite_enumerated_set sage/sets/recursively_enumerated_set + sage/sets/condition_set sage/sets/finite_set_maps sage/sets/finite_set_map_cy sage/sets/totally_ordered_finite_set diff --git a/src/doc/en/reference/structure/index.rst b/src/doc/en/reference/structure/index.rst index a24b48183a6..0e74c143bf5 100644 --- a/src/doc/en/reference/structure/index.rst +++ b/src/doc/en/reference/structure/index.rst @@ -89,7 +89,6 @@ Internals sage/structure/debug_options sage/structure/list_clone_timings sage/structure/list_clone_timings_cy - sage/structure/misc sage/structure/test_factory .. include:: ../footer.txt diff --git a/src/doc/en/thematic_tutorials/numerical_sage/cvxopt.rst b/src/doc/en/thematic_tutorials/numerical_sage/cvxopt.rst index e5f54ec4c2c..7b365f60ba4 100644 --- a/src/doc/en/thematic_tutorials/numerical_sage/cvxopt.rst +++ b/src/doc/en/thematic_tutorials/numerical_sage/cvxopt.rst @@ -28,15 +28,15 @@ by :: - sage: import numpy - sage: from cvxopt.base import spmatrix - sage: from cvxopt.base import matrix as m - sage: from cvxopt import umfpack - sage: Integer=int - sage: V = [2,3, 3,-1,4, 4,-3,1,2, 2, 6,1] - sage: I = [0,1, 0, 2,4, 1, 2,3,4, 2, 1,4] - sage: J = [0,0, 1, 1,1, 2, 2,2,2, 3, 4,4] - sage: A = spmatrix(V,I,J) + sage: import numpy # optional - cvxopt + sage: from cvxopt.base import spmatrix # optional - cvxopt + sage: from cvxopt.base import matrix as m # optional - cvxopt + sage: from cvxopt import umfpack # optional - cvxopt + sage: Integer=int # optional - cvxopt + sage: V = [2,3, 3,-1,4, 4,-3,1,2, 2, 6,1] # optional - cvxopt + sage: I = [0,1, 0, 2,4, 1, 2,3,4, 2, 1,4] # optional - cvxopt + sage: J = [0,0, 1, 1,1, 2, 2,2,2, 3, 4,4] # optional - cvxopt + sage: A = spmatrix(V,I,J) # optional - cvxopt To solve an equation :math:`AX=B`, with :math:`B=[1,1,1,1,1]`, we could do the following. @@ -45,9 +45,9 @@ we could do the following. :: - sage: B = numpy.array([1.0]*5) - sage: B.shape=(5,1) - sage: print(B) + sage: B = numpy.array([1.0]*5) # optional - cvxopt + sage: B.shape=(5,1) # optional - cvxopt + sage: print(B) # optional - cvxopt [[1.] [1.] [1.] @@ -55,15 +55,15 @@ we could do the following. [1.]] - sage: print(A) + sage: print(A) # optional - cvxopt [ 2.00e+00 3.00e+00 0 0 0 ] [ 3.00e+00 0 4.00e+00 0 6.00e+00] [ 0 -1.00e+00 -3.00e+00 2.00e+00 0 ] [ 0 0 1.00e+00 0 0 ] [ 0 4.00e+00 2.00e+00 0 1.00e+00] - sage: C=m(B) - sage: umfpack.linsolve(A,C) - sage: print(C) + sage: C=m(B) # optional - cvxopt + sage: umfpack.linsolve(A,C) # optional - cvxopt + sage: print(C) # optional - cvxopt [ 5.79e-01] [-5.26e-02] [ 1.00e+00] @@ -81,13 +81,13 @@ We could compute the approximate minimum degree ordering by doing :: - sage: RealNumber=float - sage: Integer=int - sage: from cvxopt.base import spmatrix - sage: from cvxopt import amd - sage: A=spmatrix([10,3,5,-2,5,2],[0,2,1,2,2,3],[0,0,1,1,2,3]) - sage: P=amd.order(A) - sage: print(P) + sage: RealNumber=float # optional - cvxopt + sage: Integer=int # optional - cvxopt + sage: from cvxopt.base import spmatrix # optional - cvxopt + sage: from cvxopt import amd # optional - cvxopt + sage: A=spmatrix([10,3,5,-2,5,2],[0,2,1,2,2,3],[0,0,1,1,2,3]) # optional - cvxopt + sage: P=amd.order(A) # optional - cvxopt + sage: print(P) # optional - cvxopt [ 1] [ 0] [ 2] @@ -108,14 +108,14 @@ For a simple linear programming example, if we want to solve :: - sage: RealNumber=float - sage: Integer=int - sage: from cvxopt.base import matrix as m - sage: from cvxopt import solvers - sage: c = m([-4., -5.]) - sage: G = m([[2., 1., -1., 0.], [1., 2., 0., -1.]]) - sage: h = m([3., 3., 0., 0.]) - sage: sol = solvers.lp(c,G,h) #random + sage: RealNumber=float # optional - cvxopt + sage: Integer=int # optional - cvxopt + sage: from cvxopt.base import matrix as m # optional - cvxopt + sage: from cvxopt import solvers # optional - cvxopt + sage: c = m([-4., -5.]) # optional - cvxopt + sage: G = m([[2., 1., -1., 0.], [1., 2., 0., -1.]]) # optional - cvxopt + sage: h = m([3., 3., 0., 0.]) # optional - cvxopt + sage: sol = solvers.lp(c,G,h) # random # optional - cvxopt pcost dcost gap pres dres k/t 0: -8.1000e+00 -1.8300e+01 4e+00 0e+00 8e-01 1e+00 1: -8.8055e+00 -9.4357e+00 2e-01 1e-16 4e-02 3e-02 @@ -127,7 +127,7 @@ For a simple linear programming example, if we want to solve :: - sage: print(sol['x']) # ... below since can get -00 or +00 depending on architecture + sage: print(sol['x']) # optional - cvxopt # ... below since can get -00 or +00 depending on architecture [ 1.00e...00] [ 1.00e+00] diff --git a/src/doc/en/tutorial/latex.rst b/src/doc/en/tutorial/latex.rst index 0f2e7e47cc1..1e2dadbbfa2 100644 --- a/src/doc/en/tutorial/latex.rst +++ b/src/doc/en/tutorial/latex.rst @@ -383,7 +383,7 @@ properly. To actually see the examples, it is necessary to use LaTeX example for testing display of a commutative diagram produced by xypic. - To use, try to view this object -- it won't work. Now try + To use, try to view this object -- it will not work. Now try 'latex.add_to_preamble("\\usepackage[matrix,arrow,curve,cmtip]{xy}")', and try viewing again. You should get a picture (a part of the diagram arising from a filtered chain complex). diff --git a/src/doc/fr/tutorial/latex.rst b/src/doc/fr/tutorial/latex.rst index 4ec9426700b..542c557ac17 100644 --- a/src/doc/fr/tutorial/latex.rst +++ b/src/doc/fr/tutorial/latex.rst @@ -367,7 +367,7 @@ la commande ``view()`` pour visualiser l'exemple. :: LaTeX example for testing display of a commutative diagram produced by xypic. - To use, try to view this object -- it won't work. Now try + To use, try to view this object -- it will not work. Now try 'latex.add_to_preamble("\\usepackage[matrix,arrow,curve,cmtip]{xy}")', and try viewing again. You should get a picture (a part of the diagram arising from a filtered chain complex). diff --git a/src/doc/ja/tutorial/latex.rst b/src/doc/ja/tutorial/latex.rst index c737dbf0146..11d8e2910c8 100644 --- a/src/doc/ja/tutorial/latex.rst +++ b/src/doc/ja/tutorial/latex.rst @@ -329,7 +329,7 @@ LaTeX表式とLaTeXエンジンの生成するdvi形式にdvipngが扱えないs LaTeX example for testing display of a commutative diagram produced by xypic. - To use, try to view this object -- it won't work. Now try + To use, try to view this object -- it will not work. Now try 'latex.add_to_preamble("\\usepackage[matrix,arrow,curve,cmtip]{xy}")', and try viewing again. You should get a picture (a part of the diagram arising from a filtered chain complex). diff --git a/src/doc/pt/tutorial/latex.rst b/src/doc/pt/tutorial/latex.rst index cba4fd4344e..fe84b74027a 100644 --- a/src/doc/pt/tutorial/latex.rst +++ b/src/doc/pt/tutorial/latex.rst @@ -395,7 +395,7 @@ processador, etc. estão configurados corretamente). LaTeX example for testing display of a commutative diagram produced by xypic. - To use, try to view this object -- it won't work. Now try + To use, try to view this object -- it will not work. Now try 'latex.add_to_preamble("\\usepackage[matrix,arrow,curve,cmtip]{xy}")', and try viewing again. You should get a picture (a part of the diagram arising from a filtered chain complex). diff --git a/src/sage/algebras/catalog.py b/src/sage/algebras/catalog.py index 6c02093c4d3..c321572cf54 100644 --- a/src/sage/algebras/catalog.py +++ b/src/sage/algebras/catalog.py @@ -49,10 +49,12 @@ ` - :class:`algebras.OrlikSolomon ` -- :class:`algebras.QuantumMatrixCoordinate - ` +- :class:`algebras.QuantumClifford + ` - :class:`algebras.QuantumGL ` +- :class:`algebras.QuantumMatrixCoordinate + ` - :class:`algebras.QSym ` - :class:`algebras.Partition ` - :class:`algebras.PlanarPartition ` @@ -115,6 +117,7 @@ lazy_import('sage.combinat.chas.fsym', 'FreeSymmetricFunctions', 'FSym') lazy_import('sage.combinat.ncsf_qsym.qsym', 'QuasiSymmetricFunctions', 'QSym') lazy_import('sage.combinat.grossman_larson_algebras', 'GrossmanLarsonAlgebra', 'GrossmanLarson') +lazy_import('sage.algebras.quantum_clifford', 'QuantumCliffordAlgebra', 'QuantumClifford') lazy_import('sage.algebras.quantum_matrix_coordinate_algebra', 'QuantumMatrixCoordinateAlgebra', 'QuantumMatrixCoordinate') lazy_import('sage.algebras.quantum_matrix_coordinate_algebra', 'QuantumGL') diff --git a/src/sage/algebras/free_algebra_quotient.py b/src/sage/algebras/free_algebra_quotient.py index 13faa60d992..232a5b30f47 100644 --- a/src/sage/algebras/free_algebra_quotient.py +++ b/src/sage/algebras/free_algebra_quotient.py @@ -145,15 +145,15 @@ def __init__(self, A, mons, mats, names): raise TypeError("Argument A must be an algebra.") R = A.base_ring() # if not R.is_field(): # TODO: why? -# raise TypeError, "Base ring of argument A must be a field." +# raise TypeError("Base ring of argument A must be a field.") n = A.ngens() assert n == len(mats) self.__free_algebra = A self.__ngens = n self.__dim = len(mons) - self.__module = FreeModule(R,self.__dim) + self.__module = FreeModule(R, self.__dim) self.__matrix_action = mats - self.__monomial_basis = mons # elements of free monoid + self.__monomial_basis = mons # elements of free monoid Algebra.__init__(self, R, names, normalize=True) def _element_constructor_(self, x): diff --git a/src/sage/algebras/nil_coxeter_algebra.py b/src/sage/algebras/nil_coxeter_algebra.py index 041b9094760..f44039253ac 100644 --- a/src/sage/algebras/nil_coxeter_algebra.py +++ b/src/sage/algebras/nil_coxeter_algebra.py @@ -65,18 +65,16 @@ def __init__(self, W, base_ring = QQ, prefix='u'): self._base_ring = base_ring self._cartan_type = W.cartan_type() H = IwahoriHeckeAlgebra(W, 0, 0, base_ring=base_ring) - super(IwahoriHeckeAlgebra.T,self).__init__(H, prefix=prefix) + super(IwahoriHeckeAlgebra.T, self).__init__(H, prefix=prefix) def _repr_(self): r""" - EXAMPLES :: + EXAMPLES:: sage: NilCoxeterAlgebra(WeylGroup(['A',3,1])) # indirect doctest The Nil-Coxeter Algebra of Type A3~ over Rational Field - """ - - return "The Nil-Coxeter Algebra of Type %s over %s"%(self._cartan_type._repr_(compact=True), self.base_ring()) + return "The Nil-Coxeter Algebra of Type %s over %s" % (self._cartan_type._repr_(compact=True), self.base_ring()) def homogeneous_generator_noncommutative_variables(self, r): r""" diff --git a/src/sage/algebras/quantum_clifford.py b/src/sage/algebras/quantum_clifford.py new file mode 100644 index 00000000000..2835334405b --- /dev/null +++ b/src/sage/algebras/quantum_clifford.py @@ -0,0 +1,550 @@ +r""" +Quantum Clifford Algebras + +AUTHORS: + +- Travis Scrimshaw (2021-05): 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.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.integer_ring import ZZ +from sage.categories.algebras import Algebras +from sage.categories.fields import Fields +from sage.combinat.free_module import CombinatorialFreeModule +from sage.categories.cartesian_product import cartesian_product +from sage.sets.family import Family +from sage.sets.finite_enumerated_set import FiniteEnumeratedSet +from itertools import product + +class QuantumCliffordAlgebra(CombinatorialFreeModule): + r""" + The quantum Clifford algebra. + + The *quantum Clifford algebra*, or `q`-Clifford algebra, + of rank `n` and twist `k` is the unital associative algebra + `\mathrm{Cl}_{q}(n, k)` over a field `F` with generators + `\psi_a, \psi_a^*, \omega_a` for `a = 1, \dotsc, n` that + satisfy the following relations: + + .. MATH:: + + \begin{aligned} + \omega_a \omega_b & = \omega_b \omega_a, + & \omega_a^{4k} & = (1 + q^{-2k}) \omega_a^{2k} - q^{-2k}, + \\ \omega_a \psi_b & = q^{\delta_{ab}} \psi_b \omega_a, + & \omega_a \psi^*_b & = \psi^*_b \omega_a, + \\ \psi_a \psi_b & + \psi_b \psi_a = 0, + & \psi^*_a \psi^*_b & + \psi^*_b \psi^*_a = 0, + \\ \psi_a \psi^*_a & = \frac{q^k \omega_a^{3k} - q^{-k} \omega_a^k}{q^k - q^{-k}}, + & \psi^*_a \psi_a & = \frac{q^{2k} (\omega_a - \omega_a^{3k})}{q^k - q^{-k}}, + \\ \psi_a \psi^*_b & + \psi_b^* \psi_a = 0 + & & \text{if } a \neq b, + \end{aligned} + + where `q \in F` such that `q^{2k} \neq 1`. + + When `k = 2`, we recover the original definition given by Hayashi in + [Hayashi1990]_. The `k = 1` version was used in [Kwon2014]_. + + INPUT: + + - ``n`` -- positive integer; the rank + - ``k`` -- positive integer (default: 1); the twist + - ``q`` -- (optional) the parameter `q` + - ``F`` -- (default: `\QQ(q)`) the base field that contains ``q`` + + EXAMPLES: + + We construct the rank 3 and twist 1 `q`-Clifford algebra:: + + sage: Cl = algebras.QuantumClifford(3) + sage: Cl + Quantum Clifford algebra of rank 3 and twist 1 with q=q over + Fraction Field of Univariate Polynomial Ring in q over Integer Ring + sage: q = Cl.q() + + Some sample computations:: + + sage: p0, p1, p2, d0, d1, d2, w0, w1, w2 = Cl.gens() + sage: p0 * p1 + psi0*psi1 + sage: p1 * p0 + -psi0*psi1 + sage: p0 * w0 * p1 * d0 * w2 + (1/(q^3-q))*psi1*w2 + (-q/(q^2-1))*psi1*w0^2*w2 + sage: w0^4 + -1/q^2 + ((q^2+1)/q^2)*w0^2 + + We construct the homomorphism from `U_q(\mathfrak{sl}_3)` to + `\mathrm{Cl}(3, 1)` given in (3.17) of [Hayashi1990]_:: + + sage: e1 = p0*d1; e2 = p1*d2 + sage: f1 = p1*d0; f2 = p2*d1 + sage: k1 = w0*~w1; k2 = w1*~w2 + sage: k1i = w1*~w0; k2i = w2*~w1 + sage: (e1, e2, f1, f2, k1, k2, k1i, k2i) + (psi0*psid1, psi1*psid2, + -psid0*psi1, -psid1*psi2, + (q^2+1)*w0*w1 - q^2*w0*w1^3, (q^2+1)*w1*w2 - q^2*w1*w2^3, + (q^2+1)*w0*w1 - q^2*w0^3*w1, (q^2+1)*w1*w2 - q^2*w1^3*w2) + + We check that `k_i` and `k_i^{-1}` are inverses:: + + sage: k1 * k1i + 1 + sage: k2 * k2i + 1 + + The relations between `e_i`, `f_i`, and `k_i`:: + + sage: k1 * f1 == q^-2 * f1 * k1 + True + sage: k2 * f1 == q^1 * f1 * k2 + True + sage: k2 * e1 == q^-1 * e1 * k2 + True + sage: k1 * e1 == q^2 * e1 * k1 + True + sage: e1 * f1 - f1 * e1 == (k1 - k1i)/(q-q^-1) + True + sage: e2 * f1 - f1 * e2 + 0 + + The `q`-Serre relations:: + + sage: e1 * e1 * e2 - (q^1 + q^-1) * e1 * e2 * e1 + e2 * e1 * e1 + 0 + sage: f1 * f1 * f2 - (q^1 + q^-1) * f1 * f2 * f1 + f2 * f1 * f1 + 0 + """ + @staticmethod + def __classcall_private__(cls, n, k=1, q=None, F=None): + r""" + Standardize input to ensure a unique representation. + + TESTS:: + + sage: Cl1 = algebras.QuantumClifford(3) + sage: q = PolynomialRing(ZZ, 'q').fraction_field().gen() + sage: Cl2 = algebras.QuantumClifford(3, q=q) + sage: Cl3 = algebras.QuantumClifford(3, 1, q, q.parent()) + sage: Cl1 is Cl2 and Cl2 is Cl3 + True + """ + if q is None: + q = PolynomialRing(ZZ, 'q').fraction_field().gen() + if F is None: + F = q.parent() + q = F(q) + if F not in Fields(): + raise TypeError("base ring must be a field") + return super(QuantumCliffordAlgebra, cls).__classcall__(cls, n, k, q, F) + + def __init__(self, n, k, q, F): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: Cl = algebras.QuantumClifford(1,2) + sage: TestSuite(Cl).run(elements=Cl.basis()) + + sage: Cl = algebras.QuantumClifford(1,3) + sage: TestSuite(Cl).run(elements=Cl.basis()) # long time + + sage: Cl = algebras.QuantumClifford(3) # long time + sage: elts = Cl.some_elements() + list(Cl.algebra_generators()) # long time + sage: TestSuite(Cl).run(elements=elts) # long time + + sage: Cl = algebras.QuantumClifford(2,4) # long time + sage: elts = Cl.some_elements() + list(Cl.algebra_generators()) # long time + sage: TestSuite(Cl).run(elements=elts) # long time + """ + self._n = n + self._k = k + self._q = q + self._psi = cartesian_product([(-1,0,1)]*n) + self._w_poly = PolynomialRing(F, n, 'w') + indices = [(tuple(psi), tuple(w)) + for psi in self._psi + for w in product(*[list(range((4-2*abs(psi[i]))*k)) for i in range(n)])] + indices = FiniteEnumeratedSet(indices) + + cat = Algebras(F).FiniteDimensional().WithBasis() + CombinatorialFreeModule.__init__(self, F, indices, category=cat) + self._assign_names(self.algebra_generators().keys()) + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: algebras.QuantumClifford(3) + Quantum Clifford algebra of rank 3 and twist 1 with q=q over + Fraction Field of Univariate Polynomial Ring in q over Integer Ring + """ + return "Quantum Clifford algebra of rank {} and twist {} with q={} over {}".format( + self._n, self._k, self._q, self.base_ring()) + + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: Cl = algebras.QuantumClifford(3) + sage: latex(Cl) + \operatorname{Cl}_{q}(3, 1) + """ + return "\\operatorname{Cl}_{%s}(%s, %s)" % (self._q, self._n, self._k) + + def _repr_term(self, m): + r""" + Return a string representation of the basis element indexed by ``m``. + + EXAMPLES:: + + sage: Cl = algebras.QuantumClifford(3, 3) + sage: Cl._repr_term( ((1, 0, -1), (0, -2, 5)) ) + 'psi0*psid2*w1^-2*w2^5' + sage: Cl._repr_term( ((1, 0, -1), (0, 0, 0)) ) + 'psi0*psid2' + sage: Cl._repr_term( ((0, 0, 0), (0, -2, 5)) ) + 'w1^-2*w2^5' + sage: Cl._repr_term( ((0, 0, 0), (0, 0, 0)) ) + '1' + + sage: Cl(5) + 5 + """ + p, v = m + rp = '*'.join('psi%s'%i if p[i] > 0 else 'psid%s'%i + for i in range(self._n) if p[i] != 0) + gen_str = lambda e: '' if e == 1 else '^%s'%e + rv = '*'.join('w%s'%i + gen_str(v[i]) for i in range(self._n) if v[i] != 0) + if rp: + if rv: + return rp + '*' + rv + return rp + if rv: + return rv + return '1' + + def _latex_term(self, m): + r""" + Return a latex representation for the basis element indexed by ``m``. + + EXAMPLES:: + + sage: Cl = algebras.QuantumClifford(3, 3) + sage: Cl._latex_term( ((1, 0, -1), (0, -2, 5)) ) + '\\psi_{0}\\psi^{\\dagger}_{2}\\omega_{1}^{-2}\\omega_{2}^{5}' + sage: Cl._latex_term( ((1, 0, -1), (0, 0, 0)) ) + '\\psi_{0}\\psi^{\\dagger}_{2}' + sage: Cl._latex_term( ((0, 0, 0), (0, -2, 5)) ) + '\\omega_{1}^{-2}\\omega_{2}^{5}' + sage: Cl._latex_term( ((0, 0, 0), (0, 0, 0)) ) + '1' + + sage: latex(Cl(5)) + 5 + """ + p, v = m + rp = ''.join('\\psi_{%s}'%i if p[i] > 0 else '\\psi^{\\dagger}_{%s}'%i + for i in range(self._n) if p[i] != 0) + gen_str = lambda e: '' if e == 1 else '^{%s}'%e + rv = ''.join('\\omega_{%s}'%i + gen_str(v[i]) + for i in range(self._n) if v[i] != 0) + if not rp and not rv: + return '1' + return rp + rv + + def q(self): + r""" + Return the `q` of ``self``. + + EXAMPLES:: + + sage: Cl = algebras.QuantumClifford(3) + sage: Cl.q() + q + + sage: Cl = algebras.QuantumClifford(3, q=QQ(-5)) + sage: Cl.q() + -5 + """ + return self._q + + def twist(self): + r""" + Return the twist `k` of ``self``. + + EXAMPLES:: + + sage: Cl = algebras.QuantumClifford(3, 2) + sage: Cl.twist() + 2 + """ + return self._k + + def rank(self): + r""" + Return the rank `k` of ``self``. + + EXAMPLES:: + + sage: Cl = algebras.QuantumClifford(3, 2) + sage: Cl.rank() + 3 + """ + return self._n + + def dimension(self): + r""" + Return the dimension of ``self``. + + EXAMPLES:: + + sage: Cl = algebras.QuantumClifford(3) + sage: Cl.dimension() + 512 + + sage: Cl = algebras.QuantumClifford(4, 2) + sage: Cl.dimension() + 65536 + """ + return ZZ(8*self._k) ** self._n + + @cached_method + def algebra_generators(self): + r""" + Return the algebra generators of ``self``. + + EXAMPLES:: + + sage: Cl = algebras.QuantumClifford(3) + sage: Cl.algebra_generators() + Finite family {'psi0': psi0, 'psi1': psi1, 'psi2': psi2, + 'psid0': psid0, 'psid1': psid1, 'psid2': psid2, + 'w0': w0, 'w1': w1, 'w2': w2} + """ + one = (0,) * self._n # one in the corresponding free abelian group + zero = [0] * self._n + d = {} + for i in range(self._n): + r = list(zero) # Make a copy + r[i] = 1 + d['psi%s'%i] = self.monomial( (self._psi(r), one) ) + r[i] = -1 + d['psid%s'%i] = self.monomial( (self._psi(r), one) ) + zero = self._psi(zero) + for i in range(self._n): + temp = list(zero) # Make a copy + temp[i] = 1 + d['w%s'%i] = self.monomial( (zero, tuple(temp)) ) + return Family(sorted(d), lambda i: d[i]) + + @cached_method + def gens(self): + r""" + Return the generators of ``self``. + + EXAMPLES:: + + sage: Cl = algebras.QuantumClifford(3) + sage: Cl.gens() + (psi0, psi1, psi2, psid0, psid1, psid2, w0, w1, w2) + """ + return tuple(self.algebra_generators()) + + @cached_method + def one_basis(self): + r""" + Return the index of the basis element of `1`. + + EXAMPLES:: + + sage: Cl = algebras.QuantumClifford(3) + sage: Cl.one_basis() + ((0, 0, 0), (0, 0, 0)) + """ + return (self._psi([0]*self._n), (0,)*self._n) + + @cached_method + def product_on_basis(self, m1, m2): + r""" + Return the product of the basis elements indexed by ``m1`` and ``m2``. + + EXAMPLES:: + + sage: Cl = algebras.QuantumClifford(3) + sage: Cl.inject_variables() + Defining psi0, psi1, psi2, psid0, psid1, psid2, w0, w1, w2 + sage: psi0^2 # indirect doctest + 0 + sage: psid0^2 + 0 + sage: w0 * psi0 + q*psi0*w0 + sage: w0 * psid0 + 1/q*psid0*w0 + sage: w2 * w0 + w0*w2 + sage: w0^4 + -1/q^2 + ((q^2+1)/q^2)*w0^2 + """ + p1, w1 = m1 + p2, w2 = m2 + # Check for \psi_i^2 == 0 and for the dagger version + if any(p1[i] != 0 and p1[i] == p2[i] for i in range(self._n)): + return self.zero() + + # \psi_i is represented by a 1 in p1[i] and p2[i] + # \psi_i^{\dagger} is represented by a -1 in p1[i] and p2[i] + + k = self._k + q_power = 0 + sign = 1 + pairings = [] + supported = [] + p = [0] * self._n + # Move w1 * p2 to q^q_power * p2 * w1 + # Also find pairs \psi_i \psi_i^{\dagger} (or vice versa) + for i in range(self._n): + if p2[i] != 0: + supported.append(i) + q_power += w1[i] * p2[i] + if p1[i] != 0: + # We make pairings 1-based because we cannot distinguish 0 and -0 + pairings.append((i+1) * p1[i]) + # we know p1[i] != p2[i] if non-zero, so their sum is -1, 0, 1 + p[i] = p1[i] + p2[i] + + supported.append(self._n-1) # To get between the last support and the end + # Get the sign of moving \psi_i and \psi_i^{\dagger} into position + for i in reversed(range(1, len(supported))): + if i % 2 != 0: + for j in reversed(range(supported[i-1]+1, supported[i]+1)): + if p1[j] != 0: + sign = (-1)**i * sign + + # We move the pairs \psi_i \psi_i^{\dagger} (or the reverse) to the + # end of the \psi part. This does not change the sign because they + # move in pairs. + # We take the products. + vp = self._w_poly.gens() + poly = self._w_poly.one() + q = self._q + for i in pairings: + if i < 0: + i = -i - 1 # Go back to 0-based + vpik = -q**(2*k) * vp[i]**(3*k) + (1 + q**(2*k)) * vp[i]**k + poly *= -(vp[i]**k - vpik) / (q**k - q**(-k)) + else: + i -= 1 # Go back to 0-based + vpik = -q**(2*k) * vp[i]**(3*k) + (1 + q**(2*k)) * vp[i]**k + poly *= (q**k * vp[i]**k - q**(-k) * vpik) / (q**k - q**(-k)) + + v = list(w1) + for i in range(self._n): + v[i] += w2[i] + + # For all \psi_i v_i^k, convert this to either k = 0, 1 + # and same for \psi_i^{\dagger} + for i in range(self._n): + if p[i] > 0 and v[i] != 0: + q_power -= 2 * k * (v[i] // (2*k)) + v[i] = v[i] % (2*k) + if p[i] < 0 and v[i] != 0: + v[i] = v[i] % (2*k) + + poly *= self._w_poly.monomial(*v) + poly = poly.reduce([vp[i]**(4*k) - (1 + q**(-2*k)) * vp[i]**(2*k) + q**(-2*k) + for i in range(self._n)]) + pdict = poly.dict() + ret = {(self._psi(p), tuple(e)): pdict[e] * q**q_power * sign + for e in pdict} + + return self._from_dict(ret) + + class Element(CombinatorialFreeModule.Element): + def inverse(self): + r""" + Return the inverse if ``self`` is a basis element. + + EXAMPLES:: + + sage: Cl = algebras.QuantumClifford(3) + sage: Cl.inject_variables() + Defining psi0, psi1, psi2, psid0, psid1, psid2, w0, w1, w2 + sage: w0^-1 + (q^2+1)*w0 - q^2*w0^3 + sage: w0^-1 * w0 + 1 + sage: w0^-2 + (q^2+1) - q^2*w0^2 + sage: w0^-2 * w0^2 + 1 + sage: w0^-2 * w0 == w0^-1 + True + sage: w = w0 * w1 + sage: w^-1 + (q^4+2*q^2+1)*w0*w1 + (-q^4-q^2)*w0*w1^3 + + (-q^4-q^2)*w0^3*w1 + q^4*w0^3*w1^3 + sage: w^-1 * w + 1 + sage: w * w^-1 + 1 + + sage: (w0 + w1)^-1 + Traceback (most recent call last): + ... + NotImplementedError: inverse only implemented for basis elements + sage: (psi0 * w0)^-1 + Traceback (most recent call last): + ... + NotImplementedError: inverse only implemented for product of w generators + + sage: Cl = algebras.QuantumClifford(2, 2) + sage: Cl.inject_variables() + Defining psi0, psi1, psid0, psid1, w0, w1 + sage: w0^-1 + (q^4+1)*w0^3 - q^4*w0^7 + sage: w0 * w0^-1 + 1 + """ + if not self: + raise ZeroDivisionError + if len(self) != 1: + raise NotImplementedError("inverse only implemented for basis elements") + Cl = self.parent() + p, w = self.support_of_term() + if any(p[i] != 0 for i in range(Cl._n)): + raise NotImplementedError("inverse only implemented for" + " product of w generators") + poly = Cl._w_poly.monomial(*w) + wp = Cl._w_poly.gens() + q = Cl._q + k = Cl._k + poly = poly.subs({wi: -q**(2*k) * wi**(4*k-1) + (1 + q**(2*k)) * wi**(2*k-1) + for wi in wp}) + poly = poly.reduce([wi**(4*k) - (1 + q**(-2*k)) * wi**(2*k) + q**(-2*k) + for wi in wp]) + pdict = poly.dict() + ret = {(p, tuple(e)): c for e, c in pdict.items()} + return Cl._from_dict(ret) + + __invert__ = inverse + diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index f6b4271edfd..89671f93593 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1129,7 +1129,7 @@ def ideal(self, gens, left_order=None, right_order=None, check=True, **kwds): Fractional ideal (2, 2*i, 2*j, 2*k) """ if self.base_ring() == QQ: - return QuaternionFractionalIdeal_rational(gens, left_order=left_order, right_order=right_order, check=check) + return QuaternionFractionalIdeal_rational(self, gens, left_order=left_order, right_order=right_order, check=check) else: raise NotImplementedError("ideal only implemented for quaternion algebras over QQ") @@ -1689,7 +1689,7 @@ def left_ideal(self, gens, check=True): Fractional ideal (1 + i, 2*i, j + k, 2*k) """ if self.base_ring() == ZZ: - return QuaternionFractionalIdeal_rational(gens, left_order=self, check=check) + return QuaternionFractionalIdeal_rational(self.quaternion_algebra(), gens, left_order=self, check=check) else: raise NotImplementedError("ideal only implemented for quaternion algebras over QQ") @@ -1711,7 +1711,7 @@ def right_ideal(self, gens, check=True): Fractional ideal (1 + i, 2*i, j + k, 2*k) """ if self.base_ring() == ZZ: - return QuaternionFractionalIdeal_rational(gens, right_order=self, check=check) + return QuaternionFractionalIdeal_rational(self.quaternion_algebra(), gens, right_order=self, check=check) else: raise NotImplementedError("ideal only implemented for quaternion algebras over QQ") @@ -1726,7 +1726,7 @@ def unit_ideal(self): Fractional ideal (1/2 + 1/2*i, 1/2*j - 1/2*k, i, -k) """ if self.base_ring() == ZZ: - return QuaternionFractionalIdeal_rational(self.basis(), left_order=self, right_order=self, check=False) + return QuaternionFractionalIdeal_rational(self.quaternion_algebra(), self.basis(), left_order=self, right_order=self, check=False) else: raise NotImplementedError("ideal only implemented for quaternion algebras over QQ") @@ -1839,7 +1839,7 @@ class QuaternionFractionalIdeal_rational(QuaternionFractionalIdeal): - ``check`` -- bool (default: ``True``); if ``False``, do no type checking, and the input basis *must* be in Hermite form. """ - def __init__(self, basis, left_order=None, right_order=None, check=True): + def __init__(self, Q, basis, left_order=None, right_order=None, check=True): """ EXAMPLES:: @@ -1848,6 +1848,16 @@ def __init__(self, basis, left_order=None, right_order=None, check=True): Fractional ideal (1/2 + 1/2*i, i, 1/2*j + 1/2*k, k) sage: R.right_ideal(tuple(R.basis()), check=False) Fractional ideal (1/2 + 1/2*i, 1/2*j - 1/2*k, i, -k) + + TESTS:: + + sage: QuaternionAlgebra(-11,-1).maximal_order().unit_ideal().gens() + (1/2 + 1/2*i, 1/2*j - 1/2*k, i, -k) + + Check that :trac:`31582` is fixed:: + + sage: BrandtModule(23).right_ideals()[0].parent() + Monoid of ideals of Quaternion Algebra (-1, -23) with base ring Rational Field """ if check: if left_order is not None and not isinstance(left_order, QuaternionOrder): @@ -1856,18 +1866,11 @@ def __init__(self, basis, left_order=None, right_order=None, check=True): raise TypeError("right_order must be a quaternion order or None") if not isinstance(basis, (list, tuple)): raise TypeError("basis must be a list or tuple") - - self.__left_order = left_order - self.__right_order = right_order - - if check: - try: - Q = self.quaternion_order().quaternion_algebra() - except RuntimeError: - Q = basis[0].parent() basis = tuple([Q(v) for v in (QQ**4).span([Q(v).coefficient_tuple() for v in basis], ZZ).basis()]) - self.__basis = basis + self.__left_order = left_order + self.__right_order = right_order + Ideal_fractional.__init__(self, Q, basis) def scale(self, alpha, left=False): r""" @@ -1890,11 +1893,11 @@ def scale(self, alpha, left=False): sage: B = BrandtModule(5,37); I = B.right_ideals()[0]; i,j,k = B.quaternion_algebra().gens(); I Fractional ideal (2 + 2*j + 106*k, i + 2*j + 105*k, 4*j + 64*k, 148*k) sage: I.scale(i) - Fractional ideal [2*i + 212*j - 2*k, -2 + 210*j - 2*k, 128*j - 4*k, 296*j] + Fractional ideal (2*i + 212*j - 2*k, -2 + 210*j - 2*k, 128*j - 4*k, 296*j) sage: I.scale(i, left=True) - Fractional ideal [2*i - 212*j + 2*k, -2 - 210*j + 2*k, -128*j + 4*k, -296*j] + Fractional ideal (2*i - 212*j + 2*k, -2 - 210*j + 2*k, -128*j + 4*k, -296*j) sage: I.scale(i, left=False) - Fractional ideal [2*i + 212*j - 2*k, -2 + 210*j - 2*k, 128*j - 4*k, 296*j] + Fractional ideal (2*i + 212*j - 2*k, -2 + 210*j - 2*k, 128*j - 4*k, 296*j) sage: i * I.gens()[0] 2*i - 212*j + 2*k sage: I.gens()[0] * i @@ -1922,13 +1925,9 @@ def quaternion_algebra(self): sage: I.quaternion_algebra() Quaternion Algebra (-1, -3) with base ring Rational Field """ - try: - return self.__quaternion_algebra - except AttributeError: - pass - A = self.__basis[0].parent() - self.__quaternion_algebra = A - return A + # TODO: when the ring() method is removed from this class, the + # following line can be changed to self.ring(). + return Ideal_fractional.ring(self) def _compute_order(self, side='left'): r""" @@ -2083,8 +2082,12 @@ def quaternion_order(self): sage: R = QuaternionAlgebra(-11,-1).maximal_order() sage: R.unit_ideal().quaternion_order() is R + doctest:...: DeprecationWarning: quaternion_order() is deprecated, please use left_order() or right_order() + See https://trac.sagemath.org/31583 for details. True """ + from sage.misc.superseded import deprecation + deprecation(31583, 'quaternion_order() is deprecated, please use left_order() or right_order()') try: return self.__quaternion_order except AttributeError: @@ -2102,13 +2105,27 @@ def ring(self): """ Return ring that this is a fractional ideal for. + The :meth:`ring` method will be removed from this class in the + future. Calling :meth:`ring` will then return the ambient + quaternion algebra. This is consistent with the behaviour for + number fields. + EXAMPLES:: sage: R = QuaternionAlgebra(-11,-1).maximal_order() sage: R.unit_ideal().ring() is R + doctest:...: DeprecationWarning: ring() will return the quaternion algebra in the future, please use left_order() or right_order() + See https://trac.sagemath.org/31583 for details. True """ - return self.quaternion_order() + from sage.misc.superseded import deprecation + deprecation(31583, 'ring() will return the quaternion algebra in the future, please use left_order() or right_order()') + if self.__left_order is not None: + return self.__left_order + elif self.__right_order is not None: + return self.__right_order + else: + raise RuntimeError("unable to determine quaternion order of ideal without known order") def basis(self): """ @@ -2123,34 +2140,12 @@ def basis(self): sage: QuaternionAlgebra(-11,-1).maximal_order().unit_ideal().basis() (1/2 + 1/2*i, 1/2*j - 1/2*k, i, -k) """ - return self.__basis - - def gens(self): - r""" - Return the generators for this ideal, which are the same as - the `\ZZ`-basis for this ideal. - - EXAMPLES:: - - sage: QuaternionAlgebra(-11,-1).maximal_order().unit_ideal().gens() - (1/2 + 1/2*i, 1/2*j - 1/2*k, i, -k) - """ - return self.__basis + return self.gens() - def __eq__(self, right): + def _richcmp_(self, right, op): """ Compare this fractional quaternion ideal to ``right``. - If ``right`` is not a fractional quaternion ideal, return ``False``. - - If the fractional ideals are in different ambient - quaternion algebras, then the quaternion algebras themselves - are compared. - - INPUT: - - - ``right`` - another fractional quaternion ideal - EXAMPLES:: sage: I = QuaternionAlgebra(-11,-1).maximal_order().unit_ideal() @@ -2158,26 +2153,10 @@ def __eq__(self, right): True sage: I == 5 False - """ - if not isinstance(right, QuaternionFractionalIdeal_rational): - return False - return self.__basis == right.__basis - - def __ne__(self, other): - """ - Compare this fractional quaternion ideal to ``right``. - - INPUT: - - - ``right`` - another fractional quaternion ideal - - EXAMPLES:: - - sage: I = QuaternionAlgebra(-11,-1).maximal_order().unit_ideal() sage: I != I # indirect doctest False """ - return not self.__eq__(other) + return self.basis_matrix()._richcmp_(right.basis_matrix(), op) def __hash__(self): """ @@ -2194,7 +2173,7 @@ def __hash__(self): sage: R = QuaternionAlgebra(-11,-1).maximal_order() sage: H = hash(R.right_ideal(R.basis())) """ - return hash(self.__basis) + return hash(self.gens()) def basis_matrix(self): r""" @@ -2220,7 +2199,7 @@ def basis_matrix(self): return self.__hermite_basis_matrix except AttributeError: pass - B = quaternion_algebra_cython.rational_matrix_from_rational_quaternions(self.__basis) + B = quaternion_algebra_cython.rational_matrix_from_rational_quaternions(self.gens()) self.__hermite_basis_matrix = B return B @@ -2356,8 +2335,8 @@ def gram_matrix(self): [ 2112 13440 13056 15360] [ 1920 16320 15360 19200] """ - A = self.__basis - B = [z.conjugate() for z in self.__basis] + A = self.gens() + B = [z.conjugate() for z in A] two = QQ(2) m = [two * (a * b).reduced_trace() for b in B for a in A] M44 = MatrixSpace(QQ, 4, 4) @@ -2396,11 +2375,7 @@ def norm(self): assert r.is_square(), "first is bad!" r = r.sqrt() # If we know either the left- or the right order, use that one to compute the norm. - # Otherwise quaternion_order() will raise a RuntimeError and we compute the left order - try: - R = self.quaternion_order() - except RuntimeError: - R = self.left_order() + R = self.__left_order or self.__right_order or self.left_order() r/= R.discriminant() assert r.is_square(), "second is bad!" return r.sqrt() @@ -2578,7 +2553,7 @@ def is_equivalent(I, J, B=10): False sage: R[0].is_equivalent(R[0]) True - sage: OO = R[0].quaternion_order() + sage: OO = R[0].left_order() sage: S = OO.right_ideal([3*a for a in R[0].basis()]) sage: R[0].is_equivalent(S) True diff --git a/src/sage/algebras/rational_cherednik_algebra.py b/src/sage/algebras/rational_cherednik_algebra.py index e551c43c568..7d7e9f7929f 100644 --- a/src/sage/algebras/rational_cherednik_algebra.py +++ b/src/sage/algebras/rational_cherednik_algebra.py @@ -184,11 +184,11 @@ def _reflections(self): d[s] = (r, r.associated_coroot(), c) return d - def _repr_(self): + def _repr_(self) -> str: r""" Return a string representation of ``self``. - EXAMPLES :: + EXAMPLES:: sage: RationalCherednikAlgebra(['A',4], 2, 1, QQ) Rational Cherednik Algebra of type ['A', 4] with c=2 and t=1 diff --git a/src/sage/arith/misc.py b/src/sage/arith/misc.py index 76a3f2f7f8d..83f3a957d17 100644 --- a/src/sage/arith/misc.py +++ b/src/sage/arith/misc.py @@ -328,16 +328,16 @@ def bernoulli(n, algorithm='default', num_threads=1): TESTS:: - sage: algs = ['arb','gap','gp','pari','bernmm','flint'] + sage: algs = ['arb', 'gap', 'gp', 'pari', 'bernmm', 'flint'] sage: test_list = [ZZ.random_element(2, 2255) for _ in range(500)] - sage: vals = [[bernoulli(i,algorithm = j) for j in algs] for i in test_list] # long time (up to 21s on sage.math, 2011) - sage: union([len(union(x))==1 for x in vals]) # long time (depends on previous line) - [True] - sage: algs = ['gp','pari','bernmm'] + sage: vals = [[bernoulli(i, algorithm=j) for j in algs] for i in test_list] # long time (up to 21s on sage.math, 2011) + sage: all(len(set(x))==1 for x in vals) # long time (depends on previous line) + True + sage: algs = ['gp', 'pari', 'bernmm'] sage: test_list = [ZZ.random_element(2256, 5000) for _ in range(500)] - sage: vals = [[bernoulli(i,algorithm = j) for j in algs] for i in test_list] # long time (up to 30s on sage.math, 2011) - sage: union([len(union(x))==1 for x in vals]) # long time (depends on previous line) - [True] + sage: vals = [[bernoulli(i, algorithm=j) for j in algs] for i in test_list] # long time (up to 30s on sage.math, 2011) + sage: all(len(set(x))==1 for x in vals) # long time (depends on previous line) + True sage: from numpy import int8 sage: bernoulli(int8(12)) -691/2730 diff --git a/src/sage/calculus/all.py b/src/sage/calculus/all.py index a52d530db0b..5e40c003aa1 100644 --- a/src/sage/calculus/all.py +++ b/src/sage/calculus/all.py @@ -73,7 +73,7 @@ def symbolic_expression(x): sage: symbolic_expression(E) in SR True - If x is a list or tuple, create a vector of symbolic expressions:: + If ``x`` is a list or tuple, create a vector of symbolic expressions:: sage: v=symbolic_expression([x,1]); v (x, 1) @@ -93,6 +93,56 @@ def symbolic_expression(x): (x*y + y^2 + y == x^3 + x^2 - 10*x - 10, x*y + y^2 + y == x^3 + x^2 - 10*x - 10) sage: v.base_ring() Symbolic Ring + + If ``x`` is a function, for example defined by a ``lambda`` expression, create a + symbolic function:: + + sage: f = symbolic_expression(lambda z: z^2 + 1); f + z |--> z^2 + 1 + sage: f.parent() + Callable function ring with argument z + sage: f(7) + 50 + + If ``x`` is a list or tuple of functions, or if ``x`` is a function that returns a list + or tuple, create a callable symbolic vector:: + + sage: symbolic_expression([lambda mu, nu: mu^2 + nu^2, lambda mu, nu: mu^2 - nu^2]) + (mu, nu) |--> (mu^2 + nu^2, mu^2 - nu^2) + sage: f = symbolic_expression(lambda uwu: [1, uwu, uwu^2]); f + uwu |--> (1, uwu, uwu^2) + sage: f.parent() + Vector space of dimension 3 over Callable function ring with argument uwu + sage: f(5) + (1, 5, 25) + sage: f(5).parent() + Vector space of dimension 3 over Symbolic Ring + + TESTS: + + Also functions defined using ``def`` can be used, but we do not advertise it as a use case:: + + sage: def sos(x, y): + ....: return x^2 + y^2 + sage: symbolic_expression(sos) + (x, y) |--> x^2 + y^2 + + Functions that take a varying number of arguments or keyword-only arguments are not accepted:: + + sage: def variadic(x, *y): + ....: return x + sage: symbolic_expression(variadic) + Traceback (most recent call last): + ... + TypeError: unable to convert to a symbolic expression + + sage: def function_with_keyword_only_arg(x, *, sign=1): + ....: return sign * x + sage: symbolic_expression(function_with_keyword_only_arg) + Traceback (most recent call last): + ... + TypeError: unable to convert + to a symbolic expression """ from sage.symbolic.expression import Expression from sage.symbolic.ring import SR @@ -100,9 +150,23 @@ def symbolic_expression(x): return x elif hasattr(x, '_symbolic_'): return x._symbolic_(SR) - elif isinstance(x, (tuple,list)): - return vector(SR,x) - else: - return SR(x) + elif isinstance(x, (tuple, list)): + return vector([symbolic_expression(item) for item in x]) + elif callable(x): + from inspect import signature, Parameter + try: + s = signature(x) + except ValueError: + pass + else: + if all(param.kind in (Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD) + for param in s.parameters.values()): + vars = [SR.var(name) for name in s.parameters.keys()] + result = x(*vars) + if isinstance(result, (tuple, list)): + return vector(SR, result).function(*vars) + else: + return SR(result).function(*vars) + return SR(x) from . import desolvers diff --git a/src/sage/categories/complete_discrete_valuation.py b/src/sage/categories/complete_discrete_valuation.py index 75d723941ec..e7bc01ddd28 100644 --- a/src/sage/categories/complete_discrete_valuation.py +++ b/src/sage/categories/complete_discrete_valuation.py @@ -154,7 +154,7 @@ def lift_to_precision(self, absprec=None): sage: R(1,15).lift_to_precision(30) Traceback (most recent call last): ... - PrecisionError: Precision higher than allowed by the precision cap. + PrecisionError: precision higher than allowed by the precision cap sage: R(-1,2).lift_to_precision().precision_absolute() == R.precision_cap() True diff --git a/src/sage/categories/finite_dimensional_modules_with_basis.py b/src/sage/categories/finite_dimensional_modules_with_basis.py index 9be638117b7..e86099fbf2c 100644 --- a/src/sage/categories/finite_dimensional_modules_with_basis.py +++ b/src/sage/categories/finite_dimensional_modules_with_basis.py @@ -363,6 +363,77 @@ def echelon_form(self, elements, row_reduced=False, order=None): ret = [self.from_vector(vec, order=order) for vec in mat if vec] return ret + def invariant_module(self, S, action=operator.mul, action_on_basis=None, + side="left", **kwargs): + r""" + Return the submodule of ``self`` invariant under the action + of ``S``. + + For a semigroup `S` acting on a module `M`, the invariant + submodule is given by + + .. MATH:: + + M^S = \{m \in M : s \cdot m = m,\, \forall s \in S\}. + + INPUT: + + - ``S`` -- a finitely-generated semigroup + - ``action`` -- a function (default: :obj:`operator.mul`) + - ``side`` -- ``'left'`` or ``'right'`` (default: ``'right'``); + which side of ``self`` the elements of ``S`` acts + - ``action_on_basis`` -- (optional) define the action of ``S`` + on the basis of ``self`` + + OUTPUT: + + - :class:`~sage.modules.with_basis.invariant.FiniteDimensionalInvariantModule` + + EXAMPLES: + + We build the invariant module of the permutation representation + of the symmetric group:: + + sage: G = SymmetricGroup(3); G.rename('S3') + sage: M = FreeModule(ZZ, [1,2,3], prefix='M'); M.rename('M') + sage: action = lambda g, x: M.term(g(x)) + sage: I = M.invariant_module(G, action_on_basis=action); I + (S3)-invariant submodule of M + sage: I.basis() + Finite family {0: B[0]} + sage: [I.lift(b) for b in I.basis()] + [M[1] + M[2] + M[3]] + + sage: G.rename(); M.rename() # reset the names + + We can construct the invariant module of any module that has + an action of ``S``. In this example, we consider the dihedral + group `G = D_4` and the subgroup `H < G` of all rotations. We + construct the `H`-invariant module of the group algebra `\QQ[G]`:: + + sage: G = groups.permutation.Dihedral(4) + sage: H = G.subgroup(G.gen(0)) + sage: H + Subgroup generated by [(1,2,3,4)] of (Dihedral group of order 8 as a permutation group) + sage: H.cardinality() + 4 + sage: A = G.algebra(QQ) + sage: I = A.invariant_module(H) + sage: [I.lift(b) for b in I.basis()] + [() + (1,2,3,4) + (1,3)(2,4) + (1,4,3,2), + (2,4) + (1,2)(3,4) + (1,3) + (1,4)(2,3)] + sage: all(h * I.lift(b) == I.lift(b) for b in I.basis() for h in H) + True + """ + if action_on_basis is not None: + from sage.modules.with_basis.representation import Representation + M = Representation(S, self, action_on_basis, side=side) + else: + M = self + + from sage.modules.with_basis.invariant import FiniteDimensionalInvariantModule + return FiniteDimensionalInvariantModule(M, S, action=action, side=side, **kwargs) + class ElementMethods: def dense_coefficient_list(self, order=None): """ diff --git a/src/sage/categories/sets_cat.py b/src/sage/categories/sets_cat.py index bdf076766ac..3ba6e7e90a6 100644 --- a/src/sage/categories/sets_cat.py +++ b/src/sage/categories/sets_cat.py @@ -1716,11 +1716,11 @@ def _sympy_(self): sage: RR._sympy_().is_finite_set False - sage: F = Set([1, 2]) - sage: F is Set([1, 2]) + sage: F = Family([1, 2]) + sage: F is Family([1, 2]) False sage: sF = F._sympy_(); sF - SageSet({1, 2}) + SageSet(Family (1, 2)) sage: sF._sage_() is F True """ diff --git a/src/sage/coding/encoder.py b/src/sage/coding/encoder.py index 0e00a5170de..304e68cce02 100644 --- a/src/sage/coding/encoder.py +++ b/src/sage/coding/encoder.py @@ -156,7 +156,7 @@ def encode(self, word): sage: E.encode(word) Traceback (most recent call last): ... - ArithmeticError: reduction modulo 2 not defined + ValueError: The value to encode must be in Vector space of dimension 4 over Finite Field of size 2 """ M = self.message_space() if word not in M: diff --git a/src/sage/combinat/alternating_sign_matrix.py b/src/sage/combinat/alternating_sign_matrix.py index c3b690f5bdd..7dc60852255 100644 --- a/src/sage/combinat/alternating_sign_matrix.py +++ b/src/sage/combinat/alternating_sign_matrix.py @@ -604,7 +604,7 @@ def gyration(self): [0 0 1 0] sage: a0 = a = AlternatingSignMatrices(5).random_element() - sage: for i in range(10): + sage: for i in range(20): ....: a = a.gyration() sage: a == a0 True diff --git a/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py b/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py index b459b645422..61771810ab3 100644 --- a/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py +++ b/src/sage/combinat/cluster_algebra_quiver/cluster_seed.py @@ -4626,7 +4626,7 @@ def coeff_recurs(p, q, a1, a2, b, c): return sum((-1)**(k-1)*coeff_recurs(p, q-k, a1, a2, b, c)*_bino(a1-b*p+k-1, k) for k in range(1, q+1)) -def PathSubset(n,m): +def PathSubset(n, m): r""" Encodes a *maximal* Dyck path from (0,0) to (n,m) (for n >= m >= 0) as a subset of {0,1,2,..., 2n-1}. The encoding is given by indexing horizontal edges by odd numbers and vertical edges by evens. @@ -4648,15 +4648,12 @@ def PathSubset(n,m): sage: PathSubset(4,4) {0, 1, 2, 3, 4, 5, 6, 7} """ - from sage.misc.misc import union from sage.functions.other import floor - S = [ ] - for i in range(n): - S = union(S, [2*i+1]) + S = set(2 * i + 1 for i in range(n)) if m > 0: for j in range(n): if floor((j+1)*m/n) - floor(j*m/n) == 1: - S = union(S, [2*j]) + S.add(2 * j) return set(S) diff --git a/src/sage/combinat/combinatorial_map.py b/src/sage/combinat/combinatorial_map.py index 945ec225d4e..bc255d9527f 100644 --- a/src/sage/combinat/combinatorial_map.py +++ b/src/sage/combinat/combinatorial_map.py @@ -18,9 +18,7 @@ By default, this decorator is a no-op: it returns the decorated method as is:: - sage: MyPermutation.reverse # py2 - - sage: MyPermutation.reverse # py3 + sage: MyPermutation.reverse See :func:`combinatorial_map_wrapper` for the various options this @@ -95,14 +93,10 @@ def combinatorial_map_trivial(f=None, order=None, name=None): ....: ''' ....: # ... code ... - sage: MyPermutation.reverse # py2 - - sage: MyPermutation.reverse # py3 + sage: MyPermutation.reverse - sage: MyPermutation.descent_set # py2 - - sage: MyPermutation.descent_set # py3 + sage: MyPermutation.descent_set """ if f is None: @@ -182,9 +176,7 @@ def combinatorial_map_wrapper(f=None, order=None, name=None): The method ``major_index`` defined about is not a combinatorial map:: - sage: MyPermutation.major_index # py2 - - sage: MyPermutation.major_index # py3 + sage: MyPermutation.major_index But one can define a function that turns ``major_index`` into a combinatorial map:: @@ -255,8 +247,7 @@ def __repr__(self): EXAMPLES:: sage: sage.combinat.combinatorial_map.combinatorial_map = sage.combinat.combinatorial_map.combinatorial_map_wrapper - sage: from imp import reload # py2 - sage: from importlib import reload # py3 + sage: from importlib import reload sage: _ = reload(sage.combinat.permutation) sage: p = Permutation([1,3,2,4]) sage: p.left_tableau.__repr__() @@ -271,8 +262,7 @@ def _sage_src_lines_(self): EXAMPLES:: sage: sage.combinat.combinatorial_map.combinatorial_map = sage.combinat.combinatorial_map.combinatorial_map_wrapper - sage: from imp import reload # py2 - sage: from importlib import reload # py3 + sage: from importlib import reload sage: _ = reload(sage.combinat.permutation) sage: p = Permutation([1,3,2,4]) sage: cm = p.left_tableau; cm @@ -293,8 +283,7 @@ def __get__(self, inst, cls=None): EXAMPLES:: sage: sage.combinat.combinatorial_map.combinatorial_map = sage.combinat.combinatorial_map.combinatorial_map_wrapper - sage: from imp import reload # py2 - sage: from importlib import reload # py3 + sage: from importlib import reload sage: _ = reload(sage.combinat.permutation) sage: p = Permutation([1,3,2,4]) sage: p.left_tableau #indirect doctest @@ -310,8 +299,7 @@ def __call__(self, *args, **kwds): EXAMPLES:: sage: sage.combinat.combinatorial_map.combinatorial_map = sage.combinat.combinatorial_map.combinatorial_map_wrapper - sage: from imp import reload # py2 - sage: from importlib import reload # py3 + sage: from importlib import reload sage: _ = reload(sage.combinat.permutation) sage: p = Permutation([1,3,2,4]) sage: cm = type(p).left_tableau; cm @@ -337,8 +325,7 @@ def unbounded_map(self): EXAMPLES:: sage: sage.combinat.combinatorial_map.combinatorial_map = sage.combinat.combinatorial_map.combinatorial_map_wrapper - sage: from imp import reload # py2 - sage: from importlib import reload # py3 + sage: from importlib import reload sage: _ = reload(sage.combinat.permutation) sage: from sage.combinat.permutation import Permutation sage: pi = Permutation([1,3,2]) @@ -403,8 +390,7 @@ def combinatorial_maps_in_class(cls): EXAMPLES:: sage: sage.combinat.combinatorial_map.combinatorial_map = sage.combinat.combinatorial_map.combinatorial_map_wrapper - sage: from imp import reload # py2 - sage: from importlib import reload # py3 + sage: from importlib import reload sage: _ = reload(sage.combinat.permutation) sage: from sage.combinat.combinatorial_map import combinatorial_maps_in_class sage: p = Permutation([1,3,2,4]) diff --git a/src/sage/combinat/dyck_word.py b/src/sage/combinat/dyck_word.py index 6c36c354ed4..10f8a588a8b 100644 --- a/src/sage/combinat/dyck_word.py +++ b/src/sage/combinat/dyck_word.py @@ -77,6 +77,8 @@ # # https://www.gnu.org/licenses/ # **************************************************************************** +from __future__ import annotations +from typing import Union from .combinat import CombinatorialElement, catalan_number from sage.combinat.combinatorial_map import combinatorial_map @@ -454,7 +456,7 @@ def latex_options(self): d["valleys"] = self.parent().options.latex_valleys return d - def _repr_(self): + def _repr_(self) -> str: r""" Return a string representation of ``self`` depending on :meth:`DyckWords.options`. @@ -476,7 +478,7 @@ def _repr_(self): """ return self.parent().options._dispatch(self, '_repr_', 'display') - def _repr_list(self): + def _repr_list(self) -> str: r""" Return a string representation of ``self`` as a list. @@ -491,7 +493,7 @@ def _repr_list(self): """ return super(DyckWord, self)._repr_() - def _repr_lattice(self, type=None, labelling=None, underpath=True): + def _repr_lattice(self, type=None, labelling=None, underpath=True) -> str: r""" See :meth:`pretty_print()`. @@ -591,7 +593,7 @@ def _unicode_art_(self): from sage.typeset.unicode_art import UnicodeArt return UnicodeArt(self.to_path_string(unicode=True).splitlines()) - def __str__(self): + def __str__(self) -> str: r""" Return a string consisting of matched parentheses corresponding to the Dyck word. @@ -605,7 +607,7 @@ def __str__(self): """ return "".join(map(replace_symbols, [x for x in self])) - def to_path_string(self, unicode=False): + def to_path_string(self, unicode=False) -> str: r""" Return a path representation of the Dyck word consisting of steps ``/`` and ``\`` . @@ -810,7 +812,7 @@ def pretty_print(self, type=None, labelling=None, underpath=True): pp = pretty_print - def _latex_(self): + def _latex_(self) -> str: r""" A latex representation of ``self`` using the tikzpicture package. @@ -859,7 +861,7 @@ def _latex_(self): ht.append((a + 1, b - 1)) if i < len(heights) - 1 and heights[i + 1] > heights[i]: valleys.append(ht[-1]) - ht = iter(ht) + hti = iter(ht) if diagonal: grid = [((0, i), (i, i + 1)) for i in range(self.number_of_open_symbols())] @@ -886,8 +888,8 @@ def _latex_(self): if diagonal: res += " \\draw (0,0) -- %s;\n" % str((self.number_of_open_symbols(), self.number_of_open_symbols())) res += " \\draw[rounded corners=1, color=%s, line width=%s] (0, 0)" % (latex_options['color'], str(latex_options['line width'])) - next(ht) - for i, j in ht: + next(hti) + for i, j in hti: res += " -- (%s, %s)" % (i, j) res += ";\n" res += "\\end{tikzpicture}$}}" @@ -912,7 +914,7 @@ def plot(self, **kwds): list_sigma.append(sigma) return list_plot(list_sigma, plotjoined=True, **kwds) - def length(self): + def length(self) -> int: r""" Return the length of ``self``. @@ -930,7 +932,7 @@ def length(self): """ return len(self) - def number_of_open_symbols(self): + def number_of_open_symbols(self) -> int: r""" Return the number of open symbols in ``self``. @@ -948,7 +950,7 @@ def number_of_open_symbols(self): """ return len([x for x in self if x == open_symbol]) - def number_of_close_symbols(self): + def number_of_close_symbols(self) -> int: r""" Return the number of close symbols in ``self``. @@ -966,7 +968,7 @@ def number_of_close_symbols(self): """ return len([x for x in self if x == close_symbol]) - def is_complete(self): + def is_complete(self) -> bool: r""" Return ``True`` if ``self`` is complete. @@ -986,7 +988,7 @@ def is_complete(self): """ return self.number_of_open_symbols() == self.number_of_close_symbols() - def height(self): + def height(self) -> int: r""" Return the height of ``self``. @@ -1027,7 +1029,7 @@ def height(self): height -= 1 return height_max - def heights(self): + def heights(self) -> tuple: r""" Return the heights of ``self``. @@ -1068,7 +1070,7 @@ def heights(self): heights[i + 1] = height return tuple(heights) - def associated_parenthesis(self, pos): + def associated_parenthesis(self, pos) -> Union[int, None]: r""" Report the position for the parenthesis in ``self`` that matches the one at position ``pos``. @@ -1128,7 +1130,7 @@ def associated_parenthesis(self, pos): height -= 1 return pos - def ascent_prime_decomposition(self): + def ascent_prime_decomposition(self) -> list: r""" Decompose this Dyck word into a sequence of ascents and prime Dyck paths. @@ -1182,7 +1184,7 @@ def ascent_prime_decomposition(self): result.append(DyckWord([open_symbol] * up)) return result - def catalan_factorization(self): + def catalan_factorization(self) -> list: r""" Decompose this Dyck word into a sequence of complete Dyck words. @@ -1190,8 +1192,7 @@ def catalan_factorization(self): Each element of the list returned is a (possibly empty) complete Dyck word. The original word is obtained by placing an up step between each of these complete Dyck words. Thus, - the number of words returned is one more than the final - height. + the number of words returned is one more than the final height. See Section 1.2 of [CC1982]_ or Lemma 9.1.1 of [Lot2005]_. @@ -1225,7 +1226,7 @@ def catalan_factorization(self): j -= 1 return result - def number_of_initial_rises(self): + def number_of_initial_rises(self) -> int: r""" Return the length of the initial run of ``self``. @@ -1258,7 +1259,7 @@ def number_of_initial_rises(self): i += 1 return i - def peaks(self): + def peaks(self) -> list: r""" Return a list of the positions of the peaks of a Dyck word. @@ -1279,7 +1280,7 @@ def peaks(self): return [i for i in range(len(self) - 1) if self[i] == open_symbol and self[i + 1] == close_symbol] - def number_of_peaks(self): + def number_of_peaks(self) -> int: r""" Return the number of peaks of the Dyck path associated to ``self`` . @@ -1298,7 +1299,7 @@ def number_of_peaks(self): """ return len(self.peaks()) - def valleys(self): + def valleys(self) -> list: r""" Return a list of the positions of the valleys of a Dyck word. @@ -1318,7 +1319,7 @@ def valleys(self): return [i for i in range(len(self) - 1) if self[i] == close_symbol and self[i + 1] == open_symbol] - def number_of_valleys(self): + def number_of_valleys(self) -> int: r""" Return the number of valleys of ``self``. @@ -1344,7 +1345,7 @@ def number_of_valleys(self): """ return len(self.valleys()) - def position_of_first_return(self): + def position_of_first_return(self) -> int: r""" Return the number of vertical steps before the Dyck path returns to the main diagonal. @@ -1368,7 +1369,7 @@ def position_of_first_return(self): else: return touches[0] - def positions_of_double_rises(self): + def positions_of_double_rises(self) -> list: r""" Return a list of positions in ``self`` where there are two consecutive `1`'s. @@ -1385,7 +1386,7 @@ def positions_of_double_rises(self): return [i for i in range(len(self) - 1) if self[i] == self[i + 1] == open_symbol] - def number_of_double_rises(self): + def number_of_double_rises(self) -> int: r""" Return a the number of positions in ``self`` where there are two consecutive `1`'s. @@ -1401,7 +1402,7 @@ def number_of_double_rises(self): """ return len(self.positions_of_double_rises()) - def returns_to_zero(self): + def returns_to_zero(self) -> list: r""" Return a list of positions where ``self`` has height `0`, excluding the position `0`. @@ -1428,7 +1429,7 @@ def returns_to_zero(self): points.append(i + 1) return points - def touch_points(self): + def touch_points(self) -> list: r""" Return the abscissae (or, equivalently, ordinates) of the points where the Dyck path corresponding to ``self`` (comprising @@ -1485,7 +1486,7 @@ def touch_composition(self): return Composition([]) return Composition(descents=[i - 1 for i in self.touch_points()]) - def number_of_touch_points(self): + def number_of_touch_points(self) -> int: r""" Return the number of touches of ``self`` at the main diagonal. @@ -1577,7 +1578,7 @@ def to_standard_tableau(self): from sage.combinat.tableau import StandardTableau return StandardTableau([x for x in [open_positions, close_positions] if x]) - def to_tamari_sorting_tuple(self): + def to_tamari_sorting_tuple(self) -> list: """ Convert a Dyck word to a Tamari sorting tuple. @@ -1755,7 +1756,7 @@ def tamari_interval(self, other): from sage.combinat.interval_posets import TamariIntervalPosets return TamariIntervalPosets.from_dyck_words(self, other) - def to_area_sequence(self): + def to_area_sequence(self) -> list: r""" Return the area sequence of the Dyck word ``self``. @@ -1825,7 +1826,7 @@ class DyckWord_complete(DyckWord): For further information on Dyck words, see :class:`DyckWords_class`. """ - def semilength(self): + def semilength(self) -> int: r""" Return the semilength of ``self``. @@ -1886,7 +1887,7 @@ def to_partition(self): res.append(n) return Partition(res) - def number_of_parking_functions(self): + def number_of_parking_functions(self) -> int: r""" Return the number of parking functions with ``self`` as the supporting Dyck path. @@ -1931,7 +1932,7 @@ def list_parking_functions(self): # TODO: upon implementation of ParkingFunction class # map(ParkingFunction, Permutations([i - alist[i]+1 for i in range(len(alist))])) - def reading_permutation(self): + def reading_permutation(self) -> Permutation: r""" Return the reading permutation of ``self``. @@ -2009,7 +2010,7 @@ def characteristic_symmetric_function(self, q=None, s = SymmetricFunctions(R).s() return s(QSexpr.to_symmetric_function()) - def to_pair_of_standard_tableaux(self): + def to_pair_of_standard_tableaux(self) -> tuple: r""" Convert ``self`` to a pair of standard tableaux of the same shape and of length less than or equal to two. @@ -2045,11 +2046,12 @@ def to_pair_of_standard_tableaux(self): return (Tableau(left), Tableau(right)) @combinatorial_map(name='to 312 avoiding permutation') - def to_312_avoiding_permutation(self): + def to_312_avoiding_permutation(self) -> Permutation: r""" - Convert ``self`` to a `312`-avoiding permutation using the bijection by - Bandlow and Killpatrick in [BK2001]_. Sends the area to the - inversion number. + Convert ``self`` to a `312`-avoiding permutation using the bijection + by Bandlow and Killpatrick in [BK2001]_. + + This sends the area to the inversion number. EXAMPLES:: @@ -2081,7 +2083,7 @@ def to_312_avoiding_permutation(self): return pi @combinatorial_map(name='to non-crossing permutation') - def to_noncrossing_permutation(self): + def to_noncrossing_permutation(self) -> Permutation: r""" Use the bijection by C. Stump in [Stu2008]_ to send ``self`` to a non-crossing permutation. @@ -2127,7 +2129,7 @@ def to_noncrossing_permutation(self): return Permutations()(pi, check_input=False) @combinatorial_map(name='to 321 avoiding permutation') - def to_321_avoiding_permutation(self): + def to_321_avoiding_permutation(self) -> Permutation: r""" Use the bijection (pp. 60-61 of [Knu1973]_ or section 3.1 of [CK2008]_) to send ``self`` to a `321`-avoiding permutation. @@ -2182,7 +2184,7 @@ def to_321_avoiding_permutation(self): return RSK_inverse(A, B, output='permutation') @combinatorial_map(name='to 132 avoiding permutation') - def to_132_avoiding_permutation(self): + def to_132_avoiding_permutation(self) -> Permutation: r""" Use the bijection by C. Krattenthaler in [Kra2001]_ to send ``self`` to a `132`-avoiding permutation. @@ -2217,7 +2219,7 @@ def to_132_avoiding_permutation(self): values.remove(v) return Permutation(pi) - def to_permutation(self, map): + def to_permutation(self, map) -> Permutation: r""" This is simply a method collecting all implemented maps from Dyck words to permutations. @@ -2250,6 +2252,14 @@ def to_permutation(self, map): [1, 2, 4, 3] sage: D.to_permutation(map="Krattenthaler") [2, 1, 3, 4] + + TESTS:: + + sage: D = DyckWord([1,0,1,0]) + sage: D.to_permutation('Banana') + Traceback (most recent call last): + ... + ValueError: the given map is not valid """ if map == "Bandlow-Killpatrick": return self.to_312_avoiding_permutation() @@ -2260,7 +2270,7 @@ def to_permutation(self, map): elif map == "Stump": return self.to_noncrossing_permutation() else: - raise ValueError("The given map is not valid.") + raise ValueError("the given map is not valid") def to_noncrossing_partition(self, bijection=None): r""" @@ -2340,7 +2350,7 @@ def to_noncrossing_partition(self, bijection=None): return partition - def to_Catalan_code(self): + def to_Catalan_code(self) -> list: r""" Return the Catalan code associated to ``self``. @@ -2381,6 +2391,8 @@ def to_Catalan_code(self): if not self: return [] cut = self.associated_parenthesis(0) + if cut is None: + raise ValueError('not valid for incomplete Dyck words') recdw = DyckWord(self[1:cut] + self[cut + 1:]) returns = [0] + recdw.returns_to_zero() res = recdw.to_Catalan_code() @@ -2429,7 +2441,7 @@ def to_ordered_tree(self): root.set_immutable() return root - def to_triangulation(self): + def to_triangulation(self) -> list: r""" Map ``self`` to a triangulation. @@ -2548,7 +2560,7 @@ def to_non_decreasing_parking_function(self): from sage.combinat.non_decreasing_parking_function import NonDecreasingParkingFunction return NonDecreasingParkingFunction.from_dyck_word(self) - def major_index(self): + def major_index(self) -> int: r""" Return the major index of ``self`` . @@ -2576,7 +2588,7 @@ def major_index(self): valleys = self.valleys() return sum(valleys) + len(valleys) - def pyramid_weight(self): + def pyramid_weight(self) -> int: r""" Return the pyramid weight of ``self``. @@ -2608,8 +2620,8 @@ def pyramid_weight(self): if bseq[i + 1] <= bseq[i]: bpeak.append(i) out = 0 - for i in range(len(apeak)): - out += min(aseq[apeak[i]] - aseq[apeak[i] + 1] + 1, + for i, apeaki in enumerate(apeak): + out += min(aseq[apeaki] - aseq[apeaki + 1] + 1, bseq[bpeak[-i - 1]] - bseq[bpeak[-i - 1] + 1] + 1) return out @@ -2632,7 +2644,7 @@ def tunnels(self): if height < heights[i + 1]: yield (i, i + 1 + heights[i + 1:].index(height)) - def number_of_tunnels(self, tunnel_type='centered'): + def number_of_tunnels(self, tunnel_type='centered') -> int: r""" Return the number of tunnels of ``self`` of type ``tunnel_type``. @@ -2674,7 +2686,7 @@ def number_of_tunnels(self, tunnel_type='centered'): raise ValueError("The given tunnel_type is not valid.") @combinatorial_map(order=2, name="Reverse path") - def reverse(self): + def reverse(self) -> DyckWord: r""" Return the reverse and complement of ``self``. @@ -2704,7 +2716,7 @@ def reverse(self): list.reverse() return DyckWord(list) - def first_return_decomposition(self): + def first_return_decomposition(self) -> tuple: r""" Decompose a Dyck word into a pair of Dyck words (potentially empty) where the first word consists of the word after the first up step and @@ -2722,7 +2734,7 @@ def first_return_decomposition(self): k = self.position_of_first_return() * 2 return DyckWord(self[1:k - 1]), DyckWord(self[k:]) - def decomposition_reverse(self): + def decomposition_reverse(self) -> DyckWord: r""" Return the involution of ``self`` with a recursive definition. @@ -2749,7 +2761,7 @@ def decomposition_reverse(self): + [0] + list(D1.decomposition_reverse())) @combinatorial_map(name="Area-dinv to bounce-area") - def area_dinv_to_bounce_area_map(self): + def area_dinv_to_bounce_area_map(self) -> DyckWord: r""" Return the image of ``self`` under the map which sends a Dyck word with ``area`` equal to `r` and ``dinv`` equal to `s` to a @@ -2795,7 +2807,7 @@ def area_dinv_to_bounce_area_map(self): return DyckWord(image) @combinatorial_map(name="Bounce-area to area-dinv") - def bounce_area_to_area_dinv_map(self): + def bounce_area_to_area_dinv_map(self) -> DyckWord: r""" Return the image of the Dyck word under the map which sends a Dyck word with ``bounce`` equal to `r` and ``area`` equal to `s` to a @@ -2841,7 +2853,7 @@ def bounce_area_to_area_dinv_map(self): zeros = [0] + [j + len(out) - p for j in zeros[:aseq[i]]] return DyckWord(out) - def area(self): + def area(self) -> int: r""" Return the area for ``self`` corresponding to the area of the Dyck path. @@ -2911,7 +2923,7 @@ def area(self): a += above - diagonal return a - def bounce_path(self): + def bounce_path(self) -> DyckWord: r""" Return the bounce path of ``self`` formed by starting at `(n,n)` and traveling West until encountering the first vertical step of ``self``, @@ -2953,7 +2965,7 @@ def bounce_path(self): i -= 1 return DyckWord(area_sequence=area_seq) - def bounce(self): + def bounce(self) -> int: r""" Return the bounce statistic of ``self`` due to J. Haglund, see [Hag2008]_. @@ -3047,7 +3059,7 @@ def bounce(self): return b - def dinv(self, labeling=None): + def dinv(self, labeling=None) -> int: r""" Return the dinv statistic of ``self`` due to M. Haiman, see [Hag2008]_. @@ -3316,7 +3328,7 @@ def _element_constructor_(self, word): return word return self.element_class(self, list(word)) - def __contains__(self, x): + def __contains__(self, x) -> bool: r""" TESTS:: @@ -3338,7 +3350,7 @@ def __contains__(self, x): return is_a(x) - def from_heights(self, heights): + def from_heights(self, heights) -> DyckWord: r""" Compute a Dyck word knowing its heights. @@ -3400,7 +3412,7 @@ def from_heights(self, heights): raise ValueError("consecutive heights must differ by exactly 1: %s" % (heights,)) return self.element_class(self, res) - def min_from_heights(self, heights): + def min_from_heights(self, heights) -> DyckWord: r""" Compute the smallest Dyck word which achieves or surpasses a given sequence of heights. @@ -3480,7 +3492,7 @@ def __init__(self): """ DyckWords.__init__(self, category=InfiniteEnumeratedSets()) - def _repr_(self): + def _repr_(self) -> str: r""" TESTS:: @@ -3489,7 +3501,7 @@ def _repr_(self): """ return "Dyck words" - def _an_element_(self): + def _an_element_(self) -> DyckWord: r""" TESTS:: @@ -3623,7 +3635,7 @@ def __init__(self, k1, k2): self.k2 = ZZ(k2) DyckWords.__init__(self, category=FiniteEnumeratedSets()) - def _repr_(self): + def _repr_(self) -> str: r""" TESTS:: @@ -3632,7 +3644,7 @@ def _repr_(self): """ return "Dyck words with %s opening parentheses and %s closing parentheses" % (self.k1, self.k2) - def __contains__(self, x): + def __contains__(self, x) -> bool: r""" EXAMPLES:: @@ -3679,7 +3691,7 @@ def __iter__(self): for w in DyckWordBacktracker(self.k1, self.k2): yield self.element_class(self, w) - def cardinality(self): + def cardinality(self) -> int: r""" Return the number of Dyck words with `k_1` openers and `k_2` closers. @@ -3715,7 +3727,7 @@ class CompleteDyckWords(DyckWords): """ Element = DyckWord_complete - def __contains__(self, x): + def __contains__(self, x) -> bool: r""" TESTS:: @@ -3737,7 +3749,7 @@ def __contains__(self, x): return is_a(x, len(x) // 2) - def from_Catalan_code(self, code): + def from_Catalan_code(self, code) -> DyckWord: r""" Return the Dyck word associated to the given Catalan code ``code``. @@ -3778,7 +3790,7 @@ def from_Catalan_code(self, code): lst = [1] + res[:cuts[code[-1]]] + [0] + res[cuts[code[-1]]:] return self.element_class(self, lst) - def from_area_sequence(self, code): + def from_area_sequence(self, code) -> DyckWord: r""" Return the Dyck word associated to the given area sequence ``code``. @@ -3816,7 +3828,7 @@ def from_area_sequence(self, code): dyck_word.extend([close_symbol] * (2 * len(code) - len(dyck_word))) return self.element_class(self, dyck_word) - def from_noncrossing_partition(self, ncp): + def from_noncrossing_partition(self, ncp) -> DyckWord: r""" Convert a noncrossing partition ``ncp`` to a Dyck word. @@ -3844,7 +3856,7 @@ def from_noncrossing_partition(self, ncp): res += [open_symbol] + [close_symbol] * i return self.element_class(self, res) - def from_non_decreasing_parking_function(self, pf): + def from_non_decreasing_parking_function(self, pf) -> DyckWord: r""" Bijection from :class:`non-decreasing parking functions`. @@ -3885,7 +3897,7 @@ class CompleteDyckWords_all(CompleteDyckWords, DyckWords_all): """ All complete Dyck words. """ - def _repr_(self): + def _repr_(self) -> str: r""" TESTS:: @@ -4003,7 +4015,7 @@ def __init__(self, k): CompleteDyckWords.__init__(self, category=FiniteEnumeratedSets()) DyckWords_size.__init__(self, k, k) - def __contains__(self, x): + def __contains__(self, x) -> bool: r""" TESTS:: @@ -4018,7 +4030,7 @@ def __contains__(self, x): """ return CompleteDyckWords.__contains__(self, x) and len(x) // 2 == self.k1 - def cardinality(self): + def cardinality(self) -> int: r""" Return the number of complete Dyck words of semilength `n`, i.e. the `n`-th :func:`Catalan number`. @@ -4034,7 +4046,7 @@ def cardinality(self): """ return catalan_number(self.k1) - def random_element(self): + def random_element(self) -> DyckWord: """ Return a random complete Dyck word of semilength `n`. @@ -4125,7 +4137,7 @@ def _iter_by_recursion(self): yield self.element_class(self, list_1p0 + list(s)) -def is_area_sequence(seq): +def is_area_sequence(seq) -> bool: r""" Test if a sequence `l` of integers satisfies `l_0 = 0` and `0 \leq l_{i+1} \leq l_i + 1` for `i > 0`. @@ -4150,7 +4162,7 @@ def is_area_sequence(seq): for i in range(len(seq) - 1)) -def is_a(obj, k1=None, k2=None): +def is_a(obj, k1=None, k2=None) -> bool: r""" Test if ``obj`` is a Dyck word with exactly ``k1`` open symbols and exactly ``k2`` close symbols. diff --git a/src/sage/combinat/e_one_star.py b/src/sage/combinat/e_one_star.py index 8959223d681..2cca168d20f 100644 --- a/src/sage/combinat/e_one_star.py +++ b/src/sage/combinat/e_one_star.py @@ -201,13 +201,14 @@ # Vincent Delecroix <20100.delecroix@gmail.com> # Timo Jolivet # Stepan Starosta -# Sebastien Labbe +# Sébastien Labbé # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of # the License, or (at your option) any later version. # https://www.gnu.org/licenses/ # **************************************************************************** +from __future__ import annotations from sage.misc.functional import det from sage.structure.sage_object import SageObject @@ -299,7 +300,7 @@ def __init__(self, v, t, color=None): color = Color() self._color = Color(color) - def __repr__(self): + def __repr__(self) -> str: r""" String representation of a face. @@ -320,7 +321,7 @@ def __repr__(self): __richcmp__ = richcmp_by_eq_and_lt('_eq', '_lt') - def _eq(self, other): + def _eq(self, other) -> bool: r""" Equality of faces. @@ -336,7 +337,7 @@ def _eq(self, other): self.vector() == other.vector() and self.type() == other.type()) - def _lt(self, other): + def _lt(self, other) -> bool: r""" Compare ``self`` and ``other``. @@ -357,8 +358,9 @@ def _lt(self, other): return True if self.vector() == other.vector(): return self.type() < other.type() + return False - def __hash__(self): + def __hash__(self) -> int: r""" EXAMPLES:: @@ -445,7 +447,7 @@ def color(self, color=None): OUTPUT: - color + color or None EXAMPLES:: @@ -456,14 +458,13 @@ def color(self, color=None): sage: f.color('red') sage: f.color() RGB color (1.0, 0.0, 0.0) - """ - if color is None: - return self._color - else: + if color is not None: self._color = Color(color) + else: + return self._color - def _plot(self, projmat, face_contour, opacity): + def _plot(self, projmat, face_contour, opacity) -> Graphics: r""" Return a 2D graphic object representing the face. @@ -476,7 +477,7 @@ def _plot(self, projmat, face_contour, opacity): OUTPUT: - 2D graphic object + 2D graphic object EXAMPLES:: @@ -578,7 +579,9 @@ class Patch(SageObject): """ def __init__(self, faces, face_contour=None): r""" - Constructor of a patch (set of faces). See class doc for more information. + Constructor of a patch (set of faces). + + See class doc for more information. EXAMPLES:: @@ -599,7 +602,6 @@ def __init__(self, faces, face_contour=None): sage: next(iter(Q)).color('yellow') sage: next(iter(P)).color() RGB color (0.0, 1.0, 0.0) - """ self._faces = frozenset(Face(f.vector(), f.type(), f.color()) for f in faces) @@ -623,7 +625,7 @@ def __init__(self, faces, face_contour=None): (1, 1, 0), (0, 1, 0)]] } - def __eq__(self, other): + def __eq__(self, other) -> bool: r""" Equality test for Patch. @@ -655,7 +657,7 @@ def __eq__(self, other): """ return (isinstance(other, Patch) and self._faces == other._faces) - def __hash__(self): + def __hash__(self) -> int: r""" Hash function of Patch. @@ -689,13 +691,13 @@ def __hash__(self): """ return hash(self._faces) - def __len__(self): + def __len__(self) -> int: r""" Return the number of faces contained in the patch. OUTPUT: - integer + integer EXAMPLES:: @@ -713,7 +715,7 @@ def __iter__(self): OUTPUT: - iterator + iterator EXAMPLES:: @@ -775,7 +777,7 @@ def __sub__(self, other): """ return self.difference(other) - def __repr__(self): + def __repr__(self) -> str: r""" String representation of a patch. @@ -804,7 +806,7 @@ def __repr__(self): else: return "Patch of %s faces" % len(self) - def union(self, other): + def union(self, other) -> Patch: r""" Return a Patch consisting of the union of self and other. @@ -826,7 +828,7 @@ def union(self, other): else: return Patch(self._faces.union(other)) - def difference(self, other): + def difference(self, other) -> Patch: r""" Return the difference of self and other. @@ -848,7 +850,7 @@ def difference(self, other): else: return Patch(self._faces.difference(other)) - def dimension(self): + def dimension(self) -> None | int: r""" Return the dimension of the vectors of the faces of self @@ -879,7 +881,7 @@ def dimension(self): """ return self._dimension - def faces_of_vector(self, v): + def faces_of_vector(self, v) -> list: r""" Return a list of the faces whose vector is ``v``. @@ -897,7 +899,7 @@ def faces_of_vector(self, v): v = vector(v) return [f for f in self if f.vector() == v] - def faces_of_type(self, t): + def faces_of_type(self, t) -> list: r""" Return a list of the faces that have type ``t``. @@ -914,7 +916,7 @@ def faces_of_type(self, t): """ return [f for f in self if f.type() == t] - def faces_of_color(self, color): + def faces_of_color(self, color) -> list: r""" Return a list of the faces that have the given color. @@ -932,7 +934,7 @@ def faces_of_color(self, color): color = tuple(Color(color)) return [f for f in self if tuple(f.color()) == color] - def translate(self, v): + def translate(self, v) -> Patch: r""" Return a translated copy of self by vector ``v``. @@ -950,7 +952,7 @@ def translate(self, v): v = vector(v) return Patch(Face(f.vector() + v, f.type(), f.color()) for f in self) - def occurrences_of(self, other): + def occurrences_of(self, other) -> list: r""" Return all positions at which other appears in self, that is, all vectors v such that ``set(other.translate(v)) <= set(self)``. @@ -961,7 +963,7 @@ def occurrences_of(self, other): OUTPUT: - a list of vectors + a list of vectors EXAMPLES:: @@ -995,9 +997,9 @@ def occurrences_of(self, other): positions.append(y - x) return positions - def repaint(self, cmap='Set1'): + def repaint(self, cmap='Set1') -> None: r""" - Repaints all the faces of self from the given color map. + Repaint all the faces of self from the given color map. This only changes the colors of the faces of self. @@ -1062,7 +1064,7 @@ def repaint(self, cmap='Set1'): elif isinstance(cmap, str): # matplotlib color maps global cm - if not cm: + if cm is None: from matplotlib import cm if cmap not in cm.datad: @@ -1075,7 +1077,7 @@ def repaint(self, cmap='Set1'): else: raise TypeError("Type of cmap (=%s) must be dict, list or str" % cmap) - def plot(self, projmat=None, opacity=0.75): + def plot(self, projmat=None, opacity=0.75) -> Graphics: r""" Return a 2D graphic object depicting the patch. @@ -1182,7 +1184,7 @@ def plot3d(self): return G def plot_tikz(self, projmat=None, print_tikz_env=True, edgecolor='black', - scale=0.25, drawzero=False, extra_code_before='', extra_code_after=''): + scale=0.25, drawzero=False, extra_code_before='', extra_code_after='') -> str: r""" Return a string containing some TikZ code to be included into a LaTeX document, depicting the patch. @@ -1308,7 +1310,7 @@ def plot_tikz(self, projmat=None, print_tikz_env=True, edgecolor='black', e2 = projmat * vector([0, 1, 0]) e3 = projmat * vector([0, 0, 1]) face_contour = self._face_contour - color = () + color = None # string s contains the TiKZ code of the patch s = '' @@ -1323,7 +1325,7 @@ def plot_tikz(self, projmat=None, print_tikz_env=True, edgecolor='black', t = f.type() x, y, z = f.vector() - if tuple(color) != tuple(f.color()): # tuple is needed, comparison for RGB fails + if color is None or color != f.color(): color = f.color() s += '\\definecolor{facecolor}{rgb}{%.3f,%.3f,%.3f}\n' % (color[0], color[1], color[2]) @@ -1437,7 +1439,7 @@ def __init__(self, sigma, method='suffix'): X[letter].append((v, k)) self._base_iter = X - def __eq__(self, other): + def __eq__(self, other) -> bool: r""" Equality test for E1Star morphisms. @@ -1460,7 +1462,7 @@ def __eq__(self, other): """ return (isinstance(other, E1Star) and self._base_iter == other._base_iter) - def __call__(self, patch, iterations=1): + def __call__(self, patch, iterations=1) -> Patch: r""" Applies a generalized substitution to a Patch; this returns a new object. @@ -1473,7 +1475,7 @@ def __call__(self, patch, iterations=1): OUTPUT: - a patch + a patch EXAMPLES:: @@ -1509,7 +1511,7 @@ def __call__(self, patch, iterations=1): old_faces = new_faces return Patch(new_faces) - def __mul__(self, other): + def __mul__(self, other) -> E1Star: r""" Return the product of ``self`` and ``other``. @@ -1538,7 +1540,7 @@ def __mul__(self, other): raise TypeError("other (=%s) must be an instance of E1Star" % other) return E1Star(other.sigma() * self.sigma()) - def __repr__(self): + def __repr__(self) -> str: r""" String representation of a patch. @@ -1554,7 +1556,7 @@ def __repr__(self): def _call_on_face(self, face, color=None): r""" - Return an iterator of faces obtained by applying self on the face. + Return an iterator of faces obtained by applying ``self`` on the face. INPUT: @@ -1564,7 +1566,7 @@ def _call_on_face(self, face, color=None): OUTPUT: - iterator of faces + iterator of faces EXAMPLES:: @@ -1584,7 +1586,7 @@ def _call_on_face(self, face, color=None): @cached_method def matrix(self): r""" - Return the matrix associated with self. + Return the matrix associated with ``self``. EXAMPLES:: @@ -1601,7 +1603,7 @@ def matrix(self): @cached_method def inverse_matrix(self): r""" - Return the inverse of the matrix associated with self. + Return the inverse of the matrix associated with ``self``. EXAMPLES:: @@ -1616,9 +1618,9 @@ def inverse_matrix(self): """ return self.matrix().inverse() - def sigma(self): + def sigma(self) -> WordMorphism: r""" - Return the ``WordMorphism`` associated with self. + Return the ``WordMorphism`` associated with ``self``. EXAMPLES:: diff --git a/src/sage/combinat/knutson_tao_puzzles.py b/src/sage/combinat/knutson_tao_puzzles.py index 5dd7d2b46ed..39a36751b88 100644 --- a/src/sage/combinat/knutson_tao_puzzles.py +++ b/src/sage/combinat/knutson_tao_puzzles.py @@ -37,6 +37,8 @@ # Distributed under the terms of the GNU General Public License (GPL) # https://www.gnu.org/licenses/ # **************************************************************************** +from __future__ import annotations + from sage.plot.graphics import Graphics from sage.plot.polygon import polygon from sage.plot.line import line @@ -301,7 +303,7 @@ def __repr__(self) -> str: self['north'], self['south_east']) - def clockwise_rotation(self) -> 'NablaPiece': + def clockwise_rotation(self) -> NablaPiece: r""" Rotate the Nabla piece by 120 degree clockwise. @@ -320,7 +322,7 @@ def clockwise_rotation(self) -> 'NablaPiece': south_east=self['north'], south_west=self['south_east']) - def half_turn_rotation(self) -> 'DeltaPiece': + def half_turn_rotation(self) -> DeltaPiece: r""" Rotate the Nabla piece by 180 degree. @@ -423,7 +425,7 @@ def __repr__(self) -> str: self['south'], self['north_east']) - def clockwise_rotation(self) -> 'DeltaPiece': + def clockwise_rotation(self) -> DeltaPiece: r""" Rotate the Delta piece by 120 degree clockwise. @@ -442,7 +444,7 @@ def clockwise_rotation(self) -> 'DeltaPiece': north_west=self['south'], north_east=self['north_west']) - def half_turn_rotation(self) -> 'NablaPiece': + def half_turn_rotation(self) -> NablaPiece: r""" Rotate the Delta piece by 180 degree. @@ -559,7 +561,7 @@ def __iter__(self): yield self._north_piece yield self._south_piece - def north_piece(self) -> 'DeltaPiece': + def north_piece(self) -> DeltaPiece: r""" Return the north piece. @@ -574,7 +576,7 @@ def north_piece(self) -> 'DeltaPiece': """ return self._north_piece - def south_piece(self) -> 'NablaPiece': + def south_piece(self) -> NablaPiece: r""" Return the south piece. diff --git a/src/sage/combinat/non_decreasing_parking_function.py b/src/sage/combinat/non_decreasing_parking_function.py index b3d51b9fd3b..cea50974f0c 100644 --- a/src/sage/combinat/non_decreasing_parking_function.py +++ b/src/sage/combinat/non_decreasing_parking_function.py @@ -30,6 +30,7 @@ # # https://www.gnu.org/licenses/ # **************************************************************************** +from __future__ import annotations from copy import copy @@ -120,7 +121,7 @@ def NonDecreasingParkingFunctions(n=None): return NonDecreasingParkingFunctions_n(n) -def is_a(x, n=None): +def is_a(x, n=None) -> bool: """ Check whether a list is a non-decreasing parking function. @@ -144,9 +145,7 @@ def is_a(x, n=None): if prev > elt or elt > i + 1: return False prev = elt - if n is not None and n != len(x): - return False - return True + return n is None or n == len(x) class NonDecreasingParkingFunction(Element): @@ -220,7 +219,7 @@ def __call__(self, n): """ return self._list[n - 1] - def _mul_(self, lp): + def _mul_(self, lp) -> NonDecreasingParkingFunction: """ The composition of non-decreasing parking functions. @@ -280,7 +279,7 @@ def to_dyck_word(self): from sage.combinat.dyck_word import CompleteDyckWords_all return CompleteDyckWords_all().from_non_decreasing_parking_function(self) - def __len__(self): + def __len__(self) -> int: """ Return the length of ``self``. @@ -294,7 +293,7 @@ def __len__(self): grade = __len__ # for the category SetsWithGrading - def _repr_(self): + def _repr_(self) -> str: """ Return the string representation of ``self``. @@ -305,7 +304,7 @@ def _repr_(self): """ return str(self._list) - def _richcmp_(self, other, op): + def _richcmp_(self, other, op) -> bool: """ Compare ``self`` with ``other``. @@ -318,7 +317,7 @@ def _richcmp_(self, other, op): """ return richcmp(self._list, other._list, op) - def __hash__(self): + def __hash__(self) -> int: """ Return the hash of ``self``. @@ -332,7 +331,7 @@ def __hash__(self): return hash(tuple(self._list)) @classmethod - def from_dyck_word(cls, dw): + def from_dyck_word(cls, dw) -> NonDecreasingParkingFunction: """ Bijection from :class:`Dyck words`. It is the inverse of the @@ -380,7 +379,7 @@ def __init__(self): cat = InfiniteEnumeratedSets() & SetsWithGrading() Parent.__init__(self, category=cat) - def __repr__(self): + def __repr__(self) -> str: """ TESTS:: @@ -389,7 +388,7 @@ def __repr__(self): """ return "Non-decreasing parking functions" - def __contains__(self, x): + def __contains__(self, x) -> bool: """ TESTS:: @@ -482,7 +481,7 @@ def __init__(self, n): self.n = n Parent.__init__(self, category=Monoids().Enumerated().Finite()) - def __repr__(self): + def __repr__(self) -> str: """ TESTS:: @@ -491,7 +490,7 @@ def __repr__(self): """ return "Non-decreasing parking functions of size %s" % self.n - def __contains__(self, x): + def __contains__(self, x) -> bool: """ TESTS:: @@ -514,7 +513,7 @@ def __contains__(self, x): return True return is_a(x, self.n) - def cardinality(self): + def cardinality(self) -> Integer: """ Return the number of non-decreasing parking functions of size `n`. @@ -539,7 +538,7 @@ def cardinality(self): """ return catalan_number(self.n) - def random_element(self): + def random_element(self) -> NonDecreasingParkingFunction: """ Return a random parking function of the given size. @@ -556,7 +555,7 @@ def random_element(self): dw = DyckWords(n).random_element() return NonDecreasingParkingFunction.from_dyck_word(dw) - def one(self): + def one(self) -> NonDecreasingParkingFunction: """ Return the unit of this monoid. diff --git a/src/sage/combinat/root_system/non_symmetric_macdonald_polynomials.py b/src/sage/combinat/root_system/non_symmetric_macdonald_polynomials.py index ee8ddec7ddb..f1dc9f608b4 100644 --- a/src/sage/combinat/root_system/non_symmetric_macdonald_polynomials.py +++ b/src/sage/combinat/root_system/non_symmetric_macdonald_polynomials.py @@ -3,7 +3,7 @@ AUTHORS: -- Anne Schilling and Nicolas M. Thiery (2013): initial version +- Anne Schilling and Nicolas M. Thiéry (2013): initial version ACKNOWLEDGEMENTS: @@ -218,19 +218,7 @@ class NonSymmetricMacdonaldPolynomials(CherednikOperatorsEigenvectors): Lazy family (...)_{i in Coroot lattice of the Root system of type ['C', 2, 1]} sage: alphacheck = Y.keys().simple_roots() sage: Y1 = Y[alphacheck[1]] - sage: Y1(x) # py2 - ((q1^3+q1^2*q2)/(-q2^3))*B[-e[0] - 2*e[1] - e['delta'] + e['deltacheck']] - + ((q1^3+2*q1^2*q2+q1*q2^2)/(-q2^3))*B[-e[0] - e['delta'] + e['deltacheck']] - + ((q1^3+q1^2*q2)/(-q2^3))*B[e[0] - 2*e[1] - 2*e['delta'] + e['deltacheck']] - + ((q1^3+q1^2*q2)/(-q2^3))*B[e[0] - 2*e[1] - e['delta'] + e['deltacheck']] - + ((q1^3+2*q1^2*q2+q1*q2^2)/(-q2^3))*B[e[0] - 2*e['delta'] + e['deltacheck']] - + ((q1^3+q1^2*q2)/(-q2^3))*B[e[0] - e['delta'] + e['deltacheck']] + ((q1^2+2*q1*q2+q2^2)/(-q1*q2))*B[e[0] + e['deltacheck']] - + ((q1^3+q1^2*q2)/(-q2^3))*B[2*e[0] - e[1] - 2*e['delta'] + e['deltacheck']] - + ((-q1^2-q1*q2)/(-q2^2))*B[2*e[0] - e[1] - e['delta'] + e['deltacheck']] + (q1^3/(-q2^3))*B[3*e[0] - 2*e[1] - 3*e['delta'] + e['deltacheck']] - + ((q1^3+q1^2*q2)/(-q2^3))*B[3*e[0] - 3*e['delta'] + e['deltacheck']] - + ((q1^3+2*q1^2*q2+q1*q2^2)/(-q2^3))*B[-e[1] - e['delta'] + e['deltacheck']] - + ((-q1^2-2*q1*q2-q2^2)/(-q2^2))*B[-e[1] + e['deltacheck']] + ((q1+q2)/(-q2))*B[e[1] + e['deltacheck']] - sage: Y1(x) # py3 + sage: Y1(x) ((q1^2+2*q1*q2+q2^2)/(-q1*q2))*B[e[0] + e['deltacheck']] + ((-q1^2-2*q1*q2-q2^2)/(-q2^2))*B[-e[1] + e['deltacheck']] + ((-q1^2-q1*q2)/(-q2^2))*B[2*e[0] - e[1] - e['delta'] diff --git a/src/sage/combinat/set_partition_ordered.py b/src/sage/combinat/set_partition_ordered.py index 8c831186433..9e0a85048d4 100644 --- a/src/sage/combinat/set_partition_ordered.py +++ b/src/sage/combinat/set_partition_ordered.py @@ -882,9 +882,7 @@ class OrderedSetPartitions(UniqueRepresentation, Parent): :: sage: OS = OrderedSetPartitions("cat") - sage: OS # py2 - Ordered set partitions of {'a', 'c', 't'} - sage: OS # py3 random + sage: OS # random Ordered set partitions of {'a', 't', 'c'} sage: sorted(OS.list(), key=str) [[{'a', 'c', 't'}], diff --git a/src/sage/combinat/sf/hall_littlewood.py b/src/sage/combinat/sf/hall_littlewood.py index f07d133e2f5..e2a2f18f775 100644 --- a/src/sage/combinat/sf/hall_littlewood.py +++ b/src/sage/combinat/sf/hall_littlewood.py @@ -66,7 +66,7 @@ def __repr__(self): - a string representing the class - EXAMPLES :: + EXAMPLES:: sage: SymmetricFunctions(QQ).hall_littlewood(1) Hall-Littlewood polynomials with t=1 over Rational Field @@ -102,7 +102,7 @@ def symmetric_function_ring( self ): - returns the ring of symmetric functions - EXAMPLES :: + EXAMPLES:: sage: HL = SymmetricFunctions(FractionField(QQ['t'])).hall_littlewood() sage: HL.symmetric_function_ring() @@ -123,7 +123,7 @@ def base_ring( self ): The base ring of the symmetric functions. - EXAMPLES :: + EXAMPLES:: sage: HL = SymmetricFunctions(QQ['t'].fraction_field()).hall_littlewood(t=1) sage: HL.base_ring() @@ -532,7 +532,7 @@ def hall_littlewood_family(self): - returns the class of Hall-Littlewood bases - EXAMPLES :: + EXAMPLES:: sage: HLP = SymmetricFunctions(FractionField(QQ['t'])).hall_littlewood(1).P() sage: HLP.hall_littlewood_family() diff --git a/src/sage/combinat/sf/jack.py b/src/sage/combinat/sf/jack.py index daee291d232..514823e82fb 100644 --- a/src/sage/combinat/sf/jack.py +++ b/src/sage/combinat/sf/jack.py @@ -491,7 +491,7 @@ def __init__(self, jack): - ``self`` -- a Jack basis of the symmetric functions - ``jack`` -- a family of Jack symmetric function bases - EXAMPLES :: + EXAMPLES:: sage: Sym = SymmetricFunctions(FractionField(QQ['t'])) sage: JP = Sym.jack().P(); JP.base_ring() @@ -537,7 +537,7 @@ def _m_to_self(self, x): - an element of ``self`` equivalent to ``x`` - EXAMPLES :: + EXAMPLES:: sage: Sym = SymmetricFunctions(QQ) sage: JP = Sym.jack(t=2).P() @@ -565,7 +565,7 @@ def _self_to_m(self, x): - an element of the monomial basis equivalent to ``x`` - EXAMPLES :: + EXAMPLES:: sage: Sym = SymmetricFunctions(QQ) sage: JP = Sym.jack(t=2).P() @@ -596,7 +596,7 @@ def c1(self, part): - a polynomial in the parameter ``t`` which is equal to the scalar product of ``J(part)`` and ``P(part)`` - EXAMPLES :: + EXAMPLES:: sage: JP = SymmetricFunctions(FractionField(QQ['t'])).jack().P() sage: JP.c1(Partition([2,1])) @@ -1029,7 +1029,7 @@ def scalar_jack(self, x, t=None): - ``self`` -- an element of the Jack `P` basis - ``x`` -- an element of the `P` basis - EXAMPLES :: + EXAMPLES:: sage: JP = SymmetricFunctions(FractionField(QQ['t'])).jack().P() sage: l = [JP(p) for p in Partitions(3)] @@ -1234,7 +1234,7 @@ def _self_to_h( self, x ): - an element of the homogeneous basis equivalent to ``x`` - EXAMPLES :: + EXAMPLES:: sage: Sym = SymmetricFunctions(QQ) sage: JQp = Sym.jack(t=2).Qp() @@ -1262,7 +1262,7 @@ def _h_to_self( self, x ): - an element of the Jack `Qp` basis equivalent to ``x`` - EXAMPLES :: + EXAMPLES:: sage: Sym = SymmetricFunctions(QQ) sage: JQp = Sym.jack(t=2).Qp() @@ -1320,7 +1320,7 @@ def __init__(self, Sym): - ``self`` -- a zonal basis of the symmetric functions - ``Sym`` -- a ring of the symmetric functions - EXAMPLES :: + EXAMPLES:: sage: Z = SymmetricFunctions(QQ).zonal() sage: Z([2])^2 @@ -1355,7 +1355,7 @@ def product(self, left, right): the product of ``left`` and ``right`` expanded in the basis ``self`` - EXAMPLES :: + EXAMPLES:: sage: Sym = SymmetricFunctions(QQ) sage: Z = Sym.zonal() @@ -1386,7 +1386,7 @@ def scalar_zonal(self, x): - the scalar product between ``self`` and ``x`` - EXAMPLES :: + EXAMPLES:: sage: Sym = SymmetricFunctions(QQ) sage: Z = Sym.zonal() diff --git a/src/sage/combinat/sf/llt.py b/src/sage/combinat/sf/llt.py index b913114435b..d7e4af8fe94 100644 --- a/src/sage/combinat/sf/llt.py +++ b/src/sage/combinat/sf/llt.py @@ -168,7 +168,7 @@ def symmetric_function_ring( self ): - returns the symmetric function ring associated to ``self``. - EXAMPLES :: + EXAMPLES:: sage: L3 = SymmetricFunctions(FractionField(QQ['t'])).llt(3) sage: L3.symmetric_function_ring() diff --git a/src/sage/combinat/sf/macdonald.py b/src/sage/combinat/sf/macdonald.py index f7c852aeb92..938b09d23dd 100644 --- a/src/sage/combinat/sf/macdonald.py +++ b/src/sage/combinat/sf/macdonald.py @@ -89,7 +89,7 @@ def __repr__(self): - a string representing the Macdonald symmetric function family - EXAMPLES :: + EXAMPLES:: sage: t = QQ['t'].gen(); SymmetricFunctions(QQ['t'].fraction_field()).macdonald(q=t,t=1) Macdonald polynomials with q=t and t=1 over Fraction Field of Univariate Polynomial Ring in t over Rational Field @@ -107,7 +107,7 @@ def __init__(self, Sym, q='q', t='t'): - ``self`` -- a family of Macdonald symmetric function bases - EXAMPLES :: + EXAMPLES:: sage: t = QQ['t'].gen(); SymmetricFunctions(QQ['t'].fraction_field()).macdonald(q=t,t=1) Macdonald polynomials with q=t and t=1 over Fraction Field of Univariate Polynomial Ring in t over Rational Field @@ -144,7 +144,7 @@ def base_ring( self ): - the base ring associated to the corresponding symmetric function ring - EXAMPLES :: + EXAMPLES:: sage: Sym = SymmetricFunctions(QQ['q'].fraction_field()) sage: Mac = Sym.macdonald(t=0) @@ -166,7 +166,7 @@ def symmetric_function_ring( self ): - the symmetric function ring associated to the Macdonald bases - EXAMPLES :: + EXAMPLES:: sage: Mac = SymmetricFunctions(QQ['q'].fraction_field()).macdonald(t=0) sage: Mac.symmetric_function_ring() @@ -187,7 +187,7 @@ def P(self): - returns the `P` Macdonald basis of symmetric functions - EXAMPLES :: + EXAMPLES:: sage: Sym = SymmetricFunctions(FractionField(QQ['q','t'])) sage: P = Sym.macdonald().P(); P @@ -914,7 +914,7 @@ def macdonald_family(self): - the family of Macdonald symmetric functions associated to ``self`` - EXAMPLES :: + EXAMPLES:: sage: MacP = SymmetricFunctions(QQ['q'].fraction_field()).macdonald(t=0).P() sage: MacP.macdonald_family() diff --git a/src/sage/combinat/species/composition_species.py b/src/sage/combinat/species/composition_species.py index 5a3a2cebb8f..efc77ce508b 100644 --- a/src/sage/combinat/species/composition_species.py +++ b/src/sage/combinat/species/composition_species.py @@ -62,7 +62,7 @@ def transport(self, perm): f, gs = self._list pi = self._partition.transport(perm) f = f.change_labels(pi._list) - g = [g.change_labels(part) for g,part in zip(gs, pi)] + g = [g.change_labels(part) for g, part in zip(gs, pi)] # BUG HERE ? return self.__class__(self, self._labels, pi, f, gs) def change_labels(self, labels): @@ -80,7 +80,6 @@ def change_labels(self, labels): EXAMPLES:: - sage: p = PermutationGroupElement((2,3)) sage: E = species.SetSpecies(); C = species.CycleSpecies() sage: L = E(C) sage: S = L.structures(['a','b','c']).list() @@ -92,7 +91,7 @@ def change_labels(self, labels): f, gs = self._list pi = self._partition.change_labels(labels) f = f.change_labels(list(pi)) - g = [g.change_labels(part) for g,part in zip(gs, pi)] + g = [g.change_labels(part) for g, part in zip(gs, pi)] return self.__class__(self, labels, pi, f, g) diff --git a/src/sage/combinat/subset.py b/src/sage/combinat/subset.py index 057e5a36a56..924cf7c8432 100644 --- a/src/sage/combinat/subset.py +++ b/src/sage/combinat/subset.py @@ -135,13 +135,7 @@ def Subsets(s, k=None, submultiset=False): sage: S3 = Subsets(S2) sage: S3.cardinality() 115792089237316195423570985008687907853269984665640564039457584007913129639936 - sage: S3.unrank(14123091480) # py2 - {{{1, 3}, {1, 2, 3}, {2}, {1}}, - {{2}, {1, 2, 3}, {}, {1, 2}}, - {}, - {{2}, {1, 2, 3}, {}, {3}, {1, 2}}, - {{1, 2, 3}, {}, {1}}, {{2}, {2, 3}, {}, {1, 2}}} - sage: S3.unrank(14123091480) # py3 #random + sage: S3.unrank(14123091480) # random {{{2}, {1, 2, 3}, {1, 2}, {3}, {}}, {{1, 2, 3}, {2}, {1}, {1, 3}}, {{}, {2}, {2, 3}, {1, 2}}, @@ -152,9 +146,7 @@ def Subsets(s, k=None, submultiset=False): sage: T = Subsets(S2, 10) sage: T.cardinality() 278826214642518400 - sage: T.unrank(1441231049) # py2 - {{{3}, {1, 2}, {}, {2, 3}, {1}, {1, 3}, ..., {{2, 3}, {}}, {{}}} - sage: T.unrank(1441231049) # py3 # random + sage: T.unrank(1441231049) # random {{{1, 2, 3}, {2}, {2, 3}}, {{3}, {1, 3}, ..., {3}, {1}, {}, {1, 3}}} """ if k is not None: @@ -197,12 +189,7 @@ class Subsets_s(Parent): Subsets of Subsets of Subsets of Finite Field of size 3 sage: S.cardinality() 115792089237316195423570985008687907853269984665640564039457584007913129639936 - sage: S.unrank(3149254230) # py2 - {{{1, 2}, {0, 1, 2}, {0, 2}, {0, 1}}, - {{1, 2}, {}, {0, 2}, {1}, {0, 1, 2}, {2}}, - {{1, 2}, {0}}, {{1, 2}, {0, 1}, {0, 1, 2}, {1}}, - {{0, 2}, {1}}} - sage: S.unrank(3149254230) # py3 #random + sage: S.unrank(3149254230) # random {{{1}, {0, 2}}, {{0, 1, 2}, {0, 1}, {1}, {1, 2}}, {{2}, {1, 2}, {0, 1, 2}, {0, 2}, {1}, {}}, {{1, 2}, {0}}, diff --git a/src/sage/combinat/tamari_lattices.py b/src/sage/combinat/tamari_lattices.py index 7d37336578d..b0b8bd96253 100644 --- a/src/sage/combinat/tamari_lattices.py +++ b/src/sage/combinat/tamari_lattices.py @@ -46,11 +46,12 @@ # # https://www.gnu.org/licenses/ # **************************************************************************** +from __future__ import annotations from sage.combinat.posets.lattices import LatticePoset, MeetSemilattice from sage.arith.all import gcd -def paths_in_triangle(i, j, a, b): +def paths_in_triangle(i, j, a, b) -> list: r""" Return all Dyck paths from `(0,0)` to `(i,j)` in the `(a \times b)`-rectangle. @@ -100,7 +101,7 @@ def paths_in_triangle(i, j, a, b): return [u + tuple([0]) for u in paths_in_triangle(i - 1, j, a, b)] -def swap(p, i, m=1): +def swap(p, i, m=1) -> tuple: r""" Perform a covering move in the `(a,b)`-Tamari lattice of parameter `m`. @@ -159,7 +160,7 @@ def swap(p, i, m=1): return tuple(q) -def GeneralizedTamariLattice(a, b, m=1, check=True): +def GeneralizedTamariLattice(a, b, m=1, check=True) -> LatticePoset: r""" Return the `(a,b)`-Tamari lattice of parameter `m`. @@ -226,7 +227,7 @@ def covers(p): for p in paths_in_triangle(a, b, a, b)}, check=check) -def TamariLattice(n, m=1): +def TamariLattice(n, m=1) -> LatticePoset: r""" Return the `n`-th Tamari lattice. @@ -268,7 +269,7 @@ def TamariLattice(n, m=1): # a variation : the Dexter meet-semilattices -def swap_dexter(p, i): +def swap_dexter(p, i) -> list: r""" Perform covering moves in the `(a,b)`-Dexter posets. @@ -339,7 +340,7 @@ def swap_dexter(p, i): return resu -def DexterSemilattice(n): +def DexterSemilattice(n) -> MeetSemilattice: r""" Return the `n`-th Dexter meet-semilattice. diff --git a/src/sage/combinat/tutorial.py b/src/sage/combinat/tutorial.py index a5bb1f31de2..41f199664ad 100644 --- a/src/sage/combinat/tutorial.py +++ b/src/sage/combinat/tutorial.py @@ -673,12 +673,7 @@ overflows, and would not permit the return of {Infinity} for infinite sets:: - sage: len(S) #py2 - Traceback (most recent call last): - ... - OverflowError: Python int too large to convert to C long - - sage: len(S) #py3 + sage: len(S) Traceback (most recent call last): ... OverflowError: cannot fit 'int' into an index-sized integer diff --git a/src/sage/combinat/words/suffix_trees.py b/src/sage/combinat/words/suffix_trees.py index 351bfa7e04e..3eebe3efe08 100644 --- a/src/sage/combinat/words/suffix_trees.py +++ b/src/sage/combinat/words/suffix_trees.py @@ -1759,7 +1759,7 @@ def walk_chain(u, v, l, start): if final_state[0] == 'explicit': parent = final_state[1] transition = self._find_transition(parent,self._letters[start]) - if transition != None: + if transition is not None: child = transition[1] successful = True depth = 1 diff --git a/src/sage/crypto/mq/rijndael_gf.py b/src/sage/crypto/mq/rijndael_gf.py index ad908a0ffcd..eb6119bafe0 100644 --- a/src/sage/crypto/mq/rijndael_gf.py +++ b/src/sage/crypto/mq/rijndael_gf.py @@ -46,7 +46,7 @@ - Thomas Gagne (2015-06): initial version -EXAMPLES +EXAMPLES: We build Rijndael-GF with a block length of 4 and a key length of 6:: @@ -1020,7 +1020,7 @@ def decrypt(self, ciphertext, key, format='hex'): - A string in the format ``format`` of ``ciphertext`` decrypted with key ``key``. - EXAMPLES :: + EXAMPLES:: sage: from sage.crypto.mq.rijndael_gf import RijndaelGF sage: rgf = RijndaelGF(4, 4) @@ -1473,7 +1473,7 @@ def compose(self, f, g, algorithm='encrypt', f_attr=None, g_attr=None): then ``compose`` returns `g(f(A))_{i,j}`, where `A` is an arbitrary input state matrix. - EXAMPLES + EXAMPLES: This function allows us to determine the polynomial representations of entries across multiple round functions. For example, if we diff --git a/src/sage/databases/oeis.py b/src/sage/databases/oeis.py index 4ad63ab25d9..082d84a432e 100644 --- a/src/sage/databases/oeis.py +++ b/src/sage/databases/oeis.py @@ -170,7 +170,6 @@ from sage.misc.flatten import flatten from sage.misc.temporary_file import tmp_filename from sage.misc.unknown import Unknown -from sage.misc.misc import embedded from sage.misc.html import HtmlFragment from sage.repl.preparse import preparse @@ -1575,10 +1574,7 @@ def url_absolute(s): return re.sub(r'\"\/', '\"' + oeis_url, s) if browse is None: if format == 'guess': - if embedded(): - return self.links(format='html') - else: - return self.links(format='url') + return self.links(format='url') elif format == 'raw': return FancyTuple(self._field('H')) elif format == 'html': @@ -1840,15 +1836,10 @@ def show(self): 'links', 'formulas', 'examples', 'cross_references', 'programs', 'keywords', 'offsets', 'url', 'old_IDs', 'author', 'extensions_or_errors']: - if embedded() and s == 'links': + result = getattr(self, s)() + if result != '' and result != ('',) and result != (): print(re.sub('_', ' ', s).upper()) - getattr(self, s)() - print('\n') - else: - result = getattr(self, s)() - if result != '' and result != ('',) and result != (): - print(re.sub('_', ' ', s).upper()) - print(str(result) + '\n') + print(str(result) + '\n') def programs(self, language='all', preparsing=True, keep_comments=False): r""" diff --git a/src/sage/databases/sql_db.py b/src/sage/databases/sql_db.py index 6189e4d3d91..5f07f2fed38 100644 --- a/src/sage/databases/sql_db.py +++ b/src/sage/databases/sql_db.py @@ -363,8 +363,8 @@ def row_str(row, html): + '\n' p += 1 else: - raise NotImplementedError('Cannot display plot on ' \ - + 'command line.') + raise NotImplementedError('Cannot display plot on ' + 'command line.') else: if index in fcol_index: if id_col_index is None: @@ -382,8 +382,7 @@ def row_str(row, html): cur_str.append(field_val) return ' '.join(cur_str) - from sage.server.support import EMBEDDED_MODE - if EMBEDDED_MODE or ('html_table' in kwds and kwds['html_table']): + if 'html_table' in kwds and kwds['html_table']: # Notebook Version ret = '\n' ret += ' \n' diff --git a/src/sage/docs/conf.py b/src/sage/docs/conf.py index 76cfb48187e..972d3bf137a 100644 --- a/src/sage/docs/conf.py +++ b/src/sage/docs/conf.py @@ -392,40 +392,40 @@ def set_intersphinx_mappings(app, config): \DeclareUnicodeCharacter{03A5}{\ensuremath{\Upsilon}} \DeclareUnicodeCharacter{2113}{\ell} - \DeclareUnicodeCharacter{2148}{\id} - \DeclareUnicodeCharacter{2202}{\partial} + \DeclareUnicodeCharacter{2148}{\ensuremath{\id}} + \DeclareUnicodeCharacter{2202}{\ensuremath{\partial}} \DeclareUnicodeCharacter{2205}{\ensuremath{\emptyset}} - \DeclareUnicodeCharacter{2208}{\in} - \DeclareUnicodeCharacter{2209}{\notin} - \DeclareUnicodeCharacter{2211}{\sum} + \DeclareUnicodeCharacter{2208}{\ensuremath{\in}} + \DeclareUnicodeCharacter{2209}{\ensuremath{\notin}} + \DeclareUnicodeCharacter{2211}{\ensuremath{\sum}} \DeclareUnicodeCharacter{221A}{\ensuremath{\sqrt{}}} - \DeclareUnicodeCharacter{221E}{\infty} + \DeclareUnicodeCharacter{221E}{\ensuremath{\infty}} \DeclareUnicodeCharacter{2227}{\ensuremath{\wedge}} \DeclareUnicodeCharacter{2228}{\ensuremath{\vee}} \DeclareUnicodeCharacter{2229}{\ensuremath{\cap}} \DeclareUnicodeCharacter{222A}{\ensuremath{\cup}} \DeclareUnicodeCharacter{222B}{\ensuremath{\int}} - \DeclareUnicodeCharacter{2248}{\approx} - \DeclareUnicodeCharacter{2260}{\neq} - \DeclareUnicodeCharacter{2264}{\leq} - \DeclareUnicodeCharacter{2265}{\geq} + \DeclareUnicodeCharacter{2248}{\ensuremath{\approx}} + \DeclareUnicodeCharacter{2260}{\ensuremath{\neq}} + \DeclareUnicodeCharacter{2264}{\ensuremath{\leq}} + \DeclareUnicodeCharacter{2265}{\ensuremath{\geq}} \DeclareUnicodeCharacter{2293}{\ensuremath{\sqcap}} \DeclareUnicodeCharacter{2294}{\ensuremath{\sqcup}} \DeclareUnicodeCharacter{22C0}{\ensuremath{\bigwedge}} \DeclareUnicodeCharacter{22C1}{\ensuremath{\bigvee}} \DeclareUnicodeCharacter{22C2}{\ensuremath{\bigcap}} \DeclareUnicodeCharacter{22C3}{\ensuremath{\bigcup}} - \DeclareUnicodeCharacter{00B1}{\pm} + \DeclareUnicodeCharacter{00B1}{\ensuremath{\pm}} \DeclareUnicodeCharacter{2A02}{\ensuremath{\bigotimes}} \DeclareUnicodeCharacter{2297}{\ensuremath{\otimes}} - \DeclareUnicodeCharacter{2A01}{\oplus} - \DeclareUnicodeCharacter{00BD}{\nicefrac{1}{2}} - \DeclareUnicodeCharacter{00D7}{\times} - \DeclareUnicodeCharacter{00B7}{\cdot} - \DeclareUnicodeCharacter{230A}{\lfloor} - \DeclareUnicodeCharacter{230B}{\rfloor} - \DeclareUnicodeCharacter{2308}{\lceil} - \DeclareUnicodeCharacter{2309}{\rceil} + \DeclareUnicodeCharacter{2A01}{\ensuremath{\oplus}} + \DeclareUnicodeCharacter{00BD}{\ensuremath{\nicefrac{1}{2}}} + \DeclareUnicodeCharacter{00D7}{\ensuremath{\times}} + \DeclareUnicodeCharacter{00B7}{\ensuremath{\cdot}} + \DeclareUnicodeCharacter{230A}{\ensuremath{\lfloor}} + \DeclareUnicodeCharacter{230B}{\ensuremath{\rfloor}} + \DeclareUnicodeCharacter{2308}{\ensuremath{\lceil}} + \DeclareUnicodeCharacter{2309}{\ensuremath{\rceil}} \DeclareUnicodeCharacter{22C5}{\ensuremath{\cdot}} \DeclareUnicodeCharacter{2227}{\ensuremath{\wedge}} \DeclareUnicodeCharacter{22C0}{\ensuremath{\bigwedge}} diff --git a/src/sage/features/lrs.py b/src/sage/features/lrs.py index be62d14f9f0..ab1871a6314 100644 --- a/src/sage/features/lrs.py +++ b/src/sage/features/lrs.py @@ -54,9 +54,12 @@ def is_functional(self): return FeatureTestResult(self, False, reason="Call to `{command}` failed with exit code {e.returncode}.".format(command=" ".join(command), e=e)) - expected = "Volume= 1" - if lines.find(expected) == -1: + expected_list = ["Volume= 1", "Volume=1"] + if all(lines.find(expected) == -1 for expected in expected_list): + print(lines) return FeatureTestResult(self, False, - reason="Output of `{command}` did not contain the expected result `{expected}`.".format(command=" ".join(command), expected=expected)) + reason="Output of `{command}` did not contain the expected result {expected}.".format( + command=" ".join(command), + expected=" or ".join(expected_list))) return FeatureTestResult(self, True) diff --git a/src/sage/game_theory/normal_form_game.py b/src/sage/game_theory/normal_form_game.py index 91bff5ee115..0ca60758b69 100644 --- a/src/sage/game_theory/normal_form_game.py +++ b/src/sage/game_theory/normal_form_game.py @@ -564,7 +564,7 @@ sage: A = matrix([[3,3],[2,5],[0,6]]) sage: B = matrix([[3,3],[2,6],[3,1]]) sage: degenerate_game = NormalFormGame([A,B]) - sage: degenerate_game.obtain_nash(algorithm='lrs') # optional - lrslib + sage: degenerate_game.obtain_nash(algorithm='lrs') # random, optional - lrslib [[(0, 1/3, 2/3), (1/3, 2/3)], [(1, 0, 0), (1/2, 3)], [(1, 0, 0), (1, 3)]] sage: degenerate_game.obtain_nash(algorithm='LCP') # optional - gambit [[(0.0, 0.3333333333, 0.6666666667), (0.3333333333, 0.6666666667)], @@ -1748,17 +1748,14 @@ def _solve_lrs(self, maximization=True): if maximization is False: m1 = - m1 m2 = - m2 - game1_str, game2_str = self._Hrepresentation(m1, m2) - g1_name = tmp_filename() - with open(g1_name, 'w') as g1_file: - g1_file.write(game1_str) - g2_name = tmp_filename() - with open(g2_name, 'w') as g2_file: - g2_file.write(game2_str) + game_str = self._lrs_nash_format(m1, m2) + game_name = tmp_filename() + with open(game_name, 'w') as game_file: + game_file.write(game_str) try: - process = Popen(['lrsnash', g1_name, g2_name], + process = Popen(['lrsnash', game_name], stdout=PIPE, stderr=PIPE) except OSError as e: @@ -2239,7 +2236,11 @@ def _is_NE(self, a, b, p1_support, p2_support, M1, M2): def _Hrepresentation(self, m1, m2): r""" - Create the H-representation strings required to use lrs nash. + Create the H-representation strings required to use ``lrsnash``. + + Since lrslib 6.1, this format is referred to as "legacy format". + + This method is deprecated. EXAMPLES:: @@ -2247,6 +2248,10 @@ def _Hrepresentation(self, m1, m2): sage: B = matrix([[3, 3], [1, 4]]) sage: C = NormalFormGame([A, B]) sage: print(C._Hrepresentation(A, B)[0]) + doctest:warning... + DeprecationWarning: NormalFormGame._Hrepresentation is deprecated as it + creates the legacy input format. Use NormalFormGame._lrs_nash_format instead + See https://trac.sagemath.org/27745 for details. H-representation linearity 1 5 begin @@ -2272,6 +2277,12 @@ def _Hrepresentation(self, m1, m2): """ + from sage.misc.superseded import deprecation + deprecation(27745, + "NormalFormGame._Hrepresentation is deprecated as it " + "creates the legacy input format. " + "Use NormalFormGame._lrs_nash_format instead") + from sage.geometry.polyhedron.misc import _to_space_separated_string m = self.players[0].num_strategies n = self.players[1].num_strategies @@ -2307,6 +2318,73 @@ def _Hrepresentation(self, m1, m2): t += 'end\n' return s, t + def _lrs_nash_format(self, m1, m2): + """ + Create the input format for ``lrsnash``, version 6.1 or newer. + + EXAMPLES: + + An example from the ``lrsnash`` manual in the old and the format:: + + sage: A = matrix([[0, 6], [2, 5], [3, 3]]) + sage: B = matrix([[1, 0], [0, 2], [4, 3]]) + sage: C = NormalFormGame([A, B]) + sage: print(C._lrs_nash_format(A, B)) + 3 2 + + 0 6 + 2 5 + 3 3 + + 1 0 + 0 2 + 4 3 + + + sage: legacy_format = C._Hrepresentation(A, B) + doctest:warning... + DeprecationWarning: NormalFormGame._Hrepresentation is deprecated as it + creates the legacy input format. Use NormalFormGame._lrs_nash_format instead + See https://trac.sagemath.org/27745 for details. + sage: print('*game: player 1\n', legacy_format[0]) + *game: player 1 + H-representation + linearity 1 6 + begin + 6 5 rational + 0 1 0 0 0 + 0 0 1 0 0 + 0 0 0 1 0 + 0 -1 0 -4 1 + 0 0 -2 -3 1 + -1 1 1 1 0 + end + + sage: print('*game: player 2\n', legacy_format[1]) + *game: player 2 + H-representation + linearity 1 6 + begin + 6 4 rational + 0 0 -6 1 + 0 -2 -5 1 + 0 -3 -3 1 + 0 1 0 0 + 0 0 1 0 + -1 1 1 0 + end + """ + from sage.geometry.polyhedron.misc import _to_space_separated_string + m = self.players[0].num_strategies + n = self.players[1].num_strategies + s = f'{m} {n}\n\n' + for r in m1.rows(): + s += _to_space_separated_string(r) + '\n' + s += '\n' + for r in m2.rows(): + s += _to_space_separated_string(r) + '\n' + return s + def is_degenerate(self, certificate=False): """ A function to check whether the game is degenerate or not. diff --git a/src/sage/game_theory/parser.py b/src/sage/game_theory/parser.py index 09596a90e90..95925e85ea0 100644 --- a/src/sage/game_theory/parser.py +++ b/src/sage/game_theory/parser.py @@ -1,4 +1,17 @@ - +""" +Parser For gambit And lrs Nash Equilibria +""" + +# **************************************************************************** +# Copyright (C) 2014 James Campbell james.campbell@tanti.org.uk +# 2015 Vincent Knight +# +# 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/ +# **************************************************************************** class Parser(): r""" @@ -11,124 +24,33 @@ class Parser(): def __init__(self, raw_string): """ - Initialise a Parser instance by storing a raw_string + Initialise a Parser instance by storing a ``raw_string`` (currently only used with H representation of a game). - TESTS: - - Simply checking that we have the correct string output - for the H representation (which is the format required - for the ``'lrs'`` algorithm):: + EXAMPLES:: + sage: from sage.cpython.string import bytes_to_str sage: from sage.game_theory.parser import Parser - sage: A = matrix([[1, 2], [3, 2]]) - sage: g = NormalFormGame([A]) - sage: raw_string = g._Hrepresentation(A, -A) - sage: P = Parser(raw_string) - sage: print(P.raw_string[0]) - H-representation - linearity 1 5 - begin - 5 4 rational - 0 1 0 0 - 0 0 1 0 - 0 1 3 1 - 0 2 2 1 - -1 1 1 0 - end - - - sage: print(P.raw_string[1]) - H-representation - linearity 1 5 - begin - 5 4 rational - 0 -1 -2 1 - 0 -3 -2 1 - 0 1 0 0 - 0 0 1 0 - -1 1 1 0 - end - - - The specific case of a game with 1 strategy for each player:: - + sage: from subprocess import Popen, PIPE sage: A = matrix([[1]]) sage: B = matrix([[5]]) sage: g = NormalFormGame([A,B]) - sage: raw_string = g._Hrepresentation(A, B) - sage: P = Parser(raw_string) - sage: print(P.raw_string[0]) - H-representation - linearity 1 3 - begin - 3 3 rational - 0 1 0 - 0 -5 1 - -1 1 0 - end - - - sage: print(P.raw_string[1]) - H-representation - linearity 1 3 - begin - 3 3 rational - 0 -1 1 - 0 1 0 - -1 1 0 - end - - - Another test:: - - sage: from sage.game_theory.parser import Parser - sage: A = matrix([[-7, -5, 5], - ....: [5, 5, 3], - ....: [1, -6, 1]]) - sage: B = matrix([[-9, 7, 9], - ....: [6, -2, -3], - ....: [-4, 6, -10]]) - sage: g = NormalFormGame([A, B]) - sage: raw_string = g._Hrepresentation(A, B) - sage: P = Parser(raw_string) - sage: print(P.raw_string[0]) - H-representation - linearity 1 7 - begin - 7 5 rational - 0 1 0 0 0 - 0 0 1 0 0 - 0 0 0 1 0 - 0 9 -6 4 1 - 0 -7 2 -6 1 - 0 -9 3 10 1 - -1 1 1 1 0 - end - - - sage: print(P.raw_string[1]) - H-representation - linearity 1 7 - begin - 7 5 rational - 0 7 5 -5 1 - 0 -5 -5 -3 1 - 0 -1 6 -1 1 - 0 1 0 0 0 - 0 0 1 0 0 - 0 0 0 1 0 - -1 1 1 1 0 - end - - - This class is also used to parse the output of algorithms from the gambit - python interface using the `format_gambit` function. + sage: game_str = g._lrs_nash_format(A, B) + sage: game_name = tmp_filename() + sage: with open(game_name, 'w') as game_file: + ....: _ = game_file.write(game_str) + sage: process = Popen(['lrsnash', game_name], stdout=PIPE, stderr=PIPE) # optional - lrslib + sage: lrs_output = [bytes_to_str(row) for row in process.stdout] # optional - lrslib + sage: Parser(lrs_output).format_lrs() # optional - lrslib + [[(1,), (1,)]] + + This class is also used to parse the output of algorithms from + the gambit python interface using :meth:`format_gambit()`. """ self.raw_string = raw_string - def format_lrs(self): - """ + def format_lrs(self, legacy_format=False): + r""" Parses the output of lrs so as to return vectors corresponding to equilibria. @@ -139,40 +61,35 @@ def format_lrs(self): sage: from subprocess import Popen, PIPE sage: A = matrix([[1, 2], [3, 2]]) sage: g = NormalFormGame([A]) - sage: game1_str, game2_str = g._Hrepresentation(A, -A) - sage: g1_name = tmp_filename() - sage: g2_name = tmp_filename() - sage: g1_file = open(g1_name, 'w') - sage: g2_file = open(g2_name, 'w') - sage: _ = g1_file.write(game1_str) - sage: g1_file.close() - sage: _ = g2_file.write(game2_str) - sage: g2_file.close() - sage: process = Popen(['lrsnash', g1_name, g2_name], stdout=PIPE, stderr=PIPE) # optional - lrslib - sage: lrs_output = [bytes_to_str(row) for row in process.stdout] # optional - lrslib - - The above creates a game, writes the H representation to - temporary files, calls lrs and stores the output in `lrs_output` + sage: game_str = g._lrs_nash_format(A, -A) + sage: game_name = tmp_filename() + sage: with open(game_name, 'w') as game_file: + ....: _ = game_file.write(game_str) + sage: process = Popen(['lrsnash', game_name], stdout=PIPE, stderr=PIPE) # optional - lrslib + sage: lrs_output = [bytes_to_str(row) for row in process.stdout] # optional - lrslib + + The above creates a game, writes the H representations to + temporary files, calls lrs and stores the output in ``lrs_output`` (here slicing to get rid of some system parameters that get returned):: - sage: lrs_output[5:16] # optional - lrslib - ['\n', - '***** 4 4 rational\n', + sage: lrs_output[:-2] # optional - lrslib + [..., '2 0 1 2 \n', '1 1/2 1/2 -2 \n', '\n', '2 0 1 2 \n', '1 0 1 -2 \n', '\n', - '\n', '*Number of equilibria found: 2\n', - '*Player 1: vertices=3 bases=3 pivots=5\n'] + '*Player 1: vertices=3 bases=3 pivots=5\n', + '*Player 2: vertices=2 bases=1 pivots=6\n', + '\n'] The above is pretty messy, here is the output when we put it through the parser:: - sage: nasheq = Parser(lrs_output).format_lrs() # optional - lrslib - sage: nasheq # optional - lrslib + sage: nasheq = Parser(lrs_output).format_lrs() # optional - lrslib + sage: nasheq # optional - lrslib [[(1/2, 1/2), (0, 1)], [(0, 1), (0, 1)]] Another game:: @@ -184,22 +101,16 @@ def format_lrs(self): ....: [6, -2, -3], ....: [-4, 6, -10]]) sage: g = NormalFormGame([A, B]) - sage: game1_str, game2_str = g._Hrepresentation(A, B) - sage: g1_name = tmp_filename() - sage: g2_name = tmp_filename() - sage: g1_file = open(g1_name, 'w') - sage: g2_file = open(g2_name, 'w') - sage: _ = g1_file.write(game1_str) - sage: g1_file.close() - sage: _ = g2_file.write(game2_str) - sage: g2_file.close() - sage: process = Popen(['lrsnash', g1_name, g2_name], stdout=PIPE, stderr=PIPE) # optional - lrslib - sage: lrs_output = [bytes_to_str(row) for row in process.stdout] # optional - lrslib - sage: print(lrs_output[5:20]) # optional - lrslib - ['\n', - '***** 5 5 rational\n', - '2 1/7 0 6/7 23/7 \n', + sage: game_str = g._lrs_nash_format(A, B) + sage: game_name = tmp_filename() + sage: with open(game_name, 'w') as game_file: + ....: _ = game_file.write(game_str) + sage: process = Popen(['lrsnash', game_name], stdout=PIPE, stderr=PIPE) # optional - lrslib + sage: lrs_output = [bytes_to_str(row) for row in process.stdout] # optional - lrslib + sage: print(lrs_output[:-2]) # optional - lrslib + [..., '2 0 1/6 5/6 10/3 \n', + '2 1/7 0 6/7 23/7 \n', '1 1/3 2/3 0 1 \n', '\n', '2 0 0 1 5 \n', @@ -208,21 +119,56 @@ def format_lrs(self): '2 1 0 0 5 \n', '1 0 1 0 6 \n', '\n', - '\n', '*Number of equilibria found: 4\n', - '*Player 1: vertices=6 bases=7 pivots=10\n'] + '*Player 1: vertices=6 bases=7 pivots=10\n', + '*Player 2: vertices=4 bases=2 pivots=14\n', + '\n'] - sage: nasheq = Parser(lrs_output).format_lrs() # optional - lrslib - sage: sorted(nasheq) # optional - lrslib + sage: nasheq = Parser(lrs_output).format_lrs() # optional - lrslib + sage: sorted(nasheq) # optional - lrslib [[(0, 1, 0), (1, 0, 0)], [(1/3, 2/3, 0), (0, 1/6, 5/6)], [(1/3, 2/3, 0), (1/7, 0, 6/7)], [(1, 0, 0), (0, 0, 1)]] + + TESTS: + + An example with the legacy format:: + + sage: from sage.cpython.string import bytes_to_str + sage: from sage.game_theory.parser import Parser + sage: from subprocess import Popen, PIPE + sage: A = matrix([[1, 2], [3, 2]]) + sage: g = NormalFormGame([A]) + sage: game1_str, game2_str = g._Hrepresentation(A, -A) + doctest:warning... + DeprecationWarning: NormalFormGame._Hrepresentation is deprecated as it + creates the legacy input format. Use NormalFormGame._lrs_nash_format instead + See https://trac.sagemath.org/27745 for details. + sage: g1_name, g2_name = tmp_filename(), tmp_filename() + sage: g1_file, g2_file = open(g1_name, 'w'), open(g2_name, 'w') + sage: _ = g1_file.write(game1_str) + sage: g1_file.close() + sage: _ = g2_file.write(game2_str) + sage: g2_file.close() + sage: process = Popen(['lrsnash', g1_name, g2_name], stdout=PIPE, stderr=PIPE) # not tested, optional - lrslib + sage: lrs_output = [bytes_to_str(row) for row in process.stdout] # not tested, optional - lrslib + sage: nasheq = Parser(lrs_output).format_lrs(legacy_format=True) # not tested, optional - lrslib + sage: nasheq # not tested, optional - lrslib + [[(1/2, 1/2), (0, 1)], [(0, 1), (0, 1)]] """ equilibria = [] from sage.misc.sage_eval import sage_eval - from itertools import groupby - for collection in [list(x[1]) for x in groupby(self.raw_string[7:], lambda x: x == '\n')]: + from itertools import groupby, dropwhile + lines = iter(self.raw_string) + if legacy_format: + # Skip until the magic stars announce the beginning of the real output + while not next(lines).startswith("*****"): + pass + else: + # Skip comment lines starting with a single star + lines = dropwhile(lambda line: line.startswith('*'), lines) + for collection in [list(x[1]) for x in groupby(lines, lambda x: x == '\n')]: if collection[0].startswith('2'): s1 = tuple([sage_eval(k) for k in collection[-1].split()][1:-1]) for s2 in collection[:-1]: @@ -232,7 +178,7 @@ def format_lrs(self): return equilibria def format_gambit(self, gambit_game): - """ + r""" Parses the output of gambit so as to return vectors corresponding to equilibria obtained using the LCP algorithm. @@ -344,3 +290,4 @@ def format_gambit(self, gambit_game): nice_stuff.append(profile) return nice_stuff + diff --git a/src/sage/geometry/all.py b/src/sage/geometry/all.py index f143f171600..3b7dcf756d8 100644 --- a/src/sage/geometry/all.py +++ b/src/sage/geometry/all.py @@ -1,5 +1,6 @@ from .polyhedron.all import * from .hyperbolic_space.all import * +from .polyhedral_complex import PolyhedralComplex from sage.misc.lazy_import import lazy_import lazy_import('sage.geometry.cone', ['Cone', 'random_cone']) diff --git a/src/sage/geometry/cone.py b/src/sage/geometry/cone.py index da74c16077e..108a61d94b0 100644 --- a/src/sage/geometry/cone.py +++ b/src/sage/geometry/cone.py @@ -2006,6 +2006,27 @@ def _repr_(self): result += " face of %s" % self.ambient() return result + def _some_elements_(self): + r""" + Generate some points of ``self``. + + EXAMPLE:: + + sage: K = cones.nonnegative_orthant(3) + sage: K.some_elements() # indirect doctest + [(0, 0, 0), (1/2, 0, 0), (1/4, 1/2, 0), (1/8, 1/4, 1/2)] + """ + V = self.ambient_vector_space() + r_iter = iter(self._rays) + p = V(0) + yield p + for i in range(5): + try: + p = (p + next(r_iter)) / 2 + except StopIteration: + return + yield p + def _sort_faces(self, faces): r""" Return sorted (if necessary) ``faces`` as a tuple. diff --git a/src/sage/geometry/convex_set.py b/src/sage/geometry/convex_set.py index 74d5a7fe804..b4e84ac1e4f 100644 --- a/src/sage/geometry/convex_set.py +++ b/src/sage/geometry/convex_set.py @@ -13,10 +13,13 @@ # **************************************************************************** from sage.structure.sage_object import SageObject +from sage.sets.set import Set_base from sage.categories.sets_cat import EmptySetError from sage.misc.abstract_method import abstract_method +from sage.rings.infinity import infinity +from sage.rings.integer_ring import ZZ -class ConvexSet_base(SageObject): +class ConvexSet_base(SageObject, Set_base): """ Abstract base class for convex sets. """ @@ -38,6 +41,60 @@ def is_empty(self): """ return self.dim() < 0 + def is_finite(self): + r""" + Test whether ``self`` is a finite set. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: p = LatticePolytope([], lattice=ToricLattice(3).dual()); p + -1-d lattice polytope in 3-d lattice M + sage: p.is_finite() + True + sage: q = Polyhedron(ambient_dim=2); q + The empty polyhedron in ZZ^2 + sage: q.is_finite() + True + sage: r = Polyhedron(rays=[(1, 0)]); r + A 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 1 vertex and 1 ray + sage: r.is_finite() + False + """ + return self.dim() < 1 + + def cardinality(self): + """ + Return the cardinality of this set. + + OUTPUT: + + Either an integer or ``Infinity``. + + EXAMPLES:: + + sage: p = LatticePolytope([], lattice=ToricLattice(3).dual()); p + -1-d lattice polytope in 3-d lattice M + sage: p.cardinality() + 0 + sage: q = Polyhedron(ambient_dim=2); q + The empty polyhedron in ZZ^2 + sage: q.cardinality() + 0 + sage: r = Polyhedron(rays=[(1, 0)]); r + A 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 1 vertex and 1 ray + sage: r.cardinality() + +Infinity + """ + if self.dim() < 0: + return ZZ(0) + if self.dim() == 0: + return ZZ(1) + return infinity + def is_universe(self): r""" Test whether ``self`` is the whole ambient space. @@ -370,7 +427,7 @@ def _test_convex_set(self, tester=None, **options): ....: return 42 ....: def ambient_dim(self): ....: return 91 - sage: TestSuite(FaultyConvexSet()).run(skip=('_test_pickling', '_test_contains')) + sage: TestSuite(FaultyConvexSet()).run(skip=('_test_pickling', '_test_contains', '_test_as_set_object')) Failure in _test_convex_set: ... The following tests failed: _test_convex_set @@ -384,7 +441,7 @@ def _test_convex_set(self, tester=None, **options): ....: return QQ^3 ....: def ambient_dim(self): ....: return 3 - sage: TestSuite(BiggerOnTheInside()).run(skip=('_test_pickling', '_test_contains')) + sage: TestSuite(BiggerOnTheInside()).run(skip=('_test_pickling', '_test_contains', '_test_as_set_object')) Failure in _test_convex_set: ... The following tests failed: _test_convex_set @@ -619,29 +676,6 @@ def _test_contains(self, tester=None, **options): tester.assertTrue(self.contains(point)) tester.assertTrue(point in self) - @abstract_method(optional=True) - def intersection(self, other): - r""" - Return the intersection of ``self`` and ``other``. - - INPUT: - - - ``other`` -- another convex set - - OUTPUT: - - The intersection. - - TESTS:: - - sage: from sage.geometry.convex_set import ConvexSet_base - sage: C = ConvexSet_base() - sage: C.intersection(C) - Traceback (most recent call last): - ... - TypeError: 'NotImplementedType' object is not callable - """ - class ConvexSet_closed(ConvexSet_base): r""" diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py index d8778307934..9ec022099d0 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py @@ -140,13 +140,11 @@ def __init__(self, model, start, end, **graphics_options): r""" See :class:`HyperbolicGeodesic` for full documentation. - EXAMPLES :: + EXAMPLES:: sage: HyperbolicPlane().UHP().get_geodesic(I, 2 + I) Geodesic in UHP from I to I + 2 - """ - self._model = model self._start = start self._end = end diff --git a/src/sage/geometry/lattice_polytope.py b/src/sage/geometry/lattice_polytope.py index 9d42d71f275..fa46804aacc 100644 --- a/src/sage/geometry/lattice_polytope.py +++ b/src/sage/geometry/lattice_polytope.py @@ -3794,6 +3794,36 @@ def points(self, *args, **kwds): else: return self._points + def _some_elements_(self): + r""" + Generate some points of ``self`` as a convex polytope. + + In contrast to :meth:`points`, these are not necessarily lattice points. + + EXAMPLE:: + + sage: o = lattice_polytope.cross_polytope(3) + sage: o.some_elements() # indirect doctest + [(1, 0, 0), + (1/2, 1/2, 0), + (1/4, 1/4, 1/2), + (-3/8, 1/8, 1/4), + (-3/16, -7/16, 1/8), + (-3/32, -7/32, -7/16)] + """ + if not self._vertices: + return + V = self.ambient_vector_space() + v_iter = iter(self._vertices) + p = V(next(v_iter)) + yield p + for i in range(5): + try: + p = (p + next(v_iter)) / 2 + except StopIteration: + return + yield p + def polar(self): r""" Return the polar polytope, if this polytope is reflexive. diff --git a/src/sage/geometry/newton_polygon.py b/src/sage/geometry/newton_polygon.py index 4a2a3c0df13..54d8a3c2f4d 100644 --- a/src/sage/geometry/newton_polygon.py +++ b/src/sage/geometry/newton_polygon.py @@ -53,6 +53,8 @@ def __init__(self, polyhedron, parent): Element.__init__(self, parent) self._polyhedron = polyhedron self._vertices = None + if polyhedron.is_mutable(): + polyhedron._add_dependent_object(self) def _repr_(self): """ diff --git a/src/sage/geometry/polyhedral_complex.py b/src/sage/geometry/polyhedral_complex.py new file mode 100644 index 00000000000..10e8c34f79d --- /dev/null +++ b/src/sage/geometry/polyhedral_complex.py @@ -0,0 +1,2436 @@ +# -*- coding: utf-8 -*- +r""" +Finite polyhedral complexes + +This module implements the basic structure of finite polyhedral complexes. +For more information, see :class:`PolyhedralComplex`. + +AUTHORS: + +- Yuan Zhou (2021-05): initial implementation + +List of PolyhedralComplex methods +--------------------------------- + +**Maximal cells and cells** + +.. csv-table:: + :class: contentstable + :widths: 30, 70 + :delim: | + + :meth:`~PolyhedralComplex.maximal_cells` | Return the dictionary of the maximal cells in this polyhedral complex. + :meth:`~PolyhedralComplex.maximal_cell_iterator` | Return an iterator over maximal cells in this polyhedral complex. + :meth:`~PolyhedralComplex.maximal_cells_sorted` | Return the sorted list of all maximal cells in this polyhedral complex. + :meth:`~PolyhedralComplex.n_maximal_cells` | List the maximal cells of dimension `n` in this polyhedral complex. + :meth:`~PolyhedralComplex._n_maximal_cells_sorted` | Return the sorted list of maximal cells of dim `n` in this complex. + :meth:`~PolyhedralComplex.is_maximal_cell` | Return ``True`` if the given cell is a maximal cell in this complex. + :meth:`~PolyhedralComplex.cells` | Return the dictionary of the cells in this polyhedral complex. + :meth:`~PolyhedralComplex.cell_iterator` | Return an iterator over cells in this polyhedral complex. + :meth:`~PolyhedralComplex.cells_sorted` | Return the sorted list of all cells in this polyhedral complex. + :meth:`~sage.topology.cell_complex.GenericCellComplex.n_cells` | List the cells of dimension `n` in this polyhedral complex. + :meth:`~PolyhedralComplex._n_cells_sorted` | Return the sorted list of `n`-cells in this polyhedral complex. + :meth:`~PolyhedralComplex.is_cell` | Return ``True`` if the given cell is in this polyhedral complex. + :meth:`~PolyhedralComplex.face_poset` | Return the poset of nonempty cells in the polyhedral complex. + :meth:`~PolyhedralComplex.relative_boundary_cells` | List the maximal cells on the boundary of the polyhedral complex. + +**Properties of the polyhedral complex** + +.. csv-table:: + :class: contentstable + :widths: 30, 70 + :delim: | + + :meth:`~PolyhedralComplex.dimension` | Return the dimension of the polyhedral complex. + :meth:`~PolyhedralComplex.ambient_dimension` | Return the ambient dimension of the polyhedral complex. + :meth:`~PolyhedralComplex.is_pure` | Return ``True`` if the polyhedral complex is pure. + :meth:`~PolyhedralComplex.is_full_dimensional` | Return ``True`` if the polyhedral complex is full dimensional. + :meth:`~PolyhedralComplex.is_compact` | Return ``True`` if the polyhedral complex is bounded. + :meth:`~PolyhedralComplex.is_connected` | Return ``True`` if the polyhedral complex is connected. + :meth:`~PolyhedralComplex.is_subcomplex` | Return ``True`` if this complex is a subcomplex of the other. + :meth:`~PolyhedralComplex.is_convex` | Return ``True`` if the polyhedral complex is convex. + :meth:`~PolyhedralComplex.is_mutable` | Return ``True`` if the polyhedral complex is mutable. + :meth:`~PolyhedralComplex.is_immutable` | Return ``True`` if the polyhedral complex is not mutable. + :meth:`~PolyhedralComplex.is_simplicial_complex` | Return ``True`` if the polyhedral complex is a simplicial complex. + :meth:`~PolyhedralComplex.is_polyhedral_fan` | Return ``True`` if the polyhedral complex is a fan. + :meth:`~PolyhedralComplex.is_simplicial_fan` | Return ``True`` if the polyhedral complex is a simplicial fan. + +**New polyhedral complexes from old ones** + +.. csv-table:: + :class: contentstable + :widths: 30, 70 + :delim: | + + :meth:`~PolyhedralComplex.connected_component` | Return the connected component containing a cell as a subcomplex. + :meth:`~PolyhedralComplex.connected_components` | Return the connected components of this polyhedral complex. + :meth:`~PolyhedralComplex.n_skeleton` | Return the `n`-skeleton of this polyhedral complex. + :meth:`~PolyhedralComplex.stratify` | Return the (pure) subcomplex formed by the maximal cells of dim `n` in this complex. + :meth:`~PolyhedralComplex.boundary_subcomplex` | Return the boundary subcomplex of this polyhedral complex. + :meth:`~PolyhedralComplex.product` | Return the (Cartesian) product of this polyhedral complex with another one. + :meth:`~PolyhedralComplex.disjoint_union` | Return the disjoint union of this polyhedral complex with another one. + :meth:`~PolyhedralComplex.union` | Return the union of this polyhedral complex with another one. + :meth:`~PolyhedralComplex.join` | Return the join of this polyhedral complex with another one. + :meth:`~PolyhedralComplex.subdivide` | Return a new polyhedral complex (with option ``make_simplicial``) subdividing this one. + +**Update polyhedral complex** + +.. csv-table:: + :class: contentstable + :widths: 30, 70 + :delim: | + + :meth:`~PolyhedralComplex.set_immutable` | Make this polyhedral complex immutable. + :meth:`~PolyhedralComplex.add_cell` | Add a cell to this polyhedral complex. + :meth:`~PolyhedralComplex.remove_cell` | Remove a cell from this polyhedral complex. + +**Miscellaneous** + +.. csv-table:: + :class: contentstable + :widths: 30, 70 + :delim: | + + :meth:`~PolyhedralComplex.plot` | Return a Graphic object showing the plot of polyhedral complex. + :meth:`~PolyhedralComplex.graph` | Return a directed graph corresponding to the 1-skeleton of this polyhedral complex, given that it is bounded. + :meth:`~PolyhedralComplex.union_as_polyhedron` | Return a ``Polyhedron`` which is the union of cells in this polyhedral complex, given that it is convex. + +Classes and functions +--------------------- +""" + +# **************************************************************************** +# Copyright (C) 2021 Yuan Zhou +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from copy import copy +from sage.topology.cell_complex import GenericCellComplex +from sage.geometry.polyhedron.constructor import Polyhedron +from sage.geometry.polyhedron.base import is_Polyhedron +from sage.modules.free_module_element import vector +from sage.rings.integer_ring import ZZ +from sage.graphs.graph import Graph +from sage.combinat.posets.posets import Poset +from sage.misc.misc import powerset + + +class PolyhedralComplex(GenericCellComplex): + r""" + A polyhedral complex. + + A **polyhedral complex** `PC` is a collection of polyhedra in a certain + ambient space `\RR^n` such that the following hold. + + - If a polyhedron `P` is in `PC`, then all the faces of `P` are in `PC`. + + - If polyhedra `P` and `Q` are in `PC`, then `P \cap Q` is either empty + or a face of both `P` and `Q`. + + In this context, a "polyhedron" means the geometric realization + of a polyhedron. This is in contrast to :mod:`simplicial complex + `, whose cells are abstract simplices. + The concept of a polyhedral complex generalizes that of a **geometric** + simplicial complex. + + .. NOTE:: + + This class derives from + :class:`~sage.topology.cell_complex.GenericCellComplex`, and so + inherits its methods. Some of those methods are not listed here; + see the :mod:`Generic Cell Complex ` + page instead. + + INPUT: + + - ``maximal_cells`` -- a list, a tuple, or a dictionary (indexed by + dimension) of cells of the Complex. Each cell is of class + :class:`Polyhedron` of the same ambient dimension. To set up a + :class:PolyhedralComplex, it is sufficient to provide the maximal + faces. Use keyword argument ``partial=True`` to set up a partial + polyhedral complex, which is a subset of the faces (viewed as + relatively open) of a polyhedral complex that is not necessarily + closed under taking intersection. + + - ``maximality_check`` -- boolean (default: ``True``); + if ``True``, then the constructor checks that each given + maximal cell is indeed maximal, and ignores those that are not + + - ``face_to_face_check`` -- boolean (default: ``False``); + if ``True``, then the constructor checks whether the cells + are face-to-face, and it raises a ``ValueError`` if they are not + + - ``is_mutable`` and ``is_immutable`` -- boolean (default: ``True`` and + ``False`` respectively); set ``is_mutable=False`` or ``is_immutable=True`` + to make this polyhedral complex immutable + + - ``backend`` -- string (optional); the name of the backend used for + computations on Sage polyhedra; if it is not given, then each cell has + its own backend; otherwise it must be one of the following: + + * ``'ppl'`` - the Parma Polyhedra Library + + * ``'cdd'`` - CDD + + * ``'normaliz'`` - normaliz + + * ``'polymake'`` - polymake + + * ``'field'`` - a generic Sage implementation + + - ``ambient_dim`` -- integer (optional); used to set up an empty + complex in the intended ambient space + + EXAMPLES:: + + sage: pc = PolyhedralComplex([ + ....: Polyhedron(vertices=[(1/3, 1/3), (0, 0), (1/7, 2/7)]), + ....: Polyhedron(vertices=[(1/7, 2/7), (0, 0), (0, 1/4)])]) + sage: [p.Vrepresentation() for p in pc.cells_sorted()] + [(A vertex at (0, 0), A vertex at (0, 1/4), A vertex at (1/7, 2/7)), + (A vertex at (0, 0), A vertex at (1/3, 1/3), A vertex at (1/7, 2/7)), + (A vertex at (0, 0), A vertex at (0, 1/4)), + (A vertex at (0, 0), A vertex at (1/7, 2/7)), + (A vertex at (0, 0), A vertex at (1/3, 1/3)), + (A vertex at (0, 1/4), A vertex at (1/7, 2/7)), + (A vertex at (1/3, 1/3), A vertex at (1/7, 2/7)), + (A vertex at (0, 0),), + (A vertex at (0, 1/4),), + (A vertex at (1/7, 2/7),), + (A vertex at (1/3, 1/3),)] + sage: pc.plot() + Graphics object consisting of 10 graphics primitives + sage: pc.is_pure() + True + sage: pc.is_full_dimensional() + True + sage: pc.is_compact() + True + sage: pc.boundary_subcomplex() + Polyhedral complex with 4 maximal cells + sage: pc.is_convex() + True + sage: pc.union_as_polyhedron().Hrepresentation() + (An inequality (1, -4) x + 1 >= 0, + An inequality (-1, 1) x + 0 >= 0, + An inequality (1, 0) x + 0 >= 0) + sage: pc.face_poset() + Finite poset containing 11 elements + sage: pc.is_connected() + True + sage: pc.connected_component() == pc + True + + TESTS: + + Check that non-maximal cells are ignored if ``maximality_check=True``:: + + sage: pc = PolyhedralComplex([ + ....: Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]), + ....: Polyhedron(vertices=[(1, 2), (0, 0)]) ]) + sage: pc.maximal_cells() + {2: {A 2-dimensional polyhedron in ZZ^2 defined as the convex hull of 3 vertices}} + + Check that non face-to-face can be detected:: + + sage: PolyhedralComplex([ + ....: Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]), + ....: Polyhedron(vertices=[(2, 2), (0, 0)]) ], + ....: face_to_face_check=True) + Traceback (most recent call last): + ... + ValueError: the given cells are not face-to-face + + Check that all the cells must have the same ambient dimension:: + + sage: PolyhedralComplex([ + ....: Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]), + ....: Polyhedron(vertices=[[2], [0]]) ]) + Traceback (most recent call last): + ... + ValueError: the given cells are not polyhedra in the same ambient space + + Check that backend is passed to all the cells:: + + sage: P = Polyhedron(vertices=[(0, 0), (1, 1)]) + sage: P.backend() + 'ppl' + sage: pc = PolyhedralComplex([P], backend='cdd') + sage: Q = pc.maximal_cells_sorted()[0] + sage: Q.backend() + 'cdd' + """ + def __init__(self, maximal_cells=None, backend=None, maximality_check=True, + face_to_face_check=False, is_mutable=True, is_immutable=False, + ambient_dim=None): + r""" + Define a PolyhedralComplex. + + See ``PolyhedralComplex`` for more information. + + EXAMPLES:: + + sage: pc = PolyhedralComplex([Polyhedron(vertices=[(1, 1), (0, 0)])]) + sage: pc + Polyhedral complex with 1 maximal cell + sage: TestSuite(pc).run() + """ + self._backend = backend + if maximal_cells is None: + cells_dict = {} + elif isinstance(maximal_cells, (list, tuple)): + if backend: + maximal_cells = [p.base_extend(p.base_ring(), backend) + for p in maximal_cells] + cells_dict = cells_list_to_cells_dict(maximal_cells) + elif isinstance(maximal_cells, dict): + cells_dict = {} + for (k, l) in maximal_cells.items(): + if backend: + cells_dict[k] = set([p.base_extend(p.base_ring(), backend) + for p in l]) + else: + cells_dict[k] = set(l) + else: + raise ValueError("the maximal cells are not given in correct form") + if not cells_dict: + self._dim = -1 + if ambient_dim is None: + ambient_dim = -1 + else: + self._dim = max(cells_dict.keys()) + if ambient_dim is None: + ambient_dim = next(iter(cells_dict[self._dim])).ambient_dim() + self._ambient_dim = ambient_dim + self._maximal_cells = cells_dict + if not all((is_Polyhedron(cell) and + cell.ambient_dim() == self._ambient_dim) + for cell in self.maximal_cell_iterator()): + raise ValueError("the given cells are not polyhedra " + + "in the same ambient space") + # initialize the attributes + self._is_convex = None + self._polyhedron = None + self._maximal_cells_sorted = None # needed for hash + self._cells = None + self._face_poset = None + + if maximality_check: + self.cells() # compute self._cells and self._face_poset + self._maximal_cells = cells_list_to_cells_dict( + self._face_poset.maximal_elements()) + if face_to_face_check: + poset = self.face_poset() + maximal_cells = poset.maximal_elements() # a list + for i in range(len(maximal_cells)): + p = maximal_cells[i] + for j in range(i, len(maximal_cells)): + q = maximal_cells[j] + r = p.intersection(q) + if not (r.is_empty() or (r in poset) and + poset.is_gequal(p, r) and poset.is_gequal(q, r)): + raise ValueError("the given cells are not face-to-face") + self._is_immutable = False + if not is_mutable or is_immutable: + self.set_immutable() + + def cells(self, subcomplex=None): + """ + The cells of this polyhedral complex, in the form of a dictionary: + the keys are integers, representing dimension, and the value + associated to an integer `d` is the set of `d`-cells. + + INPUT: + + - ``subcomplex`` -- (optional) if a subcomplex is given then + return the cells which are **not** in this subcomplex + + EXAMPLES:: + + sage: pc = PolyhedralComplex([ + ....: Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]), + ....: Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])]) + sage: list(pc.cells().keys()) + [2, 1, 0] + """ + if subcomplex is not None: + raise NotImplementedError("providing subcomplex is not implemented") + if self._cells is not None: + return self._cells + maximal_cells = self.maximal_cells() + cells = {} + covers = {} + for k in range(self._dim, -1, -1): + if k in maximal_cells: + if k not in cells: + cells[k] = set([]) + cells[k].update(maximal_cells[k]) + if k in cells: + for cell in cells[k]: + if cell not in covers: + covers[cell] = [] + for facet in cell.facets(): + p = facet.as_polyhedron() + if p not in covers: + covers[p] = [] + covers[p].append(cell) + if (k-1) not in cells: + cells[k-1] = set([]) + cells[k-1].add(p) + self._face_poset = Poset(covers) + self._cells = cells + return self._cells + + def cell_iterator(self, increasing=True): + """ + An iterator for the cells in this polyhedral complex. + + INPUT: + + - ``increasing`` -- (default ``True``) if ``True``, return + cells in increasing order of dimension, thus starting with the + zero-dimensional cells; otherwise it returns cells in decreasing + order of dimension + + .. NOTE:: + + Among the cells of a fixed dimension, there is no sorting. + + EXAMPLES:: + + sage: pc = PolyhedralComplex([ + ....: Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]), + ....: Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])]) + sage: len(list(pc.cell_iterator())) + 11 + """ + cells = self.cells() + dim_index = range(0, self.dimension() + 1) + if not increasing: + dim_index = reversed(dim_index) + for d in dim_index: + if d in cells: + for c in cells[d]: + yield c + + def _n_cells_sorted(self, n, subcomplex=None): + """ + Sorted list of cells of dimension ``n`` of this polyhedral complex. + + INPUT: + + - ``n`` -- non-negative integer; the dimension + - ``subcomplex`` -- (optional) if a subcomplex is given then + return the cells which are **not** in this subcomplex + + EXAMPLES:: + + sage: pc = PolyhedralComplex([ + ....: Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]), + ....: Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])]) + sage: [p.Vrepresentation() for p in pc._n_cells_sorted(1)] + [(A vertex at (0, 0), A vertex at (0, 2)), + (A vertex at (0, 0), A vertex at (1, 1)), + (A vertex at (0, 0), A vertex at (1, 2)), + (A vertex at (0, 2), A vertex at (1, 2)), + (A vertex at (1, 1), A vertex at (1, 2))] + sage: pc._n_cells_sorted(3) + [] + """ + n_cells = self.n_cells(n, subcomplex) + return sorted(n_cells, + key=lambda p: (p.vertices(), p.rays(), p.lines())) + + def cells_sorted(self, subcomplex=None): + """ + The sorted list of the cells of this polyhedral complex + in non-increasing dimensions. + + INPUT: + + - ``subcomplex`` -- (optional) if a subcomplex is given then + return the cells which are **not** in this subcomplex + + EXAMPLES:: + + sage: pc = PolyhedralComplex([ + ....: Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]), + ....: Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])]) + sage: len(pc.cells_sorted()) + 11 + sage: pc.cells_sorted()[0].Vrepresentation() + (A vertex at (0, 0), A vertex at (0, 2), A vertex at (1, 2)) + """ + cells = [] + for n in range(self._dim, -1, -1): + cells += self._n_cells_sorted(n, subcomplex) + return cells + + def maximal_cells(self): + """ + The maximal cells of this polyhedral complex, in the form of a + dictionary: the keys are integers, representing dimension, and the + value associated to an integer `d` is the set of `d`-maximal cells. + + .. WARNING:: + + This may give the wrong answer if the polyhedral complex + was constructed with ``maximality_check`` set to ``False``. + + EXAMPLES:: + + sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]) + sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)]) + sage: p3 = Polyhedron(vertices=[(1, 2), (0, 2)]) + sage: pc = PolyhedralComplex([p1, p2, p3]) + sage: len(pc.maximal_cells()[2]) + 2 + sage: 1 in pc.maximal_cells() + False + + Wrong answer due to ``maximality_check=False``:: + + sage: pc_invalid = PolyhedralComplex([p1, p2, p3], + ....: maximality_check=False) + sage: len(pc_invalid.maximal_cells()[1]) + 1 + """ + return self._maximal_cells + + def maximal_cell_iterator(self, increasing=False): + r""" + An iterator for the maximal cells in this polyhedral complex. + + INPUT: + + - ``increasing`` -- (optional, default ``False``) if ``True``, return + maximal cells in increasing order of dimension. + Otherwise it returns cells in decreasing order of dimension. + + .. NOTE:: + + Among the cells of a fixed dimension, there is no sorting. + + .. WARNING:: + + This may give the wrong answer if the polyhedral complex + was constructed with ``maximality_check`` set to ``False``. + + EXAMPLES:: + + sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]) + sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)]) + sage: p3 = Polyhedron(vertices=[(1, 2), (0, 2)]) + sage: pc = PolyhedralComplex([p1, p2, p3]) + sage: len(list(pc.maximal_cell_iterator())) + 2 + + Wrong answer due to ``maximality_check=False``:: + + sage: pc_invalid = PolyhedralComplex([p1, p2, p3], + ....: maximality_check=False) + sage: len(list(pc_invalid.maximal_cell_iterator())) + 3 + """ + maximal_cells = self.maximal_cells() + dim_index = range(-1, self.dimension() + 1) + if not increasing: + dim_index = reversed(dim_index) + for d in dim_index: + if d in maximal_cells: + for c in maximal_cells[d]: + yield c + + def n_maximal_cells(self, n): + r""" + List of maximal cells of dimension ``n`` of this polyhedral complex. + + INPUT: + + - ``n`` -- non-negative integer; the dimension + + .. NOTE:: + + The resulting list need not be sorted. If you want a sorted + list of `n`-cells, use :meth:`_n_maximal_cells_sorted`. + + .. WARNING:: + + This may give the wrong answer if the polyhedral complex + was constructed with ``maximality_check`` set to ``False``. + + EXAMPLES:: + + sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]) + sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)]) + sage: p3 = Polyhedron(vertices=[(1, 2), (0, 2)]) + sage: pc = PolyhedralComplex([p1, p2, p3]) + sage: len(pc.n_maximal_cells(2)) + 2 + sage: len(pc.n_maximal_cells(1)) + 0 + + Wrong answer due to ``maximality_check=False``:: + + sage: pc_invalid = PolyhedralComplex([p1, p2, p3], + ....: maximality_check=False) + sage: len(pc_invalid.n_maximal_cells(1)) + 1 + """ + if n in self.maximal_cells(): + return list(self.maximal_cells()[n]) + else: + return [] + + def _n_maximal_cells_sorted(self, n): + """ + Sorted list of maximal cells of dimension ``n`` of this polyhedral + complex. + + INPUT: + + - ``n`` -- (non-negative integer) the dimension + + .. WARNING:: + + This may give the wrong answer if the polyhedral complex + was constructed with ``maximality_check`` set to ``False``. + + EXAMPLES:: + + sage: pc = PolyhedralComplex([ + ....: Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]), + ....: Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])]) + sage: pc._n_maximal_cells_sorted(2)[0].vertices_list() + [[0, 0], [0, 2], [1, 2]] + """ + n_maximal_cells = self.n_maximal_cells(n) + return sorted(n_maximal_cells, + key=lambda p: (p.vertices(), p.rays(), p.lines())) + + def maximal_cells_sorted(self): + """ + Return the sorted list of the maximal cells of this polyhedral complex + by non-increasing dimensions. + + EXAMPLES:: + + sage: pc = PolyhedralComplex([ + ....: Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]), + ....: Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])]) + sage: [p.vertices_list() for p in pc.maximal_cells_sorted()] + [[[0, 0], [0, 2], [1, 2]], [[0, 0], [1, 1], [1, 2]]] + """ + if self._maximal_cells_sorted is None: + maximal_cells = [] + for n in range(self._dim, -1, -1): + maximal_cells += self._n_maximal_cells_sorted(n) + self._maximal_cells_sorted = maximal_cells + return self._maximal_cells_sorted + + def is_maximal_cell(self, c): + """ + Return whether the given cell ``c`` is a maximal cell of ``self``. + + .. WARNING:: + + This may give the wrong answer if the polyhedral complex + was constructed with ``maximality_check`` set to ``False``. + + EXAMPLES:: + + sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]) + sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)]) + sage: p3 = Polyhedron(vertices=[(1, 2), (0, 2)]) + sage: pc = PolyhedralComplex([p1, p2, p3]) + sage: pc.is_maximal_cell(p1) + True + sage: pc.is_maximal_cell(p3) + False + + Wrong answer due to ``maximality_check=False``:: + + sage: pc_invalid = PolyhedralComplex([p1, p2, p3], + ....: maximality_check=False) + sage: pc_invalid.is_maximal_cell(p3) + True + """ + d = c.dimension() + # return (c in self.n_maximal_cells(d)) # use set instead of list + return (d in self.maximal_cells()) and (c in self.maximal_cells()[d]) + + def is_cell(self, c): + """ + Return whether the given cell ``c`` is a cell of ``self``. + + EXAMPLES:: + + sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]) + sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)]) + sage: p3 = Polyhedron(vertices=[(1, 2), (0, 2)]) + sage: pc = PolyhedralComplex([p1, p2]) + sage: pc.is_cell(p3) + True + sage: pc.is_cell(Polyhedron(vertices=[(0, 0)])) + True + """ + d = c.dimension() + return (d in self.cells()) and (c in self.cells()[d]) + + def dimension(self): + """ + The dimension of this cell complex: the maximum + dimension of its cells. + + EXAMPLES:: + + sage: pc = PolyhedralComplex([ + ....: Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]), + ....: Polyhedron(vertices=[(1, 2), (0, 2)]) ]) + sage: pc.dimension() + 2 + sage: empty_pc = PolyhedralComplex([]) + sage: empty_pc.dimension() + -1 + """ + return self._dim + + def ambient_dimension(self): + """ + The ambient dimension of this cell complex: the ambient + dimension of each of its cells. + + EXAMPLES:: + + sage: pc = PolyhedralComplex([Polyhedron(vertices=[(1, 2, 3)])]) + sage: pc.ambient_dimension() + 3 + sage: empty_pc = PolyhedralComplex([]) + sage: empty_pc.ambient_dimension() + -1 + sage: pc0 = PolyhedralComplex(ambient_dim=2) + sage: pc0.ambient_dimension() + 2 + """ + return self._ambient_dim + + def plot(self, **kwds): + """ + Return a plot of the polyhedral complex, if it is of dim at most 3. + + EXAMPLES:: + + sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]) + sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)]) + sage: pc = PolyhedralComplex([p1, p2]) + sage: pc.plot() + Graphics object consisting of 10 graphics primitives + """ + if self.dimension() > 3: + raise ValueError("cannot plot in high dimension") + return sum(cell.plot(**kwds) for cell in self.maximal_cell_iterator()) + + def is_pure(self): + """ + Test if this polyhedral complex is pure. + + A polyhedral complex is pure if and only if all of its maximal cells + have the same dimension. + + .. WARNING:: + + This may give the wrong answer if the polyhedral complex + was constructed with ``maximality_check`` set to ``False``. + + EXAMPLES:: + + sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]) + sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)]) + sage: p3 = Polyhedron(vertices=[(1, 2), (0, 2)]) + sage: pc = PolyhedralComplex([p1, p2, p3]) + sage: pc.is_pure() + True + + Wrong answer due to ``maximality_check=False``:: + + sage: pc_invalid = PolyhedralComplex([p1, p2, p3], + ....: maximality_check=False) + sage: pc_invalid.is_pure() + False + """ + return len(self._maximal_cells) == 1 + + def is_full_dimensional(self): + """ + Return whether this polyhedral complex is full-dimensional: + its dimension is equal to its ambient dimension. + + EXAMPLES:: + + sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]) + sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)]) + sage: p3 = Polyhedron(vertices=[(1, 2), (0, 2)]) + sage: pc = PolyhedralComplex([p1, p2, p3]) + sage: pc.is_full_dimensional() + True + sage: PolyhedralComplex([p3]).is_full_dimensional() + False + """ + return self._dim == self._ambient_dim + + def __hash__(self): + """ + Compute the hash value of ``self`` using its ``maximal_cells_sorted``. + + EXAMPLES:: + + sage: p1 = Polyhedron(vertices=[(1/3, 1/3), (0, 0), (1, 2)]) + sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 1/2)]) + sage: pc1 = PolyhedralComplex([p1, p2], is_mutable=False) + sage: hash(pc1) == hash(pc1) + True + sage: pc2 = PolyhedralComplex([p2, p1], is_mutable=False) + sage: hash(pc1) == hash(pc2) + True + sage: pc3 = PolyhedralComplex([p1, p2]) + sage: hash(pc3) + Traceback (most recent call last): + ... + ValueError: this polyhedral complex must be immutable; call set_immutable() + """ + if not self._is_immutable: + raise ValueError("this polyhedral complex must be immutable; " + + "call set_immutable()") + return hash(tuple(self.maximal_cells_sorted())) + + def __eq__(self, right): + """ + Two polyhedral complexes are equal iff their maximal cells are equal. + + EXAMPLES:: + + sage: p1 = Polyhedron(vertices=[(1/3, 1/3), (0, 0), (1, 2)]) + sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 1/2)]) + sage: pc1 = PolyhedralComplex([p1, p2]) + sage: pc1 == pc1 + True + sage: pc2 = PolyhedralComplex([p2, p1]) + sage: pc1 == pc2 + True + """ + return isinstance(right, PolyhedralComplex) and ( + self.maximal_cells_sorted() == right.maximal_cells_sorted()) + + def __ne__(self, right): + """ + Return ``True`` if ``self`` and ``right`` are not equal. + + EXAMPLES:: + + sage: pc1 = PolyhedralComplex([ + ....: Polyhedron(vertices=[(1/3, 1/3), (0, 0), (1, 2)])]) + sage: pc2 = PolyhedralComplex([ + ....: Polyhedron(vertices=[(1, 2), (0, 0), (0, 1/2)])]) + sage: pc1 != pc2 + True + """ + return not self.__eq__(right) + + def __copy__(self): + """ + Return a mutable copy of ``self``. + + EXAMPLES:: + + sage: pc1 = PolyhedralComplex([Polyhedron(vertices=[(0, 0)])]) + sage: pc2 = copy(pc1) + sage: pc1 == pc2 + True + """ + return PolyhedralComplex(self._maximal_cells, maximality_check=False, + backend=self._backend) + + def _an_element_(self): + """ + Return a (maximal) cell of this complex. + + EXAMPLES:: + + sage: PolyhedralComplex()._an_element_() + Traceback (most recent call last): + ... + EmptySetError: the complex is empty + sage: pc = PolyhedralComplex([ + ....: Polyhedron(vertices=[(1/3, 1/3), (0, 0), (1, 2)]), + ....: Polyhedron(vertices=[(1, 2), (0, 0), (0, 1/2)])]) + sage: pc._an_element_().vertices_list() + [[0, 0], [0, 1/2], [1, 2]] + """ + try: + return next(self.maximal_cell_iterator(increasing=False)) + except StopIteration: + from sage.categories.sets_cat import EmptySetError + raise EmptySetError("the complex is empty") + + def __contains__(self, x): + """ + True if ``x`` is a polyhedron which is contained in this complex. + + EXAMPLES:: + + sage: p1 = Polyhedron(vertices=[(1/3, 1/3), (0, 0), (1, 2)]) + sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 1/2)]) + sage: pc = PolyhedralComplex([p1, p2]) + sage: (p1 in pc) and (p2 in pc) + True + sage: Polyhedron(vertices=[(1, 2), (0, 0)]) in pc + True + sage: Polyhedron(vertices=[(1, 1), (0, 0)]) in pc + False + sage: Polyhedron(vertices=[(0, 0)]) in pc + True + sage: (0, 0) in pc # not a polyhedron + False + """ + if not is_Polyhedron(x): + return False + dim = x.dimension() + return dim in self.cells() and x in self.cells()[dim] + + def __call__(self, x): + """ + If ``x`` is a polyhedron in this complex, return it. + Otherwise, raise a ``ValueError``. + + EXAMPLES:: + + sage: pc = PolyhedralComplex([ + ....: Polyhedron(vertices=[(1/3, 1/3), (0, 0), (1, 2)]), + ....: Polyhedron(vertices=[(1, 2), (0, 0), (0, 1/2)])]) + sage: pc(Polyhedron(vertices=[(1, 2), (0, 0)])) + A 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: pc(Polyhedron(vertices=[(1, 1)])) + Traceback (most recent call last): + ... + ValueError: the polyhedron is not in this complex + """ + if x not in self: + raise ValueError('the polyhedron is not in this complex') + return x + + def face_poset(self): + r""" + The face poset of this polyhedral complex, the poset of + nonempty cells, ordered by inclusion. + + EXAMPLES:: + + sage: pc = PolyhedralComplex([ + ....: Polyhedron(vertices=[(1/3, 1/3), (0, 0), (1, 2)]), + ....: Polyhedron(vertices=[(1, 2), (0, 0), (0, 1/2)])]) + sage: poset = pc.face_poset() + sage: poset + Finite poset containing 11 elements + sage: d = {i: i.vertices_matrix() for i in poset} + sage: poset.plot(element_labels=d) + Graphics object consisting of 28 graphics primitives + + For a nonbounded polyhedral complex:: + + sage: pc = PolyhedralComplex([ + ....: Polyhedron(vertices=[(1/3, 1/3), (0, 0), (1, 2)]), + ....: Polyhedron(vertices=[(1, 2), (0, 0), (0, 1/2)]), + ....: Polyhedron(vertices=[(-1/2, -1/2)], lines=[(1, -1)]), + ....: Polyhedron(rays=[(1, 0)])]) + sage: poset = pc.face_poset() + sage: poset + Finite poset containing 13 elements + sage: d = {i:''.join([str(v)+'\n' + ....: for v in i.Vrepresentation()]) for i in poset} + sage: poset.show(element_labels=d, figsize=15) # not tested + sage: pc = PolyhedralComplex([ + ....: Polyhedron(rays=[(1,0),(0,1)]), + ....: Polyhedron(rays=[(-1,0),(0,1)]), + ....: Polyhedron(rays=[(-1,0),(0,-1)]), + ....: Polyhedron(rays=[(1,0),(0,-1)])]) + sage: pc.face_poset() + Finite poset containing 9 elements + """ + if self._face_poset is None: + self.cells() # poset is obtained and cached in cells() + return self._face_poset + + def is_subcomplex(self, other): + r""" + Return whether ``self`` is a subcomplex of ``other``. + + INPUT: + + - ``other`` -- a polyhedral complex + + Each maximal cell of ``self`` must be a cell of ``other`` + for this to be ``True``. + + EXAMPLES:: + + sage: p1 = Polyhedron(vertices=[(1/3, 1/3), (0, 0), (1, 2)]) + sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 1/2)]) + sage: p3 = Polyhedron(vertices=[(0, 0), (1, 0)]) + sage: pc = PolyhedralComplex([p1, Polyhedron(vertices=[(1, 0)])]) + sage: pc.is_subcomplex(PolyhedralComplex([p1, p2, p3])) + True + sage: pc.is_subcomplex(PolyhedralComplex([p1, p2])) + False + """ + other_cells = other.cells() + for (d, stratum) in self.maximal_cells().items(): + if not stratum.issubset(other_cells.get(d, set([]))): + return False + return True + + def is_compact(self): + """ + Test for boundedness of the polyhedral complex + + EXAMPLES:: + + sage: p1 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 1/2)]) + sage: p2 = Polyhedron(rays=[(1, 0)]) + sage: PolyhedralComplex([p1]).is_compact() + True + sage: PolyhedralComplex([p1, p2]).is_compact() + False + """ + return all(p.is_compact() for p in self.maximal_cell_iterator()) + + def graph(self): + """ + The 1-skeleton of this polyhedral complex, as a graph. + + The vertices of the graph are of type ``vector``. Raises + a ``NotImplementedError`` if the polyhedral complex is unbounded. + + .. WARNING:: + + This may give the wrong answer if the polyhedral complex + was constructed with ``maximality_check`` set to ``False``. + + EXAMPLES:: + + sage: pc = PolyhedralComplex([ + ....: Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]), + ....: Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])]) + sage: g = pc.graph(); g + Graph on 4 vertices + sage: g.vertices() + [(0, 0), (0, 2), (1, 1), (1, 2)] + sage: g.edges(labels=False) + [((0, 0), (0, 2)), ((0, 0), (1, 1)), ((0, 0), (1, 2)), ((0, 2), (1, 2)), ((1, 1), (1, 2))] + sage: PolyhedralComplex([Polyhedron(rays=[(1,1)])]).graph() + Traceback (most recent call last): + ... + NotImplementedError: the polyhedral complex is unbounded + + Wrong answer due to ``maximality_check=False``:: + + sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]) + sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)]) + sage: p3 = Polyhedron(vertices=[(1, 2), (0, 2)]) + sage: PolyhedralComplex([p1, p2]).is_pure() + True + sage: PolyhedralComplex([p2, p3], maximality_check=True).is_pure() + True + sage: PolyhedralComplex([p2, p3], maximality_check=False).is_pure() + False + """ + if not self.is_compact(): + raise NotImplementedError("the polyhedral complex is unbounded") + edges = self.n_cells(1) + d = {} + for e in edges: + v, max_e = sorted(e.vertices_matrix().columns()) + if v in d: + d[v].append(max_e) + else: + d[v] = [max_e] + for v in self.n_maximal_cells(0): + d[v] = [] + return Graph(d) + + def is_connected(self): + """ + Return whether ``self`` is connected. + + EXAMPLES:: + + sage: pc1 = PolyhedralComplex([ + ....: Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]), + ....: Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])]) + sage: pc1.is_connected() + True + sage: pc2 = PolyhedralComplex([ + ....: Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]), + ....: Polyhedron(vertices=[(0, 2)])]) + sage: pc2.is_connected() + False + sage: pc3 = PolyhedralComplex([ + ....: Polyhedron(vertices=[(1/3, 1/3), (0, 0), (1, 2)]), + ....: Polyhedron(vertices=[(1, 2), (0, 0), (0, 1/2)]), + ....: Polyhedron(vertices=[(-1/2, -1/2)], lines=[(1, -1)]), + ....: Polyhedron(rays=[(1, 0)])]) + sage: pc3.is_connected() + False + sage: pc4 = PolyhedralComplex([ + ....: Polyhedron(vertices=[(1/3, 1/3), (0, 0), (1, 2)]), + ....: Polyhedron(rays=[(1, 0)])]) + sage: pc4.is_connected() + True + """ + if self.is_compact(): + return self.graph().is_connected() # faster than using poset? + else: + return self.face_poset().is_connected() + + def connected_component(self, cell=None): + """ + Return the connected component of this polyhedral complex + containing a given cell. + + INPUT: + + - ``cell`` -- (default: ``self.an_element()``) a cell of ``self`` + + OUTPUT: + + The connected component containing ``cell``. If the polyhedral complex + is empty or if it does not contain the given cell, raise an error. + + EXAMPLES:: + + sage: t1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]) + sage: t2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)]) + sage: v1 = Polyhedron(vertices=[(1, 1)]) + sage: v2 = Polyhedron(vertices=[(0, 2)]) + sage: v3 = Polyhedron(vertices=[(-1, 0)]) + sage: o = Polyhedron(vertices=[(0, 0)]) + sage: r = Polyhedron(rays=[(1, 0)]) + sage: l = Polyhedron(vertices=[(-1, 0)], lines=[(1, -1)]) + sage: pc1 = PolyhedralComplex([t1, t2]) + sage: pc1.connected_component() == pc1 + True + sage: pc1.connected_component(v1) == pc1 + True + sage: pc2 = PolyhedralComplex([t1, v2]) + sage: pc2.connected_component(t1) == PolyhedralComplex([t1]) + True + sage: pc2.connected_component(o) == PolyhedralComplex([t1]) + True + sage: pc2.connected_component(v3) + Traceback (most recent call last): + ... + ValueError: the polyhedral complex does not contain the given cell + sage: pc2.connected_component(r) + Traceback (most recent call last): + ... + ValueError: the polyhedral complex does not contain the given cell + sage: pc3 = PolyhedralComplex([t1, t2, r]) + sage: pc3.connected_component(v2) == pc3 + True + sage: pc4 = PolyhedralComplex([t1, t2, r, l]) + sage: pc4.connected_component(o) == pc3 + True + sage: pc4.connected_component(v3) + Traceback (most recent call last): + ... + ValueError: the polyhedral complex does not contain the given cell + sage: pc5 = PolyhedralComplex([t1, t2, r, l, v3]) + sage: pc5.connected_component(v3) == PolyhedralComplex([v3]) + True + sage: PolyhedralComplex([]).connected_component() + Traceback (most recent call last): + ... + ValueError: the empty polyhedral complex has no connected components + """ + if self.dimension() == -1: + raise ValueError( + "the empty polyhedral complex has no connected components") + if cell is None: + cell = self._an_element_() + if self.is_compact(): # use graph (faster than poset?) + if not cell.is_compact(): + raise ValueError( + "the polyhedral complex does not contain the given cell") + v = cell.vertices_matrix().columns()[0] + g = self.graph() + if v not in g: + raise ValueError( + "the polyhedral complex does not contain the given cell") + vertices = g.connected_component_containing_vertex(v) + facets = [f for f in self.maximal_cell_iterator() + if any(vf in f.vertices_matrix().columns() + for vf in vertices)] + else: # use face_poset + g = self.face_poset().hasse_diagram() + if cell not in g: + raise ValueError( + "the polyhedral complex does not contain the given cell") + faces = g.connected_component_containing_vertex(cell) + facets = [f for f in self.maximal_cell_iterator() + if f in faces] + return PolyhedralComplex(facets, maximality_check=False, + is_immutable=self._is_immutable, + backend=self._backend) + + def connected_components(self): + """ + Return the connected components of this polyhedral complex, + as list of (sub-)PolyhedralComplexes. + + EXAMPLES:: + + sage: t1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]) + sage: t2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)]) + sage: v1 = Polyhedron(vertices=[(1, 1)]) + sage: v2 = Polyhedron(vertices=[(0, 2)]) + sage: v3 = Polyhedron(vertices=[(-1, 0)]) + sage: o = Polyhedron(vertices=[(0, 0)]) + sage: r = Polyhedron(rays=[(1, 0)]) + sage: l = Polyhedron(vertices=[(-1, 0)], lines=[(1, -1)]) + sage: pc1 = PolyhedralComplex([t1, t2]) + sage: len(pc1.connected_components()) + 1 + sage: pc2 = PolyhedralComplex([t1, v2]) + sage: len(pc2.connected_components()) + 2 + sage: pc3 = PolyhedralComplex([t1, t2, r]) + sage: len(pc3.connected_components()) + 1 + sage: pc4 = PolyhedralComplex([t1, t2, r, l]) + sage: len(pc4.connected_components()) + 2 + sage: pc5 = PolyhedralComplex([t1, t2, r, l, v3]) + sage: len(pc5.connected_components()) + 3 + sage: PolyhedralComplex([]).connected_components() + Traceback (most recent call last): + ... + ValueError: the empty polyhedral complex has no connected components + """ + if self.dimension() == -1: + raise ValueError( + "the empty polyhedral complex has no connected components") + if self.is_compact(): # use graph (faster than poset)? + g = self.graph() + lists_of_vertices = g.connected_components(sort=False) + lists_of_facets = [[f for f in self.maximal_cell_iterator() + if any(vf in f.vertices_matrix().columns() + for vf in vertices)] + for vertices in lists_of_vertices] + else: # use face_poset + g = self.face_poset().hasse_diagram() + lists_of_faces = g.connected_components(sort=False) + lists_of_facets = [ + [f for f in self.maximal_cell_iterator() if f in faces] + for faces in lists_of_faces] + results = [PolyhedralComplex(facets, maximality_check=False, + is_immutable=self._is_immutable, + backend=self._backend) + for facets in lists_of_facets] + return results + + def n_skeleton(self, n): + r""" + The `n`-skeleton of this polyhedral complex. + + The `n`-skeleton of a polyhedral complex is obtained by discarding + all of the cells in dimensions larger than `n`. + + INPUT: + + - ``n`` -- non-negative integer; the dimension + + .. SEEALSO:: + + :meth:`stratify` + + EXAMPLES:: + + sage: pc = PolyhedralComplex([ + ....: Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]), + ....: Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])]) + sage: pc.n_skeleton(2) + Polyhedral complex with 2 maximal cells + sage: pc.n_skeleton(1) + Polyhedral complex with 5 maximal cells + sage: pc.n_skeleton(0) + Polyhedral complex with 4 maximal cells + """ + if n >= self.dimension(): + return copy(self) + facets = [f for f in self.maximal_cell_iterator() if f.dimension() < n] + facets.extend(self.n_cells(n)) + return PolyhedralComplex(facets, maximality_check=False, + is_immutable=self._is_immutable, + backend=self._backend) + + def stratify(self, n): + r""" + Return the pure sub-polyhedral complex which is constructed from the + `n`-dimensional maximal cells of this polyhedral complex. + + .. SEEALSO:: + + :meth:`n_skeleton` + + .. WARNING:: + + This may give the wrong answer if the polyhedral complex + was constructed with ``maximality_check`` set to ``False``. + + EXAMPLES:: + + sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]) + sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)]) + sage: p3 = Polyhedron(vertices=[(1, 2), (0, 2)]) + sage: pc = PolyhedralComplex([p1, p2, p3]) + sage: pc.stratify(2) == pc + True + sage: pc.stratify(1) + Polyhedral complex with 0 maximal cells + + Wrong answer due to ``maximality_check=False``:: + + sage: pc_invalid = PolyhedralComplex([p1, p2, p3], + ....: maximality_check=False) + sage: pc_invalid.stratify(1) + Polyhedral complex with 1 maximal cell + """ + n_faces = self.n_maximal_cells(n) + return PolyhedralComplex(n_faces, maximality_check=False, + is_immutable=self._is_immutable, + backend=self._backend) + + def boundary_subcomplex(self): + """ + Return the sub-polyhedral complex that is the boundary of ``self``. + + A point `P` is on the boundary of a set `S` if `P` is in the + closure of `S` but not in the interior of `S`. + + EXAMPLES:: + + sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]) + sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)]) + sage: p3 = Polyhedron(vertices=[(1, 2), (0, 2)]) + sage: bd = PolyhedralComplex([p1, p2]).boundary_subcomplex() + sage: len(bd.n_maximal_cells(2)) + 0 + sage: len(bd.n_maximal_cells(1)) + 4 + sage: pt = PolyhedralComplex([p3]) + sage: pt.boundary_subcomplex() == pt + True + + Test on polyhedral complex which is not pure:: + + sage: pc_non_pure = PolyhedralComplex([p1, p3]) + sage: pc_non_pure.boundary_subcomplex() == pc_non_pure.n_skeleton(1) + True + + Test with ``maximality_check == False``:: + + sage: pc_invalid = PolyhedralComplex([p2, p3], + ....: maximality_check=False) + sage: pc_invalid.boundary_subcomplex() == pc_invalid.n_skeleton(1) + True + + Test unbounded cases:: + + sage: pc1 = PolyhedralComplex([ + ....: Polyhedron(vertices=[[1,0], [0,1]], rays=[[1,0], [0,1]])]) + sage: pc1.boundary_subcomplex() == pc1.n_skeleton(1) + True + sage: pc1b = PolyhedralComplex([Polyhedron( + ....: vertices=[[1,0,0], [0,1,0]], rays=[[1,0,0],[0,1,0]])]) + sage: pc1b.boundary_subcomplex() == pc1b + True + sage: pc2 = PolyhedralComplex([ + ....: Polyhedron(vertices=[[-1,0], [1,0]], lines=[[0,1]])]) + sage: pc2.boundary_subcomplex() == pc2.n_skeleton(1) + True + sage: pc3 = PolyhedralComplex([ + ....: Polyhedron(vertices=[[1,0], [0,1]], rays=[[1,0], [0,1]]), + ....: Polyhedron(vertices=[[1,0], [0,-1]], rays=[[1,0], [0,-1]])]) + sage: pc3.boundary_subcomplex() == pc3.n_skeleton(1) + False + """ + if self.is_full_dimensional(): + return PolyhedralComplex(self.relative_boundary_cells(), + is_immutable=self._is_immutable, + backend=self._backend) + else: + ans = copy(self) + if self._is_immutable: + ans.set_immutable() + return ans + + def relative_boundary_cells(self): + r""" + Return the maximal cells of the relative-boundary sub-complex. + + A point `P` is in the relative boundary of a set `S` if `P` is in the + closure of `S` but not in the relative interior of `S`. + + .. WARNING:: + + This may give the wrong answer if the polyhedral complex + was constructed with ``maximality_check`` set to ``False``. + + EXAMPLES:: + + sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]) + sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)]) + sage: p3 = Polyhedron(vertices=[(1, 2), (0, 2)]) + sage: p4 = Polyhedron(vertices=[(2, 2)]) + sage: pc = PolyhedralComplex([p1, p2]) + sage: rbd_cells = pc.relative_boundary_cells() + sage: len(rbd_cells) + 4 + sage: all(p.dimension() == 1 for p in rbd_cells) + True + sage: pc_lower_dim = PolyhedralComplex([p3]) + sage: sorted([p.vertices() for p in pc_lower_dim.relative_boundary_cells()]) + [(A vertex at (0, 2),), (A vertex at (1, 2),)] + + Test on polyhedral complex which is not pure:: + + sage: pc_non_pure = PolyhedralComplex([p1, p3, p4]) + sage: (set(pc_non_pure.relative_boundary_cells()) + ....: == set([f.as_polyhedron() for f in p1.faces(1)] + [p3, p4])) + True + + Test with ``maximality_check == False``:: + + sage: pc_invalid = PolyhedralComplex([p2, p3], + ....: maximality_check=False) + sage: (set(pc_invalid.relative_boundary_cells()) + ....: == set([f.as_polyhedron() for f in p2.faces(1)])) + True + + Test unbounded case:: + + sage: pc3 = PolyhedralComplex([ + ....: Polyhedron(vertices=[[1,0], [0,1]], rays=[[1,0], [0,1]]), + ....: Polyhedron(vertices=[[1,0], [0,-1]], rays=[[1,0], [0,-1]])]) + sage: len(pc3.relative_boundary_cells()) + 4 + """ + d = self.dimension() + poset = self.face_poset() + faces = self.n_cells(d - 1) + ans = [face for face in faces if len(poset.upper_covers(face)) == 1] + if not self.is_pure(): + ans += [p for p in poset.maximal_elements() if p.dimension() < d] + return ans + + def is_convex(self): + r""" + Return whether the set of points in ``self`` is a convex set. + + When ``self`` is convex, the union of its cells is a Polyhedron. + + .. SEEALSO:: + + :meth:`union_as_polyhedron` + + EXAMPLES:: + + sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]) + sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)]) + sage: p3 = Polyhedron(vertices=[(0, 0), (1, 1), (2, 0)]) + sage: p4 = Polyhedron(vertices=[(2, 2)]) + sage: PolyhedralComplex([p1, p2]).is_convex() + True + sage: PolyhedralComplex([p1, p3]).is_convex() + False + sage: PolyhedralComplex([p1, p4]).is_convex() + False + + Test unbounded cases:: + + sage: pc1 = PolyhedralComplex([ + ....: Polyhedron(vertices=[[1,0], [0,1]], rays=[[1,0], [0,1]])]) + sage: pc1.is_convex() + True + sage: pc2 = PolyhedralComplex([ + ....: Polyhedron(vertices=[[-1,0], [1,0]], lines=[[0,1]])]) + sage: pc2.is_convex() + True + sage: pc3 = PolyhedralComplex([ + ....: Polyhedron(vertices=[[1,0], [0,1]], rays=[[1,0], [0,1]]), + ....: Polyhedron(vertices=[[1,0], [0,-1]], rays=[[1,0], [0,-1]])]) + sage: pc3.is_convex() + False + sage: pc4 = PolyhedralComplex([Polyhedron(rays=[[1,0], [-1,1]]), + ....: Polyhedron(rays=[[1,0], [-1,-1]])]) + sage: pc4.is_convex() + False + + The whole 3d space minus the first orthant is not convex:: + + sage: pc5 = PolyhedralComplex([ + ....: Polyhedron(rays=[[1,0,0], [0,1,0], [0,0,-1]]), + ....: Polyhedron(rays=[[1,0,0], [0,-1,0], [0,0,-1]]), + ....: Polyhedron(rays=[[1,0,0], [0,-1,0], [0,0,1]]), + ....: Polyhedron(rays=[[-1,0,0], [0,-1,0], [0,0,-1]]), + ....: Polyhedron(rays=[[-1,0,0], [0,-1,0], [0,0,1]]), + ....: Polyhedron(rays=[[-1,0,0], [0,1,0], [0,0,-1]]), + ....: Polyhedron(rays=[[-1,0,0], [0,1,0], [0,0,1]])]) + sage: pc5.is_convex() + False + + Test some non-full-dimensional examples:: + + sage: l = PolyhedralComplex([Polyhedron(vertices=[(1, 2), (0, 2)])]) + sage: l.is_convex() + True + sage: pc1b = PolyhedralComplex([Polyhedron( + ....: vertices=[[1,0,0], [0,1,0]], rays=[[1,0,0],[0,1,0]])]) + sage: pc1b.is_convex() + True + sage: pc4b = PolyhedralComplex([ + ....: Polyhedron(rays=[[1,0,0], [-1,1,0]]), + ....: Polyhedron(rays=[[1,0,0], [-1,-1,0]])]) + sage: pc4b.is_convex() + False + """ + if self._is_convex is not None: + return self._is_convex + if not self.is_pure(): + self._is_convex = False + return False + d = self.dimension() + if not self.is_full_dimensional(): + # if max cells must lie in different subspaces, can't be convex. + from sage.modules.free_module import span + f = self.n_maximal_cells(d)[0] + affine_space = span(f.equations_list(), f.base_ring()) + for f in self.n_maximal_cells(d)[1::]: + if span(f.equations_list(), f.base_ring()) != affine_space: + self._is_convex = False + return False + # orient the (relative) boundary halfspaces toward a strict convex + # combination of the vertices. Then check if all vertices are contained + # After making sure that the affine hulls of the cells are the same, + # it does not matter that is not full dimensional. + boundaries = self.relative_boundary_cells() + vertices = set([]) + rays = set([]) + lines = set([]) + for cell in boundaries: + # it suffices to consider only vertices on the boundaries + # Note that a line (as polyhedron) has vertex too + for v in cell.vertices_list(): + vv = vector(v) + vv.set_immutable() + vertices.add(vv) + for cell in self.n_maximal_cells(d): + for r in cell.rays_list(): + rr = vector(r) + rr.set_immutable() + rays.add(rr) + for li in cell.lines_list(): + ll = vector(li) + ll.set_immutable() + lines.add(ll) + center = sum(vertices) / len(vertices) + for cell in boundaries: + for equation in cell.equations_list(): + coeff = vector(equation[1::]) + const = equation[0] + if const + coeff * center == 0: + sign = 0 + elif const + coeff * center > 0: + sign = 1 + for v in vertices: + if const + coeff * v < 0: + self._is_convex = False + return False + elif const + coeff * center < 0: + sign = -1 + for v in vertices: + if const + coeff * v > 0: + self._is_convex = False + return False + for r in rays: + if sign == 0: + sign = coeff * r + else: + if sign * (coeff * r) < 0: + self._is_convex = False + return False + # lines are in the affine space of each boundary cell already + self._is_convex = True + self._polyhedron = Polyhedron(vertices=vertices, rays=rays, lines=lines, + backend=self._backend) + return True + + def union_as_polyhedron(self): + """ + Return ``self`` as a :class:`Polyhedron` if ``self`` is convex. + + EXAMPLES:: + + sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]) + sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)]) + sage: p3 = Polyhedron(vertices=[(0, 0), (1, 1), (2, 0)]) + sage: P = PolyhedralComplex([p1, p2]).union_as_polyhedron() + sage: P.vertices_list() + [[0, 0], [0, 2], [1, 1], [1, 2]] + sage: PolyhedralComplex([p1, p3]).union_as_polyhedron() + Traceback (most recent call last): + ... + ValueError: the polyhedral complex is not convex + """ + if not self.is_convex(): + raise ValueError("the polyhedral complex is not convex") + return self._polyhedron + + def product(self, right): + """ + The (Cartesian) product of this polyhedral complex with another one. + + INPUT: + + - ``right`` -- the other polyhedral complex (the right-hand factor) + + OUTPUT: + + - the product ``self x right`` + + EXAMPLES:: + + sage: pc = PolyhedralComplex([Polyhedron(vertices=[[0], [1]])]) + sage: pc_square = pc.product(pc) + sage: pc_square + Polyhedral complex with 1 maximal cell + sage: next(pc_square.maximal_cell_iterator()).vertices() + (A vertex at (0, 0), + A vertex at (0, 1), + A vertex at (1, 0), + A vertex at (1, 1)) + """ + maximal_cells = [f.product(g) for f in self.maximal_cell_iterator() + for g in right.maximal_cell_iterator()] + return PolyhedralComplex(maximal_cells, maximality_check=False, + is_immutable=(self._is_immutable and + right._is_immutable), + backend=self._backend) + + def disjoint_union(self, right): + """ + The disjoint union of this polyhedral complex with another one. + + INPUT: + + - ``right`` -- the other polyhedral complex (the right-hand factor) + + EXAMPLES:: + + sage: p1 = Polyhedron(vertices=[(-1, 0), (0, 0), (0, 1)]) + sage: p2 = Polyhedron(vertices=[(0, -1), (0, 0), (1, 0)]) + sage: p3 = Polyhedron(vertices=[(0, -1), (1, -1), (1, 0)]) + sage: pc = PolyhedralComplex([p1]).disjoint_union(PolyhedralComplex([p3])) + sage: set(pc.maximal_cell_iterator()) == set([p1, p3]) + True + sage: pc.disjoint_union(PolyhedralComplex([p2])) + Traceback (most recent call last): + ... + ValueError: the two complexes are not disjoint + """ + maximal_cells_self = list(self.maximal_cell_iterator()) + maximal_cells_right = list(right.maximal_cell_iterator()) + for cell in maximal_cells_self: + for cell_right in maximal_cells_right: + if not cell.intersection(cell_right).is_empty(): + raise ValueError("the two complexes are not disjoint") + return PolyhedralComplex(maximal_cells_self + maximal_cells_right, + maximality_check=False, + face_to_face_check=False, + is_immutable=(self._is_immutable and + right._is_immutable), + backend=self._backend) + + def union(self, right): + """ + The union of this polyhedral complex with another one. + + INPUT: + + - ``right`` -- the other polyhedral complex (the right-hand factor) + + EXAMPLES:: + + sage: p1 = Polyhedron(vertices=[(-1, 0), (0, 0), (0, 1)]) + sage: p2 = Polyhedron(vertices=[(0, -1), (0, 0), (1, 0)]) + sage: p3 = Polyhedron(vertices=[(0, -1), (1, -1), (1, 0)]) + sage: pc = PolyhedralComplex([p1]).union(PolyhedralComplex([p3])) + sage: set(pc.maximal_cell_iterator()) == set([p1, p3]) + True + sage: pc.union(PolyhedralComplex([p2])) + Polyhedral complex with 3 maximal cells + sage: p4 = Polyhedron(vertices=[(0, -1), (0, 0), (1, 0), (1, -1)]) + sage: pc.union(PolyhedralComplex([p4])) + Traceback (most recent call last): + ... + ValueError: the given cells are not face-to-face + """ + maximal_cells = list(self.maximal_cell_iterator()) + list( + right.maximal_cell_iterator()) + return PolyhedralComplex(maximal_cells, maximality_check=True, + face_to_face_check=True, + is_immutable=(self._is_immutable and + right._is_immutable), + backend=self._backend) + + def join(self, right): + """ + The join of this polyhedral complex with another one. + + INPUT: + + - ``right`` -- the other polyhedral complex (the right-hand factor) + + EXAMPLES:: + + sage: pc = PolyhedralComplex([Polyhedron(vertices=[[0], [1]])]) + sage: pc_join = pc.join(pc) + sage: pc_join + Polyhedral complex with 1 maximal cell + sage: next(pc_join.maximal_cell_iterator()).vertices() + (A vertex at (0, 0, 0), + A vertex at (0, 0, 1), + A vertex at (0, 1, 1), + A vertex at (1, 0, 0)) + """ + maximal_cells = [f.join(g) for f in self.maximal_cell_iterator() + for g in right.maximal_cell_iterator()] + return PolyhedralComplex(maximal_cells, maximality_check=False, + is_immutable=(self._is_immutable and + right._is_immutable), + backend=self._backend) + + ############################################################ + # abstract methods not implemented in generic cell complex + ############################################################ + + def wedge(self, right): + """ + The wedge (one-point union) of ``self`` with ``right``. + + .. TODO:: + + Implement the wedge product of two polyhedral complexes. + + EXAMPLES:: + + sage: pc = PolyhedralComplex([Polyhedron(vertices=[[0], [1]])]) + sage: pc.wedge(pc) + Traceback (most recent call last): + ... + NotImplementedError: wedge is not implemented for polyhedral complex + """ + raise NotImplementedError("wedge is not implemented for " + + "polyhedral complex") + + ############################################################ + # chain complexes, homology + ############################################################ + def chain_complex(self, subcomplex=None, augmented=False, + verbose=False, check=True, dimensions=None, + base_ring=ZZ, cochain=False): + """ + The chain complex associated to this polyhedral complex. + + .. TODO:: + + Implement chain complexes of a polyhedral complex. + + EXAMPLES:: + + sage: pc = PolyhedralComplex([Polyhedron(vertices=[[0], [1]])]) + sage: pc.chain_complex() + Traceback (most recent call last): + ... + NotImplementedError: chain_complex is not implemented for polyhedral complex + """ + raise NotImplementedError("chain_complex is not implemented for " + + "polyhedral complex") + + def alexander_whitney(self, cell, dim_left): + """ + The decomposition of ``cell`` in this complex into left and right + factors, suitable for computing cup products. + + .. TODO:: + + Implement :meth:`alexander_whitney` of a polyhedral complex. + + EXAMPLES:: + + sage: pc = PolyhedralComplex([Polyhedron(vertices=[[0], [1]])]) + sage: pc.alexander_whitney(None, 1) + Traceback (most recent call last): + ... + NotImplementedError: alexander_whitney is not implemented for polyhedral complex + """ + raise NotImplementedError("alexander_whitney is not implemented for " + + "polyhedral complex") + + ############################################################ + # end of chain complexes, homology + ############################################################ + + # this function overrides the standard one for GenericCellComplex, + # this one counts the number of maximal cells, not all cells, to + # avoid calling and computing self.cells() + def _repr_(self): + """ + Print representation. + + .. WARNING:: + + This may give the wrong answer if the polyhedral complex + was constructed with ``maximality_check`` set to ``False``. + + EXAMPLES:: + + sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]) + sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)]) + sage: p3 = Polyhedron(vertices=[(1, 2), (0, 2)]) + sage: PolyhedralComplex([p1, p2, p3]) + Polyhedral complex with 2 maximal cells + + Wrong answer due to ``maximality_check=False``:: + + sage: PolyhedralComplex([p1, p2, p3], maximality_check=False) + Polyhedral complex with 3 maximal cells + """ + num = len(list(self.maximal_cell_iterator())) + if num == 1: + return "Polyhedral complex with %s maximal cell" % num + else: + return "Polyhedral complex with %s maximal cells" % num + + def set_immutable(self): + """ + Make this polyhedral complex immutable. + + EXAMPLES:: + + sage: pc = PolyhedralComplex([Polyhedron(vertices=[[0], [1]])]) + sage: pc.is_mutable() + True + sage: pc.set_immutable() + sage: pc.is_mutable() + False + """ + self._is_immutable = True + + def is_mutable(self): + """ + Return whether ``self`` is mutable. + + EXAMPLES:: + + sage: pc1 = PolyhedralComplex([Polyhedron(vertices=[[0], [1]])]) + sage: pc1.is_mutable() + True + sage: pc2 = PolyhedralComplex([Polyhedron(vertices=[[0], [1]])], + ....: is_mutable=False) + sage: pc2.is_mutable() + False + sage: pc1 == pc2 + True + sage: pc3 = PolyhedralComplex([Polyhedron(vertices=[[0], [1]])], + ....: is_immutable=True) + sage: pc3.is_mutable() + False + sage: pc2 == pc3 + True + """ + return not self._is_immutable + + def is_immutable(self): + """ + Return whether ``self`` is immutable. + + EXAMPLES:: + + sage: pc1 = PolyhedralComplex([Polyhedron(vertices=[[0], [1]])]) + sage: pc1.is_immutable() + False + sage: pc2 = PolyhedralComplex([Polyhedron(vertices=[[0], [1]])], + ....: is_mutable=False) + sage: pc2.is_immutable() + True + sage: pc3 = PolyhedralComplex([Polyhedron(vertices=[[0], [1]])], + ....: is_immutable=True) + sage: pc3.is_immutable() + True + """ + return self._is_immutable + + def add_cell(self, cell): + """ + Add a cell to this polyhedral complex. + + INPUT: + + - ``cell`` -- a polyhedron + + This **changes** the polyhedral complex, by adding a new cell and all + of its subfaces. + + EXAMPLES: + + Set up an empty complex in the intended ambient space, then add a cell:: + + sage: pc = PolyhedralComplex(ambient_dim=2) + sage: pc.add_cell(Polyhedron(vertices=[(1, 2), (0, 2)])) + sage: pc + Polyhedral complex with 1 maximal cell + + If you add a cell which is already present, there is no effect:: + + sage: pc.add_cell(Polyhedron(vertices=[(1, 2)])) + sage: pc + Polyhedral complex with 1 maximal cell + sage: pc.dimension() + 1 + + Add a cell and check that dimension is correctly updated:: + + sage: pc.add_cell(Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)])) + sage: pc.dimension() + 2 + sage: pc.maximal_cells() + {2: {A 2-dimensional polyhedron in ZZ^2 defined as the convex hull of 3 vertices}} + sage: pc.is_convex() + True + + Add another cell and check that the properties are correctly updated:: + + sage: pc.add_cell(Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)])) + sage: pc + Polyhedral complex with 2 maximal cells + sage: len(pc._cells[1]) + 5 + sage: pc._face_poset + Finite poset containing 11 elements + sage: pc._is_convex + True + sage: pc._polyhedron.vertices_list() + [[0, 0], [0, 2], [1, 1], [1, 2]] + + Add a ray which makes the complex non convex:: + + sage: pc.add_cell(Polyhedron(rays=[(1, 0)])) + sage: pc + Polyhedral complex with 3 maximal cells + sage: len(pc._cells[1]) + 6 + sage: (pc._is_convex is False) and (pc._polyhedron is None) + True + + TESTS:: + + sage: pc.add_cell(Polyhedron(vertices=[[0]])) + Traceback (most recent call last): + ... + ValueError: the given cell is not a polyhedron in the same ambient space + sage: pc.add_cell(Polyhedron(vertices=[(1, 1), (0, 0), (2, 0)])) + Traceback (most recent call last): + ... + ValueError: the cell is not face-to-face with complex + sage: pc.set_immutable() + sage: pc.add_cell(Polyhedron(vertices=[(-1, -1)])) + Traceback (most recent call last): + ... + ValueError: this polyhedral complex is not mutable + """ + if self._is_immutable: + raise ValueError("this polyhedral complex is not mutable") + if not is_Polyhedron(cell) or cell.ambient_dim() != self._ambient_dim: + raise ValueError("the given cell is not a polyhedron " + + "in the same ambient space") + # if cell is already in self, do nothing. + if self.is_cell(cell): + return + if self._backend: + cell = cell.base_extend(cell.base_ring(), self._backend) + # update cells and face poset + cells = self.cells() + covers = {p: self.face_poset().upper_covers(p) + for p in self.cell_iterator()} + d = cell.dimension() + d_cells = [cell] + if d not in cells: + cells[d] = set(d_cells) + else: + cells[d].add(cell) + covers[cell] = [] + while d > 0: + d = d - 1 + new_facets = [] + for c in d_cells: + for facet in c.facets(): + p = facet.as_polyhedron() + if d not in cells: + cells[d] = set([]) + if p not in cells[d]: + cells[d].add(p) + covers[p] = [c] + new_facets.append(p) + else: + covers[p].append(c) + d_cells = new_facets + self._face_poset = poset = Poset(covers) + self._cells = cells + # check face-to-face between cell and previous maximal cells + for p in self.maximal_cell_iterator(): + r = p.intersection(cell) + if not (r.is_empty() or (r in poset) and + poset.is_gequal(p, r) and poset.is_gequal(cell, r)): + raise ValueError("the cell is not face-to-face with complex") + # update dim and maximal cells + d = cell.dimension() + if d > self._dim: + self._dim = d + maximal_cells = poset.maximal_elements() # a list + self._maximal_cells = cells_list_to_cells_dict(maximal_cells) + # update convexity if self was known to be convex, reset otherwise. + if self._is_convex: + try: + new_complex = PolyhedralComplex([self._polyhedron, cell], + face_to_face_check=True) + except ValueError: + self._is_convex = False + self._polyhedron = None + else: + self._is_convex = new_complex.is_convex() + self._polyhedron = new_complex._polyhedron + else: + self._is_convex = None + self._polyhedron = None + # reset cached attribute + self._maximal_cells_sorted = None # needed for hash + + def remove_cell(self, cell, check=False): + r""" + Remove ``cell`` from ``self`` and all the cells that contain ``cell`` + as a subface. + + INPUT: + + - ``cell`` -- a cell of the polyhedral complex + + - ``check`` -- boolean (default: ``False``); if ``True``, + raise an error if ``cell`` is not a cell of this complex + + This does not return anything; instead, it **changes** the + polyhedral complex. + + EXAMPLES: + + If you add a cell which is already present, there is no effect:: + + sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]) + sage: p2 = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)]) + sage: r = Polyhedron(rays=[(1, 0)]) + sage: pc = PolyhedralComplex([p1, p2, r]) + sage: pc.dimension() + 2 + sage: pc.remove_cell(Polyhedron(vertices=[(0, 0), (1, 2)])) + sage: pc.dimension() + 1 + sage: pc + Polyhedral complex with 5 maximal cells + sage: pc.remove_cell(Polyhedron(vertices=[(1, 2)])) + sage: pc.dimension() + 1 + sage: pc + Polyhedral complex with 3 maximal cells + sage: pc.remove_cell(Polyhedron(vertices=[(0, 0)])) + sage: pc.dimension() + 0 + + TESTS: + + Check that ValueError and empty complex are treated properly:: + + sage: p = Polyhedron(vertices=[[1]]) + sage: pc = PolyhedralComplex([p]) + sage: pc.remove_cell(Polyhedron(vertices=[[0]]), check=True) + Traceback (most recent call last): + ... + ValueError: trying to remove a cell which is not in the polyhedral complex + sage: pc.remove_cell(Polyhedron(vertices=[(1, 1)])) + Traceback (most recent call last): + ... + ValueError: the given cell is not a polyhedron in the same ambient space + sage: pc.remove_cell(p) + sage: pc.dimension() + -1 + sage: pc = PolyhedralComplex([Polyhedron(vertices=[[0]])], is_mutable=False) + sage: pc.remove_cell(Polyhedron(vertices=[[0]])) + Traceback (most recent call last): + ... + ValueError: this polyhedral complex is not mutable + + Check that this function is coherent with + :meth:`~sage.topology.simplicial_complex.SimplicialComplex.remove_face`:: + + sage: v1 = (1, 0, 0, 0); v2 = (0, 1, 0, 0); v3 = (0, 0, 1, 0); v4 = (0, 0, 0, 1) + sage: Z = PolyhedralComplex([Polyhedron(vertices=[v1, v2, v3, v4])]); Z + Polyhedral complex with 1 maximal cell + sage: Z.remove_cell(Polyhedron(vertices=[v1, v2])) + sage: Z + Polyhedral complex with 2 maximal cells + sage: [c.vertices_list() for c in Z.maximal_cells_sorted()] + [[[0, 0, 0, 1], [0, 0, 1, 0], [0, 1, 0, 0]], + [[0, 0, 0, 1], [0, 0, 1, 0], [1, 0, 0, 0]]] + + sage: v0 = (0, 0, 0, 0) + sage: S = PolyhedralComplex([Polyhedron(vertices=[v0, v1, v2]), Polyhedron(vertices=[v2, v3])]) + sage: S.maximal_cells() + {1: {A 1-dimensional polyhedron in ZZ^4 defined as the convex hull of 2 vertices}, + 2: {A 2-dimensional polyhedron in ZZ^4 defined as the convex hull of 3 vertices}} + sage: S.remove_cell(Polyhedron(vertices=[v0, v1, v2])) + sage: S + Polyhedral complex with 4 maximal cells + sage: [c.vertices_list() for c in S.maximal_cells_sorted()] + [[[0, 0, 0, 0], [0, 1, 0, 0]], + [[0, 0, 0, 0], [1, 0, 0, 0]], + [[0, 0, 1, 0], [0, 1, 0, 0]], + [[0, 1, 0, 0], [1, 0, 0, 0]]] + + sage: T = PolyhedralComplex([Polyhedron(vertices=[[1], [2]]), Polyhedron(vertices=[[1], [-3]])]) + sage: T.remove_cell(Polyhedron(vertices=[[-3], [1]])) + sage: [c.vertices_list() for c in T.maximal_cells_sorted()] + [[[1], [2]], [[-3]]] + sage: [c.vertices_list() for c in T.cells_sorted()] + [[[1], [2]], [[-3]], [[1]], [[2]]] + """ + if self._is_immutable: + raise ValueError("this polyhedral complex is not mutable") + if not is_Polyhedron(cell) or cell.ambient_dim() != self._ambient_dim: + raise ValueError("the given cell is not a polyhedron " + + "in the same ambient space") + # if cell is not in self, delete nothing. + if not self.is_cell(cell): # self.cells() is called + if check: + raise ValueError("trying to remove a cell which is not " + + "in the polyhedral complex") + return + # update cells and face poset + poset = self._face_poset + deleting = poset.order_filter([cell]) + for c in deleting: + d = c.dimension() + self._cells[d].remove(c) + if not self._cells[d]: + del self._cells[d] + covers = {p: [q for q in poset.upper_covers(p) if q not in deleting] + for p in self.cell_iterator()} + self._face_poset = Poset(covers) + # update dim and maximal cells + maximal_cells = self._face_poset.maximal_elements() # a list + self._maximal_cells = cells_list_to_cells_dict(maximal_cells) + if not maximal_cells: + self._dim = -1 + else: + self._dim = max(self._maximal_cells.keys()) + # reset cached attributes + self._maximal_cells_sorted = None # needed for hash + self._is_convex = None + self._polyhedron = None + + def is_simplicial_complex(self): + """ + Test if this polyhedral complex is a simplicial complex. + + A polyhedral complex is **simplicial** if all of its (maximal) cells + are simplices, i.e., every cell is a bounded polytope with `d+1` + vertices, where `d` is the dimension of the polytope. + + EXAMPLES:: + + sage: p1 = Polyhedron(vertices=[(0, 0), (1, 1), (1, 2)]) + sage: p2 = Polyhedron(rays=[(1, 0)]) + sage: PolyhedralComplex([p1]).is_simplicial_complex() + True + sage: PolyhedralComplex([p2]).is_simplicial_complex() + False + """ + return all(p.is_simplex() for p in self.maximal_cell_iterator()) + + def is_polyhedral_fan(self): + """ + Test if this polyhedral complex is a polyhedral fan. + + A polyhedral complex is a **fan** if all of its (maximal) cells + are cones. + + EXAMPLES:: + + sage: p1 = Polyhedron(vertices=[(0, 0), (1, 1), (1, 2)]) + sage: p2 = Polyhedron(rays=[(1, 0)]) + sage: PolyhedralComplex([p1]).is_polyhedral_fan() + False + sage: PolyhedralComplex([p2]).is_polyhedral_fan() + True + sage: halfplane = Polyhedron(rays=[(1, 0), (-1, 0), (0, 1)]) + sage: PolyhedralComplex([halfplane]).is_polyhedral_fan() + True + """ + return all((p.n_vertices() == 1) and ( + vector(p.vertices_list()[0]) == p.ambient_space().zero()) + for p in self.maximal_cell_iterator()) + + def is_simplicial_fan(self): + """ + Test if this polyhedral complex is a simplicial fan. + + A polyhedral complex is a **simplicial fan** if all of its (maximal) + cells are simplical cones, i.e., every cell is a pointed cone (with + vertex being the origin) generated by `d` linearly independent rays, + where `d` is the dimension of the cone. + + EXAMPLES:: + + sage: p1 = Polyhedron(vertices=[(0, 0), (1, 1), (1, 2)]) + sage: p2 = Polyhedron(rays=[(1, 0)]) + sage: PolyhedralComplex([p1]).is_simplicial_fan() + False + sage: PolyhedralComplex([p2]).is_simplicial_fan() + True + sage: halfplane = Polyhedron(rays=[(1, 0), (-1, 0), (0, 1)]) + sage: PolyhedralComplex([halfplane]).is_simplicial_fan() + False + """ + return self.is_polyhedral_fan() and all( + (p.n_lines() == 0 and p.n_rays() == p.dimension()) + for p in self.maximal_cell_iterator()) + + def subdivide(self, make_simplicial=False, + new_vertices=None, new_rays=None): + """ + Construct a new polyhedral complex by iterative stellar subdivision of + ``self`` for each new vertex/ray given. + + Currently, subdivision is only supported for bounded polyhedral complex + or polyhedral fan. + + INPUT: + + - ``make_simplicial`` -- boolean (default: ``False``); if ``True``, + the returned polyhedral complex is simplicial + + - ``new_vertices``, ``new_rays`` -- list (optional); new generators + to be added during subdivision + + EXAMPLES:: + + sage: square_vertices = [(1, 1, 1), (-1, 1, 1), (-1, -1, 1), (1, -1, 1)] + sage: pc = PolyhedralComplex([ + ....: Polyhedron(vertices=[(0, 0, 0)] + square_vertices), + ....: Polyhedron(vertices=[(0, 0, 2)] + square_vertices)]) + sage: pc.is_compact() and not pc.is_simplicial_complex() + True + sage: subdivided_pc = pc.subdivide(new_vertices=[(0, 0, 1)]) + sage: subdivided_pc + Polyhedral complex with 8 maximal cells + sage: subdivided_pc.is_simplicial_complex() + True + sage: simplicial_pc = pc.subdivide(make_simplicial=True) + sage: simplicial_pc + Polyhedral complex with 4 maximal cells + sage: simplicial_pc.is_simplicial_complex() + True + + sage: fan = PolyhedralComplex([Polyhedron(rays=square_vertices)]) + sage: fan.is_polyhedral_fan() and not fan.is_simplicial_fan() + True + sage: fan.subdivide(new_vertices=[(0, 0, 1)]) + Traceback (most recent call last): + ... + ValueError: new vertices cannot be used for subdivision + sage: subdivided_fan = fan.subdivide(new_rays=[(0, 0, 1)]) + sage: subdivided_fan + Polyhedral complex with 4 maximal cells + sage: subdivided_fan.is_simplicial_fan() + True + sage: simplicial_fan = fan.subdivide(make_simplicial=True) + sage: simplicial_fan + Polyhedral complex with 2 maximal cells + sage: simplicial_fan.is_simplicial_fan() + True + + sage: halfspace = PolyhedralComplex([Polyhedron(rays=[(0, 0, 1)], + ....: lines=[(1, 0, 0), (0, 1, 0)])]) + sage: halfspace.is_simplicial_fan() + False + sage: subdiv_halfspace = halfspace.subdivide(make_simplicial=True) + sage: subdiv_halfspace + Polyhedral complex with 4 maximal cells + sage: subdiv_halfspace.is_simplicial_fan() + True + """ + if self.is_compact(): + if new_rays: + raise ValueError("rays/lines cannot be used for subdivision") + # bounded version of `fan.subdivide`; not require rational. + vertices = set([]) + if make_simplicial and not self.is_simplicial_complex(): + for p in self.maximal_cell_iterator(): + for v in p.vertices_list(): + vertices.add(tuple(v)) + if new_vertices: + for v in new_vertices: + vertices.add(tuple(v)) + if not vertices: + return self # Nothing has to be done + # bounded version of `fan._subdivide_stellar`; not require rational. + cells = list(self.maximal_cell_iterator()) + for v in vertices: + new = [] + for cell in cells: + if v in cell: + for cell_facet in cell.facets(): + facet = cell_facet.as_polyhedron() + if v in facet: + continue + p = facet.convex_hull(Polyhedron(vertices=[v])) + new.append(p) + else: + new.append(cell) + cells = new + return PolyhedralComplex(cells, maximality_check=False, + backend=self._backend) + elif self.is_polyhedral_fan(): + if new_vertices and any(vi != 0 for v in new_vertices for vi in v): + raise ValueError("new vertices cannot be used for subdivision") + # mimic :meth:`~sage.geometry.fan .subdivide` + # but here we allow for non-pointed cones, and we subdivide them. + rays_normalized = set([]) + self_rays = [] + cones = [] + for p in self.maximal_cell_iterator(): + prays = p.rays_list() + for r in prays: + r_n = vector(r).normalized() + r_n.set_immutable() + if r_n not in rays_normalized: + rays_normalized.add(r_n) + self_rays.append(vector(r)) + plines = p.lines_list() + if not plines: + cones.append(p) + continue + # consider a line as two rays + for pl in plines: + l_plus = vector(pl).normalized() + l_plus.set_immutable() + if l_plus not in rays_normalized: + rays_normalized.add(l_plus) + self_rays.append(vector(pl)) + l_minus = (-vector(pl)).normalized() + l_minus.set_immutable() + if l_minus not in rays_normalized: + rays_normalized.add(l_minus) + self_rays.append(-vector(pl)) + # subdivide the non-pointed p into pointed cones + # we rely on the canonical V-repr of Sage polyhedra. + num_lines = len(plines) + for neg_rays in powerset(range(num_lines)): + lines = [vector(plines[i]) if i not in neg_rays + else -vector(plines[i]) for i in range(num_lines)] + cones.append(Polyhedron(rays=(prays + lines), + backend=self._backend)) + rays = [] + if new_rays: + for r in new_rays: + if vector(r).is_zero(): + raise ValueError("zero cannot be used for subdivision") + r_n = vector(r).normalized() + r_n.set_immutable() + if r_n not in rays_normalized: + rays_normalized.add(r_n) + rays.append(vector(r)) + if make_simplicial and not self.is_simplicial_fan(): + rays = self_rays + rays + if not rays: + return self # Nothing has to be done + # mimic :class:`RationalPolyhedralFan`._subdivide_stellar(rays) + # start with self maximal cells (subdivided into pointed cones) + for ray in rays: + new = [] + for cone in cones: + if ray in cone: + for cone_facet in cone.facets(): + facet = cone_facet.as_polyhedron() + if ray in facet: + continue + new_cone = facet.convex_hull(Polyhedron(rays=[ray])) + new.append(new_cone) + else: + new.append(cone) + cones = new + return PolyhedralComplex(cones, maximality_check=False, + backend=self._backend) + else: + # TODO: `self`` is unbounded, make it projectively simplicial. + # (1) homogenize self of dim d to fan in space of dim d+1; + # (2) call fan.subdivide(make_simplicial=True); + # (3) take section back to the space of dim d. + raise NotImplementedError('subdivision of a non-compact polyhedral ' + + 'complex that is not a fan is not supported') + +############################################################ +# Helper functions +############################################################ + + +def cells_list_to_cells_dict(cells_list): + r""" + Helper function that returns the dictionary whose keys are the dimensions, + and the value associated to an integer `d` is the set of `d`-dimensional + polyhedra in the given list. + + EXAMPLES:: + + sage: p1 = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]) + sage: p2 = Polyhedron(vertices=[(1, 1), (0, 0)]) + sage: p3 = Polyhedron(vertices=[(0, 0)]) + sage: p4 = Polyhedron(vertices=[(1, 1)]) + sage: sage.geometry.polyhedral_complex.cells_list_to_cells_dict([p1, p2, p3, p4]) + {0: {A 0-dimensional polyhedron in ZZ^2 defined as the convex hull of 1 vertex, + A 0-dimensional polyhedron in ZZ^2 defined as the convex hull of 1 vertex}, + 1: {A 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices}, + 2: {A 2-dimensional polyhedron in ZZ^2 defined as the convex hull of 3 vertices}} + """ + cells_dict = {} + for cell in cells_list: + d = cell.dimension() + if d in cells_dict: + cells_dict[d].add(cell) + else: + cells_dict[d] = set([cell]) + return cells_dict + diff --git a/src/sage/geometry/polyhedron/backend_polymake.py b/src/sage/geometry/polyhedron/backend_polymake.py index 964418d70aa..6f595395153 100644 --- a/src/sage/geometry/polyhedron/backend_polymake.py +++ b/src/sage/geometry/polyhedron/backend_polymake.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +# -*- coding: utf-8 -*- """ The polymake backend for polyhedral computations @@ -22,6 +22,7 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +import itertools from sage.structure.element import Element @@ -57,7 +58,7 @@ class Polyhedron_polymake(Polyhedron_base): sage: p = Polyhedron(vertices=[(0,0),(1,0),(0,1)], rays=[(1,1)], # optional - polymake ....: lines=[], backend='polymake') - sage: TestSuite(p).run(skip='_test_pickling') # optional - polymake + sage: TestSuite(p).run() # optional - polymake A lower-dimensional affine cone; we test that there are no mysterious inequalities coming in from the homogenization:: @@ -97,7 +98,7 @@ class Polyhedron_polymake(Polyhedron_base): sage: V = polytopes.dodecahedron().vertices_list() sage: Polyhedron(vertices=V, backend='polymake') # optional - polymake - A 3-dimensional polyhedron in (Number Field in sqrt5 with defining polynomial x^2 - 5)^3 defined as the convex hull of 20 vertices + A 3-dimensional polyhedron in (Number Field in sqrt5 with defining polynomial x^2 - 5 with sqrt5 = 2.236067977499790?)^3 defined as the convex hull of 20 vertices TESTS: @@ -207,17 +208,29 @@ def __init__(self, parent, Vrep, Hrep, polymake_polytope=None, **kwds): TESTS: - We skip the pickling test because pickling is currently - not implemented:: - sage: p = Polyhedron(backend='polymake') # optional - polymake - sage: TestSuite(p).run(skip="_test_pickling") # optional - polymake + sage: TestSuite(p).run() # optional - polymake sage: p = Polyhedron(vertices=[(1, 1)], rays=[(0, 1)], # optional - polymake ....: backend='polymake') - sage: TestSuite(p).run(skip="_test_pickling") # optional - polymake + sage: TestSuite(p).run() # optional - polymake + + We skip the Lawrence test because it involves numerically unstable + floating point arithmetic:: + sage: p = Polyhedron(vertices=[(-1,-1), (1,0), (1,1), (0,1)], # optional - polymake ....: backend='polymake') - sage: TestSuite(p).run(skip="_test_pickling") # optional - polymake + sage: TestSuite(p).run(skip='_test_lawrence') # optional - polymake + + :: + + sage: p = Polyhedron(rays=[[1,1]], backend='polymake') # optional - polymake + sage: TestSuite(p).run() # optional - polymake + sage: p = Polyhedron(rays=[[1]], backend='polymake') # optional - polymake + sage: TestSuite(p).run() # optional - polymake + sage: p = Polyhedron(rays=[[1,1,1]], lines=[[1,0,0]], backend='polymake') # optional - polymake + sage: TestSuite(p).run() # optional - polymake + sage: p = Polyhedron(vertices=[[]], backend='polymake') # optional - polymake + sage: TestSuite(p).run() # optional - polymake """ if polymake_polytope is not None: if Hrep is not None or Vrep is not None: @@ -247,7 +260,7 @@ def _init_from_Vrepresentation(self, vertices, rays, lines, minimize=True, verbo INPUT: - - ``vertices`` -- list of point; each point can be specified + - ``vertices`` -- list of points; each point can be specified as any iterable container of :meth:`~sage.geometry.polyhedron.base.base_ring` elements @@ -269,14 +282,47 @@ def _init_from_Vrepresentation(self, vertices, rays, lines, minimize=True, verbo sage: Polyhedron_polymake._init_from_Vrepresentation(p, [], [], []) # optional - polymake """ from sage.interfaces.polymake import polymake + data = self._polymake_Vrepresentation_data(vertices, rays, lines) polymake_field = polymake(self.base_ring().fraction_field()) - p = polymake.new_object("Polytope<{}>".format(polymake_field), - CONE_AMBIENT_DIM=1+self.parent().ambient_dim(), - POINTS= [ [1] + v for v in vertices ] \ - + [ [0] + r for r in rays ], - INPUT_LINEALITY=[ [0] + l for l in lines ]) + p = polymake.new_object("Polytope<{}>".format(polymake_field), **data) self._init_from_polymake_polytope(p) + def _polymake_Vrepresentation_data(self, vertices, rays, lines, minimal=False): + r""" + Compute a dictionary with V-representation input for a polymake Polytope object. + + INPUT: + + - ``vertices`` -- list of points; each point can be specified + as any iterable container of + :meth:`~sage.geometry.polyhedron.base.base_ring` elements + + - ``rays`` -- list of rays; each ray can be specified as any + iterable container of + :meth:`~sage.geometry.polyhedron.base.base_ring` elements + + - ``lines`` -- list of lines; each line can be specified as + any iterable container of + :meth:`~sage.geometry.polyhedron.base.base_ring` elements + + - ``minimal`` -- boolean (default: ``False``); whether the input is already minimal + + .. WARNING:: + + If ``minimal`` the representation is assumed to be correct. + It is not checked. + """ + if not minimal: + return dict(CONE_AMBIENT_DIM=1+self.parent().ambient_dim(), + POINTS=( [ [1] + list(v) for v in vertices ] + + [ [0] + list(r) for r in rays ]), + INPUT_LINEALITY=[ [0] + list(l) for l in lines ]) + else: + return dict(CONE_AMBIENT_DIM=1+self.parent().ambient_dim(), + VERTICES=( [ [1] + list(v) for v in vertices ] + + [ [0] + list(r) for r in rays ]), + LINEALITY_SPACE=[ [0] + list(l) for l in lines ]) + def _init_from_Hrepresentation(self, ieqs, eqns, minimize=True, verbose=False): r""" Construct polyhedron from H-representation data. @@ -303,26 +349,129 @@ def _init_from_Hrepresentation(self, ieqs, eqns, minimize=True, verbose=False): sage: Polyhedron_polymake._init_from_Hrepresentation(p, [], []) # optional - polymake """ from sage.interfaces.polymake import polymake - if ieqs is None: ieqs = [] - if eqns is None: eqns = [] + data = self._polymake_Hrepresentation_data(ieqs, eqns) + polymake_field = polymake(self.base_ring().fraction_field()) + p = polymake.new_object("Polytope<{}>".format(polymake_field), **data) + self._init_from_polymake_polytope(p) + + def _polymake_Hrepresentation_data(self, ieqs, eqns, minimal=False): + r""" + Compute a dictionary with H-representation input for a polymake Polytope object. + + INPUT: + + - ``ieqs`` -- list of inequalities; each line can be specified + as any iterable container of + :meth:`~sage.geometry.polyhedron.base.base_ring` elements + + - ``eqns`` -- list of equalities; each line can be specified + as any iterable container of + :meth:`~sage.geometry.polyhedron.base.base_ring` elements + + - ``minimal`` -- boolean (default: ``False``); whether the input is already minimal + + .. WARNING:: + + If ``minimal`` the representation is assumed to be correct. + It is not checked. + """ + if ieqs is None: + ieqs = [] + if eqns is None: + eqns = [] # Polymake 3.0r2 and 3.1 crash with a segfault for a test case # using QuadraticExtension, when some all-zero inequalities are input. # https://forum.polymake.org/viewtopic.php?f=8&t=547 # Filter them out. - ieqs = [ v for v in ieqs if not all(self._is_zero(x) for x in v) ] + ieqs = [ list(v) for v in ieqs if not all(self._is_zero(x) for x in v) ] # We do a similar filtering for equations. # Since Polymake 3.2, we can not give all zero vectors in equations - eqns = [ v for v in eqns if not all(self._is_zero(x) for x in v) ] + eqns = [ list(v) for v in eqns if not all(self._is_zero(x) for x in v) ] if not ieqs: # Put in one trivial (all-zero) inequality. This is so that # the ambient dimension is set correctly. # Since Polymake 3.2, the constant should not be zero. ieqs.append([1] + [0]*self.ambient_dim()) + if not minimal: + return dict(EQUATIONS=eqns, + INEQUALITIES=ieqs) + else: + return dict(AFFINE_HULL=eqns, + FACETS=ieqs) + + def _init_from_Vrepresentation_and_Hrepresentation(self, Vrep, Hrep): + """ + Construct polyhedron from V-representation and H-representation data. + + See :class:`Polyhedron_base` for a description of ``Vrep`` and ``Hrep``. + + .. WARNING:: + + The representation is assumed to be correct. + It is not checked. + + EXAMPLES:: + + sage: from sage.geometry.polyhedron.parent import Polyhedra_polymake + sage: from sage.geometry.polyhedron.backend_polymake import Polyhedron_polymake + sage: parent = Polyhedra_polymake(ZZ, 1, 'polymake') + sage: Vrep = [[[0], [1]], [], []] + sage: Hrep = [[[0, 1], [1, -1]], []] + sage: p = Polyhedron_polymake(parent, Vrep, Hrep, # indirect doctest # optional - polymake + ....: Vrep_minimal=True, Hrep_minimal=True) + sage: p # optional - polymake + A 1-dimensional polyhedron in ZZ^1 defined as the convex hull of 2 vertices + """ + Vrep = [list(x) for x in Vrep] + Hrep = [list(x) for x in Hrep] + p = self._polymake_polytope_from_Vrepresentation_and_Hrepresentation(Vrep, Hrep) + if p is None: + self._init_empty_polyhedron() + return + + self._polymake_polytope = p + + # As the conversion from polymake to sage is slow, + # we skip it. + parent = self.parent() + vertices, rays, lines = Vrep + inequalities, equations = Hrep + self._Vrepresentation = [] + self._Hrepresentation = [] + for x in vertices: + parent._make_Vertex(self, x) + for x in rays: + parent._make_Ray(self, x) + for x in lines: + parent._make_Line(self, x) + for x in inequalities: + parent._make_Inequality(self, x) + for x in equations: + parent._make_Equation(self, x) + self._Vrepresentation = tuple(self._Vrepresentation) + self._Hrepresentation = tuple(self._Hrepresentation) + + def _polymake_polytope_from_Vrepresentation_and_Hrepresentation(self, Vrep, Hrep): + if not any(Vrep): + # The empty polyhedron. + return + + from sage.interfaces.polymake import polymake + data = self._polymake_Vrepresentation_data(*Vrep, minimal=True) + + if any(Vrep[1:]): + from sage.matrix.constructor import Matrix + polymake_rays = [r for r in data['VERTICES'] if r[0] == 0] + if Matrix(data['VERTICES']).rank() == Matrix(polymake_rays).rank() + 1: + # The recession cone is full-dimensional. + # In this case the homogenized inequalities + # do not ensure nonnegativy in the last coordinate. + # In the homogeneous cone the far face is a facet. + Hrep[0] += [[1] + [0]*self.ambient_dim()] + data.update(self._polymake_Hrepresentation_data(*Hrep, minimal=True)) + polymake_field = polymake(self.base_ring().fraction_field()) - p = polymake.new_object("Polytope<{}>".format(polymake_field), - EQUATIONS=eqns, - INEQUALITIES=ieqs) - self._init_from_polymake_polytope(p) + return polymake.new_object("Polytope<{}>".format(polymake_field), **data) def _init_Vrepresentation_from_polymake(self): r""" @@ -343,8 +492,7 @@ def _init_Vrepresentation_from_polymake(self): self._Vrepresentation = [] parent = self.parent() p = self._polymake_polytope - for g in p.VERTICES: - g = g.sage() + for g in p.VERTICES.sage(): d = g[0] if d == 0: parent._make_Ray(self, g[1:]) @@ -352,8 +500,7 @@ def _init_Vrepresentation_from_polymake(self): parent._make_Vertex(self, g[1:]) else: raise NotImplementedError("Non-normalized vertex encountered: {}".format(g)) - for g in p.LINEALITY_SPACE: - g = g.sage() + for g in p.LINEALITY_SPACE.sage(): d = g[0] if d == 0: parent._make_Line(self, g[1:]) @@ -383,14 +530,14 @@ def _init_Hrepresentation_from_polymake(self): else: self._Hrepresentation = [] parent = self.parent() - for g in p.FACETS: + for g in p.FACETS.sage(): if all(x==0 for x in g[1:]): # Ignore vertical inequality pass else: - parent._make_Inequality(self, g.sage()) - for g in p.AFFINE_HULL: - parent._make_Equation(self, g.sage()) + parent._make_Inequality(self, g) + for g in p.AFFINE_HULL.sage(): + parent._make_Equation(self, g) self._Hrepresentation = tuple(self._Hrepresentation) @classmethod @@ -407,6 +554,25 @@ def _from_polymake_polytope(cls, parent, polymake_polytope): sage: parent = Polyhedra(QQ, 2, backend='polymake') # optional - polymake sage: Q=Polyhedron_polymake._from_polymake_polytope(parent, PP) # optional - polymake """ + if parent is None: + from .parent import Polyhedra + from sage.rings.rational_field import QQ + from sage.rings.qqbar import AA + if polymake_polytope.typeof()[0] == 'Polymake::polytope::Polytope__Rational': + base_ring = QQ + else: + from sage.structure.element import coercion_model + data = [g.sage() + for g in itertools.chain(polymake_polytope.VERTICES, + polymake_polytope.LINEALITY_SPACE, + polymake_polytope.FACETS, + polymake_polytope.AFFINE_HULL)] + if data: + base_ring = coercion_model.common_parent(*data).base_ring() + else: + base_ring = QQ + ambient_dim = polymake_polytope.AMBIENT_DIM().sage() + parent = Polyhedra(base_ring=base_ring, ambient_dim=ambient_dim, backend='polymake') return cls(parent, None, None, polymake_polytope=polymake_polytope) def _polymake_(self, polymake): @@ -426,6 +592,138 @@ def _polymake_(self, polymake): else: return super(Polyhedron_polymake, self)._polymake_(polymake) + def __getstate__(self): + r""" + Remove the polymake polytope object for pickling. + + TESTS:: + + sage: P = polytopes.simplex(backend='polymake') # optional - polymake + sage: P.__getstate__() # optional - polymake + (Polyhedra in QQ^4, + {'_Hrepresentation': (An inequality (0, -1, -1, -1) x + 1 >= 0, + An inequality (0, 1, 0, 0) x + 0 >= 0, + An inequality (0, 0, 1, 0) x + 0 >= 0, + An inequality (0, 0, 0, 1) x + 0 >= 0, + An equation (1, 1, 1, 1) x - 1 == 0), + '_Vrepresentation': (A vertex at (1, 0, 0, 0), + A vertex at (0, 1, 0, 0), + A vertex at (0, 0, 1, 0), + A vertex at (0, 0, 0, 1)), + '_pickle_equations': [(-1, 1, 1, 1, 1)], + '_pickle_inequalities': [(1, 0, -1, -1, -1), + (0, 0, 1, 0, 0), + (0, 0, 0, 1, 0), + (0, 0, 0, 0, 1)], + '_pickle_lines': [], + '_pickle_rays': [], + '_pickle_vertices': [(1, 0, 0, 0), + (0, 1, 0, 0), + (0, 0, 1, 0), + (0, 0, 0, 1)]}) + """ + state = super().__getstate__() + state = (state[0], state[1].copy()) + # Remove the unpicklable entries. + if '_polymake_polytope' in state[1]: + del state[1]['_polymake_polytope'] + state[1]["_pickle_vertices"] = [v._vector for v in self.vertices()] + state[1]["_pickle_rays"] = [v._vector for v in self.rays()] + state[1]["_pickle_lines"] = [v._vector for v in self.lines()] + state[1]["_pickle_inequalities"] = [v._vector for v in self.inequalities()] + state[1]["_pickle_equations"] = [v._vector for v in self.equations()] + return state + + def __setstate__(self, state): + r""" + Initialize the polymake polytope object after pickling. + + TESTS: + + Test that the obtained cone is valid:: + + sage: from sage.geometry.polyhedron.backend_polymake import Polyhedron_polymake # optional - polymake + sage: P = polytopes.permutahedron(4, backend='polymake') # optional - polymake + sage: P1 = loads(dumps(P)) # optional - polymake + sage: P2 = Polyhedron_polymake(P1.parent(), None, None, P1._polymake_polytope) # optional - polymake + sage: P._test_polymake_pickling(other=P2) # optional - polymake + + sage: P = Polyhedron(lines=[[1,0], [0,1]], backend='polymake') # optional - polymake + sage: P1 = loads(dumps(P)) # optional - polymake + sage: P2 = Polyhedron_polymake(P1.parent(), None, None, P1._polymake_polytope) # optional - polymake + sage: P._test_polymake_pickling(other=P2) # optional - polymake + + sage: P = Polyhedron(backend='polymake') # optional - polymake + sage: P1 = loads(dumps(P)) # optional - polymake + sage: P._test_polymake_pickling(other=P1) # optional - polymake + + sage: P = polytopes.permutahedron(4, backend='polymake') * Polyhedron(lines=[[1]], backend='polymake') # optional - polymake + sage: P1 = loads(dumps(P)) # optional - polymake + sage: P2 = Polyhedron_polymake(P1.parent(), None, None, P1._polymake_polytope) # optional - polymake + sage: P._test_polymake_pickling(other=P2) # optional - polymake + + sage: print("Possible output"); P = polytopes.dodecahedron(backend='polymake') # optional - polymake + Possible output... + sage: P1 = loads(dumps(P)) # optional - polymake + sage: P2 = Polyhedron_polymake(P1.parent(), None, None, P1._polymake_polytope) # optional - polymake + sage: P._test_polymake_pickling(other=P2) # optional - polymake + """ + if "_pickle_vertices" in state[1]: + vertices = state[1].pop("_pickle_vertices") + rays = state[1].pop("_pickle_rays") + lines = state[1].pop("_pickle_lines") + inequalities = state[1].pop("_pickle_inequalities") + equations = state[1].pop("_pickle_equations") + else: + vertices = None + + super().__setstate__(state) + + if vertices is None: + vertices = self.vertices() + rays = self.rays() + lines = self.lines() + inequalities = self.inequalities() + equations = self.equations() + + + p = self._polymake_polytope_from_Vrepresentation_and_Hrepresentation([vertices, rays, lines], [inequalities, equations]) + if p is not None: + self._polymake_polytope = p + + def _test_polymake_pickling(self, tester=None, other=None, **options): + """ + Run tests to see that our polymake pickling/unpickling works. + + INPUT: + + - ``other`` -- a pickling polytope of ``self`` to be tested against + + TESTS:: + + sage: polytopes.cross_polytope(3, backend='polymake')._test_polymake_pickling() # optional - polymake + """ + if tester is None: + tester = self._tester(**options) + + if other is None: + from sage.misc.persist import loads, dumps + other = loads(dumps(self)) + + tester.assertEqual(self, other) + + if not hasattr(self, '_polymake_polytope'): + tester.assertFalse(hasattr(other, '_polymake_polytope')) + return + + P = self._polymake_polytope + P1 = other._polymake_polytope + + tester.assertEqual(P.F_VECTOR, P1.F_VECTOR) + tester.assertEqual(P.VERTICES, P1.VERTICES) + tester.assertEqual(P.LINEALITY_SPACE, P1.LINEALITY_SPACE) + tester.assertEqual(P.FACETS, P1.FACETS) + tester.assertEqual(P.AFFINE_HULL, P1.AFFINE_HULL) ######################################################################### class Polyhedron_QQ_polymake(Polyhedron_polymake, Polyhedron_QQ): @@ -442,7 +740,7 @@ class Polyhedron_QQ_polymake(Polyhedron_polymake, Polyhedron_QQ): sage: p = Polyhedron(vertices=[(0,0),(1,0),(0,1)], # optional - polymake ....: rays=[(1,1)], lines=[], ....: backend='polymake', base_ring=QQ) - sage: TestSuite(p).run(skip='_test_pickling') # optional - polymake + sage: TestSuite(p).run() # optional - polymake """ pass @@ -462,7 +760,7 @@ class Polyhedron_ZZ_polymake(Polyhedron_polymake, Polyhedron_ZZ): sage: p = Polyhedron(vertices=[(0,0),(1,0),(0,1)], # optional - polymake ....: rays=[(1,1)], lines=[], ....: backend='polymake', base_ring=ZZ) - sage: TestSuite(p).run(skip='_test_pickling') # optional - polymake + sage: TestSuite(p).run() # optional - polymake """ pass diff --git a/src/sage/geometry/polyhedron/backend_ppl.py b/src/sage/geometry/polyhedron/backend_ppl.py index 52d0f04a851..c6b1e9e05e8 100644 --- a/src/sage/geometry/polyhedron/backend_ppl.py +++ b/src/sage/geometry/polyhedron/backend_ppl.py @@ -2,11 +2,12 @@ The PPL (Parma Polyhedra Library) backend for polyhedral computations """ +from sage.structure.element import Element from sage.rings.all import ZZ from sage.rings.integer import Integer from sage.arith.functions import LCM_list from sage.misc.functional import denominator -from .base import Polyhedron_base +from .base_mutable import Polyhedron_mutable from .base_QQ import Polyhedron_QQ from .base_ZZ import Polyhedron_ZZ @@ -18,7 +19,7 @@ ######################################################################### -class Polyhedron_ppl(Polyhedron_base): +class Polyhedron_ppl(Polyhedron_mutable): """ Polyhedra with ppl @@ -34,6 +35,39 @@ class Polyhedron_ppl(Polyhedron_base): sage: TestSuite(p).run() """ + _backend_object_name = "ppl_polyhedron" + _is_mutable = True + + def __init__(self, parent, Vrep, Hrep, ppl_polyhedron=None, mutable=False, **kwds): + """ + Initializes the polyhedron. + + See :class:`Polyhedron_ppl` for a description of the input + data. + + TESTS:: + + sage: p = Polyhedron() + sage: TestSuite(p).run() + sage: p = Polyhedron(vertices=[(1, 1)], rays=[(0, 1)]) + sage: TestSuite(p).run() + sage: q = polytopes.cube() + sage: p = q.parent().element_class(q.parent(), None, None, q._ppl_polyhedron) + sage: TestSuite(p).run() + """ + # This is important. For some reason the element constructor copies the list sometimes. + self._dependent_objects = [] + if ppl_polyhedron: + if Hrep is not None or Vrep is not None: + raise ValueError("only one of Vrep, Hrep, or ppl_polyhedron can be different from None") + Element.__init__(self, parent=parent) + minimize = True if 'minimize' in kwds and kwds['minimize'] else False + self._init_from_ppl_polyhedron(ppl_polyhedron, minimize) + else: + Polyhedron_mutable.__init__(self, parent, Vrep, Hrep, **kwds) + if not mutable: + self.set_immutable() + def _init_from_Vrepresentation(self, vertices, rays, lines, minimize=True, verbose=False): """ Construct polyhedron from V-representation data. @@ -64,34 +98,18 @@ def _init_from_Vrepresentation(self, vertices, rays, lines, minimize=True, verbo gs = Generator_System() if vertices is None: vertices = [] for v in vertices: - d = LCM_list([denominator(v_i) for v_i in v]) - if d.is_one(): - gs.insert(point(Linear_Expression(v, 0))) - else: - dv = [ d*v_i for v_i in v ] - gs.insert(point(Linear_Expression(dv, 0), d)) + gs.insert(self._convert_generator_to_ppl(v, 2)) if rays is None: rays = [] for r in rays: - d = LCM_list([denominator(r_i) for r_i in r]) - if d.is_one(): - gs.insert(ray(Linear_Expression(r, 0))) - else: - dr = [ d*r_i for r_i in r ] - gs.insert(ray(Linear_Expression(dr, 0))) + gs.insert(self._convert_generator_to_ppl(r, 3)) if lines is None: lines = [] for l in lines: - d = LCM_list([denominator(l_i) for l_i in l]) - if d.is_one(): - gs.insert(line(Linear_Expression(l, 0))) - else: - dl = [ d*l_i for l_i in l ] - gs.insert(line(Linear_Expression(dl, 0))) + gs.insert(self._convert_generator_to_ppl(l, 4)) if gs.empty(): - self._ppl_polyhedron = C_Polyhedron(self.ambient_dim(), 'empty') + ppl_polyhedron = C_Polyhedron(self.ambient_dim(), 'empty') else: - self._ppl_polyhedron = C_Polyhedron(gs) - self._init_Vrepresentation_from_ppl(minimize) - self._init_Hrepresentation_from_ppl(minimize) + ppl_polyhedron = C_Polyhedron(gs) + self._init_from_ppl_polyhedron(ppl_polyhedron, minimize) def _init_from_Hrepresentation(self, ieqs, eqns, minimize=True, verbose=False): """ @@ -119,24 +137,92 @@ def _init_from_Hrepresentation(self, ieqs, eqns, minimize=True, verbose=False): cs = Constraint_System() if ieqs is None: ieqs = [] for ieq in ieqs: - d = LCM_list([denominator(ieq_i) for ieq_i in ieq]) - dieq = [ ZZ(d*ieq_i) for ieq_i in ieq ] - b = dieq[0] - A = dieq[1:] - cs.insert(Linear_Expression(A, b) >= 0) + cs.insert(self._convert_constraint_to_ppl(ieq, 0)) if eqns is None: eqns = [] for eqn in eqns: - d = LCM_list([denominator(eqn_i) for eqn_i in eqn]) - deqn = [ ZZ(d*eqn_i) for eqn_i in eqn ] - b = deqn[0] - A = deqn[1:] - cs.insert(Linear_Expression(A, b) == 0) + cs.insert(self._convert_constraint_to_ppl(eqn, 1)) if cs.empty(): - self._ppl_polyhedron = C_Polyhedron(self.ambient_dim(), 'universe') + ppl_polyhedron = C_Polyhedron(self.ambient_dim(), 'universe') else: - self._ppl_polyhedron = C_Polyhedron(cs) - self._init_Vrepresentation_from_ppl(minimize) - self._init_Hrepresentation_from_ppl(minimize) + ppl_polyhedron = C_Polyhedron(cs) + self._init_from_ppl_polyhedron(ppl_polyhedron, minimize) + + def _init_from_ppl_polyhedron(self, ppl_polyhedron, minimize=True): + """ + Create the V-/Hrepresentation objects from the ppl polyhedron. + + TESTS:: + + sage: p = Polyhedron(backend='ppl') + sage: from sage.geometry.polyhedron.backend_ppl import Polyhedron_ppl + sage: Polyhedron_ppl._init_from_Hrepresentation(p, [], []) # indirect doctest + """ + self._ppl_polyhedron = ppl_polyhedron + + def set_immutable(self): + r""" + Make this polyhedron immutable. This operation cannot be undone. + + EXAMPLES:: + + sage: p = Polyhedron([[1, 1]], mutable=True) + sage: p.is_mutable() + True + sage: hasattr(p, "_Vrepresentation") + False + sage: p.set_immutable() + sage: hasattr(p, "_Vrepresentation") + True + """ + if not hasattr(self, '_Vrepresentation'): + self._init_Vrepresentation_from_ppl(True) + if not hasattr(self, '_Hrepresentation'): + self._init_Hrepresentation_from_ppl(True) + self._is_mutable = False + + def Vrepresentation(self, index=None): + """ + Return the objects of the V-representation. Each entry is + either a vertex, a ray, or a line. + + See :mod:`sage.geometry.polyhedron.constructor` for a + definition of vertex/ray/line. + + INPUT: + + - ``index`` -- either an integer or ``None`` + + OUTPUT: + + The optional argument is an index running from ``0`` to + ``self.n_Vrepresentation()-1``. If present, the + V-representation object at the given index will be + returned. Without an argument, returns the list of all + V-representation objects. + + EXAMPLES:: + + sage: p = polytopes.cube() + sage: p.Vrepresentation(0) + A vertex at (1, -1, -1) + + :: + + sage: P = p.parent() + sage: p = P._element_constructor_(p, mutable=True) + sage: p.Vrepresentation(0) + A vertex at (-1, -1, -1) + sage: p._clear_cache() + sage: p.Vrepresentation(0) + A vertex at (-1, -1, -1) + sage: TestSuite(p).run() + """ + if not hasattr(self, '_Vrepresentation'): + self._init_Vrepresentation_from_ppl(True) + if index is None: + return self._Vrepresentation + else: + return self._Vrepresentation[index] def _init_Vrepresentation_from_ppl(self, minimize): """ @@ -157,6 +243,8 @@ def _init_Vrepresentation_from_ppl(self, minimize): sage: p._ppl_polyhedron.minimized_generators() Generator_System {point(0/2, 1/2), point(2/1, 0/1), point(24/6, 5/6)} """ + if not self._is_mutable: + raise TypeError("Vrepresentation of mutable polyhedra cannot be recomputed") self._Vrepresentation = [] gs = self._ppl_polyhedron.minimized_generators() parent = self.parent() @@ -195,6 +283,8 @@ def _init_Hrepresentation_from_ppl(self, minimize): sage: p._ppl_polyhedron.minimized_generators() Generator_System {point(0/2, 1/2), point(2/1, 0/1), point(24/6, 5/6)} """ + if not self._is_mutable: + raise TypeError("Hrepresentation of mutable polyhedra cannot be recomputed") self._Hrepresentation = [] cs = self._ppl_polyhedron.minimized_constraints() parent = self.parent() @@ -205,6 +295,49 @@ def _init_Hrepresentation_from_ppl(self, minimize): parent._make_Equation(self, (c.inhomogeneous_term(),) + c.coefficients()) self._Hrepresentation = tuple(self._Hrepresentation) + def Hrepresentation(self, index=None): + """ + Return the objects of the H-representation. Each entry is + either an inequality or a equation. + + INPUT: + + - ``index`` -- either an integer or ``None`` + + OUTPUT: + + The optional argument is an index running from ``0`` to + ``self.n_Hrepresentation()-1``. If present, the + H-representation object at the given index will be + returned. Without an argument, returns the list of all + H-representation objects. + + EXAMPLES:: + + sage: p = polytopes.hypercube(3) + sage: p.Hrepresentation(0) + An inequality (-1, 0, 0) x + 1 >= 0 + sage: p.Hrepresentation(0) == p.Hrepresentation()[0] + True + + :: + + sage: P = p.parent() + sage: p = P._element_constructor_(p, mutable=True) + sage: p.Hrepresentation(0) + An inequality (0, 0, -1) x + 1 >= 0 + sage: p._clear_cache() + sage: p.Hrepresentation(0) + An inequality (0, 0, -1) x + 1 >= 0 + sage: TestSuite(p).run() + """ + if not hasattr(self, '_Hrepresentation'): + self._init_Hrepresentation_from_ppl(True) + if index is None: + return self._Hrepresentation + else: + return self._Hrepresentation[index] + def _init_empty_polyhedron(self): """ Initializes an empty polyhedron. @@ -224,6 +357,72 @@ def _init_empty_polyhedron(self): super(Polyhedron_ppl, self)._init_empty_polyhedron() self._ppl_polyhedron = C_Polyhedron(self.ambient_dim(), 'empty') + @staticmethod + def _convert_generator_to_ppl(v, typ): + r""" + Convert a generator to ``ppl``. + + INPUT: + + - ``v`` -- a vertex, ray, or line. + + - ``typ`` -- integer; 2 -- vertex; 3 -- ray; 4 -- line + + EXAMPLES:: + + sage: P = Polyhedron() + sage: P._convert_generator_to_ppl([1, 1/2, 3], 2) + point(2/2, 1/2, 6/2) + sage: P._convert_generator_to_ppl([1, 1/2, 3], 3) + ray(2, 1, 6) + sage: P._convert_generator_to_ppl([1, 1/2, 3], 4) + line(2, 1, 6) + """ + if typ == 2: + ob = point + elif typ == 3: + ob = ray + else: + ob = line + + d = LCM_list([denominator(v_i) for v_i in v]) + if d.is_one(): + return ob(Linear_Expression(v, 0)) + else: + dv = [ d*v_i for v_i in v ] + if typ == 2: + return ob(Linear_Expression(dv, 0), d) + else: + return ob(Linear_Expression(dv, 0)) + + @staticmethod + def _convert_constraint_to_ppl(c, typ): + r""" + Convert a constraint to ``ppl``. + + INPUT: + + - ``c`` -- an inequality or equation. + + - ``typ`` -- integer; 0 -- inequality; 3 -- equation + + EXAMPLES:: + + sage: P = Polyhedron() + sage: P._convert_constraint_to_ppl([1, 1/2, 3], 0) + x0+6*x1+2>=0 + sage: P._convert_constraint_to_ppl([1, 1/2, 3], 1) + x0+6*x1+2==0 + """ + d = LCM_list([denominator(c_i) for c_i in c]) + dc = [ ZZ(d*c_i) for c_i in c ] + b = dc[0] + A = dc[1:] + if typ == 0: + return Linear_Expression(A, b) >= 0 + else: + return Linear_Expression(A, b) == 0 + diff --git a/src/sage/geometry/polyhedron/base.py b/src/sage/geometry/polyhedron/base.py index 90352cb4e00..8b0ed75b980 100644 --- a/src/sage/geometry/polyhedron/base.py +++ b/src/sage/geometry/polyhedron/base.py @@ -125,6 +125,8 @@ class Polyhedron_base(Element, ConvexSet_closed): one of``Vrep`` or ``Hrep`` to pick this in case the backend cannot initialize from complete double description + - ``mutable`` -- ignored + If both ``Vrep`` and ``Hrep`` are provided, then ``Vrep_minimal`` and ``Hrep_minimal`` must be set to ``True``. @@ -171,7 +173,7 @@ class Polyhedron_base(Element, ConvexSet_closed): sage: TestSuite(P).run() """ - def __init__(self, parent, Vrep, Hrep, Vrep_minimal=None, Hrep_minimal=None, pref_rep=None, **kwds): + def __init__(self, parent, Vrep, Hrep, Vrep_minimal=None, Hrep_minimal=None, pref_rep=None, mutable=False, **kwds): """ Initializes the polyhedron. @@ -607,7 +609,7 @@ def base_extend(self, base_ring, backend=None): """ new_parent = self.parent().base_extend(base_ring, backend) - return new_parent(self) + return new_parent(self, copy=True) def change_ring(self, base_ring, backend=None): """ @@ -733,7 +735,7 @@ def _richcmp_(self, other, op): sage: P > Q False """ - if self._Vrepresentation is None or other._Vrepresentation is None: + if self.Vrepresentation() is None or other.Vrepresentation() is None: raise RuntimeError('some V representation is missing') # make sure deleted polyhedra are not used in cache @@ -778,6 +780,30 @@ def _is_subpolyhedron(self, other): for other_H in other.Hrepresentation() for self_V in self.Vrepresentation()) + def is_mutable(self): + r""" + Return True if the polyhedron is mutable, i.e. it can be modified in place. + + EXAMPLES:: + + sage: p = polytopes.cube(backend='field') + sage: p.is_mutable() + False + """ + return False + + def is_immutable(self): + r""" + Return True if the polyhedron is immutable, i.e. it cannot be modified in place. + + EXAMPLES:: + + sage: p = polytopes.cube(backend='field') + sage: p.is_immutable() + True + """ + return True + @cached_method def vertex_facet_graph(self, labels=True): r""" @@ -1706,10 +1732,10 @@ def Hrepresentation(self, index=None): EXAMPLES:: - sage: p = polytopes.hypercube(3) + sage: p = polytopes.hypercube(3, backend='field') sage: p.Hrepresentation(0) An inequality (-1, 0, 0) x + 1 >= 0 - sage: p.Hrepresentation(0) == p.Hrepresentation() [0] + sage: p.Hrepresentation(0) == p.Hrepresentation()[0] True """ if index is None: @@ -3992,22 +4018,22 @@ def is_prism(self, certificate=False): sage: R = Q.prism() sage: R.is_prism(certificate=True) (True, - [[A vertex at (1, 0, 0, 0), + [[A vertex at (1, 6, 36, 216), + A vertex at (1, 0, 0, 0), + A vertex at (1, 7, 49, 343), + A vertex at (1, 5, 25, 125), A vertex at (1, 1, 1, 1), A vertex at (1, 2, 4, 8), - A vertex at (1, 3, 9, 27), A vertex at (1, 4, 16, 64), - A vertex at (1, 5, 25, 125), - A vertex at (1, 6, 36, 216), - A vertex at (1, 7, 49, 343)], - [A vertex at (0, 0, 0, 0), + A vertex at (1, 3, 9, 27)], + [A vertex at (0, 3, 9, 27), + A vertex at (0, 6, 36, 216), + A vertex at (0, 0, 0, 0), + A vertex at (0, 7, 49, 343), + A vertex at (0, 5, 25, 125), A vertex at (0, 1, 1, 1), A vertex at (0, 2, 4, 8), - A vertex at (0, 3, 9, 27), - A vertex at (0, 4, 16, 64), - A vertex at (0, 5, 25, 125), - A vertex at (0, 6, 36, 216), - A vertex at (0, 7, 49, 343)]]) + A vertex at (0, 4, 16, 64)]]) TESTS:: @@ -4131,7 +4157,7 @@ def gale_transform(self): sage: p = Polyhedron(vertices = [[0,0],[0,1],[1,0]]) sage: p2 = p.prism() sage: p2.gale_transform() - ((-1, -1), (1, 0), (0, 1), (1, 1), (-1, 0), (0, -1)) + ((-1, 0), (0, -1), (1, 1), (-1, -1), (1, 0), (0, 1)) REFERENCES: @@ -5668,7 +5694,11 @@ def intersection(self, other): new_eqns = self.equations() + other.equations() parent = self.parent() try: - return parent.element_class(parent, None, [new_ieqs, new_eqns]) + intersection = parent.element_class(parent, None, [new_ieqs, new_eqns]) + + # Force calculation of the vertices. + _ = intersection.n_vertices() + return intersection except TypeError as msg: if self.base_ring() is ZZ: parent = parent.base_extend(QQ) @@ -5939,7 +5969,7 @@ def stack(self, face, position=None): sage: hexaprism = polytopes.regular_polygon(6).prism() sage: hexaprism.f_vector() (1, 12, 18, 8, 1) - sage: square_face = hexaprism.faces(2)[0] + sage: square_face = hexaprism.faces(2)[2] sage: stacked_hexaprism = hexaprism.stack(square_face) sage: stacked_hexaprism.f_vector() (1, 13, 22, 11, 1) @@ -6075,29 +6105,29 @@ def wedge(self, face, width=1): sage: W2 = Q.wedge(Q.faces(2)[7]); W2 A 4-dimensional polyhedron in QQ^5 defined as the convex hull of 9 vertices sage: W2.vertices() - (A vertex at (0, 1, 0, 1, 0), - A vertex at (0, 0, 1, 1, 0), - A vertex at (1, 0, 0, 1, -1), - A vertex at (1, 0, 0, 1, 1), - A vertex at (1, 0, 1, 0, 1), + (A vertex at (1, 1, 0, 0, 1), A vertex at (1, 1, 0, 0, -1), - A vertex at (0, 1, 1, 0, 0), + A vertex at (1, 0, 1, 0, 1), A vertex at (1, 0, 1, 0, -1), - A vertex at (1, 1, 0, 0, 1)) + A vertex at (1, 0, 0, 1, 1), + A vertex at (1, 0, 0, 1, -1), + A vertex at (0, 0, 1, 1, 0), + A vertex at (0, 1, 1, 0, 0), + A vertex at (0, 1, 0, 1, 0)) sage: W3 = Q.wedge(Q.faces(1)[11]); W3 A 4-dimensional polyhedron in QQ^5 defined as the convex hull of 10 vertices sage: W3.vertices() - (A vertex at (0, 1, 0, 1, 0), - A vertex at (0, 0, 1, 1, 0), - A vertex at (1, 0, 0, 1, -1), - A vertex at (1, 0, 0, 1, 1), + (A vertex at (1, 1, 0, 0, -2), + A vertex at (1, 1, 0, 0, 2), + A vertex at (1, 0, 1, 0, -2), A vertex at (1, 0, 1, 0, 2), + A vertex at (1, 0, 0, 1, 1), + A vertex at (1, 0, 0, 1, -1), + A vertex at (0, 1, 0, 1, 0), A vertex at (0, 1, 1, 0, 1), - A vertex at (1, 0, 1, 0, -2), - A vertex at (1, 1, 0, 0, 2), - A vertex at (0, 1, 1, 0, -1), - A vertex at (1, 1, 0, 0, -2)) + A vertex at (0, 0, 1, 1, 0), + A vertex at (0, 1, 1, 0, -1)) sage: C_3_7 = polytopes.cyclic_polytope(3,7) sage: P_6 = polytopes.regular_polygon(6) @@ -6640,6 +6670,48 @@ def hasse_diagram(self): Digraph on 20 vertices sage: D.degree_polynomial() x^5 + x^4*y + x*y^4 + y^5 + 4*x^3*y + 8*x^2*y^2 + 4*x*y^3 + + Faces of an mutable polyhedron are not hashable. Hence those are not suitable as + vertices of the hasse diagram. Use the combinatorial polyhedron instead:: + + sage: P = polytopes.regular_polygon(4).pyramid() + sage: parent = P.parent() + sage: parent = parent.change_ring(QQ, backend='ppl') + sage: Q = parent._element_constructor_(P, mutable=True) + sage: Q.hasse_diagram() + Traceback (most recent call last): + ... + TypeError: mutable polyhedra are unhashable + sage: C = Q.combinatorial_polyhedron() + sage: D = C.hasse_diagram() + sage: set(D.vertices()) == set(range(20)) + True + sage: def index_to_combinatorial_face(n): + ....: return C.face_by_face_lattice_index(n) + sage: D.relabel(index_to_combinatorial_face, inplace=True) + sage: D.vertices() + [A -1-dimensional face of a 3-dimensional combinatorial polyhedron, + A 0-dimensional face of a 3-dimensional combinatorial polyhedron, + A 0-dimensional face of a 3-dimensional combinatorial polyhedron, + A 0-dimensional face of a 3-dimensional combinatorial polyhedron, + A 0-dimensional face of a 3-dimensional combinatorial polyhedron, + A 0-dimensional face of a 3-dimensional combinatorial polyhedron, + A 1-dimensional face of a 3-dimensional combinatorial polyhedron, + A 1-dimensional face of a 3-dimensional combinatorial polyhedron, + A 1-dimensional face of a 3-dimensional combinatorial polyhedron, + A 1-dimensional face of a 3-dimensional combinatorial polyhedron, + A 1-dimensional face of a 3-dimensional combinatorial polyhedron, + A 1-dimensional face of a 3-dimensional combinatorial polyhedron, + A 1-dimensional face of a 3-dimensional combinatorial polyhedron, + A 1-dimensional face of a 3-dimensional combinatorial polyhedron, + A 2-dimensional face of a 3-dimensional combinatorial polyhedron, + A 2-dimensional face of a 3-dimensional combinatorial polyhedron, + A 2-dimensional face of a 3-dimensional combinatorial polyhedron, + A 2-dimensional face of a 3-dimensional combinatorial polyhedron, + A 2-dimensional face of a 3-dimensional combinatorial polyhedron, + A 3-dimensional face of a 3-dimensional combinatorial polyhedron] + sage: D.degree_polynomial() + x^5 + x^4*y + x*y^4 + y^5 + 4*x^3*y + 8*x^2*y^2 + 4*x*y^3 """ from sage.geometry.polyhedron.face import combinatorial_face_to_polyhedral_face @@ -7819,13 +7891,25 @@ def pyramid(self): 'cdd' """ assert self.is_compact(), "Not a polytope." + c = self.center() - new_verts = \ - [[0] + x for x in self.Vrep_generator()] + \ - [[1] + list(self.center())] + from itertools import chain + new_verts = chain(([0] + x for x in self.Vrep_generator()), + [[1] + list(c)]) + new_ieqs = chain(([i.b()] + [-c*i.A() - i.b()] + list(i.A()) for i in self.inequalities()), + [[0, 1] + [0]*self.ambient_dim()]) + new_eqns = ([e.b()] + [0] + list(e.A()) for e in self.equations()) + pref_rep = 'Hrep' if self.n_vertices() > self.n_inequalities() else 'Vrep' parent = self.parent().base_extend(self.center().parent(), ambient_dim=self.ambient_dim()+1) - return parent.element_class(parent, [new_verts, [], []], None) + + if self.n_vertices() == 1: + # Fix the polyhedron with one vertex. + return parent.element_class(parent, [new_verts, [], []], None) + + return parent.element_class(parent, [new_verts, [], []], + [new_ieqs, new_eqns], + Vrep_minimal=True, Hrep_minimal=True, pref_rep=pref_rep) def _test_pyramid(self, tester=None, **options): """ @@ -7875,6 +7959,18 @@ def check_pyramid_certificate(P, cert): tester.assertTrue(pyr_polar.is_combinatorially_isomorphic(pyr_polar)) + # Basic properties of the pyramid. + + # Check that the prism preserves the backend. + tester.assertEqual(pyr.backend(), self.backend()) + + tester.assertEqual(1 + self.n_vertices(), pyr.n_vertices()) + tester.assertEqual(self.n_equations(), pyr.n_equations()) + tester.assertEqual(1 + self.n_inequalities(), pyr.n_inequalities()) + + if self.n_vertices() < 15 and self.n_facets() < 15: + pyr._test_basic_properties() + def bipyramid(self): """ Return a polyhedron that is a bipyramid over the original. @@ -7907,15 +8003,77 @@ def bipyramid(self): sage: polytopes.simplex(backend='cdd').bipyramid().backend() 'cdd' """ - new_verts = \ - [[ 0] + list(x) for x in self.vertex_generator()] + \ - [[ 1] + list(self.center())] + \ - [[-1] + list(self.center())] - new_rays = [[0] + r for r in self.rays()] - new_lines = [[0] + list(l) for l in self.lines()] - + c = self.center() + from itertools import chain + new_verts = chain(([0] + list(x) for x in self.vertex_generator()), + [[1] + list(c), [-1] + list(c)]) + new_rays = ([0] + r for r in self.rays()) + new_lines = ([0] + l for l in self.lines()) + new_ieqs = chain(([i.b()] + [ c*i.A() + i.b()] + list(i.A()) for i in self.inequalities()), + ([i.b()] + [-c*i.A() - i.b()] + list(i.A()) for i in self.inequalities())) + new_eqns = ([e.b()] + [0] + list(e.A()) for e in self.equations()) + + pref_rep = 'Hrep' if 2 + (self.n_vertices() + self.n_rays()) >= 2*self.n_inequalities() else 'Vrep' parent = self.parent().base_extend(self.center().parent(), ambient_dim=self.ambient_dim()+1) - return parent.element_class(parent, [new_verts, new_rays, new_lines], None) + + if c not in self.relative_interior(): + # Fix polyhedra with non-proper center. + return parent.element_class(parent, [new_verts, new_rays, new_lines], None) + + return parent.element_class(parent, [new_verts, new_rays, new_lines], + [new_ieqs, new_eqns], + Vrep_minimal=True, Hrep_minimal=True, pref_rep=pref_rep) + + def _test_bipyramid(self, tester=None, **options): + """ + Run tests on the method :meth:`.bipyramid`. + + TESTS:: + + sage: polytopes.cross_polytope(3)._test_bipyramid() + """ + if tester is None: + tester = self._tester(**options) + + if (self.n_vertices() + self.n_rays() >= 40 + or self.n_facets() >= 40 + or self.n_vertices() <= 1): + return + + bipyramid = self.bipyramid() + + # Check that the double description is set up correctly. + if self.base_ring().is_exact() and self.n_vertices() + self.n_rays() < 15 and self.n_facets() < 15: + bipyramid._test_basic_properties(tester) + + # Check that the bipyramid preserves the backend. + tester.assertEqual(bipyramid.backend(), self.backend()) + + if self.center() not in self.relative_interior(): + # In this case (unbounded) the bipyramid behaves a bit different. + return + + tester.assertEqual(2 + self.n_vertices(), bipyramid.n_vertices()) + tester.assertEqual(self.n_rays(), bipyramid.n_rays()) + tester.assertEqual(self.n_lines(), bipyramid.n_lines()) + tester.assertEqual(self.n_equations(), bipyramid.n_equations()) + tester.assertEqual(2*self.n_inequalities(), bipyramid.n_inequalities()) + + if not self.is_compact(): + # ``is_bipyramid`` is only implemented for compact polyhedra. + return + + b, cert = bipyramid.is_bipyramid(certificate=True) + tester.assertTrue(b) + + if not self.is_bipyramid() and self.base_ring().is_exact(): + # In this case the certificate is unique. + + R = self.base_ring() + a = (R(1),) + tuple(self.center()) + b = (R(-1),) + tuple(self.center()) + c, d = [tuple(v) for v in cert] + tester.assertEqual(sorted([a, b]), sorted([c, d])) def prism(self): """ @@ -7936,14 +8094,72 @@ def prism(self): sage: polytopes.simplex(backend='cdd').prism().backend() 'cdd' """ - new_verts = [] - new_verts.extend( [ [0] + v for v in self.vertices()] ) - new_verts.extend( [ [1] + v for v in self.vertices()] ) - new_rays = [ [0] + r for r in self.rays()] - new_lines = [ [0] + l for l in self.lines()] - + from itertools import chain + new_verts = chain(([0] + v for v in self.vertices()), + ([1] + v for v in self.vertices())) + new_rays = ([0] + r for r in self.rays()) + new_lines = ([0] + l for l in self.lines()) + new_eqns = ([e.b()] + [0] + list(e[1:]) for e in self.equations()) + new_ieqs = chain(([i.b()] + [0] + list(i[1:]) for i in self.inequalities()), + [[0, 1] + [0]*self.ambient_dim(), [1, -1] + [0]*self.ambient_dim()]) + + pref_rep = 'Hrep' if 2*(self.n_vertices() + self.n_rays()) >= self.n_inequalities() + 2 else 'Vrep' parent = self.parent().change_ring(self.base_ring(), ambient_dim=self.ambient_dim()+1) - return parent.element_class(parent, [new_verts, new_rays, new_lines], None) + + if not self.vertices(): + # Fix the empty polyhedron. + return parent.element_class(parent, [[], [], []], None) + + return parent.element_class(parent, [new_verts, new_rays, new_lines], + [new_ieqs, new_eqns], + Vrep_minimal=True, Hrep_minimal=True, pref_rep=pref_rep) + + def _test_prism(self, tester=None, **options): + """ + Run tests on the method :meth:`.prism`. + + TESTS:: + + sage: polytopes.cross_polytope(3)._test_prism() + """ + if tester is None: + tester = self._tester(**options) + + if self.n_vertices() + self.n_rays() < 40 and self.n_facets() < 40: + prism = self.prism() + + # Check that the double description is set up correctly. + if self.base_ring().is_exact() and self.n_vertices() + self.n_rays() < 15 and self.n_facets() < 15: + prism._test_basic_properties(tester) + + # Check that the prism preserves the backend. + tester.assertEqual(prism.backend(), self.backend()) + + tester.assertEqual(2*self.n_vertices(), prism.n_vertices()) + tester.assertEqual(self.n_rays(), prism.n_rays()) + tester.assertEqual(self.n_lines(), prism.n_lines()) + tester.assertEqual(self.n_equations(), prism.n_equations()) + if self.is_empty(): + return + + tester.assertEqual(2 + self.n_inequalities(), prism.n_inequalities()) + + if not self.is_compact(): + # ``is_prism`` only implemented for compact polyhedra. + return + + b, cert = prism.is_prism(certificate=True) + tester.assertTrue(b) + + if not self.is_prism() and self.base_ring().is_exact(): + # In this case the certificate is unique. + + R = self.base_ring() + cert_set = set(frozenset(tuple(v) for v in f) for f in cert) + expected_cert = set(frozenset((i,) + tuple(v) + for v in self.vertices()) + for i in (R(0), R(1))) + tester.assertEqual(cert_set, expected_cert) def one_point_suspension(self, vertex): """ @@ -8909,11 +9125,22 @@ def contains(self, point): True sage: full.contains([0]) False + + TESTS: + + Passing non-iterable objects does not cause an exception, see :trac:`32013`:: + + sage: None in Polyhedron(vertices=[(0,0)], rays=[(1,0)], base_ring=QQ) + False """ try: p = vector(point) except TypeError: # point not iterable or no common ring for elements - if len(point) > 0: + try: + l = len(point) + except TypeError: + return False + if l > 0: return False else: p = vector(self.base_ring(), []) @@ -9012,7 +9239,11 @@ def interior_contains(self, point): try: p = vector(point) except TypeError: # point not iterable or no common ring for elements - if len(point) > 0: + try: + l = len(point) + except TypeError: + return False + if l > 0: return False else: p = vector(self.base_ring(), []) @@ -9129,7 +9360,11 @@ def relative_interior_contains(self, point): try: p = vector(point) except TypeError: # point not iterable or no common ring for elements - if len(point) > 0: + try: + l = len(point) + except TypeError: + return False + if l > 0: return False else: p = vector(self.base_ring(), []) @@ -10508,7 +10743,7 @@ def _test_is_combinatorially_isomorphic(self, tester=None, **options): if self.n_vertices(): tester.assertTrue(self.is_combinatorially_isomorphic(self + self.center())) - if self.n_vertices() < 20 and self.n_facets() < 20: + if self.n_vertices() < 20 and self.n_facets() < 20 and self.is_immutable(): tester.assertTrue(self.is_combinatorially_isomorphic(ZZ(4)*self, algorithm='face_lattice')) if self.n_vertices(): tester.assertTrue(self.is_combinatorially_isomorphic(self + self.center(), algorithm='face_lattice')) diff --git a/src/sage/geometry/polyhedron/base_mutable.py b/src/sage/geometry/polyhedron/base_mutable.py new file mode 100644 index 00000000000..d982135f1f5 --- /dev/null +++ b/src/sage/geometry/polyhedron/base_mutable.py @@ -0,0 +1,212 @@ +r""" +Base class for mutable polyhedra. + +Just like vectors and matrices they can be set immutable. +The constructor does this by default. +""" + +from .base import Polyhedron_base + + +class Polyhedron_mutable(Polyhedron_base): + """ + Base class for polyhedra that allow mutability. + + This should not be used directly. + """ + + def __hash__(self): + r""" + TESTS:: + + sage: p = Polyhedron([[1, 1]], mutable=True) + sage: set([p]) + Traceback (most recent call last): + ... + TypeError: mutable polyhedra are unhashable + sage: p.set_immutable() + sage: set([p]) + {A 0-dimensional polyhedron in ZZ^2 defined as the convex hull of 1 vertex} + """ + if self._is_mutable: + raise TypeError("mutable polyhedra are unhashable") + return Polyhedron_base.__hash__(self) + + def _clear_cache(self): + r""" + Clear the Vrepresentation and Hrepresentation data of ``self``. + + TESTS:: + + sage: p = polytopes.permutahedron(4) + sage: P = p.parent() + sage: q = P._element_constructor_(p, mutable=True) + sage: TestSuite(q).run() + sage: q._clear_cache() + sage: TestSuite(q).run() + + :: + + sage: q.set_immutable() + sage: q._clear_cache() + Traceback (most recent call last): + ... + TypeError: cannot clear cache of immutable polyhedra + """ + if not self._is_mutable: + raise TypeError("cannot clear cache of immutable polyhedra") + + # Invalidate object pointing towards this polyhedron (faces etc.). + for ob in self._dependent_objects: + ob._polyhedron = None + backend_object = self.__dict__["_" + self._backend_object_name] + del self.__dict__ + self.__dict__["_" + self._backend_object_name] = backend_object + self._is_mutable = True + self._dependent_objects = [] + + def _add_dependent_object(self, ob): + r""" + Add an object that has ``self`` has attribute ``_polyhedron``. + + When ``self`` is modified, we delete this attribute to invalidate those objects. + + EXAMPLES:: + + sage: p = Polyhedron([[1, 1]], mutable=True) + sage: class foo: + ....: def __init__(self, p): + ....: self._polyhedron = p + ....: + sage: a = foo(p) + sage: a.__dict__ + {'_polyhedron': A 0-dimensional polyhedron in ZZ^2 defined as the convex hull of 1 vertex} + sage: p._add_dependent_object(a) + sage: p._clear_cache() + sage: a.__dict__ + {'_polyhedron': None} + + TESTS:: + + sage: from sage.geometry.newton_polygon import NewtonPolygon + sage: p = Polyhedron([[1, 1]], mutable=True) + sage: n = NewtonPolygon(p) + sage: n + Finite Newton polygon with 1 vertex: (1, 1) + sage: n = NewtonPolygon(p) + sage: p._clear_cache() + sage: n + ) failed: AttributeError: 'NoneType' object has no attribute 'vertices'> + + :: + + sage: f = p.faces(0)[0]; f + A 0-dimensional face of a Polyhedron in ZZ^2 defined as the convex hull of 1 vertex + sage: p._clear_cache() + sage: f + ) failed: AttributeError: 'NoneType' object has no attribute 'parent'> + + :: + + sage: v = p.vertices()[0] + sage: p = Polyhedron([[1, 1]], mutable=True) + sage: v = p.Vrepresentation(0); v + A vertex at (1, 1) + sage: h = p.Hrepresentation(0); h + An equation (0, 1) x - 1 == 0 + sage: p._clear_cache() + sage: v.polyhedron() is None + True + sage: h.polyhedron() is None + True + + :: + + sage: p = Polyhedron([[1, 0], [0, 1]], mutable=True) + sage: r = p.relative_interior() + sage: p._clear_cache() + sage: r + Relative interior of None + """ + if not ob._polyhedron is self: + raise ValueError + self._dependent_objects.append(ob) + + def is_mutable(self): + r""" + Return True if the polyhedron is mutable, i.e. it can be modified in place. + + EXAMPLES:: + + sage: p = Polyhedron([[1, 1]], mutable=True) + sage: p.is_mutable() + True + sage: p = Polyhedron([[1, 1]], mutable=False) + sage: p.is_mutable() + False + """ + return self._is_mutable + + def is_immutable(self): + r""" + Return True if the polyhedron is immutable, i.e. it cannot be modified in place. + + EXAMPLES:: + + sage: p = Polyhedron([[1, 1]], mutable=True) + sage: p.is_immutable() + False + sage: p = Polyhedron([[1, 1]], mutable=False) + sage: p.is_immutable() + True + """ + return not self._is_mutable + + def set_immutable(self): + r""" + Make this polyhedron immutable. This operation cannot be undone. + + TESTS:: + + sage: from sage.geometry.polyhedron.base_mutable import Polyhedron_mutable + sage: p = polytopes.cube() + sage: Polyhedron_mutable.set_immutable(p) + Traceback (most recent call last): + ... + NotImplementedError: a derived class must implement this + """ + raise NotImplementedError("a derived class must implement this") + + def Vrepresentation(self): + r""" + A derived class must overwrite such that it restores Vrepresentation + after clearing it. + + TESTS:: + + sage: from sage.geometry.polyhedron.base_mutable import Polyhedron_mutable + sage: p = polytopes.cube() + sage: Polyhedron_mutable.Vrepresentation(p) + Traceback (most recent call last): + ... + NotImplementedError: a derived class must implement this + """ + # A derived class must implemented it to recalculate, if necessary. + raise NotImplementedError("a derived class must implement this") + + def Hrepresentation(self): + r""" + A derived class must overwrite such that it restores Hrepresentation + after clearing it. + + TESTS:: + + sage: from sage.geometry.polyhedron.base_mutable import Polyhedron_mutable + sage: p = polytopes.cube() + sage: Polyhedron_mutable.Hrepresentation(p) + Traceback (most recent call last): + ... + NotImplementedError: a derived class must implement this + """ + # A derived class must implemented it to recalculate, if necessary. + raise NotImplementedError("a derived class must implement this") diff --git a/src/sage/geometry/polyhedron/constructor.py b/src/sage/geometry/polyhedron/constructor.py index 29d84875ac2..7f68ca41ce0 100644 --- a/src/sage/geometry/polyhedron/constructor.py +++ b/src/sage/geometry/polyhedron/constructor.py @@ -301,7 +301,7 @@ def Polyhedron(vertices=None, rays=None, lines=None, ieqs=None, eqns=None, ambient_dim=None, base_ring=None, minimize=True, verbose=False, - backend=None): + backend=None, mutable=False): r""" Construct a polyhedron object. @@ -346,32 +346,35 @@ def Polyhedron(vertices=None, rays=None, lines=None, * ``'cdd'``: use cdd (:mod:`~sage.geometry.polyhedron.backend_cdd`) with `\QQ` or - `\RDF` coefficients depending on ``base_ring``. + `\RDF` coefficients depending on ``base_ring`` * ``'normaliz'``: use normaliz (:mod:`~sage.geometry.polyhedron.backend_normaliz`) with `\ZZ` or - `\QQ` coefficients depending on ``base_ring``. + `\QQ` coefficients depending on ``base_ring`` * ``'polymake'``: use polymake (:mod:`~sage.geometry.polyhedron.backend_polymake`) with `\QQ`, `\RDF` or - ``QuadraticField`` coefficients depending on ``base_ring``. + ``QuadraticField`` coefficients depending on ``base_ring`` * ``'ppl'``: use ppl (:mod:`~sage.geometry.polyhedron.backend_ppl`) with `\ZZ` or - `\QQ` coefficients depending on ``base_ring``. + `\QQ` coefficients depending on ``base_ring`` * ``'field'``: use python implementation (:mod:`~sage.geometry.polyhedron.backend_field`) for any field Some backends support further optional arguments: - - ``minimize`` -- boolean (default: ``True``). Whether to - immediately remove redundant H/V-representation data. Currently + - ``minimize`` -- boolean (default: ``True``); whether to + immediately remove redundant H/V-representation data; currently not used. - - ``verbose`` -- boolean (default: ``False``). Whether to print - verbose output for debugging purposes. Only supported by the cdd and - normaliz backends. + - ``verbose`` -- boolean (default: ``False``); whether to print + verbose output for debugging purposes; only supported by the cdd and + normaliz backends + + - ``mutable`` -- boolean (default: ``False``); whether the polyhedron + is mutable OUTPUT: @@ -460,6 +463,18 @@ def Polyhedron(vertices=None, rays=None, lines=None, ... ValueError: invalid base ring + Create a mutable polyhedron:: + + sage: P = Polyhedron(vertices=[[0, 1], [1, 0]], mutable=True) + sage: P.is_mutable() + True + sage: hasattr(P, "_Vrepresentation") + False + sage: P.Vrepresentation() + (A vertex at (0, 1), A vertex at (1, 0)) + sage: hasattr(P, "_Vrepresentation") + True + .. NOTE:: * Once constructed, a ``Polyhedron`` object is immutable. @@ -658,4 +673,4 @@ def Polyhedron(vertices=None, rays=None, lines=None, Hrep = [ieqs, eqns] if got_Vrep: Vrep = [vertices, rays, lines] - return parent(Vrep, Hrep, convert=convert, verbose=verbose) + return parent(Vrep, Hrep, convert=convert, verbose=verbose, mutable=mutable) diff --git a/src/sage/geometry/polyhedron/face.py b/src/sage/geometry/polyhedron/face.py index 75a8b4f8757..99e24417ede 100644 --- a/src/sage/geometry/polyhedron/face.py +++ b/src/sage/geometry/polyhedron/face.py @@ -158,6 +158,8 @@ def __init__(self, polyhedron, V_indices, H_indices): self._ambient_Hrepresentation_indices = tuple(H_indices) self._ambient_Vrepresentation = tuple( polyhedron.Vrepresentation(i) for i in V_indices ) self._ambient_Hrepresentation = tuple( polyhedron.Hrepresentation(i) for i in H_indices ) + if polyhedron.is_mutable(): + polyhedron._add_dependent_object(self) def __hash__(self): r""" diff --git a/src/sage/geometry/polyhedron/library.py b/src/sage/geometry/polyhedron/library.py index fa1bd00d953..4fe82a3a62a 100644 --- a/src/sage/geometry/polyhedron/library.py +++ b/src/sage/geometry/polyhedron/library.py @@ -1734,7 +1734,7 @@ def Kirkman_icosahedron(self, backend=None): sage: vertices = ki.vertices() sage: edges = [[vector(edge[0]),vector(edge[1])] for edge in ki.bounded_edges()] sage: edge_lengths = [norm(edge[0]-edge[1]) for edge in edges] - sage: union(edge_lengths) + sage: sorted(set(edge_lengths)) [7, 8, 9, 11, 12, 14, 16] TESTS:: diff --git a/src/sage/geometry/polyhedron/parent.py b/src/sage/geometry/polyhedron/parent.py index 57f3a349bfc..45c71c26c18 100644 --- a/src/sage/geometry/polyhedron/parent.py +++ b/src/sage/geometry/polyhedron/parent.py @@ -104,6 +104,10 @@ def Polyhedra(ambient_space_or_base_ring=None, ambient_dim=None, backend=None, * Traceback (most recent call last): ... ValueError: invalid base ring: Number Field in I with defining polynomial x^2 + 1 with I = 1*I cannot be coerced to a real field + sage: Polyhedra(AA, 3, backend='polymake') # optional - polymake + Traceback (most recent call last): + ... + ValueError: the 'polymake' backend for polyhedron cannot be used with Algebraic Real Field """ if ambient_space_or_base_ring is not None: @@ -157,7 +161,13 @@ def Polyhedra(ambient_space_or_base_ring=None, ambient_dim=None, backend=None, * elif backend == 'cdd' and base_ring is RDF: return Polyhedra_RDF_cdd(RDF, ambient_dim, backend) elif backend == 'polymake': - return Polyhedra_polymake(base_ring.fraction_field(), ambient_dim, backend) + base_field = base_ring.fraction_field() + try: + from sage.interfaces.polymake import polymake + polymake_base_field = polymake(base_field) + except TypeError: + raise ValueError(f"the 'polymake' backend for polyhedron cannot be used with {base_field}") + return Polyhedra_polymake(base_field, ambient_dim, backend) elif backend == 'field': if not base_ring.is_exact(): raise ValueError("the 'field' backend for polyhedron cannot be used with non-exact fields") @@ -296,6 +306,8 @@ def recycle(self, polyhedron): Vrep._polyhedron = None polyhedron._Hrepresentation = None polyhedron._Vrepresentation = None + if polyhedron.is_mutable(): + polyhedron._dependent_objects = [] def ambient_dim(self): r""" @@ -565,6 +577,45 @@ def _element_constructor_(self, *args, **kwds): A 1-dimensional polyhedron in RDF^1 defined as the convex hull of 2 vertices sage: P.intersection(Q) A 1-dimensional polyhedron in RDF^1 defined as the convex hull of 2 vertices + + The default is not to copy an object if the parent is ``self``:: + + sage: p = polytopes.cube(backend='field') + sage: P = p.parent() + sage: q = P._element_constructor_(p) + sage: q is p + True + sage: r = P._element_constructor_(p, copy=True) + sage: r is p + False + + When the parent of the object is not ``self``, the default is not to copy:: + + sage: Q = P.base_extend(AA) + sage: q = Q._element_constructor_(p) + sage: q is p + False + sage: q = Q._element_constructor_(p, copy=False) + Traceback (most recent call last): + ... + ValueError: you need to make a copy when changing the parent + + For mutable polyhedra either ``copy`` or ``mutable`` must be specified:: + + sage: p = Polyhedron(vertices=[[0, 1], [1, 0]], mutable=True) + sage: P = p.parent() + sage: q = P._element_constructor_(p) + Traceback (most recent call last): + ... + ValueError: must make a copy to obtain immutable object from mutable input + sage: q = P._element_constructor_(p, mutable=True) + sage: q is p + True + sage: r = P._element_constructor_(p, copy=True) + sage: r.is_mutable() + False + sage: r is p + False """ nargs = len(args) convert = kwds.pop('convert', True) @@ -596,8 +647,18 @@ def convert_base_ring_Hrep(lstlst): Vrep = [convert_base_ring(_) for _ in Vrep] return self.element_class(self, Vrep, Hrep, **kwds) if nargs == 1 and is_Polyhedron(args[0]): + copy = kwds.pop('copy', args[0].parent() is not self) + mutable = kwds.pop('mutable', False) + + if not copy and args[0].parent() is not self: + raise ValueError("you need to make a copy when changing the parent") + if args[0].is_mutable() and not copy and not mutable: + raise ValueError("must make a copy to obtain immutable object from mutable input") + if not copy and mutable is args[0].is_mutable(): + return args[0] + polyhedron = args[0] - return self._element_constructor_polyhedron(polyhedron, **kwds) + return self._element_constructor_polyhedron(polyhedron, mutable=mutable, **kwds) if nargs == 1 and args[0] == 0: return self.zero() raise ValueError('Cannot convert to polyhedron object.') @@ -614,7 +675,7 @@ def _element_constructor_polyhedron(self, polyhedron, **kwds): EXAMPLES:: sage: from sage.geometry.polyhedron.parent import Polyhedra - sage: P = Polyhedra(QQ, 3) + sage: P = Polyhedra(QQ, 3, backend='cdd') sage: p = Polyhedron(vertices=[(0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1)]) sage: p A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 4 vertices @@ -1075,12 +1136,64 @@ def _make_Line(self, polyhedron, data): class Polyhedra_ZZ_ppl(Polyhedra_base): Element = Polyhedron_ZZ_ppl + def _element_constructor_polyhedron(self, polyhedron, **kwds): + """ + The element (polyhedron) constructor for the case of 1 argument, a polyhedron. + + Set up with the ``ppl_polyhedron`` of ``self``, if available. + + EXAMPLES:: + + sage: from sage.geometry.polyhedron.parent import Polyhedra + sage: P = Polyhedra(ZZ, 3) + sage: p = Polyhedron(vertices=[(0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1)], base_ring=QQ) + sage: p + A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 4 vertices + sage: P(p) + A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 4 vertices + + sage: p = Polyhedron(vertices=[(0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1)], backend='cdd') + sage: P(p) + A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 4 vertices + """ + from copy import copy + if polyhedron.backend() == "ppl": + return self._element_constructor_(None, None, ppl_polyhedron=copy(polyhedron._ppl_polyhedron), **kwds) + else: + return Polyhedra_base._element_constructor_polyhedron(self, polyhedron, **kwds) + class Polyhedra_ZZ_normaliz(Polyhedra_base): Element = Polyhedron_ZZ_normaliz class Polyhedra_QQ_ppl(Polyhedra_base): Element = Polyhedron_QQ_ppl + def _element_constructor_polyhedron(self, polyhedron, **kwds): + """ + The element (polyhedron) constructor for the case of 1 argument, a polyhedron. + + Set up with the ``ppl_polyhedron`` of ``self``, if available. + + EXAMPLES:: + + sage: from sage.geometry.polyhedron.parent import Polyhedra + sage: P = Polyhedra(QQ, 3) + sage: p = Polyhedron(vertices=[(0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1)]) + sage: p + A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 4 vertices + sage: P(p) + A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 4 vertices + + sage: p = Polyhedron(vertices=[(0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1)], backend='cdd') + sage: P(p) + A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 4 vertices + """ + from copy import copy + if polyhedron.backend() == "ppl": + return self._element_constructor_(None, None, ppl_polyhedron=copy(polyhedron._ppl_polyhedron), **kwds) + else: + return Polyhedra_base._element_constructor_polyhedron(self, polyhedron, **kwds) + class Polyhedra_QQ_normaliz(Polyhedra_base): Element = Polyhedron_QQ_normaliz diff --git a/src/sage/geometry/polyhedron/representation.py b/src/sage/geometry/polyhedron/representation.py index dd538422e94..1fcefddd53b 100644 --- a/src/sage/geometry/polyhedron/representation.py +++ b/src/sage/geometry/polyhedron/representation.py @@ -435,6 +435,8 @@ def _set_data(self, polyhedron, data): self._index = len(polyhedron._Hrepresentation) polyhedron._Hrepresentation.append(self) self._polyhedron = polyhedron + if polyhedron.is_mutable(): + polyhedron._add_dependent_object(self) def is_H(self): """ @@ -1166,6 +1168,8 @@ def _set_data(self, polyhedron, data): self._index = len(polyhedron._Vrepresentation) polyhedron._Vrepresentation.append(self) self._polyhedron = polyhedron + if polyhedron.is_mutable(): + polyhedron._add_dependent_object(self) def is_V(self): """ diff --git a/src/sage/geometry/relative_interior.py b/src/sage/geometry/relative_interior.py index 9f5263ff50a..2a13e967f1d 100644 --- a/src/sage/geometry/relative_interior.py +++ b/src/sage/geometry/relative_interior.py @@ -52,6 +52,9 @@ def __init__(self, polyhedron): sage: TestSuite(RelativeInterior(P)).run() """ self._polyhedron = polyhedron + if hasattr(polyhedron, "is_mutable") and polyhedron.is_mutable(): + if hasattr(polyhedron, "_add_dependent_object"): + polyhedron._add_dependent_object(self) def __hash__(self): r""" diff --git a/src/sage/graphs/base/boost_graph.pyx b/src/sage/graphs/base/boost_graph.pyx index c42063a9ba7..af1de86c552 100644 --- a/src/sage/graphs/base/boost_graph.pyx +++ b/src/sage/graphs/base/boost_graph.pyx @@ -2381,8 +2381,8 @@ cdef double diameter_DiFUB(BoostVecWeightedDiGraphU g_boost, # distances respectively. # Now order_1 and order_2 will contain order of vertices in which # further distance computations will be done. - sorted(order_1, reverse=True) - sorted(order_2, reverse=True) + order_1 = sorted(order_1, reverse=True) + order_2 = sorted(order_2, reverse=True) LB = max(LB, LB_1, LB_2) if LB == sys.float_info.max: diff --git a/src/sage/graphs/digraph.py b/src/sage/graphs/digraph.py index 160795f5c4b..8d6c3891292 100644 --- a/src/sage/graphs/digraph.py +++ b/src/sage/graphs/digraph.py @@ -2490,6 +2490,17 @@ def diameter(self, by_weight=False, algorithm=None, weight_function=None, Traceback (most recent call last): ... ValueError: diameter is not defined for the empty DiGraph + + :trac:`32095` is fixed:: + + sage: g6 = 'guQOUOQCW[IaDBCVP_IE\\RfxV@WMSaeHgheEIA@tfOJkB~@EpGLCrs' + sage: g6 += 'aPIpwgQI_`Abs_x?VWxNJAo@w\\hffCDAW]bYGMIZGC_PYOrIw[Gp[' + sage: g6 += '@FTgc_O}E?fXAnGCB{gSaUcD' + sage: G = Graph(g6).to_directed() + sage: G.diameter(algorithm='DiFUB', by_weight=False) + 3 + sage: G.diameter(algorithm='DiFUB', by_weight=True) + 3.0 """ if not self.order(): raise ValueError("diameter is not defined for the empty DiGraph") diff --git a/src/sage/graphs/generators/random.py b/src/sage/graphs/generators/random.py index e06bc8a6d87..51995c35296 100644 --- a/src/sage/graphs/generators/random.py +++ b/src/sage/graphs/generators/random.py @@ -603,13 +603,21 @@ def RandomBoundedToleranceGraph(n): sage: g = graphs.RandomBoundedToleranceGraph(8) sage: g.clique_number() == g.chromatic_number() True + + TESTS: + + Check that :trac:`32186` is fixed:: + + sage: for _ in range(100): _ = graphs.RandomBoundedToleranceGraph(1) """ from sage.misc.prandom import randint + from sage.combinat.combination import Combinations from sage.graphs.generators.intersection import ToleranceGraph W = n ** 2 * 2 ** n + C = Combinations(W + 1, 2) - tolrep = [(l_r[0], l_r[1], randint(0, l_r[1] - l_r[0])) for l_r in [sorted((randint(0, W), randint(0, W))) for i in range(n)]] + tolrep = [(l_r[0], l_r[1], randint(1, l_r[1] - l_r[0])) for l_r in [C.random_element() for i in range(n)]] return ToleranceGraph(tolrep) @@ -1314,9 +1322,17 @@ def RandomTree(n): ....: for i in range(100) ) True + Random tree with one and zero vertices:: + + sage: graphs.RandomTree(0) + Graph on 0 vertices + sage: graphs.RandomTree(1) + Graph on 1 vertex """ from sage.misc.prandom import randint - g = Graph() + g = Graph(n) + if n <= 1: + return g # create random Prufer code code = [ randint(0,n-1) for i in range(n-2) ] @@ -1330,8 +1346,6 @@ def RandomTree(n): for k in code: count[k] += 1 - g.add_vertices(range(n)) - for s in code: for x in range(n): if count[x] == 0: diff --git a/src/sage/graphs/graph_decompositions/cutwidth.pyx b/src/sage/graphs/graph_decompositions/cutwidth.pyx index d4c1414d50a..0a8678b4a2b 100644 --- a/src/sage/graphs/graph_decompositions/cutwidth.pyx +++ b/src/sage/graphs/graph_decompositions/cutwidth.pyx @@ -358,6 +358,25 @@ def cutwidth(G, algorithm="exponential", cut_off=0, solver=None, verbose=False): Traceback (most recent call last): ... ValueError: the specified cut off parameter must be an integer + + Cutwidth of a graph with one edge (:trac:`32131`):: + + sage: from sage.graphs.graph_decompositions.cutwidth import cutwidth + sage: G = Graph([(0, 1)]) + sage: cutwidth(G, algorithm="exponential") + (1, [0, 1]) + sage: cutwidth(G, algorithm="MILP", solver='GLPK') + (1, [0, 1]) + + Cutwidth of a disconnected graph:: + + sage: from sage.graphs.graph_decompositions.cutwidth import cutwidth + sage: G = Graph(5) + sage: G.add_edge(2, 3) + sage: cutwidth(G, algorithm="exponential") + (1, [2, 3, 0, 1, 4]) + sage: cutwidth(G, algorithm="MILP", solver='GLPK') + (1, [2, 3, 0, 1, 4]) """ from sage.graphs.graph import Graph @@ -394,10 +413,10 @@ def cutwidth(G, algorithm="exponential", cut_off=0, solver=None, verbose=False): else: # We have a connected graph and we call the desired algorithm if algorithm == "exponential": - cwH, LH = cutwidth_dyn(G, lower_bound=this_cut_off) + cwH, LH = cutwidth_dyn(H, lower_bound=this_cut_off) elif algorithm == "MILP": - cwH, LH = cutwidth_MILP(G, lower_bound=this_cut_off, solver=solver, verbose=verbose) + cwH, LH = cutwidth_MILP(H, lower_bound=this_cut_off, solver=solver, verbose=verbose) else: raise ValueError('algorithm "{}" has not been implemented yet, please contribute'.format(algorithm)) @@ -491,7 +510,7 @@ def cutwidth_dyn(G, lower_bound=0): cdef list order try: - for k in range(lower_bound, G.size()): + for k in range(lower_bound, G.size() + 1): for i in range(g.n): sig_check() if exists(g, neighborhoods, 0, 0, i, k) <= k: diff --git a/src/sage/graphs/graph_editor.py b/src/sage/graphs/graph_editor.py index 1d6830535a6..e56c443b8f5 100644 --- a/src/sage/graphs/graph_editor.py +++ b/src/sage/graphs/graph_editor.py @@ -1,7 +1,7 @@ r""" Graph editor """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2009 Radoslav Kirov # # Distributed under the terms of the GNU General Public License (GPL) @@ -13,15 +13,13 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** import sys from .graph_generators import graphs from sage.misc.html import html -from sage.server.support import EMBEDDED_MODE - def graph_to_js(g): """ @@ -108,10 +106,9 @@ def graph_editor(graph=None, graph_name=None, if graph is None: graph = graphs.CompleteGraph(2) - if not EMBEDDED_MODE: - return "This graph editor only runs in the Sage notebook." + return "This graph editor only runs in the deprecated Sage notebook." - graph.layout(save_pos = True, **layout_options) + graph.layout(save_pos=True, **layout_options) if graph_name is None: graph_name = '' diff --git a/src/sage/groups/abelian_gps/abelian_group.py b/src/sage/groups/abelian_gps/abelian_group.py index 6567ac6c18b..514f68fd629 100644 --- a/src/sage/groups/abelian_gps/abelian_group.py +++ b/src/sage/groups/abelian_gps/abelian_group.py @@ -191,7 +191,7 @@ immutables. Rename invariants to gens_orders. """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2006 William Stein # Copyright (C) 2006 David Joyner # Copyright (C) 2012 Volker Braun @@ -200,8 +200,8 @@ # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from sage.rings.integer import Integer from sage.rings.integer_ring import ZZ @@ -1161,8 +1161,8 @@ def random_element(self): EXAMPLES:: sage: G = AbelianGroup([2,3,9]) - sage: G.random_element() - f1^2 + sage: G.random_element().parent() is G + True """ from sage.misc.prandom import randint result = self.one() @@ -1415,22 +1415,22 @@ def subgroups(self, check=False): INPUT: - - check: if True, performs the same computation in GAP and checks that - the number of subgroups generated is the same. (I don't know how to - convert GAP's output back into Sage, so we don't actually compare the - subgroups). + - check: if ``True``, performs the same computation in GAP and + checks that the number of subgroups generated is the + same. (I don't know how to convert GAP's output back into + Sage, so we don't actually compare the subgroups). ALGORITHM: - If the group is cyclic, the problem is easy. Otherwise, write it as - a direct product A x B, where B is cyclic. Compute the subgroups of - A (by recursion). + If the group is cyclic, the problem is easy. Otherwise, write it as + a direct product A x B, where B is cyclic. Compute the subgroups of + A (by recursion). - Now, for every subgroup C of A x B, let G be its *projection onto* - A and H its *intersection with* B. Then there is a well-defined - homomorphism f: G -> B/H that sends a in G to the class mod H of b, - where (a,b) is any element of C lifting a; and every subgroup C - arises from a unique triple (G, H, f). + Now, for every subgroup C of A x B, let G be its *projection onto* + A and H its *intersection with* B. Then there is a well-defined + homomorphism f: G -> B/H that sends a in G to the class mod H of b, + where (a,b) is any element of C lifting a; and every subgroup C + arises from a unique triple (G, H, f). .. TODO:: @@ -1460,6 +1460,9 @@ def subgroups(self, check=False): sage: B.subgroups() [Multiplicative Abelian subgroup isomorphic to C2 generated by {f1}, Trivial Abelian subgroup] + sage: B.subgroups(check=True) + [Multiplicative Abelian subgroup isomorphic to C2 generated by {f1}, + Trivial Abelian subgroup] """ if not self.is_finite(): raise ValueError("group must be finite") @@ -1469,7 +1472,7 @@ def subgroups(self, check=False): return [self] if self.ngens() == 1: n = self.gen(0).order() - return [ self.subgroup([self.gen(0)**i]) for i in divisors(n) ] + return [self.subgroup([self.gen(0)**i]) for i in divisors(n)] v = self.gens_orders() A = AbelianGroup(v[:-1]) @@ -1484,28 +1487,29 @@ def subgroups(self, check=False): verbose("invariants are: %s" % [t.order() for t in G.gens()]) for H in divisors(x): # H = the subgroup of *index* H. - its = [range(0, H, H/gcd(H, G.gen(i).order())) for i in range(ngens)] + its = [range(0, H, H // gcd(H, G.gen(i).order())) + for i in range(ngens)] for f in cartesian_product_iterator(its): - verbose("using hom from G to C_%s sending gens to %s" % (H,f)) + verbose("using hom from G to C_%s sending gens to %s" % (H, f)) new_sub = [] for a in range(ngens): val = G.gen(a).list() + [f[a]] if any(l != 0 for l in val): new_sub.append(val) if H != x: - new_sub.append([0]*A.ngens() + [H]) + new_sub.append([0] * A.ngens() + [H]) subgps.append(self.subgroup_reduced(new_sub)) if check: - from sage.interfaces.all import gap verbose("Running Gap cross-check") - t = ZZ(gap.eval("Size(SubgroupsSolvableGroup(AbelianGroup(%s)))" % v)) + from sage.libs.gap.libgap import libgap + t = libgap(v).AbelianGroup().SubgroupsSolvableGroup().Size().sage() if t != len(subgps): raise ArithmeticError("For %s Gap finds %s subgroups, I found %s" % (v, t, len(subgps))) verbose("Gap check OK for %s: %s" % (v, t)) return subgps - def subgroup_reduced(self,elts, verbose=False): + def subgroup_reduced(self, elts, verbose=False): r""" Given a list of lists of integers (corresponding to elements of self), find a set of independent generators for the subgroup generated by diff --git a/src/sage/groups/abelian_gps/dual_abelian_group.py b/src/sage/groups/abelian_gps/dual_abelian_group.py index 2e65e7f124a..b61e4b9654d 100644 --- a/src/sage/groups/abelian_gps/dual_abelian_group.py +++ b/src/sage/groups/abelian_gps/dual_abelian_group.py @@ -217,8 +217,8 @@ def random_element(self): sage: G = AbelianGroup([2,3,9]) sage: Gd = G.dual_group(base_ring=CC) - sage: Gd.random_element() - X1^2 + sage: Gd.random_element().parent() is Gd + True sage: N = 43^2-1 sage: G = AbelianGroup([N],names="a") @@ -226,10 +226,10 @@ def random_element(self): sage: a, = G.gens() sage: A, = Gd.gens() sage: x = a^(N/4); y = a^(N/3); z = a^(N/14) - sage: X = A*Gd.random_element(); X - A^615 - sage: len([a for a in [x,y,z] if abs(X(a)-1)>10^(-8)]) - 2 + sage: found = [False]*4 + sage: while not all(found): + ....: X = A*Gd.random_element() + ....: found[len([b for b in [x,y,z] if abs(X(b)-1)>10^(-8)])] = True """ from sage.misc.prandom import randint result = self.one() diff --git a/src/sage/groups/additive_abelian/qmodnz.py b/src/sage/groups/additive_abelian/qmodnz.py index c3f17bb150e..344344234a6 100644 --- a/src/sage/groups/additive_abelian/qmodnz.py +++ b/src/sage/groups/additive_abelian/qmodnz.py @@ -10,8 +10,8 @@ You can create random elements:: - sage: [G.random_element() for _ in range(4)] - [15/16, 0, 1/2, 139/190] + sage: all(G.random_element().parent() is G for _ in range(4)) + True There is an iterator over the (infinitely many) elements:: @@ -192,12 +192,8 @@ def random_element(self): EXAMPLES:: sage: G = QQ/(6*ZZ) - sage: G.random_element() - 47/16 - sage: G.random_element() - 1 - sage: G.random_element() - 3/5 + sage: G.random_element().parent() is G + True """ if self.n == 0: return self(QQ.random_element()) diff --git a/src/sage/groups/additive_abelian/qmodnz_element.py b/src/sage/groups/additive_abelian/qmodnz_element.py index 232fdf5a9eb..c5a857468c4 100644 --- a/src/sage/groups/additive_abelian/qmodnz_element.py +++ b/src/sage/groups/additive_abelian/qmodnz_element.py @@ -55,8 +55,8 @@ def __init__(self, parent, x, construct=False): EXAMPLES:: sage: G = QQ/(3*ZZ) - sage: G.random_element() - 47/16 + sage: G.random_element().parent() is G + True """ AdditiveGroupElement.__init__(self, parent) diff --git a/src/sage/groups/affine_gps/affine_group.py b/src/sage/groups/affine_gps/affine_group.py index 6c1629f6dc8..e02d5ef425f 100644 --- a/src/sage/groups/affine_gps/affine_group.py +++ b/src/sage/groups/affine_gps/affine_group.py @@ -514,10 +514,12 @@ def some_elements(self): [0 1 0 0] [0] x |-> [0 0 1 0] x + [0] [0 0 0 1] [0], - [2 0 0 0] [0] - [0 1 0 0] [2] - x |-> [0 0 1 0] x + [0] - [0 0 0 1] [1]] + [2 0 0 0] [...] + [0 1 0 0] [...] + x |-> [0 0 1 0] x + [...] + [0 0 0 1] [...]] + sage: all(v.parent() is G for v in G.some_elements()) + True sage: G = AffineGroup(2,QQ) sage: G.some_elements() diff --git a/src/sage/groups/braid.py b/src/sage/groups/braid.py index 81d09d8e369..a7fc91b1e7b 100644 --- a/src/sage/groups/braid.py +++ b/src/sage/groups/braid.py @@ -2077,7 +2077,7 @@ def mapping_class_action(self, F): A :class:`MappingClassGroupAction`. - EXAMPLES :: + EXAMPLES:: sage: B = BraidGroup(3) sage: B.inject_variables() diff --git a/src/sage/groups/class_function.py b/src/sage/groups/class_function.py index e86c32e589d..60d0318ac97 100644 --- a/src/sage/groups/class_function.py +++ b/src/sage/groups/class_function.py @@ -84,7 +84,6 @@ class function on the conjugacy classes, in that order. ### ##################################################################### - @richcmp_method class ClassFunction_gap(SageObject): """ @@ -104,6 +103,7 @@ class ClassFunction_gap(SageObject): sage: loads(dumps(chi)) == chi True """ + def __init__(self, G, values): r""" Return the character of the group ``G`` with values given by the list @@ -140,7 +140,6 @@ def _gap_init_(self): """ return str(self._gap_classfunction) - def _gap_(self, *args): r""" Coerce self into a GAP element. @@ -160,7 +159,6 @@ def _gap_(self, *args): """ return self._gap_classfunction - def __repr__(self): r""" Return a string representation. @@ -178,7 +176,6 @@ def __repr__(self): """ return "Character of %s" % repr(self._group) - def __iter__(self): r""" Iterate through the values of self evaluated on the conjugacy @@ -223,6 +220,16 @@ def __richcmp__(self, other, op): else: return NotImplemented + def __hash__(self): + r""" + TESTS:: + + sage: G = SymmetricGroup(5) + sage: chi1 = ClassFunction(G,[1,1,1,1,1,1,1]) + sage: d = {chi1:'trivial'} + """ + return hash((self._group, tuple(self))) + def __reduce__(self): r""" Add pickle support. diff --git a/src/sage/groups/perm_gps/permgroup.py b/src/sage/groups/perm_gps/permgroup.py index e379fcf9d4a..d6660002817 100644 --- a/src/sage/groups/perm_gps/permgroup.py +++ b/src/sage/groups/perm_gps/permgroup.py @@ -327,8 +327,11 @@ def PermutationGroup(gens=None, gap_group=None, domain=None, canonicalize=True, True sage: G = PermutationGroup([[(1,2,3),(4,5)],[(3,4)]]) sage: current_randstate().set_seed_gap() - sage: G._gap_().DerivedSeries() - [ Group( [ (3,4), (1,2,3)(4,5) ] ), Group( [ (1,5)(3,4), (1,5)(2,4), (1,3,5) ] ) ] + sage: G1, G2 = G._gap_().DerivedSeries() + sage: G1 + Group( [ (3,4), (1,2,3)(4,5) ] ) + sage: G2.GeneratorsSmallest() + [ (3,4,5), (2,3)(4,5), (1,2)(4,5) ] TESTS:: @@ -1549,12 +1552,12 @@ def orbit(self, point, action="OnPoints"): Action of `S_4` on sets of disjoint sets:: sage: S4 = groups.permutation.Symmetric(4) - sage: O = S4.orbit(((1,2),(3,4)), action = "OnSetsDisjointSets") + sage: O = S4.orbit(((1,2),(3,4)), action="OnSetsDisjointSets") sage: {1, 2} in O[0] and {3, 4} in O[0] True sage: {1, 4} in O[1] and {2, 3} in O[1] True - sage: all(set(union(*x)) == {1,2,3,4} for x in O) + sage: all(x[0].union(x[1]) == {1,2,3,4} for x in O) True Action of `S_4` (on a nonstandard domain) on tuples of sets:: @@ -2890,8 +2893,12 @@ def as_finitely_presented_group(self, reduced=False): sage: S = SymmetricGroup(6) sage: perm_ls = [S.random_element() for i in range(3)] sage: G = PermutationGroup(perm_ls) - sage: G.as_finitely_presented_group().as_permutation_group().is_isomorphic(G) - True + sage: while True: + ....: try: + ....: assert G.as_finitely_presented_group().as_permutation_group().is_isomorphic(G) # sometimes results in GAP error (see :trac:`32141`) + ....: break + ....: except ValueError: + ....: pass `D_9` is the only non-Abelian group of order 18 with an automorphism group of order 54 [TW1980]_:: diff --git a/src/sage/interfaces/fricas.py b/src/sage/interfaces/fricas.py index 0d8539087e7..16563a8e044 100644 --- a/src/sage/interfaces/fricas.py +++ b/src/sage/interfaces/fricas.py @@ -207,7 +207,7 @@ from sage.rings.rational_field import QQ from sage.rings.infinity import infinity from sage.misc.lazy_import import lazy_import -lazy_import('sage.libs.pynac.pynac', ['symbol_table']) +lazy_import('sage.libs.pynac.pynac', ['symbol_table', 'register_symbol']) lazy_import('sage.calculus.var', ['var', 'function']) lazy_import('sage.symbolic.constants', ['I', 'e', 'pi']) @@ -332,6 +332,8 @@ def _start(self): self.eval(FRICAS_LINENUMBER_OFF_CODE, reformat=False) for line in FRICAS_HELPER_CODE: self.eval(line, reformat=False) + # register translations between SymbolicRing and FriCAS Expression + self._register_symbols() def _install_hints(self): """ @@ -573,6 +575,81 @@ def _check_errors(self, line, output): if FRICAS_ERROR_IN_LIBRARY_CODE in output: raise RuntimeError("An error occurred when FriCAS evaluated '%s':\n%s" % (line, output)) + @staticmethod + def _register_symbols(): + """ + Register translations between elements of the ``SymbolicRing`` and FriCAS ``Expression`` domain. + + This method is called from :meth:`_start`, to work around a + circular import problem involving ``pi``. + + """ + from sage.calculus.functional import diff + from sage.functions.log import dilog, lambert_w + from sage.functions.trig import sin, cos, tan, cot, sec, csc + from sage.functions.hyperbolic import tanh, sinh, cosh, coth, sech, csch + from sage.functions.other import abs + from sage.functions.gamma import gamma + from sage.misc.functional import symbolic_sum, symbolic_prod + register_symbol(pi, {'fricas': 'pi'}) # pi is also a function in fricas + register_symbol(cos, {'fricas': 'cos'}) + register_symbol(sin, {'fricas': 'sin'}) + register_symbol(tan, {'fricas': 'tan'}) + register_symbol(cot, {'fricas': 'cot'}) + register_symbol(sec, {'fricas': 'sec'}) + register_symbol(csc, {'fricas': 'csc'}) + register_symbol(tanh, {'fricas': 'tanh'}) + register_symbol(sinh, {'fricas': 'sinh'}) + register_symbol(cosh, {'fricas': 'cosh'}) + register_symbol(coth, {'fricas': 'coth'}) + register_symbol(sech, {'fricas': 'sech'}) + register_symbol(csch, {'fricas': 'csch'}) + register_symbol(gamma, {'fricas': 'Gamma'}) + register_symbol(lambda x, y: x + y, {'fricas': '+'}) + register_symbol(lambda x, y: x - y, {'fricas': '-'}) + register_symbol(lambda x, y: x * y, {'fricas': '*'}) + register_symbol(lambda x, y: x / y, {'fricas': '/'}) + register_symbol(lambda x, y: x ** y, {'fricas': '^'}) + register_symbol(lambda f, x: diff(f, x), {'fricas': 'D'}) + register_symbol(lambda x, y: x + y * I, {'fricas': 'complex'}) + register_symbol(lambda x: dilog(1 - x), {'fricas': 'dilog'}) + register_symbol(lambda z: lambert_w(z), {'fricas': 'lambertW'}) + register_symbol(abs, {'fricas': 'abs'}) + # construct occurs in the InputForm of hypergeometricF + register_symbol(lambda *x: x, {'fricas': 'construct'}) + # the following is a hack to deal with + # integrate(sin((x^2+1)/x),x)::INFORM giving + # (integral (sin (/ (+ (^ x 2) 1) x)) (:: x Symbol)) + register_symbol(lambda x, y: x, {'fricas': '::'}) + + def _convert_eval(f, a, b): + # it might be that FriCAS also returns a two-argument + # eval, where the second argument is a list of equations, + # in which case this function needs to be adapted + return f.subs({a: b}) + + register_symbol(_convert_eval, {'fricas': 'eval'}) + + def _convert_sum(x, y): + v, seg = y.operands() + a, b = seg.operands() + return symbolic_sum(x, v, a, b) + + def _convert_prod(x, y): + v, seg = y.operands() + a, b = seg.operands() + return symbolic_prod(x, v, a, b) + + register_symbol(_convert_sum, {'fricas': 'sum'}) + register_symbol(_convert_prod, {'fricas': 'product'}) + + def explicitly_not_implemented(*args): + raise NotImplementedError("the translation of the FriCAS Expression '%s' to sage is not yet implemented" % args) + + register_symbol(lambda *args: explicitly_not_implemented("rootOfADE"), {'fricas': 'rootOfADE'}) + register_symbol(lambda *args: explicitly_not_implemented("rootOfRec"), {'fricas': 'rootOfRec'}) + + def set(self, var, value): """ Set a variable to a value in FriCAS. @@ -1612,73 +1689,32 @@ def _sage_expression(fricas_InputForm): sage: fricas.Gamma(3, 2).sage() # optional - fricas gamma(3, 2) - """ - from sage.libs.pynac.pynac import register_symbol - from sage.calculus.functional import diff - from sage.functions.log import dilog, lambert_w - from sage.functions.trig import sin, cos, tan, cot, sec, csc - from sage.functions.hyperbolic import tanh, sinh, cosh, coth, sech, csch - from sage.functions.other import abs - from sage.functions.gamma import gamma - from sage.misc.functional import symbolic_sum, symbolic_prod - register_symbol(pi, {'fricas': 'pi'}) # pi is also a function in fricas - register_symbol(cos, {'fricas': 'cos'}) - register_symbol(sin, {'fricas': 'sin'}) - register_symbol(tan, {'fricas': 'tan'}) - register_symbol(cot, {'fricas': 'cot'}) - register_symbol(sec, {'fricas': 'sec'}) - register_symbol(csc, {'fricas': 'csc'}) - register_symbol(tanh, {'fricas': 'tanh'}) - register_symbol(sinh, {'fricas': 'sinh'}) - register_symbol(cosh, {'fricas': 'cosh'}) - register_symbol(coth, {'fricas': 'coth'}) - register_symbol(sech, {'fricas': 'sech'}) - register_symbol(csch, {'fricas': 'csch'}) - register_symbol(gamma, {'fricas': 'Gamma'}) - register_symbol(lambda x, y: x + y, {'fricas': '+'}) - register_symbol(lambda x, y: x - y, {'fricas': '-'}) - register_symbol(lambda x, y: x * y, {'fricas': '*'}) - register_symbol(lambda x, y: x / y, {'fricas': '/'}) - register_symbol(lambda x, y: x ** y, {'fricas': '^'}) - register_symbol(lambda f, x: diff(f, x), {'fricas': 'D'}) - register_symbol(lambda x, y: x + y * I, {'fricas': 'complex'}) - register_symbol(lambda x: dilog(1 - x), {'fricas': 'dilog'}) - register_symbol(lambda z: lambert_w(z), {'fricas': 'lambertW'}) - register_symbol(abs, {'fricas': 'abs'}) - # construct occurs in the InputForm of hypergeometricF - register_symbol(lambda *x: x, {'fricas': 'construct'}) - # the following is a hack to deal with - # integrate(sin((x^2+1)/x),x)::INFORM giving - # (integral (sin (/ (+ (^ x 2) 1) x)) (:: x Symbol)) - register_symbol(lambda x, y: x, {'fricas': '::'}) - def _convert_eval(f, a, b): - # it might be that FriCAS also returns a two-argument - # eval, where the second argument is a list of equations, - # in which case this function needs to be adapted - return f.subs({a: b}) + Check that :trac:`32133` is fixed:: - register_symbol(_convert_eval, {'fricas': 'eval'}) + sage: var("y") + y + sage: f = fricas.zerosOf(y^4 + y + 1, y); f # optional - fricas + +-----------------------------+ + | 2 2 + \|- 3 %y1 - 2 %y0 %y1 - 3 %y0 - %y1 - %y0 + [%y0, %y1, --------------------------------------------, + 2 + +-----------------------------+ + | 2 2 + - \|- 3 %y1 - 2 %y0 %y1 - 3 %y0 - %y1 - %y0 + ----------------------------------------------] + 2 - def _convert_sum(x, y): - v, seg = y.operands() - a, b = seg.operands() - return symbolic_sum(x, v, a, b) - - def _convert_prod(x, y): - v, seg = y.operands() - a, b = seg.operands() - return symbolic_prod(x, v, a, b) - - register_symbol(_convert_sum, {'fricas': 'sum'}) - register_symbol(_convert_prod, {'fricas': 'product'}) - - def explicitly_not_implemented(*args): - raise NotImplementedError("the translation of the FriCAS Expression '%s' to sage is not yet implemented" % args) - - register_symbol(lambda *args: explicitly_not_implemented("rootOfADE"), {'fricas': 'rootOfADE'}) - register_symbol(lambda *args: explicitly_not_implemented("rootOfRec"), {'fricas': 'rootOfRec'}) + sage: f[1].sage() # optional - fricas + -1/2*sqrt(1/3)*sqrt((3*(1/18*I*sqrt(229)*sqrt(3) + 1/2)^(2/3) + 4)/(1/18*I*sqrt(229)*sqrt(3) + 1/2)^(1/3)) + 1/2*sqrt(-(1/18*I*sqrt(229)*sqrt(3) + 1/2)^(1/3) + 6*sqrt(1/3)/sqrt((3*(1/18*I*sqrt(229)*sqrt(3) + 1/2)^(2/3) + 4)/(1/18*I*sqrt(229)*sqrt(3) + 1/2)^(1/3)) - 4/3/(1/18*I*sqrt(229)*sqrt(3) + 1/2)^(1/3)) + """ + # a FriCAS expressions may contain implicit references to a + # rootOf expression within itself, as for example in the + # result of integrate(1/(1+x^5), x). Each algebraic number + # appearing in the expression is only introduced once and + # assigned a variable (usually of the form %%...). rootOf = dict() # (variable, polynomial) rootOf_ev = dict() # variable -> (complex) algebraic number @@ -1706,20 +1742,24 @@ def convert_rootOf(x, y): assert False, "circular dependency in rootOf expression" # substitute known roots poly = poly.subs(rootOf_ev) - evars = [v for v in rvars if v not in rootOf] # extraneous variables - assert set(evars) == set(poly.variables()).difference([var]) + evars = set(poly.variables()).difference([var]) del rootOf[var] if evars: - # we just need any root per FriCAS specification + # we just need any root per FriCAS specification - + # however, if there are extra variables, we cannot + # use QQbar.any_root rootOf_ev[var] = poly.roots(var, multiplicities=False)[0] else: R = PolynomialRing(QQbar, "x") - # PolynomialRing does not accept variable names with leading underscores + # PolynomialRing does not accept variable names with + # leading underscores poly = R(poly.subs({var: R.gen()})) # we just need any root per FriCAS specification - rootOf_ev[var] = poly.roots(multiplicities=False)[0].radical_expression() + rootOf_ev[var] = poly.any_root() - return ex.subs(rootOf_ev) + return ex.subs({var: (val.radical_expression() + if val.parent() is QQbar else val) + for var, val in rootOf_ev.items()}) def _sage_(self): r""" diff --git a/src/sage/interfaces/interface.py b/src/sage/interfaces/interface.py index 3a0e4e9ff70..7ace8e6ba03 100644 --- a/src/sage/interfaces/interface.py +++ b/src/sage/interfaces/interface.py @@ -633,6 +633,7 @@ def __getattr__(self, attrname): """ TESTS:: + sage: from sage.structure.parent_base import ParentWithBase sage: ParentWithBase.__getattribute__(singular, '_coerce_map_from_') """ @@ -647,7 +648,7 @@ def console(self): raise NotImplementedError def help(self, s): - return AsciiArtString('No help on %s available'%s) + return AsciiArtString('No help on %s available' % s) @instancedoc @@ -660,7 +661,7 @@ def __init__(self, parent, name): self._name = name def _repr_(self): - return "%s"%self._name + return "%s" % self._name def __call__(self, *args, **kwds): return self._parent.function_call(self._name, list(args), kwds) diff --git a/src/sage/interfaces/maxima_abstract.py b/src/sage/interfaces/maxima_abstract.py index a9ac97fc34a..3fe2dd7bbab 100644 --- a/src/sage/interfaces/maxima_abstract.py +++ b/src/sage/interfaces/maxima_abstract.py @@ -62,7 +62,6 @@ from sage.misc.misc import ECL_TMP from sage.misc.multireplace import multiple_replace from sage.structure.richcmp import richcmp, rich_to_bool -import sage.server.support from .interface import (Interface, InterfaceElement, InterfaceFunctionElement, InterfaceFunction, AsciiArtString) @@ -166,9 +165,6 @@ def _command_runner(self, command, s, redirect=True): ... """ cmd = '{} --very-quiet --batch-string="{}({});" '.format(MAXIMA, command, s) - if sage.server.support.EMBEDDED_MODE: - cmd += '< /dev/null' - env = os.environ.copy() env['TMPDIR'] = str(ECL_TMP) @@ -674,9 +670,11 @@ def function(self, args, defn, rep=None, latex=None): ## represented in 2-d. ## INPUT: -## flag -- bool (default: True) -## EXAMPLES +## flag -- bool (default: True) + +## EXAMPLES:: + ## sage: maxima('1/2') ## 1/2 ## sage: maxima.display2d(True) diff --git a/src/sage/interfaces/polymake.py b/src/sage/interfaces/polymake.py index 521e1dc4f18..8ac83e87ad6 100644 --- a/src/sage/interfaces/polymake.py +++ b/src/sage/interfaces/polymake.py @@ -32,7 +32,7 @@ from .expect import Expect from .interface import (Interface, InterfaceElement, InterfaceFunctionElement) - +from sage.cpython.string import bytes_to_str, str_to_bytes from sage.misc.verbose import get_verbose from sage.misc.cachefunc import cached_method from sage.interfaces.tab_completion import ExtraTabCompletion @@ -112,29 +112,34 @@ class PolymakeAbstract(ExtraTabCompletion, Interface): but through its subclasses Polymake (Pexpect interface) or PolymakeJuPyMake (JuPyMake interface). - EXAMPLES: + EXAMPLES:: sage: from sage.interfaces.polymake import PolymakeAbstract, polymake_expect, polymake_jupymake We test the verbosity management with very early doctests because messages will not be repeated. - Testing the Pexpect interface:: + Testing the deprecated pexpect-based interface:: sage: type(polymake_expect) <...sage.interfaces.polymake.PolymakeExpect... sage: isinstance(polymake_expect, PolymakeAbstract) True - sage: p = polymake_expect.rand_sphere(4, 20, seed=5) # optional - polymake - sage: p # optional - polymake + sage: p = polymake_expect.rand_sphere(4, 20, seed=5) # optional - polymake_expect + doctest...: DeprecationWarning: the pexpect-based interface to + polymake is deprecated. + Install package jupymake so that Sage can use the more robust + jupymake-based interface to polymake + See https://trac.sagemath.org/27745 for details. + sage: p # optional - polymake_expect Random spherical polytope of dimension 4; seed=5... sage: set_verbose(3) - sage: p.H_VECTOR # optional - polymake + sage: p.H_VECTOR # optional - polymake_expect used package ppl The Parma Polyhedra Library ... 1 16 40 16 1 sage: set_verbose(0) - sage: p.F_VECTOR # optional - polymake + sage: p.F_VECTOR # optional - polymake_expect 20 94 148 74 Testing the JuPyMake interface:: @@ -173,8 +178,8 @@ def version(self): EXAMPLES:: - sage: polymake.version() # optional - polymake - '3...' + sage: polymake.version() # optional - polymake # random + '4...' TESTS:: @@ -182,10 +187,7 @@ def version(self): sage: Polymake(command='foobar').version() Traceback (most recent call last): ... - RuntimeError: unable to start polymake because the command 'foobar' failed: - The command was not found or was not executable: foobar. - Please install the optional polymake package for sage (but read its SPKG.txt first!) - or install polymake system-wide + RuntimeError: runtime error with deprecated pexpect-based interface to polymake; please install jupymake """ return self.get('$Polymake::Version') @@ -329,8 +331,50 @@ def convert(y): r = self.new("{" + ",".join(A) + "}") r.__sage_dict = z # do this to avoid having the entries of the list be garbage collected return r - else: - return super(PolymakeAbstract, self)._coerce_impl(x, use_special=use_special) + + from sage.rings.all import Integer, Rational, RDF + from sage.rings.number_field.number_field import is_QuadraticField + + def to_str(x): + if isinstance(x, list): + s = '[' + for y in x: + s += to_str(y) + ', ' + s += ']' + return s + if isinstance(x, (Integer, Rational, int)): + return '{}'.format(x) + parent = None + try: + parent = x.parent() + except AttributeError: + pass + + if is_QuadraticField(parent): + return x._polymake_init_() + try: + if x.parent().is_exact(): + # No other exact rings are supported. + raise NotImplementedError + except AttributeError: + pass + + try: + x = RDF(x) + return '{}'.format(x) + except: + pass + + raise NotImplementedError + + # Iteratively calling polymake for conversion takes a long time. + # However, it takes iterated arrays of integers, rationals and floats directly. + try: + return self.new(to_str(x)) + except NotImplementedError: + pass + + return super(PolymakeAbstract, self)._coerce_impl(x, use_special=use_special) def console(self): """ @@ -353,19 +397,20 @@ def _install_hints(self): TESTS:: sage: print(polymake._install_hints()) - Please install the optional polymake package for sage (but read its SPKG.txt first!) + Please install the optional polymake package for sage or install polymake system-wide + (use the shell command 'sage --info polymake' for more information) """ - return "Please install the optional polymake package for sage (but read its SPKG.txt first!)"+os.linesep+"or install polymake system-wide" + return "Please install the optional polymake package for sage" + os.linesep + "or install polymake system-wide" + os.linesep + "(use the shell command 'sage --info polymake' for more information)" def _start(self): """ Start the polymake interface in the application "polytope". - NOTE: + .. NOTE:: - There should be no need to call this explicitly. + There should be no need to call this explicitly. TESTS:: @@ -442,12 +487,12 @@ def _next_var_name(self): return r'SAGE{}'.format(self.__seq) def clear(self, var): - """ - Clear the variable named var. + r""" + Clear the variable named ``var``. - NOTE: + .. NOTE:: - This is implicitly done when deleting an element in the interface. + This is implicitly done when deleting an element in the interface. TESTS:: @@ -469,24 +514,24 @@ def _create(self, value, name=None): INPUT: - - ``value``, a string: Polymake command (or value) whose result + - ``value`` -- string; Polymake command (or value) whose result is to be assigned to a variable - - ``name``, optional string: If given, the new variable has this name. - Otherwise, the name is automatically generated. + - ``name`` -- (optional) string; if given, the new variable has this + name; otherwise, the name is automatically generated RETURN: The command by which the assigned value can now be retrieved. - NOTE: + .. NOTE:: - In order to overcome problems with the perl programming language, - we store *all* data as arrays. If the given value is an array - of length different from one, then the new variable contains that - array. Otherwise, the new variable is an array of length one whose - only entry is the given value, which has to be a scalar (which - also includes Perl references). In other words, perl hashes - are not suitable. + In order to overcome problems with the perl programming language, + we store *all* data as arrays. If the given value is an array + of length different from one, then the new variable contains that + array. Otherwise, the new variable is an array of length one whose + only entry is the given value, which has to be a scalar (which + also includes Perl references). In other words, perl hashes + are not suitable. EXAMPLES:: @@ -666,15 +711,15 @@ def help(self, topic, pager=True): return H def _tab_completion(self): - """ - Returns a list of polymake function names. + r""" + Return a list of polymake function names. - NOTE: + ..NOTE:: - - The list of functions depends on the current application. The - result is cached, of course separately for each application. - - It is generally not the case that all the returned function names - can actually successfully be called. + - The list of functions depends on the current application. The + result is cached, of course separately for each application. + - It is generally not the case that all the returned function + names can actually successfully be called. TESTS:: @@ -683,19 +728,18 @@ def _tab_completion(self): True sage: polymake.application('polytope') # optional - polymake - Since 'normal_fan' is not defined in the polymake application 'polytope', - we now get - :: + Since ``'normal_fan'`` is not defined in the polymake application + ``'polytope'``, we now get:: sage: 'normal_fan' in dir(polymake) # optional - polymake False - Global functions from 'core' are available:: + Global functions from ``'core'`` are available:: sage: 'show_credits' in dir(polymake) # optional - polymake True - Global functions from 'common' are available:: + Global functions from ``'common'`` are available:: sage: 'lex_ordered' in dir(polymake) # optional - polymake True @@ -709,7 +753,9 @@ def _tab_completion(self): s = self.eval("apropos '';").split('\n') out = [] for name in s: - if name.startswith("/common/functions/") or name.startswith("/core/functions") or name.startswith("/" + self._application + "/functions/"): + if (name.startswith("/common/functions/") + or name.startswith("/core/functions") + or name.startswith("/" + self._application + "/functions/")): out.append(name.split("/")[-1]) self.__tab_completion[self._application] = sorted(out) return self.__tab_completion[self._application] @@ -1040,12 +1086,12 @@ def known_properties(self): """ List the names of properties that have been computed so far on this element. - NOTE: + .. NOTE:: - This is in many cases equivalent to use polymake's ``list_properties``, - which returns a blank separated string representation of the list of properties. - However, on some elements, ``list_properties`` would simply result in - an error. + This is in many cases equivalent to use polymake's + ``list_properties``, which returns a blank separated string + representation of the list of properties. However, on some + elements, ``list_properties`` would simply result in an error. EXAMPLES:: @@ -1173,14 +1219,14 @@ def _tab_completion(self): """ Return a list of available function and property names. - NOTE: + .. NOTE:: - This currently returns the names of functions defined in the current - application, regardless whether they can be applied to this element - or not, together with the list of properties of this element that - polymake knows how to compute. It does not contain the list of available - member functions of this element. This may change in future versions - of polymake. + This currently returns the names of functions defined in the current + application, regardless whether they can be applied to this element + or not, together with the list of properties of this element that + polymake knows how to compute. It does not contain the list of available + member functions of this element. This may change in future versions + of polymake. EXAMPLES:: @@ -1201,16 +1247,16 @@ def __getattr__(self, attrname): Return a property of this element, or a polymake function with this element as first argument, or a member function of this element. - NOTE: + .. NOTE:: - If the attribute name is known as the name of a property, it is - interpreted as such. Otherwise, if it is known as a function in - the current application, the function is returned with this - element inserted as first argument, and potential further arguments, - when called. Otherwise, it is assumed that it is a member function - of this element, and treated as such. Note that member functions - are currently invisible in tab completion, thus, the user has - to know the name of the member function. + If the attribute name is known as the name of a property, it is + interpreted as such. Otherwise, if it is known as a function in + the current application, the function is returned with this + element inserted as first argument, and potential further arguments, + when called. Otherwise, it is assumed that it is a member function + of this element, and treated as such. Note that member functions + are currently invisible in tab completion, thus, the user has + to know the name of the member function. EXAMPLES: @@ -1265,10 +1311,10 @@ def get_member_function(self, attrname): """ Request a member function of this element. - NOTE: + .. NOTE:: - It is not checked whether a member function with the given name - exists. + It is not checked whether a member function with the given name + exists. EXAMPLES:: @@ -1298,11 +1344,11 @@ def get_member(self, attrname): """ Get a member/property of this element. - NOTE: + .. NOTE:: - Normally, it should be possible to just access the property - in the usual Python syntax for attribute access. However, if - that fails, one can request the member explicitly. + Normally, it should be possible to just access the property + in the usual Python syntax for attribute access. However, if + that fails, one can request the member explicitly. EXAMPLES:: @@ -1420,11 +1466,12 @@ def __len__(self): @cached_method def typeof(self): """ - Returns the type of a polymake "big" object, and its underlying Perl type. + Return the type of a polymake "big" object, and its + underlying Perl type. - NOTE: + .. NOTE:: - This is mainly for internal use. + This is mainly for internal use. EXAMPLES:: @@ -1500,11 +1547,55 @@ def _sage_(self): sage: polymake.cube(3).sage() # optional - polymake A 3-dimensional polyhedron in QQ^3 defined as the convex hull of 8 vertices sage: polymake.icosahedron().sage() # optional - polymake - A 3-dimensional polyhedron in AA^3 defined as the convex hull of 12 vertices + A 3-dimensional polyhedron in + (Number Field in a with defining polynomial x^2 - 5 with a = 2.236067977499790?)^3 + defined as the convex hull of 12 vertices """ T1, T2 = self.typeof() self._check_valid() + try: + # Try to just read things from the string representation. + if 'Sparse' in T1: + raise NotImplementedError + + r = self._repr_() + if 'Float' in T1: + from sage.rings.all import RDF + base_ring = RDF + str_to_base_ring = lambda s: RDF(s) + elif 'QuadraticExtension' in T1 and 'r' in r: + i = r.find('r') + i1 = min((r[i:]+' ').find(' '), (r[i:]+'\n').find('\n')) + d = int(r[i+1:i+i1]) + from sage.rings.number_field.number_field import QuadraticField + base_ring = QuadraticField(d) + + def str_to_base_ring(s): + m = re.match(r'(-?[0-9/]+)[+]?((-?[0-9/]+)r([0-9/]+))?', s) + a, b = m.group(1), m.group(3) + return base_ring(a) + base_ring(b)*base_ring.gen() + + elif 'Rational' in T1: + from sage.rings.all import QQ + base_ring = QQ + str_to_base_ring = lambda s: QQ(s) + else: + raise NotImplementedError + + if 'Vector' in T1: + from sage.modules.free_module_element import vector + if r == '': + return vector(base_ring) + return vector(base_ring, [str_to_base_ring(s) for s in r.split(' ')]) + elif 'Matrix' in T1: + from sage.matrix.constructor import matrix + if r == '': + return matrix(base_ring) + return matrix(base_ring, [[str_to_base_ring(s) for s in t.split(' ')] for t in r.split('\n')]) + except: + pass + if T1: Temp = self.typename() if Temp: @@ -1531,17 +1622,7 @@ def _sage_(self): return matrix([x.sage() for x in self]) elif T1 == 'Polytope': from sage.geometry.polyhedron.backend_polymake import Polyhedron_polymake - from sage.geometry.polyhedron.parent import Polyhedra - from sage.rings.rational_field import QQ - from sage.rings.qqbar import AA - if self.typeof()[0] == 'Polymake::polytope::Polytope__Rational': - base_ring = QQ - else: - # We could try to find out a more specific field. - base_ring = AA - ambient_dim = self.AMBIENT_DIM() - parent = Polyhedra(base_ring, ambient_dim, backend='polymake') - return Polyhedron_polymake._from_polymake_polytope(parent, self) + return Polyhedron_polymake._from_polymake_polytope(None, self) else: return super(PolymakeElement, self)._sage_() @@ -1681,7 +1762,7 @@ def __call__(self, *args, **kwds): sage: p.get_schedule('"VERTICES"') # optional - polymake # random sensitivity check for VertexPerm cdd.convex_hull.canon: POINTED, RAYS, LINEALITY_SPACE : INPUT_RAYS - sage: p.minkowski_sum_fukuda(p).F_VECTOR # optional - polymake + sage: p.minkowski_sum_fukuda(p).F_VECTOR # optional - polymake # not tested 13 33 22 """ @@ -1693,10 +1774,10 @@ def _sage_doc_(self): """ Return documentation of this function. - NOTE: + .. NOTE:: - For unclear reasons, accessing documentation with `?` sometimes - does not include the return value of this method. + For unclear reasons, accessing documentation with ``?`` sometimes + does not include the return value of this method. EXAMPLES:: @@ -1766,19 +1847,19 @@ class PolymakeExpect(PolymakeAbstract, Expect): sage: from sage.interfaces.polymake import polymake_expect as polymake sage: type(polymake) <...sage.interfaces.polymake.PolymakeExpect... - sage: p = polymake.rand_sphere(4, 20, seed=5) # optional - polymake - sage: p # optional - polymake + sage: p = polymake.rand_sphere(4, 20, seed=5) # optional - polymake_expect + sage: p # optional - polymake_expect Random spherical polytope of dimension 4; seed=5... sage: set_verbose(3) - sage: p.H_VECTOR; # optional - polymake # random + sage: p.H_VECTOR; # optional - polymake_expect # random used package ppl The Parma Polyhedra Library ... - sage: p.H_VECTOR # optional - polymake + sage: p.H_VECTOR # optional - polymake_expect 1 16 40 16 1 sage: set_verbose(0) - sage: p.F_VECTOR # optional - polymake + sage: p.F_VECTOR # optional - polymake_expect 20 94 148 74 - sage: print(p.F_VECTOR._sage_doc_()) # optional - polymake # random + sage: print(p.F_VECTOR._sage_doc_()) # optional - polymake_expect # random property_types/Algebraic Types/Vector: A type for vectors with entries of type Element. @@ -1823,30 +1904,39 @@ def _start(self, alt_message=None): """ Start the polymake interface in the application "polytope". - NOTE: + .. NOTE:: - There should be no need to call this explicitly. + There should be no need to call this explicitly. TESTS:: sage: from sage.interfaces.polymake import polymake_expect as polymake - sage: polymake.application('fan') # optional - polymake - sage: 'normal_fan' in dir(polymake) # optional - polymake + sage: polymake.application('fan') # optional - polymake_expect + sage: 'normal_fan' in dir(polymake) # optional - polymake_expect True - sage: polymake.quit() # optional - polymake - sage: polymake._start() # optional - polymake + sage: polymake.quit() # optional - polymake_expect + sage: polymake._start() # optional - polymake_expect + doctest...: DeprecationWarning: the pexpect-based interface to + polymake is deprecated. + Install package jupymake so that Sage can use the more robust + jupymake-based interface to polymake + See https://trac.sagemath.org/27745 for details. Since 'normal_fan' is not defined in the polymake application 'polytope', - we now get - :: + we now get:: - sage: 'normal_fan' in dir(polymake) # optional - polymake + sage: 'normal_fan' in dir(polymake) # optional - polymake_expect False """ + from sage.misc.superseded import deprecation if not self.is_running(): - self._change_prompt("polytope > ") - Expect._start(self, alt_message=None) + try: + self._change_prompt("polytope > ") + Expect._start(self, alt_message=None) + except RuntimeError: + raise RuntimeError("runtime error with deprecated pexpect-based interface to polymake; please install jupymake") + deprecation(27745, "the pexpect-based interface to polymake is deprecated. Install package jupymake so that Sage can use the more robust jupymake-based interface to polymake") PolymakeAbstract._start(self) self.eval('use File::Slurp;') @@ -1861,16 +1951,16 @@ def _quit_string(self): return "exit;" def _keyboard_interrupt(self): - """ - Interrupt a computation with + r""" + Interrupt a computation with . TESTS: For reasons that are not clear to the author, the following test - is very flaky. Therefore, this test is marked as "not tested". + is very flaky. Therefore, this test is marked as "not tested". :: sage: from sage.interfaces.polymake import polymake_expect as polymake - sage: c = polymake.cube(15) # optional - polymake + sage: c = polymake.cube(15) # optional - polymake_expect sage: alarm(1) # not tested sage: try: # not tested # indirect doctest ....: c.F_VECTOR @@ -1886,7 +1976,7 @@ def _keyboard_interrupt(self): Afterwards, the interface should still be running. :: - sage: c.N_FACETS # optional - polymake + sage: c.N_FACETS # optional - polymake_expect 30 """ @@ -1918,32 +2008,32 @@ def _synchronize(self): TESTS:: sage: from sage.interfaces.polymake import polymake_expect as polymake - sage: Q = polymake.cube(4) # optional - polymake - sage: polymake('"ok"') # optional - polymake + sage: Q = polymake.cube(4) # optional - polymake_expect + sage: polymake('"ok"') # optional - polymake_expect ok - sage: polymake._expect.sendline() # optional - polymake + sage: polymake._expect.sendline() # optional - polymake_expect 1 Now the interface is badly out of sync:: - sage: polymake('"foobar"') # optional - polymake + sage: polymake('"foobar"') # optional - polymake_expect ) failed: - PolymakeError: Can't locate object method "description" via package "1" + ...PolymakeError: Can't locate object method "description" via package "1" (perhaps you forgot to load "1"?)...> - sage: Q.typeof() # optional - polymake # random + sage: Q.typeof() # optional - polymake_expect # random ('foobar...', 'Polymake::polytope::Polytope__Rational') - sage: Q.typeof.clear_cache() # optional - polymake + sage: Q.typeof.clear_cache() # optional - polymake_expect After synchronisation, things work again as expected:: - sage: polymake._synchronize() # optional - polymake + sage: polymake._synchronize() # optional - polymake_expect doctest:warning ... UserWarning: Polymake seems out of sync: The expected output did not appear before reaching the next prompt. - sage: polymake('"back to normal"') # optional - polymake + sage: polymake('"back to normal"') # optional - polymake_expect back to normal - sage: Q.typeof() # optional - polymake + sage: Q.typeof() # optional - polymake_expect ('Polymake::polytope::Polytope__Rational', 'ARRAY') """ @@ -1951,7 +2041,7 @@ def _synchronize(self): return rnd = randrange(2147483647) res = str(rnd+1) - cmd = 'print 1+{};' + self._expect.linesep + cmd = 'print 1+{};' + os.linesep self._sendstr(cmd.format(rnd)) pat = self._expect.expect(self._prompt, timeout=0.5) # 0: normal prompt @@ -1973,7 +2063,7 @@ def _synchronize(self): elif pat == 0: # We got the right prompt, but perhaps in a wrong position in the stream # The result of the addition should appear *before* our prompt - if res not in self._expect.before: + if res not in bytes_to_str(self._expect.before): try: warnings.warn("{} seems out of sync: The expected output did not appear before reaching the next prompt.".format(self)) while True: @@ -1999,35 +2089,37 @@ def _eval_line(self, line, allow_use_file=True, wait_for_prompt=True, restart_if INPUT: - - ``line``, a command (string) to be evaluated - - ``allow_use_file`` (optional bool, default ``True``), whether or not - to use a file if the line is very long. - - ``wait_for_prompt`` (optional, default ``True``), whether or not - to wait before polymake returns a prompt. If it is a string, it is considered - as alternative prompt to be waited for. - - ``restart_if_needed`` (optional bool, default ``True``), whether or + - ``line`` -- string; a command to be evaluated + - ``allow_use_file`` -- (default: ``True``) bool; whether or not + to use a file if the line is very long + - ``wait_for_prompt`` -- (default: ``True``) bool; whether or not + to wait before polymake returns a prompt. If it is a string, it is + considered as alternative prompt to be waited for + - ``restart_if_needed`` (default: ``True``) bool; whether or not to restart polymake in case something goes wrong - further optional arguments (e.g., timeout) that will be passed to :meth:`pexpect.pty_spawn.spawn.expect`. Note that they are ignored if the line is too long and thus is evaluated via a file. So, - if a timeout is defined, it should be accompanied by ``allow_use_file=False``. + if a timeout is defined, it should be accompanied by + ``allow_use_file=False``. Different reaction types of polymake, including warnings, comments, errors, request for user interaction, and yielding a continuation prompt, are taken into account. - Usually, this method is indirectly called via :meth:`~sage.interfaces.expect.Expect.eval`. + Usually, this method is indirectly called via + :meth:`~sage.interfaces.expect.Expect.eval`. EXAMPLES:: sage: from sage.interfaces.polymake import polymake_expect as polymake - sage: p = polymake.cube(3) # optional - polymake # indirect doctest + sage: p = polymake.cube(3) # optional - polymake_expect # indirect doctest Here we see that remarks printed by polymake are displayed if the verbosity is positive:: sage: set_verbose(1) - sage: p.N_LATTICE_POINTS # optional - polymake # random + sage: p.N_LATTICE_POINTS # optional - polymake_expect # random used package latte LattE (Lattice point Enumeration) is a computer software dedicated to the problems of counting lattice points and integration inside convex polytopes. @@ -2173,27 +2265,27 @@ def _eval_line(self, line, allow_use_file=True, wait_for_prompt=True, restart_if if self._terminal_echo: out = E.before else: - out = E.before.rstrip('\n\r') + out = E.before.rstrip(b'\n\r') if self._terminal_echo and first: - i = out.find("\n") - j = out.rfind("\r") - out = out[i + 1:j].replace('\r\n', '\n') + i = out.find(b"\n") + j = out.rfind(b"\r") + out = out[i + 1:j].replace(b'\r\n', b'\n') else: - out = out.strip().replace('\r\n', '\n') + out = out.strip().replace(b'\r\n', b'\n') first = False if have_error: p_errors.append(out) have_error = False - out = "" + out = b"" elif have_warning: p_warnings.append(out) have_warning = False - out = "" + out = b"" elif have_log: if get_verbose() > 0: - print(out) + print(bytes_to_str(out)) have_log = False - out = "" + out = b"" # 0: normal prompt # 1: continuation prompt # 2: user input expected when requestion "help" @@ -2228,12 +2320,12 @@ def _eval_line(self, line, allow_use_file=True, wait_for_prompt=True, restart_if warnings.warn("{} expects user interaction. We abort and return the options that {} provides.".format(self, self)) i = pat while i: - self._expect.send(chr(3)) + self._expect.sendline(chr(3)) sleep(0.1) i = self._expect.expect(self._prompt, timeout=0.1) # User interaction is expected to happen when requesting help if line.startswith('help'): - out = os.linesep.join(out.split(os.linesep)[:-1]) + out = str_to_bytes(os.linesep).join(out.split(str_to_bytes(os.linesep))[:-1]) break else: RuntimeError("Polymake unexpectedly {}".format(_available_polymake_answers[pat])) @@ -2265,15 +2357,15 @@ def _eval_line(self, line, allow_use_file=True, wait_for_prompt=True, restart_if sleep(0.1) raise RuntimeError("Polymake {}".format(_available_polymake_answers[pat])) else: - out = '' + out = b'' except KeyboardInterrupt: self._keyboard_interrupt() raise KeyboardInterrupt("Ctrl-c pressed while running {}".format(self)) for w in p_warnings: - warnings.warn(w, RuntimeWarning) + warnings.warn(bytes_to_str(w), RuntimeWarning) for e in p_errors: - raise PolymakeError(e) - return out + raise PolymakeError(bytes_to_str(e)) + return bytes_to_str(out) def application(self, app): """ @@ -2292,20 +2384,20 @@ def application(self, app): and finds that the polytope is very ample:: sage: from sage.interfaces.polymake import polymake_expect as polymake - sage: q = polymake.new_object("Polytope", INEQUALITIES=[[5,-4,0,1],[-3,0,-4,1],[-2,1,0,0],[-4,4,4,-1],[0,0,1,0],[8,0,0,-1],[1,0,-1,0],[3,-1,0,0]]) # optional - polymake - sage: q.H_VECTOR # optional - polymake + sage: q = polymake.new_object("Polytope", INEQUALITIES=[[5,-4,0,1],[-3,0,-4,1],[-2,1,0,0],[-4,4,4,-1],[0,0,1,0],[8,0,0,-1],[1,0,-1,0],[3,-1,0,0]]) # optional - polymake_expect + sage: q.H_VECTOR # optional - polymake_expect 1 5 5 1 - sage: q.F_VECTOR # optional - polymake + sage: q.F_VECTOR # optional - polymake_expect 8 14 8 - sage: q.VERY_AMPLE # optional - polymake + sage: q.VERY_AMPLE # optional - polymake_expect true In the application 'fan', polymake can now compute the normal fan of `q` and its (primitive) rays:: - sage: polymake.application('fan') # optional - polymake - sage: g = q.normal_fan() # optional - polymake - sage: g.RAYS # optional - polymake + sage: polymake.application('fan') # optional - polymake_expect + sage: g = q.normal_fan() # optional - polymake_expect + sage: g.RAYS # optional - polymake_expect -1 0 1/4 0 -1 1/4 1 0 0 @@ -2314,7 +2406,7 @@ def application(self, app): 0 0 -1 0 -1 0 -1 0 0 - sage: g.RAYS.primitive() # optional - polymake + sage: g.RAYS.primitive() # optional - polymake_expect -4 0 1 0 -4 1 1 0 0 @@ -2333,20 +2425,20 @@ def application(self, app): but only in 'tropical', the following shows the effect of changing the application. :: - sage: polymake.application('polytope') # optional - polymake - sage: 'trop_witness' in dir(polymake) # optional - polymake + sage: polymake.application('polytope') # optional - polymake_expect + sage: 'trop_witness' in dir(polymake) # optional - polymake_expect False - sage: polymake.application('tropical') # optional - polymake - sage: 'trop_witness' in dir(polymake) # optional - polymake + sage: polymake.application('tropical') # optional - polymake_expect + sage: 'trop_witness' in dir(polymake) # optional - polymake_expect True - sage: polymake.application('polytope') # optional - polymake - sage: 'trop_witness' in dir(polymake) # optional - polymake + sage: polymake.application('polytope') # optional - polymake_expect + sage: 'trop_witness' in dir(polymake) # optional - polymake_expect False For completeness, we show what happens when asking for an application that doesn't exist:: - sage: polymake.application('killerapp') # optional - polymake + sage: polymake.application('killerapp') # optional - polymake_expect Traceback (most recent call last): ... ValueError: Unknown polymake application 'killerapp' @@ -2354,7 +2446,7 @@ def application(self, app): Of course, a different error results when we send an explicit command in polymake to change to an unknown application:: - sage: polymake.eval('application "killerapp";') # optional - polymake + sage: polymake.eval('application "killerapp";') # optional - polymake_expect Traceback (most recent call last): ... PolymakeError: Unknown application killerapp @@ -2366,8 +2458,8 @@ def application(self, app): raise ValueError("Unknown polymake application '{}'".format(app)) self._application = app patterns = ["{} > ".format(app), # 0: normal prompt - r"{} \([0-9]+\)> ".format(app), # 1: continuation prompt - "Please choose {}".format(app), # 2: user input expected when requesting "help" + r"{} \([0-9]+\)> ".format(app), # 1: continuation prompt + "Please choose ", # 2: user input expected when requesting "help" "killed by signal", # 3: what we are looking for when interrupting a computation "polymake: +ERROR: +", # 4: error "polymake: +WARNING: +", # 5: warning @@ -2375,7 +2467,7 @@ def application(self, app): pexpect.EOF, # 7: unexpected end of the stream pexpect.TIMEOUT] # 8: timeout self._change_prompt(self._expect.compile_pattern_list(patterns)) - self._sendstr('application "{}";{}'.format(app, self._expect.linesep)) + self._sendstr('application "{}";{}'.format(app, os.linesep)) pat = self._expect.expect_list(self._prompt) if pat: raise RuntimeError("When changing the application, polymake unexpectedly {}".format(_available_polymake_answers[pat])) @@ -2385,7 +2477,6 @@ def application(self, app): class PolymakeJuPyMake(PolymakeAbstract): - r""" Interface to the polymake interpreter using JuPyMake. @@ -2433,6 +2524,8 @@ class PolymakeJuPyMake(PolymakeAbstract): To obtain Perl strings, use strings containing double-quote characters. Python dicts are translated to Perl hashes. + :: + sage: L = polymake.db_query({'"_id"': '"F.4D.0047"'}, # long time, optional - jupymake internet perl_mongodb ....: db='"LatticePolytopes"', ....: collection='"SmoothReflexive"'); L @@ -2453,7 +2546,7 @@ def __init__(self, seed=None, verbose=False): INPUT: - ``verbose`` -- boolean (default: ``False``); whether to print the - commands passed to polymake. + commands passed to polymake TESTS:: @@ -2468,7 +2561,7 @@ def __init__(self, seed=None, verbose=False): def is_running(self): """ - Return True if self is currently running. + Return ``True`` if ``self`` is currently running. TESTS:: @@ -2514,7 +2607,7 @@ def eval(self, code, **kwds): INPUT: - - ``code``, a command (string) to be evaluated + - ``code`` -- a command (string) to be evaluated Different reaction types of polymake, including warnings, comments, errors, request for user interaction, and yielding a continuation prompt, @@ -2565,7 +2658,8 @@ def eval(self, code, **kwds): When requesting help, polymake sometimes expect the user to choose from a list. In that situation, we abort with a warning, and show the list from which the user can choose; we could demonstrate this using - the :meth:`help` method, but here we use an explicit code evaluation:: + the :meth:`~sage.interfaces.polymake.PolymakeAbstract.help` method, + but here we use an explicit code evaluation:: sage: print(polymake.eval('help "TRIANGULATION";')) # optional - jupymake # random doctest:warning @@ -2590,7 +2684,8 @@ def eval(self, code, **kwds): ... RuntimeError: Polymake fails to respond timely - We verify that after the timeout, polymake is still able to give answers:: + We verify that after the timeout, polymake is still able + to give answers:: sage: c # optional - jupymake cube of dimension 15 @@ -2601,7 +2696,6 @@ def eval(self, code, **kwds): It may happen that in some situation the interface collapses and thus polymake would automatically be restarted, thereby losing all data that have been computed before. - """ if not self.is_running(): self._start() @@ -2624,8 +2718,8 @@ def eval(self, code, **kwds): def reduce_load_Polymake(): - """ - Returns the polymake interface object defined in :mod:`sage.interfaces.polymake`. + r""" + Return the polymake interface object defined in :mod:`sage.interfaces.polymake`. EXAMPLES:: @@ -2645,3 +2739,4 @@ def reduce_load_Polymake(): polymake = polymake_jupymake else: polymake = polymake_expect + diff --git a/src/sage/interfaces/r.py b/src/sage/interfaces/r.py index 1544af8026b..211956848a5 100644 --- a/src/sage/interfaces/r.py +++ b/src/sage/interfaces/r.py @@ -635,12 +635,8 @@ def png(self, *args, **kwds): #Check to see if R has PNG support s = self.eval('capabilities("png")') t = self.eval('capabilities("aqua")') - if "TRUE" not in s+t: + if "TRUE" not in s + t: raise RuntimeError("R was not compiled with PNG support") - - from sage.server.support import EMBEDDED_MODE - if EMBEDDED_MODE: - self.setwd('"%s"'%os.path.abspath('.')) return RFunction(self, 'png')(*args, **kwds) def convert_r_list(self, l): @@ -1310,9 +1306,6 @@ def plot(self, *args, **kwds): """ # We have to define this to override the plot function defined in the # superclass. - from sage.server.support import EMBEDDED_MODE - if EMBEDDED_MODE: - self.setwd('"%s"'%os.path.abspath('.')) RFunction(self, 'plot')(*args, **kwds) return RFunction(self, 'dev.off')() diff --git a/src/sage/interfaces/sympy_wrapper.py b/src/sage/interfaces/sympy_wrapper.py index 00f7b65dd27..7f49cb76923 100644 --- a/src/sage/interfaces/sympy_wrapper.py +++ b/src/sage/interfaces/sympy_wrapper.py @@ -65,11 +65,11 @@ def _sage_(self): EXAMPLES:: - sage: F = Set([1, 2]) - sage: F is Set([1, 2]) + sage: F = Family([1, 2]) + sage: F is Family([1, 2]) False sage: sF = F._sympy_(); sF - SageSet({1, 2}) + SageSet(Family (1, 2)) sage: sF._sage_() is F True """ @@ -82,7 +82,7 @@ def is_empty(self): EXAMPLES:: - sage: Empty = Set([]) + sage: Empty = Family([]) sage: sEmpty = Empty._sympy_() sage: sEmpty.is_empty True @@ -173,3 +173,21 @@ def __len__(self): 48 """ return len(self._sage_()) + + def __str__(self): + """ + Return the print representation of ``self``. + + EXAMPLES:: + + sage: sPrimes = Primes()._sympy_() + sage: str(sPrimes) # indirect doctest + 'SageSet(Set of all prime numbers: 2, 3, 5, 7, ...)' + sage: repr(sPrimes) + 'SageSet(Set of all prime numbers: 2, 3, 5, 7, ...)' + """ + # Provide this method so that sympy's printing code does not try to inspect + # the Sage object. + return f"SageSet({self._sage_()})" + + __repr__ = __str__ diff --git a/src/sage/libs/flint/flint_wrap.h b/src/sage/libs/flint/flint_wrap.h index 9c0ca4e88b1..ae02e473a95 100644 --- a/src/sage/libs/flint/flint_wrap.h +++ b/src/sage/libs/flint/flint_wrap.h @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include diff --git a/src/sage/libs/flint/fmpz_mod.pxd b/src/sage/libs/flint/fmpz_mod.pxd new file mode 100644 index 00000000000..555b3123398 --- /dev/null +++ b/src/sage/libs/flint/fmpz_mod.pxd @@ -0,0 +1,46 @@ +# distutils: libraries = flint +# distutils: depends = flint/fmpz_mod.h + +# flint/fmpz_mod.h +cdef extern from "flint_wrap.h": + void fmpz_mod_ctx_init(fmpz_mod_ctx_t ctx, const fmpz_t n) + void fmpz_mod_ctx_init_ui(fmpz_mod_ctx_t ctx, ulong n) + void fmpz_mod_ctx_clear(fmpz_mod_ctx_t ctx) + + const fmpz * fmpz_mod_ctx_modulus(const fmpz_mod_ctx_t ctx) + + void fmpz_mod_ctx_set_modulus(fmpz_mod_ctx_t ctx, const fmpz_t n) + void fmpz_mod_ctx_set_modulus_ui(fmpz_mod_ctx_t ctx, ulong n) + + int fmpz_mod_is_canonical(const fmpz_t a, const fmpz_mod_ctx_t ctx) + void fmpz_mod_assert_canonical(const fmpz_t a, const fmpz_mod_ctx_t ctx) + + int fmpz_mod_is_one(const fmpz_t a, const fmpz_mod_ctx_t ctx) + void fmpz_mod_add(fmpz_t a, const fmpz_t b, const fmpz_t c, const fmpz_mod_ctx_t ctx) + void fmpz_mod_sub(fmpz_t a, const fmpz_t b, const fmpz_t c, const fmpz_mod_ctx_t ctx) + void fmpz_mod_neg(fmpz_t a, const fmpz_t b, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_mul(fmpz_t a, const fmpz_t b, const fmpz_t c, const fmpz_mod_ctx_t ctx) + void fmpz_mod_inv(fmpz_t a, const fmpz_t b, const fmpz_mod_ctx_t ctx) + + int fmpz_mod_divides(fmpz_t a, const fmpz_t b, const fmpz_t c, const fmpz_mod_ctx_t ctx) + void fmpz_mod_pow_ui(fmpz_t a, const fmpz_t b, ulong pow, const fmpz_mod_ctx_t ctx) + int fmpz_mod_pow_fmpz(fmpz_t a, const fmpz_t b, const fmpz_t pow, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_discrete_log_pohlig_hellman_init(fmpz_mod_discrete_log_pohlig_hellman_t L) + + void fmpz_mod_discrete_log_pohlig_hellman_clear(fmpz_mod_discrete_log_pohlig_hellman_t L) + + double fmpz_mod_discrete_log_pohlig_hellman_precompute_prime( + fmpz_mod_discrete_log_pohlig_hellman_t L, + const fmpz_t p) + + void fmpz_mod_discrete_log_pohlig_hellman_run( + fmpz_t x, + const fmpz_mod_discrete_log_pohlig_hellman_t L, + const fmpz_t y) + + const fmpz * fmpz_mod_discrete_log_pohlig_hellman_primitive_root( + fmpz_mod_discrete_log_pohlig_hellman_t L) + + int fmpz_next_smooth_prime(fmpz_t a, const fmpz_t b) diff --git a/src/sage/libs/flint/fmpz_mod_poly.pxd b/src/sage/libs/flint/fmpz_mod_poly.pxd index 159033c4861..24b653eb3b7 100644 --- a/src/sage/libs/flint/fmpz_mod_poly.pxd +++ b/src/sage/libs/flint/fmpz_mod_poly.pxd @@ -1,12 +1,507 @@ # distutils: libraries = flint # distutils: depends = flint/fmpz_mod_poly.h -from sage.libs.flint.types cimport fmpz_mod_poly_t, fmpz_t, slong +from sage.libs.flint.types cimport * +from sage.libs.flint.thread_pool cimport * # flint/fmpz_mod_poly.h cdef extern from "flint_wrap.h": - void fmpz_mod_poly_init(fmpz_mod_poly_t poly, const fmpz_t p) - void fmpz_mod_poly_clear(fmpz_mod_poly_t poly) + void fmpz_mod_poly_init(fmpz_mod_poly_t poly, const fmpz_t p, const fmpz_mod_ctx_t ctx) + void fmpz_mod_poly_clear(fmpz_mod_poly_t poly, const fmpz_mod_ctx_t ctx) - void fmpz_mod_poly_set_coeff_fmpz(fmpz_mod_poly_t poly, slong n, const fmpz_t x) - void fmpz_mod_poly_get_coeff_fmpz(fmpz_t x, const fmpz_mod_poly_t poly, slong n) + void fmpz_mod_poly_realloc(fmpz_mod_poly_t poly, slong alloc, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_fit_length(fmpz_mod_poly_t poly, slong len, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_truncate(fmpz_mod_poly_t poly, slong len, const fmpz_mod_ctx_t ctx) + void fmpz_mod_poly_set_trunc(fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly, slong n, const fmpz_mod_ctx_t ctx) + + slong fmpz_mod_poly_degree(const fmpz_mod_poly_t poly, const fmpz_mod_ctx_t ctx) + slong fmpz_mod_poly_length(const fmpz_mod_poly_t poly, const fmpz_mod_ctx_t ctx) + fmpz * fmpz_mod_poly_lead(const fmpz_mod_poly_t poly, const fmpz_mod_ctx_t ctx) + int fmpz_mod_poly_is_one(const fmpz_mod_poly_t poly, const fmpz_mod_ctx_t ctx) + int fmpz_mod_poly_is_gen(const fmpz_mod_poly_t op, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_set(fmpz_mod_poly_t poly1, + const fmpz_mod_poly_t poly2, const fmpz_mod_ctx_t ctx) + void fmpz_mod_poly_swap(fmpz_mod_poly_t poly1, + fmpz_mod_poly_t poly2, const fmpz_mod_ctx_t ctx) + void fmpz_mod_poly_reverse(fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly, slong n, const fmpz_mod_ctx_t ctx) + void fmpz_mod_poly_zero(fmpz_mod_poly_t poly, const fmpz_mod_ctx_t ctx) + void fmpz_mod_poly_one(fmpz_mod_poly_t poly, const fmpz_mod_ctx_t ctx) + void fmpz_mod_poly_zero_coeffs(fmpz_mod_poly_t poly, + slong i, slong j, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_set_ui(fmpz_mod_poly_t f, ulong x, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_set_fmpz(fmpz_mod_poly_t poly, const fmpz_t c, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_set_fmpz_poly(fmpz_mod_poly_t f, + const fmpz_poly_t g, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_get_fmpz_poly(fmpz_poly_t f, + const fmpz_mod_poly_t g, const fmpz_mod_ctx_t ctx) + + int fmpz_mod_poly_equal(const fmpz_mod_poly_t poly1, + const fmpz_mod_poly_t poly2, const fmpz_mod_ctx_t ctx) + + int fmpz_mod_poly_equal_trunc(const fmpz_mod_poly_t poly1, + const fmpz_mod_poly_t poly2, slong n, const fmpz_mod_ctx_t ctx) + + int fmpz_mod_poly_is_zero(const fmpz_mod_poly_t poly, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_set_coeff_fmpz(fmpz_mod_poly_t poly, slong n, + const fmpz_t x, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_set_coeff_ui(fmpz_mod_poly_t poly, slong n, + ulong x, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_set_coeff_si(fmpz_mod_poly_t poly, slong n, + slong x, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_get_coeff_fmpz(fmpz_t x, const fmpz_mod_poly_t poly, + slong n, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_set_coeff_mpz(fmpz_mod_poly_t poly, + slong n, const mpz_t x, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_get_coeff_mpz(mpz_t x, + const fmpz_mod_poly_t poly, slong n, const fmpz_mod_ctx_t ctx) + + + void _fmpz_mod_poly_shift_left(fmpz * res, const fmpz * poly, + slong len, slong n) + + void fmpz_mod_poly_shift_left(fmpz_mod_poly_t f, + const fmpz_mod_poly_t g, slong n, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_shift_right(fmpz_mod_poly_t f, + const fmpz_mod_poly_t g, slong n, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_add(fmpz_mod_poly_t res, const fmpz_mod_poly_t poly1, + const fmpz_mod_poly_t poly2, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_add_series(fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly1, const fmpz_mod_poly_t poly2, + slong n, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_sub(fmpz_mod_poly_t res, const fmpz_mod_poly_t poly1, + const fmpz_mod_poly_t poly2, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_sub_series(fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly1, const fmpz_mod_poly_t poly2, + slong n, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_neg(fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_scalar_mul_fmpz(fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly, const fmpz_t x, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_scalar_mul_ui(fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly, ulong x, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_scalar_div_fmpz(fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly, const fmpz_t x, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_mul(fmpz_mod_poly_t res, const fmpz_mod_poly_t poly1, + const fmpz_mod_poly_t poly2, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_mullow(fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly1, const fmpz_mod_poly_t poly2, + slong n, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_sqr(fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_mulmod(fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly1, const fmpz_mod_poly_t poly2, + const fmpz_mod_poly_t f, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_mulmod_preinv(fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly1, const fmpz_mod_poly_t poly2, + const fmpz_mod_poly_t f, const fmpz_mod_poly_t finv, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_pow(fmpz_mod_poly_t rop, const fmpz_mod_poly_t op, + ulong e, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_pow_trunc(fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly, ulong e, slong trunc, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_pow_trunc_binexp(fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly, ulong e, slong trunc, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_powmod_ui_binexp(fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly, ulong e, + const fmpz_mod_poly_t f, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_powmod_ui_binexp_preinv(fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly, ulong e, + const fmpz_mod_poly_t f, const fmpz_mod_poly_t finv, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_powmod_fmpz_binexp(fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly, const fmpz_t e, + const fmpz_mod_poly_t f, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_powmod_fmpz_binexp_preinv(fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly, const fmpz_t e, + const fmpz_mod_poly_t f, const fmpz_mod_poly_t finv, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_powmod_x_fmpz_preinv(fmpz_mod_poly_t res, + const fmpz_t e, const fmpz_mod_poly_t f, const fmpz_mod_poly_t finv, + const fmpz_mod_ctx_t ctx) + + + void fmpz_mod_poly_powers_mod_naive(fmpz_mod_poly_struct * res, + const fmpz_mod_poly_t f, slong n, const fmpz_mod_poly_t g, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_powers_mod_bsgs(fmpz_mod_poly_struct * res, + const fmpz_mod_poly_t f, slong n, const fmpz_mod_poly_t g, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_frobenius_powers_2exp_precomp( + fmpz_mod_poly_frobenius_powers_2exp_t pow, const fmpz_mod_poly_t f, + const fmpz_mod_poly_t finv, ulong m, const fmpz_mod_ctx_t ctx) + + + void fmpz_mod_poly_frobenius_powers_2exp_clear( + fmpz_mod_poly_frobenius_powers_2exp_t pow, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_frobenius_power(fmpz_mod_poly_t res, + fmpz_mod_poly_frobenius_powers_2exp_t pow, + const fmpz_mod_poly_t f, ulong m, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_frobenius_powers_precomp( + fmpz_mod_poly_frobenius_powers_t pow, const fmpz_mod_poly_t f, + const fmpz_mod_poly_t finv, ulong m, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_frobenius_powers_clear( + fmpz_mod_poly_frobenius_powers_t pow, const fmpz_mod_ctx_t ctx) + + + void fmpz_mod_poly_divrem_basecase(fmpz_mod_poly_t Q, fmpz_mod_poly_t R, + const fmpz_mod_poly_t A, const fmpz_mod_poly_t B, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_div_basecase(fmpz_mod_poly_t Q, + const fmpz_mod_poly_t A, const fmpz_mod_poly_t B, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_div_newton_n_preinv(fmpz_mod_poly_t Q, + const fmpz_mod_poly_t A, const fmpz_mod_poly_t B, + const fmpz_mod_poly_t Binv, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_divrem_newton_n_preinv(fmpz_mod_poly_t Q, + fmpz_mod_poly_t R, const fmpz_mod_poly_t A, const fmpz_mod_poly_t B, + const fmpz_mod_poly_t Binv, const fmpz_mod_ctx_t ctx) + + ulong fmpz_mod_poly_remove(fmpz_mod_poly_t f, + const fmpz_mod_poly_t p, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_rem_basecase(fmpz_mod_poly_t R, + const fmpz_mod_poly_t A, const fmpz_mod_poly_t B, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_divrem_divconquer(fmpz_mod_poly_t Q, + fmpz_mod_poly_t R, const fmpz_mod_poly_t A, const fmpz_mod_poly_t B, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_divrem(fmpz_mod_poly_t Q, fmpz_mod_poly_t R, + const fmpz_mod_poly_t A, const fmpz_mod_poly_t B, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_divrem_f(fmpz_t f, fmpz_mod_poly_t Q, + fmpz_mod_poly_t R, const fmpz_mod_poly_t A, const fmpz_mod_poly_t B, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_rem(fmpz_mod_poly_t R, const fmpz_mod_poly_t A, + const fmpz_mod_poly_t B, const fmpz_mod_ctx_t ctx) + + + void fmpz_mod_poly_rem_f(fmpz_t f, fmpz_mod_poly_t R, const fmpz_mod_poly_t A, + const fmpz_mod_poly_t B, const fmpz_mod_ctx_t ctx) + + + void fmpz_mod_poly_inv_series_newton(fmpz_mod_poly_t Qinv, + const fmpz_mod_poly_t Q, slong n, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_inv_series_newton_f(fmpz_t f, fmpz_mod_poly_t Qinv, + const fmpz_mod_poly_t Q, slong n, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_inv_series(fmpz_mod_poly_t Qinv, const fmpz_mod_poly_t Q, + slong n, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_inv_series_f(fmpz_t f, fmpz_mod_poly_t Qinv, + const fmpz_mod_poly_t Q, slong n, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_div_series(fmpz_mod_poly_t Q, + const fmpz_mod_poly_t A, const fmpz_mod_poly_t B, slong n, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_make_monic(fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_make_monic_f(fmpz_t f, fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_gcd_euclidean(fmpz_mod_poly_t G, + const fmpz_mod_poly_t A, const fmpz_mod_poly_t B, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_gcd_euclidean_f(fmpz_t f, fmpz_mod_poly_t G, + const fmpz_mod_poly_t A, const fmpz_mod_poly_t B, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_gcd_f(fmpz_t f, fmpz_mod_poly_t G, const fmpz_mod_poly_t A, + const fmpz_mod_poly_t B, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_gcd_hgcd(fmpz_mod_poly_t G, + const fmpz_mod_poly_t A, const fmpz_mod_poly_t B, + const fmpz_mod_ctx_t ctx) + + + void fmpz_mod_poly_gcd(fmpz_mod_poly_t G, const fmpz_mod_poly_t A, + const fmpz_mod_poly_t B, const fmpz_mod_ctx_t ctx) + + + void fmpz_mod_poly_xgcd_euclidean(fmpz_mod_poly_t G, + fmpz_mod_poly_t S, fmpz_mod_poly_t T, + const fmpz_mod_poly_t A, const fmpz_mod_poly_t B, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_xgcd_euclidean_f(fmpz_t f, fmpz_mod_poly_t G, + fmpz_mod_poly_t S, fmpz_mod_poly_t T, + const fmpz_mod_poly_t A, const fmpz_mod_poly_t B, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_xgcd_hgcd(fmpz_mod_poly_t G, fmpz_mod_poly_t S, + fmpz_mod_poly_t T, const fmpz_mod_poly_t A, const fmpz_mod_poly_t B, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_xgcd(fmpz_mod_poly_t G, fmpz_mod_poly_t S, fmpz_mod_poly_t T, + const fmpz_mod_poly_t A, const fmpz_mod_poly_t B, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_xgcd_f(fmpz_t f, fmpz_mod_poly_t G, fmpz_mod_poly_t S, + fmpz_mod_poly_t T, const fmpz_mod_poly_t A, const fmpz_mod_poly_t B, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_gcdinv_euclidean_f(fmpz_t f, fmpz_mod_poly_t G, + fmpz_mod_poly_t S, const fmpz_mod_poly_t A, const fmpz_mod_poly_t B, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_gcdinv_euclidean(fmpz_mod_poly_t G, + fmpz_mod_poly_t S, const fmpz_mod_poly_t A, const fmpz_mod_poly_t B, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_gcdinv(fmpz_mod_poly_t G, fmpz_mod_poly_t S, + const fmpz_mod_poly_t A, const fmpz_mod_poly_t B, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_gcdinv_f(fmpz_t f, fmpz_mod_poly_t G, + fmpz_mod_poly_t S, const fmpz_mod_poly_t A, const fmpz_mod_poly_t B, + const fmpz_mod_ctx_t ctx) + + int fmpz_mod_poly_invmod(fmpz_mod_poly_t A, const fmpz_mod_poly_t B, + const fmpz_mod_poly_t P, const fmpz_mod_ctx_t ctx) + + int fmpz_mod_poly_invmod_f(fmpz_t f, fmpz_mod_poly_t A, + const fmpz_mod_poly_t B, const fmpz_mod_poly_t P, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_minpoly_bm(fmpz_mod_poly_t poly, const fmpz* seq, slong len, + const fmpz_mod_ctx_t ctx) + void fmpz_mod_poly_minpoly_hgcd(fmpz_mod_poly_t poly, const fmpz* seq, slong len, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_minpoly(fmpz_mod_poly_t poly, const fmpz* seq, slong len, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_resultant_euclidean(fmpz_t r, + const fmpz_mod_poly_t f, const fmpz_mod_poly_t g, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_resultant_hgcd(fmpz_t res, const fmpz_mod_poly_t A, + const fmpz_mod_poly_t B, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_resultant(fmpz_t res, const fmpz_mod_poly_t f, + const fmpz_mod_poly_t g, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_discriminant(fmpz_t d, const fmpz_mod_poly_t f, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_derivative(fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_evaluate_fmpz(fmpz_t res, + const fmpz_mod_poly_t poly, const fmpz_t a, + const fmpz_mod_ctx_t ctx) + + fmpz_poly_struct ** _fmpz_mod_poly_tree_alloc(slong len) + + void fmpz_mod_poly_evaluate_fmpz_vec_iter(fmpz * ys, + const fmpz_mod_poly_t poly, const fmpz * xs, slong n, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_evaluate_fmpz_vec_fast(fmpz * ys, + const fmpz_mod_poly_t poly, const fmpz * xs, slong n, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_evaluate_fmpz_vec(fmpz * ys, + const fmpz_mod_poly_t poly, const fmpz * xs, slong n, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_compose_horner(fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly1, const fmpz_mod_poly_t poly2, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_compose_divconquer(fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly1, const fmpz_mod_poly_t poly2, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_compose(fmpz_mod_poly_t res, const fmpz_mod_poly_t poly1, + const fmpz_mod_poly_t poly2, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_compose_mod(fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly1, const fmpz_mod_poly_t poly2, + const fmpz_mod_poly_t poly3, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_compose_mod_brent_kung(fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly1, const fmpz_mod_poly_t poly2, + const fmpz_mod_poly_t poly3, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_precompute_matrix(fmpz_mat_t A, + const fmpz_mod_poly_t poly1, const fmpz_mod_poly_t poly2, + const fmpz_mod_poly_t poly2inv, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_compose_mod_brent_kung_precomp_preinv(fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly1, const fmpz_mat_t A, + const fmpz_mod_poly_t poly3, const fmpz_mod_poly_t poly3inv, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_compose_mod_brent_kung_preinv(fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly1, const fmpz_mod_poly_t poly2, + const fmpz_mod_poly_t poly3, const fmpz_mod_poly_t poly3inv, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_compose_mod_horner(fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly1, const fmpz_mod_poly_t poly2, + const fmpz_mod_poly_t poly3, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_compose_mod_brent_kung_vec_preinv( + fmpz_mod_poly_struct * res, const fmpz_mod_poly_struct * polys, + slong len1,slong n, const fmpz_mod_poly_t g, const fmpz_mod_poly_t poly, + const fmpz_mod_poly_t polyinv, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_compose_mod_brent_kung_vec_preinv_threaded_pool(fmpz_mod_poly_struct * res, + const fmpz_mod_poly_struct * polys, slong len1, slong n, + const fmpz_mod_poly_t g, const fmpz_mod_poly_t poly, + const fmpz_mod_poly_t polyinv, const fmpz_mod_ctx_t ctx, + thread_pool_handle * threads, slong num_threads) + + void fmpz_mod_poly_compose_mod_brent_kung_vec_preinv_threaded(fmpz_mod_poly_struct * res, + const fmpz_mod_poly_struct * polys, slong len1, slong n, + const fmpz_mod_poly_t g, const fmpz_mod_poly_t poly, + const fmpz_mod_poly_t polyinv, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_radix_init(fmpz_mod_poly_radix_t D, + const fmpz_mod_poly_t R, slong degF, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_radix_clear(fmpz_mod_poly_radix_t D) + + void fmpz_mod_poly_radix(fmpz_mod_poly_struct **B, const fmpz_mod_poly_t F, + const fmpz_mod_poly_radix_t D, const fmpz_mod_ctx_t ctx) + + int fmpz_mod_poly_fprint(FILE * file, const fmpz_mod_poly_t poly, + const fmpz_mod_ctx_t ctx) + + int fmpz_mod_poly_fread(FILE * file, fmpz_mod_poly_t poly, + fmpz_mod_ctx_t ctx) + + + int fmpz_mod_poly_fprint_pretty(FILE * file, const fmpz_mod_poly_t poly, + const char * x, const fmpz_mod_ctx_t ctx) + + + + int fmpz_mod_poly_print(const fmpz_mod_poly_t poly, const fmpz_mod_ctx_t ctx) + + + int fmpz_mod_poly_print_pretty(const fmpz_mod_poly_t poly, const char * x, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_product_roots_fmpz_vec(fmpz_poly_t poly, const fmpz * xs, + slong n, const fmpz_t mod) + + int fmpz_mod_poly_find_distinct_nonzero_roots(fmpz * roots, + const fmpz_mod_poly_t P, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_berlekamp_massey_init( + fmpz_mod_berlekamp_massey_t B, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_berlekamp_massey_start_over( + fmpz_mod_berlekamp_massey_t B, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_berlekamp_massey_clear(fmpz_mod_berlekamp_massey_t B, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_berlekamp_massey_print( + const fmpz_mod_berlekamp_massey_t B, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_berlekamp_massey_add_points( + fmpz_mod_berlekamp_massey_t B, const fmpz * a, slong count, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_berlekamp_massey_add_zeros( + fmpz_mod_berlekamp_massey_t B, slong count, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_berlekamp_massey_add_point( + fmpz_mod_berlekamp_massey_t B, const fmpz_t a, + const fmpz_mod_ctx_t ctx) + + void fmpz_mod_berlekamp_massey_add_point_ui( + fmpz_mod_berlekamp_massey_t B, ulong a, + const fmpz_mod_ctx_t ctx) + + int fmpz_mod_berlekamp_massey_reduce( + fmpz_mod_berlekamp_massey_t B, const fmpz_mod_ctx_t ctx) + + const fmpz * fmpz_mod_berlekamp_massey_points( + const fmpz_mod_berlekamp_massey_t B) + + slong fmpz_mod_berlekamp_massey_point_count( + const fmpz_mod_berlekamp_massey_t B) + + const fmpz_mod_poly_struct * fmpz_mod_berlekamp_massey_V_poly( + const fmpz_mod_berlekamp_massey_t B) + + const fmpz_mod_poly_struct * fmpz_mod_berlekamp_massey_R_poly( + const fmpz_mod_berlekamp_massey_t B) + + void fmpz_mod_poly_add_si(fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly, slong c, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_sub_si(fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly, slong c, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_si_sub(fmpz_mod_poly_t res, slong c, + const fmpz_mod_poly_t poly, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_add_fmpz(fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly, fmpz_t c, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_sub_fmpz(fmpz_mod_poly_t res, + const fmpz_mod_poly_t poly, fmpz_t c, const fmpz_mod_ctx_t ctx) + + void fmpz_mod_poly_fmpz_sub(fmpz_mod_poly_t res, fmpz_t c, + const fmpz_mod_poly_t poly, const fmpz_mod_ctx_t ctx) diff --git a/src/sage/libs/flint/thread_pool.pxd b/src/sage/libs/flint/thread_pool.pxd new file mode 100644 index 00000000000..b773695d2de --- /dev/null +++ b/src/sage/libs/flint/thread_pool.pxd @@ -0,0 +1,57 @@ +# distutils: libraries = flint +# distutils: depends = flint/thread_pool.h + +#***************************************************************************** +# Copyright (C) 2021 Vincent Delecroix +# +# 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 slong + +# flint/thread_pool.h +cdef extern from "flint/thread_pool.h": + ctypedef struct thread_pool_entry_struct: + pass + + ctypedef thread_pool_entry_struct thread_pool_entry_t[1]; + + ctypedef struct thread_pool_struct: + pass + + ctypedef thread_pool_struct thread_pool_t[1] + ctypedef int thread_pool_handle + + + extern thread_pool_t global_thread_pool + extern int global_thread_pool_initialized + + void * thread_pool_idle_loop(void * varg) + + void thread_pool_init(thread_pool_t T, slong l) + + int thread_pool_set_affinity(thread_pool_t T, + int * cpus, slong length) + + int thread_pool_restore_affinity(thread_pool_t T) + + slong thread_pool_get_size(thread_pool_t T) + + int thread_pool_set_size(thread_pool_t T, slong new_size) + + slong thread_pool_request(thread_pool_t T, + thread_pool_handle * out, slong requested) + + void thread_pool_wake(thread_pool_t T, thread_pool_handle i, + int max_workers, void (*f)(void*), void * a) + + void thread_pool_wait(thread_pool_t T, thread_pool_handle i) + + void thread_pool_give_back(thread_pool_t T, thread_pool_handle i) + + void thread_pool_clear(thread_pool_t T) + diff --git a/src/sage/libs/flint/types.pxd b/src/sage/libs/flint/types.pxd index 3e0d595ee9f..7208f89d9aa 100644 --- a/src/sage/libs/flint/types.pxd +++ b/src/sage/libs/flint/types.pxd @@ -42,6 +42,19 @@ cdef extern from "flint_wrap.h": ctypedef fmpz_preinvn_struct[1] fmpz_preinvn_t +# flint/fmpz_mod.h: +cdef extern from "flint_wrap.h": + ctypedef struct fmpz_mod_ctx_struct: + pass + + ctypedef fmpz_mod_ctx_struct fmpz_mod_ctx_t[1] + + ctypedef struct fmpz_mod_discrete_log_pohlig_hellman_entry_struct: + pass + ctypedef struct fmpz_mod_discrete_log_pohlig_hellman_struct: + pass + ctypedef fmpz_mod_discrete_log_pohlig_hellman_struct fmpz_mod_discrete_log_pohlig_hellman_t[1] + # flint/fmpz_poly.h: cdef extern from "flint_wrap.h": ctypedef struct fmpz_poly_struct: @@ -90,9 +103,34 @@ cdef extern from "flint_wrap.h": cdef extern from "flint_wrap.h": ctypedef struct fmpz_mod_poly_struct: pass - ctypedef fmpz_mod_poly_struct fmpz_mod_poly_t[1] + ctypedef struct fmpz_mod_poly_res_struct: + pass + ctypedef fmpz_mod_poly_res_struct fmpz_mod_poly_res_t[1] + + ctypedef struct fmpz_mod_poly_frobenius_powers_2exp_struct: + pass + ctypedef fmpz_mod_poly_frobenius_powers_2exp_struct fmpz_mod_poly_frobenius_powers_2exp_t[1] + + ctypedef struct fmpz_mod_poly_frobenius_powers_struct: + pass + ctypedef fmpz_mod_poly_frobenius_powers_struct fmpz_mod_poly_frobenius_powers_t[1] + + ctypedef struct fmpz_mod_poly_matrix_precompute_arg_t: + pass + + ctypedef struct fmpz_mod_poly_compose_mod_precomp_preinv_arg_t: + pass + + ctypedef struct fmpz_mod_poly_radix_struct: + pass + ctypedef fmpz_mod_poly_radix_struct fmpz_mod_poly_radix_t[1] + + ctypedef struct fmpz_mod_berlekamp_massey_struct: + pass + ctypedef fmpz_mod_berlekamp_massey_struct fmpz_mod_berlekamp_massey_t[1] + # flint/nmod_poly.h: cdef extern from "flint_wrap.h": ctypedef struct nmod_t: diff --git a/src/sage/logic/boolformula.py b/src/sage/logic/boolformula.py index 5bc168d8404..bfaa37739dc 100644 --- a/src/sage/logic/boolformula.py +++ b/src/sage/logic/boolformula.py @@ -1508,9 +1508,9 @@ def get_next_op(self, str): i += 1 return str[i] - def __len__(self): + def length(self): r""" - Return the length of a Boolean formula. + Return the length of ``self``. OUTPUT: @@ -1521,37 +1521,41 @@ def __len__(self): sage: import sage.logic.propcalc as propcalc sage: s = propcalc.formula("a") - sage: len(s) + sage: s.length() 1 sage: s = propcalc.formula("(a)") - sage: len(s) + sage: s.length() 1 sage: s = propcalc.formula("~a") - sage: len(s) + sage: s.length() 2 sage: s = propcalc.formula("a -> b") - sage: len(s) + sage: s.length() 3 sage: s = propcalc.formula("alpha -> beta") - sage: len(s) + sage: s.length() 3 sage: s = propcalc.formula("a -> a") - sage: len(s) + sage: s.length() 3 sage: s = propcalc.formula("~(a -> b)") - sage: len(s) + sage: s.length() 4 sage: s = propcalc.formula("((a&b)|(a&c))->~d") - sage: len(s) + sage: s.length() 10 TESTS:: sage: s = propcalc.formula("(((alpha) -> ((beta))))") - sage: len(s) + sage: s.length() 3 """ return len(flatten(self.full_tree())) + # For backward compatibility, we allow `self.length()` to be called as + # `len(self)`, but this may be deprecated in the future (see :trac:`32148`): + __len__ = length + # allow is_consequence to be called as a function (not only as a method of BooleanFormula) is_consequence = BooleanFormula.is_consequence diff --git a/src/sage/manifolds/chart.py b/src/sage/manifolds/chart.py index ca0dcba378b..df0e9aa0659 100644 --- a/src/sage/manifolds/chart.py +++ b/src/sage/manifolds/chart.py @@ -84,10 +84,6 @@ class Chart(UniqueRepresentation, SageObject): If no period and no LaTeX spelling are to be set for any coordinate, the argument ``coordinates`` can be omitted when the shortcut operator ``<,>`` is used to declare the chart (see examples below). - - ``names`` -- (default: ``None``) unused argument, except if - ``coordinates`` is not provided; it must then be a tuple containing - the coordinate symbols (this is guaranteed if the shortcut operator - ``<,>`` is used) - ``calc_method`` -- (default: ``None``) string defining the calculus method for computations involving coordinates of the chart; must be one of @@ -97,6 +93,27 @@ class Chart(UniqueRepresentation, SageObject): - ``None``: the default of :class:`~sage.manifolds.calculus_method.CalculusMethod` will be used + - ``names`` -- (default: ``None``) unused argument, except if + ``coordinates`` is not provided; it must then be a tuple containing + the coordinate symbols (this is guaranteed if the shortcut operator + ``<,>`` is used) + - ``coord_restrictions``: Additional restrictions on the coordinates. + A restriction can be any symbolic equality or inequality involving + the coordinates, such as ``x > y`` or ``x^2 + y^2 != 0``. The items + of the list (or set or frozenset) ``coord_restrictions`` are combined + with the ``and`` operator; if some restrictions are to be combined with + the ``or`` operator instead, they have to be passed as a tuple in some + single item of the list (or set or frozenset) ``coord_restrictions``. + For example:: + + coord_restrictions=[x > y, (x != 0, y != 0), z^2 < x] + + means ``(x > y) and ((x != 0) or (y != 0)) and (z^2 < x)``. + If the list ``coord_restrictions`` contains only one item, this + item can be passed as such, i.e. writing ``x > y`` instead + of the single element list ``[x > y]``. If the chart variables have + not been declared as variables yet, ``coord_restrictions`` must + be ``lambda``-quoted. EXAMPLES: @@ -255,13 +272,59 @@ class Chart(UniqueRepresentation, SageObject): sage: X(p) == p.coord(X) True + Setting additional coordinate restrictions:: + + sage: M = Manifold(2, 'M', field='complex', structure='topological') + sage: X. = M.chart(coord_restrictions=lambda x,y: abs(x) > 1) + sage: X.valid_coordinates(2+i, 1) + True + sage: X.valid_coordinates(i, 1) + False + .. SEEALSO:: :class:`sage.manifolds.chart.RealChart` for charts on topological manifolds over `\RR`. """ - def __init__(self, domain, coordinates='', names=None, calc_method=None): + + @staticmethod + def __classcall__(cls, domain, coordinates='', + calc_method=None, names=None, + coord_restrictions=None, **coordinate_options): + r""" + Normalize init args and implement unique representation behavior. + + TESTS:: + + sage: from sage.manifolds.chart import Chart + sage: M = Manifold(2, 'M', field='complex', structure='topological') + sage: var("u v") + (u, v) + sage: Chart(M, (u, v)) is Chart(M, "u v") + True + """ + if isinstance(coordinates, str): + if coordinates == '': + for x in names: + coordinates += x + ' ' + coordinates = coordinates[:-1] + coordinates, coordinate_options = cls._parse_coordinates(domain, coordinates) + + coord_string = ' '.join(str(x) for x in coordinates) + + try: + return domain._charts_by_coord[coord_string] + except KeyError: + # Make coord_restrictions hashable + coord_restrictions = cls._normalize_coord_restrictions(coordinates, coord_restrictions) + self = super().__classcall__(cls, domain, coordinates, calc_method, + coord_restrictions=coord_restrictions, + **coordinate_options) + domain._charts_by_coord[coord_string] = self + return self + + def __init__(self, domain, coordinates, calc_method=None, periods=None, coord_restrictions=None): r""" Construct a chart. @@ -273,19 +336,25 @@ def __init__(self, domain, coordinates='', names=None, calc_method=None): Chart (M, (x, y)) sage: type(X) - sage: assumptions() # no assumptions on x,y set by X._init_coordinates + sage: assumptions() # no assumptions on x,y set [] sage: TestSuite(X).run() + Check that :trac:`32112` has been fixed:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: U = M.open_subset('U') + sage: V = M.open_subset('V') + sage: XU = U.chart('x y') + sage: XV = V.chart('x y') + sage: M.top_charts() + [Chart (U, (x, y)), Chart (V, (x, y))] + """ from sage.manifolds.manifold import TopologicalManifold if not isinstance(domain, TopologicalManifold): raise TypeError("the first argument must be an open subset of " + "a topological manifold") - if coordinates == '': - for x in names: - coordinates += x + ' ' - coordinates = coordinates[:-1] self._manifold = domain.manifold() self._domain = domain self._sindex = self._manifold.start_index() @@ -295,36 +364,31 @@ def __init__(self, domain, coordinates='', names=None, calc_method=None): self.simplify = self._calc_method.simplify # Treatment of the coordinates: - self._periods = {} # dict. of periods (if any); key = coord. index - if ' ' in coordinates: - coord_list = coordinates.split() + if periods is None: + self._periods = {} else: - coord_list = [coordinates] - if len(coord_list) != self._manifold.dim(): + # dictionary of periods (if any); key = coord. index + self._periods = {self._sindex + i: period + for i, period in enumerate(periods) + if period is not None} + + if len(coordinates) != self._manifold.dim(): raise ValueError("the list of coordinates must contain " + "{} elements".format(self._manifold.dim())) - # The treatment of coordinates is performed by a separate method, - # _init_coordinates, which sets self._xx and - # which may be redefined for subclasses (for instance RealChart). - self._init_coordinates(coord_list) - coord_string = ' '.join(str(x) for x in self._xx) - if coord_string in self._domain._charts_by_coord: - raise ValueError("the chart with coordinates " + coord_string + - " has already been declared on " + - "the {}".format(self._domain)) - self._domain._charts_by_coord[coord_string] = self + self._xx = coordinates # - # Additional restrictions on the coordinates - self._restrictions = [] # to be set with method add_restrictions() + # Additional restrictions on the coordinates. + self._restrictions = sorted(coord_restrictions, key=str) # # The chart is added to the domain's atlas, as well as to all the # atlases of the domain's supersets; moreover the first defined chart # is considered as the default chart - for sd in self._domain.open_supersets(): - # the chart is added in the top charts only if its coordinates have - # not been used: + for sd in domain.open_supersets(): + # the chart is added in the top charts iff its coordinates have + # not been used on a domain including the chart's domain: for chart in sd._atlas: - if self._xx == chart._xx: + if (domain.is_subset(chart._domain) + and self._xx == chart._xx): break else: sd._top_charts.append(self) @@ -332,7 +396,7 @@ def __init__(self, domain, coordinates='', names=None, calc_method=None): if sd._def_chart is None: sd._def_chart = self # The chart is added to the list of the domain's covering charts: - self._domain._covering_charts.append(self) + domain._covering_charts.append(self) # Initialization of the set of charts that are restrictions of the # current chart to subsets of the chart domain: self._subcharts = set([self]) @@ -346,58 +410,109 @@ def __init__(self, domain, coordinates='', names=None, calc_method=None): # The null and one functions of the coordinates: # Expression in self of the zero and one scalar fields of open sets # containing the domain of self: - for dom in self._domain.open_supersets(): + for dom in domain.open_supersets(): dom._zero_scalar_field._express[self] = self.function_ring().zero() dom._one_scalar_field._express[self] = self.function_ring().one() - def _init_coordinates(self, coord_list): + @classmethod + def _parse_coordinates(cls, domain, coordinates): r""" Initialization of the coordinates as symbolic variables. - This method must be redefined by derived classes in order to take - into account specificities (e.g. enforcing real coordinates). - INPUT: - - ``coord_list`` -- list of coordinate fields, which items in each - field separated by ":"; there are at most 2 items per field: - the coordinate name and the coordinate LaTeX symbol + - ``coord_list`` -- list (or space-separated concatenation) of + coordinate fields. Each field is a string of at most 3 items, + separated by ":". These items are: the coordinate symbol, the + (optional) indicator of the periodic character of the + coordinate, and the (optional) coordinate LaTeX symbol + + OUTPUT: + + - a tuple of variables (as elements of ``SR``) + - a dictionary with possible keys: + - `"periods": a tuple of periods TESTS:: + sage: from sage.manifolds.chart import Chart sage: M = Manifold(2, 'M', field='complex', structure='topological') - sage: X. = M.chart() - sage: X._init_coordinates(['z1', 'z2']) - sage: X - Chart (M, (z1, z2)) - sage: X._init_coordinates([r'z1:\zeta_1', r'z2:\zeta_2']) - sage: X - Chart (M, (z1, z2)) - sage: latex(X) - \left(M,({\zeta_1}, {\zeta_2})\right) - + sage: Chart._parse_coordinates(M, ['z1', 'z2']) + ((z1, z2), {'periods': (None, None)}) + sage: Chart._parse_coordinates(M, 'z1 z2') + ((z1, z2), {'periods': (None, None)}) + sage: Chart._parse_coordinates(M, [r'z1:\zeta_1', r'z2:\zeta_2']) + ((z1, z2), {'periods': (None, None)}) """ + if isinstance(coordinates, str): + coord_list = coordinates.split() + else: + coord_list = coordinates xx_list = [] # will contain the coordinates as Sage symbolic variables + period_list = [] for coord_index, coord_field in enumerate(coord_list): coord_properties = coord_field.split(':') coord_symb = coord_properties[0].strip() # the coordinate symbol coord_latex = None # possibly redefined below + period = None # possibly redefined below # scan of the properties other than the symbol: for prop in coord_properties[1:]: prop1 = prop.strip() if prop1[0:6] == 'period': - if self._manifold.base_field_type() in ['real', 'complex']: + if domain.base_field_type() in ['real', 'complex']: period = SR(prop1[7:]) else: - period = self._manifold.base_field()(prop1[7:]) - self._periods[coord_index + self._sindex] = period + period = domain.base_field()(prop1[7:]) else: # prop1 is the coordinate's LaTeX symbol coord_latex = prop1 # Construction of the coordinate as a Sage symbolic variable: coord_var = SR.var(coord_symb, latex_name=coord_latex) xx_list.append(coord_var) - self._xx = tuple(xx_list) + period_list.append(period) + return tuple(xx_list), dict(periods=tuple(period_list)) + + @staticmethod + def _normalize_coord_restrictions(coordinates, coord_restrictions): + r""" + Rewrite ``coord_restrictions`` as a ``frozenset``, representing a logical "and", of other clauses. + + Also replace ``list``s by ``frozenset``s, making the result hashable. + + EXAMPLES:: + + sage: from sage.manifolds.chart import Chart + sage: coordinates = var("x y z") + sage: Chart._normalize_coord_restrictions(coordinates, None) + frozenset() + sage: Chart._normalize_coord_restrictions(coordinates, x > y) + frozenset({x > y}) + sage: Chart._normalize_coord_restrictions(coordinates, (x != 0, y != 0)) + frozenset({(x != 0, y != 0)}) + sage: Chart._normalize_coord_restrictions(coordinates, [x > y, (x != 0, y != 0), z^2 < x]) + frozenset({(x != 0, y != 0), x > y, z^2 < x}) + + """ + def normalize(r): + if isinstance(r, tuple): # or + return tuple(normalize(x) for x in r) + elif isinstance(r, (list, set, frozenset)): # and + return frozenset(normalize(x) for x in r) + else: + return r + + if coord_restrictions is None: + return frozenset() + + if callable(coord_restrictions) and not isinstance(coord_restrictions, Expression): + # lambda-quoted + coord_restrictions = coord_restrictions(*coordinates) + + if not isinstance(coord_restrictions, (list, set, frozenset)): + # case of a single condition or conditions to be combined by "or" + coord_restrictions = [coord_restrictions] + + return normalize(coord_restrictions) def _repr_(self): r""" @@ -411,7 +526,7 @@ def _repr_(self): Chart (M, (x, y)) """ - return 'Chart ({}, {})'.format(self._domain._name, self._xx) + return 'Chart ({}, {})'.format(self.domain()._name, self._xx) def _latex_(self): r""" @@ -430,7 +545,7 @@ def _latex_(self): \left(M,({\zeta_1}, {\zeta2})\right) """ - description = r'\left(' + latex(self._domain).strip() + ',(' + description = r'\left(' + latex(self.domain()).strip() + ',(' n = len(self._xx) for i in range(n-1): description += latex(self._xx[i]).strip() + ', ' @@ -627,6 +742,9 @@ def add_restrictions(self, restrictions): r""" Add some restrictions on the coordinates. + This is deprecated; provide the restrictions at the time of creating + the chart. + INPUT: - ``restrictions`` -- list of restrictions on the @@ -652,16 +770,19 @@ def add_restrictions(self, restrictions): sage: M = Manifold(2, 'M', field='complex', structure='topological') sage: X. = M.chart() sage: X.add_restrictions(abs(x) > 1) + doctest:warning... + DeprecationWarning: Chart.add_restrictions is deprecated; provide the + restrictions at the time of creating the chart + See https://trac.sagemath.org/32102 for details. sage: X.valid_coordinates(2+i, 1) True sage: X.valid_coordinates(i, 1) False """ - if not isinstance(restrictions, list): - # case of a single condition or conditions to be combined by "or" - restrictions = [restrictions] - self._restrictions.extend(restrictions) + from sage.misc.superseded import deprecation + deprecation(32102, "Chart.add_restrictions is deprecated; provide the restrictions at the time of creating the chart") + self._restrictions.extend(self._normalize_coord_restrictions(self._xx, restrictions)) def restrict(self, subset, restrictions=None): r""" @@ -712,22 +833,22 @@ def restrict(self, subset, restrictions=None): Chart (B, (z1, z2)) """ - if subset == self._domain: + if subset == self.domain(): return self if subset not in self._dom_restrict: - if not subset.is_subset(self._domain): + if not subset.is_subset(self.domain()): raise ValueError("the specified subset is not a subset " + "of the domain of definition of the chart") coordinates = "" for coord in self._xx: coordinates += repr(coord) + ' ' + res_coord_restrictions = set(self._restrictions) + res_coord_restrictions.update(self._normalize_coord_restrictions(self._xx, restrictions)) res = type(self)(subset, coordinates, - calc_method=self._calc_method._current) - res._restrictions.extend(self._restrictions) - # The coordinate restrictions are added to the result chart and - # possibly transformed into coordinate bounds: - if restrictions is not None: - res.add_restrictions(restrictions) + calc_method=self._calc_method._current, + # The coordinate restrictions are added + # to the result chart + coord_restrictions=res_coord_restrictions) # Update of supercharts and subcharts: res._supercharts.update(self._supercharts) for schart in self._supercharts: @@ -758,8 +879,7 @@ def valid_coordinates(self, *coordinates, **kwds): EXAMPLES:: sage: M = Manifold(2, 'M', field='complex', structure='topological') - sage: X. = M.chart() - sage: X.add_restrictions([abs(x)<1, y!=0]) + sage: X. = M.chart(coord_restrictions=lambda x,y: [abs(x)<1, y!=0]) sage: X.valid_coordinates(0, i) True sage: X.valid_coordinates(i, 1) @@ -776,15 +896,14 @@ def valid_coordinates(self, *coordinates, **kwds): sage: var('a') # the parameter is a symbolic variable a - sage: Y. = M.chart() - sage: Y.add_restrictions(abs(v) = M.chart(coord_restrictions=lambda u,v: abs(v) = M.chart() + sage: X.codomain() + Vector space of dimension 2 over Complex Field with 53 bits of precision + + """ + from sage.modules.free_module import VectorSpace + ambient = VectorSpace(self.manifold().base_field(), self.manifold().dimension()) + if self._restrictions: + return self._restrict_set(ambient, self._restrictions) + else: + return ambient + + def _restrict_set(self, universe, coord_restrictions): + """ + Return a set corresponding to coordinate restrictions. + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: X. = M.chart() + sage: universe = RR^2 + sage: X._restrict_set(universe, x>0) + { (x, y) ∈ Vector space of dimension 2 over Real Field with 53 bits of precision : x > 0 } + sage: X._restrict_set(universe, x>0) + { (x, y) ∈ Vector space of dimension 2 over Real Field with 53 bits of precision : x > 0 } + sage: X._restrict_set(universe, (x>0, [x 0 } and + { (x, y) ∈ Vector space of dimension 2 over Real Field with 53 bits of precision : x < y, y < 0 } + sage: X._restrict_set(universe, [(x0]) + Set-theoretic intersection of + Set-theoretic union of + { (x, y) ∈ Vector space of dimension 2 over Real Field with 53 bits of precision : x < y } and + { (x, y) ∈ Vector space of dimension 2 over Real Field with 53 bits of precision : y < 0 } and + { (x, y) ∈ Vector space of dimension 2 over Real Field with 53 bits of precision : x > 0 } + """ + if isinstance(coord_restrictions, tuple): # case of 'or' conditions + A = self._restrict_set(universe, coord_restrictions[0]) + if len(coord_restrictions) == 1: + return A + else: + return A.union(self._restrict_set(universe, coord_restrictions[1:])) + elif isinstance(coord_restrictions, (list, set, frozenset)): # case of 'and' conditions + A = self._restrict_set(universe, coord_restrictions[0]) + if len(coord_restrictions) == 1: + return A + else: + return A.intersection(self._restrict_set(universe, coord_restrictions[1:])) + # Case of a single condition: + from sage.sets.condition_set import ConditionSet + return ConditionSet(universe, coord_restrictions, vars=self._xx) + def transition_map(self, other, transformations, intersection_name=None, restrictions1=None, restrictions2=None): r""" @@ -962,8 +1134,8 @@ def transition_map(self, other, transformations, intersection_name=None, [Chart (R^2, (x, y)), Chart (U, (r, phi)), Chart (U, (x, y))] """ - dom1 = self._domain - dom2 = other._domain + dom1 = self.domain() + dom2 = other.domain() dom = dom1.intersection(dom2, name=intersection_name) if dom is dom1: chart1 = self @@ -1450,10 +1622,6 @@ class RealChart(Chart): If interval range, no period and no LaTeX spelling are to be set for any coordinate, the argument ``coordinates`` can be omitted when the shortcut operator ``<,>`` is used to declare the chart (see examples below). - - ``names`` -- (default: ``None``) unused argument, except if - ``coordinates`` is not provided; it must then be a tuple containing - the coordinate symbols (this is guaranteed if the shortcut operator - ``<,>`` is used) - ``calc_method`` -- (default: ``None``) string defining the calculus method for computations involving coordinates of the chart; must be one of @@ -1463,6 +1631,27 @@ class RealChart(Chart): - ``None``: the default of :class:`~sage.manifolds.calculus_method.CalculusMethod` will be used + - ``names`` -- (default: ``None``) unused argument, except if + ``coordinates`` is not provided; it must then be a tuple containing + the coordinate symbols (this is guaranteed if the shortcut operator + ``<,>`` is used) + - ``coord_restrictions``: Additional restrictions on the coordinates. + A restriction can be any symbolic equality or inequality involving + the coordinates, such as ``x > y`` or ``x^2 + y^2 != 0``. The items + of the list (or set or frozenset) ``coord_restrictions`` are combined + with the ``and`` operator; if some restrictions are to be combined with + the ``or`` operator instead, they have to be passed as a tuple in some + single item of the list (or set or frozenset) ``coord_restrictions``. + For example:: + + coord_restrictions=[x > y, (x != 0, y != 0), z^2 < x] + + means ``(x > y) and ((x != 0) or (y != 0)) and (z^2 < x)``. + If the list ``coord_restrictions`` contains only one item, this + item can be passed as such, i.e. writing ``x > y`` instead + of the single element list ``[x > y]``. If the chart variables have + not been declared as variables yet, ``coord_restrictions`` must + be ``lambda``-quoted. EXAMPLES: @@ -1647,8 +1836,7 @@ class RealChart(Chart): `\{y = 0, x \geq 0\}`, we must have `y \neq 0` or `x < 0` on U. Accordingly, we set:: - sage: c_cartU. = U.chart() - sage: c_cartU.add_restrictions((y!=0, x<0)) + sage: c_cartU. = U.chart(coord_restrictions=lambda x,y,z: (y!=0, x<0)) sage: U.atlas() [Chart (U, (r, th, ph)), Chart (U, (x, y, z))] sage: M.atlas() @@ -1665,13 +1853,13 @@ class RealChart(Chart): Note that, as an example, the following would have meant `y \neq 0` *and* `x < 0`:: - c_cartU.add_restrictions([y!=0, x<0]) + c_cartU. = U.chart(coord_restrictions=lambda x,y,z: [y!=0, x<0]) Chart grids can be drawn in 2D or 3D graphics thanks to the method :meth:`plot`. """ - def __init__(self, domain, coordinates='', names=None, calc_method=None): + def __init__(self, domain, coordinates, calc_method=None, bounds=None, periods=None, coord_restrictions=None): r""" Construct a chart on a real topological manifold. @@ -1689,11 +1877,14 @@ def __init__(self, domain, coordinates='', names=None, calc_method=None): sage: TestSuite(X).run() """ - Chart.__init__(self, domain, coordinates=coordinates, names=names, - calc_method=calc_method) + super().__init__(domain, coordinates, calc_method=calc_method, + periods=periods, coord_restrictions=coord_restrictions) + self._bounds = bounds + self._tighten_bounds() self._fast_valid_coordinates = None - def _init_coordinates(self, coord_list): + @classmethod + def _parse_coordinates(cls, domain, coordinates): r""" Initialization of the coordinates as symbolic variables. @@ -1702,34 +1893,36 @@ def _init_coordinates(self, coord_list): INPUT: - - ``coord_list`` -- list of coordinate fields, which items in each - field separated by ":"; there are at most 3 items per field: - the coordinate name, the coordinate LaTeX symbol and the - coordinate range + - ``coord_list`` -- list (or space-separated concatenation) of + coordinate fields. Each field is a string of at most 3 items, + separated by ":". These items are: the coordinate symbol, the + (optional) coordinate range or indicator of the periodic + character of the coordinate, and the (optional) coordinate + LaTeX symbol TESTS:: + sage: from sage.manifolds.chart import RealChart sage: M = Manifold(2, 'M', structure='topological') - sage: X. = M.chart() - sage: X._init_coordinates(['x', 'y']) - sage: X - Chart (M, (x, y)) - sage: latex(X) - \left(M,(x, y)\right) - sage: X.coord_range() - x: (-oo, +oo); y: (-oo, +oo) - sage: X._init_coordinates([r'x1:\xi:(0,1)', r'y1:\eta']) - sage: X - Chart (M, (x1, y1)) - sage: latex(X) - \left(M,({\xi}, {\eta})\right) - sage: X.coord_range() - x1: (0, 1); y1: (-oo, +oo) - + sage: RealChart._parse_coordinates(M, ['x', 'y']) + ((x, y), + {'bounds': (((-Infinity, False), (+Infinity, False)), + ((-Infinity, False), (+Infinity, False))), + 'periods': (None, None)}) + sage: RealChart._parse_coordinates(M, [r'x1:\xi:(0,1)', r'y1:\eta']) + ((x1, y1), + {'bounds': (((0, False), (1, False)), + ((-Infinity, False), (+Infinity, False))), + 'periods': (None, None)}) """ from sage.symbolic.assumptions import assume + if isinstance(coordinates, str): + coord_list = coordinates.split() + else: + coord_list = coordinates xx_list = [] # will contain the coordinates as Sage symbolic variables bounds_list = [] # will contain the coordinate bounds + period_list = [] for coord_index, coord_field in enumerate(coord_list): coord_properties = coord_field.split(':') coord_symb = coord_properties[0].strip() # the coordinate symbol @@ -1739,6 +1932,7 @@ def _init_coordinates(self, coord_list): xmin_included = False xmax = +Infinity xmax_included = False + period = None # scan of the properties other than the symbol: is_periodic = False for prop in coord_properties[1:]: @@ -1771,7 +1965,7 @@ def _init_coordinates(self, coord_list): latex_name=coord_latex) assume(coord_var, 'real') if is_periodic: - self._periods[coord_index + self._sindex] = xmax - xmin + period = xmax - xmin xmin_included = 'periodic' xmax_included = 'periodic' else: @@ -1787,8 +1981,9 @@ def _init_coordinates(self, coord_list): assume(coord_var < xmax) xx_list.append(coord_var) bounds_list.append(((xmin, xmin_included), (xmax, xmax_included))) - self._xx = tuple(xx_list) - self._bounds = tuple(bounds_list) + period_list.append(period) + return tuple(xx_list), dict(bounds=tuple(bounds_list), + periods=tuple(period_list)) def coord_bounds(self, i=None): r""" @@ -1853,6 +2048,49 @@ def coord_bounds(self, i=None): else: return self._bounds[i-self._sindex] + def codomain(self): + """ + Return the codomain of ``self`` as a set. + + EXAMPLES:: + + sage: M = Manifold(2, 'R^2', structure='topological') + sage: U = M.open_subset('U') # the complement of the half line {y=0, x >= 0} + sage: c_spher. = U.chart(r'r:(0,+oo) phi:(0,2*pi):\phi') + sage: c_spher.codomain() + The Cartesian product of ((0, +oo), (0, 2*pi)) + + sage: M = Manifold(3, 'R^3', r'\RR^3', structure='topological', start_index=1) + sage: c_cart. = M.chart() + sage: c_cart.codomain() + Vector space of dimension 3 over Real Field with 53 bits of precision + + In the current implementation, the codomain of periodic coordinates are represented + by a fundamental domain:: + + sage: V = M.open_subset('V') + sage: c_spher1. = \ + ....: V.chart(r'r:(0,+oo) th:(0,pi):\theta ph1:(0,2*pi):periodic:\phi_1') + sage: c_spher1.codomain() + The Cartesian product of ((0, +oo), (0, pi), [0, 2*pi)) + """ + from sage.sets.real_set import RealSet + from sage.modules.free_module import VectorSpace + from sage.categories.cartesian_product import cartesian_product + intervals = tuple(RealSet.interval(xmin, xmax, + lower_closed=(min_included == 'periodic' or min_included), + upper_closed=(max_included != 'periodic' and max_included)) + for ((xmin, min_included), (xmax, max_included)) in self._bounds) + if all(interval.is_universe() + for interval in intervals): + ambient = VectorSpace(self.manifold().base_field(), self.manifold().dimension()) + else: + ambient = cartesian_product(intervals) + if self._restrictions: + return self._restrict_set(ambient, self._restrictions) + else: + return ambient + def coord_range(self, xx=None): r""" Display the range of a coordinate (or all coordinates), as an @@ -1945,6 +2183,9 @@ def add_restrictions(self, restrictions): r""" Add some restrictions on the coordinates. + This is deprecated; provide the restrictions at the time of creating + the chart. + INPUT: - ``restrictions`` -- list of restrictions on the @@ -1972,6 +2213,10 @@ def add_restrictions(self, restrictions): sage: M = Manifold(2, 'M', structure='topological') # the open unit disc sage: X. = M.chart() sage: X.add_restrictions(x^2+y^2<1) + doctest:warning... + DeprecationWarning: Chart.add_restrictions is deprecated; provide the + restrictions at the time of creating the chart + See https://trac.sagemath.org/32102 for details. sage: X.valid_coordinates(0,2) False sage: X.valid_coordinates(0,1/3) @@ -1999,20 +2244,32 @@ def add_restrictions(self, restrictions): sage: X_U.coord_range() x: (-oo, 0); y: (1/2, +oo) + """ + super().add_restrictions(restrictions) + self._tighten_bounds() + + def _tighten_bounds(self): + """ + Update coordinate bounds from the coordinate restrictions + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') # the open unit disc + sage: X. = M.chart() + sage: U = M.open_subset('U') + sage: X_U = X.restrict(U, restrictions=[x<0, y>1/2]) + sage: X_U.coord_range() + x: (-oo, 0); y: (1/2, +oo) + """ import operator - if not isinstance(restrictions, list): - # case of a single condition or conditions to be combined by "or" - restrictions = [restrictions] - self._restrictions.extend(restrictions) - # Update of the coordinate bounds from the restrictions: bounds = list(self._bounds) # convert to a list for modifications new_restrictions = [] for restrict in self._restrictions: restrict_used = False # determines whether restrict is used # to set some coordinate bound - if not isinstance(restrict, (tuple, list)): # case of combined - # conditions excluded + if not isinstance(restrict, (tuple, list, set, frozenset)): # case of combined + # conditions excluded operands = restrict.operands() left = operands[0] right = operands[1] @@ -2119,23 +2376,24 @@ def restrict(self, subset, restrictions=None): True """ - if subset == self._domain: + if subset == self.domain(): return self if subset not in self._dom_restrict: - if not subset.is_subset(self._domain): + if not subset.is_subset(self.domain()): raise ValueError("the specified subset is not a subset " + "of the domain of definition of the chart") coordinates = "" for coord in self._xx: coordinates += repr(coord) + ' ' + res_coord_restrictions = set(self._restrictions) + res_coord_restrictions.update(self._normalize_coord_restrictions(self._xx, restrictions)) res = type(self)(subset, coordinates, - calc_method=self._calc_method._current) - res._bounds = self._bounds - res._restrictions.extend(self._restrictions) - # The coordinate restrictions are added to the result chart and - # possibly transformed into coordinate bounds: - if restrictions is not None: - res.add_restrictions(restrictions) + calc_method=self._calc_method._current, + bounds=self._bounds, + # The coordinate restrictions are added + # to the result chart and possibly + # transformed into coordinate bounds: + coord_restrictions=res_coord_restrictions) # Update of supercharts and subcharts: res._supercharts.update(self._supercharts) for schart in self._supercharts: @@ -2313,7 +2571,7 @@ def valid_coordinates_numerical(self, *coordinates): # case fast callable has to be computed from operator import lt, gt - if not isinstance(self._restrictions, list): + if not isinstance(self._restrictions, (list, set, frozenset)): if isinstance(self._restrictions, tuple): self._restrictions = [self._restrictions] elif isinstance(self._restrictions, Expression): @@ -2814,10 +3072,10 @@ def _plot_xx_list(xx_list, rem_coords, ranges, steps, number_values): transf = None # to be the MultiCoordFunction object relating self # to the ambient chart if mapping is None: - if not self._domain.is_subset(chart._domain): + if not self.domain().is_subset(chart.domain()): raise ValueError("the domain of {} is not ".format(self) + "included in that of {}".format(chart)) - coord_changes = chart._domain._coord_changes + coord_changes = chart.domain()._coord_changes for chart_pair in coord_changes: if chart_pair == (self, chart): transf = coord_changes[chart_pair]._transf @@ -2832,10 +3090,10 @@ def _plot_xx_list(xx_list, rem_coords, ranges, steps, number_values): if not isinstance(mapping, ContinuousMap): raise TypeError("the argument 'mapping' must be a " "continuous manifold map") - if not self._domain.is_subset(mapping._domain): + if not self.domain().is_subset(mapping.domain()): raise ValueError("the domain of {} is not ".format(self) + "included in that of {}".format(mapping)) - if not chart._domain.is_subset(mapping._codomain): + if not chart.domain().is_subset(mapping._codomain): raise ValueError("the domain of {} is not ".format(chart) + "included in the codomain of {}".format( mapping)) @@ -3095,8 +3353,8 @@ def __init__(self, chart1, chart2, *transformations): self._inverse = None # If the two charts are on the same open subset, the coordinate change # is added to the subset (and supersets) dictionary: - if chart1._domain == chart2._domain: - domain = chart1._domain + if chart1.domain() == chart2.domain(): + domain = chart1.domain() for sdom in domain.open_supersets(): sdom._coord_changes[(chart1, chart2)] = self diff --git a/src/sage/manifolds/chart_func.py b/src/sage/manifolds/chart_func.py index fac02a79bde..144bf2ad52f 100644 --- a/src/sage/manifolds/chart_func.py +++ b/src/sage/manifolds/chart_func.py @@ -1066,7 +1066,7 @@ def derivative(self, coord): # NB: for efficiency, we access directly to the "private" attributes # of other classes. A more conventional OOP writing would be # coordsi = coord - self._chart.domain().start_index() - coordsi = coord - self._chart._domain._sindex + coordsi = coord - self._chart.domain()._sindex if coordsi < 0 or coordsi >= self._nc: raise ValueError("coordinate index out of range") return self._der[coordsi] diff --git a/src/sage/manifolds/continuous_map.py b/src/sage/manifolds/continuous_map.py index 14f20bf2c24..83df25f28c1 100644 --- a/src/sage/manifolds/continuous_map.py +++ b/src/sage/manifolds/continuous_map.py @@ -837,8 +837,7 @@ def image(self, subset=None, inverse=None): sage: M = Manifold(2, 'M', structure="topological") sage: N = Manifold(1, 'N', ambient=M, structure="topological") sage: CM. = M.chart() - sage: CN. = N.chart() - sage: CN.add_restrictions([u > -1, u < 1]) + sage: CN. = N.chart(coord_restrictions=lambda u: [u > -1, u < 1]) sage: Phi = N.continuous_map(M, {(CN,CM): [u, u^2]}, name='Phi') sage: Phi.image() Image of the Continuous map Phi diff --git a/src/sage/manifolds/continuous_map_image.py b/src/sage/manifolds/continuous_map_image.py index 467124551d1..303117f7978 100644 --- a/src/sage/manifolds/continuous_map_image.py +++ b/src/sage/manifolds/continuous_map_image.py @@ -48,8 +48,7 @@ def __init__(self, map, inverse=None, name=None, latex_name=None, domain_subset= sage: M = Manifold(2, 'M', structure="topological") sage: N = Manifold(1, 'N', ambient=M, structure="topological") sage: CM. = M.chart() - sage: CN. = N.chart() - sage: CN.add_restrictions([u > -1, u < 1]) + sage: CN. = N.chart(coord_restrictions=lambda u: [u > -1, u < 1]) sage: Phi = N.continuous_map(M, {(CN,CM): [u, 1 + u^2]}, name='Phi') sage: Phi_inv = M.continuous_map(N, {(CM, CN): [x]}, name='Phi_inv') sage: Phi_N = Phi.image(inverse=Phi_inv) @@ -81,8 +80,7 @@ def _repr_(self): sage: M = Manifold(2, 'M', structure="topological") sage: N = Manifold(1, 'N', ambient=M, structure="topological") sage: CM. = M.chart() - sage: CN. = N.chart() - sage: CN.add_restrictions([u > -1, u < 1]) + sage: CN. = N.chart(coord_restrictions=lambda u: [u > -1, u < 1]) sage: Phi = N.continuous_map(M, {(CN,CM): [u, 1 + u^2]}, name='Phi') sage: Phi.image() # indirect doctest Image of the Continuous map Phi @@ -114,8 +112,7 @@ def _an_element_(self): sage: M = Manifold(2, 'M', structure="topological") sage: N = Manifold(1, 'N', ambient=M, structure="topological") sage: CM. = M.chart() - sage: CN. = N.chart() - sage: CN.add_restrictions([u > -1, u < 1]) + sage: CN. = N.chart(coord_restrictions=lambda u: [u > -1, u < 1]) sage: Phi = N.continuous_map(M, {(CN,CM): [u, 1 + u^2]}, name='Phi') sage: Phi_N = Phi.image() sage: p = Phi_N.an_element(); p # indirect doctest @@ -134,8 +131,7 @@ def __contains__(self, point): sage: M = Manifold(2, 'M', structure="topological") sage: N = Manifold(1, 'N', ambient=M, structure="topological") sage: CM. = M.chart() - sage: CN. = N.chart() - sage: CN.add_restrictions([u > -1, u < 1]) + sage: CN. = N.chart(coord_restrictions=lambda u: [u > -1, u < 1]) sage: Phi = N.continuous_map(M, {(CN,CM): [u, 1 + u^2]}, name='Phi') sage: Phi_inv = M.continuous_map(N, {(CM, CN): [x]}, name='Phi_inv') sage: Phi_N = Phi.image(inverse=Phi_inv) diff --git a/src/sage/manifolds/differentiable/chart.py b/src/sage/manifolds/differentiable/chart.py index 52a0fc53466..dd9aa591202 100644 --- a/src/sage/manifolds/differentiable/chart.py +++ b/src/sage/manifolds/differentiable/chart.py @@ -72,10 +72,6 @@ class DiffChart(Chart): If no period and no LaTeX spelling are to be set for any coordinate, the argument ``coordinates`` can be omitted when the shortcut operator ``<,>`` is used to declare the chart (see examples below). - - ``names`` -- (default: ``None``) unused argument, except if - ``coordinates`` is not provided; it must then be a tuple containing - the coordinate symbols (this is guaranteed if the shortcut operator - ``<,>`` is used). - ``calc_method`` -- (default: ``None``) string defining the calculus method for computations involving coordinates of the chart; must be one of @@ -85,6 +81,27 @@ class DiffChart(Chart): - ``None``: the default of :class:`~sage.manifolds.calculus_method.CalculusMethod` will be used + - ``names`` -- (default: ``None``) unused argument, except if + ``coordinates`` is not provided; it must then be a tuple containing + the coordinate symbols (this is guaranteed if the shortcut operator + ``<,>`` is used). + - ``coord_restrictions``: Additional restrictions on the coordinates. + A restriction can be any symbolic equality or inequality involving + the coordinates, such as ``x > y`` or ``x^2 + y^2 != 0``. The items + of the list (or set or frozenset) ``coord_restrictions`` are combined + with the ``and`` operator; if some restrictions are to be combined with + the ``or`` operator instead, they have to be passed as a tuple in some + single item of the list (or set or frozenset) ``coord_restrictions``. + For example:: + + coord_restrictions=[x > y, (x != 0, y != 0), z^2 < x] + + means ``(x > y) and ((x != 0) or (y != 0)) and (z^2 < x)``. + If the list ``coord_restrictions`` contains only one item, this + item can be passed as such, i.e. writing ``x > y`` instead + of the single element list ``[x > y]``. If the chart variables have + not been declared as variables yet, ``coord_restrictions`` must + be ``lambda``-quoted. EXAMPLES: @@ -259,7 +276,7 @@ class DiffChart(Chart): on differentiable manifolds over `\RR`. """ - def __init__(self, domain, coordinates='', names=None, calc_method=None): + def __init__(self, domain, coordinates, calc_method=None, periods=None, coord_restrictions=None): r""" Construct a chart. @@ -276,8 +293,8 @@ def __init__(self, domain, coordinates='', names=None, calc_method=None): sage: TestSuite(X).run() """ - Chart.__init__(self, domain, coordinates=coordinates, names=names, - calc_method=calc_method) + super().__init__(domain, coordinates, calc_method=calc_method, + periods=periods, coord_restrictions=coord_restrictions) # Construction of the coordinate frame associated to the chart: self._frame = CoordFrame(self) self._coframe = self._frame._coframe @@ -391,8 +408,8 @@ def transition_map(self, other, transformations, intersection_name=None, [Chart (R^2, (x, y)), Chart (U, (r, phi)), Chart (U, (x, y))] """ - dom1 = self._domain - dom2 = other._domain + dom1 = self.domain() + dom2 = other.domain() dom = dom1.intersection(dom2, name=intersection_name) if dom is dom1: chart1 = self @@ -548,7 +565,7 @@ def restrict(self, subset, restrictions=None): Chart (B, (z1, z2)) """ - if subset == self._domain: + if subset == self.domain(): return self if subset not in self._dom_restrict: resu = Chart.restrict(self, subset, restrictions=restrictions) @@ -558,8 +575,8 @@ def restrict(self, subset, restrictions=None): sframe._subframes.add(resu._frame) sframe._restrictions[subset] = resu._frame # The subchart frame is not a "top frame" in the supersets - # (including self._domain): - for dom in self._domain.open_supersets(): + # (including self.domain()): + for dom in self.domain().open_supersets(): if resu._frame in dom._top_frames: # it was added by the Chart constructor invoked in # Chart.restrict above @@ -709,10 +726,6 @@ class RealDiffChart(DiffChart, RealChart): If interval range, no period and no LaTeX spelling are to be set for any coordinate, the argument ``coordinates`` can be omitted when the shortcut operator ``<,>`` is used to declare the chart (see examples below). - - ``names`` -- (default: ``None``) unused argument, except if - ``coordinates`` is not provided; it must then be a tuple containing - the coordinate symbols (this is guaranteed if the shortcut operator - ``<,>`` is used). - ``calc_method`` -- (default: ``None``) string defining the calculus method for computations involving coordinates of the chart; must be one of @@ -722,6 +735,27 @@ class RealDiffChart(DiffChart, RealChart): - ``None``: the default of :class:`~sage.manifolds.calculus_method.CalculusMethod` will be used + - ``names`` -- (default: ``None``) unused argument, except if + ``coordinates`` is not provided; it must then be a tuple containing + the coordinate symbols (this is guaranteed if the shortcut operator + ``<,>`` is used). + - ``coord_restrictions``: Additional restrictions on the coordinates. + A restriction can be any symbolic equality or inequality involving + the coordinates, such as ``x > y`` or ``x^2 + y^2 != 0``. The items + of the list (or set or frozenset) ``coord_restrictions`` are combined + with the ``and`` operator; if some restrictions are to be combined with + the ``or`` operator instead, they have to be passed as a tuple in some + single item of the list (or set or frozenset) ``coord_restrictions``. + For example:: + + coord_restrictions=[x > y, (x != 0, y != 0), z^2 < x] + + means ``(x > y) and ((x != 0) or (y != 0)) and (z^2 < x)``. + If the list ``coord_restrictions`` contains only one item, this + item can be passed as such, i.e. writing ``x > y`` instead + of the single element list ``[x > y]``. If the chart variables have + not been declared as variables yet, ``coord_restrictions`` must + be ``lambda``-quoted. EXAMPLES: @@ -908,9 +942,9 @@ class RealDiffChart(DiffChart, RealChart): `\{y=0, x\geq 0\}`, we must have `y\not=0` or `x<0` on U. Accordingly, we set:: - sage: c_cartU. = U.chart() - sage: c_cartU.add_restrictions((y!=0, x<0)) # the tuple (y!=0, x<0) means y!=0 or x<0 - sage: # c_cartU.add_restrictions([y!=0, x<0]) would have meant y!=0 AND x<0 + sage: c_cartU. = U.chart(coord_restrictions=lambda x,y,z: (y!=0, x<0)) + ....: # the tuple (y!=0, x<0) means y!=0 or x<0 + ....: # [y!=0, x<0] would have meant y!=0 AND x<0 sage: U.atlas() [Chart (U, (r, th, ph)), Chart (U, (x, y, z))] sage: M.atlas() @@ -942,7 +976,8 @@ class RealDiffChart(DiffChart, RealChart): :meth:`~sage.manifolds.chart.RealChart.plot`. """ - def __init__(self, domain, coordinates='', names=None, calc_method=None): + def __init__(self, domain, coordinates, calc_method=None, + bounds=None, periods=None, coord_restrictions=None): r""" Construct a chart on a real differentiable manifold. @@ -960,8 +995,8 @@ def __init__(self, domain, coordinates='', names=None, calc_method=None): sage: TestSuite(X).run() """ - RealChart.__init__(self, domain, coordinates=coordinates, names=names, - calc_method = calc_method) + RealChart.__init__(self, domain, coordinates, calc_method=calc_method, + bounds=bounds, periods=periods, coord_restrictions=coord_restrictions) # Construction of the coordinate frame associated to the chart: self._frame = CoordFrame(self) self._coframe = self._frame._coframe @@ -1031,7 +1066,7 @@ def restrict(self, subset, restrictions=None): True """ - if subset == self._domain: + if subset == self.domain(): return self if subset not in self._dom_restrict: resu = RealChart.restrict(self, subset, restrictions=restrictions) @@ -1041,8 +1076,8 @@ def restrict(self, subset, restrictions=None): sframe._subframes.add(resu._frame) sframe._restrictions[subset] = resu._frame # The subchart frame is not a "top frame" in the supersets - # (including self._domain): - for dom in self._domain.open_supersets(): + # (including self.domain()): + for dom in self.domain().open_supersets(): if resu._frame in dom._top_frames: # it was added by the Chart constructor invoked in # Chart.restrict above @@ -1125,8 +1160,8 @@ def __init__(self, chart1, chart2, *transformations): self._jacobian = self._transf.jacobian() # If the two charts are on the same open subset, the Jacobian matrix is # added to the dictionary of changes of frame: - if chart1._domain == chart2._domain: - domain = chart1._domain + if chart1.domain() == chart2.domain(): + domain = chart1.domain() frame1 = chart1._frame frame2 = chart2._frame vf_module = domain.vector_field_module() diff --git a/src/sage/manifolds/differentiable/diff_map.py b/src/sage/manifolds/differentiable/diff_map.py index 7ab97c33e24..097ffc8c646 100644 --- a/src/sage/manifolds/differentiable/diff_map.py +++ b/src/sage/manifolds/differentiable/diff_map.py @@ -1094,9 +1094,9 @@ def paral_comp(tcomp, chart1, chart2, coord2_1, jacob, coord1 = chart1._xx ff = tensor._express[chart2] resu_fc.append( chart1.function(ff(*(phi(*coord1)))) ) - dom_resu = resu_fc[0].parent()._chart._domain + dom_resu = resu_fc[0].parent()._chart.domain() for fc in resu_fc[1:]: - dom_resu = dom_resu.union(fc.parent()._chart._domain) + dom_resu = dom_resu.union(fc.parent()._chart.domain()) resu = dom_resu.scalar_field(name=resu_name, latex_name=resu_latex_name) for fc in resu_fc: diff --git a/src/sage/manifolds/differentiable/examples/real_line.py b/src/sage/manifolds/differentiable/examples/real_line.py index e4c30257f01..1c9f20c2f9a 100644 --- a/src/sage/manifolds/differentiable/examples/real_line.py +++ b/src/sage/manifolds/differentiable/examples/real_line.py @@ -368,8 +368,6 @@ def __init__(self, lower, upper, ambient_interval=None, coordinate = 't' else: coordinate = names[0] - self._canon_chart = self.chart(coordinates=coordinate) - t = self._canon_chart[start_index] else: if lower < ambient_interval.lower_bound(): raise ValueError("the lower bound is smaller than that of " @@ -379,20 +377,19 @@ def __init__(self, lower, upper, ambient_interval=None, + "the containing interval") self.declare_subset(ambient_interval) ambient_interval._top_subsets.add(self) - t = ambient_interval.canonical_coordinate() if lower != minus_infinity: if upper != infinity: - restrictions = [t > lower, t < upper] + restrictions = lambda t: [t > lower, t < upper] else: - restrictions = t > lower + restrictions = lambda t: t > lower else: if upper != infinity: - restrictions = t < upper + restrictions = lambda t: t < upper else: restrictions = None if ambient_interval is None: - if restrictions is not None: - self._canon_chart.add_restrictions(restrictions) + self._canon_chart = self.chart(coordinates=coordinate, + coord_restrictions=restrictions) else: self._canon_chart = ambient_interval.canonical_chart().restrict(self, restrictions=restrictions) diff --git a/src/sage/manifolds/differentiable/integrated_curve.py b/src/sage/manifolds/differentiable/integrated_curve.py index d3e3dd899ff..b972b26c8eb 100644 --- a/src/sage/manifolds/differentiable/integrated_curve.py +++ b/src/sage/manifolds/differentiable/integrated_curve.py @@ -1469,17 +1469,13 @@ def solve_across_charts(self, charts=None, step=None, solution_key=None, maps:: sage: M = Manifold(2, 'M', structure="Riemannian") - sage: C. = M.chart() - sage: P. = M.chart() + sage: C. = M.chart(coord_restrictions=lambda x,y: x**2+y**2 < 3**2) + sage: P. = M.chart(coord_restrictions=lambda r, th: r > 2) sage: P_to_C = P.transition_map(C,(r*cos(th), r*sin(th))) sage: C_to_P = C.transition_map(P,(sqrt(x**2+y**2), atan2(y,x))) - Let us also add restrictions on those charts, to avoid any - singularity. We have to make sure that the charts still intersect. - Here the intersection is the donut region `2 < r < 3`:: - - sage: P.add_restrictions(r > 2) - sage: C.add_restrictions(x**2+y**2 < 3**2) + Here we added restrictions on those charts, to avoid any + singularity. The intersection is the donut region `2 < r < 3`. We still have to define the metric. This is done in the Cartesian frame. The metric in the polar frame is computed automatically:: @@ -1574,14 +1570,12 @@ def solve_across_charts(self, charts=None, step=None, solution_key=None, .. PLOT:: M = Manifold(2, 'M', structure="Riemannian") - C= M.chart(names = ("x", "y")) + C= M.chart(names = ("x", "y"), coord_restrictions=lambda x,y: x**2+y**2 < 3**2) x, y = C[:] - P = M.chart(names = ("r", "ph")) + P = M.chart(names = ("r", "th"), coord_restrictions=lambda r,th: r > 2) r, th = P[:] P_to_C = P.transition_map(C,(r*cos(th), r*sin(th))) C_to_P = C.transition_map(P,(sqrt(x**2+y**2), atan2(y,x))) - P.add_restrictions(r > 2) - C.add_restrictions(x**2+y**2 < 3**2) g = M.metric() g[0,0,C] = 1 g[1,1,C] = 1 diff --git a/src/sage/manifolds/differentiable/levi_civita_connection.py b/src/sage/manifolds/differentiable/levi_civita_connection.py index 447804bf239..47fc3772c2a 100644 --- a/src/sage/manifolds/differentiable/levi_civita_connection.py +++ b/src/sage/manifolds/differentiable/levi_civita_connection.py @@ -628,8 +628,8 @@ def riemann(self, name=None, latex_name=None): metric of the hyperbolic plane (Poincaré disk model):: sage: M = Manifold(2, 'M', start_index=1) - sage: X. = M.chart('x:(-1,1) y:(-1,1)') # Cartesian coord. on the Poincaré disk - sage: X.add_restrictions(x^2+y^2<1) + sage: X. = M.chart('x:(-1,1) y:(-1,1)', coord_restrictions=lambda x,y: x^2+y^2<1) + ....: # Cartesian coord. on the Poincaré disk sage: g = M.metric('g') sage: g[1,1], g[2,2] = 4/(1-x^2-y^2)^2, 4/(1-x^2-y^2)^2 sage: nab = g.connection() diff --git a/src/sage/manifolds/differentiable/manifold.py b/src/sage/manifolds/differentiable/manifold.py index f4c1d41d224..1b175da72d6 100644 --- a/src/sage/manifolds/differentiable/manifold.py +++ b/src/sage/manifolds/differentiable/manifold.py @@ -1017,8 +1017,8 @@ def diffeomorphism(self, codomain=None, coord_functions=None, chart1=None, sage: M = Manifold(2, 'M') # the open unit disk sage: forget() # for doctests only - sage: c_xy. = M.chart('x:(-1,1) y:(-1,1)') # Cartesian coord on M - sage: c_xy.add_restrictions(x^2+y^2<1) + sage: c_xy. = M.chart('x:(-1,1) y:(-1,1)', coord_restrictions=lambda x,y: x^2+y^2<1) + ....: # Cartesian coord on M sage: N = Manifold(2, 'N') # R^2 sage: c_XY. = N.chart() # canonical coordinates on R^2 sage: Phi = M.diffeomorphism(N, [x/sqrt(1-x^2-y^2), y/sqrt(1-x^2-y^2)], diff --git a/src/sage/manifolds/differentiable/mixed_form.py b/src/sage/manifolds/differentiable/mixed_form.py index 4f6e65bc21e..abe628b404a 100644 --- a/src/sage/manifolds/differentiable/mixed_form.py +++ b/src/sage/manifolds/differentiable/mixed_form.py @@ -25,10 +25,10 @@ #****************************************************************************** from sage.misc.cachefunc import cached_method -from sage.structure.element import AlgebraElement +from sage.structure.element import AlgebraElement, ModuleElementWithMutability from sage.rings.integer import Integer -class MixedForm(AlgebraElement): +class MixedForm(AlgebraElement, ModuleElementWithMutability): r""" An instance of this class is a mixed form along some differentiable map `\varphi: M \to N` between two differentiable manifolds `M` and `N`. More @@ -47,7 +47,10 @@ class MixedForm(AlgebraElement): \forall x\in M, \quad a(x) \in \bigoplus^n_{k=0} \Lambda^k\left( T_{\varphi(x)}^* N \right), where `\Lambda^k(T^*_{\varphi(x)} N)` is the `k`-th exterior power of the - dual of the tangent space `T_{\varphi(x)} N`. + dual of the tangent space `T_{\varphi(x)} N`. Thus, a mixed differential + form `a` consists of homogeneous components `a_i`, `i=0,1, \dots, n`, where + the `i`-th homogeneous component represents a differential form of + degree `i`. The standard case of a mixed form *on* `M` corresponds to `M=N` with `\varphi = \mathrm{Id}_M`. @@ -78,8 +81,20 @@ class MixedForm(AlgebraElement): Graded algebra Omega^*(M) of mixed differential forms on the 2-dimensional differentiable manifold M - One convenient way to define the homogeneous components of a mixed form is - to define some differential forms first:: + The default way to specify the `i`-th homogeneous component + of a mixed form is by accessing it via ``A[i]`` or using :meth:`set_comp`:: + + sage: A = M.mixed_form(name='A') + sage: A[0].set_expr(x) # scalar field + sage: A.set_comp(1)[0] = y*x + sage: A.set_comp(2)[0,1] = y^2*x + sage: A.display() # display names + A = A_0 + A_1 + A_2 + sage: A.display_expansion() # display expansion in basis + A = x + x*y dx + x*y^2 dx∧dy + + Another way to define the homogeneous components is using predefined + differential forms:: sage: f = M.scalar_field(x, name='f'); f Scalar field f on the 2-dimensional differentiable manifold M @@ -92,32 +107,33 @@ class MixedForm(AlgebraElement): sage: eta[e_xy,0,1] = y^2*x; eta.display() eta = x*y^2 dx∧dy - The components of the mixed form ``F`` can be set very easily:: + The components of a mixed form ``B`` can then be set as follows:: - sage: A[:] = [f, omega, eta]; A.display() # display names - A = f + omega + eta - sage: A.display_expansion() # display in coordinates - A = x + x*y dx + x*y^2 dx∧dy - sage: A[0] + sage: B = M.mixed_form(name='B') + sage: B[:] = [f, omega, eta]; B.display() # display names + B = f + omega + eta + sage: B.display_expansion() # display in coordinates + B = x + x*y dx + x*y^2 dx∧dy + sage: B[0] Scalar field f on the 2-dimensional differentiable manifold M - sage: A[0] is f - True - sage: A[1] + sage: B[1] 1-form omega on the 2-dimensional differentiable manifold M - sage: A[1] is omega - True - sage: A[2] + sage: B[2] 2-form eta on the 2-dimensional differentiable manifold M - sage: A[2] is eta - True + + As we can see, the names are applied. However note that the differential + forms are different instances:: + + sage: f is B[0] + False Alternatively, the components can be determined from scratch:: sage: B = M.mixed_form([f, omega, eta], name='B') - sage: A == B - True + sage: B.display() + B = f + omega + eta - Mixed forms are elements of an algebra, so they can be added, and multiplied + Mixed forms are elements of an algebra so they can be added, and multiplied via the wedge product:: sage: C = x*A; C @@ -149,7 +165,7 @@ class MixedForm(AlgebraElement): mixed form:: sage: dA = A.exterior_derivative(); dA.display() - dA = zero + df + domega + dA = zero + dA_0 + dA_1 sage: dA.display_expansion() dA = dx - x dx∧dy @@ -167,18 +183,15 @@ class MixedForm(AlgebraElement): sage: W = U.intersection(V) sage: e_xy = c_xy.frame(); e_uv = c_uv.frame() # define frames sage: A = M.mixed_form(name='A') - sage: A[0].set_name('f') sage: A[0].set_expr(x, c_xy) sage: A[0].display() - f: M → ℝ + A_0: M → ℝ on U: (x, y) ↦ x on W: (u, v) ↦ 1/2*u + 1/2*v - sage: A[1].set_name('omega') sage: A[1][0] = y*x; A[1].display(e_xy) - omega = x*y dx - sage: A[2].set_name('eta') + A_1 = x*y dx sage: A[2][e_uv,0,1] = u*v^2; A[2].display(e_uv) - eta = u*v^2 du∧dv + A_2 = u*v^2 du∧dv sage: A.add_comp_by_continuation(e_uv, W, c_uv) sage: A.display_expansion(e_uv) A = 1/2*u + 1/2*v + (1/8*u^2 - 1/8*v^2) du + (1/8*u^2 - 1/8*v^2) dv + u*v^2 du∧dv @@ -193,12 +206,12 @@ class MixedForm(AlgebraElement): sage: z[0] = 1 Traceback (most recent call last): ... - ValueError: the components of the element zero cannot be changed + ValueError: the components of an immutable element cannot be changed sage: one = M.mixed_form_algebra().one() sage: one[0] = 0 Traceback (most recent call last): ... - ValueError: the components of the element one cannot be changed + ValueError: the components of an immutable element cannot be changed """ def __init__(self, parent, name=None, latex_name=None): @@ -242,8 +255,7 @@ def __init__(self, parent, name=None, latex_name=None): self._max_deg = vmodule._ambient_domain.dim() self._is_zero = False # a priori, may be changed below or via # method __bool__() - # Set components: - self._comp = [self._domain.diff_form(j) for j in self.irange()] + self._comp = None # initialized on demand; see _init_comp # Set names: self._name = name if latex_name is None: @@ -251,6 +263,34 @@ def __init__(self, parent, name=None, latex_name=None): else: self._latex_name = latex_name + def _init_comp(self): + r""" + Initialize homogeneous components of ``self``. + + TESTS:: + + sage: M = Manifold(2, 'M') + sage: A = M.mixed_form(name='A') + sage: A._comp is None + True + sage: A._init_comp() + sage: A._comp + [Scalar field A_0 on the 2-dimensional differentiable manifold M, + 1-form A_1 on the 2-dimensional differentiable manifold M, + 2-form A_2 on the 2-dimensional differentiable manifold M] + + """ + self._comp = [] + for i in self.irange(): + comp_name, comp_latex_name = None, None + if self._name is not None: + comp_name = f"{self._name}_{i}" + if self._latex_name is not None: + comp_latex_name = '{' + self._latex_name + '}_{' + str(i) + '}' + diff_form = self._domain.diff_form + self._comp.append(diff_form(i, name=comp_name, + latex_name=comp_latex_name)) + def _repr_(self): r""" String representation of ``self``. @@ -526,7 +566,7 @@ def display(self): disp = display - def set_name(self, name=None, latex_name=None): + def set_name(self, name=None, latex_name=None, apply_to_comp=True): r""" Redefine the string and LaTeX representation of the object. @@ -535,8 +575,13 @@ def set_name(self, name=None, latex_name=None): - ``name`` -- (default: ``None``) name given to the mixed form - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the mixed form; if none is provided, the LaTeX symbol is set to ``name`` + - ``apply_to_comp`` -- (default: ``True``) if ``True`` all homogeneous + components will be renamed accordingly; if ``False`` only the mixed + form will be renamed - EXAMPLES:: + EXAMPLES: + + Rename a mixed form:: sage: M = Manifold(4, 'M') sage: F = M.mixed_form(name='dummy', latex_name=r'\ugly'); F @@ -544,19 +589,50 @@ def set_name(self, name=None, latex_name=None): manifold M sage: latex(F) \ugly - sage: F.set_name(name='fancy', latex_name=r'\eta'); F - Mixed differential form fancy on the 4-dimensional differentiable + sage: F.set_name(name='F', latex_name=r'\mathcal{F}'); F + Mixed differential form F on the 4-dimensional differentiable manifold M sage: latex(F) - \eta + \mathcal{F} + + If not stated otherwise, all homogeneous components are renamed + accordingly: + + sage: F.display() + F = F_0 + F_1 + F_2 + F_3 + F_4 + + Setting the argument ``set_all`` to ``False`` prevents the renaming + in the homogeneous components:: + + sage: F.set_name(name='eta', latex_name=r'\eta', apply_to_comp=False) + sage: F.display() + eta = F_0 + F_1 + F_2 + F_3 + F_4 + + To rename a homogeneous component individually, we simply access the + homogeneous component and use its + :meth:`~sage.manifolds.differentiable.tensorfield.set_name` method:: + + sage: F[0].set_name(name='g'); F.display() + eta = g + F_1 + F_2 + F_3 + F_4 """ + if self.is_immutable(): + raise ValueError("the name of an immutable element " + "cannot be changed") if name is not None: self._name = name if latex_name is None: self._latex_name = self._name if latex_name is not None: self._latex_name = latex_name + if apply_to_comp: + for i in self.irange(): + comp_name, comp_latex_name = None, None + if self._name is not None: + comp_name = f"{self._name}_{i}" + if self._latex_name is not None: + comp_latex_name = '{' + self._latex_name + '}_{' + str(i) + '}' + self[i].set_name(name=comp_name, latex_name=comp_latex_name) def __bool__(self): r""" @@ -595,7 +671,7 @@ def __bool__(self): """ if self._is_zero: return False - if any(bool(form) for form in self._comp): + if any(bool(form) for form in self): self._is_zero = False return True self._is_zero = True @@ -722,7 +798,7 @@ def _add_(self, other): return self # Generic case: resu = self._new_instance() - resu[:] = [self[j] + other[j] for j in self.irange()] + resu._comp = [self[j] + other[j] for j in self.irange()] # Compose name: if self._name is not None and other._name is not None: resu._name = self._name + '+' + other._name @@ -796,7 +872,7 @@ def _sub_(self, other): return self # Generic case: resu = self._new_instance() - resu[:] = [self[j] - other[j] for j in self.irange()] + resu._comp = [self[j] - other[j] for j in self.irange()] # Compose name: from sage.tensor.modules.format_utilities import is_atomic @@ -915,8 +991,8 @@ def wedge(self, other): return self # Generic case: resu = self._new_instance() - for j in self.irange(): - resu[j] = sum(self[k].wedge(other[j - k]) for k in range(j + 1)) + resu._comp = [sum(self[k].wedge(other[j-k]) for k in range(j+1)) + for j in self.irange()] # Compose name: from sage.typeset.unicode_characters import unicode_wedge from sage.tensor.modules.format_utilities import (format_mul_txt, @@ -969,7 +1045,7 @@ def _lmul_(self, other): if other == 1: return self resu = self._new_instance() - resu[:] = [other * form for form in self._comp] + resu._comp = [other * form for form in self] # Compose name: from sage.misc.latex import latex from sage.typeset.unicode_characters import unicode_wedge @@ -1105,20 +1181,9 @@ def copy(self, name=None, latex_name=None): sage: A is B False - Notice, that changes in the differential forms usually cause changes in - the original instance. But for the copy of a mixed form, the components - are copied as well:: - - sage: omega[e_xy,0] = y; omega.display() - omega = y dx - sage: A.display_expansion(e_xy) - A = x + y dx - sage: B.display_expansion(e_xy) - x + x dx - """ resu = self._new_instance() - resu[:] = [form.copy() for form in self] + resu._comp = [form.copy() for form in self] resu.set_name(name=name, latex_name=latex_name) resu._is_zero = self._is_zero # a priori @@ -1149,15 +1214,13 @@ def __setitem__(self, index, values): A = x + y dx + x*y dx∧dy """ - if self is self.parent().one() or self is self.parent().zero(): - raise ValueError("the components of the element " - "{} cannot be changed".format(self._name)) + if self.is_immutable(): + raise ValueError("the components of an immutable element " + "cannot be changed") if isinstance(index, (int, Integer)): - start = index - stop = index + 1 - step = 1 + start, stop, step = index, index + 1, 1 elif isinstance(index, slice): - start, stop, step = index.indices(len(self._comp)) + start, stop, step = index.indices(self._max_deg + 1) else: raise TypeError("index must be int, Integer or slice") if isinstance(values, list): @@ -1167,8 +1230,10 @@ def __setitem__(self, index, values): if len(form_list) != len(range(start, stop, step)): raise IndexError("either input or index out of range") for deg, j in zip(range(start, stop, step), range(len(form_list))): - self._comp[deg] = self._domain.diff_form_module(deg, - self._dest_map)(form_list[j]) + dmodule = self._domain.diff_form_module(deg, self._dest_map) + form = dmodule(form_list[j]) + self[deg].copy_from(form) # keep the names + self[deg].set_name(name=form._name, latex_name=form._latex_name) self._is_zero = False # a priori def __getitem__(self, deg): @@ -1200,6 +1265,8 @@ def __getitem__(self, deg): 2-form b on the 2-dimensional differentiable manifold M] """ + if self._comp is None: + self._init_comp() return self._comp[deg] def set_restriction(self, rst): @@ -1257,7 +1324,7 @@ def set_restriction(self, rst): if not isinstance(rst, MixedForm): raise TypeError("the argument must be a mixed form") if not rst._domain.is_subset(self._domain): - raise ValueError("the specified domain is not a subset of " + + raise ValueError("the specified domain is not a subset of " "the domain of definition of the mixed form") for j in self.irange(): self[j].set_restriction(rst[j]) @@ -1417,3 +1484,46 @@ def irange(self, start=None): """ return self.parent().irange(start=start) + + def set_comp(self, i): + r""" + Return the `i`-th homogeneous component for assignment. + + EXAMPLES:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: A = M.mixed_form(name='A') + sage: A.set_comp(0).set_expr(x^2) # scalar field + sage: A.set_comp(1)[:] = [-y, x] + sage: A.set_comp(2)[0,1] = x-y + sage: A.display() + A = A_0 + A_1 + A_2 + sage: A.display_expansion() + A = x^2 - y dx + x dy + (x - y) dx∧dy + + """ + return self[i] + + def set_immutable(self): + r""" + Set ``self`` and homogeneous components of ``self`` immutable. + + EXAMPLES:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: f = M.scalar_field(x^2, name='f') + sage: A = M.mixed_form([f, 0, 0], name='A') + sage: A.set_immutable() + sage: A.is_immutable() + True + sage: A[0].is_immutable() + True + sage: f.is_immutable() + False + + """ + for form in self: + form.set_immutable() + super().set_immutable() diff --git a/src/sage/manifolds/differentiable/mixed_form_algebra.py b/src/sage/manifolds/differentiable/mixed_form_algebra.py index fdf5f2ff9a2..053f58b6aad 100644 --- a/src/sage/manifolds/differentiable/mixed_form_algebra.py +++ b/src/sage/manifolds/differentiable/mixed_form_algebra.py @@ -270,8 +270,8 @@ def _an_element_(self): """ res = self.element_class(self) dom = self._domain - res[:] = [dom.diff_form_module(j, self._dest_map)._an_element_() - for j in self.irange()] + res._comp = [dom.diff_form_module(j, self._dest_map)._an_element_() + for j in self.irange()] return res def _coerce_map_from_(self, S): @@ -318,8 +318,7 @@ def _coerce_map_from_(self, S): return True # Let us check for each degree consecutively: dom = self._domain - return any(dom.diff_form_module(deg, - self._dest_map).has_coerce_map_from(S) + return any(dom.diff_form_module(deg, self._dest_map).has_coerce_map_from(S) for deg in self.irange()) @cached_method @@ -337,9 +336,10 @@ def zero(self): """ res = self.element_class(self, name='zero', latex_name='0') - res._comp[:] = [self._domain.diff_form_module(j, - dest_map=self._dest_map).zero() for j in self.irange()] + res._comp = [self._domain.diff_form_module(j, dest_map=self._dest_map).zero() + for j in self.irange()] res._is_zero = True # This element is certainly zero + res.set_immutable() return res @cached_method @@ -357,10 +357,10 @@ def one(self): """ res = self.element_class(self, name='one', latex_name='1') - res._comp[0] = self._domain.one_scalar_field() - res._comp[1:] = [self._domain.diff_form_module(j, - dest_map=self._dest_map).zero() - for j in self.irange(1)] + res._comp = [self._domain.one_scalar_field(), + *(self._domain.diff_form_module(j, dest_map=self._dest_map).zero() + for j in self.irange(1))] + res.set_immutable() return res def vector_field_module(self): diff --git a/src/sage/manifolds/differentiable/scalarfield_algebra.py b/src/sage/manifolds/differentiable/scalarfield_algebra.py index 6383c40398f..be5d9176cd3 100644 --- a/src/sage/manifolds/differentiable/scalarfield_algebra.py +++ b/src/sage/manifolds/differentiable/scalarfield_algebra.py @@ -426,7 +426,7 @@ def _coerce_map_from_(self, other): elif isinstance(other, DiffScalarFieldAlgebra): return self._domain.is_subset(other._domain) elif isinstance(other, ChartFunctionRing): - return self._domain.is_subset(other._chart._domain) + return self._domain.is_subset(other._chart.domain()) else: return False diff --git a/src/sage/manifolds/manifold.py b/src/sage/manifolds/manifold.py index c04f2ea9451..801ad12ab02 100644 --- a/src/sage/manifolds/manifold.py +++ b/src/sage/manifolds/manifold.py @@ -669,7 +669,7 @@ def _an_element_(self): sage: p in V True sage: p.coord() - (-pi - 1, 2) + (-pi - 1, 0) """ from sage.rings.infinity import Infinity @@ -1429,7 +1429,8 @@ def is_manifestly_coordinate_domain(self): """ return bool(self._covering_charts) - def chart(self, coordinates='', names=None, calc_method=None): + def chart(self, coordinates='', names=None, calc_method=None, + coord_restrictions=None): r""" Define a chart, the domain of which is the manifold. @@ -1461,6 +1462,8 @@ def chart(self, coordinates='', names=None, calc_method=None): - ``'sympy'``: SymPy - ``None``: the current calculus method defined on the manifold is used (cf. :meth:`set_calculus_method`) + - ``coord_restrictions``: Additional restrictions on the coordinates. + See below. The coordinates declared in the string ``coordinates`` are separated by ``' '`` (whitespace) and each coordinate has at most four @@ -1493,6 +1496,26 @@ def chart(self, coordinates='', names=None, calc_method=None): omitted when the shortcut operator ``<,>`` is used to declare the chart (see examples below). + Additional restrictions on the coordinates can be set using the + argument ``coord_restrictions``. + + A restriction can be any symbolic equality or inequality involving + the coordinates, such as ``x > y`` or ``x^2 + y^2 != 0``. The items + of the list (or set or frozenset) ``coord_restrictions`` are combined + with the ``and`` operator; if some restrictions are to be combined with + the ``or`` operator instead, they have to be passed as a tuple in some + single item of the list (or set or frozenset) ``coord_restrictions``. + For example:: + + coord_restrictions=[x > y, (x != 0, y != 0), z^2 < x] + + means ``(x > y) and ((x != 0) or (y != 0)) and (z^2 < x)``. + If the list ``coord_restrictions`` contains only one item, this + item can be passed as such, i.e. writing ``x > y`` instead + of the single element list ``[x > y]``. If the chart variables have + not been declared as variables yet, ``coord_restrictions`` must + be ``lambda``-quoted. + OUTPUT: - the created chart, as an instance of @@ -1560,6 +1583,16 @@ def chart(self, coordinates='', names=None, calc_method=None): sage: latex(P) \left(U,(r, {\phi})\right) + Using ``coord_restrictions``:: + + sage: D = Manifold(2, 'D', structure='topological') + sage: X. = D.chart(coord_restrictions=lambda x,y: [x^2+y^2<1, x>0]); X + Chart (D, (x, y)) + sage: X.valid_coordinates(0, 0) + False + sage: X.valid_coordinates(1/2, 0) + True + See the documentation of classes :class:`~sage.manifolds.chart.Chart` and :class:`~sage.manifolds.chart.RealChart` for more examples, @@ -1569,7 +1602,8 @@ def chart(self, coordinates='', names=None, calc_method=None): if calc_method is None: calc_method = self._calculus_method return self._structure.chart(self, coordinates=coordinates, - names=names, calc_method=calc_method) + names=names, calc_method=calc_method, + coord_restrictions=coord_restrictions) def is_open(self): """ @@ -1727,7 +1761,7 @@ def orientation(self): # Try the default chart: def_chart = self._def_chart if def_chart is not None: - if def_chart._domain is self: + if def_chart.domain() is self: self._orientation = [self._def_chart] # Still no orientation? Choose arbitrary chart: if not self._orientation: @@ -1967,7 +2001,7 @@ def scalar_field(self, coord_expression=None, chart=None, name=None, if isinstance(coord_expression, dict): # check validity of entry for chart in coord_expression: - if not chart._domain.is_subset(self): + if not chart.domain().is_subset(self): raise ValueError("the {} is not defined ".format(chart) + "on some subset of the " + str(self)) alg = self.scalar_field_algebra() @@ -2347,8 +2381,8 @@ def homeomorphism(self, codomain, coord_functions=None, chart1=None, sage: forget() # for doctests only sage: M = Manifold(2, 'M', structure='topological') # the open unit disk - sage: c_xy. = M.chart('x:(-1,1) y:(-1,1)') # Cartesian coord on M - sage: c_xy.add_restrictions(x^2+y^2<1) + sage: c_xy. = M.chart('x:(-1,1) y:(-1,1)', coord_restrictions=lambda x,y: x^2+y^2<1) + ....: # Cartesian coord on M sage: N = Manifold(2, 'N', structure='topological') # R^2 sage: c_XY. = N.chart() # canonical coordinates on R^2 sage: Phi = M.homeomorphism(N, [x/sqrt(1-x^2-y^2), y/sqrt(1-x^2-y^2)], diff --git a/src/sage/manifolds/point.py b/src/sage/manifolds/point.py index 1c77ab9ed41..3a841e2d7cb 100644 --- a/src/sage/manifolds/point.py +++ b/src/sage/manifolds/point.py @@ -374,7 +374,7 @@ def coordinates(self, chart=None, old_chart=None): chart = dom._def_chart def_chart = chart else: - dom = chart._domain + dom = chart.domain() def_chart = dom._def_chart if self not in dom: raise ValueError("the point does not belong to the domain " + diff --git a/src/sage/manifolds/scalarfield.py b/src/sage/manifolds/scalarfield.py index 0c37cb34f0c..34c9f0a04b1 100644 --- a/src/sage/manifolds/scalarfield.py +++ b/src/sage/manifolds/scalarfield.py @@ -2042,7 +2042,7 @@ def add_expr_by_continuation(self, chart, subdomain): if self.is_immutable(): raise ValueError("the expressions of an immutable element " "cannot be changed") - if not chart._domain.is_subset(self._domain): + if not chart.domain().is_subset(self._domain): raise ValueError("the chart is not defined on a subset of " + "the scalar field domain") schart = chart.restrict(subdomain) @@ -2088,7 +2088,7 @@ def set_restriction(self, rst): self._restrictions[rst._domain] = rst.copy(name=self._name, latex_name=self._latex_name) for chart, expr in rst._express.items(): - intersection = chart._domain.intersection(rst._domain) + intersection = chart.domain().intersection(rst._domain) self._express[chart.restrict(intersection)] = expr self._is_zero = False # a priori @@ -2166,13 +2166,13 @@ def _display_expression(self, chart, result): coords = chart[:] if len(coords) == 1: coords = coords[0] - if chart._domain == self._domain: + if chart.domain() == self._domain: if self._name is not None: result._txt += " " result._latex += " & " else: - result._txt += "on " + chart._domain._name + ": " - result._latex += r"\mbox{on}\ " + latex(chart._domain) \ + result._txt += "on " + chart.domain()._name + ": " + result._latex += r"\mbox{on}\ " + latex(chart.domain()) \ + r": & " result._txt += repr(coords) + " " + unicode_mapsto + " " \ + repr(expression) + "\n" @@ -2216,13 +2216,13 @@ def _display_expression(self, chart, result): if max_dom is None: try: self.coord_function(sch) - max_dom = sch._domain + max_dom = sch.domain() except (TypeError, ValueError): pass - elif max_dom.is_subset(sch._domain): + elif max_dom.is_subset(sch.domain()): try: self.coord_function(sch) - max_dom = sch._domain + max_dom = sch.domain() except (TypeError, ValueError): pass if max_dom is not None: diff --git a/src/sage/manifolds/scalarfield_algebra.py b/src/sage/manifolds/scalarfield_algebra.py index 5c57e017efa..6d6ba2f88ca 100644 --- a/src/sage/manifolds/scalarfield_algebra.py +++ b/src/sage/manifolds/scalarfield_algebra.py @@ -528,7 +528,7 @@ def _coerce_map_from_(self, other): elif isinstance(other, ScalarFieldAlgebra): return self._domain.is_subset(other._domain) elif isinstance(other, ChartFunctionRing): - return self._domain.is_subset(other._chart._domain) + return self._domain.is_subset(other._chart.domain()) else: return False diff --git a/src/sage/manifolds/topological_submanifold.py b/src/sage/manifolds/topological_submanifold.py index ebd842961de..ef343240ee6 100644 --- a/src/sage/manifolds/topological_submanifold.py +++ b/src/sage/manifolds/topological_submanifold.py @@ -881,8 +881,7 @@ def as_subset(self): sage: M = Manifold(2, 'M', structure="topological") sage: N = Manifold(1, 'N', ambient=M, structure="topological") sage: CM. = M.chart() - sage: CN. = N.chart() - sage: CN.add_restrictions([u > -1, u < 1]) + sage: CN. = N.chart(coord_restrictions=lambda u: [u > -1, u < 1]) sage: phi = N.continuous_map(M, {(CN,CM): [u, u^2]}) sage: N.set_embedding(phi) sage: N diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index 780583b9312..f8c6d91c236 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -9187,13 +9187,13 @@ cdef class Matrix(Matrix1): return False return True - def is_diagonal(self): + def is_diagonal(self) -> bool: """ - Return True if this matrix is a diagonal matrix. + Return ``True`` if this matrix is a diagonal matrix. OUTPUT: - - whether self is a diagonal matrix. + boolean EXAMPLES:: @@ -9221,9 +9221,51 @@ cdef class Matrix(Matrix1): return False return True - def is_unitary(self): + def is_triangular(self, side="lower") -> bool: + """ + Return ``True`` if this matrix is a triangular matrix. + + INPUT: + + - ``side`` -- either ``"lower"`` (default) or ``"upper"`` + + OUTPUT: + + boolean + + EXAMPLES:: + + sage: m = matrix(QQ, 2, 2, range(4)) + sage: m.is_triangular() + False + sage: m = matrix(QQ, 2, [5, 0, 0, 5]) + sage: m.is_triangular() + True + sage: m = matrix(QQ, 2, [1, 2, 0, 1]) + sage: m.is_triangular("upper") + True + sage: m.is_triangular("lower") + False + """ + if not self.is_square(): + return False + cdef Py_ssize_t i, j + + if side == "upper": + for i in range(1, self._nrows): + for j in range(i): + if not self.get_unsafe(i, j).is_zero(): + return False + else: + for i in range(self._nrows - 1): + for j in range(i + 1, self._ncols): + if not self.get_unsafe(i, j).is_zero(): + return False + return True + + def is_unitary(self) -> bool: r""" - Returns ``True`` if the columns of the matrix are an orthonormal basis. + Return ``True`` if the columns of the matrix are an orthonormal basis. For a matrix with real entries this determines if a matrix is "orthogonal" and for a matrix with complex entries this determines diff --git a/src/sage/misc/all.py b/src/sage/misc/all.py index d108d4e5f74..375b8e852f1 100644 --- a/src/sage/misc/all.py +++ b/src/sage/misc/all.py @@ -10,6 +10,8 @@ SAGE_DB, SAGE_TMP, newton_method_sizes, compose, nest) +lazy_import('sage.misc.misc', 'union', + deprecation=32096) from .verbose import (set_verbose, set_verbose_files, get_verbose_files, unset_verbose_files, get_verbose) diff --git a/src/sage/misc/latex.py b/src/sage/misc/latex.py index 279c21e2f6e..d477d34beff 100644 --- a/src/sage/misc/latex.py +++ b/src/sage/misc/latex.py @@ -29,19 +29,18 @@ import random import re import shutil -import subprocess +from subprocess import call, PIPE from sage.misc import sage_eval from sage.misc.cachefunc import cached_function, cached_method from sage.misc.sage_ostools import have_program from sage.misc.temporary_file import tmp_dir - +from sage.structure.sage_object import SageObject from sage.misc.lazy_import import lazy_import lazy_import('sage.misc.html', ('MathJax', 'MathJaxExpr'), deprecation=31536) -COMMON_HEADER = \ -r'''\usepackage{amsmath} +COMMON_HEADER = r'''\usepackage{amsmath} \usepackage{amssymb} \usepackage{amsfonts} \usepackage{graphicx} @@ -51,8 +50,7 @@ \usepackage[T1]{fontenc} ''' -LATEX_HEADER = ( -r'''\documentclass{article} +LATEX_HEADER = (r'''\documentclass{article} ''' + COMMON_HEADER + r'''\oddsidemargin 0.0in \evensidemargin 0.0in @@ -63,8 +61,7 @@ \textheight 9.0in ''') -SLIDE_HEADER = ( -r'''\documentclass[a0,8pt]{beamer} +SLIDE_HEADER = (r'''\documentclass[a0,8pt]{beamer} ''' + COMMON_HEADER + r'''\textwidth=1.1\textwidth \textheight=2\textheight @@ -72,11 +69,11 @@ @cached_function -def have_latex(): +def have_latex() -> bool: """ Return ``True`` if this computer has the program ``latex``. - If this computer doesn't have LaTeX installed, you may obtain it + If this computer does not have LaTeX installed, you may obtain it from http://ctan.org/. EXAMPLES:: @@ -89,11 +86,11 @@ def have_latex(): @cached_function -def have_pdflatex(): +def have_pdflatex() -> bool: """ Return ``True`` if this computer has the program ``pdflatex``. - If this computer doesn't have pdflatex installed, you may obtain it + If this computer does not have pdflatex installed, you may obtain it from http://ctan.org/. EXAMPLES:: @@ -106,11 +103,11 @@ def have_pdflatex(): @cached_function -def have_xelatex(): +def have_xelatex() -> bool: """ Return ``True`` if this computer has the program ``xelatex``. - If this computer doesn't have xelatex installed, you may obtain it + If this computer does not have xelatex installed, you may obtain it from http://ctan.org/. EXAMPLES:: @@ -123,11 +120,11 @@ def have_xelatex(): @cached_function -def have_dvipng(): +def have_dvipng() -> bool: """ Return ``True`` if this computer has the program ``dvipng``. - If this computer doesn't have dvipng installed, you may obtain it + If this computer does not have dvipng installed, you may obtain it from http://sourceforge.net/projects/dvipng/ EXAMPLES:: @@ -140,11 +137,11 @@ def have_dvipng(): @cached_function -def have_convert(): +def have_convert() -> bool: """ Return ``True`` if this computer has the program ``convert``. - If this computer doesn't have convert installed, you may obtain it + If this computer does not have convert installed, you may obtain it (along with the rest of the ImageMagick suite) from http://www.imagemagick.org @@ -233,6 +230,7 @@ def bool_function(x): """ return r"\mathrm{%s}" % bool(x) + def builtin_constant_function(x): r""" Returns the LaTeX code for a builtin constant ``x``. @@ -258,6 +256,7 @@ def builtin_constant_function(x): """ return "\\mbox{\\rm %s}" % x + def None_function(x): r""" Returns the LaTeX code for ``None``. @@ -275,6 +274,7 @@ def None_function(x): assert x is None return r"\mathrm{None}" + def str_function(x): r""" Return a LaTeX representation of the string ``x``. @@ -338,6 +338,7 @@ def str_function(x): x = "\\begin{array}{l}\n%s\n\\end{array}" % x return x + def dict_function(x): r""" Return the LaTeX code for a dictionary ``x``. @@ -365,6 +366,7 @@ def dict_function(x): # One can add to the latex_table in order to install latexing # functionality for other types. (Suggested by Robert Kerns of Enthought.) + def float_function(x): r""" Returns the LaTeX code for a python float ``x``. @@ -471,7 +473,7 @@ def __add__(self, other): sage: type(o) - We add extra space only if it wasn't there yet:: + We add extra space only if it was not there yet:: sage: LatexExpr("foo ") + LatexExpr("bar") foo bar @@ -529,7 +531,8 @@ def _latex_(self): """ return str(self) -def has_latex_attr(x): + +def has_latex_attr(x) -> bool: """ Return ``True`` if ``x`` has a ``_latex_`` attribute, except if ``x`` is a ``type``, in which case return ``False``. @@ -558,7 +561,6 @@ def has_latex_attr(x): """ return hasattr(x, '_latex_') and not isinstance(x, type) -from sage.structure.sage_object import SageObject class _Latex_prefs_object(SageObject): """ @@ -585,6 +587,7 @@ def __init__(self, bb=False, delimiters=["(", ")"], self._option["engine"] = "pdflatex" self._option["engine_name"] = "LaTeX" + _Latex_prefs = _Latex_prefs_object() ############################################################## @@ -592,6 +595,7 @@ def __init__(self, bb=False, delimiters=["(", ")"], # the Sage Notebook ######################################### + def latex_extra_preamble(): r""" Return the string containing the user-configured preamble, @@ -631,12 +635,13 @@ def latex_extra_preamble(): "\n".join(sage_latex_macros()), _Latex_prefs._option['macros']]) + def _run_latex_(filename, debug=False, density=150, engine=None, png=False, do_in_background=False): """ This runs LaTeX on the TeX file "filename.tex". It produces files "filename.dvi" (or "filename.pdf"` if engine is either ``pdflatex`` or ``xelatex``) and if ``png`` is ``True``, "filename.png". If ``png`` - is ``True`` and dvipng can't convert the dvi file to png (because of + is ``True`` and dvipng cannot convert the dvi file to png (because of postscript specials or other issues), then dvips is called, and the PS file is converted to a png file. @@ -675,7 +680,7 @@ def _run_latex_(filename, debug=False, density=150, engine=None, png=False, do_i If ``png`` is ``True``, then when using latex (the default), you must have 'dvipng' (or 'dvips' and 'convert') installed on your - operating system, or this command won't work. When using + operating system, or this command will not work. When using pdflatex or xelatex, you must have 'convert' installed. EXAMPLES:: @@ -762,7 +767,7 @@ def _run_latex_(filename, debug=False, density=150, engine=None, png=False, do_i if len(filename.split()) > 1: raise ValueError("filename must contain no spaces") if not debug: - redirect = subprocess.PIPE + redirect = PIPE else: redirect = None # if do_in_background: @@ -788,14 +793,16 @@ def _run_latex_(filename, debug=False, density=150, engine=None, png=False, do_i '{0}x{0}'.format(density), '-trim', filename + '.' + suffix, filename + '.png'] - e = False # it is possible to get through the following commands - # without running a program, so in that case we force error + # it is possible to get through the following commands + # without running a program, so in that case we force error + e = False # our standard way of calling programs here; change this if we want # finer-grained analysis of the return code. Think of the output as # a boolean: "the command exited normally" - subpcall = lambda x: not subprocess.call(x, stdout=redirect, - stderr=redirect, cwd=base) + def subpcall(x): + return not call(x, stdout=redirect, + stderr=redirect, cwd=base) if engine == "pdflatex" or engine == "xelatex": if debug: print(lt) @@ -842,7 +849,7 @@ def _run_latex_(filename, debug=False, density=150, engine=None, png=False, do_i print("error running dvips and ps2pdf; trying pdflatex instead...") print(pdflt) e = subpcall(pdflt) - else: # don't have dvipng, so must have convert. run latex, dvips, convert. + else: # do not have dvipng, so must have convert. run latex, dvips, convert. if debug: print(lt) print(dvips) @@ -944,7 +951,7 @@ class Latex(LatexCall): .. WARNING:: You must have dvipng (or dvips and convert) installed - on your operating system, or this command won't work. + on your operating system, or this command will not work. EXAMPLES:: @@ -992,9 +999,9 @@ def _relation_symbols(self): ' \\geq ' """ import operator - return {operator.lt:' < ', operator.le:' \\leq ', - operator.eq:' = ', operator.ne:' \\neq ', - operator.ge:' \\geq ', operator.gt:' > '} + return {operator.lt: ' < ', operator.le: ' \\leq ', + operator.eq: ' = ', operator.ne: ' \\neq ', + operator.ge: ' \\geq ', operator.gt: ' > '} def _latex_preparse(self, s, locals): r""" @@ -1013,7 +1020,7 @@ def _latex_preparse(self, s, locals): if i == -1 or i == i0: return s i0 = i - t = s[i+6:] + t = s[i + 6:] j = t.find('}') if j == -1: return s @@ -1024,7 +1031,7 @@ def _latex_preparse(self, s, locals): except Exception as msg: print(msg) k = '\\mbox{\\rm [%s undefined]}' % var - s = s[:i] + k + t[j+1:] + s = s[:i] + k + t[j + 1:] def eval(self, x, globals, strip=False, filename=None, debug=None, density=None, pdflatex=None, engine=None, locals={}): @@ -1082,7 +1089,7 @@ def eval(self, x, globals, strip=False, filename=None, debug=None, if density is None: density = self.__density if filename is None: - filename = 'sage%s' % random.randint(1, 100) # to defeat browser caches + filename = 'sage%s' % random.randint(1, 100) # to defeat browser caches else: filename = os.path.splitext(filename)[0] # get rid of extension base = tmp_dir() @@ -1115,7 +1122,7 @@ def eval(self, x, globals, strip=False, filename=None, debug=None, else: engine = self.__engine e = _run_latex_(os.path.join(base, filename + ".tex"), debug=debug, - density=density, engine=engine, png=True) + density=density, engine=engine, png=True) if e.find("Error") == -1: shutil.copy(os.path.join(base, filename + ".png"), os.path.join(orig_base, filename + ".png")) @@ -1124,7 +1131,7 @@ def eval(self, x, globals, strip=False, filename=None, debug=None, else: return - def blackboard_bold(self, t = None): + def blackboard_bold(self, t=None): r"""nodetex Controls whether Sage uses blackboard bold or ordinary bold face for typesetting ZZ, RR, etc. @@ -1331,7 +1338,7 @@ def matrix_column_alignment(self, align=None): _Latex_prefs._option['matrix_column_alignment'] = align @cached_method - def has_file(self, file_name): + def has_file(self, file_name) -> bool: """ INPUT: @@ -1347,15 +1354,15 @@ def has_file(self, file_name): False """ assert isinstance(file_name, str) - from subprocess import call, PIPE try: - retcode = call("kpsewhich %s"%file_name, shell=True, stdout=PIPE, stderr=PIPE) + retcode = call("kpsewhich %s" % file_name, shell=True, + stdout=PIPE, stderr=PIPE) return (retcode == 0) except OSError: return False @cached_method - def check_file(self, file_name, more_info = ""): + def check_file(self, file_name, more_info=""): """ INPUT: @@ -1389,7 +1396,6 @@ def check_file(self, file_name, more_info = ""): if more_info: print(more_info) - def extra_macros(self, macros=None): r"""nodetex String containing extra LaTeX macros to use with %latex and %html. @@ -1541,10 +1547,10 @@ def add_package_to_preamble_if_available(self, package_name): sage: latex.extra_preamble('') """ assert isinstance(package_name, str) - if self.has_file(package_name+".sty"): - self.add_to_preamble("\\usepackage{%s}\n"%package_name) + if self.has_file(package_name + ".sty"): + self.add_to_preamble("\\usepackage{%s}\n" % package_name) - def engine(self, e = None): + def engine(self, e=None): r""" Set Sage to use ``e`` as latex engine when typesetting with :func:`view`, in ``%latex`` cells, etc. @@ -1597,13 +1603,15 @@ def engine(self, e = None): # old latex function are now in Latex.__call__; thus the following # assignment. + latex = Latex() # Ensure that latex appear in the sphinx doc as a function # so that the link :func:`latex` is correctly set up. -latex.__doc__ = Latex.__call__.__doc__ +latex.__doc__ = Latex.__call__.__doc__ ######################################### -def _latex_file_(objects, title='SAGE', debug=False, \ + +def _latex_file_(objects, title='SAGE', debug=False, sep='', tiny=False, math_left='\\[', math_right='\\]', extra_preamble=''): @@ -1687,12 +1695,12 @@ def _latex_file_(objects, title='SAGE', debug=False, \ objects = [objects] if tiny: - size='\\tiny\n' + size = '\\tiny\n' else: - size='' + size = '' - formatted_title = "\n\\begin{center}{\\Large\\bf %s}\\end{center}\n"%str(title) if title else "" - s = '%s\n\\begin{document}%s%s'%(extra_preamble, formatted_title, size) + formatted_title = "\n\\begin{center}{\\Large\\bf %s}\\end{center}\n" % str(title) if title else "" + s = '%s\n\\begin{document}%s%s' % (extra_preamble, formatted_title, size) if title: s += '\\vspace{40mm}' @@ -1704,17 +1712,17 @@ def _latex_file_(objects, title='SAGE', debug=False, \ # Resize the pgf figure to the text width if larger. s += r'\begingroup\makeatletter\@ifundefined{pgffigure}{\newsavebox{\pgffigure}}{}\makeatother\endgroup' s += r'\begin{lrbox}{\pgffigure}' + '\n' - s += '%s'%L + s += '%s' % L s += r'\end{lrbox}' s += r'\resizebox{\ifdim\width>\textwidth\textwidth\else\width\fi}{!}{\usebox{\pgffigure}}' + '\n' elif '\\begin{verbatim}' not in L: - s += '%s%s%s'%(math_left, L, math_right) + s += '%s%s%s' % (math_left, L, math_right) else: - s += '%s'%L - if i < len(objects)-1: - s += '\n\n%s\n\n'%sep + s += '%s' % L + if i < len(objects) - 1: + s += '\n\n%s\n\n' % sep else: - s += "\n\n".join([str(x) for x in objects]) + s += "\n\n".join(str(x) for x in objects) # latex_extra_preamble() is called here and not before because some objects # may require additional packages to be displayed in LaTeX. Hence, the call @@ -1728,6 +1736,7 @@ def _latex_file_(objects, title='SAGE', debug=False, \ return s + def view(objects, title='Sage', debug=False, sep='', tiny=False, pdflatex=None, engine=None, viewer=None, tightpage=True, margin=None, mode='inline', combine_all=False, **kwds): @@ -1916,10 +1925,11 @@ def view(objects, title='Sage', debug=False, sep='', tiny=False, viewer = 'sage-native-execute ' + viewer if debug: print('viewer: "{}"'.format(viewer)) - subprocess.call('%s %s' % (viewer, output_file), shell=True, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) + call('%s %s' % (viewer, output_file), shell=True, + stdout=PIPE, stderr=PIPE) return + def png(x, filename, density=150, debug=False, do_in_background=False, tiny=False, pdflatex=True, engine='pdflatex'): """ @@ -1984,6 +1994,7 @@ def png(x, filename, density=150, debug=False, return s return + def coeff_repr(c): r""" LaTeX string representing coefficients in a linear combination. @@ -2015,6 +2026,7 @@ def coeff_repr(c): return "(%s)" % s return s + def repr_lincomb(symbols, coeffs): r""" Compute a latex representation of a linear combination of some @@ -2102,7 +2114,7 @@ def repr_lincomb(symbols, coeffs): i += 1 if first: s = "0" - s = s.replace("+ -","- ") + s = s.replace("+ -", "- ") return s @@ -2146,6 +2158,7 @@ def repr_lincomb(symbols, coeffs): 'times', 'star'] + def latex_varify(a, is_fname=False): r""" Convert a string ``a`` to a LaTeX string: if it's an element of @@ -2189,9 +2202,10 @@ def latex_varify(a, is_fname=False): elif len(a) == 1: return a elif is_fname is True: - return '{\\rm %s}'%a + return '{\\rm %s}' % a else: - return '\\mathit{%s}'%a + return '\\mathit{%s}' % a + def latex_variable_name(x, is_fname=False): r""" @@ -2274,7 +2288,7 @@ def latex_variable_name(x, is_fname=False): suffix = x[m.start():] else: prefix = x[:underscore] - suffix = x[underscore+1:] + suffix = x[underscore + 1:] if prefix == '': from sage.calculus.calculus import symtable for sym in symtable.values(): @@ -2284,7 +2298,7 @@ def latex_variable_name(x, is_fname=False): # handle the suffix specially because it very well might be numeric # I use strip to avoid using regex's -- It makes it a bit faster (and the code is more comprehensible to non-regex'ed people) if suffix.strip("1234567890") != "": - suffix = latex_variable_name(suffix, is_fname) # recurse to deal with recursive subscripts + suffix = latex_variable_name(suffix, is_fname) # recurse to deal with recursive subscripts return '%s_{%s}' % (latex_varify(prefix, is_fname), suffix) else: return latex_varify(prefix, is_fname) @@ -2339,7 +2353,7 @@ def _repr_(self): """ return r"""LaTeX example for testing display of graphs. -To use, first try calling 'view' on this object -- it won't work. +To use, first try calling 'view' on this object -- it will not work. Now, make sure that you have the most recent version of the TeX package pgf installed, along with the LaTeX package tkz-graph. Run 'latex.add_to_preamble("\\usepackage{tkz-graph}")', and try viewing it @@ -2407,11 +2421,11 @@ def _repr_(self): """ return """LaTeX example for testing display of pstricks output. -To use, first try calling 'view' on this object -- it won't work. Now, +To use, first try calling 'view' on this object -- it will not work. Now, make sure that you have the most recent version of the TeX package pstricks installed. Run 'latex.add_to_preamble("\\usepackage{pstricks}")' and try viewing it again. Call 'view' with the option `engine='latex'` --- the default behavior is to use pdflatex, which doesn't work with +-- the default behavior is to use pdflatex, which does not work with pstricks. From the command line, this should pop open a nice window with a picture of forces acting on a mass on a pendulum.""" @@ -2464,7 +2478,7 @@ def _repr_(self): """ return r"""LaTeX example for testing display of a knot produced by xypic. -To use, try to view this object -- it won't work. Now try +To use, try to view this object -- it will not work. Now try 'latex.add_to_preamble("\\usepackage[graph,knot,poly,curve]{xypic}")', and try viewing again. @@ -2514,7 +2528,7 @@ def _repr_(self): return r"""LaTeX example for testing display of a commutative diagram produced by xypic. -To use, try to view this object -- it won't work. Now try +To use, try to view this object -- it will not work. Now try 'latex.add_to_preamble("\\usepackage[matrix,arrow,curve,cmtip]{xy}")', and try viewing again. You should get a picture (a part of the diagram arising from a filtered chain complex).""" @@ -2561,4 +2575,5 @@ def _latex_(self): \restore }""" + latex_examples = LatexExamples() diff --git a/src/sage/misc/misc.py b/src/sage/misc/misc.py index 4580f836f18..96ad8ca6fb4 100644 --- a/src/sage/misc/misc.py +++ b/src/sage/misc/misc.py @@ -43,10 +43,11 @@ import resource import pdb import warnings + import sage.misc.prandom as random from .lazy_string import lazy_string -import sage.server.support - +from sage.interfaces.quit import expect_objects +from sage.env import DOT_SAGE, HOSTNAME from sage.misc.lazy_import import lazy_import lazy_import("sage.misc.call", ["AttrCallObject", "attrcall", "call_method"], @@ -59,8 +60,6 @@ lazy_import("sage.misc.repr", ["coeff_repr", "repr_lincomb"], deprecation=29892) -from sage.env import DOT_SAGE, HOSTNAME - LOCAL_IDENTIFIER = '%s.%s' % (HOSTNAME, os.getpid()) ################################################################# @@ -96,7 +95,7 @@ def sage_makedirs(dirname, mode=0o777): raise -# We create the DOT_SAGE directory (if it doesn't exist yet; note in particular +# We create the DOT_SAGE directory (if it does not exist yet; note in particular # that it may already have been created by the bin/sage script) with # restrictive permissions, since otherwise possibly just anybody can easily see # every command you type. @@ -345,7 +344,7 @@ def cputime(t=0, subprocesses=False): else: if t == 0: ret = GlobalCputime(cputime()) - for s in sage.interfaces.quit.expect_objects: + for s in expect_objects: S = s() if S and S.is_running(): try: @@ -359,7 +358,7 @@ def cputime(t=0, subprocesses=False): if not isinstance(t, GlobalCputime): t = GlobalCputime(t) ret = GlobalCputime(cputime() - t.local) - for s in sage.interfaces.quit.expect_objects: + for s in expect_objects: S = s() if S and S.is_running(): try: @@ -519,6 +518,8 @@ def union(x, y=None): EXAMPLES:: sage: answer = union([1,2,3,4], [5,6]); answer + doctest:...: DeprecationWarning: sage.misc.misc.union is deprecated... + See https://trac.sagemath.org/32096 for details. [1, 2, 3, 4, 5, 6] sage: union([1,2,3,4,5,6], [5,6]) == answer True @@ -527,6 +528,8 @@ def union(x, y=None): sage: union((1,2,3,4,5,6), set([5,6])) == answer True """ + from sage.misc.superseded import deprecation + deprecation(32096, "sage.misc.misc.union is deprecated, use 'list(set(x).union(y)' or a more suitable replacement") if y is None: return list(set(x)) return list(set(x).union(y)) @@ -833,7 +836,7 @@ def __mul__(self, right): ################################################################# # is_iterator function ################################################################# -def is_iterator(it): +def is_iterator(it) -> bool: """ Tests if it is an iterator. @@ -847,13 +850,7 @@ def is_iterator(it): sage: is_iterator(it) True - sage: class wrong(): # py2 - ....: def __init__(self): self.n = 5 - ....: def next(self): - ....: self.n -= 1 - ....: if self.n == 0: raise StopIteration - ....: return self.n - sage: class wrong(): # py3 + sage: class wrong(): ....: def __init__(self): self.n = 5 ....: def __next__(self): ....: self.n -= 1 @@ -862,11 +859,7 @@ def is_iterator(it): sage: x = wrong() sage: is_iterator(x) False - sage: list(x) # py2 - Traceback (most recent call last): - ... - TypeError: iteration over non-sequence - sage: list(x) # py3 + sage: list(x) Traceback (most recent call last): ... TypeError: 'wrong' object is not iterable @@ -927,6 +920,7 @@ def random_sublist(X, s): """ return [a for a in X if random.random() <= s] + def is_sublist(X, Y): """ Test whether ``X`` is a sublist of ``Y``. @@ -952,6 +946,7 @@ def is_sublist(X, Y): X_i += 1 return X_i == len(X) + def some_tuples(elements, repeat, bound, max_samples=None): r""" Return an iterator over at most ``bound`` number of ``repeat``-tuples of @@ -1268,19 +1263,6 @@ def pad_zeros(s, size=3): return "0" * (size - len(str(s))) + str(s) -def embedded(): - """ - Return ``True`` if this copy of Sage is running embedded in the Sage - notebook. - - EXAMPLES:: - - sage: sage.misc.misc.embedded() # output True if in the notebook - False - """ - return sage.server.support.EMBEDDED_MODE - - def is_in_string(line, pos): r""" Return ``True`` if the character at position ``pos`` in ``line`` occurs diff --git a/src/sage/misc/pager.py b/src/sage/misc/pager.py index 26e9c33bd74..84833d6135a 100644 --- a/src/sage/misc/pager.py +++ b/src/sage/misc/pager.py @@ -1,7 +1,7 @@ """ Pager for showing strings -Currently we just use the IPython pager when not in embedded mode. +Currently we just use the IPython pager. If we want to use something else, we can just change this function. Any code in sage that uses a pager should use this pager. @@ -15,12 +15,6 @@ # --------------------------------------------------------------------------- -EMBEDDED_MODE = False - - def pager(): - if EMBEDDED_MODE: - return print - else: - import IPython.core.page - return IPython.core.page.page + import IPython.core.page + return IPython.core.page.page diff --git a/src/sage/misc/sagedoc.py b/src/sage/misc/sagedoc.py index 33f28ada452..01e4ec4bcc1 100644 --- a/src/sage/misc/sagedoc.py +++ b/src/sage/misc/sagedoc.py @@ -187,6 +187,7 @@ def _rmcmd(s, cmd, left='', right=''): itempattern = re.compile(r"\\item\[?([^]]*)\]? *(.*)") itemreplace = r"* \1 \2" + def detex(s, embedded=False): r"""nodetex This strips LaTeX commands from a string; it is used by the @@ -570,6 +571,7 @@ def process_mathtt(s): s = s[:start] + s[start+8:end] + s[end+1:] return s + def format(s, embedded=False): r"""noreplace Format Sage documentation ``s`` for viewing with IPython. @@ -698,13 +700,12 @@ def format(s, embedded=False): else: first_line = s # Moreover, we must strip blank space in order to get the directives - directives = [ d.strip().lower() for d in first_line.split(',') ] + directives = [d.strip().lower() for d in first_line.split(',')] if 'noreplace' in directives or 'nodetex' in directives: - s = s[first_newline+len(os.linesep):] + s = s[first_newline + len(os.linesep):] import sage.all - import sage.server.support docs = set([]) if 'noreplace' not in directives: i_0 = 0 @@ -746,6 +747,7 @@ def format(s, embedded=False): s = detex(s, embedded=embedded) return s + def format_src(s): """ Format Sage source code ``s`` for viewing with IPython. @@ -945,22 +947,17 @@ def _search_src_or_doc(what, string, extra1='', extra2='', extra3='', html_results = format_search_as_html(title, results, [string] + extras) - from sage.server.support import EMBEDDED_MODE - if EMBEDDED_MODE: - # Running from the legacy Sage Notebook - print(html_results) - else: - # Pass through the IPython pager in a mime bundle - from IPython.core.page import page - if not isinstance(text_results, str): - text_results = text_results.decode('utf-8', 'replace') + # Pass through the IPython pager in a mime bundle + from IPython.core.page import page + if not isinstance(text_results, str): + text_results = text_results.decode('utf-8', 'replace') - page({ - 'text/plain': text_results, - # 'text/html': html_results # don't return HTML results since - # they currently are not correctly - # formatted for Jupyter use - }) + page({ + 'text/plain': text_results, + # 'text/html': html_results # don't return HTML results since + # they currently are not correctly + # formatted for Jupyter use + }) def search_src(string, extra1='', extra2='', extra3='', extra4='', @@ -1422,8 +1419,8 @@ def __call__(self, obj, output='html', view=True): """ if output != 'html' and view: view = False - # much of the following is taken from 'docstring' in server/support.py - s = '' + + s = '' newline = "\n\n" # blank line to start new paragraph try: @@ -1445,7 +1442,7 @@ def __call__(self, obj, output='html', view=True): s += newline s += '**Docstring:**' s += newline - s += sageinspect.sage_getdoc(obj, obj_name, embedded_override=True) + s += sageinspect.sage_getdoc(obj, obj_name, embedded=True) # now s should be the reST version of the docstring if output == 'html': @@ -1551,11 +1548,7 @@ def _open(self, name, testing=False): if testing: return (url, path) - from sage.server.support import EMBEDDED_MODE - if EMBEDDED_MODE: - os.system(browser() + " " + url) - else: - os.system(browser() + " " + path) + os.system(browser() + " " + path) def tutorial(self): """ diff --git a/src/sage/misc/sageinspect.py b/src/sage/misc/sageinspect.py index 35125721f27..f4729d30bbf 100644 --- a/src/sage/misc/sageinspect.py +++ b/src/sage/misc/sageinspect.py @@ -119,7 +119,6 @@ def foo(unsigned int x=1, a=')"', b={not (2+1==3):'bar'}, *args, **kwds): return import os import tokenize import re -EMBEDDED_MODE = False from sage.env import SAGE_LIB try: @@ -189,6 +188,7 @@ def isclassinstance(obj): # Parse Python identifiers __identifier_re = re.compile(r"^[^\d\W]\w*") + def _extract_embedded_position(docstring): r""" If docstring has a Cython embedded position, return a tuple @@ -264,6 +264,7 @@ def _extract_embedded_position(docstring): original = res.group('ORIGINAL') return (original, filename, lineno) + def _extract_embedded_signature(docstring, name): r""" If docstring starts with the embedded of a method called ``name``, return @@ -301,14 +302,15 @@ def _extract_embedded_signature(docstring, name): return docstring, None signature = firstline.split(name, 1)[-1] if signature.startswith("(") and signature.endswith(")"): - docstring = L[1] if len(L)>1 else '' # Remove first line, keep the rest - def_string = "def "+name+signature+": pass" + docstring = L[1] if len(L) > 1 else '' # Remove first line, keep the rest + def_string = "def " + name + signature + ": pass" try: return docstring, inspect.ArgSpec(*_sage_getargspec_cython(def_string)) except SyntaxError: docstring = os.linesep.join(L) return docstring, None + class BlockFinder: """ Provide a tokeneater() method to detect the end of a code block. @@ -366,7 +368,9 @@ def _getblock(lines): blockfinder = BlockFinder() iter_lines = iter(lines) tokenizer = tokenize.tokenize - readline = lambda: next(iter_lines).encode('utf-8') + + def readline(): + return next(iter_lines).encode('utf-8') try: for tok in tokenizer(readline): blockfinder.tokeneater(*tok) @@ -398,7 +402,7 @@ def _extract_source(lines, lineno): lineno -= 1 if isinstance(lines, str): - lines = lines.splitlines(True) # true keeps the '\n' + lines = lines.splitlines(True) # true keeps the '\n' if len(lines): # Fixes an issue with getblock lines[-1] += '\n' @@ -712,27 +716,27 @@ def visit_Compare(self, node): """ left = self.visit(node.left) ops = list(node.ops) - comparators = list(node.comparators) # the things to be compared with. + comparators = list(node.comparators) # the things to be compared with. while ops: op = ops.pop(0).__class__.__name__ right = self.visit(comparators.pop(0)) - if op=='Lt': - if not leftright: + elif op == 'Gt': + if not left > right: return False - elif op=='GtE': - if not left>=right: + elif op == 'GtE': + if not left >= right: return False - elif op=='Eq': - if not left==right: + elif op == 'Eq': + if not left == right: return False - elif op=='NotEq': - if not left!=right: + elif op == 'NotEq': + if not left != right: return False left = right return True @@ -842,6 +846,7 @@ def visit_UnaryOp(self, node): if op == 'USub': return -self.visit(node.operand) + def _grep_first_pair_of_parentheses(s): """ Return the first matching pair of parentheses in a code string. @@ -871,7 +876,6 @@ def _grep_first_pair_of_parentheses(s): Traceback (most recent call last): ... SyntaxError: The given string does not contain balanced parentheses - """ out = [] single_quote = False @@ -879,17 +883,17 @@ def _grep_first_pair_of_parentheses(s): escaped = False level = 0 for c in s: - if level>0: + if level > 0: out.append(c) - if c=='(' and not single_quote and not double_quote and not escaped: + if c == '(' and not single_quote and not double_quote and not escaped: level += 1 - elif c=='"' and not single_quote and not escaped: + elif c == '"' and not single_quote and not escaped: double_quote = not double_quote - elif c=="'" and not double_quote and not escaped: + elif c == "'" and not double_quote and not escaped: single_quote = not single_quote - elif c==')' and not single_quote and not double_quote and not escaped: + elif c == ')' and not single_quote and not double_quote and not escaped: if level == 1: - return '('+''.join(out) + return '(' + ''.join(out) level -= 1 elif c=="\\" and (single_quote or double_quote): escaped = not escaped @@ -897,6 +901,7 @@ def _grep_first_pair_of_parentheses(s): escaped = False raise SyntaxError("The given string does not contain balanced parentheses") + def _split_syntactical_unit(s): """ Split off a sub-expression from the start of a given string. @@ -959,6 +964,7 @@ def _split_syntactical_unit(s): s = s.strip() if not s: return s + # Split a given string at the next unescaped quotation mark def split_string(s, quot): escaped = False @@ -973,13 +979,13 @@ def split_string(s, quot): raise SyntaxError("EOF while scanning string literal") # 1. s is a triple-quoted string if s.startswith('"""'): - a,b = split_string(s[3:], '"""') + a, b = split_string(s[3:], '"""') return '"""'+a+'"""', b.strip() if s.startswith('r"""'): - a,b = split_string(s[4:], '"""') + a, b = split_string(s[4:], '"""') return 'r"""'+a+'"""', b.strip() if s.startswith("'''"): - a,b = split_string(s[3:], "'''") + a, b = split_string(s[3:], "'''") return "'''"+a+"'''", b.strip() if s.startswith("r'''"): a,b = split_string(s[4:], "'''") @@ -1037,6 +1043,7 @@ def split_string(s, quot): return ''.join(out), s[1:].strip() raise SyntaxError("Syntactical group starting with %s did not end with %s"%(repr(start),repr(stop))) + def _sage_getargspec_from_ast(source): r""" Return an argspec for a Python function or method by compiling its @@ -1803,33 +1810,11 @@ def sage_getdef(obj, obj_name=''): # sometimes s contains "*args" or "**keywds", and the # asterisks confuse ReST/sphinx/docutils, so escape them: # change * to \*, and change ** to \**. - if EMBEDDED_MODE: - s = s.replace('**', '\\**') # replace ** with \** - t = '' - while True: # replace * with \* - i = s.find('*') - if i == -1: - break - elif i > 0 and s[i-1] == '\\': - if s[i+1] == "*": - t += s[:i+2] - s = s[i+2:] - else: - t += s[:i+1] - s = s[i+1:] - continue - elif i > 0 and s[i-1] == '*': - t += s[:i+1] - s = s[i+1:] - continue - else: - t += s[:i] + '\\*' - s = s[i+1:] - s = t + s return obj_name + '(' + s + ')' except (AttributeError, TypeError, ValueError): return '%s( [noargspec] )'%obj_name + def _sage_getdoc_unformatted(obj): r""" Return the unformatted docstring associated to ``obj`` as a @@ -1989,17 +1974,15 @@ def sage_getdoc_original(obj): return s -def sage_getdoc(obj, obj_name='', embedded_override=False): +def sage_getdoc(obj, obj_name='', embedded=False): r""" Return the docstring associated to ``obj`` as a string. If ``obj`` is a Cython object with an embedded position in its docstring, the embedded position is stripped. - If optional argument ``embedded_override`` is False (its default - value), then the string is formatted according to the value of - EMBEDDED_MODE. If this argument is True, then it is formatted as - if EMBEDDED_MODE were True. + The optional boolean argument ``embedded`` controls the + string formatting. It is False by default. INPUT: @@ -2030,17 +2013,18 @@ def sage_getdoc(obj, obj_name='', embedded_override=False): if obj is None: return '' r = sage_getdoc_original(obj) - s = sage.misc.sagedoc.format(r, embedded=(embedded_override or EMBEDDED_MODE)) + s = sage.misc.sagedoc.format(r, embedded=embedded) # Fix object naming if obj_name != '': i = obj_name.find('.') if i != -1: obj_name = obj_name[:i] - s = s.replace('self.','%s.'%obj_name) + s = s.replace('self.', '%s.' % obj_name) return s + def sage_getsource(obj): r""" Return the source code associated to obj as a string, or None. @@ -2441,6 +2425,7 @@ class Element(object): break return _extract_source(source_lines, lineno), lineno + def sage_getvariablename(self, omit_underscore_names=True): """ Attempt to get the name of a Sage object. @@ -2488,6 +2473,7 @@ def sage_getvariablename(self, omit_underscore_names=True): else: return sorted(result) + __internal_teststring = ''' import os # 1 # preceding comment not include # 2 @@ -2504,6 +2490,7 @@ def test3(b, # 12 a=2): # 13 pass # EOF # 14''' + def __internal_tests(): r""" Test internals of the sageinspect module. diff --git a/src/sage/misc/session.pyx b/src/sage/misc/session.pyx index 2036e7f3db5..610dbdc0f2e 100644 --- a/src/sage/misc/session.pyx +++ b/src/sage/misc/session.pyx @@ -67,7 +67,6 @@ import types cdef caller_locals = builtins.locals # Sage imports -from .misc import embedded from sage.misc.persist import load, save, loads, dumps # This module-scope variables is used to save the @@ -326,17 +325,6 @@ def save_session(name='sage_session', verbose=False): print("Not saving {}: {}".format(k, msg)) pass save(D, name) - if embedded(): - # Also save D to the data directory if we're using the notebook. - # This is broken for now. Simply print some information to the user - # if the user does not save it in the DATA directory. - # save(D, '../../data/' + name) - if name.find('.sagenb/') <= 0 or name.find('/data/') <= 0: - print("To store the session in a common directory that the " - "entire worksheet can access, save it using the command:\n" - "save_session(DATA + '{0}')\n" - "You can later load it by running in any cell:\n" - "load_session(DATA + '{0}')".format(name.rsplit('/', 1)[-1])) def load_session(name='sage_session', verbose=False): @@ -382,13 +370,6 @@ def load_session(name='sage_session', verbose=False): """ state = caller_locals() - if embedded(): - if not os.path.exists(name): - nm = '../../data/' + name - if not nm.endswith('.sobj'): nm += '.sobj' - if os.path.exists(nm): - name = nm D = load(name) for k, x in D.items(): state[k] = x - diff --git a/src/sage/modular/abvar/abvar.py b/src/sage/modular/abvar/abvar.py index 9483e6fb8d6..9367c4d3d37 100644 --- a/src/sage/modular/abvar/abvar.py +++ b/src/sage/modular/abvar/abvar.py @@ -38,7 +38,7 @@ from sage.structure.sequence import Sequence, Sequence_generic from sage.structure.richcmp import (richcmp_method, richcmp_not_equal, rich_to_bool) -from sage.structure.parent_base import ParentWithBase +from sage.structure.parent import Parent from .morphism import HeckeOperator, Morphism, DegeneracyMap from .torsion_subgroup import RationalTorsionSubgroup, QQbarTorsionSubgroup from .finite_subgroup import (FiniteSubgroup_lattice, FiniteSubgroup, @@ -96,7 +96,7 @@ def is_ModularAbelianVariety(x) -> bool: @richcmp_method -class ModularAbelianVariety_abstract(ParentWithBase): +class ModularAbelianVariety_abstract(Parent): def __init__(self, groups, base_field, is_simple=None, newform_level=None, isogeny_number=None, number=None, check=True): """ @@ -165,7 +165,8 @@ def __init__(self, groups, base_field, is_simple=None, newform_level=None, self.__isogeny_number = isogeny_number if check and not is_Ring(base_field) and base_field.is_field(): raise TypeError("base_field must be a field") - ParentWithBase.__init__(self, base_field, category=ModularAbelianVarieties(base_field)) + Parent.__init__(self, base=base_field, + category=ModularAbelianVarieties(base_field)) def groups(self): r""" diff --git a/src/sage/modular/arithgroup/arithgroup_perm.py b/src/sage/modular/arithgroup/arithgroup_perm.py index ee60b091032..5ea925b2a0d 100644 --- a/src/sage/modular/arithgroup/arithgroup_perm.py +++ b/src/sage/modular/arithgroup/arithgroup_perm.py @@ -2049,8 +2049,14 @@ def _spanning_tree_kulkarni(self, root=0, on_right=True): s3,s3 s3,s3,s2 - sage: gens - [(0, 1, 's2'), (3, 3, 's3')] + sage: edges = G.coset_graph().edges(); edges + [(0, 1, 's2'), (0, 1, 's3'), (1, 0, 's2'), (1, 2, 's3'), (2, 0, 's3'), (2, 3, 's2'), (3, 2, 's2'), (3, 3, 's3')] + sage: len(gens) + 2 + sage: (3, 3, 's3') in gens + True + sage: any(e in gens for e in edges[:5]) + True """ from sage.graphs.digraph import DiGraph from sage.misc.prandom import randint @@ -2178,8 +2184,14 @@ def _spanning_tree_verrill(self, root=0, on_right=True): **** sage: wreps ['', 's', 'll', 'l'] - sage: gens - [(2, 0, 'l'), (1, 1, 'l'), (2, 3, 's')] + sage: len(gens) + 3 + sage: (1, 1, 'l') in gens + True + sage: (2, 3, 's') in gens or (3, 2, 's') in gens + True + sage: (2, 0, 'l') in gens + True .. TODO:: @@ -2279,12 +2291,10 @@ def todd_coxeter_s2_s3(self): sage: g1,g2 = gens sage: g1 in G and g2 in G True - sage: g1 - [-1 0] - [ 1 -1] - sage: g2 - [-1 3] - [-1 2] + sage: Matrix(2, 2, [-1, 3, -1, 2]) in gens + True + sage: Matrix(2, 2, [-1, 0, 1, -1]) in gens or Matrix(2, 2, [1, 0, 1, 1]) in gens + True sage: S2 = SL2Z([0,-1,1,0]) sage: S3 = SL2Z([0,1,-1,1]) sage: reps[0] == SL2Z([1,0,0,1]) @@ -2323,11 +2333,14 @@ def todd_coxeter_l_s2(self): [1 0] [ 0 -1] [1 2] [1 1] [0 1], [ 1 0], [0 1], [0 1] ] - sage: gens - [ - [1 3] [ 1 0] [ 2 -3] - [0 1], [-1 1], [ 1 -1] - ] + sage: len(gens) + 3 + sage: Matrix(2, 2, [1, 3, 0, 1]) in gens + True + sage: Matrix(2, 2, [1, 0, -1, 1]) in gens + True + sage: Matrix(2, 2, [1, -3, 1, -2]) in gens or Matrix(2, 2, [2, -3, 1, -1]) in gens + True sage: l [3, 1, 0, 2] sage: s diff --git a/src/sage/modular/arithgroup/congroup_sl2z.py b/src/sage/modular/arithgroup/congroup_sl2z.py index 906ad63baa9..57665101690 100644 --- a/src/sage/modular/arithgroup/congroup_sl2z.py +++ b/src/sage/modular/arithgroup/congroup_sl2z.py @@ -194,18 +194,23 @@ def random_element(self, bound=100, *args, **kwds): EXAMPLES:: - sage: SL2Z.random_element() - [60 13] - [83 18] - sage: SL2Z.random_element(5) - [-1 3] - [ 1 -4] + sage: s = SL2Z.random_element() + sage: s.parent() is SL2Z + True + sage: all(a in range(-99, 100) for a in list(s)) + True + sage: S = set() + sage: while len(S) < 180: + ....: s = SL2Z.random_element(5) + ....: assert all(a in range(-4, 5) for a in list(s)) + ....: assert s.parent() is SL2Z + ....: assert s in SL2Z + ....: S.add(s) Passes extra positional or keyword arguments through:: - sage: SL2Z.random_element(5, distribution='1/n') - [ 1 -4] - [ 0 1] + sage: SL2Z.random_element(5, distribution='1/n').parent() is SL2Z + True """ if bound <= 1: raise ValueError("bound must be greater than 1") diff --git a/src/sage/modular/dirichlet.py b/src/sage/modular/dirichlet.py index d101f6ab4b5..96e70a3ed00 100644 --- a/src/sage/modular/dirichlet.py +++ b/src/sage/modular/dirichlet.py @@ -3143,12 +3143,35 @@ def random_element(self): EXAMPLES:: - sage: DirichletGroup(37).random_element() - Dirichlet character modulo 37 of conductor 37 mapping 2 |--> zeta36^4 - sage: DirichletGroup(20).random_element() - Dirichlet character modulo 20 of conductor 4 mapping 11 |--> -1, 17 |--> 1 - sage: DirichletGroup(60).random_element() - Dirichlet character modulo 60 of conductor 3 mapping 31 |--> 1, 41 |--> -1, 37 |--> 1 + sage: D = DirichletGroup(37) + sage: g = D.random_element() + sage: g.parent() is D + True + sage: g**36 + Dirichlet character modulo 37 of conductor 1 mapping 2 |--> 1 + sage: S = set(D.random_element().conductor() for _ in range(100)) + sage: while S != {1, 37}: + ....: S.add(D.random_element().conductor()) + + sage: D = DirichletGroup(20) + sage: g = D.random_element() + sage: g.parent() is D + True + sage: g**4 + Dirichlet character modulo 20 of conductor 1 mapping 11 |--> 1, 17 |--> 1 + sage: S = set(D.random_element().conductor() for _ in range(100)) + sage: while S != {1, 4, 5, 20}: + ....: S.add(D.random_element().conductor()) + + sage: D = DirichletGroup(60) + sage: g = D.random_element() + sage: g.parent() is D + True + sage: g**4 + Dirichlet character modulo 60 of conductor 1 mapping 31 |--> 1, 41 |--> 1, 37 |--> 1 + sage: S = set(D.random_element().conductor() for _ in range(100)) + sage: while S != {1, 3, 4, 5, 12, 15, 20, 60}: + ....: S.add(D.random_element().conductor()) """ e = self(1) for i in range(self.ngens()): diff --git a/src/sage/modular/etaproducts.py b/src/sage/modular/etaproducts.py index 4a90b2cecfe..a033ef98311 100644 --- a/src/sage/modular/etaproducts.py +++ b/src/sage/modular/etaproducts.py @@ -30,12 +30,11 @@ # Distributed under the terms of the GNU General Public License (GPL) # https://www.gnu.org/licenses/ # *************************************************************************** - +from __future__ import annotations from sage.arith.misc import divisors, prime_divisors, euler_phi, is_square, gcd from sage.categories.groups import Groups from sage.matrix.constructor import matrix -from sage.misc.misc import union from sage.modules.free_module import FreeModule from sage.rings.finite_rings.integer_mod import Mod from sage.rings.finite_rings.integer_mod_ring import IntegerModRing @@ -149,7 +148,7 @@ def _mul_(self, other): Eta product of level 4 : (eta_1)^24 (eta_2)^-48 (eta_4)^24 """ newdict = {d: self._rdict.get(d, 0) + other._rdict.get(d, 0) - for d in union(self._rdict, other._rdict)} + for d in set(self._rdict).union(other._rdict)} P = self.parent() return P.element_class(P, newdict) @@ -166,7 +165,7 @@ def _div_(self, other): True """ newdict = {d: self._rdict.get(d, 0) - other._rdict.get(d, 0) - for d in union(self._rdict, other._rdict)} + for d in set(self._rdict).union(other._rdict)} P = self.parent() return P.element_class(P, newdict) @@ -453,7 +452,7 @@ def _repr_(self) -> str: """ return "Group of eta products on X_0(%s)" % self.level() - def one(self) -> 'EtaGroupElement': + def one(self) -> EtaGroupElement: r""" Return the identity element of ``self``. @@ -618,7 +617,7 @@ def reduce_basis(self, long_etas): Element = EtaGroupElement -def EtaProduct(level, dic) -> 'EtaGroupElement': +def EtaProduct(level, dic) -> EtaGroupElement: r""" Create an :class:`EtaGroupElement` object representing the function `\prod_{d | N} \eta(q^d)^{r_d}`. diff --git a/src/sage/modular/hecke/algebra.py b/src/sage/modular/hecke/algebra.py index 7966e76db12..ac0d0f3b60a 100644 --- a/src/sage/modular/hecke/algebra.py +++ b/src/sage/modular/hecke/algebra.py @@ -410,8 +410,12 @@ def basis(self): [0 2]) sage: M = ModularSymbols(Gamma0(22), sign=1) - sage: [B[0,0] for B in M.hecke_algebra().basis()] - [-955, -994, -706, -490, -1070] + sage: H = M.hecke_algebra() + sage: B = H.basis() + sage: len(B) + 5 + sage: all(b in H for b in B) + True sage: [B[0, 0] for B in M.anemic_hecke_algebra().basis()] Traceback (most recent call last): ... diff --git a/src/sage/modular/local_comp/local_comp.py b/src/sage/modular/local_comp/local_comp.py index a00268ee911..1143e41b138 100644 --- a/src/sage/modular/local_comp/local_comp.py +++ b/src/sage/modular/local_comp/local_comp.py @@ -74,9 +74,9 @@ def LocalComponent(f, p, twist_factor=None): Character of Q_7*, of level 0, mapping 7 |--> 1 sage: Pi.species() 'Supercuspidal' - sage: set(Pi.characters()) - {Character of unramified extension Q_7(s)* (s^2 + 6*s + 3 = 0), of level 1, mapping s |--> -d, 7 |--> 1, - Character of unramified extension Q_7(s)* (s^2 + 6*s + 3 = 0), of level 1, mapping s |--> d, 7 |--> 1} + sage: sorted(Pi.characters(), key=str) + [Character of unramified extension Q_7(s)* (s^2 + 6*s + 3 = 0), of level 1, mapping s |--> -d, 7 |--> 1, + Character of unramified extension Q_7(s)* (s^2 + 6*s + 3 = 0), of level 1, mapping s |--> d, 7 |--> 1] """ p = ZZ(p) if not p.is_prime(): @@ -634,9 +634,9 @@ def characters(self): sage: f = Newform('50a') sage: Pi = LocalComponent(f, 5) - sage: chars = Pi.characters(); set(chars) - {Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> -d - 1, 5 |--> 1, - Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> d, 5 |--> 1} + sage: chars = Pi.characters(); sorted(chars, key=str) + [Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> -d - 1, 5 |--> 1, + Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> d, 5 |--> 1] sage: chars[0].base_ring() Number Field in d with defining polynomial x^2 + x + 1 @@ -650,9 +650,9 @@ def characters(self): sage: f = Newforms(GammaH(25, [6]), 3, names='j')[0]; f q + j0*q^2 + 1/3*j0^3*q^3 - 1/3*j0^2*q^4 + O(q^6) sage: Pi = LocalComponent(f, 5) - sage: set(Pi.characters()) - {Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> 1/3*j0^2*d - 1/3*j0^3, 5 |--> 5, - Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> -1/3*j0^2*d, 5 |--> 5} + sage: sorted(Pi.characters(), key=str) + [Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> -1/3*j0^2*d, 5 |--> 5, + Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> 1/3*j0^2*d - 1/3*j0^3, 5 |--> 5] sage: Pi.characters()[0].base_ring() Number Field in d with defining polynomial x^2 - j0*x + 1/3*j0^2 over its base field @@ -667,9 +667,9 @@ def characters(self): sage: f = Newform('81a', names='j'); f q + j0*q^2 + q^4 - j0*q^5 + O(q^6) - sage: set(LocalComponent(f, 3).characters()) # long time (12s on sage.math, 2012) - {Character of unramified extension Q_3(s)* (s^2 + 2*s + 2 = 0), of level 2, mapping -2*s |--> -2*d + j0, 4 |--> 1, 3*s + 1 |--> -j0*d + 1, 3 |--> 1, - Character of unramified extension Q_3(s)* (s^2 + 2*s + 2 = 0), of level 2, mapping -2*s |--> 2*d - j0, 4 |--> 1, 3*s + 1 |--> j0*d - 2, 3 |--> 1} + sage: sorted(LocalComponent(f, 3).characters(), key=str) # long time (12s on sage.math, 2012) + [Character of unramified extension Q_3(s)* (s^2 + 2*s + 2 = 0), of level 2, mapping -2*s |--> -2*d + j0, 4 |--> 1, 3*s + 1 |--> -j0*d + 1, 3 |--> 1, + Character of unramified extension Q_3(s)* (s^2 + 2*s + 2 = 0), of level 2, mapping -2*s |--> 2*d - j0, 4 |--> 1, 3*s + 1 |--> j0*d - 2, 3 |--> 1] Some ramified examples:: @@ -699,9 +699,9 @@ def characters(self): Character of unramified extension Q_2(s)* (s^2 + s + 1 = 0), of level 3, mapping s |--> 1, 2*s + 1 |--> 1/2*a0, 4*s + 1 |--> 1, -1 |--> 1, 2 |--> 1, Character of unramified extension Q_2(s)* (s^2 + s + 1 = 0), of level 3, mapping s |--> 1, 2*s + 1 |--> 1/2*a0, 4*s + 1 |--> -1, -1 |--> 1, 2 |--> 1 ] - sage: set(Newform('243a',names='a').local_component(3).characters()) # long time - {Character of ramified extension Q_3(s)* (s^2 - 6 = 0), of level 4, mapping -2*s - 1 |--> -d - 1, 4 |--> 1, 3*s + 1 |--> -d - 1, s |--> 1, - Character of ramified extension Q_3(s)* (s^2 - 6 = 0), of level 4, mapping -2*s - 1 |--> d, 4 |--> 1, 3*s + 1 |--> d, s |--> 1} + sage: sorted(Newform('243a',names='a').local_component(3).characters(), key=str) # long time + [Character of ramified extension Q_3(s)* (s^2 - 6 = 0), of level 4, mapping -2*s - 1 |--> -d - 1, 4 |--> 1, 3*s + 1 |--> -d - 1, s |--> 1, + Character of ramified extension Q_3(s)* (s^2 - 6 = 0), of level 4, mapping -2*s - 1 |--> d, 4 |--> 1, 3*s + 1 |--> d, s |--> 1] """ T = self.type_space() p = self.prime() @@ -779,7 +779,7 @@ def characters(self): flag = G._reduce_Qp(1, x) except ValueError: flag = None - if flag is not None: + if flag is not None: verbose("skipping x=%s as congruent to %s mod p" % (x, flag)) continue @@ -1021,8 +1021,8 @@ def _repr_(self): def characters(self): r""" Return the pair of characters (either of `\QQ_p^*` or of some quadratic - extension) corresponding to this representation. - + extension) corresponding to this representation. + EXAMPLES:: sage: f = [f for f in Newforms(63, 4, names='a') if f[2] == 1][0] diff --git a/src/sage/modular/local_comp/smoothchar.py b/src/sage/modular/local_comp/smoothchar.py index 92135f7f71d..7e2a776e700 100644 --- a/src/sage/modular/local_comp/smoothchar.py +++ b/src/sage/modular/local_comp/smoothchar.py @@ -42,7 +42,7 @@ """ import operator from sage.structure.element import MultiplicativeGroupElement, parent -from sage.structure.parent_base import ParentWithBase +from sage.structure.parent import Parent from sage.structure.sequence import Sequence from sage.structure.richcmp import richcmp_not_equal, richcmp from sage.rings.all import QQ, ZZ, Zmod, NumberField @@ -54,7 +54,7 @@ from sage.categories.rings import Rings from sage.misc.mrange import xmrange from sage.misc.verbose import verbose -from sage.modular.dirichlet import DirichletGroup +from sage.modular.dirichlet import DirichletGroup class SmoothCharacterGeneric(MultiplicativeGroupElement): @@ -337,7 +337,7 @@ def galois_conjugate(self): return self.parent().character(self.level(), [self(sig(x)) for x in self.parent().unit_gens(self.level())]) -class SmoothCharacterGroupGeneric(ParentWithBase): +class SmoothCharacterGroupGeneric(Parent): r""" The group of smooth (i.e. locally constant) characters of a `p`-adic field, with values in some ring `R`. This is an abstract base class and should not @@ -359,10 +359,10 @@ def __init__(self, p, base_ring): """ if base_ring not in Rings(): raise TypeError("base ring (=%s) must be a ring" % base_ring) - ParentWithBase.__init__(self, base=base_ring, - category=Groups().Commutative()) + Parent.__init__(self, base=base_ring, + category=Groups().Commutative()) if not (p in ZZ and ZZ(p).is_prime()): - raise ValueError( "p (=%s) must be a prime integer" % p ) + raise ValueError("p (=%s) must be a prime integer" % p) self._p = ZZ.coerce(p) def _element_constructor_(self, x): diff --git a/src/sage/modular/local_comp/type_space.py b/src/sage/modular/local_comp/type_space.py index ce78d301979..c47b388946b 100644 --- a/src/sage/modular/local_comp/type_space.py +++ b/src/sage/modular/local_comp/type_space.py @@ -370,8 +370,13 @@ def minimal_twist(self): sage: f = Newforms(256,names='a')[0] sage: T = TypeSpace(f,2) - sage: g = T.minimal_twist(); g - q - a*q^3 + O(q^6) + sage: g = T.minimal_twist() + sage: g[0:3] + [0, 1, 0] + sage: str(g[3]) in ('a', '-a', '-1/2*a', '1/2*a') + True + sage: g[4:] + [] sage: g.level() 64 """ diff --git a/src/sage/modular/modform/numerical.py b/src/sage/modular/modform/numerical.py index 933b334d3ea..0d467ef2c0b 100644 --- a/src/sage/modular/modform/numerical.py +++ b/src/sage/modular/modform/numerical.py @@ -211,9 +211,10 @@ def _eigenvectors(self): sage: n = numerical_eigenforms(61, eps=2.0) sage: evectors = n._eigenvectors() - sage: evalues = diagonal_matrix(CDF, [-283.0, 142.0, 108.522012456]) - sage: diff = n._hecke_matrix*evectors - evectors*evalues - sage: sum([abs(diff[i,j]) for i in range(5) for j in range(3)]) < 1.0e-9 + sage: evalues = [(matrix((n._hecke_matrix*evectors).column(i))/matrix(evectors.column(i)))[0, 0] + ....: for i in range(evectors.ncols())] + sage: diff = n._hecke_matrix*evectors - evectors*diagonal_matrix(evalues) + sage: sum(abs(a) for a in diff.list()) < 1.0e-9 True """ verbose('Finding eigenvector basis') @@ -443,7 +444,7 @@ def systems_of_eigenvalues(self, bound): EXAMPLES:: - sage: numerical_eigenforms(61).systems_of_eigenvalues(10) # rel tol 6e-14 + sage: numerical_eigenforms(61).systems_of_eigenvalues(10) # rel tol 1e-12 [ [-1.4811943040920152, 0.8060634335253695, 3.1563251746586642, 0.6751308705666477], [-1.0, -2.0000000000000027, -3.000000000000003, 1.0000000000000044], @@ -470,7 +471,7 @@ def systems_of_abs(self, bound): EXAMPLES:: - sage: numerical_eigenforms(61).systems_of_abs(10) # rel tol 6e-14 + sage: numerical_eigenforms(61).systems_of_abs(10) # rel tol 1e-12 [ [0.3111078174659775, 2.903211925911551, 2.525427560843529, 3.214319743377552], [1.0, 2.0000000000000027, 3.000000000000003, 1.0000000000000044], diff --git a/src/sage/modular/modform/space.py b/src/sage/modular/modform/space.py index 1e1d29ac8ea..b96076cb050 100644 --- a/src/sage/modular/modform/space.py +++ b/src/sage/modular/modform/space.py @@ -1059,6 +1059,16 @@ def _element_constructor_(self, x, check=True): sage: g.is_old() True + Test that :trac:`32168` is fixed:: + + sage: M0 = ModularForms(Gamma0(8), 10) + sage: M1 = ModularForms(Gamma1(8), 10) + sage: f = M0.0; g = M1.0 + sage: f + g + 2*q + O(q^6) + sage: M1(f) + q + O(q^6) + :: sage: M = ModularForms(22,2) ; S = CuspForms(22,2) @@ -1083,9 +1093,7 @@ def _element_constructor_(self, x, check=True): sage: M(M([1, 2, 3, 4, 5]), check=True) 4 + 6*q + 47*q^2 + 143*q^3 + 358*q^4 + 630*q^5 + O(q^6) """ - if isinstance(x, self.element_class): - if x.parent() is self: - return x + if isinstance(x, ModularFormElement): if not check: from copy import copy @@ -1119,9 +1127,6 @@ def _element_constructor_(self, x, check=True): else: raise TypeError("q-expansion needed to at least precision %s" % W.degree()) - if isinstance(x, ModularFormElement): - x = x.element() - return self.element_class(self, self.free_module()(x, check)) def __richcmp__(self, x, op): diff --git a/src/sage/modular/modsym/ambient.py b/src/sage/modular/modsym/ambient.py index 3bb80562667..d49fef546ef 100644 --- a/src/sage/modular/modsym/ambient.py +++ b/src/sage/modular/modsym/ambient.py @@ -72,6 +72,7 @@ class ``ModularSymbolsAmbient``, derived from # https://www.gnu.org/licenses/ ################################################################################ # Sage packages +from sage.misc.cachefunc import cached_method import sage.misc.latex as latex from sage.misc.verbose import verbose @@ -2414,6 +2415,125 @@ class of cuspidal newforms in this ambient space. lift=False, nz=nz))) return ans + def __pari__(self): + """ + Return a PARI object corresponding to ``self``. + + TESTS:: + + sage: ModularSymbols(Gamma1(5), 2).__pari__() + Traceback (most recent call last): + ... + NotImplementedError: PARI modular symbols are only implemented for Gamma0(n) + """ + raise NotImplementedError('PARI modular symbols are only implemented for Gamma0(n)') + + def _pari_pairing(self): + r""" + Return the matrix of the canonical pairing between this space and + the corresponding space of modular symbols in PARI. + + Let `M` be an ambient space of modular symbols over a field `K` + of characteristic 0. The corresponding space `P` in PARI is + canonically dual to `M`, giving rise to a `K`-bilinear map + + .. MATH:: + + E\colon M \times P \to K. + + OUTPUT: The matrix of the bilinear map `E`. + + This is currently only implemented for spaces of modular + symbols of trivial character. + + EXAMPLES:: + + sage: M = ModularSymbols(Gamma0(5), 6) + sage: P = M.__pari__() + sage: E = M._pari_pairing(); E + [ 0 -1 0 0] + [ 0 0 8 -27] + [ 8 0 0 13] + [ 24 0 8 37] + + The duality is compatible with (for example) Hecke operators + and the star involution:: + + sage: M.hecke_matrix(5) * E == E * P.mshecke(5) + True + sage: M.star_involution().matrix() * E == E * P.msstar() + True + """ + if self.weight() == 2: + return self._pari_tensor().inverse() + from sage.matrix.constructor import matrix + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + P = self.__pari__() + I = matrix.identity(self.rank()).__pari__() + m = Integer(self.weight() - 2) + R = PolynomialRing(QQ, 'x') + x = R.gen() + def ev(s): + # The Manin symbol s = X^i (c, d) corresponds to the + # modular symbol (dX - bY)^i (-cX + aY)^(m - i) {b/d, a/c}. + # The code below computes the canonical pairing of this + # modular symbol with the distinguished basis of P. + i = s.i + a, b, c, d = s.lift_to_sl2z() + e = [R(p) for p in P.mseval(I, matrix(2, 2, [b, a, d, c]))] + g = (d*x - b)**i * (-c*x + a)**(m - i) + return [sum(f[j] * g[m - j] / m.binomial(j) for j in range(m + 1)) + for f in e] + return matrix([ev(s) for s in self.manin_symbols_basis()]) + + def _pari_tensor(self): + r""" + Return a matrix expressing the duality between this space and the + corresponding space of modular symbols in PARI. + + Let `M` be an ambient space of modular symbols over a field `K` + of characteristic 0. The corresponding space `P` in PARI is + canonically dual to `M`, giving rise to an element + + .. MATH:: + + T \in P \otimes_K M. + + OUTPUT: The matrix of the element `T \in P \otimes_K M`. + This is the inverse of the matrix returned by + :meth:`_pari_pairing`. + + This is currently only implemented for spaces of modular + symbols of trivial character. + + EXAMPLES:: + + sage: M = ModularSymbols(Gamma0(37), 2) + sage: P = M.__pari__() + sage: T = M._pari_tensor(); T + [ 1 0 0 0 0] + [ 0 0 -1 0 0] + [ 0 0 1 -1 0] + [ 0 -1 0 -1 1] + [ 0 0 0 1 -1] + + The duality is compatible with (for example) Hecke operators + and the star involution:: + + sage: T * M.hecke_matrix(3) == P.mshecke(3) * T + True + sage: T * M.star_involution().matrix() == P.msstar() * T + True + """ + if self.weight() != 2: + return self._pari_pairing().inverse() + from sage.matrix.constructor import matrix + gens = self.__pari__().mspathgens()[0][:self.rank()].sage() + # gens is a basis for the space of modular symbols of weight 2 + # (in the sense of Sage), and the distinguished basis of the + # corresponding PARI space of modular symbols is dual to this. + return matrix([self.modular_symbol(g).element() for g in gens]) + class ModularSymbolsAmbient_wtk_g0(ModularSymbolsAmbient): r""" @@ -2723,6 +2843,34 @@ def _hecke_images(self, i, v): return heilbronn.hecke_images_gamma0_weight_k(c.u,c.v, c.i, N, self.weight(), v, self.manin_gens_to_basis()) + @cached_method + def __pari__(self): + """ + Return a PARI object corresponding to ``self``. + + EXAMPLES:: + + sage: ModularSymbols(Gamma0(1), 2).__pari__() + [[[[Vecsmall([0, 1])], [0], 1, [Vecsmall([]), Vecsmall([])], + Vecsmall([1]), Vecsmall([]), Vecsmall([])], + 0, 0, 0, Vecsmall([1]), 0, 0, [[1, 1; [0, 1; -1, 0], 1]], + [[1, 1; [0, -1; 1, -1], 1; [-1, 1; -1, 0], 1]], 0, + Vecsmall([0, 0, 1, 1, 2]), [[Vecsmall([1, 0]), Vecsmall([0, 1])]], + 0, 0, 0, [Vecsmall([1, 1]), [Vecsmall([0, 1]), 0], [Vecsmall([1, 0])]]], + [0, [;], [[;], [;], 1, Vecsmall([])]], + [[], Vecsmall([2, 0]), Vecsmall([0, 0]), 0, [[;], [;], 1, Vecsmall([])]]] + + .. NOTE:: + + Spaces of modular symbols as implemented in PARI are + canonically dual to those implemented in Sage. See + :meth:`ModularSymbolsAmbient._pari_pairing` and + :meth:`ModularSymbolsAmbient._pari_tensor` for how to use + this duality. + """ + return self.level().__pari__().msinit(self.weight(), self.sign()) + + class ModularSymbolsAmbient_wt2_g0(ModularSymbolsAmbient_wtk_g0): r""" Modular symbols for `\Gamma_0(N)` of integer weight `2` over the field diff --git a/src/sage/modular/multiple_zeta.py b/src/sage/modular/multiple_zeta.py index d1f1f550247..8b7524a92a5 100644 --- a/src/sage/modular/multiple_zeta.py +++ b/src/sage/modular/multiple_zeta.py @@ -968,7 +968,7 @@ def _element_constructor_(self, x): r""" Convert ``x`` into ``self``. - INPUT + INPUT: - ``x`` -- either a list, tuple, word or a multiple zeta value @@ -1884,7 +1884,7 @@ def _element_constructor_(self, x): r""" Convert ``x`` into ``self``. - INPUT + INPUT: - ``x`` -- either a list, tuple, word or a multiple zeta value @@ -2151,7 +2151,7 @@ def _element_constructor_(self, x): r""" Convert ``x`` into ``self``. - INPUT + INPUT: - ``x`` -- either a list, tuple, word diff --git a/src/sage/modular/overconvergent/hecke_series.py b/src/sage/modular/overconvergent/hecke_series.py index fe6147b0a07..220d34696e6 100644 --- a/src/sage/modular/overconvergent/hecke_series.py +++ b/src/sage/modular/overconvergent/hecke_series.py @@ -266,8 +266,14 @@ def random_solution(B,K): EXAMPLES:: sage: from sage.modular.overconvergent.hecke_series import random_solution - sage: random_solution(5,10) - [1, 1, 1, 1, 0] + sage: s = random_solution(5,10) + sage: sum(s[i]*(i+1) for i in range(5)) + 10 + sage: S = set() + sage: while len(S) != 30: + ....: s = random_solution(5,10) + ....: assert sum(s[i]*(i+1) for i in range(5)) == 10 + ....: S.add(tuple(s)) """ a = [] for i in range(B,1,-1): @@ -703,15 +709,25 @@ def higher_level_UpGj(p, N, klist, m, modformsring, bound, extra_data=False): EXAMPLES:: sage: from sage.modular.overconvergent.hecke_series import higher_level_UpGj - sage: higher_level_UpGj(5,3,[4],2,true,6) - [ - [ 1 0 0 0 0 0] - [ 0 1 0 0 0 0] - [ 0 7 0 0 0 0] - [ 0 5 10 20 0 0] - [ 0 7 20 0 20 0] - [ 0 1 24 0 20 0] - ] + sage: A = Matrix([ + ....: [1, 0, 0, 0, 0, 0], + ....: [0, 1, 0, 0, 0, 0], + ....: [0, 7, 0, 0, 0, 0], + ....: [0, 5, 10, 20, 0, 0], + ....: [0, 7, 20, 0, 20, 0], + ....: [0, 1, 24, 0, 20, 0]]) + sage: B = Matrix([ + ....: [1, 0, 0, 0, 0, 0], + ....: [0, 1, 0, 0, 0, 0], + ....: [0, 7, 0, 0, 0, 0], + ....: [0, 19, 0, 20, 0, 0], + ....: [0, 7, 20, 0, 20, 0], + ....: [0, 1, 24, 0, 20, 0]]) + sage: C = higher_level_UpGj(5,3,[4],2,true,6) + sage: len(C) + 1 + sage: C[0] in (A, B) + True sage: len(higher_level_UpGj(5,3,[4],2,true,6,extra_data=True)) 4 """ diff --git a/src/sage/modular/overconvergent/weightspace.py b/src/sage/modular/overconvergent/weightspace.py index 66297610aa8..08aacc88588 100644 --- a/src/sage/modular/overconvergent/weightspace.py +++ b/src/sage/modular/overconvergent/weightspace.py @@ -17,11 +17,11 @@ sage: W = pAdicWeightSpace(17) sage: W - Space of 17-adic weight-characters defined over '17-adic Field with capped relative precision 20' + Space of 17-adic weight-characters defined over 17-adic Field with capped relative precision 20 sage: R. = QQ[] sage: L = Qp(17).extension(x^2 - 17, names='a'); L.rename('L') sage: W.base_extend(L) - Space of 17-adic weight-characters defined over 'L' + Space of 17-adic weight-characters defined over L We create a simple element of `\mathcal{W}`: the algebraic character, `x \mapsto x^6`:: @@ -61,10 +61,11 @@ # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** +import weakref -from sage.structure.parent_base import ParentWithBase +from sage.structure.parent import Parent from sage.structure.element import Element from sage.structure.richcmp import richcmp from sage.modular.dirichlet import DirichletGroup, trivial_character @@ -73,7 +74,7 @@ from sage.rings.padics.padic_generic_element import pAdicGenericElement from sage.misc.cachefunc import cached_method from sage.rings.padics.precision_error import PrecisionError -import weakref +from sage.categories.sets_cat import Sets _wscache = {} @@ -94,9 +95,9 @@ def WeightSpace_constructor(p, base_ring=None): EXAMPLES:: sage: pAdicWeightSpace(3) # indirect doctest - Space of 3-adic weight-characters defined over '3-adic Field with capped relative precision 20' + Space of 3-adic weight-characters defined over 3-adic Field with capped relative precision 20 sage: pAdicWeightSpace(3, QQ) - Space of 3-adic weight-characters defined over 'Rational Field' + Space of 3-adic weight-characters defined over Rational Field sage: pAdicWeightSpace(10) Traceback (most recent call last): ... @@ -113,7 +114,7 @@ def WeightSpace_constructor(p, base_ring=None): return m -class WeightSpace_class(ParentWithBase): +class WeightSpace_class(Parent): r""" The space of `p`-adic weight-characters `\mathcal{W} = {\rm Hom}(\ZZ_p^\times, \CC_p^\times)`. @@ -137,25 +138,25 @@ def __init__(self, p, base_ring): EXAMPLES:: sage: pAdicWeightSpace(17) - Space of 17-adic weight-characters defined over '17-adic Field with capped relative precision 20' + Space of 17-adic weight-characters defined over 17-adic Field with capped relative precision 20 """ - ParentWithBase.__init__(self, base=base_ring) + Parent.__init__(self, base=base_ring, category=Sets()) p = ZZ(p) if not p.is_prime(): raise ValueError("p must be prime") self._p = p self._param = Qp(p)((p == 2 and 5) or (p + 1)) - def _repr_(self): + def _repr_(self) -> str: r""" String representation of ``self``. EXAMPLES:: sage: pAdicWeightSpace(17)._repr_() - "Space of 17-adic weight-characters defined over '17-adic Field with capped relative precision 20'" + 'Space of 17-adic weight-characters defined over 17-adic Field with capped relative precision 20' """ - return "Space of %s-adic weight-characters defined over '%s'" % (self.prime(), self.base_ring()) + return "Space of %s-adic weight-characters defined over %s" % (self.prime(), self.base_ring()) def __reduce__(self): r""" @@ -168,7 +169,7 @@ def __reduce__(self): """ return (WeightSpace_constructor, (self.prime(), self.base_ring())) - def __call__(self, arg1, arg2 = None, algebraic=True): + def _element_constructor_(self, arg1, arg2=None, algebraic=True): r""" Create an element of this space. @@ -241,7 +242,7 @@ def base_extend(self, R): sage: W = pAdicWeightSpace(3, QQ) sage: W.base_extend(Qp(3)) - Space of 3-adic weight-characters defined over '3-adic Field with capped relative precision 20' + Space of 3-adic weight-characters defined over 3-adic Field with capped relative precision 20 sage: W.base_extend(IntegerModRing(12)) Traceback (most recent call last): ... @@ -252,9 +253,9 @@ def base_extend(self, R): else: raise TypeError("No coercion map from '%s' to '%s' is defined" % (self.base_ring(), R)) - def _coerce_impl(self, x): + def _coerce_map_from_(self, other): r""" - Canonical coercion of x into self. + Canonical coercion of ``other`` into ``self``. TESTS:: @@ -264,11 +265,9 @@ def _coerce_impl(self, x): sage: W2.coerce(w) # indirect doctest 3 """ - if isinstance(x, WeightCharacter) \ - and x.parent().prime() == self.prime() \ - and self.base_ring().has_coerce_map_from(x.base_ring()): - return self._coerce_in_wtchar(x) - raise TypeError + return (isinstance(other, WeightSpace_class) + and other.prime() == self.prime() + and self.base_ring().has_coerce_map_from(other.base_ring())) def _coerce_in_wtchar(self, x): r""" @@ -308,14 +307,14 @@ def __init__(self, parent): sage: pAdicWeightSpace(17)(0) 0 """ - Element.__init__(self, parent) self._p = self.parent().prime() def base_extend(self, R): r""" - Extend scalars to the base ring R (which must have a canonical map from - the current base ring) + Extend scalars to the base ring R. + + The ring R must have a canonical map from the current base ring. EXAMPLES:: @@ -325,7 +324,7 @@ def base_extend(self, R): """ return self.parent().base_extend(R).coerce(self) - def is_even(self): + def is_even(self) -> bool: r""" Return True if this weight-character sends -1 to +1. @@ -380,7 +379,7 @@ def values_on_gens(self): return ( self(self.parent()._param), self.teichmuller_type()) - def is_trivial(self): + def is_trivial(self) -> bool: r""" Return True if and only if this is the trivial character. @@ -395,7 +394,7 @@ def is_trivial(self): """ return self.values_on_gens() == (1, 0) - def _richcmp_(self, other, op): + def _richcmp_(self, other, op) -> bool: r""" Compare ``self`` to ``other``. diff --git a/src/sage/modules/with_basis/invariant.py b/src/sage/modules/with_basis/invariant.py new file mode 100644 index 00000000000..bde1cbd640a --- /dev/null +++ b/src/sage/modules/with_basis/invariant.py @@ -0,0 +1,549 @@ +r""" +Invariant modules +""" + +# **************************************************************************** +# Copyright (C) 2021 Trevor K. Karn +# 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/ +# **************************************************************************** + +import operator +from sage.modules.with_basis.subquotient import SubmoduleWithBasis +from sage.categories.finitely_generated_semigroups import FinitelyGeneratedSemigroups +from sage.categories.finite_dimensional_modules_with_basis import FiniteDimensionalModulesWithBasis +from sage.categories.groups import Groups +from sage.sets.family import Family + +class FiniteDimensionalInvariantModule(SubmoduleWithBasis): + r""" + The invariant submodule under a semigroup action. + + When a semigroup `S` acts on a module `M`, the invariant module is the + set of elements `m \in M` such that `s \cdot m = m` for all `s \in S`: + + .. MATH:: + + M^S := \{m \in M : s \cdot m = m,\, \forall s \in S \}. + + INPUT: + + - ``M`` - a module in the category of + :class:`~sage.categories.finite_dimensional_modules_with_basis.FiniteDimensionalModulesWithBasis` + + - ``S`` - a semigroup in the category of + :class:`~sage.categories.finitely_generated_semigroups.FinitelyGeneratedSemigroups` + + - ``action`` - (default: ``operator.mul``) the action of ``S`` on ``M``. + + - ``side`` - (default: ``'left'``) the side on which ``S`` acts. + + EXAMPLES: + + First, we create the invariant defined by the cyclic group action on the + free module with basis `\{1,2,3\}`:: + + sage: G = CyclicPermutationGroup(3) + sage: M = CombinatorialFreeModule(QQ, [1,2,3], prefix='M') + sage: action = lambda g, m: M.monomial(g(m)) # cyclically permute coordinates + + In order to give the module an action of ``G``, we create a + :class:`~sage.modules.with_basis.representation.Representation`:: + + sage: from sage.modules.with_basis.representation import Representation + sage: R = Representation(G, M, action) + sage: I = R.invariant_module() + + Then we can lift the basis from the invariant to the original module:: + + sage: [I.lift(b) for b in I.basis()] + [M[1] + M[2] + M[3]] + + The we could also have the action be a right-action, instead of the + default left-action:: + + sage: def rt_action(g, m): return M.monomial(g(m)) # cyclically permute coordinates + sage: R = Representation(G, M, rt_action, side='right') # same as last but on right + sage: g = G.an_element(); g + (1,2,3) + sage: r = R.an_element(); r + 2*M[1] + 2*M[2] + 3*M[3] + sage: R.side() + 'right' + + So now we can see that multiplication with ``g`` on the right sends + ``M[1]`` to ``M[2]`` and so on:: + + sage: r * g + 3*M[1] + 2*M[2] + 2*M[3] + sage: I = R.invariant_module() + sage: [I.lift(b) for b in I.basis()] + [M[1] + M[2] + M[3]] + + Now we will take the regular representation of the symmetric group on + three elements to be the module, and compute its invariant submodule:: + + sage: G = SymmetricGroup(3) + sage: R = G.regular_representation(QQ) + sage: I = R.invariant_module() + sage: [I.lift(b).to_vector() for b in I.basis()] + [(1, 1, 1, 1, 1, 1)] + + We can also check the scalar multiplication by elements of the base ring + (for this example, the rational field):: + + sage: [I.lift(3*b).to_vector() for b in I.basis()] + [(3, 3, 3, 3, 3, 3)] + + A more subtle example is the invariant submodule of a skew-commutative + module, for example the exterior algebra `E[x_0,x_1,x_2]` generated + by three elements:: + + sage: G = CyclicPermutationGroup(3) + sage: M = algebras.Exterior(QQ, 'x', 3) + sage: def cyclic_ext_action(g, m): + ....: # cyclically permute generators + ....: return M.prod([M.monomial((g(j+1)-1,)) for j in m]) + + If you care about being able to exploit the algebra structure of the + exterior algebra (i.e. if you want to multiply elements together), you + should make sure the representation knows it is also an algebra with + the semigroup action being by algebra endomorphisms:: + + sage: cat = Algebras(QQ).WithBasis().FiniteDimensional() + sage: R = Representation(G, M, cyclic_ext_action, category=cat) + sage: I = R.invariant_module() + + We can express the basis in the ambient algebra (`E[x_0,x_1,x_2]`):: + + sage: [I.lift(b) for b in I.basis()] + [1, x0 + x1 + x2, x0*x1 - x0*x2 + x1*x2, x0*x1*x2] + + or we can express the basis intrinsicallly to the invariant ``I``:: + + sage: B = I.basis() + sage: m = 3*B[0] + 2*B[1] + 7*B[3] + + This lifts to the exterior algebra:: + + sage: I.lift(m) + 3 + 2*x0 + 7*x0*x1*x2 + 2*x1 + 2*x2 + + We can also check using the invariant element ``m`` that arithmetic works:: + + sage: m^2 + 9*B[0] + 12*B[1] + 42*B[3] + sage: m+m + 6*B[0] + 4*B[1] + 14*B[3] + + To see the actual elements expressed in the exterior algebra, we lift them + again:: + + sage: I.lift(m+m) + 6 + 4*x0 + 14*x0*x1*x2 + 4*x1 + 4*x2 + sage: 7*m + 21*B[0] + 14*B[1] + 49*B[3] + sage: I.lift(7*m) + 21 + 14*x0 + 49*x0*x1*x2 + 14*x1 + 14*x2 + + The classic example of an invariant module is the module of symmetric + functions, which is the invariant module of polynomials whose variables + are acted upon by permutation. We can create a module isomorphic to the + homogeneous component of a a polynomial ring in `n` variable of a fixed + degree `d` by looking at weak compositions of `d` of length `n`, which + we consider as the exponent vector. For example, `x^2yz \in \QQ[x,y,z]` + would have the exponent vector `(2,1,1)`. The vector `(2,1,1)` is a + weak composition of `4`, with length `3`, and so we can think of it as + being in the degree-`4` homogeneous component of a polynomial ring + in three variables:: + + sage: C = IntegerVectors(4, length=3, min_part=0) # representing degree-4 monomials + sage: M = CombinatorialFreeModule(QQ, C) # isomorphic to deg-4 homog. polynomials + sage: G = SymmetricGroup(3) + sage: def perm_action(g,x): return M.monomial(C(g(list(x)))) + sage: perm_action(G((1,2,3)), C([4,3,2])) + B[[3, 2, 4]] + sage: R = Representation(G, M, perm_action) + sage: I = R.invariant_module() + sage: [I.lift(b) for b in I.basis()] + [B[[0, 0, 4]] + B[[0, 4, 0]] + B[[4, 0, 0]], + B[[0, 1, 3]] + B[[0, 3, 1]] + B[[1, 0, 3]] + + B[[1, 3, 0]] + B[[3, 0, 1]] + B[[3, 1, 0]], + B[[0, 2, 2]] + B[[2, 0, 2]] + B[[2, 2, 0]], + B[[1, 1, 2]] + B[[1, 2, 1]] + B[[2, 1, 1]]] + + These are the monomial symmetric functions, which are a well-known + basis for the symmetric functions. For comparison:: + + sage: Sym = SymmetricFunctions(QQ) + sage: m = Sym.monomial() + sage: [m[mu].expand(3) for mu in Partitions(4)] + [x0^4 + x1^4 + x2^4, + x0^3*x1 + x0*x1^3 + x0^3*x2 + x1^3*x2 + x0*x2^3 + x1*x2^3, + x0^2*x1^2 + x0^2*x2^2 + x1^2*x2^2, + x0^2*x1*x2 + x0*x1^2*x2 + x0*x1*x2^2, + 0] + + .. NOTE:: + + The current implementation works when `S` is a finitely-generated + semigroup, and when `M` is a finite-dimensional free module with + a distinguished basis. + + .. TODO:: + + Extend this to have multiple actions, including actions on both sides. + + .. TODO:: + + Extend when `M` does not have a basis and `S` is a permutation + group using: + + - https://arxiv.org/abs/0812.3082 + - https://www.dmtcs.org/pdfpapers/dmAA0123.pdf + """ + def __init__(self, M, S, action=operator.mul, side='left', *args, **kwargs): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: G = CyclicPermutationGroup(3) + sage: R = G.regular_representation() + sage: I = R.invariant_module() + sage: TestSuite(I).run() + + TESTS:: + + sage: G = GroupExp()(QQ) # a group that is not finitely generated + sage: M = CombinatorialFreeModule(QQ, [1,2,3]) + sage: def on_basis(g,m): return M.monomial(m) # trivial rep'n + sage: from sage.modules.with_basis.representation import Representation + sage: R = Representation(G, M, on_basis) + sage: R.invariant_module() + Traceback (most recent call last): + ... + ValueError: Multiplicative form of Rational Field is not finitely generated + """ + if S not in FinitelyGeneratedSemigroups(): + raise ValueError(f"{S} is not finitely generated") + if M not in FiniteDimensionalModulesWithBasis: + raise ValueError(f"{M} is not a finite dimensional module with a distinguished basis") + + if side == "left": + def _invariant_map(g, x): + return action(g, x) - x + elif side == "right": + def _invariant_map(g, x): + return action(x, g) - x + else: + raise ValueError("side must either be 'left' or 'right'") + + self._side = side + self._action = action + self._semigroup = S + + category = kwargs.pop("category", M.category().Subobjects()) + + # Give the intersection of kernels of the map `s*x-x` to determine when + # `s*x = x` for all generators `s` of `S` + basis = M.annihilator_basis(S.gens(), action=_invariant_map, side="left") + + super().__init__(Family(basis), + support_order=M._compute_support_order(basis), + ambient=M, + unitriangular=False, + category=category, + *args, **kwargs) + + def _repr_(self): + r""" + Return a string representaion of ``self``. + + EXAMPLES:: + + sage: G = CyclicPermutationGroup(3) + sage: R = G.trivial_representation() + sage: R.invariant_module() + (Cyclic group of order 3 as a permutation group)-invariant submodule of + Trivial representation of Cyclic group of order 3 as a permutation group over Integer Ring + + sage: G = CyclicPermutationGroup(3) + sage: M = CombinatorialFreeModule(QQ, [1,2,3], prefix='M') + sage: action = lambda g, m: M.monomial(g(m)) # cyclically permute coordinates + sage: M.invariant_module(G, action_on_basis=action) + (Cyclic group of order 3 as a permutation group)-invariant submodule of + Free module generated by {1, 2, 3} over Rational Field + """ + M = self._ambient + from sage.modules.with_basis.representation import Representation + if isinstance(self._ambient, Representation): + M = M._module + return f"({self._semigroup})-invariant submodule of {M}" + + def _latex_(self): + r""" + Return a latex representaion of ``self``. + + EXAMPLES:: + + sage: G = CyclicPermutationGroup(3) + sage: R = G.algebra(QQ) + sage: latex(R.invariant_module(G)) + \left( \Bold{Q}[\langle (1,2,3) \rangle] \right)^{\langle (1,2,3) \rangle} + """ + M = self._ambient + from sage.modules.with_basis.representation import Representation + if isinstance(self._ambient, Representation): + M = M._module + from sage.misc.latex import latex + return "\\left( {} \\right)^{{{}}}".format(latex(M), latex(self._semigroup)) + + def _test_invariant(self, **options): + """ + Check (on some elements) that ``self`` is invariant. + + EXAMPLES:: + + sage: G = SymmetricGroup(3) + sage: M = CombinatorialFreeModule(QQ, [1,2,3], prefix='M') + sage: def action(g, x): return M.monomial(g(x)) + sage: I = M.invariant_module(G, action_on_basis=action) + sage: I._test_invariant() + + sage: G = SymmetricGroup(10) + sage: M = CombinatorialFreeModule(QQ, list(range(1,11)), prefix='M') + sage: def action(g, x): return M.monomial(g(x)) + sage: I = M.invariant_module(G, action_on_basis=action) + sage: I._test_invariant(max_runs=10) + """ + tester = self._tester(**options) + X = tester.some_elements() + L = [] + max_len = tester._max_runs + + # FIXME: This is max_len * dim number of runs!!! + for i, x in enumerate(self._semigroup): + L.append(x) + if i >= max_len: + break + + for x in L: + for elt in X: + lifted = self.lift(elt) + if self._side == 'left': + tester.assertEqual(self._action(x, lifted), lifted) + else: + tester.assertEqual(self._action(lifted, x), lifted) + + def semigroup(self): + r""" + Return the semigroup `S` whose action ``self`` is invariant under. + + EXAMPLES:: + + sage: G = SymmetricGroup(3) + sage: M = CombinatorialFreeModule(QQ, [1,2,3], prefix='M') + sage: def action(g,x): return M.monomial(g(x)) + sage: I = M.invariant_module(G, action_on_basis=action) + sage: I.semigroup() + Symmetric group of order 3! as a permutation group + """ + return self._semigroup + + semigroup_representation = SubmoduleWithBasis.ambient + + class Element(SubmoduleWithBasis.Element): + def _mul_(self, other): + r""" + Multiply ``self`` and ``other``. + + EXAMPLES: + + In general, there is not a well defined multiplication between + two elements of a given module, but there is a multiplication + with scalars:: + + sage: M = CombinatorialFreeModule(QQ,[1,2,3],prefix='M'); + sage: G = CyclicPermutationGroup(3); G.rename('G') + sage: g = G.an_element(); g + (1,2,3) + sage: from sage.modules.with_basis.representation import Representation + sage: R = Representation(G,M,lambda g,x:M.monomial(g(x))); R.rename('R') + sage: I = R.invariant_module() + sage: B = I.basis() + sage: [I.lift(b) for b in B] + [M[1] + M[2] + M[3]] + sage: v = B[0] + sage: v*v + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for *: 'R' and 'R' + sage: (1/2) * v + 1/2*B[0] + sage: v * (1/2) + 1/2*B[0] + sage: R.rename() # reset name + + Sometimes, the module is also a ring. To ensure the multiplication + works as desired, we should be sure to pass the correct category to + the :class:`~sage.modules.with_basis.representation.Representation`. + In the following example, we use the exterior algebra over `\QQ` + with three generators, which is in the category of finite + dimensional `\QQ`-algebras with a basis:: + + sage: G = CyclicPermutationGroup(3); G.rename('G') + sage: M = algebras.Exterior(QQ, 'x', 3) + sage: def on_basis(g,m): return M.prod([M.monomial((g(j+1)-1,)) for j in m]) # cyclically permute generators + sage: R = Representation(G, M, on_basis, category=Algebras(QQ).WithBasis().FiniteDimensional(), side='right') + sage: I = R.invariant_module(); I.rename('I') + sage: B = I.basis() + sage: v = B[0] + 2*B[1]; I.lift(v) + 1 + 2*x0 + 2*x1 + 2*x2 + sage: w = B[2]; I.lift(w) + x0*x1 - x0*x2 + x1*x2 + sage: v * w + B[2] + 6*B[3] + sage: I.lift(v*w) + x0*x1 + 6*x0*x1*x2 - x0*x2 + x1*x2 + sage: w * v + B[2] + 6*B[3] + sage: (1/2) * v + 1/2*B[0] + B[1] + sage: w * (1/2) + 1/2*B[2] + sage: g = G((1,3,2)) + sage: v * g + B[0] + 2*B[1] + sage: w * g + B[2] + sage: g * v + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for *: 'G' and 'I' + sage: I.rename() # reset name + + sage: R = Representation(G, M, on_basis, category=Algebras(QQ).WithBasis().FiniteDimensional()) + sage: I = R.invariant_module(); I.rename('I') + sage: B = I.basis() + sage: v = B[0] + 2*B[1]; I.lift(v) + 1 + 2*x0 + 2*x1 + 2*x2 + sage: w = B[2]; I.lift(w) + x0*x1 - x0*x2 + x1*x2 + sage: v * w + B[2] + 6*B[3] + sage: I.lift(v*w) + x0*x1 + 6*x0*x1*x2 - x0*x2 + x1*x2 + sage: w * v + B[2] + 6*B[3] + sage: (1/2) * v + 1/2*B[0] + B[1] + sage: w * (1/2) + 1/2*B[2] + sage: g = G((1,3,2)) + sage: v * v + B[0] + 4*B[1] + sage: g * w + B[2] + sage: v * g + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for *: 'I' and 'G' + sage: G.rename(); I.rename() # reset names + """ + P = self.parent() + return P.retract(P.lift(self) * P.lift(other)) + + def _acted_upon_(self, scalar, self_on_left=False): + """ + EXAMPLES:: + + sage: G = CyclicPermutationGroup(3) + sage: g = G.an_element(); g + (1,2,3) + sage: M = CombinatorialFreeModule(QQ, [1,2,3]) + sage: E = algebras.Exterior(QQ, 'x', 3) + sage: from sage.modules.with_basis.representation import Representation + sage: R = Representation(G, M, lambda g,x: M.monomial(g(x))) + sage: I = R.invariant_module() + sage: [b._acted_upon_(G((1,3,2))) for b in I.basis()] + [B[0]] + sage: v = I.an_element(); v + 2*B[0] + sage: g * v + 2*B[0] + sage: [g * v for g in G.list()] + [2*B[0], 2*B[0], 2*B[0]] + + + sage: def on_basis(g,m): return E.prod([E.monomial((g(j+1)-1,)) for j in m]) # cyclically permute generators + sage: R = Representation(G, E, on_basis, category=Algebras(QQ).WithBasis().FiniteDimensional()) + sage: I = R.invariant_module() + sage: B = I.basis() + sage: [I.lift(b) for b in B] + [1, x0 + x1 + x2, x0*x1 - x0*x2 + x1*x2, x0*x1*x2] + sage: [[g*b for g in G] for b in B] + [[B[0], B[0], B[0]], + [B[1], B[1], B[1]], + [B[2], B[2], B[2]], + [B[3], B[3], B[3]]] + sage: 3 * I.basis()[0] + 3*B[0] + sage: 3*B[0] + B[1]*2 + 3*B[0] + 2*B[1] + + sage: R = G.regular_representation(QQ) + sage: I = R.invariant_module() + sage: B = I.basis() + sage: [I.lift(b) for b in B] + [() + (1,2,3) + (1,3,2)] + sage: B[0]._acted_upon_(G((1,3,2))) + B[0] + sage: B[0]._acted_upon_(G((1,3,2)), self_on_left=True) is None + True + + sage: R = G.regular_representation(QQ, side='right') + sage: I = R.invariant_module() + sage: B = I.basis() + sage: [I.lift(b) for b in B] + [() + (1,2,3) + (1,3,2)] + sage: g = G((1,3,2)) + sage: B[0]._acted_upon_(g, self_on_left=True) + B[0] + sage: B[0]._acted_upon_(g, self_on_left=False) is None + True + + sage: R = Representation(G, M, lambda g,x: M.monomial(g(x)), side='right') + sage: I = R.invariant_module() + sage: v = I.an_element(); v + 2*B[0] + sage: v * g + 2*B[0] + sage: [v * g for g in G.list()] + [2*B[0], 2*B[0], 2*B[0]] + sage: [b._acted_upon_(G((1,3,2)), self_on_left=True) for b in I.basis()] + [B[0]] + + sage: def on_basis(g,m): return E.prod([E.monomial((g(j+1)-1,)) for j in m]) # cyclically permute generators + sage: R = Representation(G, E, on_basis, category=Algebras(QQ).WithBasis().FiniteDimensional(), side='right') + sage: I = R.invariant_module() + sage: B = I.basis() + sage: [I.lift(b) for b in B] + [1, x0 + x1 + x2, x0*x1 - x0*x2 + x1*x2, x0*x1*x2] + sage: [[b * g for g in G] for b in B] + [[B[0], B[0], B[0]], + [B[1], B[1], B[1]], + [B[2], B[2], B[2]], + [B[3], B[3], B[3]]] + sage: 3 * B[0] + B[1] * 2 + 3*B[0] + 2*B[1] + """ + if scalar in self.parent()._semigroup and self_on_left == (self.parent()._side == 'right'): + return self + return super()._acted_upon_(scalar, self_on_left) + diff --git a/src/sage/modules/with_basis/representation.py b/src/sage/modules/with_basis/representation.py index 27fffed163d..317a401f1fc 100644 --- a/src/sage/modules/with_basis/representation.py +++ b/src/sage/modules/with_basis/representation.py @@ -93,6 +93,62 @@ def side(self): 'left' """ + def invariant_module(self, S=None, **kwargs): + r""" + Return the submodule of ``self`` invariant under the action of ``S``. + + For a semigroup `S` acting on a module `M`, the invariant + submodule is given by + + .. MATH:: + + M^S = \{m \in M : s \cdot m = m \forall s \in S\}. + + INPUT: + + - ``S`` -- a finitely-generated semigroup (default: the semigroup + this is a representation of) + - ``action`` -- a function (default: :obj:`operator.mul`) + - ``side`` -- ``'left'`` or ``'right'`` (default: :meth:`side()`); + which side of ``self`` the elements of ``S`` acts + + .. NOTE:: + + Two sided actions are considered as left actions for the + invariant module. + + OUTPUT: + + - :class:`~sage.modules.with_basis.invariant.FiniteDimensionalInvariantModule` + + EXAMPLES:: + + sage: S3 = SymmetricGroup(3) + sage: M = S3.regular_representation() + sage: I = M.invariant_module() + sage: [I.lift(b) for b in I.basis()] + [() + (2,3) + (1,2) + (1,2,3) + (1,3,2) + (1,3)] + + We build the `D_4`-invariant representation inside of the regular + representation of `S_4`:: + + sage: D4 = groups.permutation.Dihedral(4) + sage: S4 = SymmetricGroup(4) + sage: R = S4.regular_representation() + sage: I = R.invariant_module(D4) + sage: [I.lift(b) for b in I.basis()] + [() + (2,4) + (1,2)(3,4) + (1,2,3,4) + (1,3) + (1,3)(2,4) + (1,4,3,2) + (1,4)(2,3), + (3,4) + (2,3,4) + (1,2) + (1,2,4) + (1,3,2) + (1,3,2,4) + (1,4,3) + (1,4,2,3), + (2,3) + (2,4,3) + (1,2,3) + (1,2,4,3) + (1,3,4,2) + (1,3,4) + (1,4,2) + (1,4)] + """ + if S is None: + S = self.semigroup() + side = kwargs.pop('side', self.side()) + if side == "twosided": + side = "left" + + return super().invariant_module(S, side=side, **kwargs) + class Representation(Representation_abstract): """ Representation of a semigroup. @@ -442,10 +498,22 @@ def _acted_upon_(self, scalar, self_on_left=False): TypeError: unsupported operand parent(s) for *: 'Algebra of Weyl Group of type ['B', 2] ... over Rational Field' and 'Left Regular Representation of Weyl Group of type ['B', 2] ... over Integer Ring' + + Check that things that coerce into the group (algebra) also have + an action:: + + sage: D4 = groups.permutation.Dihedral(4) + sage: S4 = SymmetricGroup(4) + sage: S4.has_coerce_map_from(D4) + True + sage: R = S4.regular_representation() + sage: D4.an_element() * R.an_element() + 2*(2,4) + 3*(1,2,3,4) + (1,3) + (1,4,2,3) """ if isinstance(scalar, Element): P = self.parent() - if scalar.parent() is P._semigroup: + sP = scalar.parent() + if sP is P._semigroup: if not self: return self if self_on_left == P._left_repr: @@ -453,7 +521,7 @@ def _acted_upon_(self, scalar, self_on_left=False): return P.linear_combination(((P._on_basis(scalar, m), c) for m,c in self), not self_on_left) - if scalar.parent() is P._semigroup_algebra: + if sP is P._semigroup_algebra: if not self: return self ret = P.zero() @@ -464,9 +532,23 @@ def _acted_upon_(self, scalar, self_on_left=False): for m,c in self), not self_on_left) return ret - return CombinatorialFreeModule.Element._acted_upon_(self, scalar, self_on_left) + if P._semigroup.has_coerce_map_from(sP): + scalar = P._semigroup(scalar) + return self._acted_upon_(scalar, self_on_left) + + # Check for scalars first before general coercion to the semigroup algebra. + # This will result in a faster action for the scalars. + ret = CombinatorialFreeModule.Element._acted_upon_(self, scalar, self_on_left) + if ret is not None: + return ret + + if P._semigroup_algebra.has_coerce_map_from(sP): + scalar = P._semigroup_algebra(scalar) + return self._acted_upon_(scalar, self_on_left) + + return None - _rmul_ = _lmul_ = _acted_upon_ + return CombinatorialFreeModule.Element._acted_upon_(self, scalar, self_on_left) class RegularRepresentation(Representation): r""" @@ -631,20 +713,38 @@ def _acted_upon_(self, scalar, self_on_left=False): sage: z = V.zero() sage: all(b * z == z for b in SGA.basis()) True + + sage: H = groups.permutation.Dihedral(5) + sage: G = SymmetricGroup(5) + sage: G.has_coerce_map_from(H) + True + sage: R = G.trivial_representation(QQ) + sage: H.an_element() * R.an_element() + 2*B['v'] + + sage: AG = G.algebra(QQ) + sage: AG.an_element() * R.an_element() + 14*B['v'] + + sage: AH = H.algebra(ZZ) + sage: AG.has_coerce_map_from(AH) + True + sage: AH.an_element() * R.an_element() + 14*B['v'] """ if isinstance(scalar, Element): - if scalar.parent() is self.parent()._semigroup: + P = self.parent() + if P._semigroup.has_coerce_map_from(scalar.parent()): return self - if scalar.parent() is self.parent()._semigroup_algebra: + if P._semigroup_algebra.has_coerce_map_from(scalar.parent()): if not self: return self + scalar = P._semigroup_algebra(scalar) d = self.monomial_coefficients(copy=True) d['v'] *= sum(scalar.coefficients()) - return self.parent()._from_dict(d) + return P._from_dict(d) return CombinatorialFreeModule.Element._acted_upon_(self, scalar, self_on_left) - _rmul_ = _lmul_ = _acted_upon_ - class SignRepresentation_abstract(Representation_abstract): """ @@ -757,26 +857,48 @@ def _acted_upon_(self, scalar, self_on_left=False): 0 sage: (c-s)*x 4*B['v'] + + sage: H = groups.permutation.Dihedral(4) + sage: G = SymmetricGroup(4) + sage: G.has_coerce_map_from(H) + True + sage: R = G.sign_representation() + sage: H.an_element() * R.an_element() + -2*B['v'] + + sage: AG = G.algebra(ZZ) + sage: AH = H.algebra(ZZ) + sage: AG.has_coerce_map_from(AH) + True + sage: AH.an_element() * R.an_element() + -2*B['v'] """ if isinstance(scalar, Element): P = self.parent() - if not self: - return self - if scalar.parent() is P._semigroup: + if P._semigroup.has_coerce_map_from(scalar.parent()): + scalar = P._semigroup(scalar) return self if P.sign_function(scalar) > 0 else -self - if scalar.parent() is P._semigroup_algebra: + # We need to check for scalars first + ret = CombinatorialFreeModule.Element._acted_upon_(self, scalar, self_on_left) + if ret is not None: + return ret + + if P._semigroup_algebra.has_coerce_map_from(scalar.parent()): + if not self: + return self sum_scalar_coeff = 0 + scalar = P._semigroup_algebra(scalar) for ms, cs in scalar: sum_scalar_coeff += P.sign_function(ms) * cs return sum_scalar_coeff * self + return None + return CombinatorialFreeModule.Element._acted_upon_( self, scalar, self_on_left ) - _rmul_ = _lmul_ = _acted_upon_ - class SignRepresentationPermgroup(SignRepresentation_abstract): """ @@ -788,7 +910,6 @@ class SignRepresentationPermgroup(SignRepresentation_abstract): sage: V = G.sign_representation() sage: TestSuite(V).run() """ - def _default_sign(self, elem): """ Return the sign of the element @@ -807,7 +928,6 @@ def _default_sign(self, elem): sage: V._default_sign(elem) -1 """ - return elem.sign() @@ -821,7 +941,6 @@ class SignRepresentationMatrixGroup(SignRepresentation_abstract): sage: V = G.sign_representation() sage: TestSuite(V).run() """ - def _default_sign(self, elem): """ Return the sign of the element @@ -854,7 +973,6 @@ class SignRepresentationCoxeterGroup(SignRepresentation_abstract): sage: V = G.sign_representation() sage: TestSuite(V).run() """ - def _default_sign(self, elem): """ Return the sign of the element @@ -872,3 +990,4 @@ def _default_sign(self, elem): 1 """ return -1 if elem.length() % 2 == 1 else 1 + diff --git a/src/sage/numerical/backends/cvxopt_backend.pyx b/src/sage/numerical/backends/cvxopt_backend.pyx index 8273744c883..11911062428 100644 --- a/src/sage/numerical/backends/cvxopt_backend.pyx +++ b/src/sage/numerical/backends/cvxopt_backend.pyx @@ -30,13 +30,13 @@ cdef class CVXOPTBackend(GenericBackend): EXAMPLES:: - sage: p = MixedIntegerLinearProgram(solver="CVXOPT") + sage: p = MixedIntegerLinearProgram(solver="CVXOPT") # optional - cvxopt TESTS: :trac:`20332`:: - sage: p + sage: p # optional - cvxopt Mixed Integer Program (no objective, 0 variables, 0 constraints) """ @@ -62,7 +62,7 @@ cdef class CVXOPTBackend(GenericBackend): EXAMPLES:: sage: from sage.numerical.backends.generic_backend import get_solver - sage: p = get_solver(solver = "CVXOPT") + sage: p = get_solver(solver = "CVXOPT") # optional - cvxopt """ @@ -101,15 +101,15 @@ cdef class CVXOPTBackend(GenericBackend): EXAMPLES:: sage: from sage.numerical.backends.generic_backend import get_solver - sage: p = MixedIntegerLinearProgram(solver = "CVXOPT") - sage: b = p.new_variable() - sage: p.add_constraint(b[1] + b[2] <= 6) - sage: p.add_constraint(b[2] <= 5) - sage: p.set_objective(b[1] + b[2]) - sage: cp = copy(p.get_backend()) - sage: cp.solve() + sage: p = MixedIntegerLinearProgram(solver = "CVXOPT") # optional - cvxopt + sage: b = p.new_variable() # optional - cvxopt + sage: p.add_constraint(b[1] + b[2] <= 6) # optional - cvxopt + sage: p.add_constraint(b[2] <= 5) # optional - cvxopt + sage: p.set_objective(b[1] + b[2]) # optional - cvxopt + sage: cp = copy(p.get_backend()) # optional - cvxopt + sage: cp.solve() # optional - cvxopt 0 - sage: cp.get_objective_value() + sage: cp.get_objective_value() # optional - cvxopt 6.0 """ cdef CVXOPTBackend cp = type(self)() @@ -160,35 +160,35 @@ cdef class CVXOPTBackend(GenericBackend): EXAMPLES:: sage: from sage.numerical.backends.generic_backend import get_solver - sage: p = get_solver(solver = "CVXOPT") - sage: p.ncols() + sage: p = get_solver(solver = "CVXOPT") # optional - cvxopt + sage: p.ncols() # optional - cvxopt 0 - sage: p.add_variable() + sage: p.add_variable() # optional - cvxopt 0 - sage: p.ncols() + sage: p.ncols() # optional - cvxopt 1 - sage: p.add_variable() + sage: p.add_variable() # optional - cvxopt 1 - sage: p.add_variable(lower_bound=-2.0) + sage: p.add_variable(lower_bound=-2.0) # optional - cvxopt 2 - sage: p.add_variable(continuous=True) + sage: p.add_variable(continuous=True) # optional - cvxopt 3 - sage: p.add_variable(name='x',obj=1.0) + sage: p.add_variable(name='x',obj=1.0) # optional - cvxopt 4 - sage: p.col_name(3) + sage: p.col_name(3) # optional - cvxopt 'x_3' - sage: p.col_name(4) + sage: p.col_name(4) # optional - cvxopt 'x' - sage: p.objective_coefficient(4) + sage: p.objective_coefficient(4) # optional - cvxopt 1.00000000000000 TESTS:: - sage: p.add_variable(integer=True) + sage: p.add_variable(integer=True) # optional - cvxopt Traceback (most recent call last): ... RuntimeError: CVXOPT only supports continuous variables - sage: p.add_variable(binary=True) + sage: p.add_variable(binary=True) # optional - cvxopt Traceback (most recent call last): ... RuntimeError: CVXOPT only supports continuous variables @@ -211,11 +211,11 @@ cdef class CVXOPTBackend(GenericBackend): EXAMPLES:: sage: from sage.numerical.backends.generic_backend import get_solver - sage: p = get_solver(solver = "cvxopt") - sage: p.add_variables(5) + sage: p = get_solver(solver = "cvxopt") # optional - cvxopt + sage: p.add_variables(5) # optional - cvxopt 4 - sage: p.set_variable_type(3, -1) - sage: p.set_variable_type(3, -2) + sage: p.set_variable_type(3, -1) # optional - cvxopt + sage: p.set_variable_type(3, -2) # optional - cvxopt Traceback (most recent call last): ... ValueError: ... @@ -237,11 +237,11 @@ cdef class CVXOPTBackend(GenericBackend): EXAMPLES:: sage: from sage.numerical.backends.generic_backend import get_solver - sage: p = get_solver(solver = "CVXOPT") - sage: p.is_maximization() + sage: p = get_solver(solver = "CVXOPT") # optional - cvxopt + sage: p.is_maximization() # optional - cvxopt True - sage: p.set_sense(-1) - sage: p.is_maximization() + sage: p.set_sense(-1) # optional - cvxopt + sage: p.is_maximization() # optional - cvxopt False """ if sense == 1: @@ -263,13 +263,13 @@ cdef class CVXOPTBackend(GenericBackend): EXAMPLES:: sage: from sage.numerical.backends.generic_backend import get_solver - sage: p = get_solver(solver = "CVXOPT") - sage: p.add_variable() + sage: p = get_solver(solver = "CVXOPT") # optional - cvxopt + sage: p.add_variable() # optional - cvxopt 0 - sage: p.objective_coefficient(0) + sage: p.objective_coefficient(0) # optional - cvxopt 0.0 - sage: p.objective_coefficient(0,2) - sage: p.objective_coefficient(0) + sage: p.objective_coefficient(0,2) # optional - cvxopt + sage: p.objective_coefficient(0) # optional - cvxopt 2.0 """ if coeff is not None: @@ -290,12 +290,12 @@ cdef class CVXOPTBackend(GenericBackend): EXAMPLES:: - sage: from sage.numerical.backends.generic_backend import get_solver - sage: p = get_solver(solver = "CVXOPT") - sage: p.add_variables(5) + sage: from sage.numerical.backends.generic_backend import get_solver # optional - cvxopt + sage: p = get_solver(solver = "CVXOPT") # optional - cvxopt + sage: p.add_variables(5) # optional - cvxopt 4 - sage: p.set_objective([1, 1, 2, 1, 3]) - sage: [p.objective_coefficient(x) for x in range(5)] + sage: p.set_objective([1, 1, 2, 1, 3]) # optional - cvxopt + sage: [p.objective_coefficient(x) for x in range(5)] # optional - cvxopt [1, 1, 2, 1, 3] """ for i in range(len(coeff)): @@ -334,14 +334,14 @@ cdef class CVXOPTBackend(GenericBackend): EXAMPLES:: sage: from sage.numerical.backends.generic_backend import get_solver - sage: p = get_solver(solver = "CVXOPT") - sage: p.ncols() + sage: p = get_solver(solver = "CVXOPT") # optional - cvxopt + sage: p.ncols() # optional - cvxopt 0 - sage: p.nrows() + sage: p.nrows() # optional - cvxopt 0 - sage: p.add_linear_constraints(5, 0, None) - sage: p.add_col(range(5), range(5)) - sage: p.nrows() + sage: p.add_linear_constraints(5, 0, None) # optional - cvxopt + sage: p.add_col(range(5), range(5)) # optional - cvxopt + sage: p.nrows() # optional - cvxopt 5 """ column = [] @@ -377,16 +377,16 @@ cdef class CVXOPTBackend(GenericBackend): EXAMPLES:: sage: from sage.numerical.backends.generic_backend import get_solver - sage: p = get_solver(solver = "CVXOPT") - sage: p.add_variables(5) + sage: p = get_solver(solver = "CVXOPT") # optional - cvxopt + sage: p.add_variables(5) # optional - cvxopt 4 - sage: p.add_linear_constraint(zip(range(5), range(5)), 2.0, 2.0) - sage: p.row(0) + sage: p.add_linear_constraint(zip(range(5), range(5)), 2.0, 2.0) # optional - cvxopt + sage: p.row(0) # optional - cvxopt ([1, 2, 3, 4], [1, 2, 3, 4]) - sage: p.row_bounds(0) + sage: p.row_bounds(0) # optional - cvxopt (2.00000000000000, 2.00000000000000) - sage: p.add_linear_constraint(zip(range(5), range(5)), 1.0, 1.0, name='foo') - sage: p.row_name(-1) + sage: p.add_linear_constraint(zip(range(5), range(5)), 1.0, 1.0, name='foo') # optional - cvxopt + sage: p.row_name(-1) # optional - cvxopt 'foo' """ coefficients = list(coefficients) @@ -414,76 +414,76 @@ cdef class CVXOPTBackend(GenericBackend): EXAMPLES:: - sage: p = MixedIntegerLinearProgram(solver = "cvxopt", maximization=False) - sage: x=p.new_variable(nonnegative=True) - sage: p.set_objective(-4*x[0] - 5*x[1]) - sage: p.add_constraint(2*x[0] + x[1] <= 3) - sage: p.add_constraint(2*x[1] + x[0] <= 3) - sage: N(p.solve(), digits=2) + sage: p = MixedIntegerLinearProgram(solver = "cvxopt", maximization=False) # optional - cvxopt + sage: x=p.new_variable(nonnegative=True) # optional - cvxopt + sage: p.set_objective(-4*x[0] - 5*x[1]) # optional - cvxopt + sage: p.add_constraint(2*x[0] + x[1] <= 3) # optional - cvxopt + sage: p.add_constraint(2*x[1] + x[0] <= 3) # optional - cvxopt + sage: N(p.solve(), digits=2) # optional - cvxopt -9.0 - sage: p = MixedIntegerLinearProgram(solver = "cvxopt", maximization=False) - sage: x=p.new_variable(nonnegative=True) - sage: p.set_objective(x[0] + 2*x[1]) - sage: p.add_constraint(-5*x[0] + x[1] <= 7) - sage: p.add_constraint(-5*x[0] + x[1] >= 7) - sage: p.add_constraint(x[0] + x[1] >= 26 ) - sage: p.add_constraint( x[0] >= 3) - sage: p.add_constraint( x[1] >= 4) - sage: N(p.solve(),digits=4) + sage: p = MixedIntegerLinearProgram(solver = "cvxopt", maximization=False) # optional - cvxopt + sage: x=p.new_variable(nonnegative=True) # optional - cvxopt + sage: p.set_objective(x[0] + 2*x[1]) # optional - cvxopt + sage: p.add_constraint(-5*x[0] + x[1] <= 7) # optional - cvxopt + sage: p.add_constraint(-5*x[0] + x[1] >= 7) # optional - cvxopt + sage: p.add_constraint(x[0] + x[1] >= 26 ) # optional - cvxopt + sage: p.add_constraint( x[0] >= 3) # optional - cvxopt + sage: p.add_constraint( x[1] >= 4) # optional - cvxopt + sage: N(p.solve(),digits=4) # optional - cvxopt 48.83 - sage: p = MixedIntegerLinearProgram(solver = "cvxopt") - sage: x=p.new_variable(nonnegative=True) - sage: p.set_objective(x[0] + x[1] + 3*x[2]) - sage: p.solver_parameter("show_progress",True) - sage: p.add_constraint(x[0] + 2*x[1] <= 4) - sage: p.add_constraint(5*x[2] - x[1] <= 8) - sage: N(p.solve(), digits=2) + sage: p = MixedIntegerLinearProgram(solver = "cvxopt") # optional - cvxopt + sage: x=p.new_variable(nonnegative=True) # optional - cvxopt + sage: p.set_objective(x[0] + x[1] + 3*x[2]) # optional - cvxopt + sage: p.solver_parameter("show_progress",True) # optional - cvxopt + sage: p.add_constraint(x[0] + 2*x[1] <= 4) # optional - cvxopt + sage: p.add_constraint(5*x[2] - x[1] <= 8) # optional - cvxopt + sage: N(p.solve(), digits=2) # optional - cvxopt pcost dcost gap pres dres k/t ... 8.8 sage: #CVXOPT gives different values for variables compared to the other solvers. - sage: c = MixedIntegerLinearProgram(solver = "cvxopt") - sage: p = MixedIntegerLinearProgram(solver = "ppl") - sage: g = MixedIntegerLinearProgram() - sage: xc=c.new_variable(nonnegative=True) - sage: xp=p.new_variable(nonnegative=True) - sage: xg=g.new_variable(nonnegative=True) - sage: c.set_objective(xc[2]) - sage: p.set_objective(xp[2]) - sage: g.set_objective(xg[2]) + sage: c = MixedIntegerLinearProgram(solver = "cvxopt") # optional - cvxopt + sage: p = MixedIntegerLinearProgram(solver = "ppl") # optional - cvxopt + sage: g = MixedIntegerLinearProgram() # optional - cvxopt + sage: xc=c.new_variable(nonnegative=True) # optional - cvxopt + sage: xp=p.new_variable(nonnegative=True) # optional - cvxopt + sage: xg=g.new_variable(nonnegative=True) # optional - cvxopt + sage: c.set_objective(xc[2]) # optional - cvxopt + sage: p.set_objective(xp[2]) # optional - cvxopt + sage: g.set_objective(xg[2]) # optional - cvxopt sage: #we create a cube for all three solvers - sage: c.add_constraint(xc[0] <= 100) - sage: c.add_constraint(xc[1] <= 100) - sage: c.add_constraint(xc[2] <= 100) - sage: p.add_constraint(xp[0] <= 100) - sage: p.add_constraint(xp[1] <= 100) - sage: p.add_constraint(xp[2] <= 100) - sage: g.add_constraint(xg[0] <= 100) - sage: g.add_constraint(xg[1] <= 100) - sage: g.add_constraint(xg[2] <= 100) - sage: N(c.solve(),digits=4) + sage: c.add_constraint(xc[0] <= 100) # optional - cvxopt + sage: c.add_constraint(xc[1] <= 100) # optional - cvxopt + sage: c.add_constraint(xc[2] <= 100) # optional - cvxopt + sage: p.add_constraint(xp[0] <= 100) # optional - cvxopt + sage: p.add_constraint(xp[1] <= 100) # optional - cvxopt + sage: p.add_constraint(xp[2] <= 100) # optional - cvxopt + sage: g.add_constraint(xg[0] <= 100) # optional - cvxopt + sage: g.add_constraint(xg[1] <= 100) # optional - cvxopt + sage: g.add_constraint(xg[2] <= 100) # optional - cvxopt + sage: N(c.solve(),digits=4) # optional - cvxopt 100.0 - sage: N(c.get_values(xc[0]),digits=3) + sage: N(c.get_values(xc[0]),digits=3) # optional - cvxopt 50.0 - sage: N(c.get_values(xc[1]),digits=3) + sage: N(c.get_values(xc[1]),digits=3) # optional - cvxopt 50.0 - sage: N(c.get_values(xc[2]),digits=4) + sage: N(c.get_values(xc[2]),digits=4) # optional - cvxopt 100.0 - sage: N(p.solve(),digits=4) + sage: N(p.solve(),digits=4) # optional - cvxopt 100.0 - sage: N(p.get_values(xp[0]),2) + sage: N(p.get_values(xp[0]),2) # optional - cvxopt 0.00 - sage: N(p.get_values(xp[1]),2) + sage: N(p.get_values(xp[1]),2) # optional - cvxopt 0.00 - sage: N(p.get_values(xp[2]),digits=4) + sage: N(p.get_values(xp[2]),digits=4) # optional - cvxopt 100.0 - sage: N(g.solve(),digits=4) + sage: N(g.solve(),digits=4) # optional - cvxopt 100.0 - sage: N(g.get_values(xg[0]),2) + sage: N(g.get_values(xg[0]),2) # optional - cvxopt 0.00 - sage: N(g.get_values(xg[1]),2) + sage: N(g.get_values(xg[1]),2) # optional - cvxopt 0.00 - sage: N(g.get_values(xg[2]),digits=4) + sage: N(g.get_values(xg[2]),digits=4) # optional - cvxopt 100.0 """ from cvxopt import matrix, solvers @@ -567,18 +567,18 @@ cdef class CVXOPTBackend(GenericBackend): EXAMPLES:: sage: from sage.numerical.backends.generic_backend import get_solver - sage: p = get_solver(solver = "cvxopt") - sage: p.add_variables(2) + sage: p = get_solver(solver = "cvxopt") # optional - cvxopt + sage: p.add_variables(2) # optional - cvxopt 1 - sage: p.add_linear_constraint([(0,1), (1,2)], None, 3) - sage: p.set_objective([2, 5]) - sage: p.solve() + sage: p.add_linear_constraint([(0,1), (1,2)], None, 3) # optional - cvxopt + sage: p.set_objective([2, 5]) # optional - cvxopt + sage: p.solve() # optional - cvxopt 0 - sage: N(p.get_objective_value(),4) + sage: N(p.get_objective_value(),4) # optional - cvxopt 7.5 - sage: N(p.get_variable_value(0),4) + sage: N(p.get_variable_value(0),4) # optional - cvxopt 3.6e-7 - sage: N(p.get_variable_value(1),4) + sage: N(p.get_variable_value(1),4) # optional - cvxopt 1.5 """ sum = self.obj_constant_term @@ -599,18 +599,18 @@ cdef class CVXOPTBackend(GenericBackend): EXAMPLES:: sage: from sage.numerical.backends.generic_backend import get_solver - sage: p = get_solver(solver = "CVXOPT") - sage: p.add_variables(2) + sage: p = get_solver(solver = "CVXOPT") # optional - cvxopt + sage: p.add_variables(2) # optional - cvxopt 1 - sage: p.add_linear_constraint([(0,1), (1, 2)], None, 3) - sage: p.set_objective([2, 5]) - sage: p.solve() + sage: p.add_linear_constraint([(0,1), (1, 2)], None, 3) # optional - cvxopt + sage: p.set_objective([2, 5]) # optional - cvxopt + sage: p.solve() # optional - cvxopt 0 - sage: N(p.get_objective_value(),4) + sage: N(p.get_objective_value(),4) # optional - cvxopt 7.5 - sage: N(p.get_variable_value(0),4) + sage: N(p.get_variable_value(0),4) # optional - cvxopt 3.6e-7 - sage: N(p.get_variable_value(1),4) + sage: N(p.get_variable_value(1),4) # optional - cvxopt 1.5 """ return self.answer['x'][variable] @@ -622,12 +622,12 @@ cdef class CVXOPTBackend(GenericBackend): EXAMPLES:: sage: from sage.numerical.backends.generic_backend import get_solver - sage: p = get_solver(solver = "CVXOPT") - sage: p.ncols() + sage: p = get_solver(solver = "CVXOPT") # optional - cvxopt + sage: p.ncols() # optional - cvxopt 0 - sage: p.add_variables(2) + sage: p.add_variables(2) # optional - cvxopt 1 - sage: p.ncols() + sage: p.ncols() # optional - cvxopt 2 """ @@ -640,13 +640,13 @@ cdef class CVXOPTBackend(GenericBackend): EXAMPLES:: sage: from sage.numerical.backends.generic_backend import get_solver - sage: p = get_solver(solver = "CVXOPT") - sage: p.nrows() + sage: p = get_solver(solver = "CVXOPT") # optional - cvxopt + sage: p.nrows() # optional - cvxopt 0 - sage: p.add_variables(5) + sage: p.add_variables(5) # optional - cvxopt 4 - sage: p.add_linear_constraints(2, 2.0, None) - sage: p.nrows() + sage: p.add_linear_constraints(2, 2.0, None) # optional - cvxopt + sage: p.nrows() # optional - cvxopt 2 """ return len(self.row_upper_bound) @@ -659,11 +659,11 @@ cdef class CVXOPTBackend(GenericBackend): EXAMPLES:: sage: from sage.numerical.backends.generic_backend import get_solver - sage: p = get_solver(solver = "CVXOPT") - sage: p.is_maximization() + sage: p = get_solver(solver = "CVXOPT") # optional - cvxopt + sage: p.is_maximization() # optional - cvxopt True - sage: p.set_sense(-1) - sage: p.is_maximization() + sage: p.set_sense(-1) # optional - cvxopt + sage: p.is_maximization() # optional - cvxopt False """ if self.is_maximize == 1: @@ -683,11 +683,11 @@ cdef class CVXOPTBackend(GenericBackend): EXAMPLES:: sage: from sage.numerical.backends.generic_backend import get_solver - sage: p = get_solver(solver = "CVXOPT") - sage: p.problem_name() + sage: p = get_solver(solver = "CVXOPT") # optional - cvxopt + sage: p.problem_name() # optional - cvxopt '' - sage: p.problem_name("There once was a french fry") - sage: print(p.problem_name()) + sage: p.problem_name("There once was a french fry") # optional - cvxopt + sage: print(p.problem_name()) # optional - cvxopt There once was a french fry """ if name is None: @@ -713,13 +713,13 @@ cdef class CVXOPTBackend(GenericBackend): EXAMPLES:: sage: from sage.numerical.backends.generic_backend import get_solver - sage: p = get_solver(solver = "CVXOPT") - sage: p.add_variables(5) + sage: p = get_solver(solver = "CVXOPT") # optional - cvxopt + sage: p.add_variables(5) # optional - cvxopt 4 - sage: p.add_linear_constraint(list(zip(range(5), range(5))), 2, 2) - sage: p.row(0) + sage: p.add_linear_constraint(list(zip(range(5), range(5))), 2, 2) # optional - cvxopt + sage: p.row(0) # optional - cvxopt ([1, 2, 3, 4], [1, 2, 3, 4]) - sage: p.row_bounds(0) + sage: p.row_bounds(0) # optional - cvxopt (2, 2) """ coeff = [] @@ -750,13 +750,13 @@ cdef class CVXOPTBackend(GenericBackend): EXAMPLES:: sage: from sage.numerical.backends.generic_backend import get_solver - sage: p = get_solver(solver = "CVXOPT") - sage: p.add_variables(5) + sage: p = get_solver(solver = "CVXOPT") # optional - cvxopt + sage: p.add_variables(5) # optional - cvxopt 4 - sage: p.add_linear_constraint(list(zip(range(5), range(5))), 2, 2) - sage: p.row(0) + sage: p.add_linear_constraint(list(zip(range(5), range(5))), 2, 2) # optional - cvxopt + sage: p.row(0) # optional - cvxopt ([1, 2, 3, 4], [1, 2, 3, 4]) - sage: p.row_bounds(0) + sage: p.row_bounds(0) # optional - cvxopt (2, 2) """ return (self.row_lower_bound[index], self.row_upper_bound[index]) @@ -778,13 +778,13 @@ cdef class CVXOPTBackend(GenericBackend): EXAMPLES:: sage: from sage.numerical.backends.generic_backend import get_solver - sage: p = get_solver(solver = "CVXOPT") - sage: p.add_variable() + sage: p = get_solver(solver = "CVXOPT") # optional - cvxopt + sage: p.add_variable() # optional - cvxopt 0 - sage: p.col_bounds(0) + sage: p.col_bounds(0) # optional - cvxopt (0.0, None) - sage: p.variable_upper_bound(0, 5) - sage: p.col_bounds(0) + sage: p.variable_upper_bound(0, 5) # optional - cvxopt + sage: p.col_bounds(0) # optional - cvxopt (0.0, 5) """ return (self.col_lower_bound[index], self.col_upper_bound[index]) @@ -801,16 +801,16 @@ cdef class CVXOPTBackend(GenericBackend): EXAMPLES:: sage: from sage.numerical.backends.generic_backend import get_solver - sage: p = get_solver(solver = "CVXOPT") - sage: p.ncols() + sage: p = get_solver(solver = "CVXOPT") # optional - cvxopt + sage: p.ncols() # optional - cvxopt 0 - sage: p.add_variable() + sage: p.add_variable() # optional - cvxopt 0 - sage: p.set_variable_type(0,0) + sage: p.set_variable_type(0,0) # optional - cvxopt Traceback (most recent call last): ... ValueError: ... - sage: p.is_variable_binary(0) + sage: p.is_variable_binary(0) # optional - cvxopt False """ @@ -828,17 +828,17 @@ cdef class CVXOPTBackend(GenericBackend): EXAMPLES:: sage: from sage.numerical.backends.generic_backend import get_solver - sage: p = get_solver(solver = "CVXOPT") - sage: p.ncols() + sage: p = get_solver(solver = "CVXOPT") # optional - cvxopt + sage: p.ncols() # optional - cvxopt 0 - sage: p.add_variable() + sage: p.add_variable() # optional - cvxopt 0 - sage: p.set_variable_type(0,-1) - sage: p.set_variable_type(0,1) + sage: p.set_variable_type(0,-1) # optional - cvxopt + sage: p.set_variable_type(0,1) # optional - cvxopt Traceback (most recent call last): ... ValueError: ... - sage: p.is_variable_integer(0) + sage: p.is_variable_integer(0) # optional - cvxopt False """ return False @@ -855,18 +855,18 @@ cdef class CVXOPTBackend(GenericBackend): EXAMPLES:: sage: from sage.numerical.backends.generic_backend import get_solver - sage: p = get_solver(solver = "CVXOPT") - sage: p.ncols() + sage: p = get_solver(solver = "CVXOPT") # optional - cvxopt + sage: p.ncols() # optional - cvxopt 0 - sage: p.add_variable() + sage: p.add_variable() # optional - cvxopt 0 - sage: p.is_variable_continuous(0) + sage: p.is_variable_continuous(0) # optional - cvxopt True - sage: p.set_variable_type(0,1) + sage: p.set_variable_type(0,1) # optional - cvxopt Traceback (most recent call last): ... ValueError: ... - sage: p.is_variable_continuous(0) + sage: p.is_variable_continuous(0) # optional - cvxopt True """ @@ -883,9 +883,9 @@ cdef class CVXOPTBackend(GenericBackend): EXAMPLES:: sage: from sage.numerical.backends.generic_backend import get_solver - sage: p = get_solver(solver = "CVXOPT") - sage: p.add_linear_constraints(1, 2, None, names=["Empty constraint 1"]) - sage: p.row_name(0) + sage: p = get_solver(solver = "CVXOPT") # optional - cvxopt + sage: p.add_linear_constraints(1, 2, None, names=["Empty constraint 1"]) # optional - cvxopt + sage: p.row_name(0) # optional - cvxopt 'Empty constraint 1' """ if self.row_name_var[index] is not None: @@ -906,10 +906,10 @@ cdef class CVXOPTBackend(GenericBackend): EXAMPLES:: sage: from sage.numerical.backends.generic_backend import get_solver - sage: p = get_solver(solver = "CVXOPT") - sage: p.add_variable(name="I am a variable") + sage: p = get_solver(solver = "CVXOPT") # optional - cvxopt + sage: p.add_variable(name="I am a variable") # optional - cvxopt 0 - sage: p.col_name(0) + sage: p.col_name(0) # optional - cvxopt 'I am a variable' """ if self.col_name_var[index] is not None: @@ -931,13 +931,13 @@ cdef class CVXOPTBackend(GenericBackend): EXAMPLES:: sage: from sage.numerical.backends.generic_backend import get_solver - sage: p = get_solver(solver = "CVXOPT") - sage: p.add_variable() + sage: p = get_solver(solver = "CVXOPT") # optional - cvxopt + sage: p.add_variable() # optional - cvxopt 0 - sage: p.col_bounds(0) + sage: p.col_bounds(0) # optional - cvxopt (0.0, None) - sage: p.variable_upper_bound(0, 5) - sage: p.col_bounds(0) + sage: p.variable_upper_bound(0, 5) # optional - cvxopt + sage: p.col_bounds(0) # optional - cvxopt (0.0, 5) """ if value is not False: @@ -960,14 +960,13 @@ cdef class CVXOPTBackend(GenericBackend): EXAMPLES:: sage: from sage.numerical.backends.generic_backend import get_solver - - sage: p = get_solver(solver = "CVXOPT") - sage: p.add_variable() + sage: p = get_solver(solver = "CVXOPT") # optional - cvxopt + sage: p.add_variable() # optional - cvxopt 0 - sage: p.col_bounds(0) + sage: p.col_bounds(0) # optional - cvxopt (0.0, None) - sage: p.variable_lower_bound(0, 5) - sage: p.col_bounds(0) + sage: p.variable_lower_bound(0, 5) # optional - cvxopt + sage: p.col_bounds(0) # optional - cvxopt (5, None) """ if value is not False: @@ -994,11 +993,11 @@ cdef class CVXOPTBackend(GenericBackend): EXAMPLES:: sage: from sage.numerical.backends.generic_backend import get_solver - sage: p = get_solver(solver = "CVXOPT") - sage: p.solver_parameter("show_progress") + sage: p = get_solver(solver="CVXOPT") # optional - cvxopt + sage: p.solver_parameter("show_progress") # optional - cvxopt False - sage: p.solver_parameter("show_progress", True) - sage: p.solver_parameter("show_progress") + sage: p.solver_parameter("show_progress", True) # optional - cvxopt + sage: p.solver_parameter("show_progress") # optional - cvxopt True """ if value is None: diff --git a/src/sage/numerical/backends/cvxopt_sdp_backend.pyx b/src/sage/numerical/backends/cvxopt_sdp_backend.pyx index 3d76b76c1b8..ced0da89b05 100644 --- a/src/sage/numerical/backends/cvxopt_sdp_backend.pyx +++ b/src/sage/numerical/backends/cvxopt_sdp_backend.pyx @@ -36,7 +36,7 @@ cdef class CVXOPTSDPBackend(MatrixSDPBackend): EXAMPLES:: sage: from sage.numerical.backends.generic_sdp_backend import get_solver - sage: p = get_solver(solver = "CVXOPT") + sage: p = get_solver(solver = "CVXOPT") # optional - cvxopt """ @@ -68,9 +68,9 @@ cdef class CVXOPTSDPBackend(MatrixSDPBackend): EXAMPLES:: - sage: p = SemidefiniteProgram(solver = "cvxopt", maximization=False) - sage: x = p.new_variable() - sage: p.set_objective(x[0] - x[1] + x[2]) + sage: p = SemidefiniteProgram(solver = "cvxopt", maximization=False) # optional - cvxopt + sage: x = p.new_variable() # optional - cvxopt + sage: p.set_objective(x[0] - x[1] + x[2]) # optional - cvxopt sage: a1 = matrix([[-7., -11.], [-11., 3.]]) sage: a2 = matrix([[7., -18.], [-18., 8.]]) sage: a3 = matrix([[-2., -8.], [-8., 1.]]) @@ -79,13 +79,13 @@ cdef class CVXOPTSDPBackend(MatrixSDPBackend): sage: b2 = matrix([[0., 10., 16.], [10., -10., -10.], [16., -10., 3.]]) sage: b3 = matrix([[-5., 2., -17.], [2., -6., 8.], [-17., 8., 6.]]) sage: b4 = matrix([[14., 9., 40.], [9., 91., 10.], [40., 10., 15.]]) - sage: p.add_constraint(a1*x[0] + a3*x[2] <= a4) - sage: p.add_constraint(b1*x[0] + b2*x[1] + b3*x[2] <= b4) - sage: N(p.solve(), digits=4) + sage: p.add_constraint(a1*x[0] + a3*x[2] <= a4) # optional - cvxopt + sage: p.add_constraint(b1*x[0] + b2*x[1] + b3*x[2] <= b4) # optional - cvxopt + sage: N(p.solve(), digits=4) # optional - cvxopt -3.225 - sage: p = SemidefiniteProgram(solver = "cvxopt", maximization=False) - sage: x = p.new_variable() - sage: p.set_objective(x[0] - x[1] + x[2]) + sage: p = SemidefiniteProgram(solver = "cvxopt", maximization=False) # optional - cvxopt + sage: x = p.new_variable() # optional - cvxopt + sage: p.set_objective(x[0] - x[1] + x[2]) # optional - cvxopt sage: a1 = matrix([[-7., -11.], [-11., 3.]]) sage: a2 = matrix([[7., -18.], [-18., 8.]]) sage: a3 = matrix([[-2., -8.], [-8., 1.]]) @@ -94,9 +94,9 @@ cdef class CVXOPTSDPBackend(MatrixSDPBackend): sage: b2 = matrix([[0., 10., 16.], [10., -10., -10.], [16., -10., 3.]]) sage: b3 = matrix([[-5., 2., -17.], [2., -6., 8.], [-17., 8., 6.]]) sage: b4 = matrix([[14., 9., 40.], [9., 91., 10.], [40., 10., 15.]]) - sage: p.add_constraint(a1*x[0] + a2*x[1] + a3*x[2] <= a4) - sage: p.add_constraint(b1*x[0] + b2*x[1] + b3*x[2] <= b4) - sage: N(p.solve(), digits=4) + sage: p.add_constraint(a1*x[0] + a2*x[1] + a3*x[2] <= a4) # optional - cvxopt + sage: p.add_constraint(b1*x[0] + b2*x[1] + b3*x[2] <= b4) # optional - cvxopt + sage: N(p.solve(), digits=4) # optional - cvxopt -3.154 """ @@ -171,9 +171,9 @@ cdef class CVXOPTSDPBackend(MatrixSDPBackend): EXAMPLES:: - sage: p = SemidefiniteProgram(solver = "cvxopt", maximization=False) - sage: x = p.new_variable() - sage: p.set_objective(x[0] - x[1] + x[2]) + sage: p = SemidefiniteProgram(solver = "cvxopt", maximization=False) # optional - cvxopt + sage: x = p.new_variable() # optional - cvxopt + sage: p.set_objective(x[0] - x[1] + x[2]) # optional - cvxopt sage: a1 = matrix([[-7., -11.], [-11., 3.]]) sage: a2 = matrix([[7., -18.], [-18., 8.]]) sage: a3 = matrix([[-2., -8.], [-8., 1.]]) @@ -182,11 +182,11 @@ cdef class CVXOPTSDPBackend(MatrixSDPBackend): sage: b2 = matrix([[0., 10., 16.], [10., -10., -10.], [16., -10., 3.]]) sage: b3 = matrix([[-5., 2., -17.], [2., -6., 8.], [-17., 8., 6.]]) sage: b4 = matrix([[14., 9., 40.], [9., 91., 10.], [40., 10., 15.]]) - sage: p.add_constraint(a1*x[0] + a2*x[1] + a3*x[2] <= a4) - sage: p.add_constraint(b1*x[0] + b2*x[1] + b3*x[2] <= b4) - sage: N(p.solve(), digits=4) + sage: p.add_constraint(a1*x[0] + a2*x[1] + a3*x[2] <= a4) # optional - cvxopt + sage: p.add_constraint(b1*x[0] + b2*x[1] + b3*x[2] <= b4) # optional - cvxopt + sage: N(p.solve(), digits=4) # optional - cvxopt -3.154 - sage: N(p.get_backend().get_objective_value(), digits=4) + sage: N(p.get_backend().get_objective_value(), digits=4) # optional - cvxopt -3.154 """ sum = self.obj_constant_term @@ -204,20 +204,20 @@ cdef class CVXOPTSDPBackend(MatrixSDPBackend): TESTS:: - sage: p = SemidefiniteProgram(maximization = False, solver='cvxopt') - sage: x = p.new_variable() - sage: p.set_objective(x[0] - x[1]) + sage: p = SemidefiniteProgram(maximization = False, solver='cvxopt') # optional - cvxopt + sage: x = p.new_variable() # optional - cvxopt + sage: p.set_objective(x[0] - x[1]) # optional - cvxopt sage: a1 = matrix([[1, 2.], [2., 3.]]) sage: a2 = matrix([[3, 4.], [4., 5.]]) sage: a3 = matrix([[5, 6.], [6., 7.]]) sage: b1 = matrix([[1, 1.], [1., 1.]]) sage: b2 = matrix([[2, 2.], [2., 2.]]) sage: b3 = matrix([[3, 3.], [3., 3.]]) - sage: p.add_constraint(a1*x[0] + a2*x[1] <= a3) - sage: p.add_constraint(b1*x[0] + b2*x[1] <= b3) - sage: p.solve(); # tol 1e-08 + sage: p.add_constraint(a1*x[0] + a2*x[1] <= a3) # optional - cvxopt + sage: p.add_constraint(b1*x[0] + b2*x[1] <= b3) # optional - cvxopt + sage: p.solve(); # tol 1e-08 # optional - cvxopt -3.0 - sage: p.get_backend()._get_answer() + sage: p.get_backend()._get_answer() # optional - cvxopt {...} """ return self.answer @@ -232,11 +232,9 @@ cdef class CVXOPTSDPBackend(MatrixSDPBackend): EXAMPLES:: - sage: from sage.numerical.backends.generic_sdp_backend import get_solver - sage: p = get_solver(solver = "cvxopt") - sage: p = SemidefiniteProgram(solver = "cvxopt", maximization=False) - sage: x = p.new_variable() - sage: p.set_objective(x[0] - x[1] + x[2]) + sage: p = SemidefiniteProgram(solver = "cvxopt", maximization=False) # optional - cvxopt + sage: x = p.new_variable() # optional - cvxopt + sage: p.set_objective(x[0] - x[1] + x[2]) # optional - cvxopt sage: a1 = matrix([[-7., -11.], [-11., 3.]]) sage: a2 = matrix([[7., -18.], [-18., 8.]]) sage: a3 = matrix([[-2., -8.], [-8., 1.]]) @@ -245,15 +243,15 @@ cdef class CVXOPTSDPBackend(MatrixSDPBackend): sage: b2 = matrix([[0., 10., 16.], [10., -10., -10.], [16., -10., 3.]]) sage: b3 = matrix([[-5., 2., -17.], [2., -6., 8.], [-17., 8., 6.]]) sage: b4 = matrix([[14., 9., 40.], [9., 91., 10.], [40., 10., 15.]]) - sage: p.add_constraint(a1*x[0] + a2*x[1] + a3*x[2] <= a4) - sage: p.add_constraint(b1*x[0] + b2*x[1] + b3*x[2] <= b4) - sage: N(p.solve(), digits=4) + sage: p.add_constraint(a1*x[0] + a2*x[1] + a3*x[2] <= a4) # optional - cvxopt + sage: p.add_constraint(b1*x[0] + b2*x[1] + b3*x[2] <= b4) # optional - cvxopt + sage: N(p.solve(), digits=4) # optional - cvxopt -3.154 - sage: N(p.get_backend().get_variable_value(0), digits=3) + sage: N(p.get_backend().get_variable_value(0), digits=3) # optional - cvxopt -0.368 - sage: N(p.get_backend().get_variable_value(1), digits=4) + sage: N(p.get_backend().get_variable_value(1), digits=4) # optional - cvxopt 1.898 - sage: N(p.get_backend().get_variable_value(2), digits=3) + sage: N(p.get_backend().get_variable_value(2), digits=3) # optional - cvxopt -0.888 """ return self.answer['x'][variable] @@ -272,34 +270,34 @@ cdef class CVXOPTSDPBackend(MatrixSDPBackend): EXAMPLES:: - sage: p = SemidefiniteProgram(maximization = False, solver='cvxopt') - sage: x = p.new_variable() - sage: p.set_objective(x[0] - x[1]) + sage: p = SemidefiniteProgram(maximization = False, solver='cvxopt') # optional - cvxopt + sage: x = p.new_variable() # optional - cvxopt + sage: p.set_objective(x[0] - x[1]) # optional - cvxopt sage: a1 = matrix([[1, 2.], [2., 3.]]) sage: a2 = matrix([[3, 4.], [4., 5.]]) sage: a3 = matrix([[5, 6.], [6., 7.]]) sage: b1 = matrix([[1, 1.], [1., 1.]]) sage: b2 = matrix([[2, 2.], [2., 2.]]) sage: b3 = matrix([[3, 3.], [3., 3.]]) - sage: p.add_constraint(a1*x[0] + a2*x[1] <= a3) - sage: p.add_constraint(b1*x[0] + b2*x[1] <= b3) - sage: p.solve() # tol 1e-08 + sage: p.add_constraint(a1*x[0] + a2*x[1] <= a3) # optional - cvxopt + sage: p.add_constraint(b1*x[0] + b2*x[1] <= b3) # optional - cvxopt + sage: p.solve() # tol 1e-08 # optional - cvxopt -3.0 - sage: B=p.get_backend() - sage: x=p.get_values(x).values() - sage: -(a3*B.dual_variable(0)).trace()-(b3*B.dual_variable(1)).trace() # tol 1e-07 + sage: B=p.get_backend() # optional - cvxopt + sage: x=p.get_values(x).values() # optional - cvxopt + sage: -(a3*B.dual_variable(0)).trace()-(b3*B.dual_variable(1)).trace() # tol 1e-07 # optional - cvxopt -3.0 - sage: g = sum((B.slack(j)*B.dual_variable(j)).trace() for j in range(2)); g # tol 1.5e-08 + sage: g = sum((B.slack(j)*B.dual_variable(j)).trace() for j in range(2)); g # tol 1.5e-08 # optional - cvxopt 0.0 TESTS:: - sage: B.dual_variable(7) + sage: B.dual_variable(7) # optional - cvxopt Traceback (most recent call last): ... IndexError: list index out of range - sage: abs(g - B._get_answer()['gap']) # tol 1e-22 + sage: abs(g - B._get_answer()['gap']) # tol 1e-22 # optional - cvxopt 0.0 """ @@ -322,33 +320,33 @@ cdef class CVXOPTSDPBackend(MatrixSDPBackend): EXAMPLES:: - sage: p = SemidefiniteProgram(maximization = False, solver='cvxopt') - sage: x = p.new_variable() - sage: p.set_objective(x[0] - x[1]) + sage: p = SemidefiniteProgram(maximization = False, solver='cvxopt') # optional - cvxopt + sage: x = p.new_variable() # optional - cvxopt + sage: p.set_objective(x[0] - x[1]) # optional - cvxopt sage: a1 = matrix([[1, 2.], [2., 3.]]) sage: a2 = matrix([[3, 4.], [4., 5.]]) sage: a3 = matrix([[5, 6.], [6., 7.]]) sage: b1 = matrix([[1, 1.], [1., 1.]]) sage: b2 = matrix([[2, 2.], [2., 2.]]) sage: b3 = matrix([[3, 3.], [3., 3.]]) - sage: p.add_constraint(a1*x[0] + a2*x[1] <= a3) - sage: p.add_constraint(b1*x[0] + b2*x[1] <= b3) - sage: p.solve() # tol 1e-08 + sage: p.add_constraint(a1*x[0] + a2*x[1] <= a3) # optional - cvxopt + sage: p.add_constraint(b1*x[0] + b2*x[1] <= b3) # optional - cvxopt + sage: p.solve() # tol 1e-08 # optional - cvxopt -3.0 - sage: B = p.get_backend() - sage: B1 = B.slack(1); B1 # tol 1e-08 + sage: B = p.get_backend() # optional - cvxopt + sage: B1 = B.slack(1); B1 # tol 1e-08 # optional - cvxopt [0.0 0.0] [0.0 0.0] - sage: B1.is_positive_definite() + sage: B1.is_positive_definite() # optional - cvxopt True - sage: x = sorted(p.get_values(x).values()) - sage: x[0]*b1 + x[1]*b2 - b3 + B1 # tol 1e-09 + sage: x = sorted(p.get_values(x).values()) # optional - cvxopt + sage: x[0]*b1 + x[1]*b2 - b3 + B1 # tol 1e-09 # optional - cvxopt [0.0 0.0] [0.0 0.0] TESTS:: - sage: B.slack(7) + sage: B.slack(7) # optional - cvxopt Traceback (most recent call last): ... IndexError: list index out of range @@ -379,11 +377,11 @@ cdef class CVXOPTSDPBackend(MatrixSDPBackend): EXAMPLES:: sage: from sage.numerical.backends.generic_sdp_backend import get_solver - sage: p = get_solver(solver = "CVXOPT") - sage: p.solver_parameter("show_progress") + sage: p = get_solver(solver = "CVXOPT") # optional - cvxopt + sage: p.solver_parameter("show_progress") # optional - cvxopt False - sage: p.solver_parameter("show_progress", True) - sage: p.solver_parameter("show_progress") + sage: p.solver_parameter("show_progress", True) # optional - cvxopt + sage: p.solver_parameter("show_progress") # optional - cvxopt True """ if value is None: diff --git a/src/sage/numerical/backends/generic_sdp_backend.pyx b/src/sage/numerical/backends/generic_sdp_backend.pyx index 1ac2b02edeb..1ad7bddc186 100644 --- a/src/sage/numerical/backends/generic_sdp_backend.pyx +++ b/src/sage/numerical/backends/generic_sdp_backend.pyx @@ -663,6 +663,10 @@ def default_sdp_solver(solver=None): except ValueError: pass + from warnings import warn + warn("default_sdp_solver set to 'Matrix' (MatrixSDPBackend), which can construct but not solve problems. Install cvxopt for actual solver functionality") + default_sdp_solver("Matrix") + if callable(solver): default_solver = solver return @@ -675,9 +679,12 @@ def default_sdp_solver(solver=None): default_solver = solver except ImportError: raise ValueError("CVXOPT is not available. Please refer to the documentation to install it.") + elif solver == "Matrix": + default_solver = solver else: - raise ValueError("'solver' should be set to 'CVXOPT', a class, or None.") + raise ValueError("'solver' should be set to 'CVXOPT', 'Matrix', a class, or None.") + cpdef GenericSDPBackend get_solver(solver=None, base_ring=None): """ @@ -729,6 +736,8 @@ cpdef GenericSDPBackend get_solver(solver=None, base_ring=None): if solver == "Cvxopt": from sage.numerical.backends.cvxopt_sdp_backend import CVXOPTSDPBackend return CVXOPTSDPBackend(base_ring=base_ring) - + elif solver == "Matrix": + from sage.numerical.backends.matrix_sdp_backend import MatrixSDPBackend + return MatrixSDPBackend(base_ring=base_ring) else: - raise ValueError("'solver' should be set to 'CVXOPT', a class, or None (in which case the default one is used).") + raise ValueError("'solver' should be set to 'CVXOPT', 'Matrix', a class, or None (in which case the default one is used).") diff --git a/src/sage/numerical/gauss_legendre.pyx b/src/sage/numerical/gauss_legendre.pyx index 1234405acaa..37df33b23ce 100644 --- a/src/sage/numerical/gauss_legendre.pyx +++ b/src/sage/numerical/gauss_legendre.pyx @@ -200,6 +200,51 @@ def estimate_error(results, prec, epsilon): e.append(D4.exp()) return max(e) +def integrate_vector_N(f, prec, N=3): + r""" + Integrate a one-argument vector-valued function numerically using Gauss-Legendre, + setting the number of nodes. + + This function uses the Gauss-Legendre quadrature scheme to approximate the + integral `\int_0^1 f(t) \, dt`. It is different from ``integrate_vector`` + by using a specific number of nodes rather than targeting a specified error + bound on the result. + + INPUT: + + - ``f`` -- callable. Vector-valued integrand. + + - ``prec`` -- integer. Binary precision to be used. + + - ``N`` -- integer (default: 3). Number of nodes to use. + + OUTPUT: + + Vector approximating value of the integral. + + EXAMPLES:: + + sage: from sage.numerical.gauss_legendre import integrate_vector_N + sage: prec = 100 + sage: K = RealField(prec) + sage: V = VectorSpace(K,1) + sage: f = lambda t: V([t]) + sage: integrate_vector_N(f, prec, 4) + (0.50000000000000000000000000000) + + .. NOTE:: + + The nodes and weights are calculated in the real field with ``prec`` + bits of precision. If the the vector space in which ``f`` takes values + is over a field which is incompatible with this field (e.g. a finite + field) then a TypeError occurs. + """ + nodelist = nodes(N, prec) + I = nodelist[0][1]*f(nodelist[0][0]) + for i in range(1,len(nodelist)): + I += nodelist[i][1]*f(nodelist[i][0]) + return I + def integrate_vector(f, prec, epsilon=None): r""" Integrate a one-argument vector-valued function numerically using Gauss-Legendre. diff --git a/src/sage/numerical/optimize.py b/src/sage/numerical/optimize.py index f09634c3bf2..7ac1829f3dc 100644 --- a/src/sage/numerical/optimize.py +++ b/src/sage/numerical/optimize.py @@ -11,6 +11,7 @@ ---------------------- """ +from sage.misc.superseded import deprecation from sage.modules.free_module_element import vector from sage.rings.real_double import RDF @@ -553,6 +554,9 @@ def linear_program(c, G, h, A=None, b=None, solver=None): - Maximize `-h'z - b'y` subject to `G'z + A'y + c = 0` and `z \geq 0`. + This function is deprecated. Use :class:`MixedIntegerLinearProgram` instead. + + This function depends on the optional package ``cvxopt``. INPUT: @@ -594,17 +598,20 @@ def linear_program(c, G, h, A=None, b=None, solver=None): sage: c=vector(RDF,[-4,-5]) sage: G=matrix(RDF,[[2,1],[1,2],[-1,0],[0,-1]]) sage: h=vector(RDF,[3,3,0,0]) - sage: sol=linear_program(c,G,h) - sage: sol['x'] + sage: sol=linear_program(c,G,h) # optional - cvxopt + doctest:warning... + DeprecationWarning: linear_program is deprecated; use MixedIntegerLinearProgram instead + See https://trac.sagemath.org/32226 for details. + sage: sol['x'] # optional - cvxopt (0.999..., 1.000...) Here we solve the same problem with 'glpk' interface to 'cvxopt':: - sage: sol=linear_program(c,G,h,solver='glpk') + sage: sol=linear_program(c,G,h,solver='glpk') # optional - cvxopt GLPK Simplex Optimizer... ... OPTIMAL LP SOLUTION FOUND - sage: sol['x'] + sage: sol['x'] # optional - cvxopt (1.0, 1.0) Next, we maximize `x+y-50` subject to `50x + 24y \leq 2400`, @@ -613,15 +620,17 @@ def linear_program(c, G, h, A=None, b=None, solver=None): sage: v=vector([-1.0,-1.0,-1.0]) sage: m=matrix([[50.0,24.0,0.0],[30.0,33.0,0.0],[-1.0,0.0,0.0],[0.0,-1.0,0.0],[0.0,0.0,1.0],[0.0,0.0,-1.0]]) sage: h=vector([2400.0,2100.0,-45.0,-5.0,1.0,-1.0]) - sage: sol=linear_program(v,m,h) - sage: sol['x'] + sage: sol=linear_program(v,m,h) # optional - cvxopt + sage: sol['x'] # optional - cvxopt (45.000000..., 6.2499999..., 1.00000000...) - sage: sol=linear_program(v,m,h,solver='glpk') + sage: sol=linear_program(v,m,h,solver='glpk') # optional - cvxopt GLPK Simplex Optimizer... OPTIMAL LP SOLUTION FOUND - sage: sol['x'] + sage: sol['x'] # optional - cvxopt (45.0..., 6.25..., 1.0...) """ + deprecation(32226, 'linear_program is deprecated; use MixedIntegerLinearProgram instead') + from cvxopt.base import matrix as m from cvxopt import solvers solvers.options['show_progress']=False diff --git a/src/sage/numerical/sdp.pyx b/src/sage/numerical/sdp.pyx index e7c67a34a13..452a058aa6e 100644 --- a/src/sage/numerical/sdp.pyx +++ b/src/sage/numerical/sdp.pyx @@ -1,5 +1,5 @@ r""" -SemiDefinite Programming +Semidefinite Programming A semidefinite program (:wikipedia:`SDP `) is an optimization problem (:wikipedia:`Optimization_(mathematics)>`) @@ -72,17 +72,17 @@ The following example shows all these steps:: sage: p.add_constraint(a1*x[0] + a2*x[1] <= a3) sage: p.add_constraint(b1*x[0] + b2*x[1] <= b3) sage: p.add_constraint(c1*x[0] + c2*x[1] >= matrix.zero(2,2,sparse=True)) - sage: p.solver_parameter("show_progress", True) - sage: opt = p.solve() + sage: p.solver_parameter("show_progress", True) # optional - cvxopt + sage: opt = p.solve() # optional - cvxopt pcost dcost gap pres dres k/t 0: ... ... Optimal solution found. - sage: print('Objective Value: {}'.format(N(opt,3))) + sage: print('Objective Value: {}'.format(N(opt,3))) # optional - cvxopt Objective Value: 1.0 - sage: [N(x, 3) for x in sorted(p.get_values(x).values())] + sage: [N(x, 3) for x in sorted(p.get_values(x).values())] # optional - cvxopt [3.0e-8, 1.0] - sage: p.show() + sage: p.show() # optional - cvxopt Maximization: x_0 - x_1 Constraints: @@ -97,20 +97,20 @@ of primal and dual problems. Thus we can get the optimizer `X` of the dual probl as follows, as diagonal blocks, one per each constraint, via :meth:`~SemidefiniteProgram.dual_variable`. E.g.:: - sage: p.dual_variable(1) # rel tol 2e-03 + sage: p.dual_variable(1) # rel tol 2e-03 # optional - cvxopt [ 85555.0 -85555.0] [-85555.0 85555.0] We can see that the optimal value of the dual is equal (up to numerical noise) to `opt`.:: - sage: opt-((p.dual_variable(0)*a3).trace()+(p.dual_variable(1)*b3).trace()) # tol 8e-08 + sage: opt-((p.dual_variable(0)*a3).trace()+(p.dual_variable(1)*b3).trace()) # tol 8e-08 # optional - cvxopt 0.0 Dual variable blocks at optimality are orthogonal to "slack variables", that is, matrices `C-\sum_k x_k A_k`, cf. (Primal problem) above, available via :meth:`~SemidefiniteProgram.slack`. E.g.:: - sage: (p.slack(0)*p.dual_variable(0)).trace() # tol 2e-07 + sage: (p.slack(0)*p.dual_variable(0)).trace() # tol 2e-07 # optional - cvxopt 0.0 @@ -122,21 +122,21 @@ More interesting example, the :func:`Lovasz theta =SemidefiniteProgram() sage: p.add_constraint((1/7)*matrix.identity(7)>=-y[0]*c2-y[1]*c3) sage: p.set_objective(y[0]*(c2**2).trace()+y[1]*(c3**2).trace()) - sage: x=p.solve(); x+1 + sage: x=p.solve(); x+1 # optional - cvxopt 3.31766... Unlike in the previous example, the slack variable is very far from 0:: - sage: p.slack(0).trace() # tol 1e-14 + sage: p.slack(0).trace() # tol 1e-14 # optional - cvxopt 1.0 The default CVXOPT backend computes with the Real Double Field, for example:: - sage: p = SemidefiniteProgram(solver='cvxopt') - sage: p.base_ring() + sage: p = SemidefiniteProgram(solver='cvxopt') # optional - cvxopt + sage: p.base_ring() # optional - cvxopt Real Double Field - sage: x = p.new_variable() - sage: 0.5 + 3/2*x[1] + sage: x = p.new_variable() # optional - cvxopt + sage: 0.5 + 3/2*x[1] # optional - cvxopt 0.5 + 1.5*x_0 For representing an SDP with exact data, there is another backend:: @@ -275,7 +275,7 @@ cdef class SemidefiniteProgram(SageObject): Computation of a basic Semidefinite Program:: - sage: p = SemidefiniteProgram(solver = "cvxopt", maximization=False) + sage: p = SemidefiniteProgram(maximization=False) sage: x = p.new_variable() sage: p.set_objective(x[0] - x[1]) sage: a1 = matrix([[1, 2.], [2., 3.]]) @@ -286,7 +286,7 @@ cdef class SemidefiniteProgram(SageObject): sage: b3 = matrix([[3, 3.], [3., 3.]]) sage: p.add_constraint(a1*x[0] + a2*x[1] <= a3) sage: p.add_constraint(b1*x[0] + b2*x[1] <= b3) - sage: N(p.solve(), 2) + sage: N(p.solve(), 2) # optional - cvxopt -3.0 """ @@ -726,25 +726,21 @@ cdef class SemidefiniteProgram(SageObject): sage: b3 = matrix([[3, 3.], [3., 3.]]) sage: p.add_constraint(a1*x[3] + a2*x[5] <= a3) sage: p.add_constraint(b1*x[3] + b2*x[5] <= b3) - sage: N(p.solve(),3) + sage: N(p.solve(),3) # optional - cvxopt -3.0 To return the optimal value of ``x[3]``:: - sage: N(p.get_values(x[3]),3) + sage: N(p.get_values(x[3]),3) # optional - cvxopt -1.0 To get a dictionary identical to ``x`` containing optimal values for the corresponding variables :: - sage: x_sol = p.get_values(x) - sage: sorted(x_sol) + sage: x_sol = p.get_values(x) # optional - cvxopt + sage: sorted(x_sol) # optional - cvxopt [3, 5] - Obviously, it also works with variables of higher dimension:: - - sage: x_sol = p.get_values(x) - """ val = [] for l in lists: @@ -801,10 +797,10 @@ cdef class SemidefiniteProgram(SageObject): sage: a2 = matrix([[1,1],[1,1]]) sage: a3 = matrix([[1,-1],[-1,1]]) sage: p.add_constraint(a1*x[1]+a2*x[2] <= a3) - sage: N(p.solve(),digits=3) + sage: N(p.solve(),digits=3) # optional - cvxopt 16.2 sage: p.set_objective(None) - sage: _ = p.solve() + sage: _ = p.solve() # optional - cvxopt """ cdef list values = [] @@ -860,7 +856,7 @@ cdef class SemidefiniteProgram(SageObject): sage: a2 = matrix([[1,1],[1,1]]) sage: a3 = matrix([[1,-1],[-1,1]]) sage: p.add_constraint(a1*x[1]+a2*x[2] <= a3) - sage: N(p.solve(),digits=3) + sage: N(p.solve(),digits=3) # optional - cvxopt 16.2 One can also define double-bounds or equality using the symbol @@ -873,14 +869,14 @@ cdef class SemidefiniteProgram(SageObject): sage: a2 = matrix([[1,1],[1,1]]) sage: a3 = matrix([[1,-1],[-1,1]]) sage: p.add_constraint(a3 >= a1*x[1] + a2*x[2]) - sage: N(p.solve(),digits=3) + sage: N(p.solve(),digits=3) # optional - cvxopt 16.2 TESTS: Complex constraints:: - sage: p = SemidefiniteProgram(solver = "cvxopt") + sage: p = SemidefiniteProgram() sage: b = p.new_variable() sage: a1 = matrix([[1,2],[2,3]]) sage: a2 = matrix([[1,-2],[-2,4]]) @@ -895,7 +891,7 @@ cdef class SemidefiniteProgram(SageObject): Empty constraint:: - sage: p=SemidefiniteProgram() + sage: p = SemidefiniteProgram() sage: p.add_constraint(sum([])) @@ -942,7 +938,7 @@ cdef class SemidefiniteProgram(SageObject): The SDP from the header of this module:: - sage: p = SemidefiniteProgram(solver = "cvxopt", maximization = False) + sage: p = SemidefiniteProgram(maximization=False) sage: x = p.new_variable() sage: p.set_objective(x[0] - x[1]) sage: a1 = matrix([[1, 2.], [2., 3.]]) @@ -953,12 +949,12 @@ cdef class SemidefiniteProgram(SageObject): sage: b3 = matrix([[3, 3.], [3., 3.]]) sage: p.add_constraint(a1*x[0] + a2*x[1] <= a3) sage: p.add_constraint(b1*x[0] + b2*x[1] <= b3) - sage: N(p.solve(),4) + sage: N(p.solve(),4) # optional - cvxopt -11. - sage: x = p.get_values(x) - sage: N(x[0],4) + sage: x = p.get_values(x) # optional - cvxopt + sage: N(x[0],4) # optional - cvxopt -8.0 - sage: N(x[1],4) + sage: N(x[1],4) # optional - cvxopt 3.0 """ self._backend.solve() @@ -994,20 +990,20 @@ cdef class SemidefiniteProgram(SageObject): sage: b3 = matrix([[3, 3.], [3., 3.]]) sage: p.add_constraint(a1*x[0] + a2*x[1] <= a3) sage: p.add_constraint(b1*x[0] + b2*x[1] <= b3) - sage: p.solve() # tol 1e-08 + sage: p.solve() # tol 1e-08 # optional - cvxopt -3.0 - sage: x = p.get_values(x).values() - sage: -(a3*p.dual_variable(0)).trace()-(b3*p.dual_variable(1)).trace() # tol 1e-07 + sage: x = p.get_values(x).values() # optional - cvxopt + sage: -(a3*p.dual_variable(0)).trace()-(b3*p.dual_variable(1)).trace() # tol 1e-07 # optional - cvxopt -3.0 Dual variable is orthogonal to the slack :: - sage: g = sum((p.slack(j)*p.dual_variable(j)).trace() for j in range(2)); g # tol 1.2e-08 + sage: g = sum((p.slack(j)*p.dual_variable(j)).trace() for j in range(2)); g # tol 1.2e-08 # optional - cvxopt 0.0 TESTS:: - sage: p.dual_variable(7) + sage: p.dual_variable(7) # optional - cvxopt Traceback (most recent call last): ... IndexError: list index out of range @@ -1041,21 +1037,21 @@ cdef class SemidefiniteProgram(SageObject): sage: b3 = matrix([[3, 3.], [3., 3.]]) sage: p.add_constraint(a1*x[0] + a2*x[1] <= a3) sage: p.add_constraint(b1*x[0] + b2*x[1] <= b3) - sage: p.solve() # tol 1e-08 + sage: p.solve() # tol 1e-08 # optional - cvxopt -3.0 - sage: B1 = p.slack(1); B1 # tol 1e-08 + sage: B1 = p.slack(1); B1 # tol 1e-08 # optional - cvxopt [0.0 0.0] [0.0 0.0] - sage: B1.is_positive_definite() + sage: B1.is_positive_definite() # optional - cvxopt True - sage: x = sorted(p.get_values(x).values()) - sage: x[0]*b1 + x[1]*b2 - b3 + B1 # tol 1e-09 + sage: x = sorted(p.get_values(x).values()) # optional - cvxopt + sage: x[0]*b1 + x[1]*b2 - b3 + B1 # tol 1e-09 # optional - cvxopt [0.0 0.0] [0.0 0.0] TESTS:: - sage: p.slack(7) + sage: p.slack(7) # optional - cvxopt Traceback (most recent call last): ... IndexError: list index out of range @@ -1082,20 +1078,20 @@ cdef class SemidefiniteProgram(SageObject): EXAMPLES:: - sage: p. = SemidefiniteProgram(solver = "cvxopt", maximization = False) - sage: p.solver_parameter("show_progress", True) - sage: p.solver_parameter("show_progress") + sage: p. = SemidefiniteProgram(solver = "cvxopt", maximization = False) # optional - cvxopt + sage: p.solver_parameter("show_progress", True) # optional - cvxopt + sage: p.solver_parameter("show_progress") # optional - cvxopt True - sage: p.set_objective(x[0] - x[1]) + sage: p.set_objective(x[0] - x[1]) # optional - cvxopt sage: a1 = matrix([[1, 2.], [2., 3.]]) sage: a2 = matrix([[3, 4.], [4., 2.]]) sage: a3 = matrix([[5, 6.], [6., 7.]]) sage: b1 = matrix([[1, 1.], [1., 1.]]) sage: b2 = matrix([[2, 2.], [2., 1.]]) sage: b3 = matrix([[3, 3.], [3., 3.]]) - sage: p.add_constraint(a1*x[0] + a2*x[1] <= a3) - sage: p.add_constraint(b1*x[0] + b2*x[1] <= b3) - sage: N(p.solve(),4) + sage: p.add_constraint(a1*x[0] + a2*x[1] <= a3) # optional - cvxopt + sage: p.add_constraint(b1*x[0] + b2*x[1] <= b3) # optional - cvxopt + sage: N(p.solve(),4) # optional - cvxopt pcost dcost gap pres dres k/t 0: 1... ... @@ -1183,13 +1179,13 @@ class SDPSolverException(RuntimeError): No solution:: - sage: p = SemidefiniteProgram(solver="cvxopt") - sage: x = p.new_variable() - sage: p.set_objective(x[0]) + sage: p = SemidefiniteProgram(solver="cvxopt") # optional - cvxopt + sage: x = p.new_variable() # optional - cvxopt + sage: p.set_objective(x[0]) # optional - cvxopt sage: a = matrix([[1,2],[2,4]]) sage: b = matrix([[1,9],[9,4]]) - sage: p.add_constraint( a*x[0] == b ) - sage: p.solve() + sage: p.add_constraint( a*x[0] == b ) # optional - cvxopt + sage: p.solve() # optional - cvxopt Traceback (most recent call last): ... SDPSolverException: ... diff --git a/src/sage/repl/interpreter.py b/src/sage/repl/interpreter.py index f3234b2000d..71dbe429fdd 100644 --- a/src/sage/repl/interpreter.py +++ b/src/sage/repl/interpreter.py @@ -154,20 +154,6 @@ from IPython.core.crashhandler import CrashHandler -def embedded(): - """ - Return True if Sage is being run from the notebook. - - EXAMPLES:: - - sage: from sage.repl.interpreter import embedded - sage: embedded() - False - """ - import sage.server.support - return sage.server.support.EMBEDDED_MODE - - # TODO: This global variable _do_preparse should be associated with an # IPython InteractiveShell as opposed to a global variable in this # module. diff --git a/src/sage/repl/preparse.py b/src/sage/repl/preparse.py index 77a9aa88bda..e5ac7f7ab73 100644 --- a/src/sage/repl/preparse.py +++ b/src/sage/repl/preparse.py @@ -1745,7 +1745,7 @@ def preparse(line, reset=True, do_time=False, ignore_prompts=False, 'a * BackslashOperator() * b \\' sage: preparse("time R. = ZZ[]", do_time=True) - '__time__=misc.cputime(); __wall__=misc.walltime(); R = ZZ[\'x\']; print("Time: CPU %.2f s, Wall: %.2f s"%(misc.cputime(__time__), misc.walltime(__wall__))); (x,) = R._first_ngens(1)' + '__time__=cputime(); __wall__=walltime(); R = ZZ[\'x\']; print("Time: CPU %.2f s, Wall: %.2f s"%(cputime(__time__), walltime(__wall__))); (x,) = R._first_ngens(1)' TESTS: @@ -1838,8 +1838,8 @@ def preparse(line, reset=True, do_time=False, ignore_prompts=False, if do_time: # Time keyword L = re.sub(r';time;(\s*)(\S[^;]*)', - r';\1__time__=misc.cputime(); __wall__=misc.walltime(); \2; print(' + - '"Time: CPU %%.2f s, Wall: %%.2f s"%%(misc.cputime(__time__), misc.walltime(__wall__)))', + r';\1__time__=cputime(); __wall__=walltime(); \2; print(' + + '"Time: CPU %%.2f s, Wall: %%.2f s"%%(cputime(__time__), walltime(__wall__)))', L) # Remove extra ;'s diff --git a/src/sage/rings/asymptotic/term_monoid.py b/src/sage/rings/asymptotic/term_monoid.py index 81e40c4e61e..51ddd16dd1d 100644 --- a/src/sage/rings/asymptotic/term_monoid.py +++ b/src/sage/rings/asymptotic/term_monoid.py @@ -2906,6 +2906,59 @@ def _repr_(self): return 'Term with coefficient %s and growth %s' % \ (self.coefficient, self.growth) + def _repr_product_(self, latex=False): + r""" + A representation string for this term with coefficient as a product. + + INPUT: + + - ``latex`` -- (default: ``False``) a boolean. If set, then + LaTeX-output is returned. + + OUTPUT: + + A string + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermWithCoefficientMonoid + sage: from sage.rings.asymptotic.term_monoid import TermMonoidFactory + sage: TermMonoid = TermMonoidFactory('__main__.TermMonoid') + + sage: G = GrowthGroup('x^ZZ'); x = G.gen() + sage: T = TermWithCoefficientMonoid(TermMonoid, G, ZZ) + sage: T(x^2, 5)._repr_product_() + '5*x^2' + """ + if latex: + from sage.misc.latex import latex as latex_repr + f = latex_repr + else: + f = repr + + g = f(self.growth) + c = f(self.coefficient) + + if g == '1': + return c + elif c == '1': + return '{g}'.format(g=g) + elif c == '-1': + return '-{g}'.format(g=g) + elif self.coefficient._is_atomic() or (-self.coefficient)._is_atomic(): + # note that -pi/2 is not atomic, but -5 is. As subtractions are handled + # in the asymptotic ring, we ignore such non-atomicity. + s = '{c} {g}' if latex else '{c}*{g}' + else: + s = r'\left({c}\right) {g}' if latex else '({c})*{g}' + s = s.format(c=c, g=g) + + if latex: + import re + s = re.sub(r'([0-9])\s+([0-9])', r'\1 \\cdot \2', s) + return s + def _mul_(self, other): r""" Multiplication method for asymptotic terms with coefficients. @@ -3337,34 +3390,7 @@ def _repr_(self, latex=False): sage: (1+a)/n (a + 1)*n^(-1) """ - if latex: - from sage.misc.latex import latex as latex_repr - f = latex_repr - else: - f = repr - - g = f(self.growth) - c = f(self.coefficient) - - if g == '1': - return c - elif c == '1': - return '{g}'.format(g=g) - elif c == '-1': - return '-{g}'.format(g=g) - elif self.coefficient._is_atomic() or (-self.coefficient)._is_atomic(): - # note that -pi/2 is not atomic, but -5 is. As subtractions are handled - # in the asymptotic ring, we ignore such non-atomicity. - s = '{c} {g}' if latex else '{c}*{g}' - else: - s = r'\left({c}\right) {g}' if latex else '({c})*{g}' - s = s.format(c=c, g=g) - - if latex: - import re - s = re.sub(r'([0-9])\s+([0-9])', r'\1 \\cdot \2', s) - - return s + return self._repr_product_(latex=latex) def _latex_(self): r""" diff --git a/src/sage/rings/finite_rings/integer_mod.pyx b/src/sage/rings/finite_rings/integer_mod.pyx index 03d7838b4a5..5e406272e1d 100644 --- a/src/sage/rings/finite_rings/integer_mod.pyx +++ b/src/sage/rings/finite_rings/integer_mod.pyx @@ -1387,6 +1387,11 @@ cdef class IntegerMod_abstract(FiniteRingElement): sage: mod(-1, 4489).nth_root(2, all=True) [] + We check that :trac:`32084` is fixed:: + + sage: mod(24, 25).nth_root(50)^50 + 24 + Check that the code path cunningham might be used:: sage: a = Mod(9,11) @@ -1504,7 +1509,8 @@ cdef class IntegerMod_abstract(FiniteRingElement): if self == 1: if all: return [s*K(p*k+m.lift()) for k in range(p**(k-(2 if p==2 else 1))) for m in modp for s in sign] - else: return self_orig + else: + return K(modp.lift()) else: if all: return [] else: raise ValueError("no nth root") diff --git a/src/sage/rings/integer_ring.pyx b/src/sage/rings/integer_ring.pyx index 2d4aa4eb8af..02560020601 100644 --- a/src/sage/rings/integer_ring.pyx +++ b/src/sage/rings/integer_ring.pyx @@ -709,8 +709,43 @@ cdef class IntegerRing_class(PrincipalIdealDomain): sage: ZZ.random_element(11.0, distribution="gaussian") 5 + TESTS: + + Check that :trac:`32124` is fixed:: + + sage: ZZ.random_element(5, -5, distribution="1/n").parent() is ZZ + True + sage: ZZ.random_element(5, -5, distribution="gaussian").parent() is ZZ + True + sage: ZZ.random_element(5, -5, distribution="mpz_rrandomb").parent() is ZZ + True + + sage: ZZ.random_element(-10, -5, distribution="mpz_rrandomb") + Traceback (most recent call last): + ... + TypeError: x must be > 0 + sage: ZZ.random_element(-10, -5, distribution="gaussian") + Traceback (most recent call last): + ... + TypeError: x must be > 0 + + Checking error messages:: + + sage: ZZ.random_element(-3) + Traceback (most recent call last): + ... + TypeError: x must be > 0 + sage: ZZ.random_element(4, 2) + Traceback (most recent call last): + ... + TypeError: x must be < y """ cdef Integer z = Integer.__new__(Integer) + if distribution == "1/n": + x = None + y = None + elif distribution == "mpz_rrandomb" or distribution == "gaussian": + y = None if x is not None and y is None and x <= 0: raise TypeError("x must be > 0") if x is not None and y is not None and x >= y: diff --git a/src/sage/rings/number_field/number_field.py b/src/sage/rings/number_field/number_field.py index 03d38c67168..928b0efcfe2 100644 --- a/src/sage/rings/number_field/number_field.py +++ b/src/sage/rings/number_field/number_field.py @@ -134,7 +134,6 @@ import sage.rings.ring from sage.misc.latex import latex_variable_name -from sage.misc.misc import union from .unit_group import UnitGroup from .class_group import ClassGroup @@ -10047,7 +10046,7 @@ def hilbert_conductor(self,a,b): """ a, b = self(a), self(b) d = self.ideal(1) - for p in union(union( self.ideal(2).prime_factors(), self.ideal(a).prime_factors()), self.ideal(b).prime_factors()): + for p in set(self.ideal(2).prime_factors()).union(self.ideal(a).prime_factors()).union(self.ideal(b).prime_factors()): if self.hilbert_symbol(a,b,p) == -1: d *= p return d diff --git a/src/sage/rings/number_field/number_field_ideal.py b/src/sage/rings/number_field/number_field_ideal.py index da9c90f3b2f..54d7ebb1535 100644 --- a/src/sage/rings/number_field/number_field_ideal.py +++ b/src/sage/rings/number_field/number_field_ideal.py @@ -2196,8 +2196,8 @@ def invertible_residues(self, reduce=True): Check that the integrality is not lost, cf. :trac:`30801`:: sage: K. = NumberField(x^2 + x + 1) - sage: list(K.ideal(8).invertible_residues())[:5] - [1, a - 1, -3*a, -2*a + 3, -a - 1] + sage: all(x.is_integral() for x in K.ideal(8).invertible_residues()) + True AUTHOR: John Cremona """ diff --git a/src/sage/rings/padics/CA_template.pxi b/src/sage/rings/padics/CA_template.pxi index 809b24116f5..5a3065ff10a 100644 --- a/src/sage/rings/padics/CA_template.pxi +++ b/src/sage/rings/padics/CA_template.pxi @@ -1,18 +1,20 @@ -""" +r""" Capped absolute template for complete discrete valuation rings In order to use this template you need to write a linkage file and gluing file. -For an example see mpz_linkage.pxi (linkage file) and padic_capped_absolute_element.pyx (gluing file). +For an example see ``mpz_linkage.pxi`` (linkage file) and +``padic_capped_absolute_element.pyx`` (gluing file). -The linkage file implements a common API that is then used in the class CAElement defined here. -See the documentation of mpz_linkage.pxi for the functions needed. +The linkage file implements a common API that is then used in the class +:class:`CAElement` defined here. +See the documentation of ``mpz_linkage.pxi`` for the functions needed. The gluing file does the following: -- ctypedef's celement to be the appropriate type (e.g. mpz_t) +- ctypedef's celement to be the appropriate type (e.g. ``mpz_t``) - includes the linkage file - includes this template -- defines a concrete class inheriting from CAElement, and implements +- defines a concrete class inheriting from :class:`CAElement`, and implements any desired extra methods AUTHORS: @@ -93,7 +95,7 @@ cdef class CAElement(pAdicTemplateElement): cdef CAElement _new_c(self): """ - Creates a new element with the same basic info. + Create a new element with the same basic info. TESTS:: @@ -118,7 +120,7 @@ cdef class CAElement(pAdicTemplateElement): cdef pAdicTemplateElement _new_with_value(self, celement value, long absprec): """ - Creates a new element with a given value and absolute precision. + Create a new element with a given value and absolute precision. Used by code that doesn't know the precision type. """ @@ -130,13 +132,13 @@ cdef class CAElement(pAdicTemplateElement): cdef int _get_unit(self, celement value) except -1: """ - Sets ``value`` to the unit of this p-adic element. + Set ``value`` to the unit of this p-adic element. """ cremove(value, self.value, self.absprec, self.prime_pow, True) cdef int check_preccap(self) except -1: """ - Checks that this element doesn't have precision higher than + Check that this element doesn't have precision higher than allowed by the precision cap. TESTS:: @@ -144,10 +146,10 @@ cdef class CAElement(pAdicTemplateElement): sage: ZpCA(5)(1).lift_to_precision(30) # indirect doctest Traceback (most recent call last): ... - PrecisionError: Precision higher than allowed by the precision cap. + PrecisionError: precision higher than allowed by the precision cap """ if self.absprec > self.prime_pow.ram_prec_cap: - raise PrecisionError("Precision higher than allowed by the precision cap.") + raise PrecisionError("precision higher than allowed by the precision cap") def __copy__(self): """ @@ -270,7 +272,9 @@ cdef class CAElement(pAdicTemplateElement): sage: ~R(5) * 5 1 + O(17^20) sage: ~R(5) - 7 + 3*17 + 10*17^2 + 13*17^3 + 6*17^4 + 3*17^5 + 10*17^6 + 13*17^7 + 6*17^8 + 3*17^9 + 10*17^10 + 13*17^11 + 6*17^12 + 3*17^13 + 10*17^14 + 13*17^15 + 6*17^16 + 3*17^17 + 10*17^18 + 13*17^19 + O(17^20) + 7 + 3*17 + 10*17^2 + 13*17^3 + 6*17^4 + 3*17^5 + 10*17^6 + 13*17^7 + + 6*17^8 + 3*17^9 + 10*17^10 + 13*17^11 + 6*17^12 + 3*17^13 + + 10*17^14 + 13*17^15 + 6*17^16 + 3*17^17 + 10*17^18 + 13*17^19 + O(17^20) sage: ~R(-1) == R(-1) #indirect doctest True """ @@ -503,7 +507,7 @@ cdef class CAElement(pAdicTemplateElement): else: if not exact_exp and self.absprec > 0: raise ValueError("in order to raise to a p-adic exponent, base must be a unit") - raise PrecisionError("Need more precision") + raise PrecisionError("need more precision") else: val = self.valuation_c() if exact_exp: @@ -529,7 +533,7 @@ cdef class CAElement(pAdicTemplateElement): return ans cdef pAdicTemplateElement _lshift_c(self, long shift): - """ + r""" Multiplies by `\pi^{\mbox{shift}}`. Negative shifts may truncate the result. @@ -561,7 +565,7 @@ cdef class CAElement(pAdicTemplateElement): return ans cdef pAdicTemplateElement _rshift_c(self, long shift): - """ + r""" Divides by ``π^{\mbox{shift}}``. Positive shifts may truncate the result. @@ -594,7 +598,7 @@ cdef class CAElement(pAdicTemplateElement): def add_bigoh(self, absprec): """ - Returns a new element with absolute precision decreased to + Return a new element with absolute precision decreased to ``absprec``. The precision never increases. INPUT: @@ -652,8 +656,8 @@ cdef class CAElement(pAdicTemplateElement): cpdef bint _is_exact_zero(self) except -1: """ - Tests whether this element is an exact zero, which is always - False for capped absolute elements. + Test whether this element is an exact zero, which is always + ``False`` for capped absolute elements. This function exists for compatibility with capped relative elements. @@ -667,7 +671,7 @@ cdef class CAElement(pAdicTemplateElement): cpdef bint _is_inexact_zero(self) except -1: """ - Determines whether this element is indistinguishable from + Determine whether this element is indistinguishable from zero. EXAMPLES:: @@ -684,7 +688,7 @@ cdef class CAElement(pAdicTemplateElement): def is_zero(self, absprec = None): r""" - Determines whether this element is zero modulo + Determine whether this element is zero modulo `\pi^{\mbox{absprec}}`. If ``absprec is None``, returns ``True`` if this element is @@ -706,23 +710,23 @@ cdef class CAElement(pAdicTemplateElement): sage: R(17^6).is_zero(absprec=10) Traceback (most recent call last): ... - PrecisionError: Not enough precision to determine if element is zero + PrecisionError: not enough precision to determine if element is zero """ if absprec is infinity: - raise PrecisionError("Not enough precision to determine if element is zero") + raise PrecisionError("not enough precision to determine if element is zero") cdef bint iszero = ciszero(self.value, self.prime_pow) if absprec is None: return iszero cdef long val = self.valuation_c() if isinstance(absprec, int): if iszero and absprec > self.absprec: - raise PrecisionError("Not enough precision to determine if element is zero") + raise PrecisionError("not enough precision to determine if element is zero") return val >= absprec if not isinstance(absprec, Integer): absprec = Integer(absprec) if iszero: if mpz_cmp_si((absprec).value, val) > 0: - raise PrecisionError("Not enough precision to determine if element is zero") + raise PrecisionError("not enough precision to determine if element is zero") else: return True return mpz_cmp_si((absprec).value, val) <= 0 @@ -745,7 +749,7 @@ cdef class CAElement(pAdicTemplateElement): def is_equal_to(self, _right, absprec=None): r""" - Determines whether the inputs are equal modulo + Determine whether the inputs are equal modulo `\pi^{\mbox{absprec}}`. INPUT: @@ -768,10 +772,10 @@ cdef class CAElement(pAdicTemplateElement): sage: R(13).is_equal_to(R(13+2^10),absprec=10) Traceback (most recent call last): ... - PrecisionError: Elements not known to enough precision + PrecisionError: elements not known to enough precision """ if absprec is infinity: - raise PrecisionError("Elements not known to enough precision") + raise PrecisionError("elements not known to enough precision") cdef CAElement right cdef long aprec, rprec, sval, rval if self.parent() is _right.parent(): @@ -787,10 +791,10 @@ cdef class CAElement(pAdicTemplateElement): if mpz_sgn((absprec).value) < 0: return True else: - raise PrecisionError("Elements not known to enough precision") + raise PrecisionError("elements not known to enough precision") aprec = mpz_get_si((absprec).value) if aprec > self.absprec or aprec > right.absprec: - raise PrecisionError("Elements not known to enough precision") + raise PrecisionError("elements not known to enough precision") return ccmp(self.value, right.value, aprec, aprec < self.absprec, aprec < right.absprec, self.prime_pow) == 0 cdef int _cmp_units(self, pAdicGenericElement _right) except -2: @@ -811,7 +815,7 @@ cdef class CAElement(pAdicTemplateElement): cdef pAdicTemplateElement lift_to_precision_c(self, long absprec): """ - Returns an arbitrary lift of this element to higher precision. + Return an arbitrary lift of this element to higher precision. If ``absprec`` is less than the absolute precision of this element this function will return the input element. @@ -819,7 +823,7 @@ cdef class CAElement(pAdicTemplateElement): INPUT: - ``absprec`` -- an integer, at most the precision cap of the - parent. + parent EXAMPLES:: @@ -860,7 +864,7 @@ cdef class CAElement(pAdicTemplateElement): def _teichmuller_set_unsafe(self): """ - Sets this element to the Teichmuller representative with the + Set this element to the Teichmuller representative with the same residue. .. WARNING:: @@ -942,7 +946,7 @@ cdef class CAElement(pAdicTemplateElement): INPUT: - - ``var`` -- string, the variable name for the polynomial + - ``var`` -- string; the variable name for the polynomial EXAMPLES:: @@ -992,7 +996,7 @@ cdef class CAElement(pAdicTemplateElement): cpdef pAdicTemplateElement unit_part(CAElement self): r""" - Returns the unit part of this element. + Return the unit part of this element. EXAMPLES:: @@ -1012,7 +1016,7 @@ cdef class CAElement(pAdicTemplateElement): cdef long valuation_c(self): """ - Returns the valuation of this element. + Return the valuation of this element. TESTS:: @@ -1039,8 +1043,8 @@ cdef class CAElement(pAdicTemplateElement): return cvaluation(self.value, self.absprec, self.prime_pow) cpdef val_unit(self): - """ - Returns a 2-tuple, the first element set to the valuation of this + r""" + Return a 2-tuple, the first element set to the valuation of this element, and the second to the unit part of this element. For a zero element, the unit part is ``O(p^0)``. @@ -1213,7 +1217,7 @@ cdef class pAdicCoercion_ZZ_CA(RingHomomorphism): def section(self): """ - Returns a map back to the ring of integers that approximates an element + Return a map back to the ring of integers that approximates an element by an integer. EXAMPLES:: @@ -1517,7 +1521,7 @@ cdef class pAdicCoercion_CA_frac_field(RingHomomorphism): def section(self): """ - Returns a map back to the ring that converts elements of + Return a map back to the ring that converts elements of non-negative valuation. EXAMPLES:: @@ -1627,8 +1631,8 @@ cdef class pAdicCoercion_CA_frac_field(RingHomomorphism): cdef class pAdicConvert_CA_frac_field(Morphism): - """ - The section of the inclusion from `\ZZ_q`` to its fraction field. + r""" + The section of the inclusion from `\ZZ_q` to its fraction field. EXAMPLES:: @@ -1794,19 +1798,19 @@ cdef class pAdicConvert_CA_frac_field(Morphism): Morphism._update_slots(self, _slots) def unpickle_cae_v2(cls, parent, value, absprec): - """ + r""" Unpickle capped absolute elements. INPUT: - - ``cls`` -- the class of the capped absolute element. + - ``cls`` -- the class of the capped absolute element - - ``parent`` -- the parent, a `p`-adic ring + - ``parent`` -- a `p`-adic ring - ``value`` -- a Python object wrapping a celement, of the kind - accepted by the cunpickle function. + accepted by the cunpickle function - - ``absprec`` -- a Python int or Sage integer. + - ``absprec`` -- a Python int or Sage integer EXAMPLES:: @@ -1827,3 +1831,4 @@ def unpickle_cae_v2(cls, parent, value, absprec): cunpickle(ans.value, value, ans.prime_pow) ans.absprec = absprec return ans + diff --git a/src/sage/rings/padics/CR_template.pxi b/src/sage/rings/padics/CR_template.pxi index 5424be0f500..f69c7529781 100644 --- a/src/sage/rings/padics/CR_template.pxi +++ b/src/sage/rings/padics/CR_template.pxi @@ -68,7 +68,7 @@ cdef inline int assert_nonzero(CRElement x) except -1: if exactzero(x.ordp): raise ZeroDivisionError("cannot divide by zero") if x.relprec == 0: - raise PrecisionError("cannot divide by something indistinguishable from zero.") + raise PrecisionError("cannot divide by something indistinguishable from zero") cdef class CRElement(pAdicTemplateElement): cdef int _set(self, x, long val, long xprec, absprec, relprec) except -1: @@ -232,10 +232,10 @@ cdef class CRElement(pAdicTemplateElement): sage: Zp(5)(1).lift_to_precision(30) Traceback (most recent call last): ... - PrecisionError: Precision higher than allowed by the precision cap. + PrecisionError: precision higher than allowed by the precision cap """ if self.relprec > self.prime_pow.ram_prec_cap: - raise PrecisionError("Precision higher than allowed by the precision cap.") + raise PrecisionError("precision higher than allowed by the precision cap") def __copy__(self): """ @@ -1012,7 +1012,7 @@ cdef class CRElement(pAdicTemplateElement): sage: b.is_zero(6) Traceback (most recent call last): ... - PrecisionError: Not enough precision to determine if element is zero + PrecisionError: not enough precision to determine if element is zero """ if absprec is None: return self.relprec == 0 @@ -1022,13 +1022,13 @@ cdef class CRElement(pAdicTemplateElement): return False if isinstance(absprec, int): if self.relprec == 0 and absprec > self.ordp: - raise PrecisionError("Not enough precision to determine if element is zero") + raise PrecisionError("not enough precision to determine if element is zero") return self.ordp >= absprec if not isinstance(absprec, Integer): absprec = Integer(absprec) if self.relprec == 0: if mpz_cmp_si((absprec).value, self.ordp) > 0: - raise PrecisionError("Not enough precision to determine if element is zero") + raise PrecisionError("not enough precision to determine if element is zero") else: return True return mpz_cmp_si((absprec).value, self.ordp) <= 0 @@ -1071,7 +1071,7 @@ cdef class CRElement(pAdicTemplateElement): sage: a.is_equal_to(aa, 15) Traceback (most recent call last): ... - PrecisionError: Elements not known to enough precision + PrecisionError: elements not known to enough precision sage: a.is_equal_to(a, 50000) True @@ -1081,26 +1081,26 @@ cdef class CRElement(pAdicTemplateElement): sage: a.is_equal_to(b, 5) Traceback (most recent call last): ... - PrecisionError: Elements not known to enough precision + PrecisionError: elements not known to enough precision sage: b.is_equal_to(b, 5) Traceback (most recent call last): ... - PrecisionError: Elements not known to enough precision + PrecisionError: elements not known to enough precision sage: b.is_equal_to(bb, 3) True sage: b.is_equal_to(bb, 4) Traceback (most recent call last): ... - PrecisionError: Elements not known to enough precision + PrecisionError: elements not known to enough precision sage: c.is_equal_to(b, 2), c.is_equal_to(b, 3) (True, False) sage: c.is_equal_to(b, 4) Traceback (most recent call last): ... - PrecisionError: Elements not known to enough precision + PrecisionError: elements not known to enough precision sage: c.is_equal_to(cc, 2), c.is_equal_to(cc, 4), c.is_equal_to(cc, 5) (True, True, False) @@ -1112,28 +1112,28 @@ cdef class CRElement(pAdicTemplateElement): sage: aa.is_equal_to(a, 15) Traceback (most recent call last): ... - PrecisionError: Elements not known to enough precision + PrecisionError: elements not known to enough precision sage: b.is_equal_to(a), b.is_equal_to(a, 2) (True, True) sage: b.is_equal_to(a, 5) Traceback (most recent call last): ... - PrecisionError: Elements not known to enough precision + PrecisionError: elements not known to enough precision sage: bb.is_equal_to(b, 3) True sage: bb.is_equal_to(b, 4) Traceback (most recent call last): ... - PrecisionError: Elements not known to enough precision + PrecisionError: elements not known to enough precision sage: b.is_equal_to(c, 2), b.is_equal_to(c, 3) (True, False) sage: b.is_equal_to(c, 4) Traceback (most recent call last): ... - PrecisionError: Elements not known to enough precision + PrecisionError: elements not known to enough precision sage: cc.is_equal_to(c, 2), cc.is_equal_to(c, 4), cc.is_equal_to(c, 5) (True, True, False) @@ -1147,7 +1147,7 @@ cdef class CRElement(pAdicTemplateElement): if exactzero(self.ordp) and exactzero(right.ordp): return True elif absprec is infinity: - raise PrecisionError("Elements not known to enough precision") + raise PrecisionError("elements not known to enough precision") if absprec is None: aprec = min(self.ordp + self.relprec, right.ordp + right.relprec) else: @@ -1158,10 +1158,10 @@ cdef class CRElement(pAdicTemplateElement): exactzero(self.ordp) and exactzero(right.ordp): return True else: - raise PrecisionError("Elements not known to enough precision") + raise PrecisionError("elements not known to enough precision") aprec = mpz_get_si((absprec).value) if aprec > self.ordp + self.relprec or aprec > right.ordp + right.relprec: - raise PrecisionError("Elements not known to enough precision") + raise PrecisionError("elements not known to enough precision") if self.ordp >= aprec and right.ordp >= aprec: return True elif self.ordp != right.ordp: @@ -1213,7 +1213,7 @@ cdef class CRElement(pAdicTemplateElement): sage: c.lift_to_precision(40) Traceback (most recent call last): ... - PrecisionError: Precision higher than allowed by the precision cap. + PrecisionError: precision higher than allowed by the precision cap """ cpdef CRElement ans if absprec == maxordp: @@ -1285,7 +1285,7 @@ cdef class CRElement(pAdicTemplateElement): if self.ordp > 0: self._set_exact_zero() elif self.ordp < 0: - raise ValueError("cannot set negative valuation element to Teichmuller representative.") + raise ValueError("cannot set negative valuation element to Teichmuller representative") elif self.relprec == 0: raise ValueError("not enough precision") else: @@ -1503,7 +1503,7 @@ cdef class CRElement(pAdicTemplateElement): """ # Since we keep this element normalized there's not much to do here. if p is not None and p != self.parent().prime(): - raise ValueError('Ring (%s) residue field of the wrong characteristic.'%self.parent()) + raise ValueError('ring (%s) residue field of the wrong characteristic'%self.parent()) if exactzero((self).ordp): raise ValueError("unit part of 0 not defined") cdef Integer val = Integer.__new__(Integer) diff --git a/src/sage/rings/padics/padic_ZZ_pX_CA_element.pyx b/src/sage/rings/padics/padic_ZZ_pX_CA_element.pyx index 16a9e0d4ff7..225c52efa83 100644 --- a/src/sage/rings/padics/padic_ZZ_pX_CA_element.pyx +++ b/src/sage/rings/padics/padic_ZZ_pX_CA_element.pyx @@ -311,13 +311,13 @@ cdef class pAdicZZpXCAElement(pAdicZZpXElement): self._set_from_mpz_both(xlift.value, aprec, rprec) return if parent.prime() != x.parent().prime(): - raise TypeError("Cannot coerce between p-adic parents with different primes.") + raise TypeError("cannot coerce between p-adic parents with different primes") if isinstance(x, pari_gen) or isinstance(x, GpElement): if isinstance(x, GpElement): x = x.__pari__() if x.type() == "t_PADIC": if x.variable() != self.prime_pow.prime: - raise TypeError("Cannot coerce a pari p-adic with the wrong prime.") + raise TypeError("cannot coerce a pari p-adic with the wrong prime") ltmp = x.padicprec(self.prime_pow.prime) * self.prime_pow.e if ltmp < aprec: aprec = ltmp @@ -422,7 +422,7 @@ cdef class pAdicZZpXCAElement(pAdicZZpXElement): aprec = rprec + __x.ordp self._set(&(poly).x, aprec) else: - raise NotImplementedError("Conversion from different p-adic extensions not yet supported") + raise NotImplementedError("conversion from different p-adic extensions not yet supported") else: try: x = list(x) @@ -1267,7 +1267,7 @@ cdef class pAdicZZpXCAElement(pAdicZZpXElement): mpz_clear(tmp) return ans elif isinstance(_right, Rational) or (isinstance(_right, pAdicGenericElement) and _right._is_base_elt(self.prime_pow.prime)): - raise ValueError("Need more precision") + raise ValueError("need more precision") else: raise TypeError("exponent must be an integer, rational or base p-adic with the same prime") if isinstance(_right, (int, long)): @@ -1564,14 +1564,14 @@ cdef class pAdicZZpXCAElement(pAdicZZpXElement): sage: ZZ(w) Traceback (most recent call last): ... - ValueError: This element not well approximated by an integer. + ValueError: this element not well approximated by an integer sage: ZZ(W(5)) 5 """ cdef Integer ans cdef ZZ_c tmp_z if ZZ_pX_deg(self.value) > 0: - raise ValueError("This element not well approximated by an integer.") + raise ValueError("this element not well approximated by an integer") ans = PY_NEW(Integer) tmp_z = ZZ_p_rep(ZZ_pX_ConstTerm(self.value)) ZZ_to_mpz(ans.value, &tmp_z) @@ -1598,14 +1598,15 @@ cdef class pAdicZZpXCAElement(pAdicZZpXElement): ans.value = self.value return ans - def is_zero(self, absprec = None): - """ - Returns whether the valuation of ``self`` is at least ``absprec``. If - ``absprec`` is ``None``, returns if ``self`` is indistinguishable from - zero. + def is_zero(self, absprec=None): + r""" + Return whether the valuation of ``self`` is at least ``absprec``. + + If ``absprec`` is ``None``, returns if ``self`` is indistinguishable + from zero. If ``self`` is an inexact zero of valuation less than ``absprec``, - raises a PrecisionError. + raises a ``PrecisionError``. EXAMPLES:: @@ -1636,20 +1637,20 @@ cdef class pAdicZZpXCAElement(pAdicZZpXElement): if mpz_sgn((absprec).value) < 0: ans = True elif ZZ_pX_IsZero(self.value): - raise PrecisionError("Not enough precision to determine if element is zero") + raise PrecisionError("not enough precision to determine if element is zero") else: ans = False else: aprec = mpz_get_si((absprec).value) if ZZ_pX_IsZero(self.value) and aprec > self.absprec: - raise PrecisionError("Not enough precision to determine if element is zero") + raise PrecisionError("not enough precision to determine if element is zero") else: ans = (self.valuation_c() >= aprec) return ans cpdef ntl_ZZ_pX _ntl_rep(self): """ - Returns an ``ntl_ZZ_pX`` that holds the value of ``self``. + Return an ``ntl_ZZ_pX`` that holds the value of ``self``. EXAMPLES:: @@ -1671,7 +1672,7 @@ cdef class pAdicZZpXCAElement(pAdicZZpXElement): cpdef _ntl_rep_abs(self): """ - Returns a pair ``(f, 0)`` where ``f = self._ntl_rep()``. + Return a pair ``(f, 0)`` where ``f = self._ntl_rep()``. EXAMPLES:: @@ -1837,11 +1838,11 @@ cdef class pAdicZZpXCAElement(pAdicZZpXElement): if mpz_sgn((absprec).value) < 0: return self else: - raise PrecisionError("Precision higher than allowed by the precision cap.") + raise PrecisionError("precision higher than allowed by the precision cap") else: aprec = mpz_get_si((absprec).value) if aprec > self.prime_pow.ram_prec_cap: - raise PrecisionError("Precision higher than allowed by the precision cap.") + raise PrecisionError("precision higher than allowed by the precision cap") if aprec <= self.absprec: return self ans = self._new_c(aprec) # restores context @@ -2350,3 +2351,4 @@ def make_ZZpXCAElement(parent, value, absprec, version): return ans else: raise ValueError("unknown unpickling version") + diff --git a/src/sage/rings/padics/padic_ZZ_pX_CR_element.pyx b/src/sage/rings/padics/padic_ZZ_pX_CR_element.pyx index 87d70edaf51..db39fab2b60 100644 --- a/src/sage/rings/padics/padic_ZZ_pX_CR_element.pyx +++ b/src/sage/rings/padics/padic_ZZ_pX_CR_element.pyx @@ -341,7 +341,7 @@ cdef class pAdicZZpXCRElement(pAdicZZpXElement): if isinstance(x, pari_gen): if x.type() == "t_PADIC": if x.variable() != self.prime_pow.prime: - raise TypeError("Cannot coerce a pari p-adic with the wrong prime.") + raise TypeError("cannot coerce a pari p-adic with the wrong prime") ltmp = x.padicprec(self.prime_pow.prime) * self.prime_pow.e if absprec is infinity or ltmp < aprec: aprec = ltmp @@ -485,7 +485,7 @@ cdef class pAdicZZpXCRElement(pAdicZZpXElement): rprec = _x.relprec self._set(&_x.unit, _x.ordp, rprec) else: - raise NotImplementedError("Conversion from different p-adic extensions not yet supported") + raise NotImplementedError("conversion from different p-adic extensions not yet supported") else: try: x = list(x) @@ -1663,7 +1663,7 @@ cdef class pAdicZZpXCRElement(pAdicZZpXElement): if self._is_exact_zero(): return self if self.prime_pow.in_field or mpz_sgn((shift).value) > 0: - raise ValueError("Shift does not fit in long") + raise ValueError("shift does not fit in long") else: ans = self._new_c(0) ans.ordp = 0 @@ -2304,9 +2304,9 @@ cdef class pAdicZZpXCRElement(pAdicZZpXElement): return ans def _integer_(self, Z=None): - """ + r""" Return an integer congruent to this element modulo - `\pi`^``self.absolute_precision()``, if possible + `\pi^{\mathrm{self.absolute_precision()}`, if possible. EXAMPLES:: @@ -2322,7 +2322,7 @@ cdef class pAdicZZpXCRElement(pAdicZZpXElement): sage: ZZ(w) Traceback (most recent call last): ... - ValueError: This element not well approximated by an integer. + ValueError: this element not well approximated by an integer sage: ZZ(W(5)) # todo: this should be different... 381469726562505 """ @@ -2334,23 +2334,23 @@ cdef class pAdicZZpXCRElement(pAdicZZpXElement): if self.ordp < 0: self._normalize() if self.ordp < 0: - raise ValueError("This element has negative valuation") + raise ValueError("this element has negative valuation") cdef ntl_ZZ_pX f = self._ntl_rep_abs()[0] if f.degree() > 0: - raise ValueError("This element not well approximated by an integer.") + raise ValueError("this element not well approximated by an integer") ans = PY_NEW(Integer) tmp_z = ZZ_p_rep(ZZ_pX_ConstTerm(f.x)) ZZ_to_mpz(ans.value, &tmp_z) return ans def is_zero(self, absprec = None): - """ + r""" Return whether the valuation of this element is at least ``absprec``. If ``absprec`` is ``None``, checks if this element is indistinguishable from zero. If this element is an inexact zero of valuation less than ``absprec``, - raises a PrecisionError. + raises a ``PrecisionError``. EXAMPLES:: @@ -2384,13 +2384,13 @@ cdef class pAdicZZpXCRElement(pAdicZZpXElement): if mpz_sgn((absprec).value) < 0: ans = True elif self.relprec == 0: - raise PrecisionError("Not enough precision to determine if element is zero") + raise PrecisionError("not enough precision to determine if element is zero") else: ans = False else: aprec = mpz_get_si((absprec).value) if self.relprec == 0 and aprec > self.ordp: - raise PrecisionError("Not enough precision to determine if element is zero") + raise PrecisionError("not enough precision to determine if element is zero") else: ans = (self.ordp >= aprec) return ans @@ -2693,7 +2693,7 @@ cdef class pAdicZZpXCRElement(pAdicZZpXElement): if self.relprec == 0: raise ValueError("absprec larger than maximum allowable valuation") else: - raise PrecisionError("Precision higher than allowed by the precision cap.") + raise PrecisionError("precision higher than allowed by the precision cap") else: aprec = mpz_get_si((absprec).value) if aprec <= self.ordp + self.relprec: @@ -2710,7 +2710,7 @@ cdef class pAdicZZpXCRElement(pAdicZZpXElement): # Now we're done handling all the special cases. rprec = aprec - self.ordp if rprec > self.prime_pow.ram_prec_cap: - raise PrecisionError("Precision higher than allowed by the precision cap.") + raise PrecisionError("precision higher than allowed by the precision cap") ans = self._new_c(rprec) ans.ordp = self.ordp ZZ_pX_conv_modulus(ans.unit, self.unit, self.prime_pow.get_context_capdiv(rprec).x) @@ -3044,13 +3044,13 @@ cdef class pAdicZZpXCRElement(pAdicZZpXElement): sage: K.teichmuller(K(2/5)) Traceback (most recent call last): ... - ValueError: cannot set negative valuation element to Teichmuller representative. + ValueError: cannot set negative valuation element to Teichmuller representative """ self._normalize() if self.ordp > 0: self._set_exact_zero() elif self.ordp < 0: - raise ValueError("cannot set negative valuation element to Teichmuller representative.") + raise ValueError("cannot set negative valuation element to Teichmuller representative") elif self.relprec == 0: raise ValueError("not enough precision known") else: @@ -3269,3 +3269,4 @@ def make_ZZpXCRElement(parent, unit, ordp, relprec, version): return ans else: raise ValueError("unknown unpickling version") + diff --git a/src/sage/rings/padics/padic_ZZ_pX_FM_element.pyx b/src/sage/rings/padics/padic_ZZ_pX_FM_element.pyx index c3cf8f96770..9c0bcff4f69 100644 --- a/src/sage/rings/padics/padic_ZZ_pX_FM_element.pyx +++ b/src/sage/rings/padics/padic_ZZ_pX_FM_element.pyx @@ -4,7 +4,7 @@ # distutils: library_dirs = NTL_LIBDIR # distutils: extra_link_args = NTL_LIBEXTRA # distutils: language = c++ -""" +r""" `p`-Adic ``ZZ_pX`` FM Element This file implements elements of Eisenstein and unramified extensions @@ -24,37 +24,37 @@ element contains the following data: identical among all elements with the same parent, holding common data. - + ``prime_pow.deg`` -- The degree of the extension + * ``prime_pow.deg`` -- the degree of the extension - + ``prime_pow.e`` -- The ramification index + * ``prime_pow.e`` -- the ramification index - + ``prime_pow.f`` -- The inertia degree + * ``prime_pow.f`` -- the inertia degree - + ``prime_pow.prec_cap`` -- the unramified precision cap. For - Eisenstein extensions this is the smallest power of p that is - zero. + * ``prime_pow.prec_cap`` -- the unramified precision cap: for + Eisenstein extensions this is the smallest power of `p` that is + zero - + ``prime_pow.ram_prec_cap`` -- the ramified precision cap. For + * ``prime_pow.ram_prec_cap`` -- the ramified precision cap: for Eisenstein extensions this will be the smallest power of `x` that - is indistinguishable from zero. + is indistinguishable from zero - + ``prime_pow.pow_ZZ_tmp``, prime_pow.pow_mpz_t_tmp``, + * ``prime_pow.pow_ZZ_tmp``, prime_pow.pow_mpz_t_tmp``, ``prime_pow.pow_Integer`` -- functions for accessing powers of `p`. The first two return pointers. See ``sage/rings/padics/pow_computer_ext`` for examples and important warnings. - + ``prime_pow.get_context``, ``prime_pow.get_context_capdiv``, + * ``prime_pow.get_context``, ``prime_pow.get_context_capdiv``, ``prime_pow.get_top_context`` -- obtain an ``ntl_ZZ_pContext_class`` corresponding to `p^n`. The capdiv version divides by ``prime_pow.e`` as appropriate. ``top_context`` corresponds to `p^{prec_cap}`. - + ``prime_pow.restore_context``, + * ``prime_pow.restore_context``, ``prime_pow.restore_context_capdiv``, - ``prime_pow.restore_top_context`` -- restores the given context. + ``prime_pow.restore_top_context`` -- restores the given context - + ``prime_pow.get_modulus``, ``get_modulus_capdiv``, + * ``prime_pow.get_modulus``, ``get_modulus_capdiv``, ``get_top_modulus`` -- Returns a ``ZZ_pX_Modulus_c*`` pointing to a polynomial modulus defined modulo `p^n` (appropriately divided by ``prime_pow.e`` in the capdiv case). @@ -155,7 +155,7 @@ from sage.rings.padics.pow_computer_ext cimport PowComputer_ZZ_pX_FM_Eis cdef class pAdicZZpXFMElement(pAdicZZpXElement): def __init__(self, parent, x, absprec=None, relprec=None, empty=False): - """ + r""" Creates an element of a fixed modulus, unramified or eisenstein extension of `\ZZ_p` or `\QQ_p`. @@ -174,7 +174,7 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): - ``relprec`` -- not used - ``empty`` -- whether to return after initializing to zero - (without setting anything). + (without setting anything) EXAMPLES:: @@ -320,7 +320,8 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): sage: f = x^5 + 75*x^3 - 15*x^2 +125*x - 5 sage: W. = R.ext(f) sage: z = W(70/3); z # indirect doctest - 3*w^5 + w^7 + 2*w^9 + 2*w^10 + 4*w^11 + w^12 + 2*w^13 + 3*w^15 + 2*w^16 + 3*w^17 + w^18 + 3*w^19 + 3*w^20 + 2*w^21 + 2*w^22 + 3*w^23 + 4*w^24 + 3*w^5 + w^7 + 2*w^9 + 2*w^10 + 4*w^11 + w^12 + 2*w^13 + 3*w^15 + 2*w^16 + + 3*w^17 + w^18 + 3*w^19 + 3*w^20 + 2*w^21 + 2*w^22 + 3*w^23 + 4*w^24 sage: z * 3 4*w^5 + 3*w^7 + w^9 + 2*w^10 + 2*w^11 + w^13 + 3*w^16 + w^17 + w^18 + 4*w^20 + 4*w^21 + w^22 + 2*w^23 sage: W(70) @@ -353,7 +354,8 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): sage: f = x^5 + 75*x^3 - 15*x^2 +125*x - 5 sage: W. = R.ext(f) sage: z = W(ntl.ZZ_pX([4,1,16],5^2)); z # indirect doctest - 4 + w + w^2 + 3*w^7 + w^9 + 2*w^11 + 4*w^13 + 3*w^14 + 2*w^15 + w^16 + 3*w^18 + 2*w^19 + 4*w^20 + 4*w^21 + 2*w^22 + 2*w^23 + 4*w^24 + 4 + w + w^2 + 3*w^7 + w^9 + 2*w^11 + 4*w^13 + 3*w^14 + 2*w^15 + w^16 + + 3*w^18 + 2*w^19 + 4*w^20 + 4*w^21 + 2*w^22 + 2*w^23 + 4*w^24 sage: z._ntl_rep() [4 1 16] """ @@ -373,7 +375,8 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): sage: f = x^5 + 75*x^3 - 15*x^2 +125*x - 5 sage: W. = R.ext(f) sage: z = W(ntl.ZZX([4,1,16])); z # indirect doctest - 4 + w + w^2 + 3*w^7 + w^9 + 2*w^11 + 4*w^13 + 3*w^14 + 2*w^15 + w^16 + 3*w^18 + 2*w^19 + 4*w^20 + 4*w^21 + 2*w^22 + 2*w^23 + 4*w^24 + 4 + w + w^2 + 3*w^7 + w^9 + 2*w^11 + 4*w^13 + 3*w^14 + 2*w^15 + w^16 + + 3*w^18 + 2*w^19 + 4*w^20 + 4*w^21 + 2*w^22 + 2*w^23 + 4*w^24 sage: z._ntl_rep() [4 1 16] """ @@ -397,11 +400,12 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): sage: z._is_inexact_zero() True """ - return ZZ_pX_IsZero(self.value) or (self.prime_pow.e * self.prime_pow.prec_cap != self.prime_pow.ram_prec_cap and self.valuation_c() >= self.prime_pow.ram_prec_cap) + return ZZ_pX_IsZero(self.value) or (self.prime_pow.e * self.prime_pow.prec_cap != self.prime_pow.ram_prec_cap + and self.valuation_c() >= self.prime_pow.ram_prec_cap) def __reduce__(self): """ - Pickles ``self``. + Pickle ``self``. EXAMPLES:: @@ -421,7 +425,7 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): cdef pAdicZZpXFMElement _new_c(self): """ - Returns a new element with the same parent as ``self``. + Return a new element with the same parent as ``self``. EXAMPLES:: @@ -475,8 +479,7 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): def __invert__(self): """ - Returns the inverse of ``self``, as long as ``self`` is a - unit. + Return the inverse of ``self``, as long as ``self`` is a unit. If ``self`` is not a unit, raises a ``ValueError``. @@ -488,7 +491,8 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): sage: W. = R.ext(f) sage: z = (1 + w)^5 sage: y = ~z; y # indirect doctest - 1 + 4*w^5 + 4*w^6 + 3*w^7 + w^8 + 2*w^10 + w^11 + w^12 + 2*w^14 + 3*w^16 + 3*w^17 + 4*w^18 + 4*w^19 + 2*w^20 + 2*w^21 + 4*w^22 + 3*w^23 + 3*w^24 + 1 + 4*w^5 + 4*w^6 + 3*w^7 + w^8 + 2*w^10 + w^11 + w^12 + 2*w^14 + 3*w^16 + + 3*w^17 + 4*w^18 + 4*w^19 + 2*w^20 + 2*w^21 + 4*w^22 + 3*w^23 + 3*w^24 sage: y.parent() 5-adic Eisenstein Extension Ring in w defined by x^5 + 75*x^3 - 15*x^2 + 125*x - 5 sage: z = z - 1 @@ -510,8 +514,9 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): cdef pAdicZZpXFMElement _lshift_c(self, long n): """ - Multiplies ``self`` by the uniformizer raised to the power - ``n``. If ``n`` is negative, right shifts by ``-n``. + Multiply ``self`` by the uniformizer raised to the power ``n``. + + If ``n`` is negative, right shifts by ``-n``. EXAMPLES:: @@ -541,8 +546,9 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): def __lshift__(pAdicZZpXFMElement self, shift): """ - Multiplies ``self`` by the uniformizer raised to the power - ``n``. If ``n`` is negative, right shifts by ``-n``. + Multiply ``self`` by the uniformizer raised to the power ``n``. + + If ``n`` is negative, right shifts by ``-n``. EXAMPLES:: @@ -569,7 +575,8 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): cdef pAdicZZpXFMElement _rshift_c(self, long n): """ - Divides ``self`` by the uniformizer raised to the power ``n``. + Divide ``self`` by the uniformizer raised to the power ``n``. + Throws away the non-positive part of the series expansion. The top digits will be garbage. If ``n`` is negative, left shifts by ``-n``. @@ -629,7 +636,8 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): def __rshift__(pAdicZZpXFMElement self, shift): """ - Divides ``self`` by the uniformizer raised to the power ``n``. + Divide ``self`` by the uniformizer raised to the power ``n``. + Throws away the non-positive part of the series expansion. The top digits will be garbage. If ``n`` is negative, left shifts by ``-n``. @@ -642,11 +650,14 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): sage: W. = R.ext(f) sage: z = (1 + w)^5 sage: z - 1 + w^5 + w^6 + 2*w^7 + 4*w^8 + 3*w^10 + w^12 + 4*w^13 + 4*w^14 + 4*w^15 + 4*w^16 + 4*w^17 + 4*w^20 + w^21 + 4*w^24 + 1 + w^5 + w^6 + 2*w^7 + 4*w^8 + 3*w^10 + w^12 + 4*w^13 + 4*w^14 + + 4*w^15 + 4*w^16 + 4*w^17 + 4*w^20 + w^21 + 4*w^24 sage: z >> (6) # indirect doctest - 1 + 2*w + 4*w^2 + 3*w^4 + w^6 + 4*w^7 + 4*w^8 + 4*w^9 + 4*w^10 + 4*w^11 + 4*w^14 + w^15 + 4*w^18 + 4*w^19 + 2*w^20 + 3*w^21 + 2*w^22 + 3*w^24 + 1 + 2*w + 4*w^2 + 3*w^4 + w^6 + 4*w^7 + 4*w^8 + 4*w^9 + 4*w^10 + 4*w^11 + + 4*w^14 + w^15 + 4*w^18 + 4*w^19 + 2*w^20 + 3*w^21 + 2*w^22 + 3*w^24 sage: z >> (-4) - w^4 + w^9 + w^10 + 2*w^11 + 4*w^12 + 3*w^14 + w^16 + 4*w^17 + 4*w^18 + 4*w^19 + 4*w^20 + 4*w^21 + 4*w^24 + w^4 + w^9 + w^10 + 2*w^11 + 4*w^12 + 3*w^14 + w^16 + 4*w^17 + + 4*w^18 + 4*w^19 + 4*w^20 + 4*w^21 + 4*w^24 """ cdef pAdicZZpXFMElement ans if not isinstance(shift, Integer): @@ -667,9 +678,11 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): sage: f = x^5 + 75*x^3 - 15*x^2 +125*x - 5 sage: W. = R.ext(f) sage: z = (1 + w)^5; z - 1 + w^5 + w^6 + 2*w^7 + 4*w^8 + 3*w^10 + w^12 + 4*w^13 + 4*w^14 + 4*w^15 + 4*w^16 + 4*w^17 + 4*w^20 + w^21 + 4*w^24 + 1 + w^5 + w^6 + 2*w^7 + 4*w^8 + 3*w^10 + w^12 + 4*w^13 + 4*w^14 + + 4*w^15 + 4*w^16 + 4*w^17 + 4*w^20 + w^21 + 4*w^24 sage: -z # indirect doctest - 4 + 3*w^5 + 4*w^6 + w^7 + w^8 + w^9 + w^10 + w^11 + 2*w^12 + 4*w^13 + 4*w^15 + 3*w^16 + w^17 + 2*w^18 + 3*w^19 + 2*w^21 + 4*w^23 + 4*w^24 + 4 + 3*w^5 + 4*w^6 + w^7 + w^8 + w^9 + w^10 + w^11 + 2*w^12 + 4*w^13 + + 4*w^15 + 3*w^16 + w^17 + 2*w^18 + 3*w^19 + 2*w^21 + 4*w^23 + 4*w^24 sage: y = z + (-z); y 0 sage: -y @@ -690,9 +703,11 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): sage: f = x^5 + 75*x^3 - 15*x^2 +125*x - 5 sage: W. = R.ext(f) sage: (1 + w)^5 # indirect doctest - 1 + w^5 + w^6 + 2*w^7 + 4*w^8 + 3*w^10 + w^12 + 4*w^13 + 4*w^14 + 4*w^15 + 4*w^16 + 4*w^17 + 4*w^20 + w^21 + 4*w^24 + 1 + w^5 + w^6 + 2*w^7 + 4*w^8 + 3*w^10 + w^12 + 4*w^13 + 4*w^14 + + 4*w^15 + 4*w^16 + 4*w^17 + 4*w^20 + w^21 + 4*w^24 sage: (1 + w)^-5 - 1 + 4*w^5 + 4*w^6 + 3*w^7 + w^8 + 2*w^10 + w^11 + w^12 + 2*w^14 + 3*w^16 + 3*w^17 + 4*w^18 + 4*w^19 + 2*w^20 + 2*w^21 + 4*w^22 + 3*w^23 + 3*w^24 + 1 + 4*w^5 + 4*w^6 + 3*w^7 + w^8 + 2*w^10 + w^11 + w^12 + 2*w^14 + + 3*w^16 + 3*w^17 + 4*w^18 + 4*w^19 + 2*w^20 + 2*w^21 + 4*w^22 + 3*w^23 + 3*w^24 TESTS: @@ -745,7 +760,7 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): cpdef _add_(self, right): """ - Returns ``self`` + ``right``. + Return ``self`` + ``right``. EXAMPLES:: @@ -764,7 +779,7 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): cpdef _mul_(self, right): """ - Returns the product of ``self`` and ``right``. + Return the product of ``self`` and ``right``. EXAMPLES:: @@ -775,7 +790,8 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): sage: a = W(329) sage: b = W(111) sage: a*b #indirect doctest - 4 + 3*w^5 + w^7 + 2*w^9 + 4*w^11 + 3*w^12 + 2*w^13 + w^14 + 2*w^15 + 3*w^16 + 4*w^17 + 4*w^18 + 2*w^19 + 2*w^21 + 4*w^22 + 2*w^23 + w^24 + 4 + 3*w^5 + w^7 + 2*w^9 + 4*w^11 + 3*w^12 + 2*w^13 + w^14 + 2*w^15 + + 3*w^16 + 4*w^17 + 4*w^18 + 2*w^19 + 2*w^21 + 4*w^22 + 2*w^23 + w^24 sage: a * 0 0 sage: W(125) * W(375) @@ -787,7 +803,7 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): cpdef _sub_(self, right): """ - Returns the difference of ``self`` and ``right``. + Return the difference of ``self`` and ``right``. EXAMPLES:: @@ -798,9 +814,11 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): sage: a = W(329) sage: b = W(111) sage: a - b #indirect doctest - 3 + 3*w^5 + w^7 + 2*w^9 + 3*w^10 + 4*w^11 + 2*w^13 + 2*w^14 + w^15 + 4*w^16 + 2*w^18 + 3*w^19 + 2*w^20 + 3*w^21 + w^22 + w^24 + 3 + 3*w^5 + w^7 + 2*w^9 + 3*w^10 + 4*w^11 + 2*w^13 + 2*w^14 + w^15 + + 4*w^16 + 2*w^18 + 3*w^19 + 2*w^20 + 3*w^21 + w^22 + w^24 sage: W(218) - 3 + 3*w^5 + w^7 + 2*w^9 + 3*w^10 + 4*w^11 + 2*w^13 + 2*w^14 + w^15 + 4*w^16 + 2*w^18 + 3*w^19 + 2*w^20 + 3*w^21 + w^22 + w^24 + 3 + 3*w^5 + w^7 + 2*w^9 + 3*w^10 + 4*w^11 + 2*w^13 + 2*w^14 + w^15 + + 4*w^16 + 2*w^18 + 3*w^19 + 2*w^20 + 3*w^21 + w^22 + w^24 """ cdef pAdicZZpXFMElement ans = self._new_c() ZZ_pX_sub(ans.value, self.value, (right).value) @@ -840,7 +858,8 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): cdef pAdicZZpXFMElement ans = self._new_c() sig_on() if self.prime_pow.e == 1: - ZZ_pX_InvMod_newton_unram(ans.value, right.value, self.prime_pow.get_top_modulus()[0], self.prime_pow.get_top_context().x, self.prime_pow.get_context(1).x) + ZZ_pX_InvMod_newton_unram(ans.value, right.value, self.prime_pow.get_top_modulus()[0], + self.prime_pow.get_top_context().x, self.prime_pow.get_context(1).x) else: ZZ_pX_InvMod_newton_ram(ans.value, right.value, self.prime_pow.get_top_modulus()[0], self.prime_pow.get_top_context().x) ZZ_pX_MulMod_pre(ans.value, self.value, ans.value, self.prime_pow.get_top_modulus()[0]) @@ -849,7 +868,7 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): def __copy__(self): """ - Returns a copy of ``self``. + Return a copy of ``self``. EXAMPLES:: @@ -858,9 +877,11 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): sage: f = x^5 + 75*x^3 - 15*x^2 +125*x - 5 sage: W. = R.ext(f) sage: b = W(45); b - 4*w^5 + 3*w^7 + w^9 + w^10 + 2*w^11 + w^12 + w^13 + 3*w^14 + w^16 + 2*w^17 + w^19 + 4*w^20 + w^21 + 3*w^22 + 3*w^23 + 4*w^24 + 4*w^5 + 3*w^7 + w^9 + w^10 + 2*w^11 + w^12 + w^13 + 3*w^14 + w^16 + + 2*w^17 + w^19 + 4*w^20 + w^21 + 3*w^22 + 3*w^23 + 4*w^24 sage: c = copy(b); c - 4*w^5 + 3*w^7 + w^9 + w^10 + 2*w^11 + w^12 + w^13 + 3*w^14 + w^16 + 2*w^17 + w^19 + 4*w^20 + w^21 + 3*w^22 + 3*w^23 + 4*w^24 + 4*w^5 + 3*w^7 + w^9 + w^10 + 2*w^11 + w^12 + w^13 + 3*w^14 + w^16 + + 2*w^17 + w^19 + 4*w^20 + w^21 + 3*w^22 + 3*w^23 + 4*w^24 sage: c is b False """ @@ -870,8 +891,8 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): def is_zero(self, absprec = None): """ - Returns whether the valuation of ``self`` is at least - ``absprec``. If ``absprec`` is ``None``, returns whether + Return whether the valuation of ``self`` is at least + ``absprec``; if ``absprec`` is ``None``, return whether ``self`` is indistinguishable from zero. EXAMPLES:: @@ -908,8 +929,8 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): return ans def add_bigoh(self, absprec): - """ - Return a new element truncated modulo \pi^absprec. + r""" + Return a new element truncated modulo `\pi^{\text{absprec}}`. This is only implemented for unramified extension at this point. @@ -920,7 +941,7 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): OUTPUT: - a new element truncated modulo `\pi^{\mbox{absprec}}`. + A new element truncated modulo `\pi^{\mbox{absprec}}`. EXAMPLES:: @@ -955,9 +976,9 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): return ans def _integer_(self, Z=None): - """ - Returns an integer congruent to this element modulo - `\pi` ^ ``self.absolute_precision()``, if possible. + r""" + Return an integer congruent to this element modulo + `\pi^a`, where `a` is ``self.absolute_precision()``, if possible. EXAMPLES:: @@ -986,15 +1007,16 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): return ans def matrix_mod_pn(self): - """ - Returns the matrix of right multiplication by the element on + r""" + Return the matrix of right multiplication by the element on the power basis `1, x, x^2, \ldots, x^{d-1}` for this - extension field. Thus the \emph{rows} of this matrix give the - images of each of the `x^i`. The entries of the matrices are - ``IntegerMod`` elements, defined modulo ``p^(self.absprec() / - e)``. + extension field. - Raises an error if self has negative valuation. + The **rows** of this matrix give the images of each of the `x^i`. + The entries of the matrices are ``IntegerMod`` elements, + defined modulo ``p^(self.absprec() / e)``. + + Raises an error if ``self`` has negative valuation. EXAMPLES:: @@ -1046,14 +1068,17 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): # """ # raise NotImplementedError - def norm(self, base = None): - """ + def norm(self, base=None): + r""" Return the absolute or relative norm of this element. - NOTE! This is not the `p`-adic absolute value. This is a - field theoretic norm down to a ground ring. + .. NOTE:: + + This is not the `p`-adic absolute value. This is a + field theoretic norm down to a ground ring. - If you want the `p`-adic absolute value, use the ``abs()`` function instead. + If you want the `p`-adic absolute value, use the :func:`abs()` + function instead. If `K` is given then `K` must be a subfield of the parent `L` of ``self``, in which case the norm is the relative norm from `L` to `K`. @@ -1083,8 +1108,8 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): norm_of_uniformizer = (-1)**self.parent().degree() * self.parent().defining_polynomial()[0] return self.parent().ground_ring()(self.unit_part().matrix_mod_pn().det()) * norm_of_uniformizer**self.valuation() - def trace(self, base = None): - """ + def trace(self, base=None): + r""" Return the absolute or relative trace of this element. If `K` is given then `K` must be a subfield of the parent `L` of @@ -1124,7 +1149,7 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): def _ntl_rep(self): """ - Returns an ``ntl_ZZ_pX`` holding ``self.value``. + Return an ``ntl_ZZ_pX`` holding ``self.value``. EXAMPLES:: @@ -1149,7 +1174,8 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): INPUT: - - ``pad`` -- whether to pad the result with zeros of the appropriate precision + - ``pad`` -- whether to pad the result with zeros of the + appropriate precision EXAMPLES:: @@ -1192,10 +1218,11 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): cdef ZZ_p_c _const_term(self): """ - Returns the constant term of ``self.unit``. + Return the constant term of ``self.unit``. + + .. NOTE:: - Note: this may be divisible by `p` if ``self`` is not - normalized. + This may be divisible by `p` if ``self`` is not normalized. EXAMPLES:: @@ -1209,9 +1236,9 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): """ return ZZ_pX_ConstTerm(self.value) - def is_equal_to(self, right, absprec = None): + def is_equal_to(self, right, absprec=None): """ - Returns whether ``self`` is equal to ``right`` modulo + Return whether ``self`` is equal to ``right`` modulo ``self.uniformizer()^absprec``. If ``absprec`` is ``None``, returns if ``self`` is equal to @@ -1242,7 +1269,7 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): def lift_to_precision(self, absprec=None): """ - Returns ``self``. + Return ``self``. EXAMPLES:: @@ -1255,23 +1282,23 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): """ return self - def expansion(self, n = None, lift_mode = 'simple'): - """ - Returns a list giving a series representation of this element. + def expansion(self, n=None, lift_mode='simple'): + r""" + Return a list giving a series representation of this element. - If ``lift_mode == 'simple' or 'smallest'``, the returned list will consist of - + integers (in the eisenstein case) or + * integers (in the eisenstein case) or - + lists of integers (in the unramified case). + * lists of integers (in the unramified case). - this element can be reconstructed as - + a sum of elements of the list times powers of the + * a sum of elements of the list times powers of the uniformiser (in the eisenstein case), or - + as a sum of powers of the `p` times polynomials in the + * as a sum of powers of the `p` times polynomials in the generator (in the unramified case). - If ``lift_mode == 'simple'``, all integers will be in the range @@ -1287,8 +1314,8 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): INPUT: - - ``n`` -- integer (default ``None``). If given, returns the corresponding - entry in the expansion. + - ``n`` -- integer (default ``None``); if given, returns the + corresponding entry in the expansion EXAMPLES:: @@ -1364,16 +1391,16 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): def teichmuller_expansion(self, n = None): r""" - Returns a list [`a_0`, `a_1`,..., `a_n`] such that + Return a list `[a_0, a_1, \ldots, a_n]` such that - `a_i^q = a_i` - ``self.unit_part()`` = `\sum_{i = 0}^n a_i \pi^i`, where `\pi` is a - uniformizer of self.parent() + uniformizer of ``self.parent()`` INPUT: - - ``n`` -- integer (default ``None``). If given, returns the corresponding - entry in the expansion. + - ``n`` -- integer (default ``None``); f given, returns the corresponding + entry in the expansion EXAMPLES:: @@ -1381,7 +1408,10 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): sage: E = a.teichmuller_expansion(); E 5-adic expansion of a (teichmuller) sage: list(E) - [a + (2*a^3 + 2*a^2 + 3*a + 4)*5 + (4*a^3 + 3*a^2 + 3*a + 2)*5^2 + (4*a^2 + 2*a + 2)*5^3, (3*a^3 + 3*a^2 + 2*a + 1) + (a^3 + 4*a^2 + 1)*5 + (a^2 + 4*a + 4)*5^2 + (4*a^2 + a + 3)*5^3, (4*a^3 + 2*a^2 + a + 1) + (2*a^3 + 2*a^2 + 2*a + 4)*5 + (3*a^3 + 2*a^2 + a + 1)*5^2 + (a^3 + a^2 + 2)*5^3, (a^3 + a^2 + a + 4) + (3*a^3 + 1)*5 + (3*a^3 + a + 2)*5^2 + (3*a^3 + 3*a^2 + 3*a + 1)*5^3] + [a + (2*a^3 + 2*a^2 + 3*a + 4)*5 + (4*a^3 + 3*a^2 + 3*a + 2)*5^2 + (4*a^2 + 2*a + 2)*5^3, + (3*a^3 + 3*a^2 + 2*a + 1) + (a^3 + 4*a^2 + 1)*5 + (a^2 + 4*a + 4)*5^2 + (4*a^2 + a + 3)*5^3, + (4*a^3 + 2*a^2 + a + 1) + (2*a^3 + 2*a^2 + 2*a + 4)*5 + (3*a^3 + 2*a^2 + a + 1)*5^2 + (a^3 + a^2 + 2)*5^3, + (a^3 + a^2 + a + 4) + (3*a^3 + 1)*5 + (3*a^3 + a + 2)*5^2 + (3*a^3 + 3*a^2 + 3*a + 1)*5^3] sage: sum([c * 5^i for i, c in enumerate(E)]) a sage: all(c^625 == c for c in E) @@ -1453,7 +1483,7 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): def _teichmuller_set_unsafe(self): """ - Sets this element to the Teichmuller representative with the + Set this element to the Teichmuller representative with the same residue. .. WARNING:: @@ -1468,7 +1498,8 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): sage: f = x^5 + 75*x^3 - 15*x^2 +125*x - 5 sage: W. = R.ext(f) sage: y = W.teichmuller(3); y #indirect doctest - 3 + 3*w^5 + w^7 + 2*w^9 + 2*w^10 + 4*w^11 + w^12 + 2*w^13 + 3*w^15 + 2*w^16 + 3*w^17 + w^18 + 3*w^19 + 3*w^20 + 2*w^21 + 2*w^22 + 3*w^23 + 4*w^24 + 3 + 3*w^5 + w^7 + 2*w^9 + 2*w^10 + 4*w^11 + w^12 + 2*w^13 + 3*w^15 + + 2*w^16 + 3*w^17 + w^18 + 3*w^19 + 3*w^20 + 2*w^21 + 2*w^22 + 3*w^23 + 4*w^24 sage: y^5 == y True sage: g = x^3 + 3*x + 3 @@ -1515,7 +1546,7 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): def precision_absolute(self): """ - Returns the absolute precision of ``self``, ie the precision cap + Return the absolute precision of ``self``, ie the precision cap of ``self.parent()``. EXAMPLES:: @@ -1533,7 +1564,8 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): sage: a.precision_relative() 15 sage: a.unit_part() - 3 + 2*w^2 + w^4 + w^6 + w^7 + 3*w^8 + 3*w^9 + 2*w^11 + 3*w^12 + 3*w^13 + w^15 + 4*w^16 + 2*w^17 + w^18 + 3*w^21 + w^22 + 3*w^24 + 3 + 2*w^2 + w^4 + w^6 + w^7 + 3*w^8 + 3*w^9 + 2*w^11 + 3*w^12 + + 3*w^13 + w^15 + 4*w^16 + 2*w^17 + w^18 + 3*w^21 + w^22 + 3*w^24 """ cdef Integer ans = Integer.__new__(Integer) mpz_set_ui(ans.value, self.prime_pow.ram_prec_cap) @@ -1541,7 +1573,7 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): def precision_relative(self): """ - Returns the relative precision of ``self``, ie the precision cap + Return the relative precision of ``self``, ie the precision cap of ``self.parent()`` minus the ``valuation of self``. EXAMPLES:: @@ -1559,7 +1591,8 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): sage: a.precision_relative() 15 sage: a.unit_part() - 3 + 2*w^2 + w^4 + w^6 + w^7 + 3*w^8 + 3*w^9 + 2*w^11 + 3*w^12 + 3*w^13 + w^15 + 4*w^16 + 2*w^17 + w^18 + 3*w^21 + w^22 + 3*w^24 + 3 + 2*w^2 + w^4 + w^6 + w^7 + 3*w^8 + 3*w^9 + 2*w^11 + 3*w^12 + + 3*w^13 + w^15 + 4*w^16 + 2*w^17 + w^18 + 3*w^21 + w^22 + 3*w^24 """ cdef Integer ans = Integer.__new__(Integer) mpz_set_ui(ans.value, self.prime_pow.ram_prec_cap - self.valuation_c()) @@ -1567,14 +1600,14 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): cpdef pAdicZZpXFMElement unit_part(self): """ - Returns the unit part of ``self``, ie + Return the unit part of ``self``, ie ``self / uniformizer^(self.valuation())`` .. WARNING:: If this element has positive valuation then the unit part is not defined to the full precision of the ring. Asking - for the unit part of ZpFM(5)(0) will not raise an error, + for the unit part of ``ZpFM(5)(0)`` will not raise an error, but rather return itself. EXAMPLES:: @@ -1592,7 +1625,8 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): sage: a.precision_relative() 15 sage: a.unit_part() - 3 + 2*w^2 + w^4 + w^6 + w^7 + 3*w^8 + 3*w^9 + 2*w^11 + 3*w^12 + 3*w^13 + w^15 + 4*w^16 + 2*w^17 + w^18 + 3*w^21 + w^22 + 3*w^24 + 3 + 2*w^2 + w^4 + w^6 + w^7 + 3*w^8 + 3*w^9 + 2*w^11 + 3*w^12 + + 3*w^13 + w^15 + 4*w^16 + 2*w^17 + w^18 + 3*w^21 + w^22 + 3*w^24 The unit part inserts nonsense digits if this element has positive valuation:: @@ -1604,7 +1638,7 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): cdef long valuation_c(self): """ - Returns the valuation of ``self``. + Return the valuation of ``self``. EXAMPLES:: @@ -1621,7 +1655,8 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): sage: a.precision_relative() 15 sage: a.unit_part() - 3 + 2*w^2 + w^4 + w^6 + w^7 + 3*w^8 + 3*w^9 + 2*w^11 + 3*w^12 + 3*w^13 + w^15 + 4*w^16 + 2*w^17 + w^18 + 3*w^21 + w^22 + 3*w^24 + 3 + 2*w^2 + w^4 + w^6 + w^7 + 3*w^8 + 3*w^9 + 2*w^11 + 3*w^12 + + 3*w^13 + w^15 + 4*w^16 + 2*w^17 + w^18 + 3*w^21 + w^22 + 3*w^24 """ cdef long valuation, index ZZ_pX_min_val_coeff(valuation, index, self.value, self.prime_pow.pow_ZZ_tmp(1)[0]) @@ -1636,21 +1671,21 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): return index + valuation * self.prime_pow.e cdef ext_p_list(self, bint pos): - """ - Returns a list giving a series representation of ``self``. + r""" + Return a list giving a series representation of ``self``. - The returned list will consist of - + integers (in the eisenstein case) or + * integers (in the eisenstein case) or - + a lists of integers (in the unramified case). + * a lists of integers (in the unramified case). - ``self`` can be reconstructed - + as a sum of elements of the list times powers of the + * as a sum of elements of the list times powers of the uniformiser (in the eisenstein case), or - + as a sum of powers of `p` times polynomials in the + * as a sum of powers of `p` times polynomials in the generator (in the unramified case). - If ``pos`` is ``True``, all integers will be in the range `[0,p-1]`, @@ -1688,7 +1723,7 @@ cdef class pAdicZZpXFMElement(pAdicZZpXElement): def make_ZZpXFMElement(parent, f): """ - Creates a new ``pAdicZZpXFMElement`` out of an ``ntl_ZZ_pX`` f, with + Create a new ``pAdicZZpXFMElement`` out of an ``ntl_ZZ_pX`` ``f``, with parent ``parent``. For use with pickling. EXAMPLES:: @@ -1702,3 +1737,4 @@ def make_ZZpXFMElement(parent, f): True """ return pAdicZZpXFMElement(parent, f) + diff --git a/src/sage/rings/padics/padic_ZZ_pX_element.pyx b/src/sage/rings/padics/padic_ZZ_pX_element.pyx index 420bf72703f..1cdd02f9620 100644 --- a/src/sage/rings/padics/padic_ZZ_pX_element.pyx +++ b/src/sage/rings/padics/padic_ZZ_pX_element.pyx @@ -4,7 +4,7 @@ # distutils: library_dirs = NTL_LIBDIR # distutils: extra_link_args = NTL_LIBEXTRA # distutils: language = c++ -""" +r""" `p`-Adic ``ZZ_pX Element`` A common superclass implementing features shared by all elements that @@ -350,12 +350,14 @@ cdef class pAdicZZpXElement(pAdicExtElement): return ans def norm(self, base=None): - """ + r""" Return the absolute or relative norm of this element. - NOTE! This is not the `p`-adic absolute value. This is a - field theoretic norm down to a ground ring. If you want the - `p`-adic absolute value, use the ``abs()`` function instead. + .. NOTE:: + + This is not the `p`-adic absolute value. This is a + field theoretic norm down to a ground ring. If you want the + `p`-adic absolute value, use the ``abs()`` function instead. If ``base`` is given then ``base`` must be a subfield of the parent `L` of ``self``, in which case the norm is the relative @@ -426,7 +428,7 @@ cdef class pAdicZZpXElement(pAdicExtElement): return self.parent().ground_ring()(self.unit_part().matrix_mod_pn().det()) * norm_of_uniformizer**self.valuation() def trace(self, base = None): - """ + r""" Return the absolute or relative trace of this element. If ``base`` is given then ``base`` must be a subfield of the @@ -495,7 +497,7 @@ cdef class pAdicZZpXElement(pAdicExtElement): def _rational_(self): """ - Returns a rational approximation of ``self``. + Return a rational approximation of ``self``. This does not try to optimize which rational is picked: see ``algdep`` for another option. @@ -550,9 +552,10 @@ cdef class pAdicZZpXElement(pAdicExtElement): raise NotImplementedError def _test_preprocess_list(R, L): - """ - Given a list of elements convertible to ``ntl_ZZ_p``s, finds the - appropriate absolute precision and returns a list of either ``ntl_ZZs`` or ``ntl_ZZ_ps``. + r""" + Given a list of elements convertible to ``ntl_ZZ_p``s, find the + appropriate absolute precision and return a list of either + ``ntl_ZZs`` or ``ntl_ZZ_ps``. INPUT: @@ -564,16 +567,16 @@ def _test_preprocess_list(R, L): OUTPUT: - - ``LL`` -- if all inputs are integral, a list of ``ntl_ZZs``. - Otherwise, a list of ``ntl_ZZ_ps``, modulo `p^n` which is + - ``LL`` -- if all inputs are integral, a list of ``ntl_ZZs``; + otherwise, a list of ``ntl_ZZ_ps``, modulo `p^n` which is determined by the precision cap of ``R`` and the precisions of - the elements in ``L``. + the elements in ``L`` - - ``min_val`` -- A valuation by which to multiply the elements of - ``LL`` in order to recover the input elements of ``L``. + - ``min_val`` -- a valuation by which to multiply the elements of + ``LL`` in order to recover the input elements of ``L`` - - ``ctx`` -- An ``ntl_ZZ_p_Context`` giving the power of `p` - modulo which the elements in ``LL`` are defined. If ``None``, + - ``ctx`` -- an ``ntl_ZZ_p_Context`` giving the power of `p` + modulo which the elements in ``LL`` are defined; if ``None``, then the elements of ``LL`` are ``ntl_ZZs``. EXAMPLES:: @@ -604,7 +607,7 @@ def _test_preprocess_list(R, L): cdef preprocess_list(pAdicZZpXElement elt, L): """ - See the documentation for _test_preprocess_list + See the documentation for :func:`_test_preprocess_list`. """ cdef Py_ssize_t i cdef ZZ_c tmp @@ -678,7 +681,7 @@ cdef preprocess_list(pAdicZZpXElement elt, L): return L, min_val, ctx def _find_val_aprec_test(R, L): - """ + r""" Given a list ``L``, finds the minimum valuation, minimum absolute precision and minimum common type of the elements. @@ -689,20 +692,19 @@ def _find_val_aprec_test(R, L): OUTPUT: - - ``min_val`` -- the minimum valuation of any element in the list. + - ``min_val`` -- the minimum valuation of any element in the list - ``min_aprec`` -- the minimum absolute precision of any element - in the list. If infinite, a predefined constant ``big`` is - returned instead. - + in the list; if infinite, a predefined constant ``big`` is + returned instead - ``total_type`` -- - + If all elements are integers or ints, 2. + * if all elements are integers or ints: 2 - + If all elements are rationals or integers, 1. + * if all elements are rationals or integers: 1 - + If some elements have finite precision, 0. + * if some elements have finite precision: 0 EXAMPLES:: @@ -720,7 +722,7 @@ def _find_val_aprec_test(R, L): return find_val_aprec(R.prime_pow, L) cdef find_val_aprec(PowComputer_ext pp, L): - """ + r""" Given a list ``L``, finds the minimum valuation, minimum absolute precision and minimum common type of the elements. @@ -731,7 +733,7 @@ cdef find_val_aprec(PowComputer_ext pp, L): - ``L`` -- a list of integers, rationals, ``IntegerMods``, etc. - See the documentation for _find_val_aprec_test for more details. + See the documentation for :func:`_find_val_aprec_test` for more details. """ cdef Py_ssize_t i min_val = big @@ -754,31 +756,31 @@ cdef find_val_aprec(PowComputer_ext pp, L): def _test_get_val_prec(R, a): """ - Returns valuation, absolute precision and type of an input + Return valuation, absolute precision and type of an input element. INPUT: - - ``R`` -- A `p`-adic extension ring to provide a ``PowComputer_ext`` + - ``R`` -- a `p`-adic extension ring to provide a ``PowComputer_ext`` - - ``a`` -- A rational, integer, int, long, ``ntl_ZZ_p``, - ``ntl_ZZ``, ``IntegerMod`` or `p`-adic base element. + - ``a`` -- a rational, integer, int, long, ``ntl_ZZ_p``, + ``ntl_ZZ``, ``IntegerMod`` or `p`-adic base element OUTPUT: - ``val`` -- if ``a`` is exact, ``a.valuation(p)``, otherwise ``min(0, a.valuation())`` - - ``aprec`` -- the absolute precision of ``a``. If ``a`` is - exact, a large predefined constant. + - ``aprec`` -- the absolute precision of ``a``; if ``a`` is + exact, a large predefined constant - type -- - + 2 if ``a`` is an integer, int or long; + * 2 - if ``a`` is an integer, int or long - + 1 if ``a`` is a rational. + * 1 - if ``a`` is a rational - + 0 if ``a`` has finite precision. + * 0 - if ``a`` has finite precision EXAMPLES:: @@ -825,17 +827,17 @@ def _test_get_val_prec(R, a): return get_val_prec(R.prime_pow, a) cdef get_val_prec(PowComputer_ext pp, a): - """ - Returns valuation, absolute precision and type of an input element. + r""" + Return valuation, absolute precision and type of an input element. INPUT: - - ``pp`` -- A ``PowComputer_ext`` + - ``pp`` -- a ``PowComputer_ext`` - - ``a`` -- A rational, integer, int, long, ``ntl_ZZ_p``, - ``ntl_ZZ``, ``IntegerMod`` or `p`-adic base element. + - ``a`` -- a rational, integer, int, long, ``ntl_ZZ_p``, + ``ntl_ZZ``, ``IntegerMod`` or `p`-adic base element - See _test_get_val_prec for more details. + See :func:`_test_get_val_prec` for more details. """ cdef ntl_ZZ py_tmp if isinstance(a, Integer): @@ -897,3 +899,4 @@ cdef get_val_prec(PowComputer_ext pp, a): print(py_tmp) raise TypeError("modulus must be a positive power of the appropriate prime") raise TypeError("unsupported type for list element: %s" % type(a)) + diff --git a/src/sage/rings/padics/padic_capped_absolute_element.pyx b/src/sage/rings/padics/padic_capped_absolute_element.pyx index 6cdf3f0f8eb..a34e1b27d77 100644 --- a/src/sage/rings/padics/padic_capped_absolute_element.pyx +++ b/src/sage/rings/padics/padic_capped_absolute_element.pyx @@ -217,11 +217,11 @@ cdef class pAdicCappedAbsoluteElement(CAElement): sage: a.residue(-1) Traceback (most recent call last): ... - ValueError: cannot reduce modulo a negative power of p. + ValueError: cannot reduce modulo a negative power of p sage: a.residue(11) Traceback (most recent call last): ... - PrecisionError: not enough precision known in order to compute residue. + PrecisionError: not enough precision known in order to compute residue sage: a.residue(5, check_prec=False) 8 @@ -236,9 +236,9 @@ cdef class pAdicCappedAbsoluteElement(CAElement): if not isinstance(absprec, Integer): absprec = Integer(absprec) if check_prec and mpz_cmp_si((absprec).value, self.absprec) > 0: - raise PrecisionError("not enough precision known in order to compute residue.") + raise PrecisionError("not enough precision known in order to compute residue") elif mpz_sgn((absprec).value) < 0: - raise ValueError("cannot reduce modulo a negative power of p.") + raise ValueError("cannot reduce modulo a negative power of p") if field is None: field = (absprec == 1) elif field and absprec != 1: @@ -370,7 +370,7 @@ cdef class pAdicCappedAbsoluteElement(CAElement): cdef pAdicCappedAbsoluteElement ans, unit if mpz_fits_slong_p(self.prime_pow.prime.value) == 0: - raise NotImplementedError("The prime %s does not fit in a long" % self.prime_pow.prime) + raise NotImplementedError("the prime %s does not fit in a long" % self.prime_pow.prime) p = self.prime_pow.prime ans = self._new_c() @@ -431,7 +431,7 @@ cdef class pAdicCappedAbsoluteElement(CAElement): cdef pAdicCappedAbsoluteElement ans if mpz_fits_slong_p(self.prime_pow.prime.value) == 0: - raise NotImplementedError("The prime %s does not fit in a long" % self.prime_pow.prime) + raise NotImplementedError("the prime %s does not fit in a long" % self.prime_pow.prime) p = self.prime_pow.prime ans = self._new_c() @@ -487,7 +487,7 @@ cdef class pAdicCappedAbsoluteElement(CAElement): cdef pAdicCappedAbsoluteElement ans if mpz_fits_slong_p(self.prime_pow.prime.value) == 0: - raise NotImplementedError("The prime %s does not fit in a long" % self.prime_pow.prime) + raise NotImplementedError("the prime %s does not fit in a long" % self.prime_pow.prime) p = self.prime_pow.prime ans = self._new_c() diff --git a/src/sage/rings/padics/padic_capped_relative_element.pyx b/src/sage/rings/padics/padic_capped_relative_element.pyx index 26fec12d26b..c59c8e3573b 100644 --- a/src/sage/rings/padics/padic_capped_relative_element.pyx +++ b/src/sage/rings/padics/padic_capped_relative_element.pyx @@ -140,11 +140,11 @@ cdef class pAdicCappedRelativeElement(CRElement): sage: R(pari(R(0,5))) O(5^5) - # todo: doctests for converting from other types of p-adic rings + .. TODO:: doctests for converting from other types of p-adic rings """ def lift(self): - """ + r""" Return an integer or rational congruent to ``self`` modulo ``self``'s precision. If a rational is returned, its denominator will equal ``p^ordp(self)``. @@ -166,7 +166,7 @@ cdef class pAdicCappedRelativeElement(CRElement): TESTS:: - sage: O(5^5).lift() #indirect doctest + sage: O(5^5).lift() # indirect doctest 0 sage: R = Qp(5); R(0).lift() 0 @@ -197,7 +197,7 @@ cdef class pAdicCappedRelativeElement(CRElement): def __pari__(self): """ - Converts this element to an equivalent pari element. + Convert this element to an equivalent pari element. EXAMPLES:: @@ -212,7 +212,7 @@ cdef class pAdicCappedRelativeElement(CRElement): cdef pari_gen _to_gen(self): """ - Converts this element to an equivalent pari element. + Convert this element to an equivalent pari element. EXAMPLES:: @@ -236,8 +236,8 @@ cdef class pAdicCappedRelativeElement(CRElement): self.prime_pow.pow_mpz_t_tmp(self.relprec), self.unit) def _integer_(self, Z=None): - """ - Returns an integer congruent to this element modulo + r""" + Return an integer congruent to this element modulo ``p^self.absolute_precision()``. EXAMPLES:: @@ -246,26 +246,28 @@ cdef class pAdicCappedRelativeElement(CRElement): 95367431640624 """ if self.ordp < 0: - raise ValueError("Cannot form an integer out of a p-adic field element with negative valuation") + raise ValueError("cannot form an integer out of a p-adic field element with negative valuation") return self.lift_c() def residue(self, absprec=1, field=None, check_prec=True): - """ - Reduces this element modulo `p^{\mathrm{absprec}}`. + r""" + Reduce this element modulo `p^{\mathrm{absprec}}`. INPUT: - ``absprec`` -- a non-negative integer (default: ``1``) - - ``field`` -- boolean (default ``None``). Whether to return an element of GF(p) or Zmod(p). + - ``field`` -- boolean (default ``None``); whether to return an element + of `\GF{p}` or `\ZZ / p\ZZ` - - ``check_prec`` -- boolean (default ``True``). Whether to raise an error if this - element has insufficient precision to determine the reduction. + - ``check_prec`` -- boolean (default ``True``); whether to raise + an error if this element has insufficient precision to determine + the reduction OUTPUT: This element reduced modulo `p^\mathrm{absprec}` as an element of - `\ZZ/p^\mathrm{absprec}\ZZ` + `\ZZ/p^\mathrm{absprec}\ZZ`. EXAMPLES:: @@ -300,7 +302,7 @@ cdef class pAdicCappedRelativeElement(CRElement): sage: b.residue() Traceback (most recent call last): ... - ValueError: element must have non-negative valuation in order to compute residue. + ValueError: element must have non-negative valuation in order to compute residue TESTS:: @@ -311,11 +313,11 @@ cdef class pAdicCappedRelativeElement(CRElement): sage: a.residue(-1) Traceback (most recent call last): ... - ValueError: cannot reduce modulo a negative power of p. + ValueError: cannot reduce modulo a negative power of p sage: a.residue(5) Traceback (most recent call last): ... - PrecisionError: not enough precision known in order to compute residue. + PrecisionError: not enough precision known in order to compute residue sage: a.residue(5, check_prec=False) 8 @@ -332,11 +334,11 @@ cdef class pAdicCappedRelativeElement(CRElement): if not isinstance(absprec, Integer): absprec = Integer(absprec) if check_prec and absprec > self.precision_absolute(): - raise PrecisionError("not enough precision known in order to compute residue.") + raise PrecisionError("not enough precision known in order to compute residue") elif absprec < 0: - raise ValueError("cannot reduce modulo a negative power of p.") + raise ValueError("cannot reduce modulo a negative power of p") if self.ordp < 0: - raise ValueError("element must have non-negative valuation in order to compute residue.") + raise ValueError("element must have non-negative valuation in order to compute residue") if field is None: field = (absprec == 1) elif field and absprec != 1: @@ -358,7 +360,7 @@ cdef class pAdicCappedRelativeElement(CRElement): def _log_binary_splitting(self, aprec, mina=0): r""" - Return ``\log(self)`` for ``self`` equal to 1 in the residue field + Return ``\log(self)`` for ``self`` equal to 1 in the residue field. This is a helper method for :meth:`log`. It uses a fast binary splitting algorithm. @@ -366,13 +368,13 @@ cdef class pAdicCappedRelativeElement(CRElement): INPUT: - ``aprec`` -- an integer, the precision to which the result is - correct. ``aprec`` must not exceed the precision cap of the ring over - which this element is defined. + correct; ``aprec`` must not exceed the precision cap of the ring over + which this element is defined - ``mina`` -- an integer (default: 0), the series will check `n` up to this valuation (and beyond) to see if they can contribute to the - series. + series - NOTE:: + .. NOTE:: The function does not check that its argument ``self`` is 1 in the residue field. If this assumption is not fulfilled @@ -386,18 +388,18 @@ cdef class pAdicCappedRelativeElement(CRElement): 2. Write - .. MATH:: + .. MATH:: - u^{p-1} = \prod_{i=1}^\infty (1 - a_i p^{(v+1)*2^i}) + u^{p-1} = \prod_{i=1}^\infty (1 - a_i p^{(v+1)*2^i}) - with `0 \leq a_i < p^{(v+1)*2^i}` and compute - `\log(1 - a_i p^{(v+1)*2^i})` using the standard Taylor expansion + with `0 \leq a_i < p^{(v+1)*2^i}` and compute + `\log(1 - a_i p^{(v+1)*2^i})` using the standard Taylor expansion - .. MATH:: + .. MATH:: - \log(1 - x) = -x - 1/2 x^2 - 1/3 x^3 - 1/4 x^4 - 1/5 x^5 - \cdots + \log(1 - x) = -x - 1/2 x^2 - 1/3 x^3 - 1/4 x^4 - 1/5 x^5 - \cdots - together with a binary splitting method. + together with a binary splitting method. 3. Divide the result by `p^v` @@ -422,7 +424,7 @@ cdef class pAdicCappedRelativeElement(CRElement): cdef pAdicCappedRelativeElement ans if mpz_fits_slong_p(self.prime_pow.prime.value) == 0: - raise NotImplementedError("The prime %s does not fit in a long" % self.prime_pow.prime) + raise NotImplementedError("the prime %s does not fit in a long" % self.prime_pow.prime) p = self.prime_pow.prime ans = self._new_c() @@ -436,7 +438,7 @@ cdef class pAdicCappedRelativeElement(CRElement): return ans def _exp_binary_splitting(self, aprec): - """ + r""" Compute the exponential power series of this element This is a helper method for :meth:`exp`. @@ -446,7 +448,7 @@ cdef class pAdicCappedRelativeElement(CRElement): - ``aprec`` -- an integer, the precision to which to compute the exponential - NOTE:: + .. NOTE:: The function does not check that its argument ``self`` is the disk of convergence of ``exp``. If this assumption is not @@ -485,7 +487,7 @@ cdef class pAdicCappedRelativeElement(CRElement): cdef Integer selfint = self.lift_c() if mpz_fits_slong_p(self.prime_pow.prime.value) == 0: - raise NotImplementedError("The prime %s does not fit in a long" % self.prime_pow.prime) + raise NotImplementedError("the prime %s does not fit in a long" % self.prime_pow.prime) p = self.prime_pow.prime ans = self._new_c() @@ -498,22 +500,22 @@ cdef class pAdicCappedRelativeElement(CRElement): return ans def _exp_newton(self, aprec, log_algorithm=None): - """ - Compute the exponential power series of this element + r""" + Compute the exponential power series of this element. This is a helper method for :meth:`exp`. INPUT: - - ``aprec`` -- an integer, the precision to which to compute the + - ``aprec`` -- an integer; the precision to which to compute the exponential - - ``log_algorithm`` (default: None) -- the algorithm used for - computing the logarithm. This attribute is passed to the log - method. See :meth:`log` for more details about the possible - algorithms. + - ``log_algorithm`` -- (default: ``None``) the algorithm used for + computing the logarithm; this attribute is passed to the :meth:`log` + method; see :meth:`log` for more details about the possible + algorithms - NOTE:: + .. NOTE:: The function does not check that its argument ``self`` is the disk of convergence of ``exp``. If this assumption is not @@ -543,7 +545,7 @@ cdef class pAdicCappedRelativeElement(CRElement): cdef Integer selfint = self.lift_c() if mpz_fits_slong_p(self.prime_pow.prime.value) == 0: - raise NotImplementedError("The prime %s does not fit in a long" % self.prime_pow.prime) + raise NotImplementedError("the prime %s does not fit in a long" % self.prime_pow.prime) p = self.prime_pow.prime ans = self._new_c() @@ -574,17 +576,17 @@ def unpickle_pcre_v1(R, unit, ordp, relprec): return unpickle_cre_v2(pAdicCappedRelativeElement, R, unit, ordp, relprec) def base_p_list(Integer n, bint pos, PowComputer_class prime_pow): - """ - Returns a base-`p` list of digits of ``n``. + r""" + Return a base-`p` list of digits of ``n``. INPUT: - - ``n`` -- a positive Integer. + - ``n`` -- a positive :class:`Integer` - - ``pos`` -- a boolean. If True, then returns the standard base `p` expansion. - Otherwise, the digits lie in the range `-p/2` to `p/2`. + - ``pos`` -- a boolean; if ``True``, then returns the standard base `p` + expansion, otherwise the digits lie in the range `-p/2` to `p/2`. - - ``prime_pow`` -- A PowComputer giving the prime. + - ``prime_pow`` -- a :class:`PowComputer` giving the prime EXAMPLES:: @@ -608,3 +610,4 @@ def base_p_list(Integer n, bint pos, PowComputer_class prime_pow): cdef ExpansionIter expansion = ExpansionIter(dummy, n.exact_log(p) + 2, mode) mpz_set(expansion.curvalue, n.value) return trim_zeros(list(expansion)) + diff --git a/src/sage/rings/padics/padic_ext_element.pyx b/src/sage/rings/padics/padic_ext_element.pyx index f1f663cc6da..7ea8b0ddcc6 100644 --- a/src/sage/rings/padics/padic_ext_element.pyx +++ b/src/sage/rings/padics/padic_ext_element.pyx @@ -4,7 +4,7 @@ # distutils: library_dirs = NTL_LIBDIR # distutils: extra_link_args = NTL_LIBEXTRA # distutils: language = c++ -""" +r""" p-Adic Extension Element A common superclass for all elements of extension rings and field of `\ZZ_p` and @@ -37,11 +37,13 @@ cdef class pAdicExtElement(pAdicGenericElement): """ Sets self from a list. - The list should either be uniform in type, or all of the entries should be coercible to integers. - If any of the entries in L is a list, L will be cast to a ZZ_pEX + The list should either be uniform in type, or all of the entries + should be coercible to integers. If any of the entries in ``L`` + is a list, ``L`` will be cast to a ZZ_pEX. INPUT: - L -- a list. + + - ``L`` -- a list """ raise NotImplementedError @@ -270,7 +272,7 @@ cdef class pAdicExtElement(pAdicGenericElement): def _const_term_test(self): """ - Returns the constant term of a polynomial representing self. + Return the constant term of a polynomial representing ``self``. This function is mainly for troubleshooting, and the meaning of the return value will depend on whether self is capped @@ -294,26 +296,27 @@ cdef class pAdicExtElement(pAdicGenericElement): raise NotImplementedError def _ext_p_list(self, pos): - """ - Returns a list of integers (in the Eisenstein case) or a list - of lists of integers (in the unramified case). self can be - reconstructed as a sum of elements of the list times powers of - the uniformiser (in the Eisenstein case), or as a sum of + r""" + Return a list of integers (in the Eisenstein case) or a list + of lists of integers (in the unramified case). + + ``self`` can be reconstructed as a sum of elements of the list times + powers of the uniformiser (in the Eisenstein case), or as a sum of powers of the p times polynomials in the generator (in the unramified case). - Note that zeros are truncated from the returned list, so you - must use the valuation() function to completely recover self. + Note that zeros are truncated from the returned list, so you must + use the :func:`valuation()` function to completely recover ``self``. INPUT: - - pos -- bint. If True, all integers will be in the range [0,p-1], - otherwise they will be in the range [(1-p)/2, p/2]. + - ``pos`` -- boolean; if ``True``, all integers will be in the range + `[0,p-1]`, otherwise they will be in the range `[(1-p)/2, p/2]` OUTPUT: - - L -- A list of integers or list of lists giving the - series expansion of self. + - a list of integers or list of lists giving the + series expansion of ``self`` EXAMPLES:: @@ -331,13 +334,12 @@ cdef class pAdicExtElement(pAdicGenericElement): return self.ext_p_list(pos) def frobenius(self, arithmetic=True): - """ - Returns the image of this element under the Frobenius automorphism + r""" + Return the image of this element under the Frobenius automorphism applied to its parent. INPUT: - - ``self`` -- an element of an unramified extension. - ``arithmetic`` -- whether to apply the arithmetic Frobenius (acting by raising to the `p`-th power on the residue field). If ``False`` is provided, the image of geometric Frobenius (raising to the `(1/p)`-th @@ -389,7 +391,7 @@ cdef class pAdicExtElement(pAdicGenericElement): return ans 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). @@ -412,7 +414,7 @@ cdef class pAdicExtElement(pAdicGenericElement): INPUT: - - ``absprec`` - a non-negative integer (default: ``1``) + - ``absprec`` -- a non-negative integer (default: ``1``) - ``field`` -- boolean (default ``None``). For precision 1, whether to return an element of the residue field or a residue ring. Currently unused. @@ -449,7 +451,7 @@ cdef class pAdicExtElement(pAdicGenericElement): sage: a.residue(2) Traceback (most recent call last): ... - NotImplementedError: reduction modulo p^n with n>1. + NotImplementedError: reduction modulo p^n with n>1 Eisenstein case:: @@ -461,9 +463,9 @@ cdef class pAdicExtElement(pAdicGenericElement): sage: a.residue(2) Traceback (most recent call last): ... - NotImplementedError: residue() not implemented in extensions for absprec larger than one. + NotImplementedError: residue() not implemented in extensions for absprec larger than one - TESTS: + TESTS:: sage: K = Qp(3,5) sage: S. = R[] @@ -471,7 +473,7 @@ cdef class pAdicExtElement(pAdicGenericElement): sage: (a/3).residue(0) Traceback (most recent call last): ... - ValueError: element must have non-negative valuation in order to compute residue. + ValueError: element must have non-negative valuation in order to compute residue sage: R = ZpFM(3,5) sage: S. = R[] @@ -481,23 +483,22 @@ cdef class pAdicExtElement(pAdicGenericElement): sage: a.residue(-1) Traceback (most recent call last): ... - ValueError: cannot reduce modulo a negative power of the uniformizer. + ValueError: cannot reduce modulo a negative power of the uniformizer sage: a.residue(16) Traceback (most recent call last): ... - NotImplementedError: residue() not implemented in extensions for absprec larger than one. - + NotImplementedError: residue() not implemented in extensions for absprec larger than one """ if absprec < 0: - raise ValueError("cannot reduce modulo a negative power of the uniformizer.") + raise ValueError("cannot reduce modulo a negative power of the uniformizer") if self.valuation() < 0: - raise ValueError("element must have non-negative valuation in order to compute residue.") + raise ValueError("element must have non-negative valuation in order to compute residue") R = self.parent() if check_prec and (R.is_fixed_mod() or R.is_floating_point()): check_prec = False if check_prec and absprec > self.precision_absolute(): from precision_error import PrecisionError - raise PrecisionError("not enough precision known in order to compute residue.") + raise PrecisionError("not enough precision known in order to compute residue") if field and absprec != 1: raise ValueError("field keyword may only be set at precision 1") @@ -507,4 +508,5 @@ cdef class pAdicExtElement(pAdicGenericElement): elif absprec == 1: return R.residue_field()(self.expansion(0)) else: - raise NotImplementedError("residue() not implemented in extensions for absprec larger than one.") + raise NotImplementedError("residue() not implemented in extensions for absprec larger than one") + diff --git a/src/sage/rings/padics/padic_fixed_mod_element.pyx b/src/sage/rings/padics/padic_fixed_mod_element.pyx index 4211651be85..4b2723b7a8a 100644 --- a/src/sage/rings/padics/padic_fixed_mod_element.pyx +++ b/src/sage/rings/padics/padic_fixed_mod_element.pyx @@ -289,7 +289,7 @@ cdef class pAdicFixedModElement(FMElement): sage: a.residue(-1) Traceback (most recent call last): ... - ValueError: Cannot reduce modulo a negative power of p. + ValueError: cannot reduce modulo a negative power of p sage: a.residue(5) 8 @@ -306,7 +306,7 @@ cdef class pAdicFixedModElement(FMElement): if not isinstance(absprec, Integer): absprec = Integer(absprec) if absprec < 0: - raise ValueError("Cannot reduce modulo a negative power of p.") + raise ValueError("cannot reduce modulo a negative power of p") if field is None: field = (absprec == 1) elif field and absprec != 1: @@ -439,7 +439,7 @@ cdef class pAdicFixedModElement(FMElement): cdef pAdicFixedModElement ans if mpz_fits_slong_p(self.prime_pow.prime.value) == 0: - raise NotImplementedError("The prime %s does not fit in a long" % self.prime_pow.prime) + raise NotImplementedError("the prime %s does not fit in a long" % self.prime_pow.prime) p = self.prime_pow.prime ans = self._new_c() @@ -497,7 +497,7 @@ cdef class pAdicFixedModElement(FMElement): cdef pAdicFixedModElement ans if mpz_fits_slong_p(self.prime_pow.prime.value) == 0: - raise NotImplementedError("The prime %s does not fit in a long" % self.prime_pow.prime) + raise NotImplementedError("the prime %s does not fit in a long" % self.prime_pow.prime) p = self.prime_pow.prime ans = self._new_c() @@ -552,7 +552,7 @@ cdef class pAdicFixedModElement(FMElement): cdef pAdicFixedModElement ans if mpz_fits_slong_p(self.prime_pow.prime.value) == 0: - raise NotImplementedError("The prime %s does not fit in a long" % self.prime_pow.prime) + raise NotImplementedError("the prime %s does not fit in a long" % self.prime_pow.prime) p = self.prime_pow.prime ans = self._new_c() diff --git a/src/sage/rings/padics/padic_floating_point_element.pyx b/src/sage/rings/padics/padic_floating_point_element.pyx index 7b6cf59dabe..89132c93025 100644 --- a/src/sage/rings/padics/padic_floating_point_element.pyx +++ b/src/sage/rings/padics/padic_floating_point_element.pyx @@ -1,4 +1,4 @@ -""" +r""" `p`-Adic Floating Point Elements Elements of `p`-Adic Rings with Floating Point Precision @@ -139,7 +139,7 @@ cdef class pAdicFloatingPointElement(FPElement): """ def lift(self): - """ + r""" Return an integer or rational congruent to ``self`` modulo ``self``'s precision. If a rational is returned, its denominator will equal ``p^ordp(self)``. @@ -158,7 +158,7 @@ cdef class pAdicFloatingPointElement(FPElement): return self.lift_c() cdef lift_c(self): - """ + r""" Implementation of lift. TESTS:: @@ -192,7 +192,7 @@ cdef class pAdicFloatingPointElement(FPElement): def __pari__(self): """ - Converts this element to an equivalent pari element. + Convert this element to an equivalent pari element. EXAMPLES:: @@ -205,7 +205,7 @@ cdef class pAdicFloatingPointElement(FPElement): cdef pari_gen _to_gen(self): """ - Converts this element to an equivalent pari element. + Convert this element to an equivalent pari element. EXAMPLES:: @@ -224,8 +224,8 @@ cdef class pAdicFloatingPointElement(FPElement): self.prime_pow.pow_mpz_t_top(), self.unit) def _integer_(self, Z=None): - """ - Returns an integer congruent to this element modulo + r""" + Return an integer congruent to this element modulo ``p^self.absolute_precision()``. EXAMPLES:: @@ -234,25 +234,26 @@ cdef class pAdicFloatingPointElement(FPElement): 95367431640624 """ if self.ordp < 0: - raise ValueError("Cannot form an integer out of a p-adic field element with negative valuation") + raise ValueError("cannot form an integer out of a p-adic field element with negative valuation") return self.lift_c() def residue(self, absprec=1, field=None, check_prec=False): - """ - Reduces this element modulo `p^{\mathrm{absprec}}`. + r""" + Reduce this element modulo `p^{\mathrm{absprec}}`. INPUT: - ``absprec`` -- a non-negative integer (default: ``1``) - - ``field`` -- boolean (default ``None``). Whether to return an element of GF(p) or Zmod(p). + - ``field`` -- boolean (default ``None``); whether to return an + element of `\GF{p}` or `\ZZ / p\ZZ` - - ``check_prec`` -- boolean (default ``False``). No effect (for compatibility with other types). + - ``check_prec`` -- ignored (for compatibility with other types) OUTPUT: - This element reduced modulo `p^\mathrm{absprec}` as an element of - `\ZZ/p^\mathrm{absprec}\ZZ` + This element reduced modulo `p^{\mathrm{absprec}}` as an element of + `\ZZ/p^{\mathrm{absprec}}\ZZ`. EXAMPLES:: @@ -273,7 +274,7 @@ cdef class pAdicFloatingPointElement(FPElement): sage: b.residue() Traceback (most recent call last): ... - ValueError: element must have non-negative valuation in order to compute residue. + ValueError: element must have non-negative valuation in order to compute residue TESTS:: @@ -284,7 +285,7 @@ cdef class pAdicFloatingPointElement(FPElement): sage: a.residue(-1) Traceback (most recent call last): ... - ValueError: cannot reduce modulo a negative power of p. + ValueError: cannot reduce modulo a negative power of p sage: a.residue(5) 8 @@ -296,9 +297,9 @@ cdef class pAdicFloatingPointElement(FPElement): if not isinstance(absprec, Integer): absprec = Integer(absprec) if mpz_sgn((absprec).value) < 0: - raise ValueError("cannot reduce modulo a negative power of p.") + raise ValueError("cannot reduce modulo a negative power of p") if self.ordp < 0: - raise ValueError("element must have non-negative valuation in order to compute residue.") + raise ValueError("element must have non-negative valuation in order to compute residue") if field is None: field = (absprec == 1) elif field and absprec != 1: @@ -321,7 +322,7 @@ cdef class pAdicFloatingPointElement(FPElement): return Mod(selfvalue, modulus) def _exp_binary_splitting(self, aprec): - """ + r""" Compute the exponential power series of this element This is a helper method for :meth:`exp`. @@ -331,7 +332,7 @@ cdef class pAdicFloatingPointElement(FPElement): - ``aprec`` -- an integer, the precision to which to compute the exponential - NOTE:: + .. NOTE:: The function does not check that its argument ``self`` is the disk of convergence of ``exp``. If this assumption is not @@ -343,7 +344,7 @@ cdef class pAdicFloatingPointElement(FPElement): .. MATH:: - self = \sum_{i=1}^\infty a_i p^{2^i} + \mathrm{self} = \sum_{i=1}^\infty a_i p^{2^i} with `0 \leq a_i < p^{2^i}` and compute `\exp(a_i p^{2^i})` using the standard Taylor expansion @@ -370,7 +371,7 @@ cdef class pAdicFloatingPointElement(FPElement): cdef Integer selfint = self.lift_c() if mpz_fits_slong_p(self.prime_pow.prime.value) == 0: - raise NotImplementedError("The prime %s does not fit in a long" % self.prime_pow.prime) + raise NotImplementedError("the prime %s does not fit in a long" % self.prime_pow.prime) p = self.prime_pow.prime ans = self._new_c() @@ -382,22 +383,22 @@ cdef class pAdicFloatingPointElement(FPElement): return ans def _exp_newton(self, aprec, log_algorithm=None): - """ + r""" Compute the exponential power series of this element This is a helper method for :meth:`exp`. INPUT: - - ``aprec`` -- an integer, the precision to which to compute the + - ``aprec`` -- an integer; the precision to which to compute the exponential - - ``log_algorithm`` (default: None) -- the algorithm used for - computing the logarithm. This attribute is passed to the log - method. See :meth:`log` for more details about the possible - algorithms. + - ``log_algorithm`` -- (default: ``None``) the algorithm used for + computing the logarithm. This attribute is passed to the :meth:`log` + method; see :meth:`log` for more details about the possible + algorithms - NOTE:: + .. NOTE:: The function does not check that its argument ``self`` is the disk of convergence of ``exp``. If this assumption is not @@ -405,11 +406,11 @@ cdef class pAdicFloatingPointElement(FPElement): ALGORITHM: - Solve the equation `\log(x) = self` using the Newton scheme:: + Solve the equation `\log(x) = \mathrm{self}` using the Newton scheme:: .. MATH:: - x_{i+1} = x_i \cdot (1 + self - \log(x_i)) + x_{i+1} = x_i \cdot (1 + \mathrm{self} - \log(x_i)). The binary complexity of this algorithm is roughly the same than that of the computation of the logarithm. @@ -427,7 +428,7 @@ cdef class pAdicFloatingPointElement(FPElement): cdef Integer selfint = self.lift_c() if mpz_fits_slong_p(self.prime_pow.prime.value) == 0: - raise NotImplementedError("The prime %s does not fit in a long" % self.prime_pow.prime) + raise NotImplementedError("the prime %s does not fit in a long" % self.prime_pow.prime) p = self.prime_pow.prime ans = self._new_c() @@ -441,3 +442,4 @@ cdef class pAdicFloatingPointElement(FPElement): sig_off() return ans + diff --git a/src/sage/rings/padics/padic_generic.py b/src/sage/rings/padics/padic_generic.py index 6179e29f022..a76fba9a87e 100644 --- a/src/sage/rings/padics/padic_generic.py +++ b/src/sage/rings/padics/padic_generic.py @@ -48,7 +48,7 @@ def __init__(self, base, p, prec, print_mode, names, element_class, category=Non INPUT: - - ``base`` -- Base ring. + - ``base`` -- base ring - ``p`` -- prime - ``print_mode`` -- dictionary of print options - ``names`` -- how to print the uniformizer @@ -90,16 +90,26 @@ def some_elements(self): return L def _modified_print_mode(self, print_mode): + r""" - Return a dictionary of print options, starting with self's + Return a dictionary of print options, starting with ``self``'s print options but modified by the options in the dictionary - print_mode. + ``print_mode``. INPUT: - - ``print_mode`` -- dictionary with keys in ['mode', 'pos', 'ram_name', - 'unram_name', 'var_name', 'max_ram_terms', 'max_unram_terms', - 'max_terse_terms', 'sep', 'alphabet'] + - ``print_mode`` -- dictionary with keys in + + * ``mode`` + * ``pos`` + * ``ram_name`` + * ``unram_name`` + * ``var_name`` + * ``max_ram_terms`` + * ``max_unram_terms`` + * ``max_terse_terms`` + * ``sep`` + * ``alphabet`` EXAMPLES:: @@ -120,7 +130,7 @@ def _modified_print_mode(self, print_mode): def ngens(self): r""" - Return the number of generators of self. + Return the number of generators of ``self``. We conventionally define this as 1: for base rings, we take a uniformizer as the generator; for extension rings, we take a @@ -152,7 +162,7 @@ def gens(self): def __richcmp__(self, other, op): r""" - Return 0 if self == other, and 1 or -1 otherwise. + Rich comparison of ``self`` with ``other``. We consider two p-adic rings or fields to be equal if they are equal mathematically, and also have the same precision cap and @@ -197,14 +207,6 @@ def print_mode(self): r""" Return the current print mode as a string. - INPUT: - - - ``self`` -- a p-adic field - - OUTPUT: - - The print mode for this p-adic field, as a string. - EXAMPLES:: sage: R = Qp(7,5, 'capped-rel') @@ -215,13 +217,7 @@ def print_mode(self): def characteristic(self): r""" - Return the characteristic of self, which is always 0. - - INPUT: - - - ``self`` -- a p-adic parent - - OUTPUT: self's characteristic, i.e., 0. + Return the characteristic of ``self``, which is always 0. EXAMPLES:: @@ -234,10 +230,6 @@ def prime(self): r""" Return the prime, ie the characteristic of the residue field. - INPUT: - - - ``self`` -- a p-adic parent - OUTPUT: The characteristic of the residue field. @@ -254,7 +246,7 @@ def uniformizer_pow(self, n): r""" Return `p^n`, as an element of ``self``. - If `n` is infinity, returns 0. + If ``n`` is infinity, returns 0. EXAMPLES:: @@ -271,7 +263,7 @@ def uniformizer_pow(self, n): def _unram_print(self): r""" For printing. Will be ``None`` if the unramified subextension - of self is of degree 1 over `\ZZ_p` or `\QQ_p`. + of ``self`` is of degree 1 over `\ZZ_p` or `\QQ_p`. EXAMPLES:: @@ -299,14 +291,6 @@ def residue_class_field(self): r""" Return the residue class field. - INPUT: - - - ``self`` -- a p-adic ring - - OUTPUT: - - The residue field. - EXAMPLES:: sage: R = Zp(3,5,'fixed-mod') @@ -321,14 +305,6 @@ def residue_field(self): r""" Return the residue class field. - INPUT: - - - ``self`` -- a p-adic ring - - OUTPUT: - - The residue field. - EXAMPLES:: sage: R = Zp(3,5,'fixed-mod') @@ -340,7 +316,8 @@ def residue_field(self): def residue_ring(self, n): r""" - Return the quotient of the ring of integers by the nth power of the maximal ideal. + Return the quotient of the ring of integers by the ``n``-th + power of the maximal ideal. EXAMPLES:: @@ -355,14 +332,6 @@ def residue_system(self): r""" Return a list of elements representing all the residue classes. - INPUT: - - - ``self`` -- a p-adic ring - - OUTPUT: - - A list of elements representing all the residue classes. - EXAMPLES:: sage: R = Zp(3, 5,'fixed-mod') @@ -373,8 +342,8 @@ def residue_system(self): def _fraction_field_key(self, print_mode=None): r""" - Change print_mode from a dictionary to a tuple, - raising a deprecation warning if it is present. + Change ``print_mode`` from a dictionary to a tuple, raising + a deprecation warning if it is present. EXAMPLES:: @@ -405,12 +374,12 @@ def fraction_field(self, print_mode=None): INPUT: - - ``print_mode`` -- a dictionary containing print options. - Defaults to the same options as this ring. + - ``print_mode`` -- (optional) a dictionary containing print options; + defaults to the same options as this ring OUTPUT: - The fraction field of this ring. + - the fraction field of this ring EXAMPLES:: @@ -465,12 +434,12 @@ def integer_ring(self, print_mode=None): INPUT: - - ``print_mode`` -- a dictionary containing print options. - Defaults to the same options as this ring. + - ``print_mode`` -- (optional) a dictionary containing print options; + defaults to the same options as this ring OUTPUT: - The ring of elements of this field with nonnegative valuation. + - the ring of elements of this field with nonnegative valuation EXAMPLES:: @@ -524,16 +493,13 @@ def integer_ring(self, print_mode=None): def teichmuller(self, x, prec = None): r""" - Return the Teichmüller representative of `x`. - - INPUT: + Return the Teichmüller representative of ``x``. - - ``self`` -- a p-adic ring - ``x`` -- something that can be cast into ``self`` OUTPUT: - The Teichmüller lift of `x`. + - the Teichmüller lift of ``x`` EXAMPLES:: @@ -554,13 +520,16 @@ def teichmuller(self, x, prec = None): sage: f = x^5 + 75*x^3 - 15*x^2 +125*x - 5 sage: W. = R.ext(f) sage: y = W.teichmuller(3); y - 3 + 3*w^5 + w^7 + 2*w^9 + 2*w^10 + 4*w^11 + w^12 + 2*w^13 + 3*w^15 + 2*w^16 + 3*w^17 + w^18 + 3*w^19 + 3*w^20 + 2*w^21 + 2*w^22 + 3*w^23 + 4*w^24 + O(w^25) + 3 + 3*w^5 + w^7 + 2*w^9 + 2*w^10 + 4*w^11 + w^12 + 2*w^13 + 3*w^15 + + 2*w^16 + 3*w^17 + w^18 + 3*w^19 + 3*w^20 + 2*w^21 + 2*w^22 + + 3*w^23 + 4*w^24 + O(w^25) sage: y^5 == y True sage: g = x^3 + 3*x + 3 sage: A. = R.ext(g) sage: b = A.teichmuller(1 + 2*a - a^2); b - (4*a^2 + 2*a + 1) + 2*a*5 + (3*a^2 + 1)*5^2 + (a + 4)*5^3 + (a^2 + a + 1)*5^4 + O(5^5) + (4*a^2 + 2*a + 1) + 2*a*5 + (3*a^2 + 1)*5^2 + (a + 4)*5^3 + + (a^2 + a + 1)*5^4 + O(5^5) sage: b^125 == b True @@ -578,7 +547,7 @@ def teichmuller(self, x, prec = None): # Since Teichmüller representatives are defined at infinite precision, # we can lift to precision prec, as long as the absolute precision of ans is positive. if ans.precision_absolute() <= 0: - raise ValueError("Not enough precision to determine Teichmuller representative") + raise ValueError("not enough precision to determine Teichmuller representative") if ans.valuation() > 0: return self(0) if prec is None else self(0, prec) ans = ans.lift_to_precision(prec) @@ -592,10 +561,6 @@ def teichmuller_system(self): Return a set of Teichmüller representatives for the invertible elements of `\ZZ / p\ZZ`. - INPUT: - - - ``self`` -- a p-adic ring - OUTPUT: A list of Teichmüller representatives for the invertible elements @@ -746,7 +711,7 @@ def _test_add(self, **options): INPUT: - - ``options`` -- any keyword arguments accepted by :meth:`_tester`. + - ``options`` -- any keyword arguments accepted by :meth:`_tester` EXAMPLES:: @@ -785,7 +750,7 @@ def _test_sub(self, **options): INPUT: - - ``options`` -- any keyword arguments accepted by :meth:`_tester`. + - ``options`` -- any keyword arguments accepted by :meth:`_tester` EXAMPLES:: @@ -824,7 +789,7 @@ def _test_invert(self, **options): INPUT: - - ``options`` -- any keyword arguments accepted by :meth:`_tester`. + - ``options`` -- any keyword arguments accepted by :meth:`_tester` EXAMPLES:: @@ -833,7 +798,6 @@ def _test_invert(self, **options): .. SEEALSO:: :class:`TestSuite` - """ tester = self._tester(**options) @@ -863,7 +827,7 @@ def _test_mul(self, **options): INPUT: - - ``options`` -- any keyword arguments accepted by :meth:`_tester`. + - ``options`` -- any keyword arguments accepted by :meth:`_tester` EXAMPLES:: @@ -892,7 +856,7 @@ def _test_div(self, **options): INPUT: - - ``options`` -- any keyword arguments accepted by :meth:`_tester`. + - ``options`` -- any keyword arguments accepted by :meth:`_tester` EXAMPLES:: @@ -933,7 +897,7 @@ def _test_neg(self, **options): INPUT: - - ``options`` -- any keyword arguments accepted by :meth:`_tester`. + - ``options`` -- any keyword arguments accepted by :meth:`_tester` EXAMPLES:: @@ -960,7 +924,7 @@ def _test_shift(self, **options): INPUT: - - ``options`` -- any keyword arguments accepted by :meth:`_tester`. + - ``options`` -- any keyword arguments accepted by :meth:`_tester` EXAMPLES:: @@ -1006,7 +970,7 @@ def _test_log(self, **options): INPUT: - - ``options`` -- any keyword arguments accepted by :meth:`_tester`. + - ``options`` -- any keyword arguments accepted by :meth:`_tester` EXAMPLES:: @@ -1059,7 +1023,7 @@ def _test_teichmuller(self, **options): INPUT: - - ``options`` -- any keyword arguments accepted by :meth:`_tester`. + - ``options`` -- any keyword arguments accepted by :meth:`_tester` EXAMPLES:: @@ -1092,7 +1056,7 @@ def _test_convert_residue_field(self, **options): INPUT: - - ``options`` -- any keyword arguments accepted by :meth:`_tester`. + - ``options`` -- any keyword arguments accepted by :meth:`_tester` EXAMPLES:: @@ -1151,11 +1115,6 @@ def frobenius_endomorphism(self, n=1): - ``n`` -- an integer (default: 1) - OUTPUT: - - The `n`-th power of the absolute arithmetic Frobenius - endomorphism on this field. - EXAMPLES:: sage: K. = Qq(3^5) @@ -1204,7 +1163,6 @@ def _test_elements_eq_transitive(self, **options): sage: R(3) == R(6) False sage: R._test_elements_eq_transitive() - """ pass @@ -1214,7 +1172,7 @@ def valuation(self): OUTPUT: - a valuation that is normalized such that the rational prime `p` has + A valuation that is normalized such that the rational prime `p` has valuation 1. EXAMPLES:: @@ -1325,7 +1283,7 @@ def primitive_root_of_unity(self, n=None, order=False): INPUT: - - ``n`` -- an integer or ``None`` (default: ``None``): + - ``n`` -- an integer or ``None`` (default: ``None``) - ``order`` -- a boolean (default: ``False``) @@ -1407,7 +1365,7 @@ def roots_of_unity(self, n=None): INPUT: - ``n`` -- an integer or ``None`` (default: ``None``); if - ``None``, the full group of roots of unity is returned. + ``None``, the full group of roots of unity is returned EXAMPLES:: @@ -1481,29 +1439,29 @@ def _roots_univariate_polynomial(self, P, ring, multiplicities, algorithm, secur - ``secure`` -- a boolean (default: ``False``) - NOTE: + .. NOTE:: - When ``secure`` is ``True``, this method raises an error when - the precision on the input polynomial is not enough to determine - the number of roots in the ground field. This happens when two - roots cannot be separated. - A typical example is the polynomial + When ``secure`` is ``True``, this method raises an error when + the precision on the input polynomial is not enough to determine + the number of roots in the ground field. This happens when two + roots cannot be separated. + A typical example is the polynomial - .. MATH:: + .. MATH:: - (1 + O(p^10))*X^2 + O(p^10)*X + O(p^10) + (1 + O(p^10))*X^2 + O(p^10)*X + O(p^10). - Indeed its discriminant might be any `p`-adic integer divisible - by `p^{10}` (resp. `p^{11}` when `p=2`) and so can be as well - zero, a square and a non-square. - In the first case, the polynomial has one double root; in the - second case, it has two roots; in the third case, it has no - root in `\QQ_p`. + Indeed its discriminant might be any `p`-adic integer divisible + by `p^{10}` (resp. `p^{11}` when `p=2`) and so can be as well + zero, a square and a non-square. + In the first case, the polynomial has one double root; in the + second case, it has two roots; in the third case, it has no + root in `\QQ_p`. - When ``secure`` is ``False``, this method assumes that two - inseparable roots actually collapse. In the above example, - it then answers that the given polynomial has a double root - `O(p^5)`. + When ``secure`` is ``False``, this method assumes that two + inseparable roots actually collapse. In the above example, + it then answers that the given polynomial has a double root + `O(p^5)`. This keyword is ignored when using the ``pari`` algorithm. @@ -1735,13 +1693,13 @@ def _call_(self, x): sage: Zmod(121).convert_map_from(Qp(11))(3/11) Traceback (most recent call last): ... - ValueError: element must have non-negative valuation in order to compute residue. + ValueError: element must have non-negative valuation in order to compute residue """ return x.residue(self._n, field=self._field, check_prec=self._field) def section(self): r""" - Returns the section from the residue ring or field + Return the section from the residue ring or field back to the p-adic ring or field. EXAMPLES:: @@ -1915,7 +1873,7 @@ def _richcmp_(self, other, op): return NotImplemented return richcmp((self.domain(), self.codomain()), (other.domain(), other.codomain()), op) -def local_print_mode(obj, print_options, pos = None, ram_name = None): +def local_print_mode(obj, print_options, pos=None, ram_name=None): r""" Context manager for safely temporarily changing the print_mode of a p-adic ring/field. @@ -1945,3 +1903,4 @@ def local_print_mode(obj, print_options, pos = None, ram_name = None): if option not in print_options: print_options[option] = obj._printer.dict()[option] return pAdicPrinter(obj, print_options) + diff --git a/src/sage/rings/padics/padic_generic_element.pyx b/src/sage/rings/padics/padic_generic_element.pyx index 37b2393c9ce..4482e9b1600 100644 --- a/src/sage/rings/padics/padic_generic_element.pyx +++ b/src/sage/rings/padics/padic_generic_element.pyx @@ -753,7 +753,7 @@ cdef class pAdicGenericElement(LocalGenericElement): .. MATH:: - x + \frac{x^p}{p} + \frac{x^{p^2}}{p^2} + \dots + x + \frac{x^p}{p} + \frac{x^{p^2}}{p^2} + \cdots. INPUT: @@ -777,7 +777,7 @@ cdef class pAdicGenericElement(LocalGenericElement): sage: pi.artin_hasse_exp(algorithm='direct') # indirect doctest Traceback (most recent call last): ... - NotImplementedError: One factor of the Artin-Hasse exponential does not converge + NotImplementedError: one factor of the Artin-Hasse exponential does not converge There is however an important exception. When we are working over `\ZZ_2` or `\QQ_2` and `x` is congruent to `2` @@ -789,8 +789,8 @@ cdef class pAdicGenericElement(LocalGenericElement): AH(x) = - \exp(x + \frac{x^2}{2} + \frac{x^4}{4} + \dots) - with a negative sign. - This method knows about this fact and handles the computation correctly:: + with a negative sign. This method knows about this fact and handles + the computation correctly:: sage: W = Zp(2,8) sage: x = W(1234); x @@ -832,7 +832,7 @@ cdef class pAdicGenericElement(LocalGenericElement): denom *= p s = pow/denom if s.valuation() <= ep: - raise NotImplementedError("One factor of the Artin-Hasse exponential does not converge") + raise NotImplementedError("one factor of the Artin-Hasse exponential does not converge") arg += s AH = arg.exp(algorithm=exp_algorithm) return AH @@ -2328,7 +2328,7 @@ cdef class pAdicGenericElement(LocalGenericElement): -\log(1-x)=\sum_{n=1}^\infty \frac{x^n}{n}. For the result to be correct to precision ``aprec``, we sum all terms - for which the valuation of `x^n/n` is stricly smaller than ``aprec``. + for which the valuation of `x^n/n` is strictly smaller than ``aprec``. EXAMPLES:: @@ -2520,17 +2520,17 @@ cdef class pAdicGenericElement(LocalGenericElement): - ``p_branch`` -- an element in the base ring or its fraction field; the implementation will choose the branch of the - logarithm which sends `p` to ``branch``. + logarithm which sends `p` to ``branch`` - ``pi_branch`` -- an element in the base ring or its fraction field; the implementation will choose the branch of the - logarithm which sends the uniformizer to ``branch``. You + logarithm which sends the uniformizer to ``branch``; you may specify at most one of ``p_branch`` and ``pi_branch``, - and must specify one of them if this element is not a unit. + and must specify one of them if this element is not a unit - - ``aprec`` -- an integer or ``None`` (default: ``None``) if not + - ``aprec`` -- an integer or ``None`` (default: ``None``); if not ``None``, then the result will only be correct to precision - ``aprec``. + ``aprec`` - ``change_frac`` -- In general the codomain of the logarithm should be in the `p`-adic field, however, for most neighborhoods of 1, it lies @@ -2544,7 +2544,7 @@ cdef class pAdicGenericElement(LocalGenericElement): .. MATH:: - \log(1-x) = -x - 1/2 x^2 - 1/3 x^3 - 1/4 x^4 - 1/5 x^5 - \cdots + \log(1-x) = -x - 1/2 x^2 - 1/3 x^3 - 1/4 x^4 - 1/5 x^5 - \cdots. Its binary complexity is quadratic with respect to the precision. @@ -2583,10 +2583,10 @@ cdef class pAdicGenericElement(LocalGenericElement): 13 + 6*13^2 + 2*13^3 + 5*13^4 + 10*13^6 + 13^7 + 11*13^8 + 8*13^9 + O(13^10) Note that the relative precision decreases when we take log. - Precisely the absolute precision on ``\log(a)`` agrees with the relative - precision on ``a`` thanks to the relation ``d\log(a) = da/a``. + Precisely the absolute precision on ``log(a)`` agrees with the relative + precision on ``a`` thanks to the relation `d\log(a) = da/a`. - The call `log(a)` works as well:: + The call ``log(a)`` works as well:: sage: log(a) 13 + 6*13^2 + 2*13^3 + 5*13^4 + 10*13^6 + 13^7 + 11*13^8 + 8*13^9 + O(13^10) @@ -2607,7 +2607,7 @@ cdef class pAdicGenericElement(LocalGenericElement): sage: b.log() Traceback (most recent call last): ... - ValueError: You must specify a branch of the logarithm for non-units + ValueError: you must specify a branch of the logarithm for non-units sage: b.log(p_branch=4) 4 + O(5^10) sage: c = R(10) @@ -2632,7 +2632,8 @@ cdef class pAdicGenericElement(LocalGenericElement): sage: z = 1 + w^2 + 4*w^7; z 1 + w^2 + 4*w^7 + O(w^20) sage: z.log() - w^2 + 2*w^4 + 3*w^6 + 4*w^7 + w^9 + 4*w^10 + 4*w^11 + 4*w^12 + 3*w^14 + w^15 + w^17 + 3*w^18 + 3*w^19 + O(w^20) + w^2 + 2*w^4 + 3*w^6 + 4*w^7 + w^9 + 4*w^10 + 4*w^11 + 4*w^12 + + 3*w^14 + w^15 + w^17 + 3*w^18 + 3*w^19 + O(w^20) In an extension, there will usually be a difference between specifying ``p_branch`` and ``pi_branch``:: @@ -2641,7 +2642,7 @@ cdef class pAdicGenericElement(LocalGenericElement): sage: b.log() Traceback (most recent call last): ... - ValueError: You must specify a branch of the logarithm for non-units + ValueError: you must specify a branch of the logarithm for non-units sage: b.log(p_branch=0) O(w^20) sage: b.log(p_branch=w) @@ -2862,7 +2863,7 @@ cdef class pAdicGenericElement(LocalGenericElement): if self.is_zero(): raise ValueError('logarithm is not defined at zero') if p_branch is not None and pi_branch is not None: - raise ValueError("You may only specify a branch of the logarithm in one way") + raise ValueError("you may only specify a branch of the logarithm in one way") R = self.parent() p = R.prime() q = p**R.absolute_f() @@ -2873,7 +2874,7 @@ cdef class pAdicGenericElement(LocalGenericElement): else: if pi_branch is None: if p_branch is None: - raise ValueError("You must specify a branch of the logarithm for non-units") + raise ValueError("you must specify a branch of the logarithm for non-units") pi_branch = (p_branch - R._log_unit_part_p()) / e # Be careful: in ramified extensions, R._log_unit_part_p() is theoretically known at higher precision than the cap # In some cases, this may result in a loss of precision on pi_branch, and then on the final result @@ -2930,7 +2931,7 @@ cdef class pAdicGenericElement(LocalGenericElement): elif algorithm == "binary_splitting": log_unit = y._log_binary_splitting(aprec, minn) else: - raise ValueError("Algorithm must be either 'generic', 'binary_splitting' or None") + raise ValueError("algorithm must be either 'generic', 'binary_splitting' or None") retval = total + log_unit*R(denom).inverse_of_unit() if not change_frac: @@ -3066,7 +3067,7 @@ cdef class pAdicGenericElement(LocalGenericElement): 1 + 7 + 4*7^2 + 2*7^3 + O(7^5) """ - raise NotImplementedError("The binary splitting algorithm is not implemented for the parent: %s" % self.parent()) + raise NotImplementedError("the binary splitting algorithm is not implemented for the parent: %s" % self.parent()) def _exp_newton(self, aprec, log_algorithm=None): """ @@ -3129,7 +3130,7 @@ cdef class pAdicGenericElement(LocalGenericElement): return a - def exp(self, aprec = None, algorithm=None): + def exp(self, aprec=None, algorithm=None): r""" Compute the `p`-adic exponential of this element if the exponential series converges. @@ -3137,25 +3138,26 @@ cdef class pAdicGenericElement(LocalGenericElement): INPUT: - ``aprec`` -- an integer or ``None`` (default: ``None``); if - specified, computes only up to the indicated precision. + specified, computes only up to the indicated precision - ``algorithm`` -- ``generic``, ``binary_splitting``, ``newton`` or ``None`` (default) + The generic algorithm evaluates naively the series defining the exponential, namely .. MATH:: - \exp(x) = 1 + x + x^2/2 + x^3/6 + x^4/24 + \cdots + \exp(x) = 1 + x + x^2/2 + x^3/6 + x^4/24 + \cdots. Its binary complexity is quadratic with respect to the precision. The binary splitting algorithm is faster, it has a quasi-linear complexity. - The ``Newton`` algorithms solve the equation `\log(x) = self` using - a Newton scheme. It runs roughly as fast as the computation of the - logarithm. + The ``Newton`` algorithms solve the equation `\log(x) =` ``self`` + using a Newton scheme. It runs roughly as fast as the computation + of the logarithm. By default, we use the binary splitting if it is available. If it is not, we use the Newton algorithm if a fast algorithm for @@ -3349,7 +3351,7 @@ cdef class pAdicGenericElement(LocalGenericElement): elif algorithm == 'newton': ans = self._exp_newton(aprec) else: - raise ValueError("Algorithm must be 'generic', 'binary_splitting', 'newton' or None") + raise ValueError("algorithm must be 'generic', 'binary_splitting', 'newton' or None") return ans.add_bigoh(aprec) @@ -3606,7 +3608,7 @@ cdef class pAdicGenericElement(LocalGenericElement): sage: R(5).nth_root(11) Traceback (most recent call last): ... - ValueError: This element is not a nth power + ValueError: this element is not a nth power Similarly, when precision on the input is too small, an error is raised:: @@ -3616,7 +3618,7 @@ cdef class pAdicGenericElement(LocalGenericElement): sage: x.nth_root(5) Traceback (most recent call last): ... - PrecisionError: Not enough precision to be sure that this element is a nth power + PrecisionError: not enough precision to be sure that this element is a nth power Check that :trac:`30314` is fixed:: @@ -3706,7 +3708,7 @@ cdef class pAdicGenericElement(LocalGenericElement): if self._is_exact_zero(): return self if self.is_zero(): - raise PrecisionError("Not enough precision to be sure that this element is a nth power") + raise PrecisionError("not enough precision to be sure that this element is a nth power") v = n.valuation(p) m = n // (p**v) @@ -3714,14 +3716,14 @@ cdef class pAdicGenericElement(LocalGenericElement): # We check the valuation val = self.valuation() if val % n != 0: - raise ValueError("This element is not a nth power") + raise ValueError("this element is not a nth power") # and the residue a = K(self) >> val abar = a.residue() try: xbar = abar.nth_root(m) except ValueError: - raise ValueError("This element is not a nth power") + raise ValueError("this element is not a nth power") # We take the inverse mth root at small precision prec = a.precision_absolute() @@ -3749,11 +3751,11 @@ cdef class pAdicGenericElement(LocalGenericElement): else: root, accuracy = root._inverse_pth_root() if accuracy is not infinity and accuracy is not None: - raise ValueError("This element is not a nth power") + raise ValueError("this element is not a nth power") # We check the precision if v > 0 and prec < minprec: - raise PrecisionError("Not enough precision to be sure that this element is a nth power") + raise PrecisionError("not enough precision to be sure that this element is a nth power") # We lift the root using Newton iteration if v % 2 == parity: @@ -3840,7 +3842,7 @@ cdef class pAdicGenericElement(LocalGenericElement): else: invroottwist, accuracy = hint if accuracy is None: - raise NotImplementedError("Try to increase the precision cap of the parent...") + raise NotImplementedError("try to increase the precision cap of the parent...") a = self prec = a.precision_absolute() @@ -4053,7 +4055,7 @@ cdef class pAdicGenericElement(LocalGenericElement): - `Li_n(`self`)` - EXAMPLES :: + EXAMPLES:: sage: Qp(2)(-1)._polylog_res_1(6) == 0 True @@ -4124,29 +4126,26 @@ cdef class pAdicGenericElement(LocalGenericElement): verbose(H, level=3) return (H[n](z - 1) - ((z.log(p_branch))**(n-1)*(1 - z).log(p_branch))/Integer(n-1).factorial()).add_bigoh(N) - def polylog(self, n, p_branch = 0): - """ - Return `Li_n(self)`, the `n`th `p`-adic polylogarithm of this element. + def polylog(self, n, p_branch=0): + r""" + Return `Li_n(\mathrm{self})`, the `n`-th `p`-adic polylogarithm + of this element. INPUT: - ``n`` -- a non-negative integer - ``p_branch`` -- an element in the base ring or its fraction field; the implementation will choose the branch of the - logarithm which sends `p` to ``branch``. - - OUTPUT: - - - `Li_n(`self`)` + logarithm which sends `p` to ``branch`` EXAMPLES: - The `n`-th polylogarithm of `-1` is `0` for even `n` :: + The `n`-th polylogarithm of `-1` is `0` for even `n`:: sage: Qp(13)(-1).polylog(6) == 0 True - We can check some identities, for example those mentioned in [DCW2016]_ :: + We can check some identities, for example those mentioned in [DCW2016]_:: sage: x = Qp(7, prec=30)(1/3) sage: (x^2).polylog(4) - 8*x.polylog(4) - 8*(-x).polylog(4) == 0 @@ -4164,36 +4163,36 @@ cdef class pAdicGenericElement(LocalGenericElement): sage: x.polylog(2) + (1-x).polylog(2) + x.log(0)**2*(1-x).log(0) == 0 True - `Li_1(z) = -\log(1-z)` for `|z| < 1` :: + `Li_1(z) = -\log(1-z)` for `|z| < 1`:: sage: Qp(5)(10).polylog(1) == -Qp(5)(1-10).log(0) True - The dilogarithm of 1 is zero :: + The dilogarithm of 1 is zero:: sage: Qp(5)(1).polylog(2) O(5^20) - The cubing relation holds for the trilogarithm at 1 :: + The cubing relation holds for the trilogarithm at 1:: sage: K = Qp(7) sage: z = K.zeta(3) sage: -8*K(1).polylog(3) == 9*(K(z).polylog(3) + K(z^2).polylog(3)) True - The polylogarithm of 0 is 0 :: + The polylogarithm of 0 is 0:: sage: Qp(11)(0).polylog(7) 0 - Only polylogarithms for positive `n` are defined :: + Only polylogarithms for positive `n` are defined:: sage: Qp(11)(2).polylog(-1) Traceback (most recent call last): ... - ValueError: Polylogarithm only implemented for n at least 0. + ValueError: polylogarithm only implemented for n at least 0 - Check that :trac:`29222` is fixed :: + Check that :trac:`29222` is fixed:: sage: K = Qp(7) sage: print(K(1 + 7^11).polylog(4)) @@ -4210,10 +4209,8 @@ cdef class pAdicGenericElement(LocalGenericElement): .. TODO:: - - Implement for extensions - - Use the change method to create K from self.parent() - - + - Implement for extensions. + - Use the change method to create K from ``self.parent()``. """ from sage.rings.power_series_ring import PowerSeriesRing from sage.rings.padics.factory import Qp @@ -4222,14 +4219,14 @@ cdef class pAdicGenericElement(LocalGenericElement): from sage.rings.infinity import PlusInfinity if self.parent().absolute_degree() != 1: - raise NotImplementedError("Polylogarithms are not currently implemented for elements of extensions") + raise NotImplementedError("polylogarithms are not currently implemented for elements of extensions") # TODO implement this (possibly after the change method for padic generic elements is added). if n == 0: return self/(1-self) if n == 1: return -(1-self).log(p_branch) if n < 0: - raise ValueError('Polylogarithm only implemented for n at least 0.') + raise ValueError('polylogarithm only implemented for n at least 0') prec = self.precision_absolute() diff --git a/src/sage/rings/padics/padic_lattice_element.py b/src/sage/rings/padics/padic_lattice_element.py index 4714886437b..a4e44d12add 100644 --- a/src/sage/rings/padics/padic_lattice_element.py +++ b/src/sage/rings/padics/padic_lattice_element.py @@ -291,11 +291,11 @@ def residue(self, absprec=1, field=None, check_prec=True): sage: a.residue(-1) Traceback (most recent call last): ... - ValueError: cannot reduce modulo a negative power of p. + ValueError: cannot reduce modulo a negative power of p sage: a.residue(5) Traceback (most recent call last): ... - PrecisionError: not enough precision known in order to compute residue. + PrecisionError: not enough precision known in order to compute residue sage: a.residue(5, check_prec=False) 8 @@ -305,11 +305,11 @@ def residue(self, absprec=1, field=None, check_prec=True): if not isinstance(absprec, Integer): absprec = Integer(absprec) if check_prec and absprec > self.precision_absolute(): - raise PrecisionError("not enough precision known in order to compute residue.") + raise PrecisionError("not enough precision known in order to compute residue") elif absprec < 0: - raise ValueError("cannot reduce modulo a negative power of p.") + raise ValueError("cannot reduce modulo a negative power of p") if self.valuation() < 0: - raise ValueError("element must have non-negative valuation in order to compute residue.") + raise ValueError("element must have non-negative valuation in order to compute residue") if field is None: field = (absprec == 1) elif field and absprec != 1: @@ -429,14 +429,14 @@ def valuation(self, secure=False): sage: y.valuation(secure=True) Traceback (most recent call last): ... - PrecisionError: Not enough precision + PrecisionError: not enough precision """ val = self._value.valuation() prec = self.precision_absolute() if val < prec: return val elif secure: - raise PrecisionError("Not enough precision") + raise PrecisionError("not enough precision") else: return prec @@ -467,7 +467,7 @@ def precision_relative(self, secure=False): sage: y.precision_relative(secure=True) Traceback (most recent call last): ... - PrecisionError: Not enough precision + PrecisionError: not enough precision """ if not secure and self.is_zero(): return ZZ(0) @@ -1017,7 +1017,7 @@ def unit_part(self): sage: c.unit_part() Traceback (most recent call last): ... - PrecisionError: Not enough precision + PrecisionError: not enough precision """ v = self.valuation(secure=True) return self >> v @@ -1046,7 +1046,7 @@ def val_unit(self): sage: c.val_unit() Traceback (most recent call last): ... - PrecisionError: Not enough precision + PrecisionError: not enough precision """ v = self.valuation(secure=True) return v, self >> v @@ -1193,7 +1193,7 @@ def expansion(self, n=None, lift_mode='simple', start_val=None): [4, 1, 4, 4, 1, 0, 0, 0, 0, 0] """ if lift_mode != 'simple': - raise NotImplementedError("Other modes than 'simple' are not implemented yet") + raise NotImplementedError("other modes than 'simple' are not implemented yet") prec = self.precision_absolute() val = self.valuation() expansion = self._value.list(prec) @@ -1203,7 +1203,7 @@ def expansion(self, n=None, lift_mode='simple', start_val=None): try: return expansion[n-val] except KeyError: - raise PrecisionError("The digit in position %s is not determined" % n) + raise PrecisionError("the digit in position %s is not determined" % n) if start_val is None: if self._parent.is_field(): start_val = val diff --git a/src/sage/rings/padics/padic_printing.pyx b/src/sage/rings/padics/padic_printing.pyx index f0fea52b8d2..654f90fa0e5 100644 --- a/src/sage/rings/padics/padic_printing.pyx +++ b/src/sage/rings/padics/padic_printing.pyx @@ -1,4 +1,4 @@ -# distutils: libraries = NTL_LIBRARIES ntl m +# distutils: libraries = NTL_LIBRARIES gmp m # distutils: extra_compile_args = NTL_CFLAGS # distutils: include_dirs = NTL_INCDIR # distutils: library_dirs = NTL_LIBDIR diff --git a/src/sage/rings/padics/padic_relaxed_errors.pyx b/src/sage/rings/padics/padic_relaxed_errors.pyx index dff08758100..9451c7df14d 100644 --- a/src/sage/rings/padics/padic_relaxed_errors.pyx +++ b/src/sage/rings/padics/padic_relaxed_errors.pyx @@ -47,7 +47,7 @@ def raise_error(error, permissive=False): sage: raise_error(1) Traceback (most recent call last): ... - PrecisionError: computation has been abandonned; try to increase precision + PrecisionError: computation has been abandoned; try to increase precision sage: raise_error(1, permissive=True) @@ -68,4 +68,5 @@ def raise_error(error, permissive=False): 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") + raise PrecisionError("computation has been abandoned; 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 31e89a824b3..f4629a5d598 100644 --- a/src/sage/rings/padics/padic_template_element.pxi +++ b/src/sage/rings/padics/padic_template_element.pxi @@ -138,7 +138,7 @@ cdef class pAdicTemplateElement(pAdicGenericElement): elif sage.rings.finite_rings.element_base.is_FiniteFieldElement(x): k = self.parent().residue_field() if not k.has_coerce_map_from(x.parent()): - raise NotImplementedError("conversion from finite fields which do not embed into the residue field not implemented.") + raise NotImplementedError("conversion from finite fields which do not embed into the residue field not implemented") x = k(x) if not k.is_prime_field(): @@ -313,16 +313,16 @@ cdef class pAdicTemplateElement(pAdicGenericElement): raise NotImplementedError def lift_to_precision(self, absprec=None): - """ - Returns another element of the same parent with absolute precision at + r""" + Return another element of the same parent with absolute precision at least ``absprec``, congruent to this `p`-adic element modulo the precision of this element. INPUT: - - ``absprec`` -- an integer or ``None`` (default: ``None``), the + - ``absprec`` -- an integer or ``None`` (default: ``None``); the absolute precision of the result. If ``None``, lifts to the maximum - precision allowed. + precision allowed .. NOTE:: @@ -340,7 +340,7 @@ cdef class pAdicTemplateElement(pAdicGenericElement): sage: R(1,15).lift_to_precision(30) Traceback (most recent call last): ... - PrecisionError: Precision higher than allowed by the precision cap. + PrecisionError: precision higher than allowed by the precision cap sage: R(-1,2).lift_to_precision().precision_absolute() == R.precision_cap() True @@ -362,14 +362,14 @@ cdef class pAdicTemplateElement(pAdicGenericElement): if not isinstance(absprec, Integer): absprec = Integer(absprec) if mpz_fits_slong_p((absprec).value) == 0: - raise PrecisionError("Precision higher than allowed by the precision cap") + raise PrecisionError("precision higher than allowed by the precision cap") ans = self.lift_to_precision_c(mpz_get_si((absprec).value)) ans.check_preccap() return ans cdef pAdicTemplateElement lift_to_precision_c(self, long absprec): """ - Lifts this element to another with precision at least absprec. + Lift this element to another with precision at least ``absprec``. """ raise NotImplementedError @@ -384,7 +384,7 @@ cdef class pAdicTemplateElement(pAdicGenericElement): .. MATH:: - \pi^v \cdot \sum_{i=0}^\infty a_i \pi^i + \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. @@ -693,15 +693,15 @@ cdef class pAdicTemplateElement(pAdicGenericElement): sage: a.residue(2) Traceback (most recent call last): ... - NotImplementedError: reduction modulo p^n with n>1. + NotImplementedError: reduction modulo p^n with n>1 sage: a.residue(10) Traceback (most recent call last): ... - PrecisionError: insufficient precision to reduce modulo p^10. + PrecisionError: insufficient precision to reduce modulo p^10 sage: a.residue(10, check_prec=False) Traceback (most recent call last): ... - NotImplementedError: reduction modulo p^n with n>1. + NotImplementedError: reduction modulo p^n with n>1 sage: R. = ZqCA(27, 4) sage: (3 + 3*a).residue() @@ -717,17 +717,17 @@ cdef class pAdicTemplateElement(pAdicGenericElement): sage: (a/3).residue() Traceback (most recent call last): ... - ValueError: element must have non-negative valuation in order to compute residue. + ValueError: element must have non-negative valuation in order to compute residue """ if absprec < 0: - raise ValueError("cannot reduce modulo a negative power of the uniformizer.") + raise ValueError("cannot reduce modulo a negative power of the uniformizer") if self.valuation() < 0: - raise ValueError("element must have non-negative valuation in order to compute residue.") + raise ValueError("element must have non-negative valuation in order to compute residue") R = self.parent() if check_prec and (R.is_fixed_mod() or R.is_floating_point()): check_prec = False if check_prec and absprec > self.precision_absolute(): - raise PrecisionError("insufficient precision to reduce modulo p^%s."%absprec) + raise PrecisionError("insufficient precision to reduce modulo p^%s"%absprec) if field and absprec != 1: raise ValueError("field keyword may only be set at precision 1") if absprec == 0: @@ -739,14 +739,14 @@ cdef class pAdicTemplateElement(pAdicGenericElement): return parent.zero() return parent(self.expansion(0)) else: - raise NotImplementedError("reduction modulo p^n with n>1.") + raise NotImplementedError("reduction modulo p^n with n>1") cdef Integer exact_pow_helper(long *ansrelprec, long relprec, _right, PowComputer_ prime_pow): """ - This function is used by exponentiation in both CR_template.pxi - and CA_template.pxi to determine the extra precision gained from - an exponent of positive valuation. See __pow__ there and in - padic_ZZ_pX_CR_element.pyx for more details on this phenomenon. + This function is used by exponentiation in both ``CR_template.pxi`` + and ``CA_template.pxi`` to determine the extra precision gained from + an exponent of positive valuation. See ``__pow__`` there and in + ``padic_ZZ_pX_CR_element.pyx`` for more details on this phenomenon. INPUT: @@ -874,7 +874,8 @@ cdef class ExpansionIter(object): """ An iterator over a `p`-adic expansion. - This class should not be instantiated directly, but instead using :meth:`expansion`. + This class should not be instantiated directly, but instead + using :meth:`expansion`. INPUT: @@ -951,7 +952,7 @@ cdef class ExpansionIter(object): def __len__(self): """ - Returns the number of terms that will be emitted. + Return the number of terms that will be emitted. TESTS:: @@ -966,7 +967,7 @@ cdef class ExpansionIter(object): def __next__(self): """ - Provides the next coefficient in the `p`-adic expansion. + Provide the next coefficient in the `p`-adic expansion. EXAMPLES:: @@ -999,7 +1000,7 @@ cdef class ExpansionIter(object): return cexpansion_next(self.curvalue, self.mode, self.curpower, pp) cdef class ExpansionIterable(object): - """ + r""" An iterable storing a `p`-adic expansion of an element. This class should not be instantiated directly, but instead using :meth:`expansion`. @@ -1010,7 +1011,11 @@ cdef class ExpansionIterable(object): - ``prec`` -- the number of terms to be emitted - ``val_shift`` -- how many zeros to add at the beginning of the expansion, or the number of initial terms to truncate (if negative) - - ``mode`` -- either ``simple_mode``, ``smallest_mode`` or ``teichmuller_mode`` + - ``mode`` -- one of the following: + + * ``'simple_mode'`` + * ``'smallest_mode'`` + * ``'teichmuller_mode'`` EXAMPLES:: @@ -1047,7 +1052,7 @@ cdef class ExpansionIterable(object): def __dealloc__(self): """ - Deallocates memory for the iteratable. + Deallocate memory for the iteratable. TESTS:: @@ -1058,7 +1063,7 @@ cdef class ExpansionIterable(object): def __iter__(self): """ - Returns an iterator, based on a corresponding :class:`ExpansionIter`. + Return an iterator, based on a corresponding :class:`ExpansionIter`. If ``val_shift`` is positive, will first emit that many zeros (of the appropriate type: ``[]`` instead when the inertia degree @@ -1089,7 +1094,7 @@ cdef class ExpansionIterable(object): def __len__(self): """ - Returns the number of terms that will be emitted. + Return the number of terms that will be emitted. TESTS:: @@ -1106,7 +1111,7 @@ cdef class ExpansionIterable(object): def __getitem__(self, n): """ - Return the ``n``th entry in the expansion. + Return the ``n``-th entry in the expansion. Negative indices are not allowed. @@ -1122,7 +1127,7 @@ cdef class ExpansionIterable(object): sage: a = E[-1] Traceback (most recent call last): ... - ValueError: Negative indices not supported + ValueError: negative indices not supported sage: Zp(5,4)(373).expansion(lift_mode='smallest')[3] -2 """ @@ -1134,7 +1139,7 @@ cdef class ExpansionIterable(object): cdef long m = n - self.val_shift cdef celement value if n < 0: - raise ValueError("Negative indices not supported") + raise ValueError("negative indices not supported") elif m < 0: return _zero(self.mode, self.teich_ring) elif m >= self.prec: @@ -1169,3 +1174,4 @@ cdef class ExpansionIterable(object): modestr = " (teichmuller)" p = self.elt.prime_pow.prime return "%s-adic expansion of %s%s"%(p, self.elt, modestr) + diff --git a/src/sage/rings/padics/pow_computer.pyx b/src/sage/rings/padics/pow_computer.pyx index ec4ff3009dc..c2fa8031086 100644 --- a/src/sage/rings/padics/pow_computer.pyx +++ b/src/sage/rings/padics/pow_computer.pyx @@ -609,9 +609,9 @@ cdef PowComputer_base PowComputer_c(Integer m, Integer cache_limit, Integer prec 81 """ if cache_limit < 0: - raise ValueError("cache_limit must be non-negative.") + raise ValueError("cache_limit must be non-negative") if prec_cap < 0: - raise ValueError("prec_cap must be non-negative.") + raise ValueError("prec_cap must be non-negative") if mpz_cmp_si((prec_cap).value, maxpreccap) >= 0: raise ValueError("cannot create p-adic parents with precision cap larger than (1 << (sizeof(long)*8 - 2))") diff --git a/src/sage/rings/padics/relaxed_template.pxi b/src/sage/rings/padics/relaxed_template.pxi index 9ef4ea4e704..48a0468bc91 100644 --- a/src/sage/rings/padics/relaxed_template.pxi +++ b/src/sage/rings/padics/relaxed_template.pxi @@ -186,7 +186,7 @@ cdef class RelaxedElement(pAdicGenericElement): An error code which is a superposition of the following: - ``0`` -- no error - - ``ERROR_ABANDON = 1`` -- computation has been abandonned + - ``ERROR_ABANDON = 1`` -- computation has been abandoned - ``ERROR_NOTDEFINED = 2`` -- a number is not defined - ``ERROR_PRECISION = 4`` -- not enough precision - ``ERROR_OVERFLOW = 8`` -- overflow @@ -229,7 +229,7 @@ cdef class RelaxedElement(pAdicGenericElement): - ``prec`` -- an integer - ``halt`` -- an integer; the absolute precision after which the - computation is abandonned if the first significant digit has not + computation is abandoned if the first significant digit has not been found yet OUTPUT: @@ -1158,10 +1158,10 @@ cdef class RelaxedElement(pAdicGenericElement): 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 + the absolute precision after which the computation is abandoned 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 + if ``False``, the computation is never abandoned - ``permissive`` -- a boolean (default: ``False`` if ``prec`` is given, ``True`` otherwise); if ``False``, raise an error @@ -1189,7 +1189,7 @@ cdef class RelaxedElement(pAdicGenericElement): sage: b.at_precision_relative(5) Traceback (most recent call last): ... - PrecisionError: computation has been abandonned; try to increase precision + PrecisionError: computation has been abandoned; try to increase precision By setting the argument ``halt``, one can force the computation to continue until a prescribed limit:: @@ -1197,7 +1197,7 @@ cdef class RelaxedElement(pAdicGenericElement): 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 + PrecisionError: computation has been abandoned; try to increase precision sage: b.at_precision_relative(5, halt=21) # now, we're okay 5^20 + O(5^25) @@ -1339,10 +1339,10 @@ cdef class RelaxedElement(pAdicGenericElement): INPUT: - ``halt`` -- an integer or a boolean (default: ``True``); - the absolute precision after which the computation is abandonned + the absolute precision after which the computation is abandoned 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 + if ``False``, the computation is never abandoned - ``secure`` -- a boolean (default: the value given at the creation of the parent); when the valuation cannot be determined for sure, @@ -1443,10 +1443,10 @@ cdef class RelaxedElement(pAdicGenericElement): INPUT: - ``halt`` -- an integer or a boolean (default: ``True``); - the absolute precision after which the computation is abandonned + the absolute precision after which the computation is abandoned 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 + if ``False``, the computation is never abandoned EXAMPLES:: @@ -1473,7 +1473,7 @@ cdef class RelaxedElement(pAdicGenericElement): ... ValueError: unit part of 0 not defined - See :meth:`valuation` for more details on the paramter ``halt``. + See :meth:`valuation` for more details on the parameter ``halt``. """ val = self.valuation(halt) @@ -1488,10 +1488,10 @@ cdef class RelaxedElement(pAdicGenericElement): INPUT: - ``halt`` -- an integer or a boolean (default: ``True``); - the absolute precision after which the computation is abandonned + the absolute precision after which the computation is abandoned 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 + if ``False``, the computation is never abandoned EXAMPLES:: @@ -1519,7 +1519,7 @@ cdef class RelaxedElement(pAdicGenericElement): ... ValueError: unit part of 0 not defined - See :meth:`valuation` for more details on the paramter ``halt``. + See :meth:`valuation` for more details on the parameter ``halt``. """ val = self.valuation(halt) @@ -2049,7 +2049,7 @@ cdef class RelaxedElement_abandon(RelaxedElement): sage: x[0] Traceback (most recent call last): ... - PrecisionError: computation has been abandonned; try to increase precision + PrecisionError: computation has been abandoned; try to increase precision """ self._valuation = -maxordp diff --git a/src/sage/rings/polynomial/laurent_polynomial.pyx b/src/sage/rings/polynomial/laurent_polynomial.pyx index 696d1bd84d9..c0229a7b935 100644 --- a/src/sage/rings/polynomial/laurent_polynomial.pyx +++ b/src/sage/rings/polynomial/laurent_polynomial.pyx @@ -13,7 +13,6 @@ Elements of Laurent polynomial rings from sage.rings.integer cimport Integer from sage.categories.map cimport Map from sage.structure.element import is_Element, coerce_binop -from sage.misc.misc import union from sage.structure.factorization import Factorization from sage.misc.derivative import multi_derivative from sage.rings.polynomial.polynomial_element import Polynomial @@ -2661,9 +2660,9 @@ cdef class LaurentPolynomial_mpair(LaurentPolynomial): cdef dict d = self.dict() cdef tuple g = self._parent.gens() cdef Py_ssize_t nvars = len(g) - cdef list vars = [] + cdef set vars = set() for k in d: - vars = union(vars, k.nonzero_positions()) + vars.update(k.nonzero_positions()) if len(vars) == nvars: break cdef list v = [g[i] for i in vars] diff --git a/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx b/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx index 831113a5c94..cddfd4e35a3 100644 --- a/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx @@ -235,8 +235,6 @@ from sage.rings.number_field.number_field_base cimport NumberField from sage.structure.element import coerce_binop from sage.structure.parent cimport Parent -from sage.structure.parent_base cimport ParentWithBase -from sage.structure.parent_gens cimport ParentWithGens from sage.structure.category_object cimport CategoryObject from sage.structure.coerce cimport coercion_model diff --git a/src/sage/rings/polynomial/ore_function_element.py b/src/sage/rings/polynomial/ore_function_element.py index 97330acc180..e9936f7996b 100644 --- a/src/sage/rings/polynomial/ore_function_element.py +++ b/src/sage/rings/polynomial/ore_function_element.py @@ -167,12 +167,12 @@ def _richcmp_(self, other, op): sage: P = K.random_element() sage: Q = K.random_element() sage: D = K.random_element() - sage: (P*D) / (Q*D) == P/Q + sage: Q == 0 or D == 0 or (P*D) / (Q*D) == P/Q True """ if self.parent()._simplification: - return richcmp((self._numerator, self._denominator), (other._numerator, other._denominator), op) + return richcmp((self._numerator, self._denominator), (other._numerator, other._denominator), op) if op == op_EQ or op == op_NE: _, U, V = self._denominator.left_xlcm(other._denominator) return richcmp(U * self._numerator, V * other._numerator, op) @@ -549,7 +549,7 @@ def _div_(self, other): sage: f = K.random_element() sage: g = K.random_element() sage: h = K.random_element() - sage: f / (g / h) == f*h / g + sage: g == 0 or h == 0 or f / (g / h) == f*h / g True sage: 0/f @@ -558,11 +558,18 @@ def _div_(self, other): Traceback (most recent call last): ... ZeroDivisionError: cannot divide by zero + + We check that :trac:`32109` is fixed:: + + sage: K(0)/K(0) + Traceback (most recent call last): + ... + ZeroDivisionError: cannot divide by zero """ - if not self._numerator: - return self if not other._numerator: raise ZeroDivisionError("cannot divide by zero") + if not self._numerator: + return self L, U, V = self._numerator.left_xlcm(other._numerator, monic=False) denominator = U * self._denominator numerator = V * other._denominator diff --git a/src/sage/rings/polynomial/pbori/PyPolyBoRi.py b/src/sage/rings/polynomial/pbori/PyPolyBoRi.py index ea1b5e9c47a..e3f15d58ad6 100644 --- a/src/sage/rings/polynomial/pbori/PyPolyBoRi.py +++ b/src/sage/rings/polynomial/pbori/PyPolyBoRi.py @@ -65,7 +65,10 @@ [a, b, c, d, e, y2] """ -from sage.rings.polynomial.pbori.pbori import * +from .pbori import (order_dict, TermOrder_from_pb_order, BooleanPolynomialRing, + BooleanPolynomialVector, MonomialFactory, + PolynomialFactory, VariableFactory, add_up_polynomials, + gauss_on_polys) import weakref diff --git a/src/sage/rings/polynomial/pbori/__init__.py b/src/sage/rings/polynomial/pbori/__init__.py index b4dc00794bd..34f33571cca 100644 --- a/src/sage/rings/polynomial/pbori/__init__.py +++ b/src/sage/rings/polynomial/pbori/__init__.py @@ -10,7 +10,8 @@ PolyBoRi features a powerful reference implementation for Groebner basis computation. AUTHOR: - The PolyBoRi Team, 2007-2011 + +The PolyBoRi Team, 2007-2011 REFERENCES: M. Brickenstein, A. Dreyer, G. Greuel, M. Wedler, O. Wienand, diff --git a/src/sage/rings/polynomial/pbori/blocks.py b/src/sage/rings/polynomial/pbori/blocks.py index 0f37364d134..9e189de5ce5 100644 --- a/src/sage/rings/polynomial/pbori/blocks.py +++ b/src/sage/rings/polynomial/pbori/blocks.py @@ -1,9 +1,9 @@ import sys - -from .PyPolyBoRi import Ring, VariableBlock, Polynomial -from .PyPolyBoRi import VariableFactory, Variable from itertools import chain, islice +from .pbori import VariableBlock +from .PyPolyBoRi import (Ring, Polynomial, VariableFactory, Variable) + class Block(object): r""" diff --git a/src/sage/rings/polynomial/pbori/easy_polynomials.py b/src/sage/rings/polynomial/pbori/easy_polynomials.py index 6b2372c1dac..8641e93afa3 100644 --- a/src/sage/rings/polynomial/pbori/easy_polynomials.py +++ b/src/sage/rings/polynomial/pbori/easy_polynomials.py @@ -1,5 +1,5 @@ from .interpolate import variety_lex_leading_terms, nf_lex_points -from .PyPolyBoRi import easy_linear_factors +from .pbori import easy_linear_factors def easy_linear_polynomials(p): diff --git a/src/sage/rings/polynomial/pbori/fglm.py b/src/sage/rings/polynomial/pbori/fglm.py index 1afa8792d4e..bbcfd33fe35 100644 --- a/src/sage/rings/polynomial/pbori/fglm.py +++ b/src/sage/rings/polynomial/pbori/fglm.py @@ -1,5 +1,5 @@ -from .PyPolyBoRi import (BooleSet, Polynomial, BoolePolynomialVector, - FGLMStrategy) +from .PyPolyBoRi import Polynomial, BoolePolynomialVector +from .pbori import FGLMStrategy, BooleSet def _fglm(I, from_ring, to_ring): diff --git a/src/sage/rings/polynomial/pbori/gbcore.py b/src/sage/rings/polynomial/pbori/gbcore.py index ad2e784a433..6acc44db81e 100644 --- a/src/sage/rings/polynomial/pbori/gbcore.py +++ b/src/sage/rings/polynomial/pbori/gbcore.py @@ -3,8 +3,9 @@ from inspect import getfullargspec as getargspec from .nf import GeneratorLimitExceeded, symmGB_F2_C, symmGB_F2_python +from .pbori import GroebnerStrategy, ll_red_nf_redsb from .PyPolyBoRi import (Monomial, Polynomial, - GroebnerStrategy, OrderCode, ll_red_nf_redsb) + OrderCode) from .ll import eliminate, ll_encode from .statistics import used_vars_set from .heuristics import dense_system, gauss_on_linear diff --git a/src/sage/rings/polynomial/pbori/interpolate.py b/src/sage/rings/polynomial/pbori/interpolate.py index 79edb097636..68f25d57bb2 100644 --- a/src/sage/rings/polynomial/pbori/interpolate.py +++ b/src/sage/rings/polynomial/pbori/interpolate.py @@ -61,9 +61,9 @@ def gen_random_o_z(points, points_p): def variety_lex_leading_terms(points, variables): + assert isinstance(points, BooleSet), "Points needs to be a BooleSet" ring = variables.ring() standards = BooleSet(ring.zero()) - assert type(points) == BooleSet, "Points needs to be a BooleSet" points_tuple = tuple(points) myvars_div = variables.divisors() if points != myvars_div: @@ -78,8 +78,7 @@ def variety_lex_leading_terms(points, variables): len_standards = len(standards) standards_old = standards - leading_terms = BooleSet(myvars_div.diff(standards)).minimal_elements() - return leading_terms + return BooleSet(myvars_div.diff(standards)).minimal_elements() def lex_groebner_basis_points(points, variables): diff --git a/src/sage/rings/polynomial/pbori/interred.py b/src/sage/rings/polynomial/pbori/interred.py index 724b9e5cd4b..4cc8a67f8bf 100644 --- a/src/sage/rings/polynomial/pbori/interred.py +++ b/src/sage/rings/polynomial/pbori/interred.py @@ -1,4 +1,5 @@ -from .PyPolyBoRi import Polynomial, ReductionStrategy +from .pbori import ReductionStrategy +from .PyPolyBoRi import Polynomial def interred(l, completely=False): diff --git a/src/sage/rings/polynomial/pbori/ll.py b/src/sage/rings/polynomial/pbori/ll.py index 48c43220a89..e7d7e5e6347 100644 --- a/src/sage/rings/polynomial/pbori/ll.py +++ b/src/sage/rings/polynomial/pbori/ll.py @@ -1,9 +1,8 @@ -from sage.rings.polynomial.pbori.pbori import (top_index, if_then_else, - substitute_variables) -from .PyPolyBoRi import (BooleSet, Polynomial, Monomial, Ring, - BoolePolynomialVector, - ll_red_nf_redsb, ll_red_nf_noredsb, - ll_red_nf_noredsb_single_recursive_call) +from .pbori import (top_index, if_then_else, + substitute_variables, BooleSet, + ll_red_nf_redsb, ll_red_nf_noredsb, + ll_red_nf_noredsb_single_recursive_call) +from .PyPolyBoRi import (Polynomial, Monomial, Ring, BoolePolynomialVector) from .statistics import used_vars_set from .rank import rank diff --git a/src/sage/rings/polynomial/pbori/nf.py b/src/sage/rings/polynomial/pbori/nf.py index 57697c0dbe8..55f71b0686d 100644 --- a/src/sage/rings/polynomial/pbori/nf.py +++ b/src/sage/rings/polynomial/pbori/nf.py @@ -1,7 +1,8 @@ from sage.rings.polynomial.pbori.pbori import mod_mon_set -from .PyPolyBoRi import (BooleSet, Monomial, Polynomial, Variable, - GroebnerStrategy, ReductionStrategy, parallel_reduce, - easy_linear_factors, BoolePolynomialVector) +from .pbori import (BooleSet, GroebnerStrategy, ReductionStrategy, + parallel_reduce, easy_linear_factors) +from .PyPolyBoRi import (Monomial, Polynomial, Variable, + BoolePolynomialVector) from .easy_polynomials import (easy_linear_polynomials as easy_linear_polynomials_func) from .statistics import used_vars_set diff --git a/src/sage/rings/polynomial/pbori/parallel.py b/src/sage/rings/polynomial/pbori/parallel.py index 67d9322983e..bd3c4730b05 100644 --- a/src/sage/rings/polynomial/pbori/parallel.py +++ b/src/sage/rings/polynomial/pbori/parallel.py @@ -6,13 +6,13 @@ Created by Michael Brickenstein on 2008-10-31. Copyright 2008 The PolyBoRi Team """ -from sage.rings.polynomial.pbori.pbori import if_then_else -from .PyPolyBoRi import CCuddNavigator, BooleSet -from .PyPolyBoRi import (Polynomial, Ring, WeakRingRef, Monomial, Variable) -from .gbcore import groebner_basis from zlib import compress, decompress import copyreg +from .pbori import if_then_else, BooleSet, CCuddNavigator +from .PyPolyBoRi import (Polynomial, Ring, WeakRingRef, Monomial, Variable) +from .gbcore import groebner_basis + def to_fast_pickable(l): r""" @@ -157,11 +157,7 @@ def from_fast_pickable(l, r): def _calculate_gb_with_keywords(args): (I, kwds_as_single_arg) = args - import traceback - try: - return groebner_basis(I, **kwds_as_single_arg) - except: - raise ValueError(traceback.format_exc()) + return groebner_basis(I, **kwds_as_single_arg) def _decode_polynomial(code): diff --git a/src/sage/rings/polynomial/pbori/randompoly.py b/src/sage/rings/polynomial/pbori/randompoly.py index 2efc2105eab..b0327a50062 100644 --- a/src/sage/rings/polynomial/pbori/randompoly.py +++ b/src/sage/rings/polynomial/pbori/randompoly.py @@ -1,8 +1,9 @@ -from .PyPolyBoRi import (Monomial, random_set, Polynomial, - set_random_seed, ll_red_nf_redsb, Variable) -from .ll import ll_encode from random import Random from pprint import pformat + +from .PyPolyBoRi import (Monomial, Polynomial, Variable) +from .pbori import random_set, set_random_seed, ll_red_nf_redsb +from .ll import ll_encode from .blocks import declare_ring @@ -87,8 +88,6 @@ def sparse_random_system(ring, number_of_polynomials, variables_per_polynomial, return [p + ll_red_nf_redsb(p, solutions) for p in res] - - def sparse_random_system_data_file_content(number_of_variables, **kwds): r""" TESTS:: diff --git a/src/sage/rings/polynomial/pbori/specialsets.py b/src/sage/rings/polynomial/pbori/specialsets.py index 5a988da4a81..74f56c2faf5 100644 --- a/src/sage/rings/polynomial/pbori/specialsets.py +++ b/src/sage/rings/polynomial/pbori/specialsets.py @@ -1,7 +1,6 @@ -from sage.rings.polynomial.pbori.pbori import (top_index, if_then_else, - mod_mon_set) -from .PyPolyBoRi import (BooleSet, Polynomial, - Monomial, BooleConstant, Variable) +from .pbori import (top_index, if_then_else, + mod_mon_set, BooleSet, BooleConstant) +from .PyPolyBoRi import (Polynomial, Monomial, Variable) def all_monomials_of_degree_d_old(d, variables): diff --git a/src/sage/rings/polynomial/pbori/statistics.py b/src/sage/rings/polynomial/pbori/statistics.py index f4608fc50d6..93cced307a7 100644 --- a/src/sage/rings/polynomial/pbori/statistics.py +++ b/src/sage/rings/polynomial/pbori/statistics.py @@ -1,5 +1,5 @@ -from sage.rings.polynomial.pbori.pbori import top_index -from .PyPolyBoRi import Monomial, Polynomial, BooleConstant +from .pbori import top_index, BooleConstant +from .PyPolyBoRi import Monomial, Polynomial def used_vars(l, bound=None): diff --git a/src/sage/rings/polynomial/polynomial_rational_flint.pyx b/src/sage/rings/polynomial/polynomial_rational_flint.pyx index 55cfd8a4819..aba4482bd3b 100644 --- a/src/sage/rings/polynomial/polynomial_rational_flint.pyx +++ b/src/sage/rings/polynomial/polynomial_rational_flint.pyx @@ -2328,7 +2328,7 @@ cdef class Polynomial_rational_flint(Polynomial): sage: f.factor_padic(3,-1) Traceback (most recent call last): ... - ValueError: prec_cap must be non-negative. + ValueError: prec_cap must be non-negative sage: f.factor_padic(6,10) Traceback (most recent call last): ... diff --git a/src/sage/rings/real_lazy.pyx b/src/sage/rings/real_lazy.pyx index 6f64732bad4..24b679f8935 100644 --- a/src/sage/rings/real_lazy.pyx +++ b/src/sage/rings/real_lazy.pyx @@ -9,6 +9,15 @@ precision. The main purpose of these classes is to provide a place for exact rings (e.g. number fields) to embed for the coercion model (as only one embedding can be specified in the forward direction). + +TESTS: + +Bug :trac:`21991`:: + + sage: a = QuadraticField(5).gen() + sage: u = -573147844013817084101/2*a + 1281597540372340914251/2 + sage: RealIntervalField(128)(RLF(u)).is_exact() + False """ # **************************************************************************** diff --git a/src/sage/rings/tate_algebra_element.pyx b/src/sage/rings/tate_algebra_element.pyx index 495a4b080c7..ee3e01b5d9d 100644 --- a/src/sage/rings/tate_algebra_element.pyx +++ b/src/sage/rings/tate_algebra_element.pyx @@ -3140,7 +3140,7 @@ cdef class TateAlgebraElement(CommutativeAlgebraElement): sage: g.residue() Traceback (most recent call last): ... - ValueError: element must have non-negative valuation in order to compute residue. + ValueError: element must have non-negative valuation in order to compute residue The residue is not implemented for series with convergence radius different from 1. diff --git a/src/sage/rings/tests.py b/src/sage/rings/tests.py index d803d09023a..959d3807c0a 100644 --- a/src/sage/rings/tests.py +++ b/src/sage/rings/tests.py @@ -123,12 +123,21 @@ def relative_number_field(n=2, maxdeg=2): sage: import sage.rings.tests sage: sage.rings.tests.relative_number_field(3) Number Field in aaa with defining polynomial x^2 - 79*x - 53 over its base field + + TESTS: + + Check that :trac:`32117` is fixed:: + + sage: set_random_seed(3030) + sage: from sage.rings.tests import relative_number_field + sage: _ = relative_number_field(3) """ from sage.all import ZZ K = absolute_number_field(maxdeg) n -= 1 var = 'aa' R = ZZ['x'] + R1 = K['x'] while n >= 1: while True: f = R.random_element(degree=ZZ.random_element(x=1,y=maxdeg),x=-100,y=100) @@ -136,9 +145,10 @@ def relative_number_field(n=2, maxdeg=2): continue f = f * f.denominator() # bug trac #4781 f = f + R.gen()**maxdeg # make monic - if f.is_irreducible(): + if R1(f).is_irreducible(): break K = K.extension(f,var) + R1 = K['x'] var += 'a' n -= 1 return K diff --git a/src/sage/schemes/berkovich/berkovich_cp_element.py b/src/sage/schemes/berkovich/berkovich_cp_element.py index cae8feb751d..9c730a31626 100644 --- a/src/sage/schemes/berkovich/berkovich_cp_element.py +++ b/src/sage/schemes/berkovich/berkovich_cp_element.py @@ -21,17 +21,17 @@ AUTHORS: - - Alexander Galarraga (2020-06-22): initial implementation +- Alexander Galarraga (2020-06-22): initial implementation """ -#***************************************************************************** +# ***************************************************************************** # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# ***************************************************************************** from sage.structure.element import Element from sage.symbolic.expression import is_Expression @@ -45,12 +45,14 @@ from sage.rings.integer_ring import ZZ from sage.rings.infinity import Infinity + class Berkovich_Element(Element): """ The parent class for any element of a Berkovich space """ pass + class Berkovich_Element_Cp(Berkovich_Element): r""" The abstract parent class for any element of Berkovich space over `\CC_p`. @@ -85,14 +87,15 @@ def __init__(self, parent, center, radius=None, power=None, prec=20, space_type= from sage.rings.fraction_field_element import FractionFieldElement_1poly_field self._type = None - #if radius is a list or a tuple, this is a type 4 point + # if radius is a list or a tuple, this is a type 4 point if isinstance(radius, list) or isinstance(radius, tuple): if error_check: if not (isinstance(center, list) or isinstance(center, tuple)): raise TypeError("center was passed a list but radius was not a list") if len(radius) != len(center): - raise ValueError("the same number of centers and radii must be specified to create " + \ - "a type IV point") + raise ValueError("the same number of centers and radii " + "must be specified to create " + "a type IV point") self._center_lst = list(center) self._radius_lst = list(radius) self._prec = len(self._radius_lst) @@ -104,39 +107,39 @@ def __init__(self, parent, center, radius=None, power=None, prec=20, space_type= if not error_check: return - #is_FunctionFieldElement calls .parent + # is_FunctionFieldElement calls .parent elif hasattr(center, "parent") and hasattr(radius, 'parent'): from sage.rings.polynomial.multi_polynomial_element import is_MPolynomial if is_MPolynomial(center): try: center = center.univariate_polynomial() - except: - raise TypeError('center was %s, a multivariable polynomial' %center) + except AttributeError: + raise TypeError('center was %s, a multivariable polynomial' % center) - #check if the radius and the center are functions + # check if the radius and the center are functions center_func_check = is_FunctionFieldElement(center) or is_Polynomial(center) or\ isinstance(center, FractionFieldElement_1poly_field) or is_Expression(center) radius_func_check = is_FunctionFieldElement(radius) or is_Polynomial(radius) or\ isinstance(radius, FractionFieldElement_1poly_field) or is_Expression(radius) if center_func_check: - #check that both center and radii are supported univariate function + # check that both center and radii are supported univariate function center_expr_check = False radius_expr_check = False if error_check: if is_Expression(center): if len(center.variables()) != 1: - raise ValueError("an expression with %s " %(len(center.variables())) + \ - "variables cannot define the centers approximating a type IV point") + raise ValueError("an expression with %s " % (len(center.variables())) + + "variables cannot define the centers approximating a type IV point") else: - #we do this since .subs is currently buggy for polynomials but not expressions + # we do this since .subs is currently buggy for polynomials but not expressions center_expr_check = True if not radius_func_check: raise TypeError("center was passed a function but radius was not a function") if is_Expression(radius): if len(radius.variables()) != 1: - raise ValueError("an expression with %s " %(len(radius.variables())) + \ - "variables cannot define the radii approximating a type IV point") + raise ValueError("an expression with %s " % (len(radius.variables())) + + "variables cannot define the radii approximating a type IV point") else: radius_expr_check = True else: @@ -154,15 +157,15 @@ def __init__(self, parent, center, radius=None, power=None, prec=20, space_type= x = self._center_func.variables()[0] if radius_expr_check: y = self._radius_func.variables()[0] - for i in range(1,self._prec+1): + for i in range(1, self._prec + 1): if center_expr_check: - #we use .subs for expressions to avoid deprecation - center_lst.append(self._center_func.subs({x:i})) + # we use .subs for expressions to avoid deprecation + center_lst.append(self._center_func.subs({x: i})) else: - #.subs for polynomials is currently buggy + # .subs for polynomials is currently buggy center_lst.append(self._center_func(i)) if radius_expr_check: - radius_lst.append(self._radius_func.subs({y:i})) + radius_lst.append(self._radius_func.subs({y: i})) else: radius_lst.append(self._radius_func(i)) self._center_lst = center_lst @@ -177,57 +180,57 @@ def __init__(self, parent, center, radius=None, power=None, prec=20, space_type= for i in range(len(self._center_lst)): center = self._center_lst[i] radius = self._radius_lst[i] - #make sure the center is a point of projective space and not the point at infinity + # make sure the center is a point of projective space and not the point at infinity if not isinstance(center, SchemeMorphism_point_projective_field): try: center = (self._base_space)(center) except (TypeError, ValueError): - raise TypeError('could not convert %s to %s' %(center, self._base_space)) + raise TypeError('could not convert %s to %s' % (center, self._base_space)) if self._base_type == 'padic field': if not is_pAdicField(center.scheme().base_ring()): if not isinstance(center.scheme().base_ring(), pAdicBaseGeneric): try: center = (self._base_space)(center) except (TypeError, ValueError): - raise ValueError("could not convert %s to %s" %(center, self._base_space)) + raise ValueError("could not convert %s to %s" % (center, self._base_space)) else: # center is padic, not but an element of a scheme over a padic field. # we convert to scheme over a padic field center = ProjectiveSpace(center.scheme().base_ring().fraction_field(), 1)(center) if center.scheme().base_ring().prime() != self._p: - raise ValueError("center must be an element of " + \ - "%s not %s" %self._base_space, center.scheme()) + raise ValueError("center must be an element of " + + "%s not %s" % self._base_space, center.scheme()) else: if center not in self._base_space: try: center = (self._base_space)(center) except (TypeError, ValueError): - raise ValueError('could not convert %s to %s' %(center, self._base_space)) + raise ValueError('could not convert %s to %s' % (center, self._base_space)) if center.scheme().ambient_space() != center.scheme(): - raise ValueError("the center of a point of Berkovich space over " + \ - "P^1(Cp(%s)) must be a point of Cp not %s" %(self._p,center.scheme())) - if center == (center.scheme())((1,0)): - raise ValueError("the center of a disk approximating a type IV point of Berkovich " + \ - "space cannot be centered at %s" %((center.scheme())((1,0)))) - #since we are over a field, we can normalize coordinates. all code assumes normalized coordinates + raise ValueError("the center of a point of Berkovich space over " + + "P^1(Cp(%s)) must be a point of Cp not %s" % (self._p, center.scheme())) + if center == (center.scheme())((1, 0)): + raise ValueError("the center of a disk approximating a type IV point of Berkovich " + + "space cannot be centered at %s" % ((center.scheme())((1, 0)))) + # since we are over a field, we can normalize coordinates. all code assumes normalized coordinates center.normalize_coordinates() - #make sure the radius coerces into the reals + # make sure the radius coerces into the reals if not is_RealNumber(radius): if is_Expression(radius): radius = RR(radius) elif RR.has_coerce_map_from(radius.parent()): radius = RR(radius) else: - raise TypeError("the radius of a disk approximating a type IV point" + \ - "must coerce into the real numbers, %s does not coerce" %(radius)) + raise TypeError("the radius of a disk approximating a type IV point" + + "must coerce into the real numbers, %s does not coerce" % (radius)) if i != 0: - #check containment for the sequence of disks - previous_center = self._center_lst[i-1] - previous_radius = self._radius_lst[i-1] + # check containment for the sequence of disks + previous_center = self._center_lst[i - 1] + previous_radius = self._radius_lst[i - 1] dist = self._custom_abs(center[0] - previous_center[0]) if previous_radius < radius or dist > previous_radius: - raise ValueError("sequence of disks does not define a type IV point as " + \ - "containment is not proper") + raise ValueError("sequence of disks does not define a type IV point as " + + "containment is not proper") self._center_lst[i] = center self._radius_lst[i] = radius return @@ -236,25 +239,25 @@ def __init__(self, parent, center, radius=None, power=None, prec=20, space_type= center = self._center_lst[i] radius = self._radius_lst[i] if self._base_type == 'padic field': - #make sure the center is in Cp + # make sure the center is in Cp if not isinstance(center, pAdicGenericElement): try: center = (self._base_space)(center) except (TypeError, ValueError): - raise TypeError("could not convert %s to %s" %(center, self._base_space)) + raise TypeError("could not convert %s to %s" % (center, self._base_space)) elif not is_pAdicField(center.parent()): - #center is padic, not but an element of a padic field. we convert to padic field + # center is padic, not but an element of a padic field. we convert to padic field center = (center.parent().fraction_field())(center) if (center.parent()).prime() != self._p: - raise ValueError("center in %s, should be in %s") %(center.parent(), self._base_space) + raise ValueError("center in %s, should be in %s") % (center.parent(), self._base_space) else: - #make sure the center is in the appropriate number field + # make sure the center is in the appropriate number field if center.parent() == self._base_space: try: center = (self._base_space)(center) except (TypeError, ValueError): - raise ValueError('could not convert %s to %s' %(center, self._base_space)) - #make sure the radius coerces into the reals + raise ValueError('could not convert %s to %s' % (center, self._base_space)) + # make sure the radius coerces into the reals if not is_RealNumber(radius): if is_Expression(radius): radius = RR(radius) @@ -262,38 +265,38 @@ def __init__(self, parent, center, radius=None, power=None, prec=20, space_type= radius = RR(radius) self._radius_lst[i] = radius else: - raise ValueError("the radius of a disk approximating a type IV point must " + \ - "coerce into the real numbers, %s does not coerce" %(radius)) + raise ValueError("the radius of a disk approximating a type IV point must " + + "coerce into the real numbers, %s does not coerce" % (radius)) if i != 0: - #check containment for the sequence of disks - previous_center = self._center_lst[i-1] - previous_radius = self._radius_lst[i-1] + # check containment for the sequence of disks + previous_center = self._center_lst[i - 1] + previous_radius = self._radius_lst[i - 1] dist = self._custom_abs(center - previous_center) if previous_radius < radius or dist > previous_radius: - raise ValueError("sequence of disks does not define a type IV point as " + \ - "containment is not proper") + raise ValueError("sequence of disks does not define a type IV point as " + + "containment is not proper") self._center_lst[i] = center self._radius_lst[i] = radius return else: - raise ValueError("bad value %s passed to space_type. Do not initialize "%(space_type) + \ - "Berkovich_Element_Cp directly" ) + raise ValueError("bad value %s passed to space_type. Do not initialize " % (space_type) + + "Berkovich_Element_Cp directly") - #the point must now be type 1, 2, or 3, so we check that the center is of the appropriate type + # the point must now be type 1, 2, or 3, so we check that the center is of the appropriate type if error_check: if space_type == "projective": if not isinstance(center, SchemeMorphism_point_projective_field): try: center = (self._base_space)(center) except (ValueError, TypeError): - raise TypeError("could not convert %s to %s" %(center, self._base_space)) + raise TypeError("could not convert %s to %s" % (center, self._base_space)) if self._base_type == 'padic field': if not is_pAdicField(center.scheme().base_ring()): if not isinstance(center.scheme().base_ring(), pAdicBaseGeneric): try: center = (self._base_space)(center) except (TypeError, ValueError): - raise ValueError("could not convert %s to %s" %(center, self._base_space)) + raise ValueError("could not convert %s to %s" % (center, self._base_space)) else: # center is padic, not but an element of a scheme over a padic field. # we convert to scheme over a padic field @@ -301,48 +304,48 @@ def __init__(self, parent, center, radius=None, power=None, prec=20, space_type= try: center = field_scheme(center) except (TypeError, ValueError): - raise ValueError('could not convert %s to %s' %center, field_scheme) + raise ValueError('could not convert %s to %s' % center, field_scheme) if center.scheme().base_ring().prime() != self._p: - raise ValueError("center must be an element of " + \ - "%s not %s" %self._base_space, center.scheme()) + raise ValueError("center must be an element of " + + "%s not %s" % self._base_space, center.scheme()) else: if center not in self._base_space: try: center = (self._base_space)(center) except (TypeError, ValueError): - raise ValueError('could not convert %s to %s' %(center, self._base_space)) + raise ValueError('could not convert %s to %s' % (center, self._base_space)) if not(center.scheme().ambient_space() is center.scheme()): - raise ValueError("the center of a point of projective Berkovich space cannot be " + \ - "a point of %s" %(center.scheme())) - #since we are over a field, we normalize coordinates + raise ValueError("the center of a point of projective Berkovich space cannot be " + + "a point of %s" % (center.scheme())) + # since we are over a field, we normalize coordinates center.normalize_coordinates() elif space_type == 'affine': if self._base_type == 'padic field': - #make sure the center is in Cp + # make sure the center is in Cp if not isinstance(center, pAdicGenericElement): try: center = (self._base_space)(center) except (TypeError, ValueError): - raise TypeError("could not convert %s to %s" %(center, self._base_space)) + raise TypeError("could not convert %s to %s" % (center, self._base_space)) elif not is_pAdicField(center.parent()): - #center is padic, not but an element of a padic field. we convert to padic field + # center is padic, not but an element of a padic field. we convert to padic field center = (center.parent().fraction_field())(center) if (center.parent()).prime() != self._p: - raise ValueError("center in %s, should be in %s") %(center.parent(), self._base_space) + raise ValueError("center in %s, should be in %s") % (center.parent(), self._base_space) else: - #make sure the center is in the appropriate number field + # make sure the center is in the appropriate number field if not(center.parent() == self._base_space): try: center = (self._base_space)(center) except (TypeError, ValueError): - raise ValueError('could not convert %s to %s' %(center, self._base_space)) + raise ValueError('could not convert %s to %s' % (center, self._base_space)) else: - raise ValueError("bad value %s passed to space_type. Do not initialize "%(space_type) + \ - "Berkovich_Element_Cp directly") + raise ValueError("bad value %s passed to space_type. Do not initialize " % (space_type) + + "Berkovich_Element_Cp directly") self._center = center - #since this point is not type IV, these are None + # since this point is not type IV, these are None self._center_func = None self._center_lst = None self._radius_lst = None @@ -353,25 +356,25 @@ def __init__(self, parent, center, radius=None, power=None, prec=20, space_type= self._radius = 0 self._power = None return - #In order to simplify our representation, type II and III points cannot be centered at infinity + # In order to simplify our representation, type II and III points cannot be centered at infinity if space_type == "projective": - #TODO use involution map to allow for infinity to be passed in as center + # TODO use involution map to allow for infinity to be passed in as center if center[1] == 0: raise ValueError('type II and III points can not be centered at infinity') - if power != None: + if power is not None: if error_check: try: power = QQ(power) except TypeError: raise TypeError("power must convert to rationals") - if radius != None: + if radius is not None: if radius != RR(self._p**power): raise ValueError("conflicting inputs for power and radius") self._power = power self._radius = RR(self._p**power) self._type = 2 return - if radius != None: + if radius is not None: if is_Expression(radius): try: power = QQ(radius.log(self._p).expand_log()) @@ -382,8 +385,8 @@ def __init__(self, parent, center, radius=None, power=None, prec=20, space_type= self._radius = radius except TypeError: if len(radius.variables()) == 1: - raise ValueError('radius univariate function but center is constant. ' + \ - 'this does not define a type IV point') + raise ValueError('radius univariate function but center is constant. ' + + 'this does not define a type IV point') raise TypeError("symbolic radius must be a real number") if (not is_RealNumber(radius)) and power is None: if RR.has_coerce_map_from(radius.parent()): @@ -392,7 +395,7 @@ def __init__(self, parent, center, radius=None, power=None, prec=20, space_type= raise TypeError("radius must coerce into real numbers") else: self._radius = radius - if power != None: + if power is not None: self._power = power self._type = 2 return @@ -433,8 +436,8 @@ def _custom_abs(self, x): if x.valuation(self._ideal) == Infinity: return 0 if self._ideal in QQ: - return self.prime()**(-1*x.valuation(self._ideal)) - return self.prime()**(-1*x.valuation(self._ideal)/self._ideal.absolute_ramification_index()) + return self.prime()**(-x.valuation(self._ideal)) + return self.prime()**(-x.valuation(self._ideal) / self._ideal.absolute_ramification_index()) def center_function(self): """ @@ -513,7 +516,7 @@ def precision(self): sage: d.precision == d.prec True """ - if self._type in [1,2,3]: + if self._type in [1, 2, 3]: raise AttributeError("type I, II, and III points do not have a precision") return self._prec @@ -565,7 +568,7 @@ def power(self): sage: Q2.power() 1.26185950714291 """ - if self._type in [1,4]: + if self._type in [1, 4]: raise AttributeError("type I and IV points do not have a power") return self._power @@ -658,7 +661,7 @@ def diameter(self, basepoint=Infinity): x = R.gens()[0] if is_Expression(self._radius_func): radius_func_variable = self._radius_func.variables()[0] - radius_expr = self._radius_func.subs({radius_func_variable:x}) + radius_expr = self._radius_func.subs({radius_func_variable: x}) else: radius_expr = self._radius_func(x) from sage.symbolic.ring import SymbolicRing as SR @@ -666,7 +669,7 @@ def diameter(self, basepoint=Infinity): return radius_expr.limit(x="oo") return self._radius if not isinstance(basepoint, Berkovich_Element_Cp): - raise TypeError('basepoint must be a point of Berkovich space, not %s' %basepoint) + raise TypeError('basepoint must be a point of Berkovich space, not %s' % basepoint) if basepoint.parent() != self.parent(): raise ValueError('basepoint must be a point of the same Berkovich space') return self.Hsia_kernel(self, basepoint) @@ -708,7 +711,7 @@ def path_distance_metric(self, other): 0 """ if not isinstance(other, type(self)): - raise TypeError('other must be a point of Berkovich space. other was %s' %other) + raise TypeError('other must be a point of Berkovich space. other was %s' % other) if self.parent() != other.parent(): raise ValueError("other must be a point of the same Berkovich space") if self.type_of_point() == 1 or other.type_of_point() == 1: @@ -716,7 +719,7 @@ def path_distance_metric(self, other): return 0 else: return RR(Infinity) - return 2*self.join(other).diameter().log(self.prime()) \ + return 2 * self.join(other).diameter().log(self.prime()) \ - self.diameter().log(self.prime()) \ - other.diameter().log(other.prime()) @@ -759,18 +762,18 @@ def Hsia_kernel(self, other, basepoint): """ if not isinstance(other, type(self)): - raise TypeError('other must be a point of Berkovich space. other was %s' %other) + raise TypeError('other must be a point of Berkovich space. other was %s' % other) if self.parent() != other.parent(): raise ValueError("other must be a point of the same Berkovich space") if not isinstance(basepoint, type(self)): - raise TypeError('basepoint must be a point of Berkovich space. basepoint was %s' %basepoint) + raise TypeError('basepoint must be a point of Berkovich space. basepoint was %s' % basepoint) if basepoint.parent() != self.parent(): raise ValueError("basepoint must be a point of the same Berkovich space") if basepoint.type_of_point() == 1: if self == basepoint or other == basepoint: return RR(Infinity) - return self.spherical_kernel(other)/ \ - (self.spherical_kernel(basepoint)*other.spherical_kernel(basepoint)) + return self.spherical_kernel(other) / \ + (self.spherical_kernel(basepoint) * other.spherical_kernel(basepoint)) def small_metric(self, other): r""" @@ -813,14 +816,14 @@ def small_metric(self, other): 1.75000000000000 """ if not isinstance(other, Berkovich_Element_Cp): - raise TypeError('other must be a point of affine Berkovich space. other was %s' %other) + raise TypeError('other must be a point of affine Berkovich space. other was %s' % other) if self.parent() != other.parent(): raise ValueError('other must be a point of the same Berkovich space') gauss = self.parent()(RR(0), RR(1)) g_greater_than_s = gauss.gt(self) g_greater_than_o = gauss.gt(other) if g_greater_than_s and g_greater_than_o: - return 2*self.join(other, gauss).diameter() - self.diameter() - other.diameter() + return 2 * self.join(other, gauss).diameter() - self.diameter() - other.diameter() if not g_greater_than_s: new_self = self.involution_map() else: @@ -829,7 +832,7 @@ def small_metric(self, other): new_other = other.involution_map() else: new_other = other - return 2*new_self.join(new_other, gauss).diameter() \ + return 2 * new_self.join(new_other, gauss).diameter() \ - new_self.diameter() - new_other.diameter() def potential_kernel(self, other, basepoint): @@ -867,16 +870,16 @@ def potential_kernel(self, other, basepoint): 0.369070246428543 """ if not isinstance(other, type(self)): - raise TypeError('other must be a point of a Berkovich space, not %s' %other) + raise TypeError('other must be a point of a Berkovich space, not %s' % other) if other.parent() != self.parent(): raise ValueError('other must be a point of the same Berkovich space') if not isinstance(basepoint, type(self)): - raise TypeError('basepoint must be a point of Berkovich line, not %s' %basepoint) + raise TypeError('basepoint must be a point of Berkovich line, not %s' % basepoint) if basepoint.parent() != self.parent(): raise ValueError('basepoint must be a point of the same Berkovich space') return basepoint.path_distance_metric(self.join(other, basepoint)) - def spherical_kernel(self,other): + def spherical_kernel(self, other): r""" The spherical kernel of this point with ``other``. @@ -905,7 +908,7 @@ def spherical_kernel(self,other): 0 """ if not isinstance(other, type(self)): - raise TypeError('other must be a point of Berkovich space, not %s' %other) + raise TypeError('other must be a point of Berkovich space, not %s' % other) if other.parent() != self.parent(): raise ValueError('other must be a point of the same Berkovich space') gauss_point = self.parent()(ZZ(0), ZZ(1)) @@ -913,7 +916,7 @@ def spherical_kernel(self,other): dist = gauss_point.path_distance_metric(w) if dist == Infinity: return 0 - return self.prime()**(-1*dist) + return self.prime()**(-dist) def Hsia_kernel_infinity(self, other): r""" @@ -1042,21 +1045,21 @@ def _repr_(self): return "Type I point centered at " + format(self._center) elif self._type == 2: return "Type II point centered at " \ - + format(self._center) \ - + " of radius %s^%s" %(self._p, self._power) + + format(self._center) \ + + " of radius %s^%s" % (self._p, self._power) elif self._type == 3: return "Type III point centered at " \ - + format(self._center) + " of radius " \ - + format(self._radius) + + format(self._center) + " of radius " \ + + format(self._radius) else: - if self._center_func != None and self._radius_func != None: - return "Type IV point of precision %s " %self._prec + \ + if self._center_func is not None and self._radius_func is not None: + return "Type IV point of precision %s " % self._prec + \ "with centers given by %s and radii given by %s"\ - %(self._center_func, self._radius_func) + % (self._center_func, self._radius_func) else: - return "Type IV point of precision %s, approximated " %self._prec + \ + return "Type IV point of precision %s, approximated " % self._prec + \ "by disks centered at %s ... with radii %s ..." \ - %(self._center_lst[:min(self._prec, 2)], self._radius_lst[:min(self._prec, 2)]) + % (self._center_lst[:min(self._prec, 2)], self._radius_lst[:min(self._prec, 2)]) def _latex_(self): r""" @@ -1072,16 +1075,17 @@ def _latex_(self): """ from sage.misc.latex import latex if self._type == 1: - text = r"the point %s of } \Bold{C}_%s" %(self._center, self._p) - elif self._type in [2,3]: + text = r"the point %s of } \Bold{C}_%s" % (self._center, self._p) + elif self._type in [2, 3]: text = r"the disk centered at %s of radius %s in } \Bold{C}_%s" \ - %(self._center, self._radius, self._p) + % (self._center, self._radius, self._p) else: - text = "the sequence of disks with centers %s } " %self._center_lst[:2] + \ - r"\ldots \text{ and radii %s } \ldots" %self._radius_lst[:2] - return r"\text{type %s Point of }" %(self._type) \ + text = "the sequence of disks with centers %s } " % self._center_lst[:2] + \ + r"\ldots \text{ and radii %s } \ldots" % self._radius_lst[:2] + return r"\text{type %s Point of }" % (self._type) \ + latex(self.parent()) + r"\text{equivalent to " + text + class Berkovich_Element_Cp_Affine(Berkovich_Element_Cp): r""" Element class of the Berkovich affine line over `\CC_p`. @@ -1245,6 +1249,7 @@ class Berkovich_Element_Cp_Affine(Berkovich_Element_Cp): sage: TestSuite(Q5).run() """ + def __init__(self, parent, center, radius=None, power=None, prec=20, error_check=True): """ Initialization function. @@ -1255,20 +1260,20 @@ def __init__(self, parent, center, radius=None, power=None, prec=20, error_check sage: A(5, 1) Type II point centered at 5 + O(17^20) of radius 17^0 """ - #we call Berkovich_Element_Cp constructor which is shared with projective Berkovich space - #unless we are passed a point of projective Berkovich space + # we call Berkovich_Element_Cp constructor which is shared with projective Berkovich space + # unless we are passed a point of projective Berkovich space Element.__init__(self, parent) self._p = parent.prime() self._base_space = parent.base() self._base_type = parent._base_type self._ideal = parent._ideal - #if this is a point of projective Berkovich space, we raise an error + # if this is a point of projective Berkovich space, we raise an error if isinstance(center, Berkovich_Element_Cp_Projective): raise TypeError('use as_affine_point to convert to affine Berkovich space') - Berkovich_Element_Cp.__init__(self, parent=parent, center=center, radius=radius, power=power, \ - prec=prec, space_type="affine", error_check=error_check) + Berkovich_Element_Cp.__init__(self, parent=parent, center=center, radius=radius, power=power, + prec=prec, space_type="affine", error_check=error_check) def as_projective_point(self): r""" @@ -1457,7 +1462,7 @@ def lt(self, other): False """ if not isinstance(other, Berkovich_Element_Cp_Affine): - raise TypeError('other must be a point of a projective Berkovich space, but was %s' %other) + raise TypeError('other must be a point of a projective Berkovich space, but was %s' % other) if self.parent() != other.parent(): raise ValueError('other must be a point of the same projective Berkovich space') @@ -1529,7 +1534,7 @@ def gt(self, other): True """ if not isinstance(other, Berkovich_Element_Cp_Affine): - raise TypeError('other must be a point of a projective Berkovich space, but was %s' %other) + raise TypeError('other must be a point of a projective Berkovich space, but was %s' % other) if self.parent() != other.parent(): raise ValueError('other must be a point of the same projective Berkovich space') @@ -1548,7 +1553,7 @@ def gt(self, other): def join(self, other, basepoint=Infinity): """ - Computes the join of this point and ``other`` with respect to ``basepoint``. + Compute the join of this point and ``other`` with respect to ``basepoint``. The join is first point that lies on the intersection of the path from this point to ``basepoint`` and the path from ``other`` to @@ -1600,9 +1605,9 @@ def join(self, other, basepoint=Infinity): sage: Q1.join(Q7, Q2) Type III point centered at 2 + O(3^20) of radius 2.00000000000000 """ - #we error check and then pass to projective space to do the join + # we error check and then pass to projective space to do the join if not isinstance(other, Berkovich_Element_Cp_Affine): - raise TypeError('other must be a point of affine Berkovich space. other was %s' %other) + raise TypeError('other must be a point of affine Berkovich space. other was %s' % other) if self.parent() != other.parent(): raise ValueError('other must be a point of the same affine Berkovich space') if self.type_of_point() == 4 or other.type_of_point() == 4: @@ -1615,7 +1620,7 @@ def join(self, other, basepoint=Infinity): return proj_self.join(proj_other).as_affine_point() if not isinstance(basepoint, Berkovich_Element_Cp_Affine): - raise TypeError('basepoint must a point of affine Berkovich space. basepoint was %s' %basepoint) + raise TypeError('basepoint must a point of affine Berkovich space. basepoint was %s' % basepoint) if basepoint.parent() != self.parent(): raise ValueError("basepoint must be a point of the same affine Berkovich space") if basepoint.type_of_point() == 4: @@ -1623,7 +1628,6 @@ def join(self, other, basepoint=Infinity): proj_basepoint = basepoint.as_projective_point() return proj_self.join(proj_other, proj_basepoint).as_affine_point() - def involution_map(self): r""" Return the image of this point under the involution map. @@ -1691,7 +1695,7 @@ def involution_map(self): if self.type_of_point() == 1: if self.center() == 0: raise ValueError("involution map not defined on affine type I point centered at 0") - return self.parent()(1/self.center()) + return self.parent()(1 / self.center()) zero = self.parent()(ZZ(0)) radius = self.radius() @@ -1702,8 +1706,8 @@ def involution_map(self): if self.type_of_point() == 2: power = self.power() return self.parent()(ZZ(0), power=-power) - return self.parent()(ZZ(0), RR(1/radius)) - return self.parent()(1/self.center(), RR(radius / (self._custom_abs(self.center())**2))) + return self.parent()(ZZ(0), RR(1 / radius)) + return self.parent()(1 / self.center(), RR(radius / (self._custom_abs(self.center())**2))) new_center_lst = [] new_radius_lst = [] @@ -1713,17 +1717,17 @@ def involution_map(self): if zero_check: continue else: - new_center = 1/self.center()[i] - new_radius = self.radius()[i]/(self._custom_abs(self.center()[i])**2) + new_center = 1 / self.center()[i] + new_radius = self.radius()[i] / (self._custom_abs(self.center()[i])**2) new_center_lst.append(new_center) new_radius_lst.append(new_radius) - if len(new_center_lst) == 0: + if not new_center_lst: raise ValueError('precision of type IV is not high enough to define image') return self.parent()(new_center_lst, new_radius_lst, error_check=False) def contained_in_interval(self, start, end): """ - Checks if this point is an element of the interval [``start``, ``end``]. + Check if this point is an element of the interval [``start``, ``end``]. INPUT: @@ -1751,11 +1755,11 @@ def contained_in_interval(self, start, end): True """ if not isinstance(start, Berkovich_Element_Cp_Affine): - raise TypeError("start must be a point of affine Berkovich space. start was %s" %start) + raise TypeError("start must be a point of affine Berkovich space. start was %s" % start) if start.parent() != self.parent(): raise ValueError("start must be a point of the same Berkovich space as this point") if not isinstance(end, Berkovich_Element_Cp_Affine): - raise TypeError("end must be a point of affine Berkovich space. end was %s" %end) + raise TypeError("end must be a point of affine Berkovich space. end was %s" % end) if end.parent() != self.parent(): raise ValueError("end must be a point of the same Berkovich space as this point") @@ -1764,6 +1768,7 @@ def contained_in_interval(self, start, end): proj_end = end.as_projective_point() return proj_self.contained_in_interval(proj_start, proj_end) + class Berkovich_Element_Cp_Projective(Berkovich_Element_Cp): r""" Element class of the Berkovich projective line over `\CC_p`. @@ -1881,6 +1886,7 @@ class Berkovich_Element_Cp_Projective(Berkovich_Element_Cp): sage: Q1 = B(3) sage: TestSuite(Q1).run() """ + def __init__(self, parent, center, radius=None, power=None, prec=20, error_check=True): """ Initialization function. @@ -1892,20 +1898,20 @@ def __init__(self, parent, center, radius=None, power=None, prec=20, error_check sage: P(0,1) Type II point centered at (0 : 1 + O(7^20)) of radius 7^0 """ - #if we are given a point of Affine Berkovich Space, we do the conversion - #otherwise we call the Berkovich_Element_Cp constructor with space_type="projective" + # if we are given a point of Affine Berkovich Space, we do the conversion + # otherwise we call the Berkovich_Element_Cp constructor with space_type="projective" Element.__init__(self, parent) self._p = parent.prime() self._base_space = parent.base() self._base_type = parent._base_type self._ideal = parent._ideal - #conversion from Affine points is handled in this constructor + # conversion from Affine points is handled in this constructor if isinstance(center, Berkovich_Element_Cp_Affine): raise TypeError('use as_projective_point to convert to projective Berkovich space') - Berkovich_Element_Cp.__init__(self, parent=parent, center=center, radius=radius, power=power, \ - prec=prec, space_type="projective", error_check=error_check) + Berkovich_Element_Cp.__init__(self, parent=parent, center=center, radius=radius, power=power, + prec=prec, space_type="projective", error_check=error_check) def as_affine_point(self): """ @@ -2003,13 +2009,13 @@ def __eq__(self, other): return self.center() == other.center() elif stype == otype and stype == 4: raise NotImplementedError("equality for type IV points not implemented") - elif stype in [2,3] and otype in [2,3]: + elif stype in [2, 3] and otype in [2, 3]: if self.radius() != other.radius(): return False scent = self.center()[0] ocent = other.center()[0] center_dist = self._custom_abs(scent - ocent) - return center_dist <= self.radius() + return center_dist <= self.radius() else: return False @@ -2114,7 +2120,7 @@ def lt(self, other): False """ if not isinstance(other, Berkovich_Element_Cp_Projective): - raise TypeError('other must be a point of a projective Berkovich space, but was %s' %other) + raise TypeError('other must be a point of a projective Berkovich space, but was %s' % other) if self.parent() != other.parent(): raise ValueError('other must be a point of the same projective Berkovich space') @@ -2122,7 +2128,7 @@ def lt(self, other): return False # infinity is maximal with respect to the standard partial order - infinity = self.parent()((1,0)) + infinity = self.parent()((1, 0)) if self == infinity: return False if other == infinity: @@ -2204,14 +2210,14 @@ def gt(self, other): False """ if not isinstance(other, Berkovich_Element_Cp_Projective): - raise TypeError('other must be a point of a projective Berkovich space, but was %s' %other) + raise TypeError('other must be a point of a projective Berkovich space, but was %s' % other) if self.parent() != other.parent(): raise ValueError('other must be a point of the same projective Berkovich space') if self == other: return False # infinity is maximal with respect to the standard partial order - infinity = self.parent()((1,0)) + infinity = self.parent()((1, 0)) if self == infinity: return True if other == infinity: @@ -2229,7 +2235,7 @@ def gt(self, other): def join(self, other, basepoint=Infinity): """ - Computes the join of this point and ``other``, with respect to ``basepoint``. + Compute the join of this point and ``other``, with respect to ``basepoint``. The join is first point that lies on the intersection of the path from this point to ``basepoint`` and the path from ``other`` to @@ -2300,29 +2306,29 @@ def join(self, other, basepoint=Infinity): Type II point centered at (0 : 1) of radius 3^0 """ if not isinstance(other, Berkovich_Element_Cp_Projective): - raise TypeError('other must be a point of a projective Berkovich line, instead was %s' %other) + raise TypeError('other must be a point of a projective Berkovich line, instead was %s' % other) if other.parent() != self.parent(): raise ValueError('other must be a point of the same projective Berkovich line') - #if either self or other is type IV, we use the last disk in the approximation + # if either self or other is type IV, we use the last disk in the approximation if self.type_of_point() == 4: new_center = self.center()[-1] new_radius = self.radius()[-1] return self.parent()(new_center, new_radius).join(other) - if other.type_of_point() == 4: + if other.type_of_point() == 4: new_center = other.center()[-1] new_radius = other.radius()[-1] return self.join(self.parent()(new_center, new_radius)) - #we deal with the point at infinity as a special case - infty = self.parent()((1,0)) + # we deal with the point at infinity as a special case + infty = self.parent()((1, 0)) if basepoint == Infinity or basepoint == infty: if self == infty or other == infty: return infty dist = self._custom_abs(self.center()[0] - other.center()[0]) maximum = max(dist, self.radius(), other.radius()) - #optimize for when self or other are type II + # optimize for when self or other are type II if maximum == self.radius() and self.type_of_point() == 2: return self.parent()(self.center(), power=self.power()) if maximum == other.radius() and other.type_of_point() == 2: @@ -2330,11 +2336,11 @@ def join(self, other, basepoint=Infinity): return self.parent()(self.center(), maximum) if not isinstance(basepoint, Berkovich_Element_Cp_Projective): - raise TypeError('basepoint must be a point of a projective Berkovich line, instead was %s' %basepoint) + raise TypeError('basepoint must be a point of a projective Berkovich line, instead was %s' % basepoint) if basepoint.parent() != self.parent(): raise ValueError("basepoint must be a point of the same Berkovich projective line") - #if the basepoint is type IV, we use the last disk in the approximation + # if the basepoint is type IV, we use the last disk in the approximation if basepoint.type_of_point() == 4: new_center = other.center()[-1] new_radius = other.radius()[-1] @@ -2352,32 +2358,32 @@ def join(self, other, basepoint=Infinity): s_ge_o = self.gt(other) or self == other s_lt_o = self.lt(other) - #we deal with all the cases where self and other are not comparable first - if not (s_lt_o or s_ge_o): + # we deal with all the cases where self and other are not comparable first + if not (s_lt_o or s_ge_o): if not (b_ge_o or b_lt_o): if not (b_ge_s or b_lt_s): - #case where none of the points are comparable + # case where none of the points are comparable dist_b_s = self._custom_abs(self.center()[0] - basepoint.center()[0]) dist_b_o = self._custom_abs(other.center()[0] - basepoint.center()[0]) - return self.parent()(basepoint.center(), \ - min(max(dist_b_o, other.radius(), basepoint.radius()), \ - max(dist_b_s, self.radius(), basepoint.radius()))) + return self.parent()(basepoint.center(), + min(max(dist_b_o, other.radius(), basepoint.radius()), + max(dist_b_s, self.radius(), basepoint.radius()))) - #case where self and basepoint are comparable + # case where self and basepoint are comparable else: if b_ge_s: return basepoint else: return self - #case where other and basepoint are comparable + # case where other and basepoint are comparable else: if b_ge_o: return basepoint else: return other - #now the cases where self > other + # now the cases where self > other elif s_ge_o: if not (b_ge_s or b_lt_s): return self @@ -2388,7 +2394,7 @@ def join(self, other, basepoint=Infinity): if b_lt_o: return other - #join is symmetric, so we flip self and other so that self > other + # join is symmetric, so we flip self and other so that self > other else: return other.join(self, basepoint) @@ -2457,7 +2463,7 @@ def involution_map(self): 2*3^14 + 2*3^15 + 2*3^18 + 2*3^19 + 2*3^22 + 2*3^23 + O(3^24) : 1 + O(3^20))] ... with radii [0.00152415790275873, 0.00137174211248285] ... """ - infty = self.parent()((1,0)) + infty = self.parent()((1, 0)) zero = self.parent()(0) if self.type_of_point() == 1: @@ -2465,16 +2471,16 @@ def involution_map(self): return zero if self == zero: return infty - return self.parent()(1/self.center()[0]) + return self.parent()(1 / self.center()[0]) - if self.type_of_point() in [2,3]: + if self.type_of_point() in [2, 3]: zero_contained_in_self = self.gt(zero) if zero_contained_in_self: if self.type_of_point() == 2: power = self.power() return self.parent()(ZZ(0), power=-power) - return self.parent()(ZZ(0), 1/self.radius()) - return self.parent()(1/self.center()[0], self.radius()/(self._custom_abs(self.center()[0])**2)) + return self.parent()(ZZ(0), 1 / self.radius()) + return self.parent()(1 / self.center()[0], self.radius() / (self._custom_abs(self.center()[0])**2)) new_center_lst = [] new_radius_lst = [] @@ -2484,17 +2490,17 @@ def involution_map(self): if zero_check: continue else: - new_center = 1/self.center()[i][0] - new_radius = self.radius()[i]/(self._custom_abs(self.center()[i][0])**2) + new_center = 1 / self.center()[i][0] + new_radius = self.radius()[i] / (self._custom_abs(self.center()[i][0])**2) new_center_lst.append(new_center) new_radius_lst.append(new_radius) - if len(new_center_lst) == 0: + if not new_center_lst: raise ValueError('precision of type IV is not high enough to define image') return self.parent()(new_center_lst, new_radius_lst) def contained_in_interval(self, start, end): """ - Checks if this point is an element of the interval [``start``, ``end``]. + Check if this point is an element of the interval [``start``, ``end``]. INPUT: @@ -2561,22 +2567,22 @@ def contained_in_interval(self, start, end): if end.parent() != self.parent(): raise ValueError("start must be a point of the same Berkovich space as this point") - #we treat infinity as a special case + # we treat infinity as a special case infty = self.parent()((1, 0)) zero = self.parent()(ZZ(0)) if self == infty: if start == zero or end == zero: return end == infty or start == infty - return (self.involution_map()).contained_in_interval(start.involution_map(),\ - end.involution_map()) + return (self.involution_map()).contained_in_interval(start.involution_map(), + end.involution_map()) if start == infty or end == infty: if self == zero: return end == zero or start == zero if start == zero or end == zero: gauss = self.parent()(ZZ(0), ZZ(1)) return self.contained_in_interval(start, gauss) or self.contained_in_interval(gauss, end) - return self.involution_map().contained_in_interval(start.involution_map(), \ - end.involution_map()) + return self.involution_map().contained_in_interval(start.involution_map(), + end.involution_map()) join = start.join(end) j_ge_s = join.gt(self) or join == self s_ge_start = self.gt(start) or self == start diff --git a/src/sage/schemes/curves/curve.py b/src/sage/schemes/curves/curve.py index b513ae901e6..35eff517c38 100644 --- a/src/sage/schemes/curves/curve.py +++ b/src/sage/schemes/curves/curve.py @@ -86,7 +86,7 @@ def _repr_type(self): return "Generic" def _latex_(self): - """ + r""" Return a latex representation of this curve. EXAMPLES:: @@ -94,15 +94,24 @@ def _latex_(self): sage: x,y,z = PolynomialRing(QQ, 3, names='x,y,z').gens() sage: C = Curve(y^2*z - x^3 - 17*x*z^2 + y*z^2) sage: latex(C) - -x^{3} + y^{2} z - 17 x z^{2} + y z^{2} + \text{Projective Plane curve over $\Bold{Q}$ + defined by $-x^{3} + y^{2} z - 17 x z^{2} + y z^{2}$} sage: A2 = AffineSpace(2, QQ, names=['x','y']) sage: x, y = A2.coordinate_ring().gens() sage: C = Curve(y^2 - x^3 - 17*x + y) sage: latex(C) - -x^{3} + y^{2} - 17 x + y + \text{Affine Plane curve over $\Bold{Q}$ + defined by $-x^{3} + y^{2} - 17 x + y$} """ - return latex(self.defining_polynomial()) + if (self.defining_ideal().is_zero() + and self.ambient_space().dimension() == 1): + ambient_type, ring = self._repr_type(), latex(self.base_ring()) + return fr"\text{{{ambient_type} line over ${ring}$}}" + else: + ambient_type, ring = self._repr_type(), latex(self.base_ring()) + polys = ', '.join(f'${latex(p)}$' for p in self.defining_polynomials()) + return fr"\text{{{ambient_type} curve over ${ring}$ defined by {polys}}}" def defining_polynomial(self): """ diff --git a/src/sage/schemes/elliptic_curves/ell_number_field.py b/src/sage/schemes/elliptic_curves/ell_number_field.py index ccfa33915a9..821d2e96639 100644 --- a/src/sage/schemes/elliptic_curves/ell_number_field.py +++ b/src/sage/schemes/elliptic_curves/ell_number_field.py @@ -90,7 +90,9 @@ from .ell_point import EllipticCurvePoint_number_field from .constructor import EllipticCurve from sage.rings.all import PolynomialRing, ZZ, QQ, RealField, Integer -from sage.misc.all import cached_method, prod, union +from sage.misc.cachefunc import cached_method +from sage.misc.misc_c import prod + class EllipticCurve_number_field(EllipticCurve_field): r""" @@ -840,9 +842,9 @@ def global_integral_model(self): """ K = self.base_field() ai = self.a_invariants() - Ps = union(ff[0] - for a in ai if not a.is_integral() - for ff in a.denominator_ideal().factor()) + Ps = set(ff[0] + for a in ai if not a.is_integral() + for ff in a.denominator_ideal().factor()) for P in Ps: pi = K.uniformizer(P, 'positive') e = min((ai[i].valuation(P)/[1,2,3,4,6][i]) diff --git a/src/sage/schemes/elliptic_curves/ell_rational_field.py b/src/sage/schemes/elliptic_curves/ell_rational_field.py index bda999eeca3..2424cd1118b 100644 --- a/src/sage/schemes/elliptic_curves/ell_rational_field.py +++ b/src/sage/schemes/elliptic_curves/ell_rational_field.py @@ -121,9 +121,9 @@ class EllipticCurve_rational_field(EllipticCurve_number_field): INPUT: - ``ainvs`` -- a list or tuple `[a_1, a_2, a_3, a_4, a_6]` of - Weierstrass coefficients. + Weierstrass coefficients - .. note:: + .. NOTE:: This class should not be called directly; use :class:`sage.constructor.EllipticCurve` to construct @@ -203,9 +203,9 @@ def __init__(self, ainvs, **kwds): def _set_rank(self, r): """ Internal function to set the cached rank of this elliptic curve to - r. + ``r``. - .. warning:: + .. WARNING:: No checking is done! Not intended for use by users. @@ -224,9 +224,9 @@ def _set_rank(self, r): def _set_torsion_order(self, t): """ Internal function to set the cached torsion order of this elliptic - curve to t. + curve to ``t``. - .. warning:: + .. WARNING:: No checking is done! Not intended for use by users. @@ -245,9 +245,9 @@ def _set_torsion_order(self, t): def _set_cremona_label(self, L): """ Internal function to set the cached label of this elliptic curve to - L. + ``L``. - .. warning:: + .. WARNING:: No checking is done! Not intended for use by users. @@ -270,9 +270,9 @@ def _set_cremona_label(self, L): def _set_conductor(self, N): """ Internal function to set the cached conductor of this elliptic - curve to N. + curve to ``N.`` - .. warning:: + .. WARNING:: No checking is done! Not intended for use by users. Setting to the wrong value will cause strange problems (see @@ -291,9 +291,9 @@ def _set_conductor(self, N): def _set_modular_degree(self, deg): """ Internal function to set the cached modular degree of this elliptic - curve to deg. + curve to ``deg``. - .. warning:: + .. WARNING:: No checking is done! @@ -312,9 +312,9 @@ def _set_modular_degree(self, deg): def _set_gens(self, gens): """ Internal function to set the cached generators of this elliptic - curve to gens. + curve to ``gens``. - .. warning:: + .. WARNING:: No checking is done! @@ -361,7 +361,7 @@ def is_p_integral(self, p): INPUT: - - ``p`` -- a prime integer + - ``p`` -- a prime integer EXAMPLES:: @@ -412,7 +412,6 @@ def mwrank(self, options=''): INPUT: - - ``options`` (string) -- run-time options passed when starting mwrank. The format is as follows (see below for examples of usage): @@ -430,8 +429,7 @@ def mwrank(self, options=''): - ``string`` - output of mwrank on this curve - - .. note:: + .. NOTE:: The output is a raw string and completely illegible using automatic display, so it is recommended to use print for @@ -457,10 +455,8 @@ def mwrank(self, options=''): sage: E = EllipticCurve([0,0,0,877,0]) - Run mwrank with 'verbose' flag set to 0 but list generators if - found - - :: + Run mwrank with ``'verbose'`` flag set to 0 but list generators if + found:: sage: print(E.mwrank('-v0 -l')) Curve [0,0,0,877,0] : 0 <= rank <= 1 @@ -487,7 +483,6 @@ def conductor(self, algorithm="pari"): INPUT: - - ``algorithm`` - str, (default: "pari") - ``"pari"`` - use the PARI C-library :pari:`ellglobalred` @@ -498,14 +493,14 @@ def conductor(self, algorithm="pari"): coefficients (TODO: limited to small conductor until mwrank gets integer factorization) - - ``"gp"`` - use the GP interpreter. + - ``"gp"`` - use the GP interpreter - ``"generic"`` - use the general number field implementation - ``"all"`` - use all four implementations, verify - that the results are the same (or raise an error), and output the - common value. + that the results are the same (or raise an error), and + output the common value EXAMPLES:: @@ -522,7 +517,7 @@ def conductor(self, algorithm="pari"): sage: E.conductor(algorithm="all") 3006 - .. note:: + .. NOTE:: The conductor computed using each algorithm is cached separately. Thus calling ``E.conductor('pari')``, then @@ -709,7 +704,7 @@ def database_curve(self): sage: E.database_curve() Elliptic Curve defined by y^2 = x^3 + x^2 + 3*x + 5 over Rational Field - .. note:: + .. NOTE:: The model of the curve in the database can be different from the Weierstrass model for this curve, e.g., database @@ -733,8 +728,7 @@ def Np(self, p): INPUT: - - ``p`` (int) -- a prime, not necessarily of good reduction. - + - ``p`` (int) -- a prime, not necessarily of good reduction OUTPUT: @@ -795,19 +789,18 @@ def mwrank_curve(self, verbose=False): return self.__mwrank_curve def two_descent(self, verbose=True, - selmer_only = False, - first_limit = 20, - second_limit = 8, - n_aux = -1, - second_descent = 1): + selmer_only=False, + first_limit=20, + second_limit=8, + n_aux=-1, + second_descent=1): """ Compute 2-descent data for this curve. INPUT: - - - ``verbose`` - (default: True) print what mwrank is - doing. If False, **no output** is printed. + - ``verbose`` - (default: ``True``) print what mwrank is + doing; if ``False``, **no output** is printed - ``selmer_only`` - (default: ``False``) selmer_only switch @@ -823,7 +816,6 @@ def two_descent(self, verbose=True, - ``second_descent`` - (default: True) second_descent only relevant for descent via 2-isogeny - OUTPUT: Return ``True`` if the descent succeeded, i.e. if the lower bound and @@ -861,12 +853,10 @@ def aplist(self, n, python_ints=False): INPUT: + - ``n`` -- integer - - ``n`` - integer - - - ``python_ints`` - bool (default: ``False``); if ``True`` - return a list of Python ints instead of Sage integers. - + - ``python_ints`` -- bool (default: ``False``); if ``True`` + return a list of Python ints instead of Sage integers OUTPUT: list of integers @@ -893,21 +883,18 @@ def aplist(self, n, python_ints=False): else: return [Integer(a) for a in v] - - def anlist(self, n, python_ints=False): - """ + r""" The Fourier coefficients up to and including `a_n` of the - modular form attached to this elliptic curve. The i-th element of - the return list is a[i]. + modular form attached to this elliptic curve. The `i`-th element of + the return list is ``a[i]``. INPUT: + - ``n`` -- integer - - ``n`` - integer - - - ``python_ints`` - bool (default: ``False``); if ``True`` - return a list of Python ints instead of Sage integers. + - ``python_ints`` -- bool (default: ``False``); if ``True`` + return a list of Python ints instead of Sage integers OUTPUT: list of integers @@ -983,15 +970,13 @@ def q_expansion(self, prec): INPUT: - - - ``prec`` - an integer - + - ``prec`` -- an integer OUTPUT: a power series (in the variable 'q') - .. note:: + .. NOTE:: If you want the output to be a modular form and not just a `q`-expansion, use :meth:`.modular_form`. @@ -1000,7 +985,8 @@ def q_expansion(self, prec): sage: E = EllipticCurve('37a1') sage: E.q_expansion(20) - q - 2*q^2 - 3*q^3 + 2*q^4 - 2*q^5 + 6*q^6 - q^7 + 6*q^9 + 4*q^10 - 5*q^11 - 6*q^12 - 2*q^13 + 2*q^14 + 6*q^15 - 4*q^16 - 12*q^18 + O(q^20) + q - 2*q^2 - 3*q^3 + 2*q^4 - 2*q^5 + 6*q^6 - q^7 + 6*q^9 + 4*q^10 + - 5*q^11 - 6*q^12 - 2*q^13 + 2*q^14 + 6*q^15 - 4*q^16 - 12*q^18 + O(q^20) """ return PowerSeriesRing(Q, 'q')(self.anlist(prec), prec, check=True) @@ -1019,9 +1005,10 @@ def modular_form(self): If you need to see more terms in the `q`-expansion:: sage: f.q_expansion(20) - q - 2*q^2 - 3*q^3 + 2*q^4 - 2*q^5 + 6*q^6 - q^7 + 6*q^9 + 4*q^10 - 5*q^11 - 6*q^12 - 2*q^13 + 2*q^14 + 6*q^15 - 4*q^16 - 12*q^18 + O(q^20) + q - 2*q^2 - 3*q^3 + 2*q^4 - 2*q^5 + 6*q^6 - q^7 + 6*q^9 + 4*q^10 + - 5*q^11 - 6*q^12 - 2*q^13 + 2*q^14 + 6*q^15 - 4*q^16 - 12*q^18 + O(q^20) - .. note:: + .. NOTE:: If you just want the `q`-expansion, use :meth:`.q_expansion`. @@ -1041,11 +1028,8 @@ def modular_symbol_space(self, sign=1, base_ring=Q, bound=None): INPUT: - - - ``sign`` - 0, -1, or 1 - - - ``base_ring`` - a ring - + - ``sign`` -- 0, -1, or 1 + - ``base_ring`` -- a ring EXAMPLES:: @@ -1118,14 +1102,15 @@ def _modular_symbol_normalize(self, sign, normalize, implementation, nap): @cached_method(key = _modular_symbol_normalize) def modular_symbol(self, sign=+1, normalize=None, implementation='eclib', nap=0): - r"""Return the modular symbol map associated to this elliptic curve + r""" + Return the modular symbol map associated to this elliptic curve with given sign. INPUT: - - ``sign`` - +1 (default) or -1. + - ``sign`` -- +1 (default) or -1. - - ``normalize`` - (default: None); either 'L_ratio', 'period', + - ``normalize`` -- (default: ``None``); either 'L_ratio', 'period', or 'none'; ignored unless ``implementation`` is 'sage'. For 'L_ratio', the modular symbol tries to normalize correctly as explained below by comparing it to ``L_ratio`` @@ -1137,14 +1122,14 @@ def modular_symbol(self, sign=+1, normalize=None, implementation='eclib', nap=0) certainly not correctly normalized, i.e. all values will be a fixed scalar multiple of what they should be. - - ``implementation`` - either 'eclib' (default), 'sage' or + - ``implementation`` -- either 'eclib' (default), 'sage' or 'num'. Here, 'eclib' uses Cremona's ``C++`` implementation in the ``eclib`` library, 'sage' uses an implementation within Sage which is often quite a bit slower, and 'num' uses Wuthrich's implementation of numerical modular symbols. - - ``nap`` - (int, default 0); ignored unless implementation is + - ``nap`` -- (int, default 0); ignored unless implementation is 'eclib'. The number of ap of E to use in determining the normalisation of the modular symbols. If 0 (the default), then the value of 100*E.conductor().isqrt() is used. Using @@ -1152,43 +1137,43 @@ def modular_symbol(self, sign=+1, normalize=None, implementation='eclib', nap=0) DEFINITION: - The modular symbol map sends any rational number `r` to the - rational number whichis the ratio of the real or imaginary - part (depending on the sign) of the integral of `2 \pi i - f(z) dz` from `\infty` to `r`, where `f` is the newform - attached to `E`, to the real or imaginary period of `E`. - - More precisely: If the sign is +1, then the value returned - is the quotient of the real part of this integral by the - least positive period `\Omega_E^{+}` of `E`. In particular - for `r=0`, the value is equal to `L(E,1)/\Omega_E^{+}` - (unlike in ``L_ratio`` of ``lseries()``, where the value is - also divided by the number of connected components of - `E(\RR)`). In particular the modular symbol depends on `E` - and not only the isogeny class of `E`. For sign `-1`, it - is the quotient of the imaginary part of the integral - divided by the purely imaginary period of `E` with smallest - positive imaginary part. Note however there is an issue - about these normalizations, hence the optional argument - ``normalize`` explained below + The modular symbol map sends any rational number `r` to the + rational number whichis the ratio of the real or imaginary + part (depending on the sign) of the integral of `2 \pi i + f(z) dz` from `\infty` to `r`, where `f` is the newform + attached to `E`, to the real or imaginary period of `E`. + + More precisely: If the sign is +1, then the value returned + is the quotient of the real part of this integral by the + least positive period `\Omega_E^{+}` of `E`. In particular + for `r=0`, the value is equal to `L(E,1)/\Omega_E^{+}` + (unlike in ``L_ratio`` of ``lseries()``, where the value is + also divided by the number of connected components of + `E(\RR)`). In particular the modular symbol depends on `E` + and not only the isogeny class of `E`. For sign `-1`, it + is the quotient of the imaginary part of the integral + divided by the purely imaginary period of `E` with smallest + positive imaginary part. Note however there is an issue + about these normalizations, hence the optional argument + ``normalize`` explained below ALGORITHM: - For the implementations 'sage' and 'eclib', the used - algorithm starts by finding the space of modular symbols - within the full space of all modular symbols of that - level. This initial step will take a very long time if the - conductor is large (e.g. minutes for five digit - conductors). Once the space is determined, each evaluation - is very fast (logarithmic in the denominator of `r`). - - The implementation 'num' uses a different algorithm. It - uses numerical integration along paths in the upper half - plane. The bounds are rigorously proved so that the outcome - is known to be correct. The initial step costs no time, - instead each evaluation will take more time than in the - above. More information in the documentation of the class - ``ModularSymbolNumerical``. + For the implementations 'sage' and 'eclib', the used + algorithm starts by finding the space of modular symbols + within the full space of all modular symbols of that + level. This initial step will take a very long time if the + conductor is large (e.g. minutes for five digit + conductors). Once the space is determined, each evaluation + is very fast (logarithmic in the denominator of `r`). + + The implementation 'num' uses a different algorithm. It + uses numerical integration along paths in the upper half + plane. The bounds are rigorously proved so that the outcome + is known to be correct. The initial step costs no time, + instead each evaluation will take more time than in the + above. More information in the documentation of the class + ``ModularSymbolNumerical``. .. SEEALSO:: @@ -1314,7 +1299,7 @@ def modular_symbol(self, sign=+1, normalize=None, implementation='eclib', nap=0) return M def modular_symbol_numerical(self, sign=1, prec=20): - """ + r""" Return the modular symbol as a numerical function. Just as in :meth:`modular_symbol` this returns a function @@ -1336,16 +1321,16 @@ def modular_symbol_numerical(self, sign=1, prec=20): ALGORITHM: - This method does not compute spaces of modular symbols, - so it is suitable for curves of larger conductor than - can be handled by :meth:`modular_symbol`. - It is essentially the same implementation as - ``modular_symbol`` with implementation set to 'num'. - However the precision is not automatically chosen to - be certain that the output is equal to the rational - number it approximates. + This method does not compute spaces of modular symbols, + so it is suitable for curves of larger conductor than + can be handled by :meth:`modular_symbol`. + It is essentially the same implementation as + ``modular_symbol`` with implementation set to 'num'. + However the precision is not automatically chosen to + be certain that the output is equal to the rational + number it approximates. - For large conductors one should set the ``prec`` very small. + For large conductors one should set the ``prec`` very small. EXAMPLES:: @@ -1467,9 +1452,9 @@ def analytic_rank(self, algorithm="pari", leading_coefficient=False): - ``leading_coefficient`` -- (default: ``False``) Boolean; if set to True, return a tuple `(rank, lead)` where `lead` is the value of the first non-zero derivative of the L-function of the elliptic - curve. Only implemented for algorithm='pari'. + curve. Only implemented for ``algorithm='pari'``. - .. note:: + .. NOTE:: If the curve is loaded from the large Cremona database, then the modular degree is taken from the database. @@ -1481,12 +1466,12 @@ def analytic_rank(self, algorithm="pari", leading_coefficient=False): using default parameters, testing indicates that for 99.75% of curves the returned rank bound is the true rank. - .. note:: + .. NOTE:: - If you use set_verbose(1), extra information about the computation - will be printed when algorithm='zero_sum'. + If you use ``set_verbose(1)``, extra information about the + computation will be printed when ``algorithm='zero_sum'``. - .. note:: + .. NOTE:: It is an open problem to *prove* that *any* particular elliptic curve has analytic rank `\geq 4`. @@ -1507,9 +1492,10 @@ def analytic_rank(self, algorithm="pari", leading_coefficient=False): sage: E.analytic_rank(algorithm='all') 2 - With the optional parameter leading_coefficient set to ``True``, a - tuple of both the analytic rank and the leading term of the - L-series at `s = 1` is returned. This only works for algorithm=='pari':: + With the optional parameter leading_coefficient set to ``True``, + a tuple of both the analytic rank and the leading term of the + L-series at `s = 1` is returned. This only works for + ``algorithm=='pari'``:: sage: EllipticCurve([0,-1,1,-10,-20]).analytic_rank(leading_coefficient=True) (0, 0.25384186085591068...) @@ -1524,7 +1510,8 @@ def analytic_rank(self, algorithm="pari", leading_coefficient=False): TESTS: - When the input is horrendous, some of the algorithms just bomb out with a RuntimeError:: + When the input is horrendous, some of the algorithms just bomb + out with a ``RuntimeError``:: sage: EllipticCurve([1234567,89101112]).analytic_rank(algorithm='rubinstein') Traceback (most recent call last): @@ -1597,10 +1584,10 @@ def analytic_rank_upper_bound(self, INPUT: - - ``max_Delta`` -- (default: None) If not None, a positive real value + - ``max_Delta`` -- (default: ``None``) If not ``None``, a positive real value specifying the maximum Delta value used in the zero sum; larger values of Delta yield better bounds - but runtime is exponential in - Delta. If left as None, Delta is set + Delta. If left as ``None``, Delta is set to `\min\{\frac{1}{\pi}(\log(N+1000)/2-\log(2\pi)-\eta), 2.5\}`, where `N` is the conductor of the curve attached to self, and `\eta` is the Euler-Mascheroni constant `= 0.5772...`; the crossover @@ -1608,7 +1595,7 @@ def analytic_rank_upper_bound(self, empirical results show that for about 99.7% of all curves the returned value is the actual analytic rank. - - ``adaptive`` -- (default: True) Boolean + - ``adaptive`` -- (default: ``True``) boolean - ``True`` -- the computation is first run with small and then successively larger `\Delta` values up to max_Delta. If at any @@ -1618,11 +1605,11 @@ def analytic_rank_upper_bound(self, - ``False`` -- the computation is run a single time with `\Delta` equal to ``max_Delta``, and the resulting bound returned. - - ``N`` -- (default: None) If not None, a positive integer equal to - the conductor of self. This is passable so that rank estimation + - ``N`` -- (default: ``None``) If not ``None``, a positive integer equal + to the conductor of ``self``. This is passable so that rank estimation can be done for curves whose (large) conductor has been precomputed. - - ``root_number`` -- (default: "compute") String or integer + - ``root_number`` -- (default: "compute") string or integer - ``"compute"`` -- the root number of self is computed and used to (possibly) lower the analytic rank estimate by 1. @@ -1634,12 +1621,12 @@ def analytic_rank_upper_bound(self, self. This is passable so that rank estimation can be done for curves whose root number has been precomputed. - - ``bad_primes`` -- (default: None) If not None, a list of the primes + - ``bad_primes`` -- (default: ``None``) If not ``None``, a list of the primes of bad reduction for the curve attached to self. This is passable so that rank estimation can be done for curves of large conductor whose bad primes have been precomputed. - - ``ncpus`` - (default: None) If not None, a positive integer + - ``ncpus`` - (default: ``None``) If not ``None``, a positive integer defining the maximum number of CPUs to be used for the computation. If left as None, the maximum available number of CPUs will be used. Note: Due to parallelization overhead, multiple processors will @@ -1790,8 +1777,6 @@ def simon_two_descent(self, verbose=0, lim1=5, lim3=50, limtriv=3, INPUT: - - ``self`` -- an elliptic curve `E` over `\QQ` - - ``verbose`` -- 0, 1, 2, or 3 (default: 0), the verbosity level - ``lim1`` -- (default: 5) limit on trivial points on quartics @@ -1827,7 +1812,9 @@ def simon_two_descent(self, verbose=0, lim1=5, lim3=50, limtriv=3, To obtain a list of generators, use E.gens(). - IMPLEMENTATION: Uses Denis Simon's PARI/GP scripts from + IMPLEMENTATION: + + Uses Denis Simon's PARI/GP scripts from http://www.math.unicaen.fr/~simon/ EXAMPLES: @@ -1947,12 +1934,10 @@ def three_selmer_rank(self, algorithm='UseSUnits'): INPUT: - - - ``algorithm`` - 'Heuristic' (which is usually much + - ``algorithm`` -- 'Heuristic' (which is usually much faster in large examples), 'FindCubeRoots', or 'UseSUnits' (default) - OUTPUT: nonnegative integer EXAMPLES: A rank 0 curve:: @@ -2001,24 +1986,24 @@ def rank(self, use_database=True, verbose=False, INPUT: - - ``use_database (bool)`` -- (default: ``True``), if - ``True``, try to look up the rank in the Cremona database. + - ``use_database`` -- boolean (default: ``True``); if + ``True``, try to look up the rank in the Cremona database - - ``verbose`` - (default: ``False``), if specified changes - the verbosity of mwrank computations. + - ``verbose`` -- (default: ``False``) if specified changes + the verbosity of mwrank computations - - ``algorithm`` - (default: 'mwrank_lib'), one of: + - ``algorithm`` -- (default: ``'mwrank_lib'``) one of: - ``'mwrank_shell'`` - call mwrank shell command - ``'mwrank_lib'`` - call mwrank c library - - ``only_use_mwrank`` - (default: True) if False try - using analytic rank methods first. + - ``only_use_mwrank`` -- (default: ``True``) if ``False`` try + using analytic rank methods first - - ``proof`` - bool or None (default: None, see - proof.elliptic_curve or sage.structure.proof). Note that results - obtained from databases are considered proof = True + - ``proof`` -- bool (default: ``None``, see + ``proof.elliptic_curve`` or ``sage.structure.proof``); note that + results obtained from databases are considered ``proof=True`` OUTPUT: the rank of the elliptic curve as :class:`Integer` @@ -2185,8 +2170,8 @@ def rank(self, use_database=True, verbose=False, raise ValueError("unknown algorithm {!r}".format(algorithm)) def gens(self, proof=None, **kwds): - """ - Return generators for the Mordell-Weil group E(Q) *modulo* + r""" + Return generators for the Mordell-Weil group `E(Q)` *modulo* torsion. INPUT: @@ -2280,8 +2265,8 @@ def _compute_gens(self, proof, use_database=True, descent_second_limit=12, sat_bound=1000): - """ - Return generators for the Mordell-Weil group E(Q) *modulo* + r""" + Return generators for the Mordell-Weil group `E(Q)` *modulo* torsion. INPUT: @@ -2436,7 +2421,7 @@ def gens_certain(self): return self.__gens[1] def ngens(self, proof=None): - """ + r""" Return the number of generators of this elliptic curve. .. NOTE:: @@ -2525,27 +2510,27 @@ def regulator(self, proof=None, precision=53, **kwds): return reg def saturation(self, points, verbose=False, max_prime=-1, min_prime=2): - """Given a list of rational points on E, compute the saturation in - E(Q) of the subgroup they generate. + r""" + Given a list of rational points on `E`, compute the saturation in + `E(Q)` of the subgroup they generate. INPUT: + - ``points (list)`` -- list of points on `E` - - ``points (list)`` - list of points on E - - - ``verbose (bool)`` - (default: ``False``), if ``True``, give + - ``verbose (bool)`` -- (default: ``False``) if ``True``, give verbose output - - ``max_prime`` (int, default -1) -- If `-1` (the default), an + - ``max_prime`` -- int (default: `-1`); if `-1` (the default), an upper bound is computed for the primes at which the subgroup may not be saturated, and saturation is performed for all - primes up to this bound. Otherwise, the bound used is the - minimum of ``max_prime`` and the computed bound. + primes up to this bound; otherwise, the bound used is the + minimum of ``max_prime`` and the computed bound - - ``min_prime (int)`` - (default: 2), only do `p`-saturation - at primes `p` greater than or equal to this. + - ``min_prime (int)`` - (default: `2`) only do `p`-saturation + at primes `p` greater than or equal to this - .. note:: + .. NOTE:: To saturate at a single prime `p`, set ``max_prime`` and ``min_prime`` both to `p`. One situation where this is @@ -2564,13 +2549,15 @@ def saturation(self, points, verbose=False, max_prime=-1, min_prime=2): - ``regulator (real with default precision)`` - regulator of saturated points. - ALGORITHM: Uses Cremona's ``eclib`` package, which computes a + ALGORITHM: + + Uses Cremona's ``eclib`` package, which computes a bound on the saturation index. To `p`-saturate, or prove `p`-saturation, we consider the reductions of the points modulo primes `q` of good reduction such that `E(\GF{q})` has order divisible by `p`. - .. note:: + .. NOTE:: In versons of ``eclib`` up to ``v20190909``, division of points in ``eclib`` was done using floating point methods, @@ -2582,7 +2569,7 @@ def saturation(self, points, verbose=False, max_prime=-1, min_prime=2): division polynomials, and `p`-saturation cannot fail in this way. - .. note:: + .. NOTE:: The computed index of saturation may be large, in which case saturation may take a long time. For example, the @@ -2590,7 +2577,6 @@ def saturation(self, points, verbose=False, max_prime=-1, min_prime=2): saturation index bound of 11816 and takes around 40 seconds to prove saturation. - EXAMPLES:: sage: E = EllipticCurve('37a1') @@ -2718,21 +2704,22 @@ def CPS_height_bound(self): def silverman_height_bound(self, algorithm='default'): r""" - Return the Silverman height bound. This is a positive real - (floating point) number B such that for all points `P` on the - curve over any number field, `|h(P) - \hat{h}(P)| \leq B`, - where `h(P)` is the naive logarithmic height of `P` and - `\hat{h}(P)` is the canonical height. + Return the Silverman height bound. + + This is a positive real (floating point) number B such that + for all points `P` on the curve over any number field, + `|h(P) - \hat{h}(P)| \leq B`, where `h(P)` is the naive + logarithmic height of `P` and `\hat{h}(P)` is the canonical height. INPUT: - - ``algorithm`` -- + - ``algorithm`` -- one of the following: - - 'default' (default) -- compute using a Python - implementation in Sage + * ``'default'`` (default) - compute using a Python + implementation in Sage - - 'mwrank' -- use a C++ implementation in the mwrank - library + * ``'mwrank'`` -- use a C++ implementation in the mwrank + library .. NOTE:: @@ -2780,31 +2767,27 @@ def h_oo(x): raise ValueError("unknown algorithm '%s'"%algorithm) def point_search(self, height_limit, verbose=False, rank_bound=None): - """ + r""" Search for points on a curve up to an input bound on the naive logarithmic height. INPUT: + - ``height_limit`` -- float; bound on naive height - - ``height_limit (float)`` - bound on naive height - - - ``verbose (bool)`` - (default: ``False``) - - If ``True``, report on the saturation process. - - If ``False``, just return the result. - - - ``rank_bound (bool)`` - (default: ``None``) + - ``verbose`` -- boolean (default: ``False``); + if ``True``, report on the saturation process + otherwise just return the result - If provided, stop saturating once we find this many - independent nontorsion points. + - ``rank_bound`` -- boolean (optional); + if provided, stop saturating once we find this many + independent nontorsion points OUTPUT: points (list) - list of independent points which generate the subgroup of the Mordell-Weil group generated by the points found and then saturated. - .. warning:: + .. WARNING:: height_limit is logarithmic, so increasing by 1 will cause the running time to increase by a factor of approximately @@ -2862,7 +2845,7 @@ def point_search(self, height_limit, verbose=False, rank_bound=None): return points def selmer_rank(self): - """ + r""" The rank of the 2-Selmer group of the curve. EXAMPLES: The following is the curve 960D1, which has rank 0, but @@ -2909,9 +2892,11 @@ def selmer_rank(self): return self.__selmer_rank def rank_bound(self): - """ + r""" Upper bound on the rank of the curve, computed using - 2-descent. In many cases, this is the actual rank of the + 2-descent. + + In many cases, this is the actual rank of the curve. If the curve has no 2-torsion it is the same as the 2-selmer rank. @@ -2943,9 +2928,9 @@ def rank_bound(self): return self.__rank_bound def an(self, n): - """ - The n-th Fourier coefficient of the modular form corresponding to - this elliptic curve, where n is a positive integer. + r""" + The ``n``-th Fourier coefficient of the modular form corresponding to + this elliptic curve, where ``n`` is a positive integer. EXAMPLES:: @@ -2957,8 +2942,8 @@ def an(self, n): def ap(self, p): """ - The p-th Fourier coefficient of the modular form corresponding to - this elliptic curve, where p is prime. + The ``p``-th Fourier coefficient of the modular form corresponding to + this elliptic curve, where ``p`` is prime. EXAMPLES:: @@ -3018,14 +3003,15 @@ def is_minimal(self): def is_p_minimal(self, p): """ - Tests if curve is p-minimal at a given prime p. + Tests if curve is ``p``-minimal at a given prime ``p``. + + INPUT: - INPUT: p -- a prime + - ``p`` -- a prime OUTPUT: - ``True`` -- if curve is p-minimal - - ``False`` -- if curve is not p-minimal EXAMPLES:: @@ -3051,17 +3037,17 @@ def is_p_minimal(self, p): return self.discriminant().valuation(p) == Emin.discriminant().valuation(p) def kodaira_type(self, p): - """ - Local Kodaira type of the elliptic curve at `p`. + r""" + Local Kodaira type of the elliptic curve at ``p``. INPUT: - - p -- an integral prime + - ``p`` -- an integral prime OUTPUT: - - the Kodaira type of this elliptic curve at p, - as a KodairaSymbol. + - the Kodaira type of this elliptic curve at ``p``, + as a :class:`KodairaSymbol` EXAMPLES:: @@ -3074,19 +3060,17 @@ def kodaira_type(self, p): kodaira_symbol = kodaira_type def kodaira_type_old(self, p): - """ - Local Kodaira type of the elliptic curve at `p`. + r""" + Local Kodaira type of the elliptic curve at ``p``. INPUT: - - - p, an integral prime - + - ``p`` -- an integral prime OUTPUT: - - the Kodaira type of this elliptic curve at p, - as a KodairaSymbol. + - the Kodaira type of this elliptic curve at ``p``, + as a :class:`KodairaSymbol` EXAMPLES:: @@ -3110,7 +3094,7 @@ def kodaira_type_old(self, p): def tamagawa_number(self, p): r""" - The Tamagawa number of the elliptic curve at `p`. + The Tamagawa number of the elliptic curve at ``p``. This is the order of the component group `E(\QQ_p)/E^0(\QQ_p)`. @@ -3128,7 +3112,7 @@ def tamagawa_number(self, p): def tamagawa_number_old(self, p): r""" - The Tamagawa number of the elliptic curve at `p`. + The Tamagawa number of the elliptic curve at ``p``. This is the order of the component group `E(\QQ_p)/E^0(\QQ_p)`. @@ -3152,7 +3136,7 @@ def tamagawa_number_old(self, p): def tamagawa_exponent(self, p): r""" - The Tamagawa index of the elliptic curve at `p`. + The Tamagawa index of the elliptic curve at ``p``. This is the index of the component group `E(\QQ_p)/E^0(\QQ_p)`. It equals the @@ -3230,18 +3214,18 @@ def real_components(self): """ return 2 if self.discriminant() > 0 else 1 - def has_good_reduction_outside_S(self, S=[]): + def has_good_reduction_outside_S(self, S=None): r""" - Test if this elliptic curve has good reduction outside `S`. + Test if this elliptic curve has good reduction outside ``S``. INPUT: - - `S` -- list of primes (default: empty list). + - ``S`` -- list of primes (default: ``[]``). - .. note:: + .. NOTE:: - Primality of elements of S is not checked, and the output - is undefined if S is not a list or contains non-primes. + Primality of elements of ``S`` is not checked, and the output + is undefined if ``S`` is not a list or contains non-primes. This only tests the given model, so should only be applied to minimal models. @@ -3257,6 +3241,8 @@ def has_good_reduction_outside_S(self, S=[]): sage: EllipticCurve('2310a1').has_good_reduction_outside_S([2,3,5,7,11]) True """ + if S is None: + S = [] return self.discriminant().is_S_unit(S) def period_lattice(self, embedding=None): @@ -3290,21 +3276,22 @@ def period_lattice(self, embedding=None): def elliptic_exponential(self, z, embedding=None): r""" - Compute the elliptic exponential of a complex number with respect to the elliptic curve. + Compute the elliptic exponential of a complex number with + respect to the elliptic curve. INPUT: - - ``z`` (complex) -- a complex number + - ``z`` -- a complex number - - ``embedding`` - ignored (for compatibility with the - period_lattice function for elliptic_curve_number_field) + - ``embedding`` - ignored (for compatibility with the + period_lattice function for elliptic_curve_number_field) OUTPUT: The image of `z` modulo `L` under the Weierstrass parametrization `\CC/L \to E(\CC)`. - .. note:: + .. NOTE:: The precision is that of the input ``z``, or the default precision of 53 bits if ``z`` is exact. @@ -3432,13 +3419,14 @@ def lseries_gross_zagier(self, A): def Lambda(self, s, prec): r""" - Return the value of the Lambda-series of the elliptic curve E at - s, where s can be any complex number. + Return the value of the Lambda-series of the elliptic curve `E` at + ``s``, where ``s`` can be any complex number. + + IMPLEMENTATION: - IMPLEMENTATION: Fairly *slow* computation using the definitions - and implemented in Python. + Fairly *slow* computation using the definitions implemented in Python. - Uses prec terms of the power series. + Uses ``prec`` terms of the power series. EXAMPLES:: @@ -3459,10 +3447,10 @@ def _F(n, t): return gamma_inc(t+1, 2*pi*n/sqrtN) * C(sqrtN/(2*pi*n))**(t+1) return sum(a[n]*(_F(n,s-1) + eps*_F(n,1-s)) for n in range(1, prec+1)) - def is_local_integral_model(self,*p): + def is_local_integral_model(self, *p): r""" - Tests if self is integral at the prime `p`, or at all the - primes if `p` is a list or tuple of primes + Tests if ``self`` is integral at the prime ``p``, or at all the + primes if ``p`` is a list or tuple of primes. EXAMPLES:: @@ -3482,9 +3470,9 @@ def is_local_integral_model(self,*p): assert p.is_prime(), "p must be prime in is_local_integral_model()" return all(x.valuation(p) >= 0 for x in self.ainvs()) - def local_integral_model(self,p): + def local_integral_model(self, p): r""" - Return a model of self which is integral at the prime `p`. + Return a model of self which is integral at the prime ``p``. EXAMPLES:: @@ -3561,32 +3549,34 @@ def integral_short_weierstrass_model(self): return constructor.EllipticCurve([A, B]) def _generalized_congmod_numbers(self, M, invariant="both"): - """ + r""" Internal method to compute the generalized modular degree and congruence number at level `MN`, where `N` is the conductor of `E`. + Values obtained are cached. - This function is called by self.modular_degree() and self.congruence_number() when - `M>1`. Since so much of the computation of the two values is shared, this method + This function is called by :meth:`modular_degree()` and + :meth:`congruence_number()` when `M > 1`. Since so much + of the computation of the two values is shared, this method by default computes and caches both. INPUT: - - ``M`` - Non-negative integer; this function is only ever called on M>1, although - the algorithm works fine for the case `M==1` + - ``M`` -- non-negative integer; this function is only ever called on + `M > 1`, although the algorithm works fine for the case `M = 1` - - ``invariant`` - String; default "both". Options are: + - ``invariant`` -- string (default: "both"``); options are: - - "both" - Both modular degree and congruence number at level `MN` are computed + - "both" - both modular degree and congruence number at level `MN` are computed - - "moddeg" - Only modular degree is computed + - "moddeg" - only modular degree is computed - - "congnum" - Only congruence number is computed + - "congnum" - only congruence number is computed OUTPUT: - - A dictionary containing either the modular degree (a positive integer) at index "moddeg", - or the congruence number (a positive integer) at index "congnum", or both. + A dictionary containing either the modular degree (a positive integer) at index "moddeg", + or the congruence number (a positive integer) at index "congnum", or both. As far as we know there is no other implementation for this algorithm, so as yet there is nothing to check the below examples against. @@ -3653,18 +3643,18 @@ def modular_degree(self, algorithm='sympow', M=1): INPUT: - - ``algorithm`` - string: + - ``algorithm`` -- string: - - ``'sympow'`` - (default) use Mark Watkin's (newer) C - program sympow + * ``'sympow'`` - (default) use Mark Watkin's (newer) C + program sympow - - ``'magma'`` - requires that MAGMA be installed (also - implemented by Mark Watkins) + * ``'magma'`` - requires that MAGMA be installed (also + implemented by Mark Watkins) - - ``M`` - Non-negative integer; the modular degree at level `MN` is returned - (see above) + - ``M`` -- non-negative integer; the modular degree at level `MN` + is returned (see above) - .. note:: + .. NOTE:: On 64-bit computers ec does not work, so Sage uses sympow even if ec is selected on a 64-bit computer. @@ -3672,7 +3662,6 @@ def modular_degree(self, algorithm='sympow', M=1): The correctness of this function when called with algorithm "sympow" is subject to the following three hypothesis: - - Manin's conjecture: the Manin constant is 1 - Steven's conjecture: the `X_1(N)`-optimal quotient is @@ -3698,7 +3687,7 @@ def modular_degree(self, algorithm='sympow', M=1): have to guess ahead of time at what point to curtail this expansion.' (Quote from an email of Mark Watkins.) - .. note:: + .. NOTE:: If the curve is loaded from the large Cremona database, then the modular degree is taken from the database. @@ -3750,7 +3739,7 @@ def modular_degree(self, algorithm='sympow', M=1): 245 """ # Case 1: standard modular degree - if M==1: + if M == 1: try: return self.__modular_degree @@ -3778,7 +3767,7 @@ def modular_degree(self, algorithm='sympow', M=1): def modular_parametrization(self): r""" Return the modular parametrization of this elliptic curve, which is - a map from `X_0(N)` to self, where `N` is the conductor of self. + a map from `X_0(N)` to self, where `N` is the conductor of ``self``. EXAMPLES:: @@ -3834,8 +3823,8 @@ def congruence_number(self, M=1): INPUT: - - `M` -- Non-negative integer; congruence number is computed - at level `MN`, where `N` is the conductor of ``self``. + - ``M`` -- non-negative integer; congruence number is computed + at level `MN`, where `N` is the conductor of ``self`` EXAMPLES:: @@ -3951,11 +3940,11 @@ def cremona_label(self, space=False): label = cremona_label def reduction(self,p): - """ + r""" Return the reduction of the elliptic curve at a prime of good reduction. - .. note:: + .. NOTE:: The actual reduction is done in ``self.change_ring(GF(p))``; the reduction is performed after changing to a model which @@ -3965,7 +3954,7 @@ def reduction(self,p): - ``p`` -- a (positive) prime number - OUTPUT: an elliptic curve over the finite field GF(p) + OUTPUT: an elliptic curve over the finite field `\GF{p}` EXAMPLES:: @@ -4022,7 +4011,7 @@ def torsion_order(self): self.__torsion_order = self.torsion_subgroup().order() return self.__torsion_order - def _torsion_bound(self,number_of_places = 20): + def _torsion_bound(self, number_of_places=20): r""" Compute an upper bound on the order of the torsion group of the elliptic curve by counting points modulo several primes of good @@ -4033,20 +4022,18 @@ def _torsion_bound(self,number_of_places = 20): INPUT: - - ``number_of_places (default = 20)`` - the number + - ``number_of_places`` -- (default: 20) the number of places that will be used to find the bound OUTPUT: - - ``integer`` - the upper bound - - EXAMPLES: + - integer for the upper bound """ E = self bound = Integer(0) k = 0 p = Integer(2) # will run through odd primes - while k < number_of_places : + while k < number_of_places: p = p.next_prime() # check if the formal group at the place is torsion-free # if so the torsion injects into the reduction @@ -4059,13 +4046,13 @@ def _torsion_bound(self,number_of_places = 20): return bound def torsion_subgroup(self): - """ + r""" Return the torsion subgroup of this elliptic curve. OUTPUT: The EllipticCurveTorsionSubgroup instance associated to this elliptic curve. - .. note:: + .. NOTE:: To see the torsion points as a list, use :meth:`.torsion_points`. @@ -4173,16 +4160,15 @@ def torsion_points(self): @cached_method def root_number(self, p=None): - """ + r""" Return the root number of this elliptic curve. - This is 1 if the order of vanishing of the L-function L(E,s) at 1 + This is 1 if the order of vanishing of the L-function `L(E,s)` at 1 is even, and -1 if it is odd. INPUT: - - `p` -- optional, default (None); if given, return the local - root number at `p` + - `p` -- (optional) if given, return the local root number at ``p`` EXAMPLES:: @@ -4230,7 +4216,7 @@ def has_cm(self): :meth:`cm_discriminant()` and :meth:`has_rational_cm` - .. note:: + .. NOTE:: Even if `E` has CM in this sense (that its `j`-invariant is a CM `j`-invariant), since the associated negative @@ -4254,7 +4240,7 @@ def has_cm(self): return self.j_invariant() in CMJ def cm_discriminant(self): - """ + r""" Return the associated quadratic discriminant if this elliptic curve has Complex Multiplication over the algebraic closure. @@ -4288,9 +4274,8 @@ def has_rational_cm(self, field=None): INPUT: - - ``field`` -- a field, which should be an extension of `\QQ`. - If ``field`` is ``None`` (the default), it is taken to be - `\QQ`. + - ``field`` -- (default: `\QQ`) a field, which should be an + extension of `\QQ`; OUTPUT: @@ -4299,7 +4284,7 @@ def has_rational_cm(self, field=None): If ``field`` is ``None`` the output will always be ``False``. See also :meth:`cm_discriminant()` and :meth:`has_cm`. - .. note:: + .. NOTE:: If `E` has CM but the discriminant `D` is not a square in the given field `K`, which will certainly be the case for @@ -4371,7 +4356,7 @@ def has_rational_cm(self, field=None): def quadratic_twist(self, D): """ Return the global minimal model of the quadratic twist of this - curve by D. + curve by ``D``. EXAMPLES:: @@ -4392,14 +4377,14 @@ def minimal_quadratic_twist(self): discriminant of the quadratic field over which they are isomorphic. - .. note:: + .. NOTE:: If there is more than one curve with minimal conductor, the one returned is the one with smallest label (if in the database), or the one with minimal `a`-invariant list (otherwise). - .. note:: + .. NOTE:: For curves with `j`-invariant 0 or 1728 the curve returned is the minimal quadratic twist, not necessarily the minimal @@ -4428,7 +4413,8 @@ def minimal_quadratic_twist(self): sage: E.minimal_quadratic_twist() (Elliptic Curve defined by y^2 = x^3 + 4*x over Rational Field, 5) - If the curve has square-free conductor then it is already minimal (see :trac:`14060`):: + If the curve has square-free conductor then it is already + minimal (see :trac:`14060`):: sage: E = next(cremona_optimal_curves([2*3*5*7*11])) sage: (E, 1) == E.minimal_quadratic_twist() @@ -4485,26 +4471,26 @@ def isogeny_class(self, algorithm="sage", order=None): INPUT: - - ``algorithm`` - string: one of the following: + - ``algorithm`` -- string: one of the following: - "database" - use the Cremona database (only works if curve is isomorphic to a curve in the database) - "sage" (default) - use the native Sage implementation. - - ``order`` -- None, string, or list of curves (default: - None): If not None then the curves in the class are + - ``order`` -- ``None``, string, or list of curves (default: + ``None``); If not ``None`` then the curves in the class are reordered after being computed. Note that if the order is - None then the resulting order will depend on the algorithm. + ``None`` then the resulting order will depend on the algorithm. - - if ``order`` is "database" or "sage", then the reordering + - If ``order`` is "database" or "sage", then the reordering is so that the order of curves matches the order produced by that algorithm. - - if ``order`` is "lmfdb" then the curves are sorted + - If ``order`` is "lmfdb" then the curves are sorted lexicographically by a-invariants, in the LMFDB database. - - if ``order`` is a list of curves, then the curves in the + - If ``order`` is a list of curves, then the curves in the class are reordered to be isomorphic with the specified list of curves. @@ -4517,7 +4503,7 @@ class are reordered to be isomorphic with the specified for computing the isogeny matrix and the list of isogenies between curves in this class. - .. note:: + .. NOTE:: The curves in the isogeny class will all be standard minimal models. @@ -4658,14 +4644,14 @@ def isogenies_prime_degree(self, l=None): INPUT: - - ``l`` -- either None or a prime or a list of primes. + - ``l`` -- either ``None`` or a prime or a list of primes OUTPUT: (list) `\ell`-isogenies for the given `\ell` or if `\ell` is None, all `\ell`-isogenies. - .. note:: + .. NOTE:: The codomains of the isogenies returned are standard minimal models. This is because the functions @@ -4735,23 +4721,23 @@ def is_isogenous(self, other, proof=True, maxp=200): INPUT: - - ``other`` -- another elliptic curve. + - ``other`` -- another elliptic curve - - ``proof`` (default True) -- If ``False``, the function will + - ``proof`` -- (default: ``True``) if ``False``, the function will return ``True`` whenever the two curves have the same - conductor and are isogenous modulo `p` for `p` up to ``maxp``. - If ``True``, this test is followed by a rigorous test (which - may be more time-consuming). + conductor and are isogenous modulo `p` for `p` up to ``maxp``; + otherwise this test is followed by a rigorous test (which + may be more time-consuming) - - ``maxp`` (int, default 200) -- The maximum prime `p` for - which isogeny modulo `p` will be checked. + - ``maxp`` -- (default: 200) the maximum prime `p` for + which isogeny modulo `p` will be checked OUTPUT: (bool) True if there is an isogeny from curve ``self`` to curve ``other``. - METHOD: + ALGORITHM: First the conductors are compared as well as the Traces of Frobenius for good primes up to ``maxp``. If any of these @@ -4809,17 +4795,17 @@ def is_isogenous(self, other, proof=True, maxp=200): def isogeny_degree(self, other): """ - Return the minimal degree of an isogeny between self and - other. + Return the minimal degree of an isogeny between ``self`` and + ``other``. INPUT: - - ``other`` -- another elliptic curve. + - ``other`` -- another elliptic curve OUTPUT: - (int) The minimal degree of an isogeny from ``self`` to - ``other``, or 0 if the curves are not isogenous. + The minimal degree of an isogeny from ``self`` to + ``other``, or `0` if the curves are not isogenous. EXAMPLES:: @@ -4995,10 +4981,10 @@ def isogeny_graph(self, order=None): curve, where the vertices are isogenous curves over `\QQ` and the edges are prime degree isogenies. - .. note:: + .. NOTE:: - The vertices are labeled 1 to n rather than 0 to n-1 to - correspond to LMFDB and Cremona labels. + The vertices are labeled `1` to `n` rather than `0` to `n-1` + to correspond to LMFDB and Cremona labels. EXAMPLES:: @@ -5037,11 +5023,15 @@ def manin_constant(self): Return the Manin constant of this elliptic curve. If `\phi: X_0(N) \to E` is the modular - parametrization of minimal degree, then the Manin constant `c` is defined to be the rational - number `c` such that `\phi^*(\omega_E) = c\cdot \omega_f` where `\omega_E` is a Néron differential and `\omega_f = f(q) dq/q` is the differential on `X_0(N)` corresponding to the - newform `f` attached to the isogeny class of `E`. + parametrization of minimal degree, then the Manin constant `c` + is defined to be the rational number `c` such that + `\phi^*(\omega_E) = c\cdot \omega_f` where `\omega_E` is a Néron + differential and `\omega_f = f(q) dq/q` is the differential on `X_0(N)` + corresponding to the newform `f` attached to the isogeny class of `E`. - It is known that the Manin constant is an integer. It is conjectured that in each class there is at least one, more precisely the so-called strong Weil curve or `X_0(N)`-optimal curve, that has Manin constant `1`. + It is known that the Manin constant is an integer. It is conjectured + that in each class there is at least one, more precisely the so-called + strong Weil curve or `X_0(N)`-optimal curve, that has Manin constant `1`. OUTPUT: @@ -5155,7 +5145,7 @@ def _multiple_of_degree_of_isogeny_to_optimal_curve(self): the isogeny between this curve and the optimal curve in its isogeny class is a divisor of m. - .. warning:: + .. WARNING:: The result is *not* provably correct, in the sense that when the numbers are huge isogenies could be @@ -5246,13 +5236,14 @@ def is_semistable(self): return self.conductor().is_squarefree() def is_ordinary(self, p, ell=None): - """ - Return ``True`` precisely when the mod-p representation attached to - this elliptic curve is ordinary at ell. + r""" + Return ``True`` precisely when the mod-``p`` representation attached + to this elliptic curve is ordinary at ``ell``. INPUT: - - ``p`` - a prime ell - a prime (default: p) + - ``p`` -- a prime + - ``ell`` -- a prime (default: ``p``) OUTPUT: bool @@ -5273,11 +5264,11 @@ def is_ordinary(self, p, ell=None): def is_good(self, p, check=True): """ - Return ``True`` if `p` is a prime of good reduction for `E`. + Return ``True`` if ``p`` is a prime of good reduction for `E`. INPUT: - - ``p`` - a prime + - ``p`` -- a prime OUTPUT: bool @@ -5297,16 +5288,16 @@ def is_good(self, p, check=True): raise ValueError("p must be prime") return self.conductor() % p != 0 - def is_supersingular(self, p, ell=None): """ Return ``True`` precisely when p is a prime of good reduction and the - mod-p representation attached to this elliptic curve is + mod-``p`` representation attached to this elliptic curve is supersingular at ell. INPUT: - - ``p`` - a prime ell - a prime (default: p) + - ``p`` -- a prime + - ``ell`` -- a prime (default: ``p``) OUTPUT: bool @@ -5514,7 +5505,7 @@ def sha(self): def mod5family(self): """ Return the family of all elliptic curves with the same mod-5 - representation as self. + representation as ``self``. EXAMPLES:: @@ -5586,16 +5577,15 @@ def tate_curve(self, p): return Eq def height(self, precision=None): - """ - Return the real height of this elliptic curve. This is used in - integral_points() - - INPUT: + r""" + Return the real height of this elliptic curve. + This is used in :meth:`integral_points()`. - - ``precision`` - desired real precision of the result - (default real precision if None) + INPUT: + - ``precision`` -- desired real precision of the result + (default real precision if ``None``) EXAMPLES:: @@ -5635,23 +5625,24 @@ def height(self, precision=None): return max(R(1),h_j, h_gs) def faltings_height(self, stable=False, prec=None): - r"""Return the Faltings height (stable or unstable) of this elliptic curve. + r""" + Return the Faltings height (stable or unstable) of this elliptic curve. INPUT: - - ``stable`` (boolean, default ``False``) -- if ``True``, + - ``stable`` -- boolean (default: ``False``); if ``True``, return the *stable* Faltings height, otherwise the unstable - height. + height - - ``prec`` (integer or ``None``, default ``None``) -- bit - precision of output. If ``None`` (default), use standard - precision (53 bits). + - ``prec`` -- integer (default: ``None``); bit + precision of output; if ``None``, use standard + precision (53 bits) OUTPUT: (real) the Faltings height of this elliptic curve. - .. note:: + .. NOTE:: Different authors normalise the Faltings height differently. We use the formula `-\frac{1}{2}\log(A)`, @@ -5725,9 +5716,9 @@ def antilogarithm(self, z, max_denominator=None): - ``z`` -- a complex number representing an element of `\CC/L` where `L` is the period lattice of the elliptic curve - - ``max_denominator`` (int or None) -- parameter controlling + - ``max_denominator`` -- integer (optional); parameter controlling the attempted conversion of real numbers to rationals. If - None, ``simplest_rational()`` will be used; otherwise, + not given, ``simplest_rational()`` will be used; otherwise, ``nearby_rational()`` will be used with this value of ``max_denominator``. @@ -5736,7 +5727,7 @@ def antilogarithm(self, z, max_denominator=None): - point on the curve: the rational point which is the image of `z` under the Weierstrass parametrization, if it exists and can be determined from `z` and the given value - of max_denominator (if any); otherwise a ValueError exception + of max_denominator (if any); otherwise a ``ValueError`` exception is raised. EXAMPLES:: @@ -5780,7 +5771,7 @@ def integral_x_coords_in_interval(self,xmin,xmax): INPUT: - - ``xmin``, ``xmax`` (integers) -- two integers. + - ``xmin``, ``xmax`` (integers) -- two integers OUTPUT: @@ -5823,22 +5814,20 @@ def integral_points(self, mw_base='auto', both_signs=False, verbose=False): INPUT: + - ``mw_base`` -- (default: ``'auto'`` - calls ``self.gens()``) list + of EllipticCurvePoint generating the Mordell-Weil group of `E` - - ``mw_base`` - list of EllipticCurvePoint generating - the Mordell-Weil group of E (default: 'auto' - calls self.gens()) - - - ``both_signs`` - True/False (default False): if - True the output contains both P and -P, otherwise only one of each - pair. + - ``both_signs`` -- boolean (default: ``False``); if + ``True`` the output contains both `P` and `-P`, otherwise + only one of each pair - - ``verbose`` - True/False (default False): if True, + - ``verbose`` -- boolean (default: ``False``); if ``True``, some details of the computation are output + OUTPUT: A sorted list of all the integral points on `E` (up to sign + unless ``both_signs`` is ``True``) - OUTPUT: A sorted list of all the integral points on E (up to sign - unless both_signs is True) - - .. note:: + .. NOTE:: The complexity increases exponentially in the rank of curve E. The computation time (but not the output!) depends on @@ -5847,9 +5836,7 @@ def integral_points(self, mw_base='auto', both_signs=False, verbose=False): points which are not in the subgroup generated by the given points will almost certainly not be listed. - EXAMPLES: A curve of rank 3 with no torsion points - - :: + EXAMPLES: A curve of rank 3 with no torsion points:: sage: E = EllipticCurve([0,0,1,-7,6]) sage: P1=E.point((2,0)); P2=E.point((-1,3)); P3=E.point((4,6)) @@ -6215,20 +6202,19 @@ def S_integral_points(self, S, mw_base='auto', both_signs=False, verbose=False, INPUT: - - ``S`` - list of primes + - ``S`` -- list of primes - - ``mw_base`` - list of EllipticCurvePoint generating the - Mordell-Weil group of E (default: 'auto' - calls - :meth:`.gens`) + - ``mw_base`` -- (default: ``'auto'`` - calls :meth:`.gens`) list of + EllipticCurvePoint generating the Mordell-Weil group of `E` - - ``both_signs`` - True/False (default False): if True the - output contains both P and -P, otherwise only one of each - pair. + - ``both_signs`` -- boolean (default: ``False``); if ``True`` the + output contains both `P` and `-P`, otherwise only one of each + pair - - ``verbose`` - True/False (default False): if True, some - details of the computation are output. + - ``verbose`` -- boolean (default: ``False``); if ``True``, some + details of the computation are output - - ``proof`` - True/False (default True): if True ALL + - ``proof`` -- boolean (default: ``True``); if ``True`` ALL S-integral points will be returned. If False, the MW basis will be computed with the proof=False flag, and also the time-consuming final call to @@ -6916,10 +6902,10 @@ def integral_points_with_bounded_mw_coeffs(E, mw_base, N, x_bound): INPUT: - - ``E`` - an elliptic curve - - ``mw_base`` - a list of points on `E` (generators) - - ``N`` - a positive integer (bound on coefficients) - - ``x_bound`` - a positive real number (upper bound on size of x-coordinates) + - ``E`` -- an elliptic curve + - ``mw_base`` -- a list of points on `E` (generators) + - ``N`` -- a positive integer (bound on coefficients) + - ``x_bound`` -- a positive real number (upper bound on size of x-coordinates) OUTPUT: @@ -7097,3 +7083,4 @@ def elliptic_curve_congruence_graph(curves): G.add_edge(E.cremona_label(), F.cremona_label(), p_edges) return G + diff --git a/src/sage/schemes/elliptic_curves/gal_reps.py b/src/sage/schemes/elliptic_curves/gal_reps.py index b1c427c65bf..1f88e23a80c 100644 --- a/src/sage/schemes/elliptic_curves/gal_reps.py +++ b/src/sage/schemes/elliptic_curves/gal_reps.py @@ -728,7 +728,7 @@ def image_type(self, p): - a string. - EXAMPLES :: + EXAMPLES:: sage: E = EllipticCurve('14a1') sage: rho = E.galois_representation() diff --git a/src/sage/schemes/generic/algebraic_scheme.py b/src/sage/schemes/generic/algebraic_scheme.py index 4416daf1f70..9d0a2dddd89 100644 --- a/src/sage/schemes/generic/algebraic_scheme.py +++ b/src/sage/schemes/generic/algebraic_scheme.py @@ -247,7 +247,7 @@ def __init__(self, A): scheme.Scheme.__init__(self, A.base_scheme()) def _latex_(self): - """ + r""" Return a LaTeX representation of this algebraic scheme. TESTS:: @@ -257,9 +257,9 @@ def _latex_(self): sage: S = AlgebraicScheme(P); S Subscheme of Projective Space of dimension 3 over Integer Ring sage: S._latex_() - '\text{Subscheme of } {\\mathbf P}_{\\Bold{Z}}^3' + '\\text{Subscheme of ${\\mathbf P}_{\\Bold{Z}}^3$}' """ - return "\text{Subscheme of } %s" % latex(self.__A) + return r"\text{{Subscheme of ${}$}}".format(latex(self.__A)) def is_projective(self): """ diff --git a/src/sage/schemes/plane_conics/con_field.py b/src/sage/schemes/plane_conics/con_field.py index 823595850b5..04ee419f880 100644 --- a/src/sage/schemes/plane_conics/con_field.py +++ b/src/sage/schemes/plane_conics/con_field.py @@ -922,7 +922,7 @@ def point(self, v, check=True): If no rational point on ``self`` is known yet, then also caches the point for use by ``self.rational_point()`` and ``self.parametrization()``. - EXAMPLES :: + EXAMPLES:: sage: c = Conic([1, -1, 1]) sage: c.point([15, 17, 8]) @@ -961,7 +961,7 @@ def random_rational_point(self, *args1, **args2): If the base field is a finite field, then the output is uniformly distributed over the points of self. - EXAMPLES :: + EXAMPLES:: sage: c = Conic(GF(2), [1,1,1,1,1,0]) sage: [c.random_rational_point() for i in range(10)] # output is random @@ -1130,7 +1130,7 @@ def symmetric_matrix(self): The symmetric matrix `M` such that `(x y z) M (x y z)^t` is the defining equation of ``self``. - EXAMPLES :: + EXAMPLES:: sage: R. = QQ[] sage: C = Conic(x^2 + x*y/2 + y^2 + z^2) diff --git a/src/sage/schemes/plane_conics/con_number_field.py b/src/sage/schemes/plane_conics/con_number_field.py index 611690274c0..fdd9e2c02ad 100644 --- a/src/sage/schemes/plane_conics/con_number_field.py +++ b/src/sage/schemes/plane_conics/con_number_field.py @@ -47,7 +47,7 @@ def __init__(self, A, f): r""" See ``Conic`` for full documentation. - EXAMPLES :: + EXAMPLES:: sage: Conic([1, 1, 1]) Projective Conic Curve over Rational Field defined by x^2 + y^2 + z^2 @@ -364,7 +364,7 @@ def local_obstructions(self, finite=True, infinite=True, read_cache=True): Local obstructions are cached. The parameter ``read_cache`` specifies whether to look at the cache before computing anything. - EXAMPLES :: + EXAMPLES:: sage: K. = QuadraticField(-1) sage: Conic(K, [1, 2, 3]).local_obstructions() diff --git a/src/sage/schemes/plane_conics/con_rational_field.py b/src/sage/schemes/plane_conics/con_rational_field.py index 3eae681ba68..756d92dcab8 100644 --- a/src/sage/schemes/plane_conics/con_rational_field.py +++ b/src/sage/schemes/plane_conics/con_rational_field.py @@ -259,7 +259,7 @@ def local_obstructions(self, finite=True, infinite=True, read_cache=True): Local obstructions are cached. The parameter ``read_cache`` specifies whether to look at the cache before computing anything. - EXAMPLES :: + EXAMPLES:: sage: Conic(QQ, [1, 1, 1]).local_obstructions() [2, -1] @@ -318,7 +318,7 @@ def parametrization(self, point=None, morphism=True): Uses the PARI/GP function ``qfparam``. - EXAMPLES :: + EXAMPLES:: sage: c = Conic([1,1,-1]) sage: c.parametrization() diff --git a/src/sage/schemes/toric/variety.py b/src/sage/schemes/toric/variety.py index 72b1a0151a2..d70e01a482d 100644 --- a/src/sage/schemes/toric/variety.py +++ b/src/sage/schemes/toric/variety.py @@ -3174,7 +3174,7 @@ def _repr_(self): sage: toric_varieties.P2().cohomology_ring()._repr_() 'Rational cohomology ring of a 2-d CPR-Fano toric variety covered by 3 affine patches' """ - return 'Rational cohomology ring of a '+self._variety._repr_() + return f'Rational cohomology ring of a {self._variety._repr_()}' def _latex_(self): r""" @@ -3188,9 +3188,9 @@ def _latex_(self): sage: cohomology_ring = toric_varieties.P2().cohomology_ring() sage: print(cohomology_ring._latex_()) - H^\ast\left(\mathbb{P}_{\Delta^{2}_{15}},\QQ\right) + H^\ast\left(\mathbb{P}_{\Delta^{2}_{15}},\Bold{Q}\right) """ - return 'H^\\ast\\left('+self._variety._latex_()+',\\QQ\\right)' + return fr'H^\ast\left({self._variety._latex_()},{latex(QQ)}\right)' def _element_constructor_(self,x): r""" diff --git a/src/sage/server/support.py b/src/sage/server/support.py deleted file mode 100644 index ac7b5ee714b..00000000000 --- a/src/sage/server/support.py +++ /dev/null @@ -1,11 +0,0 @@ -""" -The EMBEDDED_MODE variable - -TESTS:: - - sage: from sage.server.support import EMBEDDED_MODE - sage: EMBEDDED_MODE - False -""" - -EMBEDDED_MODE = False diff --git a/src/sage/sets/all.py b/src/sage/sets/all.py index 7e5572945e4..1c1a69e57a7 100644 --- a/src/sage/sets/all.py +++ b/src/sage/sets/all.py @@ -11,4 +11,5 @@ from .primes import Primes from .family import Family from .disjoint_set import DisjointSet +from .condition_set import ConditionSet from .finite_set_maps import FiniteSetMaps diff --git a/src/sage/sets/condition_set.py b/src/sage/sets/condition_set.py new file mode 100644 index 00000000000..8119784a6b2 --- /dev/null +++ b/src/sage/sets/condition_set.py @@ -0,0 +1,477 @@ +r""" +Subsets of a Universe Defined by Predicates +""" + +# **************************************************************************** +# Copyright (C) 2021 Matthias Koeppe +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from sage.structure.category_object import normalize_names +from sage.structure.parent import Parent, Set_generic +from sage.structure.unique_representation import UniqueRepresentation +from sage.categories.sets_cat import Sets +from sage.misc.cachefunc import cached_method +from sage.misc.misc import _stable_uniq +from sage.symbolic.expression import is_Expression +from sage.symbolic.callable import is_CallableSymbolicExpression +from sage.symbolic.ring import SymbolicRing, SR, is_SymbolicVariable + +from .set import Set, Set_base, Set_boolean_operators, Set_add_sub_operators + +class ConditionSet(Set_generic, Set_base, Set_boolean_operators, Set_add_sub_operators, + UniqueRepresentation): + r""" + Set of elements of a universe that satisfy given predicates + + INPUT: + + - ``universe`` -- a set + + - ``*predicates`` -- callables + + - ``vars`` or ``names`` -- (default: inferred from ``predicates`` if any predicate is + an element of a :class:`~sage.symbolic.callable.CallableSymbolicExpressionRing_class`) + variables or names of variables + + - ``category`` -- (default: inferred from ``universe``) a category + + EXAMPLES:: + + sage: Evens = ConditionSet(ZZ, is_even); Evens + { x ∈ Integer Ring : (x) } + sage: 2 in Evens + True + sage: 3 in Evens + False + sage: 2.0 in Evens + True + + sage: Odds = ConditionSet(ZZ, is_odd); Odds + { x ∈ Integer Ring : (x) } + sage: EvensAndOdds = Evens | Odds; EvensAndOdds + Set-theoretic union of + { x ∈ Integer Ring : (x) } and + { x ∈ Integer Ring : (x) } + sage: 5 in EvensAndOdds + True + sage: 7/2 in EvensAndOdds + False + + sage: var('y') + y + sage: SmallOdds = ConditionSet(ZZ, is_odd, abs(y) <= 11, vars=[y]); SmallOdds + { y ∈ Integer Ring : abs(y) <= 11, (y) } + + sage: P = polytopes.cube(); P + A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 8 vertices + sage: P.rename("P") + sage: P_inter_B = ConditionSet(P, lambda x: x.norm() < 1.2); P_inter_B + { x ∈ P : at 0x...>(x) } + sage: vector([1, 0, 0]) in P_inter_B + True + sage: vector([1, 1, 1]) in P_inter_B + False + + sage: predicate(x, y, z) = sqrt(x^2 + y^2 + z^2) < 1.2; predicate + (x, y, z) |--> sqrt(x^2 + y^2 + z^2) < 1.20000000000000 + sage: P_inter_B_again = ConditionSet(P, predicate); P_inter_B_again + { (x, y, z) ∈ P : sqrt(x^2 + y^2 + z^2) < 1.20000000000000 } + sage: vector([1, 0, 0]) in P_inter_B_again + True + sage: vector([1, 1, 1]) in P_inter_B_again + False + + Using ``ConditionSet`` without predicates provides a way of attaching variable names + to a set:: + + sage: Z3 = ConditionSet(ZZ^3, vars=['x', 'y', 'z']); Z3 + { (x, y, z) ∈ Ambient free module of rank 3 over the principal ideal domain Integer Ring } + sage: Z3.variable_names() + ('x', 'y', 'z') + sage: Z3.arguments() + (x, y, z) + + sage: Q4. = ConditionSet(QQ^4); Q4 + { (a, b, c, d) ∈ Vector space of dimension 4 over Rational Field } + sage: Q4.variable_names() + ('a', 'b', 'c', 'd') + sage: Q4.arguments() + (a, b, c, d) + + TESTS:: + + sage: TestSuite(P_inter_B).run(skip='_test_pickling') # cannot pickle lambdas + sage: TestSuite(P_inter_B_again).run() + """ + @staticmethod + def __classcall_private__(cls, universe, *predicates, vars=None, names=None, category=None): + r""" + Normalize init arguments. + + TESTS:: + + sage: ConditionSet(ZZ, names=["x"]) is ConditionSet(ZZ, names=x) + True + sage: ConditionSet(RR, x > 0, names=x) is ConditionSet(RR, (x > 0).function(x)) + True + """ + if category is None: + category = Sets() + if isinstance(universe, Parent): + if universe in Sets().Finite(): + category = category & Sets().Finite() + + if vars is not None: + if names is not None: + raise ValueError('cannot use names and vars at the same time; they are aliases') + names, vars = vars, None + + if names is not None: + names = normalize_names(-1, names) + + callable_symbolic_predicates = [] + other_predicates = [] + + for predicate in predicates: + if is_CallableSymbolicExpression(predicate): + if names is None: + names = tuple(str(var) for var in predicate.args()) + elif len(names) != len(predicate.args()): + raise TypeError('mismatch in number of arguments') + if vars is None: + vars = predicate.args() + callable_symbolic_predicates.append(predicate) + elif is_Expression(predicate): + if names is None: + raise TypeError('use callable symbolic expressions or provide variable names') + if vars is None: + vars = tuple(SR.var(name) for name in names) + callable_symbolic_predicates.append(predicate.function(*vars)) + else: + other_predicates.append(predicate) + + predicates = list(_stable_uniq(callable_symbolic_predicates + other_predicates)) + + if not other_predicates and not callable_symbolic_predicates: + if names is None and category is None: + # No conditions, no variable names, no category, just use Set. + return Set(universe) + + if any(predicate.args() != vars + for predicate in callable_symbolic_predicates): + # TODO: Implement safe renaming of the arguments of a callable symbolic expressions + raise NotImplementedError('all callable symbolic expressions must use the same arguments') + + if names is None: + names = ("x",) + return super().__classcall__(cls, universe, *predicates, + names=names, category=category) + + def __init__(self, universe, *predicates, names=None, category=None): + r""" + TESTS:: + + sage: Evens = ConditionSet(ZZ, is_even); Evens + { x ∈ Integer Ring : (x) } + sage: TestSuite(Evens).run() + """ + self._universe = universe + self._predicates = predicates + facade = None + if isinstance(universe, Parent): + facade = universe + super().__init__(facade=facade, category=category, + names=names, normalize=False) # names already normalized by classcall + + def _first_ngens(self, n): + r""" + Return the list of variables. + + This is useful only for the use of Sage preparser:: + + sage: preparse("Q3. = ConditionSet(QQ^3)") + "Q3 = ConditionSet(QQ**Integer(3), names=('x', 'y', 'z',)); (x, y, z,) = Q3._first_ngens(3)" + + """ + return self.arguments() + + def _repr_(self): + """ + Print representation of this set. + + EXAMPLES:: + + sage: var('t') # parameter + t + sage: ZeroDimButNotNullary = ConditionSet(ZZ^0, t > 0, vars=("q")) + sage: ZeroDimButNotNullary._repr_() + '{ q ∈ Ambient free module of rank 0 + over the principal ideal domain Integer Ring : t > 0 }' + """ + s = "{ " + names = self.variable_names() + comma_sep_names = ", ".join(str(name) for name in names) + if len(names) == 1: + s += f"{comma_sep_names}" + else: + s += f"({comma_sep_names})" + universe = self._universe + s += f" ∈ {universe}" + sep = " : " + for predicate in self._predicates: + s += sep + self._repr_condition(predicate) + sep = ", " + s += " }" + return s + + @cached_method + def _repr_condition(self, predicate): + """ + Format the predicate, applied to the arguments. + + EXAMPLES:: + + sage: Evens = ConditionSet(ZZ, is_even) + sage: Evens._repr_condition(is_even) + '(x)' + sage: BigSin = ConditionSet(RR, sin(x) > 0.9, vars=[x]) + sage: BigSin._repr_condition(BigSin._predicates[0]) + 'sin(x) > 0.900000000000000' + sage: var('t') # parameter + t + sage: ZeroDimButNotNullary = ConditionSet(ZZ^0, t > 0, vars=("q")) + sage: ZeroDimButNotNullary._repr_condition(ZeroDimButNotNullary._predicates[0]) + 't > 0' + """ + if is_CallableSymbolicExpression(predicate): + args = self.arguments() + if len(args) == 1: + args = args[0] + condition = self._call_predicate(predicate, args) + return str(condition) + comma_sep_names = ", ".join(str(name) + for name in self.variable_names()) + return f"{predicate}({comma_sep_names})" + + @cached_method + def arguments(self): + """ + Return the variables of ``self`` as elements of the symbolic ring. + + EXAMPLES:: + + sage: Odds = ConditionSet(ZZ, is_odd); Odds + { x ∈ Integer Ring : (x) } + sage: args = Odds.arguments(); args + (x,) + sage: args[0].parent() + Symbolic Ring + """ + return SR.var(self.variable_names()) + + def _element_constructor_(self, *args, **kwds): + """ + Construct an element of the set. + + This element constructor raises an error if the element does not + satisfy the predicates. + + EXAMPLES:: + + sage: Evens = ConditionSet(ZZ, is_even); Evens + { x ∈ Integer Ring : (x) } + sage: element_two = Evens(2r); element_two + 2 + sage: element_two.parent() + Integer Ring + sage: element_too = Evens(2.0); element_too + 2 + sage: element_too.parent() + Integer Ring + sage: Evens(3) + Traceback (most recent call last): + ... + ValueError: 3 does not satisfy the condition + + """ + try: + universe_element_constructor = self._universe._element_constructor_ + except AttributeError: + if len(args) != 1 or kwds: + raise ValueError('element constructor only takes 1 argument') + element = args[0] + if element not in self._universe: + raise ValueError(f'{element} is not an element of the universe') + else: + element = universe_element_constructor(*args, **kwds) + if not all(self._call_predicate(predicate, element) + for predicate in self._predicates): + raise ValueError(f'{element} does not satisfy the condition') + return element + + def _call_predicate(self, predicate, element): + r""" + Call ``predicate`` on an ``element`` of the universe of ``self``. + + TESTS:: + + sage: TripleDigits = ZZ^3 + sage: predicate(x, y, z) = sqrt(x^2 + y^2 + z^2) < 12; predicate + (x, y, z) |--> sqrt(x^2 + y^2 + z^2) < 12 + sage: SmallTriples = ConditionSet(ZZ^3, predicate); SmallTriples + { (x, y, z) ∈ Ambient free module of rank 3 over the principal + ideal domain Integer Ring : sqrt(x^2 + y^2 + z^2) < 12 } + sage: predicate = SmallTriples._predicates[0] + sage: element = TripleDigits((1, 2, 3)) + sage: SmallTriples._call_predicate(predicate, element) + sqrt(14) < 12 + + sage: var('t') + t + sage: TinyUniverse = ZZ^0 + sage: Nullary = ConditionSet(TinyUniverse, t > 0, vars=()) + sage: predicate = Nullary._predicates[0] + sage: element = TinyUniverse(0) + sage: Nullary._call_predicate(predicate, element) + t > 0 + """ + if is_CallableSymbolicExpression(predicate): + if len(predicate.arguments()) != 1: + return predicate(*element) + return predicate(element) + + def _an_element_(self): + r""" + Return an element of ``self``. + + This may raise ``NotImplementedError``. + + TESTS:: + + sage: TripleDigits = ZZ^3 + sage: predicate(x, y, z) = sqrt(x^2 + y^2 + z^2) < 12; predicate + (x, y, z) |--> sqrt(x^2 + y^2 + z^2) < 12 + sage: SmallTriples = ConditionSet(ZZ^3, predicate); SmallTriples + { (x, y, z) ∈ Ambient free module of rank 3 over the principal + ideal domain Integer Ring : sqrt(x^2 + y^2 + z^2) < 12 } + sage: SmallTriples.an_element() # indirect doctest + (1, 0, 0) + """ + for element in self._universe.some_elements(): + if element in self: + return element + raise NotImplementedError + + def ambient(self): + r""" + Return the universe of ``self``. + + EXAMPLES:: + + sage: Evens = ConditionSet(ZZ, is_even); Evens + { x ∈ Integer Ring : (x) } + sage: Evens.ambient() + Integer Ring + """ + return self._universe + + @cached_method + def _sympy_(self): + r""" + Return an instance of a subclass of SymPy ``Set`` corresponding to ``self``. + + EXAMPLES:: + + sage: predicate(x, y, z) = sqrt(x^2 + y^2 + z^2) < 12; predicate + (x, y, z) |--> sqrt(x^2 + y^2 + z^2) < 12 + sage: SmallTriples = ConditionSet(ZZ^3, predicate); SmallTriples + { (x, y, z) ∈ Ambient free module of rank 3 over the principal + ideal domain Integer Ring : sqrt(x^2 + y^2 + z^2) < 12 } + sage: ST = SmallTriples._sympy_(); ST + ConditionSet((x, y, z), sqrt(x**2 + y**2 + z**2) < 12, + ProductSet(Integers, Integers, Integers)) + sage: (1, 3, 5) in ST + True + sage: (5, 7, 9) in ST + False + + sage: Interval = ConditionSet(RR, x >= -7, x <= 4, vars=[x]); Interval + { x ∈ Real Field with 53 bits of precision : x >= -7, x <= 4 } + sage: Interval._sympy_() + ConditionSet(x, (x >= -7) & (x <= 4), SageSet(Real Field with 53 bits of precision)) + + If a predicate is not symbolic, we fall back to creating a wrapper:: + + sage: Evens = ConditionSet(ZZ, is_even); Evens + { x ∈ Integer Ring : (x) } + sage: Evens._sympy_() + SageSet({ x ∈ Integer Ring : (x) }) + """ + from sage.interfaces.sympy import sympy_init + sympy_init() + import sympy + + args = self.arguments() + single_arg = len(args) == 1 + if single_arg: + args = args[0] + + try: + conditions = [self._call_predicate(predicate, args) + for predicate in self._predicates] + + sym = tuple(x._sympy_() for x in self.arguments()) + if single_arg: + sym = sym[0] + result = sympy.ConditionSet(sym, + sympy.And(*[condition._sympy_() + for condition in conditions]), + base_set=self._universe._sympy_()) + result._sage_object = self + return result + except TypeError: + # Fall back to creating a wrapper + return super()._sympy_() + + def intersection(self, X): + r""" + Return the intersection of ``self`` and ``X``. + + EXAMPLES:: + + sage: in_small_oblong(x, y) = x^2 + 3 * y^2 <= 42 + sage: SmallOblongUniverse = ConditionSet(QQ^2, in_small_oblong) + sage: SmallOblongUniverse + { (x, y) ∈ Vector space of dimension 2 over Rational Field : x^2 + 3*y^2 <= 42 } + sage: parity_check(x, y) = abs(sin(pi/2*(x + y))) < 1/1000 + sage: EvenUniverse = ConditionSet(ZZ^2, parity_check); EvenUniverse + { (x, y) ∈ Ambient free module of rank 2 over the principal ideal + domain Integer Ring : abs(sin(1/2*pi*x + 1/2*pi*y)) < (1/1000) } + sage: SmallOblongUniverse & EvenUniverse + { (x, y) ∈ Free module of degree 2 and rank 2 over Integer Ring + Echelon basis matrix: + [1 0] + [0 1] : x^2 + 3*y^2 <= 42, abs(sin(1/2*pi*x + 1/2*pi*y)) < (1/1000) } + + Combining two ``ConditionSet``s with different formal variables works correctly. + The formal variables of the intersection are taken from ``self``:: + + sage: SmallMirrorUniverse = ConditionSet(QQ^2, in_small_oblong, vars=(y, x)) + sage: SmallMirrorUniverse + { (y, x) ∈ Vector space of dimension 2 over Rational Field : 3*x^2 + y^2 <= 42 } + sage: SmallOblongUniverse & SmallMirrorUniverse + { (x, y) ∈ Vector space of dimension 2 over Rational Field : x^2 + 3*y^2 <= 42 } + sage: SmallMirrorUniverse & SmallOblongUniverse + { (y, x) ∈ Vector space of dimension 2 over Rational Field : 3*x^2 + y^2 <= 42 } + """ + if isinstance(X, ConditionSet): + return ConditionSet(self.ambient().intersection(X.ambient()), + *(self._predicates + X._predicates), + vars=self.arguments()) + return super().intersection(X) diff --git a/src/sage/sets/real_set.py b/src/sage/sets/real_set.py index b253a47fa31..0e4ad936191 100644 --- a/src/sage/sets/real_set.py +++ b/src/sage/sets/real_set.py @@ -89,6 +89,8 @@ class RealSet. from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation from sage.categories.topological_spaces import TopologicalSpaces +from sage.categories.sets_cat import Sets +from sage.sets.set import Set_base, Set_boolean_operators, Set_add_sub_operators from sage.rings.all import ZZ from sage.rings.real_lazy import LazyFieldElement, RLF from sage.rings.infinity import infinity, minus_infinity @@ -791,7 +793,8 @@ def __rmul__(self, other): return self * other @richcmp_method -class RealSet(UniqueRepresentation, Parent): +class RealSet(UniqueRepresentation, Parent, Set_base, + Set_boolean_operators, Set_add_sub_operators): @staticmethod def __classcall__(cls, *args): @@ -1395,6 +1398,33 @@ def _prep(lower, upper=None): else: return lower, upper + @staticmethod + def interval(lower, upper, *, lower_closed=None, upper_closed=None): + """ + Construct an interval + + INPUT: + + - ``lower``, ``upper`` -- two real numbers or infinity. They + will be sorted if necessary. + + - ``lower_closed``, ``upper_closed`` -- boolean; whether the interval + is closed at the lower and upper bound of the interval, respectively. + + OUTPUT: + + A new :class:`RealSet`. + + EXAMPLES:: + + sage: RealSet.interval(1, 0, lower_closed=True, upper_closed=False) + [0, 1) + """ + if lower_closed is None or upper_closed is None: + raise ValueError('lower_closed and upper_closed must be explicitly given') + lower, upper = RealSet._prep(lower, upper) + return RealSet(InternalRealInterval(lower, lower_closed, upper, upper_closed)) + @staticmethod def open(lower, upper): """ @@ -1621,9 +1651,6 @@ def union(self, *other): intervals = self._intervals + other._intervals return RealSet(*intervals) - __or__ = union - __add__ = union - def intersection(self, *other): """ Return the intersection of the two sets @@ -1668,8 +1695,6 @@ def intersection(self, *other): intervals.append(i1.intersection(i2)) return RealSet(*intervals) - __and__ = intersection - def inf(self): """ Return the infimum @@ -1794,7 +1819,30 @@ def difference(self, *other): other = RealSet(*other) return self.intersection(other.complement()) - __sub__ = difference + def symmetric_difference(self, *other): + r""" + Returns the symmetric difference of ``self`` and ``other``. + + INPUT: + + - ``other`` -- a :class:`RealSet` or data that defines one. + + OUTPUT: + + The set-theoretic symmetric difference of ``self`` and ``other`` + as a new :class:`RealSet`. + + EXAMPLES:: + + sage: s1 = RealSet(0,2); s1 + (0, 2) + sage: s2 = RealSet.unbounded_above_open(1); s2 + (1, +oo) + sage: s1.symmetric_difference(s2) + (0, 1] ∪ [2, +oo) + """ + other = RealSet(*other) + return self.difference(other).union(other.difference(self)) def contains(self, x): """ diff --git a/src/sage/sets/set.py b/src/sage/sets/set.py index 2777332e405..c4a32bf0efc 100644 --- a/src/sage/sets/set.py +++ b/src/sage/sets/set.py @@ -38,11 +38,13 @@ from sage.misc.latex import latex from sage.misc.prandom import choice +from sage.misc.cachefunc import cached_method from sage.structure.category_object import CategoryObject from sage.structure.element import Element from sage.structure.parent import Parent, Set_generic from sage.structure.richcmp import richcmp_method, richcmp, rich_to_bool +from sage.misc.classcall_metaclass import ClasscallMetaclass from sage.categories.sets_cat import Sets from sage.categories.enumerated_sets import EnumeratedSets @@ -192,7 +194,7 @@ def Set(X=None): else: return Set_object(X) - if isinstance(X, Element): + if isinstance(X, Element) and not isinstance(X, Set_base): raise TypeError("Element has no defined underlying set") try: @@ -203,8 +205,236 @@ def Set(X=None): return Set_object_enumerated(X) +class Set_base(): + r""" + Abstract base class for sets, not necessarily parents. + """ + + def union(self, X): + """ + Return the union of ``self`` and ``X``. + + EXAMPLES:: + + sage: Set(QQ).union(Set(ZZ)) + Set-theoretic union of Set of elements of Rational Field and Set of elements of Integer Ring + sage: Set(QQ) + Set(ZZ) + Set-theoretic union of Set of elements of Rational Field and Set of elements of Integer Ring + sage: X = Set(QQ).union(Set(GF(3))); X + Set-theoretic union of Set of elements of Rational Field and {0, 1, 2} + sage: 2/3 in X + True + sage: GF(3)(2) in X + True + sage: GF(5)(2) in X + False + sage: sorted(Set(GF(7)) + Set(GF(3)), key=int) + [0, 0, 1, 1, 2, 2, 3, 4, 5, 6] + """ + if isinstance(X, (Set_generic, Set_base)): + if self is X: + return self + return Set_object_union(self, X) + raise TypeError("X (=%s) must be a Set" % X) + + def intersection(self, X): + r""" + Return the intersection of ``self`` and ``X``. + + EXAMPLES:: + + sage: X = Set(ZZ).intersection(Primes()) + sage: 4 in X + False + sage: 3 in X + True + + sage: 2/1 in X + True + + sage: X = Set(GF(9,'b')).intersection(Set(GF(27,'c'))) + sage: X + {} + + sage: X = Set(GF(9,'b')).intersection(Set(GF(27,'b'))) + sage: X + {} + """ + if isinstance(X, (Set_generic, Set_base)): + if self is X: + return self + return Set_object_intersection(self, X) + raise TypeError("X (=%s) must be a Set" % X) + + def difference(self, X): + r""" + Return the set difference ``self - X``. + + EXAMPLES:: + + sage: X = Set(ZZ).difference(Primes()) + sage: 4 in X + True + sage: 3 in X + False + + sage: 4/1 in X + True + + sage: X = Set(GF(9,'b')).difference(Set(GF(27,'c'))) + sage: X + {0, 1, 2, b, b + 1, b + 2, 2*b, 2*b + 1, 2*b + 2} + + sage: X = Set(GF(9,'b')).difference(Set(GF(27,'b'))) + sage: X + {0, 1, 2, b, b + 1, b + 2, 2*b, 2*b + 1, 2*b + 2} + """ + if isinstance(X, (Set_generic, Set_base)): + if self is X: + return Set([]) + return Set_object_difference(self, X) + raise TypeError("X (=%s) must be a Set" % X) + + def symmetric_difference(self, X): + r""" + Returns the symmetric difference of ``self`` and ``X``. + + EXAMPLES:: + + sage: X = Set([1,2,3]).symmetric_difference(Set([3,4])) + sage: X + {1, 2, 4} + """ + if isinstance(X, (Set_generic, Set_base)): + if self is X: + return Set([]) + return Set_object_symmetric_difference(self, X) + raise TypeError("X (=%s) must be a Set" % X) + + def _test_as_set_object(self, tester=None, **options): + r""" + Run the test suite of ``Set(self)`` unless it is identical to ``self``. + + EXAMPLES: + + Nothing is tested for instances of :class`Set_generic` (constructed + with the :func:`Set` constructor):: + + sage: Set(ZZ)._test_as_set_object(verbose=True) + + Instances of other subclasses of :class:`Set_base` run this method:: + + sage: Polyhedron()._test_as_set_object(verbose=True) + Running the test suite of Set(self) + running ._test_an_element() . . . pass + ... + running ._test_some_elements() . . . pass + """ + if tester is None: + tester = self._tester(**options) + set_self = Set(self) + if set_self is not self: + from sage.misc.sage_unittest import TestSuite + tester.info("\n Running the test suite of Set(self)") + TestSuite(set_self).run(skip="_test_pickling", # see Trac #32025 + verbose=tester._verbose, + prefix=tester._prefix + " ") + tester.info(tester._prefix + " ", newline=False) + + +class Set_boolean_operators: + r""" + Mix-in class providing the Boolean operators ``__or__``, ``__and__``, ``__xor__``. + + The operators delegate to the methods ``union``, ``intersection``, and + ``symmetric_difference``, which need to be implemented by the class. + """ + + def __or__(self, X): + """ + Return the union of ``self`` and ``X``. + + EXAMPLES:: + + sage: Set([2,3]) | Set([3,4]) + {2, 3, 4} + sage: Set(ZZ) | Set(QQ) + Set-theoretic union of Set of elements of Integer Ring and Set of elements of Rational Field + """ + return self.union(X) + + def __and__(self, X): + """ + Returns the intersection of ``self`` and ``X``. + + EXAMPLES:: + + sage: Set([2,3]) & Set([3,4]) + {3} + sage: Set(ZZ) & Set(QQ) + Set-theoretic intersection of Set of elements of Integer Ring and Set of elements of Rational Field + """ + return self.intersection(X) + + def __xor__(self, X): + """ + Returns the symmetric difference of ``self`` and ``X``. + + EXAMPLES:: + + sage: X = Set([1,2,3,4]) + sage: Y = Set([1,2]) + sage: X.symmetric_difference(Y) + {3, 4} + sage: X.__xor__(Y) + {3, 4} + """ + return self.symmetric_difference(X) + + +class Set_add_sub_operators: + r""" + Mix-in class providing the operators ``__add__`` and ``__sub__``. + + The operators delegate to the methods ``union`` and ``intersection``, + which need to be implemented by the class. + """ + + def __add__(self, X): + """ + Return the union of ``self`` and ``X``. + + EXAMPLES:: + + sage: Set(RealField()) + Set(QQ^5) + Set-theoretic union of + Set of elements of Real Field with 53 bits of precision and + Set of elements of Vector space of dimension 5 over Rational Field + sage: Set(GF(3)) + Set(GF(2)) + {0, 1, 2, 0, 1} + sage: Set(GF(2)) + Set(GF(4,'a')) + {0, 1, a, a + 1} + sage: sorted(Set(GF(8,'b')) + Set(GF(4,'a')), key=str) + [0, 0, 1, 1, a, a + 1, b, b + 1, b^2, b^2 + 1, b^2 + b, b^2 + b + 1] + """ + return self.union(X) + + def __sub__(self, X): + """ + Return the difference of ``self`` and ``X``. + + EXAMPLES:: + + sage: X = Set(ZZ).difference(Primes()) + sage: Y = Set(ZZ) - Primes() + sage: X == Y + True + """ + return self.difference(X) + + @richcmp_method -class Set_object(Set_generic): +class Set_object(Set_generic, Set_base, Set_boolean_operators, Set_add_sub_operators): r""" A set attached to an almost arbitrary object. @@ -231,6 +461,7 @@ class Set_object(Set_generic): sage: 1 == Set([0]), Set([0]) == 1 (False, False) """ + def __init__(self, X, category=None): """ Create a Set_object @@ -333,7 +564,28 @@ def __iter__(self): """ return iter(self.__object) - an_element = EnumeratedSets.ParentMethods.__dict__['_an_element_from_iterator'] + _an_element_from_iterator = EnumeratedSets.ParentMethods.__dict__['_an_element_from_iterator'] + + def _an_element_(self): + """ + Return an element of ``self``. + + EXAMPLES:: + + sage: R = Set(RR) + sage: R.an_element() # indirect doctest + 1.00000000000000 + + sage: F = Set([1, 2, 3]) + sage: F.an_element() + 1 + """ + if self.__object is not self: + try: + return self.__object.an_element() + except (AttributeError, NotImplementedError): + pass + return self._an_element_from_iterator() def __contains__(self, x): """ @@ -404,183 +656,6 @@ def __richcmp__(self, right, op): return NotImplemented return richcmp(self.__object, right.__object, op) - def union(self, X): - """ - Return the union of ``self`` and ``X``. - - EXAMPLES:: - - sage: Set(QQ).union(Set(ZZ)) - Set-theoretic union of Set of elements of Rational Field and Set of elements of Integer Ring - sage: Set(QQ) + Set(ZZ) - Set-theoretic union of Set of elements of Rational Field and Set of elements of Integer Ring - sage: X = Set(QQ).union(Set(GF(3))); X - Set-theoretic union of Set of elements of Rational Field and {0, 1, 2} - sage: 2/3 in X - True - sage: GF(3)(2) in X - True - sage: GF(5)(2) in X - False - sage: sorted(Set(GF(7)) + Set(GF(3)), key=int) - [0, 0, 1, 1, 2, 2, 3, 4, 5, 6] - """ - if isinstance(X, Set_generic): - if self is X: - return self - return Set_object_union(self, X) - raise TypeError("X (=%s) must be a Set"%X) - - def __add__(self, X): - """ - Return the union of ``self`` and ``X``. - - EXAMPLES:: - - sage: Set(RealField()) + Set(QQ^5) - Set-theoretic union of Set of elements of Real Field with 53 bits of precision and Set of elements of Vector space of dimension 5 over Rational Field - sage: Set(GF(3)) + Set(GF(2)) - {0, 1, 2, 0, 1} - sage: Set(GF(2)) + Set(GF(4,'a')) - {0, 1, a, a + 1} - sage: sorted(Set(GF(8,'b')) + Set(GF(4,'a')), key=str) - [0, 0, 1, 1, a, a + 1, b, b + 1, b^2, b^2 + 1, b^2 + b, b^2 + b + 1] - """ - return self.union(X) - - def __or__(self, X): - """ - Return the union of ``self`` and ``X``. - - EXAMPLES:: - - sage: Set([2,3]) | Set([3,4]) - {2, 3, 4} - sage: Set(ZZ) | Set(QQ) - Set-theoretic union of Set of elements of Integer Ring and Set of elements of Rational Field - """ - - return self.union(X) - - def intersection(self, X): - r""" - Return the intersection of ``self`` and ``X``. - - EXAMPLES:: - - sage: X = Set(ZZ).intersection(Primes()) - sage: 4 in X - False - sage: 3 in X - True - - sage: 2/1 in X - True - - sage: X = Set(GF(9,'b')).intersection(Set(GF(27,'c'))) - sage: X - {} - - sage: X = Set(GF(9,'b')).intersection(Set(GF(27,'b'))) - sage: X - {} - """ - if isinstance(X, Set_generic): - if self is X: - return self - return Set_object_intersection(self, X) - raise TypeError("X (=%s) must be a Set"%X) - - - def difference(self, X): - r""" - Return the set difference ``self - X``. - - EXAMPLES:: - - sage: X = Set(ZZ).difference(Primes()) - sage: 4 in X - True - sage: 3 in X - False - - sage: 4/1 in X - True - - sage: X = Set(GF(9,'b')).difference(Set(GF(27,'c'))) - sage: X - {0, 1, 2, b, b + 1, b + 2, 2*b, 2*b + 1, 2*b + 2} - - sage: X = Set(GF(9,'b')).difference(Set(GF(27,'b'))) - sage: X - {0, 1, 2, b, b + 1, b + 2, 2*b, 2*b + 1, 2*b + 2} - """ - if isinstance(X, Set_generic): - if self is X: - return Set([]) - return Set_object_difference(self, X) - raise TypeError("X (=%s) must be a Set"%X) - - def symmetric_difference(self, X): - r""" - Returns the symmetric difference of ``self`` and ``X``. - - EXAMPLES:: - - sage: X = Set([1,2,3]).symmetric_difference(Set([3,4])) - sage: X - {1, 2, 4} - """ - - if isinstance(X, Set_generic): - if self is X: - return Set([]) - return Set_object_symmetric_difference(self, X) - raise TypeError("X (=%s) must be a Set"%X) - - - def __sub__(self, X): - """ - Return the difference of ``self`` and ``X``. - - EXAMPLES:: - - sage: X = Set(ZZ).difference(Primes()) - sage: Y = Set(ZZ) - Primes() - sage: X == Y - True - """ - return self.difference(X) - - def __and__(self, X): - """ - Returns the intersection of ``self`` and ``X``. - - EXAMPLES:: - - sage: Set([2,3]) & Set([3,4]) - {3} - sage: Set(ZZ) & Set(QQ) - Set-theoretic intersection of Set of elements of Integer Ring and Set of elements of Rational Field - """ - - return self.intersection(X) - - def __xor__(self, X): - """ - Returns the symmetric difference of ``self`` and ``X``. - - EXAMPLES:: - - sage: X = Set([1,2,3,4]) - sage: Y = Set([1,2]) - sage: X.symmetric_difference(Y) - {3, 4} - sage: X.__xor__(Y) - {3, 4} - """ - return self.symmetric_difference(X) - def cardinality(self): """ Return the cardinality of this set, which is either an integer or @@ -611,7 +686,7 @@ def cardinality(self): except TypeError: pass - raise NotImplementedError("computation of cardinality of %s not yet implemented"%self.__object) + raise NotImplementedError("computation of cardinality of %s not yet implemented" % self.__object) def is_empty(self): """ @@ -687,22 +762,21 @@ def object(self): """ return self.__object - def subsets(self,size=None): + def subsets(self, size=None): """ Return the :class:`Subsets` object representing the subsets of a set. If size is specified, return the subsets of that size. EXAMPLES:: - sage: X = Set([1,2,3]) + sage: X = Set([1, 2, 3]) sage: list(X.subsets()) [{}, {1}, {2}, {3}, {1, 2}, {1, 3}, {2, 3}, {1, 2, 3}] sage: list(X.subsets(2)) [{1, 2}, {1, 3}, {2, 3}] - """ from sage.combinat.subset import Subsets - return Subsets(self,size) + return Subsets(self, size) def subsets_lattice(self): """ @@ -725,22 +799,39 @@ def subsets_lattice(self): from sage.graphs.graph import DiGraph from sage.rings.integer import Integer n = self.cardinality() - # list, contains at position 0 <= i < 2^n + # list, contains at position 0 <= i < 2^n # the i-th subset of self - subset_of_index = [Set([self[i] for i in range(n) if v&(1<rel)._gobj): + if not has_symbol_or_function((rel)._gobj) and not det_ex.is_constant(): while hasattr(det_ex, 'pyobject') and isinstance(det_ex, Expression): try: det_ex = det_ex.pyobject() diff --git a/src/sage/symbolic/expression_conversions.py b/src/sage/symbolic/expression_conversions.py index d6681cb8366..63cef49e0b9 100644 --- a/src/sage/symbolic/expression_conversions.py +++ b/src/sage/symbolic/expression_conversions.py @@ -18,6 +18,7 @@ import operator as _operator from sage.rings.rational_field import QQ from sage.symbolic.ring import SR +from sage.symbolic.callable import is_CallableSymbolicExpression from sage.functions.all import exp from sage.symbolic.operators import arithmetic_operators, relation_operators, FDerivativeOperator, add_vararg, mul_vararg from sage.rings.number_field.number_field_element_quadratic import NumberFieldElement_gaussian @@ -693,6 +694,23 @@ def __init__(self): from sage.interfaces.sympy import sympy_init sympy_init() + def __call__(self, ex=None): + """ + EXAMPLES:: + + sage: from sage.symbolic.expression_conversions import SympyConverter + sage: s = SympyConverter() + sage: f(x, y) = x^2 + y^2; f + (x, y) |--> x^2 + y^2 + sage: s(f) + Lambda((x, y), x**2 + y**2) + """ + if is_CallableSymbolicExpression(ex): + from sympy import Symbol, Lambda + return Lambda(tuple(Symbol(str(arg)) for arg in ex.arguments()), + super().__call__(ex)) + return super().__call__(ex) + def pyobject(self, ex, obj): """ EXAMPLES:: diff --git a/src/sage/symbolic/function.pyx b/src/sage/symbolic/function.pyx index ff3c51c7680..125d228f646 100644 --- a/src/sage/symbolic/function.pyx +++ b/src/sage/symbolic/function.pyx @@ -129,6 +129,7 @@ from sage.libs.pynac.pynac cimport * from sage.rings.integer cimport smallInteger from sage.structure.sage_object cimport SageObject from sage.structure.element cimport Element, parent +from sage.misc.lazy_attribute import lazy_attribute from .expression cimport new_Expression_from_GEx, Expression from .ring import SR @@ -750,6 +751,24 @@ cdef class Function(SageObject): """ return self._conversions.get('sympy', self._name) + @lazy_attribute + def _sympy_(self): + """ + EXAMPLES:: + + sage: cos._sympy_() + cos + sage: _(0) + 1 + """ + f = self._sympy_init_() + import sympy + if getattr(sympy, f, None): + def return_sympy(): + return getattr(sympy, f) + return return_sympy + return NotImplemented + def _maxima_init_(self, I=None): """ EXAMPLES:: diff --git a/src/sage/symbolic/integration/external.py b/src/sage/symbolic/integration/external.py index fec628a0067..f4b8aa0f4cf 100644 --- a/src/sage/symbolic/integration/external.py +++ b/src/sage/symbolic/integration/external.py @@ -172,6 +172,20 @@ def fricas_integrator(expression, v, a=None, b=None, noPole=True): sage: integrate(1, x, algorithm="fricas") # optional - fricas x + + Check that :trac:`28630` is fixed:: + + sage: f = polylog(3, x) + sage: f.integral(x, algorithm='fricas') # optional - fricas + -x*dilog(x) - (x - 1)*log(-x + 1) + x*polylog(3, x) + x + + Check that :trac:`29043` is fixed:: + + sage: var("a c d"); f = (I*a*tan(d*x + c) + a)*sec(d*x + c)^10 + (a, c, d) + sage: ii = integrate(f, x, algorithm="fricas") # optional - fricas + sage: 1/315*(64512*I*a*e^(10*I*d*x + 10*I*c) + 53760*I*a*e^(8*I*d*x + 8*I*c) + 30720*I*a*e^(6*I*d*x + 6*I*c) + 11520*I*a*e^(4*I*d*x + 4*I*c) + 2560*I*a*e^(2*I*d*x + 2*I*c) + 256*I*a)/(d*e^(20*I*d*x + 20*I*c) + 10*d*e^(18*I*d*x + 18*I*c) + 45*d*e^(16*I*d*x + 16*I*c) + 120*d*e^(14*I*d*x + 14*I*c) + 210*d*e^(12*I*d*x + 12*I*c) + 252*d*e^(10*I*d*x + 10*I*c) + 210*d*e^(8*I*d*x + 8*I*c) + 120*d*e^(6*I*d*x + 6*I*c) + 45*d*e^(4*I*d*x + 4*I*c) + 10*d*e^(2*I*d*x + 2*I*c) + d) - ii # optional - fricas + 0 """ if not isinstance(expression, Expression): expression = SR(expression) diff --git a/src/sage/topology/simplicial_complex.py b/src/sage/topology/simplicial_complex.py index b607728e9ba..e34c53cee6f 100644 --- a/src/sage/topology/simplicial_complex.py +++ b/src/sage/topology/simplicial_complex.py @@ -148,7 +148,6 @@ sage: S == T True """ -from operator import index as PyNumber_Index # possible future directions for SimplicialComplex: # @@ -171,12 +170,11 @@ from sage.rings.rational_field import QQ from sage.structure.category_object import normalize_names from sage.misc.latex import latex -from sage.misc.misc import union from sage.matrix.constructor import matrix from sage.homology.chain_complex import ChainComplex from sage.graphs.graph import Graph from functools import reduce, total_ordering -from itertools import combinations +from itertools import combinations, chain lazy_import('sage.categories.simplicial_complexes', 'SimplicialComplexes') @@ -1005,7 +1003,7 @@ def __init__(self, Parent.__init__(self, category=category) C = None - vertex_set = () + vertices = () if from_characteristic_function is not None: from sage.combinat.subsets_hereditary import subsets_with_hereditary_property f, X = from_characteristic_function @@ -1022,8 +1020,13 @@ def __init__(self, if not isinstance(maximal_faces, (list, tuple, Simplex)): # Convert it into a list (in case it is an iterable) maximal_faces = list(maximal_faces) - if maximal_faces: - vertex_set = reduce(union, maximal_faces) + if len(maximal_faces) == 1 and isinstance(maximal_faces[0], (int, Integer)): + # list containing a single non-negative integer n; + # construct the simplicial complex with a single n-simplex as the only facet. + vertices = tuple(range(maximal_faces[0] + 1)) + maximal_faces = [vertices] + elif maximal_faces: + vertices = tuple(set(chain.from_iterable(maximal_faces))) if C is not None: self._facets = list(C.facets()) self._faces = copy(C._faces) @@ -1038,16 +1041,6 @@ def __init__(self, self.set_immutable() return - try: - # Check whether vertex_set is an integer - n = PyNumber_Index(vertex_set) - except TypeError: - pass - else: - vertex_set = range(n + 1) - - vertices = tuple(vertex_set) - gen_dict = {} for v in vertices: if name_check: @@ -2719,8 +2712,7 @@ def remove_face(self, face, check=False): self._facets.append(Simplex(-1)) # Recreate the vertex set - from sage.misc.misc import union - vertices = tuple(reduce(union, self._facets)) + vertices = set(chain.from_iterable(self._facets)) for v in self.vertices(): if v not in vertices: del self._vertex_to_index[v] diff --git a/src/sage/typeset/character_art.py b/src/sage/typeset/character_art.py index f02ac0ff408..a451fbb73b0 100644 --- a/src/sage/typeset/character_art.py +++ b/src/sage/typeset/character_art.py @@ -167,9 +167,7 @@ def __format__(self, fmt): sage: M = matrix([[1,2],[3,4]]) sage: format(ascii_art(M)) '[1 2]\n[3 4]' - sage: format(unicode_art(M)) # py2 - u'\u239b1 2\u239e\n\u239d3 4\u23a0' - sage: format(unicode_art(M)) # py3 + sage: format(unicode_art(M)) '\u239b1 2\u239e\n\u239d3 4\u23a0' """ return format(self._string_type(self), fmt) @@ -315,10 +313,13 @@ class PrependIterator(): """ def __init__(self, stack): self._stack = [iter(elems) for elems in stack] + def prepend(self, elems): self._stack.append(iter(elems)) + def __iter__(self): return self + def __next__(self): while self._stack: try: diff --git a/src/sage/typeset/symbols.py b/src/sage/typeset/symbols.py index e68f77a1923..c4f0831b8d7 100644 --- a/src/sage/typeset/symbols.py +++ b/src/sage/typeset/symbols.py @@ -154,11 +154,11 @@ def __call__(self, num_lines): return [self.top_2, self.bottom_2] elif num_lines == 3: return [self.top, self.middle, self.bottom] - elif num_lines %2 == 0: - ext = [self.extension] * ((num_lines-4) // 2) + elif num_lines % 2 == 0: + ext = [self.extension] * ((num_lines - 4) // 2) return [self.top] + ext + [self.middle_top, self.middle_bottom] + ext + [self.bottom] - else: # num_lines %2 == 1 - ext = [self.extension] * ((num_lines-3) // 2) + else: # num_lines %2 == 1 + ext = [self.extension] * ((num_lines - 3) // 2) return [self.top] + ext + [self.middle] + ext + [self.bottom] def print_to_stdout(self, num_lines): @@ -335,6 +335,3 @@ def character_art(self, num_lines): unicodedata.lookup('UPPER RIGHT OR LOWER LEFT CURLY BRACKET SECTION'), unicodedata.lookup('UPPER LEFT OR LOWER RIGHT CURLY BRACKET SECTION'), ) - - - diff --git a/src/sage/typeset/unicode_art.py b/src/sage/typeset/unicode_art.py index 9d9ad41ade9..03dd57a9965 100644 --- a/src/sage/typeset/unicode_art.py +++ b/src/sage/typeset/unicode_art.py @@ -59,9 +59,7 @@ def __unicode__(self): sage: i = var('i') sage: ua = unicode_art(sum(pi^i/factorial(i)*x^i, i, 0, oo)) - sage: unicode(ua) # py2 - u' \u03c0\u22c5x\n\u212f ' - sage: str(ua) # py3 + sage: str(ua) ' \u03c0\u22c5x\n\u212f ' """ return repr(self).decode("utf-8") diff --git a/src/sage/version.py b/src/sage/version.py index b97a07ff370..be6e001eed6 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.4.beta5' -date = '2021-07-18' -banner = 'SageMath version 9.4.beta5, Release Date: 2021-07-18' +version = '9.4.beta6' +date = '2021-07-24' +banner = 'SageMath version 9.4.beta6, Release Date: 2021-07-24' diff --git a/src/setup.cfg.m4 b/src/setup.cfg.m4 index 6dd796e8a54..60ff8b757a6 100644 --- a/src/setup.cfg.m4 +++ b/src/setup.cfg.m4 @@ -65,7 +65,6 @@ dnl From Makefile.in: DOC_DEPENDENCIES | 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 diff --git a/tox.ini b/tox.ini index 29e57dcea0c..14da3d82db0 100644 --- a/tox.ini +++ b/tox.ini @@ -141,8 +141,8 @@ setenv = maximal: SAGE_PACKAGE_LIST_ARGS=:standard: :optional: conda-environment: SAGE_PACKAGE_LIST_ARGS=_prereq # Whether to add the system packages needed for bootstrapping - EXTRA_SAGE_PACKAGES=_bootstrap - nobootstrap: EXTRA_SAGE_PACKAGES= + EXTRA_SAGE_PACKAGES_0=_bootstrap + nobootstrap: EXTRA_SAGE_PACKAGES_0= # local envs need HOME set, also Docker 19.03 needs HOME {local,docker}: HOME={envdir} # for local envs we can guess the package system if it is not provided as a factor @@ -376,6 +376,16 @@ setenv = manylinux-ppc64le: ARCH_IMAGE_SUFFIX=_ppc64le manylinux-s390x: ARCH_IMAGE_SUFFIX=_s390x # + # https://hub.docker.com/r/nvidia/cuda + # + ubuntu-nvidia-cuda: BASE_IMAGE=nvidia/cuda + centos-nvidia-cuda: BASE_IMAGE=nvidia/cuda + nvidia-cuda: ARCH_TAG_PREFIX=11.0-devel- + nvidia-cuda-11.0: ARCH_TAG_PREFIX=11.0-devel- + ubuntu-focal-nvidia-cuda: BASE_TAG=ubuntu20.04 + ubuntu-bionic-nvidia-cuda: BASE_TAG=ubuntu18.04 + ubuntu-xenial-nvidia-cuda: BASE_TAG=ubuntu16.04 + # # Resulting full image:tag name # docker: FULL_BASE_IMAGE_AND_TAG={env:ARCH_IMAGE_PREFIX:}{env:BASE_IMAGE}{env:ARCH_IMAGE_SUFFIX:}:{env:ARCH_TAG_PREFIX:}{env:BASE_TAG}{env:ARCH_TAG_SUFFIX:} @@ -447,6 +457,10 @@ 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 + llvm: CONFIG_CONFIGURE_ARGS_2=--with-system-gcc=force CC=clang CXX=clang++ + llvm: EXTRA_SAGE_PACKAGES_2=llvm + # LLVM is keg-only + homebrew-llvm: CONFIG_CONFIGURE_ARGS_2=--with-system-gcc=force CC={env:HOMEBREW}/opt/llvm/bin/clang CXX={env:HOMEBREW}/opt/llvm/bin/clang++ 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} @@ -468,6 +482,10 @@ setenv = # Resulting full configuration args, including EXTRA_CONFIGURE_ARGS from the user environment # CONFIGURE_ARGS=--enable-experimental-packages --enable-download-from-upstream-url {env:CONFIG_CONFIGURE_ARGS_ROOT:} {env:CONFIG_CONFIGURE_ARGS_1:} {env:CONFIG_CONFIGURE_ARGS_2:} {env:EXTRA_CONFIGURE_ARGS:} + # + # Resulting EXTRA_SAGE_PACKAGES + # + EXTRA_SAGE_PACKAGES={env:EXTRA_SAGE_PACKAGES_0:} {env:EXTRA_SAGE_PACKAGES_1:} {env:EXTRA_SAGE_PACKAGES_2:} # environment will be skipped if regular expression does not match against the sys.platform string platform = @@ -523,7 +541,7 @@ commands = local: bash -c 'if [ ! -d prefix -o -L prefix ]; then rm -f prefix; ln -sf {env:PREFIX:{envdir}/local} prefix; fi' ##commands = - docker: bash -c 'build/bin/write-dockerfile.sh {env:SYSTEM} "{env:SAGE_PACKAGE_LIST_ARGS:}" {env:WITH_SYSTEM_SPKG} {env:IGNORE_MISSING_SYSTEM_PACKAGES} > {envdir}/Dockerfile' + docker: bash -c 'build/bin/write-dockerfile.sh {env:SYSTEM} "{env:SAGE_PACKAGE_LIST_ARGS:}" {env:WITH_SYSTEM_SPKG} {env:IGNORE_MISSING_SYSTEM_PACKAGES} "{env:EXTRA_SAGE_PACKAGES}" > {envdir}/Dockerfile' # From https://hub.docker.com/r/multiarch/ubuntu-core/ # configure binfmt-support on the Docker host (works locally or remotely, i.e: using boot2docker) docker-{arm64,armhf}: docker run --rm --privileged multiarch/qemu-user-static:register --reset